Project

General

Profile

Download (35.9 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
 * @created 10.05.2011
73
 */
74
@Component
75
public class SpecimenCdmExcelImport  extends ExcelTaxonOrSpecimenImportBase<SpecimenCdmExcelImportState, SpecimenRow>  implements ICdmIO<SpecimenCdmExcelImportState> {
76
    private static final long serialVersionUID = 5489033387543936839L;
77

    
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
		TaxonName 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
				TaxonName 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 = TaxonNameFactory.NewNonViralInstance(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
			IBotanicalName dummyName = TaxonNameFactory.NewBotanicalInstance(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<INonViralName> parser) {
569
		if (name.isBotanical() || name.isZoological()){
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
		INonViralName 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
				INonViralName 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 INonViralName makeTaxonName(SpecimenCdmExcelImportState state, DeterminationLight determinationLight) {
647
		INonViralName name = TaxonNameFactory.NewNonViralInstance(null);
648
		NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
649
		if (nc != null){
650
			name = 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 TaxonName findBestMatchingName(SpecimenCdmExcelImportState state, DeterminationLight determinationLight) {
687

    
688
		INonViralName name = makeTaxonName(state, determinationLight);
689
		String titleCache = makeSearchNameTitleCache(state, determinationLight, name);
690

    
691
		//TODO
692
		List<TaxonName> 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 TaxonName getTaxonName(SpecimenCdmExcelImportState state, String name) {
835
		TaxonName result = null;
836
		result = state.getName(name);
837
		if (result != null){
838
			return result;
839
		}
840
		List<TaxonName> 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 = (TaxonName)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
}
(5-5/12)