abef0e4909d27103912caf0878e1f082637cde6c
[cdmlib.git] / cdmlib-io / src / main / java / eu / etaxonomy / cdm / io / specimen / excel / in / SpecimenCdmExcelImport.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.io.specimen.excel.in;
11
12 import java.text.ParseException;
13 import java.util.ArrayList;
14 import java.util.List;
15 import java.util.UUID;
16
17 import org.apache.commons.lang.StringUtils;
18 import org.apache.log4j.Logger;
19 import org.springframework.stereotype.Component;
20
21 import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
22 import eu.etaxonomy.cdm.api.service.config.MatchingTaxonConfigurator;
23 import eu.etaxonomy.cdm.common.CdmUtils;
24 import eu.etaxonomy.cdm.io.common.ICdmIO;
25 import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;
26 import eu.etaxonomy.cdm.io.excel.common.ExcelRowBase.PostfixTerm;
27 import eu.etaxonomy.cdm.io.excel.common.ExcelTaxonOrSpecimenImportBase;
28 import eu.etaxonomy.cdm.io.specimen.excel.in.SpecimenRow.DeterminationLight;
29 import eu.etaxonomy.cdm.model.agent.AgentBase;
30 import eu.etaxonomy.cdm.model.agent.Person;
31 import eu.etaxonomy.cdm.model.agent.Team;
32 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
33 import eu.etaxonomy.cdm.model.common.Annotation;
34 import eu.etaxonomy.cdm.model.common.AnnotationType;
35 import eu.etaxonomy.cdm.model.common.CdmBase;
36 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
37 import eu.etaxonomy.cdm.model.common.Language;
38 import eu.etaxonomy.cdm.model.common.TimePeriod;
39 import eu.etaxonomy.cdm.model.description.Feature;
40 import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
41 import eu.etaxonomy.cdm.model.description.TaxonDescription;
42 import eu.etaxonomy.cdm.model.location.Country;
43 import eu.etaxonomy.cdm.model.location.NamedArea;
44 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
45 import eu.etaxonomy.cdm.model.location.NamedAreaType;
46 import eu.etaxonomy.cdm.model.location.ReferenceSystem;
47 import eu.etaxonomy.cdm.model.name.BotanicalName;
48 import eu.etaxonomy.cdm.model.name.INonViralName;
49 import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
50 import eu.etaxonomy.cdm.model.name.NonViralName;
51 import eu.etaxonomy.cdm.model.name.Rank;
52 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
53 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
54 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
55 import eu.etaxonomy.cdm.model.name.ZoologicalName;
56 import eu.etaxonomy.cdm.model.occurrence.Collection;
57 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
58 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
59 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
60 import eu.etaxonomy.cdm.model.reference.Reference;
61 import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
62 import eu.etaxonomy.cdm.model.taxon.Taxon;
63 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
64 import eu.etaxonomy.cdm.persistence.query.MatchMode;
65 import eu.etaxonomy.cdm.strategy.exceptions.StringNotParsableException;
66 import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
67 import eu.etaxonomy.cdm.strategy.parser.INonViralNameParser;
68 import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
69 import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
70
71 /**
72 * @author a.mueller
73 * @created 10.05.2011
74 * @version 1.0
75 */
76 @Component
77 public class SpecimenCdmExcelImport extends ExcelTaxonOrSpecimenImportBase<SpecimenCdmExcelImportState, SpecimenRow> implements ICdmIO<SpecimenCdmExcelImportState> {
78 private static final Logger logger = Logger.getLogger(SpecimenCdmExcelImport.class);
79
80 private static final String WORKSHEET_NAME = "Specimen";
81
82 private static final String BASIS_OF_RECORD_COLUMN = "(?i)(BasisOfRecord)";
83 private static final String COUNTRY_COLUMN = "(?i)(Country)";
84 private static final String AREA_COLUMN = "(?i)(Area)";
85 private static final String ISO_COUNTRY_COLUMN = "(?i)(ISOCountry|CountryCode)";
86 private static final String LOCALITY_COLUMN = "(?i)(Locality)";
87 private static final String ALTITUDE_COLUMN = "(?i)(AbsoluteElevation|Altitude)";
88 private static final String ALTITUDE_MAX_COLUMN = "(?i)(AbsoluteElevation|Altitude)Max(imum)?";
89 private static final String COLLECTION_DATE_COLUMN = "(?i)(CollectionDate)";
90 private static final String COLLECTION_DATE_END_COLUMN = "(?i)(CollectionDateEnd)";
91 private static final String COLLECTOR_COLUMN = "(?i)(Collector)";
92 private static final String COLLECTORS_COLUMN = "(?i)(Collectors)";
93 private static final String PRIMARY_COLLECTOR_COLUMN = "(?i)(PrimaryCollector)";
94 private static final String LONGITUDE_COLUMN = "(?i)(Longitude)";
95 private static final String LATITUDE_COLUMN = "(?i)(Latitude)";
96 private static final String REFERENCE_SYSTEM_COLUMN = "(?i)(ReferenceSystem)";
97 private static final String ERROR_RADIUS_COLUMN = "(?i)(ErrorRadius)";
98
99
100 private static final String COLLECTORS_NUMBER_COLUMN = "(?i)((Collectors|Field)Number)";
101 private static final String ECOLOGY_COLUMN = "(?i)(Ecology|Habitat)";
102 private static final String PLANT_DESCRIPTION_COLUMN = "(?i)(PlantDescription)";
103 private static final String FIELD_NOTES_COLUMN = "(?i)(FieldNotes)";
104 private static final String SEX_COLUMN = "(?i)(Sex)";
105
106
107 private static final String ACCESSION_NUMBER_COLUMN = "(?i)(AccessionNumber)";
108 private static final String BARCODE_COLUMN = "(?i)(Barcode)";
109 private static final String COLLECTION_CODE_COLUMN = "(?i)(CollectionCode)";
110 private static final String COLLECTION_COLUMN = "(?i)(Collection)";
111 private static final String UNIT_NOTES_COLUMN = "(?i)((Unit)?Notes)";
112
113
114 private static final String TYPE_CATEGORY_COLUMN = "(?i)(TypeCategory)";
115 private static final String TYPIFIED_NAME_COLUMN = "(?i)(TypifiedName|TypeOf)";
116
117
118 private static final String SOURCE_COLUMN = "(?i)(Source)";
119 private static final String ID_IN_SOURCE_COLUMN = "(?i)(IdInSource)";
120
121
122 private static final String DETERMINATION_AUTHOR_COLUMN = "(?i)(Author)";
123 private static final String DETERMINATION_MODIFIER_COLUMN = "(?i)(DeterminationModifier)";
124 private static final String DETERMINED_BY_COLUMN = "(?i)(DeterminationBy)";
125 private static final String DETERMINED_WHEN_COLUMN = "(?i)(Det(ermination)?When)";
126 private static final String DETERMINATION_NOTES_COLUMN = "(?i)(DeterminationNote)";
127 private static final String EXTENSION_COLUMN = "(?i)(Ext(ension)?)";
128
129
130 public SpecimenCdmExcelImport() {
131 super();
132 }
133
134
135
136
137 @Override
138 protected void analyzeSingleValue(KeyValue keyValue, SpecimenCdmExcelImportState state) {
139 SpecimenRow row = state.getCurrentRow();
140 String value = keyValue.value;
141 if(keyValue.key.matches(BASIS_OF_RECORD_COLUMN)) {
142 row.setBasisOfRecord(value);
143 } else if(keyValue.key.matches(COUNTRY_COLUMN)) {
144 row.setCountry(value);
145 } else if(keyValue.key.matches(ISO_COUNTRY_COLUMN)) {
146 row.setIsoCountry(value);
147 } else if(keyValue.key.matches(LOCALITY_COLUMN)) {
148 row.setLocality(value);
149 } else if(keyValue.key.matches(FIELD_NOTES_COLUMN)) {
150 row.setLocality(value);
151 } else if(keyValue.key.matches(ALTITUDE_COLUMN)) {
152 row.setAltitude(value);
153 } else if(keyValue.key.matches(ALTITUDE_MAX_COLUMN)) {
154 row.setAltitudeMax(value);
155 } else if(keyValue.key.matches(COLLECTOR_COLUMN)) {
156 row.putCollector(keyValue.index, value);
157 } else if(keyValue.key.matches(PRIMARY_COLLECTOR_COLUMN)) {
158 row.setPrimaryCollector(value);
159 } else if(keyValue.key.matches(ECOLOGY_COLUMN)) {
160 row.setEcology(value);
161 } else if(keyValue.key.matches(PLANT_DESCRIPTION_COLUMN)) {
162 row.setPlantDescription(value);
163 } else if(keyValue.key.matches(SEX_COLUMN)) {
164 row.setSex(value);
165 } else if(keyValue.key.matches(COLLECTION_DATE_COLUMN)) {
166 row.setCollectingDate(value);
167 } else if(keyValue.key.matches(COLLECTION_DATE_END_COLUMN)) {
168 row.setCollectingDateEnd(value);
169 } else if(keyValue.key.matches(COLLECTORS_COLUMN)) {
170 row.setCollectors(value);
171 } else if(keyValue.key.matches(COLLECTOR_COLUMN)) {
172 row.putCollector(keyValue.index, value);
173 } else if(keyValue.key.matches(COLLECTORS_NUMBER_COLUMN)) {
174 row.setCollectorsNumber(value);
175 } else if(keyValue.key.matches(LONGITUDE_COLUMN)) {
176 row.setLongitude(value);
177 } else if(keyValue.key.matches(LATITUDE_COLUMN)) {
178 row.setLatitude(value);
179 } else if(keyValue.key.matches(REFERENCE_SYSTEM_COLUMN)) {
180 row.setReferenceSystem(value);
181 } else if(keyValue.key.matches(ERROR_RADIUS_COLUMN)) {
182 row.setErrorRadius(value);
183 } else if(keyValue.key.matches(AREA_COLUMN)) {
184 if (keyValue.postfix != null){
185 row.addLeveledArea(keyValue.postfix, value);
186 }else{
187 logger.warn("Not yet implemented");
188 }
189 } else if(keyValue.key.matches(LANGUAGE)) {
190 row.setLanguage(value);
191
192
193 } else if(keyValue.key.matches(ACCESSION_NUMBER_COLUMN)) {
194 row.setAccessionNumber(value);
195 } else if(keyValue.key.matches(BARCODE_COLUMN)) {
196 row.setBarcode(value);
197 } else if(keyValue.key.matches(UNIT_NOTES_COLUMN)) {
198 row.putUnitNote(keyValue.index, value);
199
200
201 } else if(keyValue.key.matches(FAMILY_COLUMN)) {
202 row.putDeterminationFamily(keyValue.index, value);
203 } else if(keyValue.key.matches(GENUS_COLUMN)) {
204 row.putDeterminationGenus(keyValue.index, value);
205 } else if(keyValue.key.matches(SPECIFIC_EPITHET_COLUMN)) {
206 row.putDeterminationSpeciesEpi(keyValue.index, value);
207 } else if(keyValue.key.matches(INFRASPECIFIC_EPITHET_COLUMN)) {
208 row.putDeterminationInfraSpeciesEpi(keyValue.index, value);
209 } else if(keyValue.key.matches(RANK_COLUMN)) {
210 row.putDeterminationRank(keyValue.index, value);
211 } else if(keyValue.key.matches(TAXON_UUID_COLUMN)) {
212 row.putDeterminationTaxonUuid(keyValue.index, value);
213 } else if(keyValue.key.matches(FULL_NAME_COLUMN)) {
214 row.putDeterminationFullName(keyValue.index, value);
215 } else if(keyValue.key.matches(DETERMINATION_AUTHOR_COLUMN)) {
216 row.putDeterminationAuthor(keyValue.index, value);
217 } else if(keyValue.key.matches(DETERMINATION_MODIFIER_COLUMN)) {
218 row.putDeterminationDeterminationModifier(keyValue.index, value);
219 } else if(keyValue.key.matches(DETERMINATION_NOTES_COLUMN)) {
220 row.putDeterminationDeterminationNotes(keyValue.index, value);
221 } else if(keyValue.key.matches(DETERMINED_BY_COLUMN)) {
222 row.putDeterminationDeterminedBy(keyValue.index, value);
223 } else if(keyValue.key.matches(DETERMINED_WHEN_COLUMN)) {
224 row.putDeterminationDeterminedWhen(keyValue.index, value);
225
226 } else if(keyValue.key.matches(COLLECTION_CODE_COLUMN)) {
227 row.setCollectionCode(value);
228 } else if(keyValue.key.matches(COLLECTION_COLUMN)) {
229 row.setCollection(value);
230
231 } else if(keyValue.key.matches(TYPE_CATEGORY_COLUMN)) {
232 row.putTypeCategory(keyValue.index, getSpecimenTypeStatus(state, value));
233 } else if(keyValue.key.matches(TYPIFIED_NAME_COLUMN)) {
234 row.putTypifiedName(keyValue.index, getTaxonName(state, value));
235
236
237 } else if(keyValue.key.matches(SOURCE_COLUMN)) {
238 row.putSourceReference(keyValue.index, getOrMakeReference(state, value) );
239 } else if(keyValue.key.matches(ID_IN_SOURCE_COLUMN)) {
240 row.putIdInSource(keyValue.index, value);
241 } else if(keyValue.key.matches(EXTENSION_COLUMN)) {
242 if (keyValue.postfix != null){
243 row.addExtension(keyValue.postfix, value);
244 }else{
245 logger.warn("Extension without postfix not yet implemented");
246 }
247
248 }else {
249 state.setUnsuccessfull();
250 logger.error("Unexpected column header " + keyValue.originalKey);
251 }
252
253 return;
254 }
255
256
257 @Override
258 protected void firstPass(SpecimenCdmExcelImportState state) {
259 SpecimenRow row = state.getCurrentRow();
260
261 //basis of record
262 SpecimenOrObservationType type = SpecimenOrObservationType.valueOf2(row.getBasisOfRecord());
263 if (type == null){
264 String message = "%s is not a valid BasisOfRecord. 'Unknown' is used instead in line %d.";
265 message = String.format(message, row.getBasisOfRecord(), state.getCurrentLine());
266 logger.warn(message);
267 type = SpecimenOrObservationType.DerivedUnit;
268 }
269 DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(type);
270
271
272 Language lang = Language.DEFAULT();
273 if (StringUtils.isNotBlank(row.getLanguage())){
274 Language langIso = getTermService().getLanguageByIso(row.getLanguage());
275 if (langIso == null){
276 String message = "Language could not be recognized: %s. Use default language instead. Line %d.";
277 message = String.format(message, langIso, state.getCurrentLine());
278 }else{
279 lang = langIso;
280 }
281 }
282
283 //country
284 handleCountry(facade, row, state);
285 handleAreas(facade,row, state);
286
287 facade.setGatheringPeriod(getTimePeriod(row.getCollectingDate(), row.getCollectingDateEnd()));
288 facade.setLocality(row.getLocality());
289 facade.setFieldNotes(row.getFieldNotes());
290 facade.setFieldNumber(row.getCollectorsNumber());
291 facade.setEcology(row.getEcology(), lang);
292 facade.setPlantDescription(row.getPlantDescription(), lang);
293 // facade.setSex(row.get)
294 handleExactLocation(facade, row, state);
295 facade.setCollector(getOrMakeAgent(state, row.getCollectors()));
296 facade.setPrimaryCollector(getOrMakePrimaryCollector(facade, row.getPrimaryCollector(), state));
297 handleAbsoluteElevation(facade, row, state);
298
299 //derivedUnit
300 facade.setBarcode(row.getBarcode());
301 facade.setAccessionNumber(row.getAccessionNumber());
302 facade.setCollection(getOrMakeCollection(state, row.getCollectionCode(), row.getCollection()));
303 for (IdentifiableSource source : row.getSources()){
304 facade.addSource(source);
305 }
306 for (SpecimenTypeDesignation designation : row.getTypeDesignations()){
307 logger.warn("FIXME"); //FIXME
308 // facade.innerDerivedUnit().addSpecimenTypeDesignation(designation);
309 }
310 handleDeterminations(state, row, facade);
311 handleExtensions(facade.innerDerivedUnit(),row, state);
312 for (String note : row.getUnitNotes()){
313 Annotation annotation = Annotation.NewInstance(note, AnnotationType.EDITORIAL(), Language.DEFAULT());
314 facade.addAnnotation(annotation);
315 }
316
317 //save
318 getOccurrenceService().save(facade.innerDerivedUnit());
319 return;
320 }
321
322 private void handleAbsoluteElevation(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
323 //altitude
324
325 try {
326 String altitude = row.getAltitude();
327 if (StringUtils.isBlank(altitude)){
328 return;
329 }
330 // if (altitude.endsWith(".0")){
331 // altitude = altitude.substring(0, altitude.length() -2);
332 // }
333 int value = Integer.valueOf(altitude);
334 facade.setAbsoluteElevation(value);
335 } catch (NumberFormatException e) {
336 String message = "Absolute elevation / altitude '%s' is not an integer number in line %d";
337 message = String.format(message, row.getAltitude(), state.getCurrentLine());
338 logger.warn(message);
339 return;
340 }
341
342 //max
343
344 try {
345 String max = row.getAltitudeMax();
346 if (StringUtils.isBlank(max)){
347 return;
348 }
349 // if (max.endsWith(".0")){
350 // max = max.substring(0, max.length() -2);
351 // }
352 int value = Integer.valueOf(max);
353 //TODO avoid unequal distance
354 int min = facade.getAbsoluteElevation();
355 if ( (value - min) % 2 == 1 ){
356 String message = "Altitude min-max difference ist not equal. Max reduced by 1 in line %d";
357 message = String.format(message, state.getCurrentLine());
358 logger.warn(message);
359 value--;
360 }
361 facade.setAbsoluteElevationRange(min, value);
362 } catch (NumberFormatException e) {
363 String message = "Absolute elevation / Altitude maximum '%s' is not an integer number in line %d";
364 message = String.format(message, row.getAltitudeMax(), state.getCurrentLine());
365 logger.warn(message);
366 return;
367 }catch (Exception e){
368 String message = "Error occurred when trying to write Absolute elevation / Altitude maximum '%s' in line %d";
369 message = String.format(message, row.getAltitudeMax(), state.getCurrentLine());
370 logger.warn(message);
371 return;
372
373 }
374
375
376 }
377
378 private void handleAreas(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
379 List<PostfixTerm> areas = row.getLeveledAreas();
380
381 for (PostfixTerm lArea : areas){
382 String description = lArea.term;
383 String abbrev = lArea.term;
384 NamedAreaType type = null;
385 String key = lArea.postfix + "_" + lArea.term;
386 UUID areaUuid = state.getArea(key);
387 NamedAreaLevel level = state.getPostfixLevel(lArea.postfix);
388
389 TermMatchMode matchMode = state.getConfig().getAreaMatchMode();
390 NamedArea area = getNamedArea(state, areaUuid, lArea.term, description, abbrev, type, level, null, matchMode);
391 facade.addCollectingArea(area);
392 if (areaUuid == null){
393 state.putArea(key, area.getUuid());
394 }
395 }
396 }
397
398
399 /**
400 * @param state
401 * @param row
402 * @param facade
403 */
404 private void handleDeterminations(SpecimenCdmExcelImportState state,SpecimenRow row, DerivedUnitFacade facade) {
405 boolean isFirstDetermination = true;
406 DeterminationLight commonDetermination = row.getCommonDetermination();
407 Taxon commonTaxon = null;
408 TaxonNameBase<?,?> commonName = null;
409
410 boolean hasCommonTaxonInfo = (commonDetermination == null) ? false : commonDetermination.hasTaxonInformation();
411 if (hasCommonTaxonInfo && commonDetermination != null){
412 TaxonBase<?> taxonBase = null;
413 if (StringUtils.isNotBlank(commonDetermination.taxonUuid)){
414 UUID taxonUuid = UUID.fromString(commonDetermination.taxonUuid);
415 taxonBase = getTaxonService().find(taxonUuid);
416 if (taxonBase == null){
417 String message = "Taxon for uuid %s not found in line %d.";
418 message = String.format(message, taxonUuid.toString(), state.getCurrentLine());
419 logger.warn(message);
420 }
421 }else{
422 taxonBase = findBestMatchingTaxon(state, commonDetermination, state.getConfig().isCreateTaxonIfNotExists());
423 }
424 commonTaxon = getAcceptedTaxon(taxonBase);
425 if (taxonBase != null){
426 commonName = taxonBase.getName();
427 }else{
428 commonTaxon = createTaxonFromDetermination(state, commonDetermination);
429 commonName = commonTaxon.getName();
430 }
431 }
432
433
434 for (DeterminationLight determinationLight : row.getDetermination()){
435 Taxon taxon;
436 if (! hasCommonTaxonInfo){
437 taxon = findBestMatchingTaxon(state, determinationLight, state.getConfig().isCreateTaxonIfNotExists());
438 }else{
439 taxon = commonTaxon;
440 }
441 if (taxon != null){
442 getTaxonService().saveOrUpdate(taxon);
443 if (state.getConfig().isMakeIndividualAssociations() && taxon != null){
444 IndividualsAssociation indivAssociciation = IndividualsAssociation.NewInstance();
445 DerivedUnit du = facade.innerDerivedUnit();
446 indivAssociciation.setAssociatedSpecimenOrObservation(du);
447 getTaxonDescription(taxon).addElement(indivAssociciation);
448 Feature feature = Feature.INDIVIDUALS_ASSOCIATION();
449 if (facade.getType().isPreservedSpecimen()){
450 feature = Feature.SPECIMEN();
451 }else if (facade.getType().isFeatureObservation()){
452 feature = Feature.OBSERVATION();
453 }
454 if (state.getConfig().isUseMaterialsExaminedForIndividualsAssociations()){
455 feature = Feature.MATERIALS_EXAMINED();
456 }
457
458 indivAssociciation.setFeature(feature);
459 }
460 if (state.getConfig().isDeterminationsAreDeterminationEvent()){
461 DeterminationEvent detEvent = makeDeterminationEvent(state, determinationLight, taxon);
462 detEvent.setPreferredFlag(isFirstDetermination);
463 facade.addDetermination(detEvent);
464 }
465 }
466
467 if (isFirstDetermination && state.getConfig().isFirstDeterminationIsStoredUnder()){
468 TaxonNameBase<?,?> name;
469
470 if (!hasCommonTaxonInfo){
471 name = findBestMatchingName(state, determinationLight);
472 }else{
473 if (commonName == null){
474 commonName = findBestMatchingName(state, commonDetermination);
475 }
476 name = commonName;
477 }
478 if (name != null){
479 facade.setStoredUnder(name);
480 }
481 }
482 isFirstDetermination = false;
483 }
484 }
485
486 private Taxon createTaxonFromDetermination( SpecimenCdmExcelImportState state, DeterminationLight commonDetermination) {
487
488 //rank
489 Rank rank;
490 try {
491 rank = StringUtils.isBlank(commonDetermination.rank) ? null : Rank.getRankByNameOrIdInVoc(commonDetermination.rank, true);
492 } catch (UnknownCdmTypeException e) {
493 rank = null;
494 }
495
496 //name
497 INonViralName name;
498 INonViralNameParser parser = NonViralNameParserImpl.NewInstance();
499 NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
500 if (StringUtils.isNotBlank(commonDetermination.fullName)){
501 name = parser.parseFullName(commonDetermination.fullName, nc, rank);
502 if (StringUtils.isBlank(name.getAuthorshipCache()) && StringUtils.isNotBlank(commonDetermination.author)){
503 setAuthorship(name, commonDetermination.author, parser);
504 }
505 }else{
506 if (nc != null){
507 name = nc.getNewTaxonNameInstance(rank);
508 }else{
509 name = NonViralName.NewInstance(rank);
510 }
511 if (StringUtils.isNotBlank(commonDetermination.genus)){
512 name.setGenusOrUninomial(commonDetermination.genus);
513 }
514 if (StringUtils.isNotBlank(commonDetermination.speciesEpi)){
515 name.setSpecificEpithet(commonDetermination.speciesEpi);
516 }
517 if (StringUtils.isNotBlank(commonDetermination.infraSpeciesEpi)){
518 name.setInfraSpecificEpithet(commonDetermination.infraSpeciesEpi);
519 }
520 if (StringUtils.isNotBlank(commonDetermination.author)){
521 setAuthorship(name, commonDetermination.author, parser);
522 }
523 //guess rank if null
524 if (name.getRank() == null){
525 if (name.getInfraGenericEpithet() != null && name.getSpecificEpithet() == null){
526 name.setRank(Rank.INFRAGENERICTAXON());
527 }else if (name.getSpecificEpithet() != null && name.getInfraSpecificEpithet() == null){
528 name.setRank(Rank.SPECIES());
529 }else if (name.getInfraSpecificEpithet() != null){
530 name.setRank(Rank.INFRASPECIFICTAXON());
531 }
532
533 }
534
535 }
536 //sec
537 Reference sec = null;
538 if (StringUtils.isNotBlank(commonDetermination.determinedBy)){
539 sec = ReferenceFactory.newGeneric();
540 TeamOrPersonBase<?> determinedBy;
541 BotanicalName dummyName = BotanicalName.NewInstance(Rank.SPECIES());
542 try {
543 parser.parseAuthors(dummyName, commonDetermination.determinedBy);
544 determinedBy = dummyName.getCombinationAuthorship();
545 } catch (StringNotParsableException e) {
546 determinedBy = Team.NewTitledInstance(commonDetermination.determinedBy, commonDetermination.determinedBy);
547 }
548 sec.setAuthorship(determinedBy);
549 }
550
551 //taxon
552 Taxon taxon = Taxon.NewInstance(name, sec);
553
554 if (StringUtils.isNotBlank(commonDetermination.family)){
555 if (name.getRank() == null || name.getRank().isLower(Rank.FAMILY()) ){
556 logger.warn("Family taxon could not be created");
557 }
558 }
559
560 //return
561 return taxon;
562
563 }
564
565
566
567
568 private void setAuthorship(INonViralName name, String author, INonViralNameParser<NonViralName> parser) {
569 if (name instanceof BotanicalName || name instanceof ZoologicalName){
570 try {
571 parser.parseAuthors(name, author);
572 } catch (StringNotParsableException e) {
573 name.setAuthorshipCache(author);
574 }
575 }else{
576 name.setAuthorshipCache(author);
577 }
578 }
579
580
581
582 /**
583 * This method tries to find the best matching taxon depending on the import configuration,
584 * the taxon name information and the concept information available.
585 *
586 *
587 * @param state
588 * @param determinationLight
589 * @param createIfNotExists
590 * @return
591 */
592 private Taxon findBestMatchingTaxon(SpecimenCdmExcelImportState state, DeterminationLight determinationLight, boolean createIfNotExists) {
593 NonViralName<?> name = makeTaxonName(state, determinationLight);
594
595 String titleCache = makeSearchNameTitleCache(state, determinationLight, name);
596
597 if (! StringUtils.isBlank(titleCache)){
598 MatchingTaxonConfigurator matchConfigurator = MatchingTaxonConfigurator.NewInstance();
599 matchConfigurator.setTaxonNameTitle(titleCache);
600 matchConfigurator.setIncludeSynonyms(false);
601 Taxon taxon = getTaxonService().findBestMatchingTaxon(matchConfigurator);
602
603 if(taxon == null && createIfNotExists){
604 logger.info("creating new Taxon from TaxonName '" + titleCache+"'");
605 UUID secUuid = null; //TODO
606 Reference sec = null;
607 if (secUuid != null){
608 sec = getReferenceService().find(secUuid);
609 }
610 taxon = Taxon.NewInstance(name, sec);
611 }else if (taxon == null){
612 String message = "Taxon '%s' not found in line %d";
613 message = String.format(message, titleCache, state.getCurrentLine());
614 logger.warn(message);
615 }
616 return taxon;
617 }else {
618 return null;
619 }
620 }
621
622 /**
623 * @param state
624 * @param determinationLight
625 * @param name
626 * @return
627 */
628 private String makeSearchNameTitleCache(SpecimenCdmExcelImportState state, DeterminationLight determinationLight,
629 NonViralName<?> name) {
630 String titleCache = determinationLight.fullName;
631 if (! state.getConfig().isPreferNameCache() || StringUtils.isBlank(titleCache) ){
632 String computedTitleCache = name.getTitleCache();
633 if (StringUtils.isNotBlank(computedTitleCache)){
634 titleCache = computedTitleCache;
635 }
636
637 }
638 return titleCache;
639 }
640
641 /**
642 * @param state
643 * @param determinationLight
644 * @return
645 */
646 private NonViralName<?> makeTaxonName(SpecimenCdmExcelImportState state, DeterminationLight determinationLight) {
647 NonViralName<?> name = NonViralName.NewInstance(null);
648 NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
649 if (nc != null){
650 name = (NonViralName<?>)nc.getNewTaxonNameInstance(null);
651 }
652 name.setGenusOrUninomial(determinationLight.genus);
653 name.setSpecificEpithet(determinationLight.speciesEpi);
654 name.setInfraSpecificEpithet(determinationLight.infraSpeciesEpi);
655
656 //FIXME bracketAuthors and teams not yet implemented!!!
657 List<String> authors = new ArrayList<String>();
658 if (StringUtils.isNotBlank(determinationLight.author)){
659 authors.add(determinationLight.author);
660 }
661 TeamOrPersonBase<?> agent = getOrMakeAgent(state, authors);
662 name.setCombinationAuthorship(agent);
663
664 try {
665 if (StringUtils.isNotBlank(determinationLight.rank) ){
666 name.setRank(Rank.getRankByNameOrIdInVoc(determinationLight.rank, nc, true));
667 }
668 } catch (UnknownCdmTypeException e) {
669 String message = "Rank not found: %s: ";
670 message = String.format(message, determinationLight.rank);
671 logger.warn(message);
672 }
673 if (StringUtils.isBlank(name.getInfraSpecificEpithet()) && StringUtils.isNotBlank(name.getSpecificEpithet() )){
674 name.setRank(Rank.SPECIES());
675 }
676 if (StringUtils.isBlank(name.getSpecificEpithet()) && StringUtils.isNotBlank(name.getGenusOrUninomial() )){
677 name.setRank(Rank.SPECIES());
678 }
679 if (StringUtils.isBlank(name.getTitleCache())){
680 //TODO test
681 name.setTitleCache(determinationLight.fullName, true);
682 }
683 return name;
684 }
685
686 private TaxonNameBase findBestMatchingName(SpecimenCdmExcelImportState state, DeterminationLight determinationLight) {
687
688 NonViralName<?> name = makeTaxonName(state, determinationLight);
689 String titleCache = makeSearchNameTitleCache(state, determinationLight, name);
690
691 //TODO
692 List<TaxonNameBase> matchingNames = getNameService().findByName(null, titleCache, MatchMode.EXACT, null, null, null, null, null).getRecords();
693 if (matchingNames.size() > 0){
694 return matchingNames.get(0);
695 } else if (matchingNames.size() > 0){
696 logger.warn("Get best matching taxon name not yet fully implemeted for specimen import");
697 return matchingNames.get(0);
698 }else{
699 return null;
700 }
701
702 }
703
704
705 private DeterminationEvent makeDeterminationEvent(SpecimenCdmExcelImportState state, DeterminationLight determination, Taxon taxon) {
706 DeterminationEvent event = DeterminationEvent.NewInstance();
707 //taxon
708 event.setTaxon(taxon);
709
710 //date
711 TimePeriod date = TimePeriodParser.parseString(determination.determinedWhen);
712 event.setTimeperiod(date);
713 //by
714 //FIXME bracketAuthors and teams not yet implemented!!!
715 List<String> authors = new ArrayList<String>();
716 if (StringUtils.isNotBlank(determination.determinedBy)){
717 authors.add(determination.determinedBy);
718 }
719 TeamOrPersonBase<?> actor = getOrMakeAgent(state, authors);
720 TeamOrPersonBase<?> secAuthor = taxon.getSec() == null ? null : taxon.getSec().getAuthorship();
721 if (actor != null && secAuthor != null && secAuthor.getTitleCache().equals(actor.getTitleCache()) && secAuthor.getNomenclaturalTitle().equals(actor.getNomenclaturalTitle())) {
722 actor = secAuthor;
723 }
724
725 event.setActor(actor);
726
727 //TODO
728 if (StringUtils.isNotBlank(determination.modifier)){
729 logger.warn("DeterminationModifiers not yet implemented for specimen import");
730 }
731 // DeterminationModifier modifier = DeterminationModifier.NewInstance(term, label, labelAbbrev);
732 // determination.modifier;
733 //notes
734 if (StringUtils.isNotEmpty(determination.notes)){
735 Annotation annotation = Annotation.NewInstance(determination.notes, AnnotationType.EDITORIAL(), Language.DEFAULT());
736 event.addAnnotation(annotation);
737 }
738
739 return event;
740 }
741
742 private TaxonDescription getTaxonDescription(Taxon taxon) {
743 TaxonDescription desc = this.getTaxonDescription(taxon, ! IMAGE_GALLERY, CREATE);
744 return desc;
745 }
746
747 private TeamOrPersonBase<?> getOrMakeAgent(SpecimenCdmExcelImportState state, List<String> agents) {
748 if (agents.size() == 0){
749 return null;
750 }else if (agents.size() == 1){
751 return getOrMakePerson(state, agents.get(0));
752 }else{
753 return getOrMakeTeam(state, agents);
754 }
755 }
756
757 private Person getOrMakePrimaryCollector(DerivedUnitFacade facade, String primaryCollector, SpecimenCdmExcelImportState state) {
758 if (StringUtils.isBlank(primaryCollector)){
759 return null;
760 }
761 AgentBase<?> collector = facade.getCollector();
762 List<Person> collectors = new ArrayList<Person>();
763 if (collector.isInstanceOf(Team.class) ){
764 Team team = CdmBase.deproxy(collector, Team.class);
765 collectors.addAll(team.getTeamMembers());
766 }else if (collector.isInstanceOf(Person.class)){
767 collectors.add(CdmBase.deproxy(collector, Person.class));
768 }else{
769 throw new IllegalStateException("Unknown subclass of agentbase: " + collector.getClass().getName() );
770 }
771 for (Person person :collectors){
772 if (primaryCollector.equalsIgnoreCase(person.getTitleCache())){
773 return person;
774 }
775 if (primaryCollector.equalsIgnoreCase(person.getNomenclaturalTitle())){
776 return person;
777 }
778 }
779 String message = "Primary Agent '%s' could not be determined in collector(s) in line %d";
780 message = String.format(message, primaryCollector, state.getCurrentLine());
781 logger.warn(message);
782 return null;
783 }
784
785 private Team getOrMakeTeam(SpecimenCdmExcelImportState state, List<String> agents) {
786 String key = CdmUtils.concat("_", agents.toArray(new String[0]));
787
788 Team result = state.getTeam(key);
789 if (result == null){
790 result = Team.NewInstance();
791 for (String member : agents){
792 Person person = getOrMakePerson(state, member);
793 result.addTeamMember(person);
794 }
795 state.putTeam(key, result);
796 }
797 return result;
798 }
799
800 private Person getOrMakePerson(SpecimenCdmExcelImportState state, String value) {
801 Person result = state.getPerson(value);
802 if (result == null){
803 result = Person.NewInstance();
804 result.setTitleCache(value, true);
805 state.putPerson(value, result);
806 }
807 return result;
808 }
809
810 private Reference getOrMakeReference(SpecimenCdmExcelImportState state, String value) {
811 Reference result = state.getReference(value);
812 if (result == null){
813 result = ReferenceFactory.newGeneric();
814 result.setTitleCache(value, true);
815 state.putReference(value, result);
816 }
817 return result;
818 }
819
820
821
822 private Collection getOrMakeCollection(SpecimenCdmExcelImportState state, String collectionCode, String collectionString) {
823 Collection result = state.getCollection(collectionCode);
824 if (result == null){
825 result = Collection.NewInstance();
826 result.setCode(collectionCode);
827 result.setName(collectionString);
828 state.putCollection(collectionCode, result);
829 }
830 return result;
831 }
832
833
834 private TaxonNameBase<?, ?> getTaxonName(SpecimenCdmExcelImportState state, String name) {
835 TaxonNameBase<?,?> result = null;
836 result = state.getName(name);
837 if (result != null){
838 return result;
839 }
840 List<TaxonNameBase> list = getNameService().findByTitle(null, name, null, null, null, null, null, null).getRecords();
841 //TODO better strategy to find best name, e.g. depending on the classification it is used in
842 if (! list.isEmpty()){
843 result = list.get(0);
844 }
845 if (result == null){
846 NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
847 NomenclaturalCode code = state.getConfig().getNomenclaturalCode();
848 result = parser.parseFullName(name, code, null);
849
850 }
851 if (result != null){
852 state.putName(name, result);
853 }
854 return result;
855 }
856
857 private SpecimenTypeDesignationStatus getSpecimenTypeStatus(SpecimenCdmExcelImportState state, String key) {
858 SpecimenTypeDesignationStatus result = null;
859 try {
860 result = state.getTransformer().getSpecimenTypeDesignationStatusByKey(key);
861 if (result == null){
862 String message = "Type status not recognized for %s in line %d";
863 message = String.format(message, key, state.getCurrentLine());
864 logger.warn(message);
865 }
866 return result;
867 } catch (UndefinedTransformerMethodException e) {
868 throw new RuntimeException("getSpecimenTypeDesignationStatusByKey not yet implemented");
869 }
870
871
872 }
873
874
875 private void handleExactLocation(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
876
877 //reference system
878 ReferenceSystem refSys = null;
879 if (StringUtils.isNotBlank(row.getReferenceSystem())){
880 String strRefSys = row.getReferenceSystem().trim().replaceAll("\\s", "");
881 UUID refUuid;
882 try {
883 refSys = state.getTransformer().getReferenceSystemByKey(strRefSys);
884 if (refSys == null){
885 //TODO we still need user defined Reference Systems here
886 refUuid = state.getTransformer().getReferenceSystemUuid(strRefSys);
887 if (refUuid == null){
888 String message = "Unknown reference system %s in line %d";
889 message = String.format(message, strRefSys, state.getCurrentLine());
890 logger.warn(message);
891 }
892 refSys = getReferenceSystem(state, refUuid, strRefSys, strRefSys, strRefSys, null);
893 }
894
895 } catch (UndefinedTransformerMethodException e) {
896 throw new RuntimeException(e);
897 }
898 }
899
900
901
902 // lat/ long /error
903 try {
904 String longitude = row.getLongitude();
905 String latitude = row.getLatitude();
906 Integer errorRadius = null;
907 if (StringUtils.isNotBlank(row.getErrorRadius())){
908 try {
909 errorRadius = Integer.valueOf(row.getErrorRadius());
910 } catch (NumberFormatException e) {
911 String message = "Error radius %s could not be transformed to Integer in line %d";
912 message = String.format(message, row.getErrorRadius(), state.getCurrentLine());
913 logger.warn(message);
914 }
915 }
916 //all
917 facade.setExactLocationByParsing(longitude, latitude, refSys, errorRadius);
918 } catch (ParseException e) {
919 String message = "Problems when parsing exact location for line %d";
920 message = String.format(message, state.getCurrentLine());
921 logger.warn(message);
922
923 }
924
925
926
927 }
928
929
930 /*
931 * Set the current Country
932 * Search in the DB if the isoCode is known
933 * If not, search if the country name is in the DB
934 * If not, create a new Label with the Level Country
935 * @param iso: the country iso code
936 * @param fullName: the country's full name
937 * @param app: the CDM application controller
938 */
939 private void handleCountry(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
940
941 if (StringUtils.isNotBlank(row.getIsoCountry())){
942 NamedArea country = getOccurrenceService().getCountryByIso(row.getIsoCountry());
943 if (country != null){
944 facade.setCountry(country);
945 return;
946 }
947 }
948 if (StringUtils.isNotBlank(row.getCountry())){
949 List<Country> countries = getOccurrenceService().getCountryByName(row.getCountry());
950 if (countries.size() >0){
951 facade.setCountry(countries.get(0));
952 }else{
953 UUID uuid = UUID.randomUUID();
954 String label = row.getCountry();
955 String text = row.getCountry();
956 String labelAbbrev = null;
957 NamedAreaType areaType = NamedAreaType.ADMINISTRATION_AREA();
958 NamedAreaLevel level = NamedAreaLevel.COUNTRY();
959 NamedArea newCountry = this.getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level);
960 facade.setCountry(newCountry);
961 }
962 }
963 }
964
965 @Override
966 protected void secondPass(SpecimenCdmExcelImportState state) {
967 //no second path defined yet
968 return;
969 }
970
971
972 @Override
973 protected String getWorksheetName() {
974 return WORKSHEET_NAME;
975 }
976
977 @Override
978 protected boolean needsNomenclaturalCode() {
979 return false;
980 }
981
982
983 /* (non-Javadoc)
984 * @see eu.etaxonomy.cdm.io.excel.common.ExcelTaxonOrSpecimenImportBase#createDataHolderRow()
985 */
986 @Override
987 protected SpecimenRow createDataHolderRow() {
988 return new SpecimenRow();
989 }
990
991
992
993
994 /* (non-Javadoc)
995 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doCheck(eu.etaxonomy.cdm.io.common.IoStateBase)
996 */
997 @Override
998 protected boolean doCheck(SpecimenCdmExcelImportState state) {
999 logger.warn("Validation not yet implemented for " + this.getClass().getSimpleName());
1000 return true;
1001 }
1002
1003
1004
1005 @Override
1006 protected boolean isIgnore(SpecimenCdmExcelImportState state) {
1007 return !state.getConfig().isDoSpecimen();
1008 }
1009
1010
1011 }