Project

General

Profile

Download (35.8 KB) Statistics
| Branch: | Tag: | Revision:
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.IBotanicalName;
48
import eu.etaxonomy.cdm.model.name.INonViralName;
49
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
50
import eu.etaxonomy.cdm.model.name.Rank;
51
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
52
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
53
import eu.etaxonomy.cdm.model.name.TaxonName;
54
import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
55
import eu.etaxonomy.cdm.model.occurrence.Collection;
56
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
57
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
58
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
59
import eu.etaxonomy.cdm.model.reference.Reference;
60
import eu.etaxonomy.cdm.model.reference.ReferenceFactory;
61
import eu.etaxonomy.cdm.model.taxon.Taxon;
62
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
63
import eu.etaxonomy.cdm.persistence.query.MatchMode;
64
import eu.etaxonomy.cdm.strategy.exceptions.StringNotParsableException;
65
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
66
import eu.etaxonomy.cdm.strategy.parser.INonViralNameParser;
67
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
68
import eu.etaxonomy.cdm.strategy.parser.TimePeriodParser;
69

    
70
/**
71
 * @author a.mueller
72
 * @since 10.05.2011
73
 */
74
@Component
75
public class SpecimenCdmExcelImport
76
        extends ExcelTaxonOrSpecimenImportBase<SpecimenCdmExcelImportState, SpecimenCdmExcelImportConfigurator, SpecimenRow>
77
        implements ICdmIO<SpecimenCdmExcelImportState> {
78
    private static final long serialVersionUID = 5489033387543936839L;
79

    
80
    private static final Logger logger = Logger.getLogger(SpecimenCdmExcelImport.class);
81

    
82
	private static final String WORKSHEET_NAME = "Specimen";
83

    
84
	private static final String BASIS_OF_RECORD_COLUMN = "(?i)(BasisOfRecord)";
85
	private static final String COUNTRY_COLUMN = "(?i)(Country)";
86
	private static final String AREA_COLUMN = "(?i)(Area)";
87
	private static final String ISO_COUNTRY_COLUMN = "(?i)(ISOCountry|CountryCode)";
88
	private static final String LOCALITY_COLUMN = "(?i)(Locality)";
89
	private static final String ALTITUDE_COLUMN = "(?i)(AbsoluteElevation|Altitude)";
90
	private static final String ALTITUDE_MAX_COLUMN = "(?i)(AbsoluteElevation|Altitude)Max(imum)?";
91
	private static final String COLLECTION_DATE_COLUMN = "(?i)(CollectionDate)";
92
	private static final String COLLECTION_DATE_END_COLUMN = "(?i)(CollectionDateEnd)";
93
	private static final String COLLECTOR_COLUMN = "(?i)(Collector)";
94
	private static final String COLLECTORS_COLUMN = "(?i)(Collectors)";
95
	private static final String PRIMARY_COLLECTOR_COLUMN = "(?i)(PrimaryCollector)";
96
	private static final String LONGITUDE_COLUMN = "(?i)(Longitude)";
97
	private static final String LATITUDE_COLUMN = "(?i)(Latitude)";
98
	private static final String REFERENCE_SYSTEM_COLUMN = "(?i)(ReferenceSystem)";
99
	private static final String ERROR_RADIUS_COLUMN = "(?i)(ErrorRadius)";
100

    
101
	private static final String COLLECTORS_NUMBER_COLUMN = "(?i)((Collectors|Field)Number)";
102
	private static final String ECOLOGY_COLUMN = "(?i)(Ecology|Habitat)";
103
	private static final String PLANT_DESCRIPTION_COLUMN = "(?i)(PlantDescription)";
104
	private static final String FIELD_NOTES_COLUMN = "(?i)(FieldNotes)";
105
	private static final String SEX_COLUMN = "(?i)(Sex)";
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
	private static final String TYPE_CATEGORY_COLUMN = "(?i)(TypeCategory)";
114
	private static final String TYPIFIED_NAME_COLUMN = "(?i)(TypifiedName|TypeOf)";
115

    
116
	private static final String SOURCE_COLUMN = "(?i)(Source)";
117
	private static final String ID_IN_SOURCE_COLUMN = "(?i)(IdInSource)";
118

    
119
	private static final String DETERMINATION_AUTHOR_COLUMN = "(?i)(Author)";
120
	private static final String DETERMINATION_MODIFIER_COLUMN = "(?i)(DeterminationModifier)";
121
	private static final String DETERMINED_BY_COLUMN = "(?i)(DeterminationBy)";
122
	private static final String DETERMINED_WHEN_COLUMN = "(?i)(Det(ermination)?When)";
123
	private static final String DETERMINATION_NOTES_COLUMN = "(?i)(DeterminationNote)";
124
	private static final String EXTENSION_COLUMN = "(?i)(Ext(ension)?)";
125

    
126
	public SpecimenCdmExcelImport() {
127
		super();
128
	}
129

    
130
	@Override
131
	protected void analyzeSingleValue(KeyValue keyValue, SpecimenCdmExcelImportState state) {
132
		SpecimenRow row = state.getCurrentRow();
133
		String value = keyValue.value;
134
		if(keyValue.key.matches(BASIS_OF_RECORD_COLUMN)) {
135
			row.setBasisOfRecord(value);
136
		} else if(keyValue.key.matches(COUNTRY_COLUMN)) {
137
			row.setCountry(value);
138
		} else if(keyValue.key.matches(ISO_COUNTRY_COLUMN)) {
139
			row.setIsoCountry(value);
140
		} else if(keyValue.key.matches(LOCALITY_COLUMN)) {
141
			row.setLocality(value);
142
		} else if(keyValue.key.matches(FIELD_NOTES_COLUMN)) {
143
			row.setLocality(value);
144
		} else if(keyValue.key.matches(ALTITUDE_COLUMN)) {
145
			row.setAltitude(value);
146
		} else if(keyValue.key.matches(ALTITUDE_MAX_COLUMN)) {
147
			row.setAltitudeMax(value);
148
		} else if(keyValue.key.matches(COLLECTOR_COLUMN)) {
149
			row.putCollector(keyValue.index, value);
150
		} else if(keyValue.key.matches(PRIMARY_COLLECTOR_COLUMN)) {
151
			row.setPrimaryCollector(value);
152
		} else if(keyValue.key.matches(ECOLOGY_COLUMN)) {
153
			row.setEcology(value);
154
		} else if(keyValue.key.matches(PLANT_DESCRIPTION_COLUMN)) {
155
			row.setPlantDescription(value);
156
		} else if(keyValue.key.matches(SEX_COLUMN)) {
157
			row.setSex(value);
158
		} else if(keyValue.key.matches(COLLECTION_DATE_COLUMN)) {
159
			row.setCollectingDate(value);
160
		} else if(keyValue.key.matches(COLLECTION_DATE_END_COLUMN)) {
161
			row.setCollectingDateEnd(value);
162
		} else if(keyValue.key.matches(COLLECTORS_COLUMN)) {
163
			row.setCollectors(value);
164
		} else if(keyValue.key.matches(COLLECTOR_COLUMN)) {
165
			row.putCollector(keyValue.index, value);
166
		} else if(keyValue.key.matches(COLLECTORS_NUMBER_COLUMN)) {
167
			row.setCollectorsNumber(value);
168
		} else if(keyValue.key.matches(LONGITUDE_COLUMN)) {
169
			row.setLongitude(value);
170
		} else if(keyValue.key.matches(LATITUDE_COLUMN)) {
171
			row.setLatitude(value);
172
		} else if(keyValue.key.matches(REFERENCE_SYSTEM_COLUMN)) {
173
			row.setReferenceSystem(value);
174
		} else if(keyValue.key.matches(ERROR_RADIUS_COLUMN)) {
175
			row.setErrorRadius(value);
176
		} else if(keyValue.key.matches(AREA_COLUMN)) {
177
			if (keyValue.postfix != null){
178
				row.addLeveledArea(keyValue.postfix, value);
179
			}else{
180
				logger.warn("Not yet implemented");
181
			}
182
		} else if(keyValue.key.matches(LANGUAGE)) {
183
			row.setLanguage(value);
184

    
185

    
186
		} else if(keyValue.key.matches(ACCESSION_NUMBER_COLUMN)) {
187
			row.setAccessionNumber(value);
188
		} else if(keyValue.key.matches(BARCODE_COLUMN)) {
189
			row.setBarcode(value);
190
		} else if(keyValue.key.matches(UNIT_NOTES_COLUMN)) {
191
			row.putUnitNote(keyValue.index, value);
192

    
193

    
194
		} else if(keyValue.key.matches(FAMILY_COLUMN)) {
195
			row.putDeterminationFamily(keyValue.index, value);
196
		} else if(keyValue.key.matches(GENUS_COLUMN)) {
197
			row.putDeterminationGenus(keyValue.index, value);
198
		} else if(keyValue.key.matches(SPECIFIC_EPITHET_COLUMN)) {
199
			row.putDeterminationSpeciesEpi(keyValue.index, value);
200
		} else if(keyValue.key.matches(INFRASPECIFIC_EPITHET_COLUMN)) {
201
			row.putDeterminationInfraSpeciesEpi(keyValue.index, value);
202
		} else if(keyValue.key.matches(RANK_COLUMN)) {
203
			row.putDeterminationRank(keyValue.index, value);
204
		} else if(keyValue.key.matches(TAXON_UUID_COLUMN)) {
205
			row.putDeterminationTaxonUuid(keyValue.index, value);
206
		} else if(keyValue.key.matches(FULL_NAME_COLUMN)) {
207
			row.putDeterminationFullName(keyValue.index, value);
208
		} else if(keyValue.key.matches(DETERMINATION_AUTHOR_COLUMN)) {
209
			row.putDeterminationAuthor(keyValue.index, value);
210
		} else if(keyValue.key.matches(DETERMINATION_MODIFIER_COLUMN)) {
211
			row.putDeterminationDeterminationModifier(keyValue.index, value);
212
		} else if(keyValue.key.matches(DETERMINATION_NOTES_COLUMN)) {
213
			row.putDeterminationDeterminationNotes(keyValue.index, value);
214
		} else if(keyValue.key.matches(DETERMINED_BY_COLUMN)) {
215
			row.putDeterminationDeterminedBy(keyValue.index, value);
216
		} else if(keyValue.key.matches(DETERMINED_WHEN_COLUMN)) {
217
			row.putDeterminationDeterminedWhen(keyValue.index, value);
218

    
219
		} else if(keyValue.key.matches(COLLECTION_CODE_COLUMN)) {
220
			row.setCollectionCode(value);
221
		} else if(keyValue.key.matches(COLLECTION_COLUMN)) {
222
			row.setCollection(value);
223

    
224
		} else if(keyValue.key.matches(TYPE_CATEGORY_COLUMN)) {
225
			row.putTypeCategory(keyValue.index, getSpecimenTypeStatus(state, value));
226
		} else if(keyValue.key.matches(TYPIFIED_NAME_COLUMN)) {
227
			row.putTypifiedName(keyValue.index, getTaxonName(state, value));
228

    
229

    
230
		} else if(keyValue.key.matches(SOURCE_COLUMN)) {
231
			row.putSourceReference(keyValue.index, getOrMakeReference(state, value) );
232
		} else if(keyValue.key.matches(ID_IN_SOURCE_COLUMN)) {
233
			row.putIdInSource(keyValue.index, value);
234
		} else if(keyValue.key.matches(EXTENSION_COLUMN)) {
235
			if (keyValue.postfix != null){
236
				row.addExtension(keyValue.postfix, value);
237
			}else{
238
				logger.warn("Extension without postfix not yet implemented");
239
			}
240
		}else {
241
			state.setUnsuccessfull();
242
			logger.error("Unexpected column header " + keyValue.originalKey);
243
		}
244

    
245
    	return;
246
	}
247

    
248
	@Override
249
	protected void firstPass(SpecimenCdmExcelImportState state) {
250
		SpecimenRow row = state.getCurrentRow();
251

    
252
		//basis of record
253
		SpecimenOrObservationType type = SpecimenOrObservationType.valueOf2(row.getBasisOfRecord());
254
		if (type == null){
255
			String message = "%s is not a valid BasisOfRecord. 'Unknown' is used instead in line %d.";
256
			message = String.format(message, row.getBasisOfRecord(), state.getCurrentLine());
257
			logger.warn(message);
258
			type = SpecimenOrObservationType.DerivedUnit;
259
		}
260
		DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(type);
261

    
262
		Language lang = Language.DEFAULT();
263
		if (StringUtils.isNotBlank(row.getLanguage())){
264
			Language langIso = getTermService().getLanguageByIso(row.getLanguage());
265
			if (langIso == null){
266
				String message = "Language could not be recognized: %s. Use default language instead. Line %d.";
267
				message = String.format(message, langIso, state.getCurrentLine());
268
			}else{
269
				lang = langIso;
270
			}
271
		}
272

    
273
		//country
274
		handleCountry(facade, row, state);
275
		handleAreas(facade,row, state);
276

    
277
		facade.setGatheringPeriod(getTimePeriod(row.getCollectingDate(), row.getCollectingDateEnd()));
278
		facade.setLocality(row.getLocality());
279
		facade.setFieldNotes(row.getFieldNotes());
280
		facade.setFieldNumber(row.getCollectorsNumber());
281
		facade.setEcology(row.getEcology(), lang);
282
		facade.setPlantDescription(row.getPlantDescription(), lang);
283
//		facade.setSex(row.get)
284
		handleExactLocation(facade, row, state);
285
		facade.setCollector(getOrMakeAgent(state, row.getCollectors()));
286
		facade.setPrimaryCollector(getOrMakePrimaryCollector(facade, row.getPrimaryCollector(), state));
287
		handleAbsoluteElevation(facade, row, state);
288

    
289
		//derivedUnit
290
		facade.setBarcode(row.getBarcode());
291
		facade.setAccessionNumber(row.getAccessionNumber());
292
		facade.setCollection(getOrMakeCollection(state, row.getCollectionCode(), row.getCollection()));
293
		for (IdentifiableSource source : row.getSources()){
294
			facade.addSource(source);
295
		}
296
		for (SpecimenTypeDesignation designation : row.getTypeDesignations()){
297
			logger.warn("FIXME"); //FIXME
298
//			facade.innerDerivedUnit().addSpecimenTypeDesignation(designation);
299
		}
300
		handleDeterminations(state, row, facade);
301
		handleExtensions(facade.innerDerivedUnit(),row, state);
302
		for (String note : row.getUnitNotes()){
303
			Annotation annotation = Annotation.NewInstance(note, AnnotationType.EDITORIAL(), Language.DEFAULT());
304
			facade.addAnnotation(annotation);
305
		}
306

    
307
		//save
308
		getOccurrenceService().save(facade.innerDerivedUnit());
309
		return;
310
	}
311

    
312
	private void handleAbsoluteElevation(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
313
		//altitude
314

    
315
		try {
316
			String altitude = row.getAltitude();
317
			if (StringUtils.isBlank(altitude)){
318
				return;
319
			}
320
//			if (altitude.endsWith(".0")){
321
//				altitude = altitude.substring(0, altitude.length() -2);
322
//			}
323
			int value = Integer.valueOf(altitude);
324
			facade.setAbsoluteElevation(value);
325
		} catch (NumberFormatException e) {
326
			String message = "Absolute elevation / altitude '%s' is not an integer number in line %d";
327
			message = String.format(message, row.getAltitude(), state.getCurrentLine());
328
			logger.warn(message);
329
			return;
330
		}
331

    
332
		//max
333

    
334
		try {
335
			String max = row.getAltitudeMax();
336
			if (StringUtils.isBlank(max)){
337
				return;
338
			}
339
//			if (max.endsWith(".0")){
340
//				max = max.substring(0, max.length() -2);
341
//			}
342
			int value = Integer.valueOf(max);
343
			//TODO avoid unequal distance
344
			int min = facade.getAbsoluteElevation();
345
			if ( (value - min) % 2 == 1 ){
346
				String message = "Altitude min-max difference ist not equal. Max reduced by 1 in line %d";
347
				message = String.format(message, state.getCurrentLine());
348
				logger.warn(message);
349
				value--;
350
			}
351
			facade.setAbsoluteElevationRange(min, value);
352
		} catch (NumberFormatException e) {
353
			String message = "Absolute elevation / Altitude maximum '%s' is not an integer number in line %d";
354
			message = String.format(message, row.getAltitudeMax(), state.getCurrentLine());
355
			logger.warn(message);
356
			return;
357
		}catch (Exception e){
358
			String message = "Error occurred when trying to write Absolute elevation / Altitude maximum '%s' in line %d";
359
			message = String.format(message, row.getAltitudeMax(), state.getCurrentLine());
360
			logger.warn(message);
361
			return;
362

    
363
		}
364
	}
365

    
366
	private void handleAreas(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
367
		List<PostfixTerm> areas = row.getLeveledAreas();
368

    
369
		for (PostfixTerm lArea : areas){
370
			String description = lArea.term;
371
			String abbrev = lArea.term;
372
			NamedAreaType type = null;
373
			String key = lArea.postfix + "_" + lArea.term;
374
			UUID areaUuid = state.getArea(key);
375
			NamedAreaLevel level = state.getPostfixLevel(lArea.postfix);
376

    
377
			TermMatchMode matchMode = state.getConfig().getAreaMatchMode();
378
			NamedArea area = getNamedArea(state, areaUuid, lArea.term, description, abbrev, type, level, null, matchMode);
379
			facade.addCollectingArea(area);
380
			if (areaUuid == null){
381
				state.putArea(key, area.getUuid());
382
			}
383
		}
384
	}
385

    
386

    
387
	/**
388
	 * @param state
389
	 * @param row
390
	 * @param facade
391
	 */
392
	private void handleDeterminations(SpecimenCdmExcelImportState state,SpecimenRow row, DerivedUnitFacade facade) {
393
		boolean isFirstDetermination = true;
394
		DeterminationLight commonDetermination = row.getCommonDetermination();
395
		Taxon commonTaxon = null;
396
		TaxonName commonName = null;
397

    
398
		boolean hasCommonTaxonInfo = (commonDetermination == null) ? false : commonDetermination.hasTaxonInformation();
399
		if (hasCommonTaxonInfo && commonDetermination != null){
400
			TaxonBase<?> taxonBase = null;
401
			if (StringUtils.isNotBlank(commonDetermination.taxonUuid)){
402
				UUID taxonUuid = UUID.fromString(commonDetermination.taxonUuid);
403
				taxonBase = getTaxonService().find(taxonUuid);
404
				if (taxonBase == null){
405
					String message = "Taxon for uuid %s not found in line %d.";
406
					message = String.format(message, taxonUuid.toString(), state.getCurrentLine());
407
					logger.warn(message);
408
				}
409
			}else{
410
				taxonBase = findBestMatchingTaxon(state, commonDetermination, state.getConfig().isCreateTaxonIfNotExists());
411
			}
412
			commonTaxon = getAcceptedTaxon(taxonBase);
413
			if (taxonBase != null){
414
				commonName = taxonBase.getName();
415
			}else{
416
				commonTaxon = createTaxonFromDetermination(state, commonDetermination);
417
				commonName = commonTaxon.getName();
418
			}
419
		}
420

    
421
		for (DeterminationLight determinationLight : row.getDetermination()){
422
			Taxon taxon;
423
			if (! hasCommonTaxonInfo){
424
				taxon = findBestMatchingTaxon(state, determinationLight, state.getConfig().isCreateTaxonIfNotExists());
425
			}else{
426
				taxon = commonTaxon;
427
			}
428
			if (taxon != null){
429
				getTaxonService().saveOrUpdate(taxon);
430
				if (state.getConfig().isMakeIndividualAssociations() && taxon != null){
431
					IndividualsAssociation indivAssociciation = IndividualsAssociation.NewInstance();
432
					DerivedUnit du = facade.innerDerivedUnit();
433
					indivAssociciation.setAssociatedSpecimenOrObservation(du);
434
					getTaxonDescription(taxon).addElement(indivAssociciation);
435
					Feature feature = Feature.INDIVIDUALS_ASSOCIATION();
436
					if (facade.getType().isPreservedSpecimen()){
437
						feature = Feature.SPECIMEN();
438
					}else if (facade.getType().isFeatureObservation()){
439
						feature = Feature.OBSERVATION();
440
					}
441
					if (state.getConfig().isUseMaterialsExaminedForIndividualsAssociations()){
442
						feature = Feature.MATERIALS_EXAMINED();
443
					}
444
					indivAssociciation.setFeature(feature);
445
				}
446
				if (state.getConfig().isDeterminationsAreDeterminationEvent()){
447
					DeterminationEvent detEvent = makeDeterminationEvent(state, determinationLight, taxon);
448
					detEvent.setPreferredFlag(isFirstDetermination);
449
					facade.addDetermination(detEvent);
450
				}
451
			}
452

    
453
			if (isFirstDetermination && state.getConfig().isFirstDeterminationIsStoredUnder()){
454
				TaxonName name;
455

    
456
				if (!hasCommonTaxonInfo){
457
					name = findBestMatchingName(state, determinationLight);
458
				}else{
459
					if (commonName == null){
460
						commonName = findBestMatchingName(state, commonDetermination);
461
					}
462
					name = commonName;
463
				}
464
				if (name != null){
465
					facade.setStoredUnder(name);
466
				}
467
			}
468
			isFirstDetermination = false;
469
		}
470
	}
471

    
472
	private Taxon createTaxonFromDetermination( SpecimenCdmExcelImportState state, DeterminationLight commonDetermination) {
473

    
474
		//rank
475
		Rank rank;
476
		try {
477
			rank = StringUtils.isBlank(commonDetermination.rank) ? null : Rank.getRankByLatinNameOrIdInVoc(commonDetermination.rank, true);
478
		} catch (UnknownCdmTypeException e) {
479
			rank = null;
480
		}
481

    
482
		//name
483
		INonViralName name;
484
		INonViralNameParser<INonViralName> parser = NonViralNameParserImpl.NewInstance();
485
		NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
486
		if (StringUtils.isNotBlank(commonDetermination.fullName)){
487
			name = parser.parseFullName(commonDetermination.fullName, nc, rank);
488
			if (StringUtils.isBlank(name.getAuthorshipCache()) && StringUtils.isNotBlank(commonDetermination.author)){
489
				setAuthorship(name, commonDetermination.author, parser);
490
			}
491
		}else{
492
			if (nc != null){
493
				name = nc.getNewTaxonNameInstance(rank);
494
			}else{
495
				name = TaxonNameFactory.NewNonViralInstance(rank);
496
			}
497
			if (StringUtils.isNotBlank(commonDetermination.genus)){
498
				name.setGenusOrUninomial(commonDetermination.genus);
499
			}
500
			if (StringUtils.isNotBlank(commonDetermination.speciesEpi)){
501
				name.setSpecificEpithet(commonDetermination.speciesEpi);
502
			}
503
			if (StringUtils.isNotBlank(commonDetermination.infraSpeciesEpi)){
504
				name.setInfraSpecificEpithet(commonDetermination.infraSpeciesEpi);
505
			}
506
			if (StringUtils.isNotBlank(commonDetermination.author)){
507
				setAuthorship(name, commonDetermination.author, parser);
508
			}
509
			//guess rank if null
510
			if (name.getRank() == null){
511
				if (name.getInfraGenericEpithet() != null && name.getSpecificEpithet() == null){
512
					name.setRank(Rank.INFRAGENERICTAXON());
513
				}else if (name.getSpecificEpithet() != null && name.getInfraSpecificEpithet() == null){
514
					name.setRank(Rank.SPECIES());
515
				}else if (name.getInfraSpecificEpithet() != null){
516
					name.setRank(Rank.INFRASPECIFICTAXON());
517
				}
518

    
519
			}
520

    
521
		}
522
		//sec
523
		Reference sec = null;
524
		if (StringUtils.isNotBlank(commonDetermination.determinedBy)){
525
			sec = ReferenceFactory.newGeneric();
526
			TeamOrPersonBase<?> determinedBy;
527
			IBotanicalName dummyName = TaxonNameFactory.NewBotanicalInstance(Rank.SPECIES());
528
			try {
529
				parser.parseAuthors(dummyName, commonDetermination.determinedBy);
530
				determinedBy = dummyName.getCombinationAuthorship();
531
			} catch (StringNotParsableException e) {
532
				determinedBy = Team.NewTitledInstance(commonDetermination.determinedBy, commonDetermination.determinedBy);
533
			}
534
			sec.setAuthorship(determinedBy);
535
		}
536

    
537
		//taxon
538
		Taxon taxon = Taxon.NewInstance(name, sec);
539

    
540
		if (StringUtils.isNotBlank(commonDetermination.family)){
541
			if (name.getRank() == null || name.getRank().isLower(Rank.FAMILY()) ){
542
				logger.warn("Family taxon could not be created");
543
			}
544
		}
545

    
546
		//return
547
		return taxon;
548
	}
549

    
550
	private void setAuthorship(INonViralName name, String author, INonViralNameParser<INonViralName> parser) {
551
		if (name.isBotanical() || name.isZoological()){
552
			try {
553
				parser.parseAuthors(name, author);
554
			} catch (StringNotParsableException e) {
555
				name.setAuthorshipCache(author);
556
			}
557
		}else{
558
			name.setAuthorshipCache(author);
559
		}
560
	}
561

    
562
	/**
563
	 * This method tries to find the best matching taxon depending on the import configuration,
564
	 * the taxon name information and the concept information available.
565
	 *
566
	 * @param state
567
	 * @param determinationLight
568
	 * @param createIfNotExists
569
	 * @return
570
	 */
571
	private Taxon findBestMatchingTaxon(SpecimenCdmExcelImportState state, DeterminationLight determinationLight, boolean createIfNotExists) {
572
		INonViralName name = makeTaxonName(state, determinationLight);
573

    
574
		String titleCache = makeSearchNameTitleCache(state, determinationLight, name);
575

    
576
		if (! StringUtils.isBlank(titleCache)){
577
			MatchingTaxonConfigurator matchConfigurator = MatchingTaxonConfigurator.NewInstance();
578
			matchConfigurator.setTaxonNameTitle(titleCache);
579
			matchConfigurator.setIncludeSynonyms(false);
580
			Taxon taxon = getTaxonService().findBestMatchingTaxon(matchConfigurator);
581

    
582
			if(taxon == null && createIfNotExists){
583
				logger.info("creating new Taxon from TaxonName '" + titleCache+"'");
584
				UUID secUuid = null; //TODO
585
				Reference sec = null;
586
				if (secUuid != null){
587
					sec = getReferenceService().find(secUuid);
588
				}
589
				taxon = Taxon.NewInstance(name, sec);
590
			}else if (taxon == null){
591
				String message = "Taxon '%s' not found in line %d";
592
				message = String.format(message, titleCache, state.getCurrentLine());
593
				logger.warn(message);
594
			}
595
			return taxon;
596
		}else {
597
			return null;
598
		}
599
	}
600

    
601
	/**
602
	 * @param state
603
	 * @param determinationLight
604
	 * @param name
605
	 * @return
606
	 */
607
	private String makeSearchNameTitleCache(SpecimenCdmExcelImportState state, DeterminationLight determinationLight,
608
				INonViralName name) {
609
		String titleCache = determinationLight.fullName;
610
		if (! state.getConfig().isPreferNameCache() || StringUtils.isBlank(titleCache) ){
611
			String computedTitleCache = name.getTitleCache();
612
			if (StringUtils.isNotBlank(computedTitleCache)){
613
				titleCache = computedTitleCache;
614
			}
615
		}
616
		return titleCache;
617
	}
618

    
619
	/**
620
	 * @param state
621
	 * @param determinationLight
622
	 * @return
623
	 */
624
	private INonViralName makeTaxonName(SpecimenCdmExcelImportState state, DeterminationLight determinationLight) {
625
		INonViralName name = TaxonNameFactory.NewNonViralInstance(null);
626
		NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
627
		if (nc != null){
628
			name = nc.getNewTaxonNameInstance(null);
629
		}
630
		name.setGenusOrUninomial(determinationLight.genus);
631
		name.setSpecificEpithet(determinationLight.speciesEpi);
632
		name.setInfraSpecificEpithet(determinationLight.infraSpeciesEpi);
633

    
634
		//FIXME bracketAuthors and teams not yet implemented!!!
635
		List<String> authors = new ArrayList<String>();
636
		if (StringUtils.isNotBlank(determinationLight.author)){
637
			authors.add(determinationLight.author);
638
		}
639
		TeamOrPersonBase<?> agent = getOrMakeAgent(state, authors);
640
		name.setCombinationAuthorship(agent);
641

    
642
		try {
643
			if (StringUtils.isNotBlank(determinationLight.rank) ){
644
				name.setRank(Rank.getRankByLatinNameOrIdInVoc(determinationLight.rank, nc, true));
645
			}
646
		} catch (UnknownCdmTypeException e) {
647
			String message = "Rank not found: %s: ";
648
			message = String.format(message, determinationLight.rank);
649
			logger.warn(message);
650
		}
651
		if (StringUtils.isBlank(name.getInfraSpecificEpithet()) && StringUtils.isNotBlank(name.getSpecificEpithet() )){
652
			name.setRank(Rank.SPECIES());
653
		}
654
		if (StringUtils.isBlank(name.getSpecificEpithet()) && StringUtils.isNotBlank(name.getGenusOrUninomial() )){
655
			name.setRank(Rank.SPECIES());
656
		}
657
		if (StringUtils.isBlank(name.getTitleCache())){
658
			//TODO test
659
			name.setTitleCache(determinationLight.fullName, true);
660
		}
661
		return name;
662
	}
663

    
664
	private TaxonName findBestMatchingName(SpecimenCdmExcelImportState state, DeterminationLight determinationLight) {
665

    
666
		INonViralName name = makeTaxonName(state, determinationLight);
667
		String titleCache = makeSearchNameTitleCache(state, determinationLight, name);
668

    
669
		//TODO
670
		List<TaxonName> matchingNames = getNameService().findByName(null, titleCache, MatchMode.EXACT, null, null, null, null, null).getRecords();
671
		if (matchingNames.size() > 0){
672
			return matchingNames.get(0);
673
		} else if (matchingNames.size() > 0){
674
			logger.warn("Get best matching taxon name not yet fully implemeted for specimen import");
675
			return matchingNames.get(0);
676
		}else{
677
			return null;
678
		}
679
	}
680

    
681
	private DeterminationEvent makeDeterminationEvent(SpecimenCdmExcelImportState state, DeterminationLight determination, Taxon taxon) {
682
		DeterminationEvent event = DeterminationEvent.NewInstance();
683
		//taxon
684
		event.setTaxon(taxon);
685

    
686
		//date
687
		TimePeriod date = TimePeriodParser.parseString(determination.determinedWhen);
688
		event.setTimeperiod(date);
689
		//by
690
		//FIXME bracketAuthors and teams not yet implemented!!!
691
		List<String> authors = new ArrayList<String>();
692
		if (StringUtils.isNotBlank(determination.determinedBy)){
693
			authors.add(determination.determinedBy);
694
		}
695
		TeamOrPersonBase<?> actor = getOrMakeAgent(state, authors);
696
		TeamOrPersonBase<?> secAuthor = taxon.getSec() == null ? null : taxon.getSec().getAuthorship();
697
		if (actor != null && secAuthor != null && secAuthor.getTitleCache().equals(actor.getTitleCache()) && secAuthor.getNomenclaturalTitleCache().equals(actor.getNomenclaturalTitleCache())) {
698
			actor = secAuthor;
699
		}
700

    
701
		event.setActor(actor);
702

    
703
		//TODO
704
		if (StringUtils.isNotBlank(determination.modifier)){
705
			logger.warn("DeterminationModifiers not yet implemented for specimen import");
706
		}
707
//		DeterminationModifier modifier = DeterminationModifier.NewInstance(term, label, labelAbbrev);
708
//		determination.modifier;
709
		//notes
710
		if (StringUtils.isNotEmpty(determination.notes)){
711
			Annotation annotation = Annotation.NewInstance(determination.notes, AnnotationType.EDITORIAL(), Language.DEFAULT());
712
			event.addAnnotation(annotation);
713
		}
714

    
715
		return event;
716
	}
717

    
718
	private TaxonDescription getTaxonDescription(Taxon taxon) {
719
		TaxonDescription desc = this.getTaxonDescription(taxon, ! IMAGE_GALLERY, CREATE);
720
		return desc;
721
	}
722

    
723
	private TeamOrPersonBase<?> getOrMakeAgent(SpecimenCdmExcelImportState state, List<String> agents) {
724
		if (agents.size() == 0){
725
			return null;
726
		}else if (agents.size() == 1){
727
			return getOrMakePerson(state, agents.get(0));
728
		}else{
729
			return getOrMakeTeam(state, agents);
730
		}
731
	}
732

    
733
	private Person getOrMakePrimaryCollector(DerivedUnitFacade facade, String primaryCollector, SpecimenCdmExcelImportState state) {
734
		if (StringUtils.isBlank(primaryCollector)){
735
			return null;
736
		}
737
		AgentBase<?> collector = facade.getCollector();
738
		List<Person> collectors = new ArrayList<>();
739
		if (collector.isInstanceOf(Team.class) ){
740
			Team team = CdmBase.deproxy(collector, Team.class);
741
			collectors.addAll(team.getTeamMembers());
742
		}else if (collector.isInstanceOf(Person.class)){
743
			collectors.add(CdmBase.deproxy(collector, Person.class));
744
		}else{
745
			throw new IllegalStateException("Unknown subclass of agentbase: " + collector.getClass().getName() );
746
		}
747
		for (Person person :collectors){
748
			if (primaryCollector.equalsIgnoreCase(person.getTitleCache())){
749
				return person;
750
			}
751
			if (primaryCollector.equalsIgnoreCase(person.getNomenclaturalTitleCache())){
752
				return person;
753
			}
754
		}
755
		String message = "Primary Agent '%s' could not be determined in collector(s) in line %d";
756
		message = String.format(message, primaryCollector, state.getCurrentLine());
757
		logger.warn(message);
758
		return null;
759
	}
760

    
761
	private Team getOrMakeTeam(SpecimenCdmExcelImportState state, List<String> agents) {
762
		String key = CdmUtils.concat("_", agents.toArray(new String[0]));
763

    
764
		Team result = state.getTeam(key);
765
		if (result == null){
766
			result = Team.NewInstance();
767
			for (String member : agents){
768
				Person person = getOrMakePerson(state, member);
769
				result.addTeamMember(person);
770
			}
771
			state.putTeam(key, result);
772
		}
773
		return result;
774
	}
775

    
776
	private Person getOrMakePerson(SpecimenCdmExcelImportState state, String value) {
777
		Person result = state.getPerson(value);
778
		if (result == null){
779
			result = Person.NewInstance();
780
			result.setTitleCache(value, true);
781
			state.putPerson(value, result);
782
		}
783
		return result;
784
	}
785

    
786
	private Reference getOrMakeReference(SpecimenCdmExcelImportState state, String value) {
787
		Reference result = state.getReference(value);
788
		if (result == null){
789
			result = ReferenceFactory.newGeneric();
790
			result.setTitleCache(value, true);
791
			state.putReference(value, result);
792
		}
793
		return result;
794
	}
795

    
796
	private Collection getOrMakeCollection(SpecimenCdmExcelImportState state, String collectionCode, String collectionString) {
797
		Collection result = state.getCollection(collectionCode);
798
		if (result == null){
799
			result = Collection.NewInstance();
800
			result.setCode(collectionCode);
801
			result.setName(collectionString);
802
			state.putCollection(collectionCode, result);
803
		}
804
		return result;
805
	}
806

    
807

    
808
	private TaxonName getTaxonName(SpecimenCdmExcelImportState state, String name) {
809
		TaxonName result = null;
810
		result = state.getName(name);
811
		if (result != null){
812
			return result;
813
		}
814
		List<TaxonName> list = getNameService().findByTitleWithRestrictions(null, name, null, null, null, null, null, null).getRecords();
815
		//TODO better strategy to find best name, e.g. depending on the classification it is used in
816
		if (! list.isEmpty()){
817
			result = list.get(0);
818
		}
819
		if (result == null){
820
			NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
821
			NomenclaturalCode code = state.getConfig().getNomenclaturalCode();
822
			result = (TaxonName)parser.parseFullName(name, code, null);
823
		}
824
		if (result != null){
825
			state.putName(name, result);
826
		}
827
		return result;
828
	}
829

    
830
	private SpecimenTypeDesignationStatus getSpecimenTypeStatus(SpecimenCdmExcelImportState state, String key)  {
831
		SpecimenTypeDesignationStatus result = null;
832
		try {
833
			result = state.getTransformer().getSpecimenTypeDesignationStatusByKey(key);
834
			if (result == null){
835
				String message = "Type status not recognized for %s in line %d";
836
				message = String.format(message, key, state.getCurrentLine());
837
				logger.warn(message);
838
			}
839
			return result;
840
		} catch (UndefinedTransformerMethodException e) {
841
			throw new RuntimeException("getSpecimenTypeDesignationStatusByKey not yet implemented");
842
		}
843
	}
844

    
845

    
846
	private void handleExactLocation(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
847

    
848
		//reference system
849
		ReferenceSystem refSys = null;
850
		if (StringUtils.isNotBlank(row.getReferenceSystem())){
851
			String strRefSys = row.getReferenceSystem().trim().replaceAll("\\s", "");
852
			UUID refUuid;
853
			try {
854
				refSys = state.getTransformer().getReferenceSystemByKey(strRefSys);
855
				if (refSys == null){
856
					//TODO we still need user defined Reference Systems here
857
					refUuid = state.getTransformer().getReferenceSystemUuid(strRefSys);
858
					if (refUuid == null){
859
						String message = "Unknown reference system %s in line %d";
860
						message = String.format(message, strRefSys, state.getCurrentLine());
861
						logger.warn(message);
862
					}
863
					refSys = getReferenceSystem(state, refUuid, strRefSys, strRefSys, strRefSys, null);
864
				}
865

    
866
			} catch (UndefinedTransformerMethodException e) {
867
				throw new RuntimeException(e);
868
			}
869
		}
870

    
871
		// lat/ long /error
872
		try {
873
			String longitude = row.getLongitude();
874
			String latitude = row.getLatitude();
875
			Integer errorRadius = null;
876
			if (StringUtils.isNotBlank(row.getErrorRadius())){
877
				try {
878
					errorRadius = Integer.valueOf(row.getErrorRadius());
879
				} catch (NumberFormatException e) {
880
					String message = "Error radius %s could not be transformed to Integer in line %d";
881
					message = String.format(message, row.getErrorRadius(), state.getCurrentLine());
882
					logger.warn(message);
883
				}
884
			}
885
			//all
886
			facade.setExactLocationByParsing(longitude, latitude, refSys, errorRadius);
887
		} catch (ParseException e) {
888
			String message = "Problems when parsing exact location for line %d";
889
			message = String.format(message, state.getCurrentLine());
890
			logger.warn(message);
891
		}
892
	}
893

    
894
	/*
895
	 * Set the current Country
896
	 * Search in the DB if the isoCode is known
897
	 * If not, search if the country name is in the DB
898
	 * If not, create a new Label with the Level Country
899
	 * @param iso: the country iso code
900
	 * @param fullName: the country's full name
901
	 * @param app: the CDM application controller
902
	 */
903
	private void handleCountry(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
904

    
905
		if (StringUtils.isNotBlank(row.getIsoCountry())){
906
			NamedArea country = getTermService().getCountryByIso(row.getIsoCountry());
907
			if (country != null){
908
				facade.setCountry(country);
909
				return;
910
			}
911
		}
912
		if (StringUtils.isNotBlank(row.getCountry())){
913
			List<Country> countries = getTermService().getCountryByName(row.getCountry());
914
			if (countries.size() >0){
915
				facade.setCountry(countries.get(0));
916
			}else{
917
				UUID uuid = UUID.randomUUID();
918
				String label = row.getCountry();
919
				String text = row.getCountry();
920
				String labelAbbrev = null;
921
				NamedAreaType areaType = NamedAreaType.ADMINISTRATION_AREA();
922
				NamedAreaLevel level = NamedAreaLevel.COUNTRY();
923
				NamedArea newCountry = this.getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level);
924
				facade.setCountry(newCountry);
925
			}
926
		}
927
	}
928

    
929
	@Override
930
	protected void secondPass(SpecimenCdmExcelImportState state) {
931
		//no second path defined yet
932
		return;
933
	}
934

    
935

    
936
	@Override
937
	protected String getWorksheetName(SpecimenCdmExcelImportConfigurator config) {
938
		return WORKSHEET_NAME;
939
	}
940

    
941
	@Override
942
	protected boolean requiresNomenclaturalCode() {
943
		return false;
944
	}
945

    
946
	@Override
947
	protected SpecimenRow createDataHolderRow() {
948
		return new SpecimenRow();
949
	}
950

    
951
	@Override
952
	protected boolean doCheck(SpecimenCdmExcelImportState state) {
953
		logger.warn("Validation not yet implemented for " + this.getClass().getSimpleName());
954
		return true;
955
	}
956

    
957
	@Override
958
	protected boolean isIgnore(SpecimenCdmExcelImportState state) {
959
		return !state.getConfig().isDoSpecimen();
960
	}
961
}
(5-5/12)