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.BotanicalName;
48
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
49
import eu.etaxonomy.cdm.model.name.NonViralName;
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.TaxonNameBase;
54
import eu.etaxonomy.cdm.model.name.ZoologicalName;
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
 * @version 1.0
74
 */
75
@Component
76
public class SpecimenCdmExcelImport  extends ExcelTaxonOrSpecimenImportBase<SpecimenCdmExcelImportState, SpecimenRow>  implements ICdmIO<SpecimenCdmExcelImportState> {
77
	private static final Logger logger = Logger.getLogger(SpecimenCdmExcelImport.class);
78

    
79
	private static final String WORKSHEET_NAME = "Specimen";
80

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

    
98

    
99
	private static final String COLLECTORS_NUMBER_COLUMN = "(?i)((Collectors|Field)Number)";
100
	private static final String ECOLOGY_COLUMN = "(?i)(Ecology|Habitat)";
101
	private static final String PLANT_DESCRIPTION_COLUMN = "(?i)(PlantDescription)";
102
	private static final String FIELD_NOTES_COLUMN = "(?i)(FieldNotes)";
103
	private static final String SEX_COLUMN = "(?i)(Sex)";
104

    
105

    
106
	private static final String ACCESSION_NUMBER_COLUMN = "(?i)(AccessionNumber)";
107
	private static final String BARCODE_COLUMN = "(?i)(Barcode)";
108
	private static final String COLLECTION_CODE_COLUMN = "(?i)(CollectionCode)";
109
	private static final String COLLECTION_COLUMN = "(?i)(Collection)";
110
	private static final String UNIT_NOTES_COLUMN = "(?i)((Unit)?Notes)";
111

    
112

    
113
	private static final String TYPE_CATEGORY_COLUMN = "(?i)(TypeCategory)";
114
	private static final String TYPIFIED_NAME_COLUMN = "(?i)(TypifiedName|TypeOf)";
115

    
116

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

    
120

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

    
128

    
129
	public SpecimenCdmExcelImport() {
130
		super();
131
	}
132

    
133

    
134

    
135

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

    
191

    
192
		} else if(keyValue.key.matches(ACCESSION_NUMBER_COLUMN)) {
193
			row.setAccessionNumber(value);
194
		} else if(keyValue.key.matches(BARCODE_COLUMN)) {
195
			row.setBarcode(value);
196
		} else if(keyValue.key.matches(UNIT_NOTES_COLUMN)) {
197
			row.putUnitNote(keyValue.index, value);
198

    
199

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

    
225
		} else if(keyValue.key.matches(COLLECTION_CODE_COLUMN)) {
226
			row.setCollectionCode(value);
227
		} else if(keyValue.key.matches(COLLECTION_COLUMN)) {
228
			row.setCollection(value);
229

    
230
		} else if(keyValue.key.matches(TYPE_CATEGORY_COLUMN)) {
231
			row.putTypeCategory(keyValue.index, getSpecimenTypeStatus(state, value));
232
		} else if(keyValue.key.matches(TYPIFIED_NAME_COLUMN)) {
233
			row.putTypifiedName(keyValue.index, getTaxonName(state, value));
234

    
235

    
236
		} else if(keyValue.key.matches(SOURCE_COLUMN)) {
237
			row.putSourceReference(keyValue.index, getOrMakeReference(state, value) );
238
		} else if(keyValue.key.matches(ID_IN_SOURCE_COLUMN)) {
239
			row.putIdInSource(keyValue.index, value);
240
		} else if(keyValue.key.matches(EXTENSION_COLUMN)) {
241
			if (keyValue.postfix != null){
242
				row.addExtension(keyValue.postfix, value);
243
			}else{
244
				logger.warn("Extension without postfix not yet implemented");
245
			}
246

    
247
		}else {
248
			state.setUnsuccessfull();
249
			logger.error("Unexpected column header " + keyValue.originalKey);
250
		}
251

    
252
    	return;
253
	}
254

    
255

    
256
	@Override
257
	protected void firstPass(SpecimenCdmExcelImportState state) {
258
		SpecimenRow row = state.getCurrentRow();
259

    
260
		//basis of record
261
		SpecimenOrObservationType type = SpecimenOrObservationType.valueOf2(row.getBasisOfRecord());
262
		if (type == null){
263
			String message = "%s is not a valid BasisOfRecord. 'Unknown' is used instead in line %d.";
264
			message = String.format(message, row.getBasisOfRecord(), state.getCurrentLine());
265
			logger.warn(message);
266
			type = SpecimenOrObservationType.DerivedUnit;
267
		}
268
		DerivedUnitFacade facade = DerivedUnitFacade.NewInstance(type);
269

    
270

    
271
		Language lang = Language.DEFAULT();
272
		if (StringUtils.isNotBlank(row.getLanguage())){
273
			Language langIso = getTermService().getLanguageByIso(row.getLanguage());
274
			if (langIso == null){
275
				String message = "Language could not be recognized: %s. Use default language instead. Line %d.";
276
				message = String.format(message, langIso, state.getCurrentLine());
277
			}else{
278
				lang = langIso;
279
			}
280
		}
281

    
282
		//country
283
		handleCountry(facade, row, state);
284
		handleAreas(facade,row, state);
285

    
286
		facade.setGatheringPeriod(getTimePeriod(row.getCollectingDate(), row.getCollectingDateEnd()));
287
		facade.setLocality(row.getLocality());
288
		facade.setFieldNotes(row.getFieldNotes());
289
		facade.setFieldNumber(row.getCollectorsNumber());
290
		facade.setEcology(row.getEcology(), lang);
291
		facade.setPlantDescription(row.getPlantDescription(), lang);
292
//		facade.setSex(row.get)
293
		handleExactLocation(facade, row, state);
294
		facade.setCollector(getOrMakeAgent(state, row.getCollectors()));
295
		facade.setPrimaryCollector(getOrMakePrimaryCollector(facade, row.getPrimaryCollector(), state));
296
		handleAbsoluteElevation(facade, row, state);
297

    
298
		//derivedUnit
299
		facade.setBarcode(row.getBarcode());
300
		facade.setAccessionNumber(row.getAccessionNumber());
301
		facade.setCollection(getOrMakeCollection(state, row.getCollectionCode(), row.getCollection()));
302
		for (IdentifiableSource source : row.getSources()){
303
			facade.addSource(source);
304
		}
305
		for (SpecimenTypeDesignation designation : row.getTypeDesignations()){
306
			logger.warn("FIXME"); //FIXME
307
//			facade.innerDerivedUnit().addSpecimenTypeDesignation(designation);
308
		}
309
		handleDeterminations(state, row, facade);
310
		handleExtensions(facade.innerDerivedUnit(),row, state);
311
		for (String note : row.getUnitNotes()){
312
			Annotation annotation = Annotation.NewInstance(note, AnnotationType.EDITORIAL(), Language.DEFAULT());
313
			facade.addAnnotation(annotation);
314
		}
315

    
316
		//save
317
		getOccurrenceService().save(facade.innerDerivedUnit());
318
		return;
319
	}
320

    
321
	private void handleAbsoluteElevation(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
322
		//altitude
323

    
324
		try {
325
			String altitude = row.getAltitude();
326
			if (StringUtils.isBlank(altitude)){
327
				return;
328
			}
329
//			if (altitude.endsWith(".0")){
330
//				altitude = altitude.substring(0, altitude.length() -2);
331
//			}
332
			int value = Integer.valueOf(altitude);
333
			facade.setAbsoluteElevation(value);
334
		} catch (NumberFormatException e) {
335
			String message = "Absolute elevation / altitude '%s' is not an integer number in line %d";
336
			message = String.format(message, row.getAltitude(), state.getCurrentLine());
337
			logger.warn(message);
338
			return;
339
		}
340

    
341
		//max
342

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

    
372
		}
373

    
374

    
375
	}
376

    
377
	private void handleAreas(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
378
		List<PostfixTerm> areas = row.getLeveledAreas();
379

    
380
		for (PostfixTerm lArea : areas){
381
			String description = lArea.term;
382
			String abbrev = lArea.term;
383
			NamedAreaType type = null;
384
			String key = lArea.postfix + "_" + lArea.term;
385
			UUID areaUuid = state.getArea(key);
386
			NamedAreaLevel level = state.getPostfixLevel(lArea.postfix);
387

    
388
			TermMatchMode matchMode = state.getConfig().getAreaMatchMode();
389
			NamedArea area = getNamedArea(state, areaUuid, lArea.term, description, abbrev, type, level, null, matchMode);
390
			facade.addCollectingArea(area);
391
			if (areaUuid == null){
392
				state.putArea(key, area.getUuid());
393
			}
394
		}
395
	}
396

    
397

    
398
	/**
399
	 * @param state
400
	 * @param row
401
	 * @param facade
402
	 */
403
	private void handleDeterminations(SpecimenCdmExcelImportState state,SpecimenRow row, DerivedUnitFacade facade) {
404
		boolean isFirstDetermination = true;
405
		DeterminationLight commonDetermination = row.getCommonDetermination();
406
		Taxon commonTaxon = null;
407
		TaxonNameBase<?,?> commonName = null;
408

    
409
		boolean hasCommonTaxonInfo = (commonDetermination == null) ? false : commonDetermination.hasTaxonInformation();
410
		if (hasCommonTaxonInfo && commonDetermination != null){
411
			TaxonBase<?> taxonBase = null;
412
			if (StringUtils.isNotBlank(commonDetermination.taxonUuid)){
413
				UUID taxonUuid = UUID.fromString(commonDetermination.taxonUuid);
414
				taxonBase = getTaxonService().find(taxonUuid);
415
				if (taxonBase == null){
416
					String message = "Taxon for uuid %s not found in line %d.";
417
					message = String.format(message, taxonUuid.toString(), state.getCurrentLine());
418
					logger.warn(message);
419
				}
420
			}else{
421
				taxonBase = findBestMatchingTaxon(state, commonDetermination, state.getConfig().isCreateTaxonIfNotExists());
422
			}
423
			commonTaxon = getAcceptedTaxon(taxonBase);
424
			if (taxonBase != null){
425
				commonName = taxonBase.getName();
426
			}else{
427
				commonTaxon = createTaxonFromDetermination(state, commonDetermination);
428
				commonName = commonTaxon.getName();
429
			}
430
		}
431

    
432

    
433
		for (DeterminationLight determinationLight : row.getDetermination()){
434
			Taxon taxon;
435
			if (! hasCommonTaxonInfo){
436
				taxon = findBestMatchingTaxon(state, determinationLight, state.getConfig().isCreateTaxonIfNotExists());
437
			}else{
438
				taxon = commonTaxon;
439
			}
440
			if (taxon != null){
441
				getTaxonService().saveOrUpdate(taxon);
442
				if (state.getConfig().isMakeIndividualAssociations() && taxon != null){
443
					IndividualsAssociation indivAssociciation = IndividualsAssociation.NewInstance();
444
					DerivedUnit du = facade.innerDerivedUnit();
445
					indivAssociciation.setAssociatedSpecimenOrObservation(du);
446
					getTaxonDescription(taxon).addElement(indivAssociciation);
447
					Feature feature = Feature.INDIVIDUALS_ASSOCIATION();
448
					if (facade.getType().isPreservedSpecimen()){
449
						feature = Feature.SPECIMEN();
450
					}else if (facade.getType().isFeatureObservation()){
451
						feature = Feature.OBSERVATION();
452
					}
453
					if (state.getConfig().isUseMaterialsExaminedForIndividualsAssociations()){
454
						feature = Feature.MATERIALS_EXAMINED();
455
					}
456

    
457
					indivAssociciation.setFeature(feature);
458
				}
459
				if (state.getConfig().isDeterminationsAreDeterminationEvent()){
460
					DeterminationEvent detEvent = makeDeterminationEvent(state, determinationLight, taxon);
461
					detEvent.setPreferredFlag(isFirstDetermination);
462
					facade.addDetermination(detEvent);
463
				}
464
			}
465

    
466
			if (isFirstDetermination && state.getConfig().isFirstDeterminationIsStoredUnder()){
467
				TaxonNameBase<?,?> name;
468

    
469
				if (!hasCommonTaxonInfo){
470
					name = findBestMatchingName(state, determinationLight);
471
				}else{
472
					if (commonName == null){
473
						commonName = findBestMatchingName(state, commonDetermination);
474
					}
475
					name = commonName;
476
				}
477
				if (name != null){
478
					facade.setStoredUnder(name);
479
				}
480
			}
481
			isFirstDetermination = false;
482
		}
483
	}
484

    
485
	private Taxon createTaxonFromDetermination( SpecimenCdmExcelImportState state, DeterminationLight commonDetermination) {
486

    
487
		//rank
488
		Rank rank;
489
		try {
490
			rank = StringUtils.isBlank(commonDetermination.rank) ? null : Rank.getRankByNameOrIdInVoc(commonDetermination.rank, true);
491
		} catch (UnknownCdmTypeException e) {
492
			rank = null;
493
		}
494

    
495
		//name
496
		NonViralName<?> name;
497
		INonViralNameParser<NonViralName> parser = NonViralNameParserImpl.NewInstance();
498
		NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
499
		if (StringUtils.isNotBlank(commonDetermination.fullName)){
500
			name = parser.parseFullName(commonDetermination.fullName, nc, rank);
501
			if (StringUtils.isBlank(name.getAuthorshipCache()) && StringUtils.isNotBlank(commonDetermination.author)){
502
				setAuthorship(name, commonDetermination.author, parser);
503
			}
504
		}else{
505
			if (nc != null){
506
				name = (NonViralName)nc.getNewTaxonNameInstance(rank);
507
			}else{
508
				name = NonViralName.NewInstance(rank);
509
			}
510
			if (StringUtils.isNotBlank(commonDetermination.genus)){
511
				name.setGenusOrUninomial(commonDetermination.genus);
512
			}
513
			if (StringUtils.isNotBlank(commonDetermination.speciesEpi)){
514
				name.setSpecificEpithet(commonDetermination.speciesEpi);
515
			}
516
			if (StringUtils.isNotBlank(commonDetermination.infraSpeciesEpi)){
517
				name.setInfraSpecificEpithet(commonDetermination.infraSpeciesEpi);
518
			}
519
			if (StringUtils.isNotBlank(commonDetermination.author)){
520
				setAuthorship(name, commonDetermination.author, parser);
521
			}
522
			//guess rank if null
523
			if (name.getRank() == null){
524
				if (name.getInfraGenericEpithet() != null && name.getSpecificEpithet() == null){
525
					name.setRank(Rank.INFRAGENERICTAXON());
526
				}else if (name.getSpecificEpithet() != null && name.getInfraSpecificEpithet() == null){
527
					name.setRank(Rank.SPECIES());
528
				}else if (name.getInfraSpecificEpithet() != null){
529
					name.setRank(Rank.INFRASPECIFICTAXON());
530
				}
531

    
532
			}
533

    
534
		}
535
		//sec
536
		Reference sec = null;
537
		if (StringUtils.isNotBlank(commonDetermination.determinedBy)){
538
			sec = ReferenceFactory.newGeneric();
539
			TeamOrPersonBase<?> determinedBy;
540
			BotanicalName dummyName = BotanicalName.NewInstance(Rank.SPECIES());
541
			try {
542
				parser.parseAuthors(dummyName, commonDetermination.determinedBy);
543
				determinedBy = dummyName.getCombinationAuthorship();
544
			} catch (StringNotParsableException e) {
545
				determinedBy = Team.NewTitledInstance(commonDetermination.determinedBy, commonDetermination.determinedBy);
546
			}
547
			sec.setAuthorship(determinedBy);
548
		}
549

    
550
		//taxon
551
		Taxon taxon = Taxon.NewInstance(name, sec);
552

    
553
		if (StringUtils.isNotBlank(commonDetermination.family)){
554
			if (name.getRank() == null || name.getRank().isLower(Rank.FAMILY()) ){
555
				logger.warn("Family taxon could not be created");
556
			}
557
		}
558

    
559
		//return
560
		return taxon;
561

    
562
	}
563

    
564

    
565

    
566

    
567
	private void setAuthorship(NonViralName<?> name, String author, INonViralNameParser<NonViralName> parser) {
568
		if (name.isInstanceOf(BotanicalName.class) || name.isInstanceOf(ZoologicalName.class)){
569
			try {
570
				parser.parseAuthors(name, author);
571
			} catch (StringNotParsableException e) {
572
				name.setAuthorshipCache(author);
573
			}
574
		}else{
575
			name.setAuthorshipCache(author);
576
		}
577

    
578
	}
579

    
580

    
581

    
582

    
583
	/**
584
	 * This method tries to find the best matching taxon depending on the import configuration,
585
	 * the taxon name information and the concept information available.
586
	 *
587
	 *
588
	 * @param state
589
	 * @param determinationLight
590
	 * @param createIfNotExists
591
	 * @return
592
	 */
593
	private Taxon findBestMatchingTaxon(SpecimenCdmExcelImportState state, DeterminationLight determinationLight, boolean createIfNotExists) {
594
		NonViralName<?> name = makeTaxonName(state, determinationLight);
595

    
596
		String titleCache = makeSearchNameTitleCache(state, determinationLight, name);
597

    
598
		if (! StringUtils.isBlank(titleCache)){
599
			MatchingTaxonConfigurator matchConfigurator = MatchingTaxonConfigurator.NewInstance();
600
			matchConfigurator.setTaxonNameTitle(titleCache);
601
			matchConfigurator.setIncludeSynonyms(false);
602
			Taxon taxon = getTaxonService().findBestMatchingTaxon(matchConfigurator);
603

    
604
			if(taxon == null && createIfNotExists){
605
				logger.info("creating new Taxon from TaxonName '" + titleCache+"'");
606
				UUID secUuid = null; //TODO
607
				Reference sec = null;
608
				if (secUuid != null){
609
					sec = getReferenceService().find(secUuid);
610
				}
611
				taxon = Taxon.NewInstance(name, sec);
612
			}else if (taxon == null){
613
				String message = "Taxon '%s' not found in line %d";
614
				message = String.format(message, titleCache, state.getCurrentLine());
615
				logger.warn(message);
616
			}
617
			return taxon;
618
		}else {
619
			return null;
620
		}
621
	}
622

    
623
	/**
624
	 * @param state
625
	 * @param determinationLight
626
	 * @param name
627
	 * @return
628
	 */
629
	private String makeSearchNameTitleCache(SpecimenCdmExcelImportState state, DeterminationLight determinationLight,
630
				NonViralName<?> name) {
631
		String titleCache = determinationLight.fullName;
632
		if (! state.getConfig().isPreferNameCache() || StringUtils.isBlank(titleCache) ){
633
			String computedTitleCache = name.getTitleCache();
634
			if (StringUtils.isNotBlank(computedTitleCache)){
635
				titleCache = computedTitleCache;
636
			}
637

    
638
		}
639
		return titleCache;
640
	}
641

    
642
	/**
643
	 * @param state
644
	 * @param determinationLight
645
	 * @return
646
	 */
647
	private NonViralName<?> makeTaxonName(SpecimenCdmExcelImportState state, DeterminationLight determinationLight) {
648
		NonViralName<?> name = NonViralName.NewInstance(null);
649
		NomenclaturalCode nc = state.getConfig().getNomenclaturalCode();
650
		if (nc != null){
651
			name = (NonViralName<?>)nc.getNewTaxonNameInstance(null);
652
		}
653
		name.setGenusOrUninomial(determinationLight.genus);
654
		name.setSpecificEpithet(determinationLight.speciesEpi);
655
		name.setInfraSpecificEpithet(determinationLight.infraSpeciesEpi);
656

    
657
		//FIXME bracketAuthors and teams not yet implemented!!!
658
		List<String> authors = new ArrayList<String>();
659
		if (StringUtils.isNotBlank(determinationLight.author)){
660
			authors.add(determinationLight.author);
661
		}
662
		TeamOrPersonBase<?> agent = getOrMakeAgent(state, authors);
663
		name.setCombinationAuthorship(agent);
664

    
665
		try {
666
			if (StringUtils.isNotBlank(determinationLight.rank) ){
667
				name.setRank(Rank.getRankByNameOrIdInVoc(determinationLight.rank, nc, true));
668
			}
669
		} catch (UnknownCdmTypeException e) {
670
			String message = "Rank not found: %s: ";
671
			message = String.format(message, determinationLight.rank);
672
			logger.warn(message);
673
		}
674
		if (StringUtils.isBlank(name.getInfraSpecificEpithet()) && StringUtils.isNotBlank(name.getSpecificEpithet() )){
675
			name.setRank(Rank.SPECIES());
676
		}
677
		if (StringUtils.isBlank(name.getSpecificEpithet()) && StringUtils.isNotBlank(name.getGenusOrUninomial() )){
678
			name.setRank(Rank.SPECIES());
679
		}
680
		if (StringUtils.isBlank(name.getTitleCache())){
681
			//TODO test
682
			name.setTitleCache(determinationLight.fullName, true);
683
		}
684
		return name;
685
	}
686

    
687
	private TaxonNameBase findBestMatchingName(SpecimenCdmExcelImportState state, DeterminationLight determinationLight) {
688

    
689
		NonViralName<?> name = makeTaxonName(state, determinationLight);
690
		String titleCache = makeSearchNameTitleCache(state, determinationLight, name);
691

    
692
		//TODO
693
		List<TaxonNameBase> matchingNames = getNameService().findByName(null, titleCache, MatchMode.EXACT, null, null, null, null, null).getRecords();
694
		if (matchingNames.size() > 0){
695
			return matchingNames.get(0);
696
		} else if (matchingNames.size() > 0){
697
			logger.warn("Get best matching taxon name not yet fully implemeted for specimen import");
698
			return matchingNames.get(0);
699
		}else{
700
			return null;
701
		}
702

    
703
	}
704

    
705

    
706
	private DeterminationEvent makeDeterminationEvent(SpecimenCdmExcelImportState state, DeterminationLight determination, Taxon taxon) {
707
		DeterminationEvent event = DeterminationEvent.NewInstance();
708
		//taxon
709
		event.setTaxon(taxon);
710

    
711
		//date
712
		TimePeriod date = TimePeriodParser.parseString(determination.determinedWhen);
713
		event.setTimeperiod(date);
714
		//by
715
		//FIXME bracketAuthors and teams not yet implemented!!!
716
		List<String> authors = new ArrayList<String>();
717
		if (StringUtils.isNotBlank(determination.determinedBy)){
718
			authors.add(determination.determinedBy);
719
		}
720
		TeamOrPersonBase<?> actor = getOrMakeAgent(state, authors);
721
		TeamOrPersonBase<?> secAuthor = taxon.getSec() == null ? null : taxon.getSec().getAuthorship();
722
		if (actor != null && secAuthor != null && secAuthor.getTitleCache().equals(actor.getTitleCache()) && secAuthor.getNomenclaturalTitle().equals(actor.getNomenclaturalTitle())) {
723
			actor = secAuthor;
724
		}
725

    
726
		event.setActor(actor);
727

    
728
		//TODO
729
		if (StringUtils.isNotBlank(determination.modifier)){
730
			logger.warn("DeterminationModifiers not yet implemented for specimen import");
731
		}
732
//		DeterminationModifier modifier = DeterminationModifier.NewInstance(term, label, labelAbbrev);
733
//		determination.modifier;
734
		//notes
735
		if (StringUtils.isNotEmpty(determination.notes)){
736
			Annotation annotation = Annotation.NewInstance(determination.notes, AnnotationType.EDITORIAL(), Language.DEFAULT());
737
			event.addAnnotation(annotation);
738
		}
739

    
740
		return event;
741
	}
742

    
743
	private TaxonDescription getTaxonDescription(Taxon taxon) {
744
		TaxonDescription desc = this.getTaxonDescription(taxon, ! IMAGE_GALLERY, CREATE);
745
		return desc;
746
	}
747

    
748
	private TeamOrPersonBase<?> getOrMakeAgent(SpecimenCdmExcelImportState state, List<String> agents) {
749
		if (agents.size() == 0){
750
			return null;
751
		}else if (agents.size() == 1){
752
			return getOrMakePerson(state, agents.get(0));
753
		}else{
754
			return getOrMakeTeam(state, agents);
755
		}
756
	}
757

    
758
	private Person getOrMakePrimaryCollector(DerivedUnitFacade facade, String primaryCollector, SpecimenCdmExcelImportState state) {
759
		if (StringUtils.isBlank(primaryCollector)){
760
			return null;
761
		}
762
		AgentBase<?> collector = facade.getCollector();
763
		List<Person> collectors = new ArrayList<Person>();
764
		if (collector.isInstanceOf(Team.class) ){
765
			Team team = CdmBase.deproxy(collector, Team.class);
766
			collectors.addAll(team.getTeamMembers());
767
		}else if (collector.isInstanceOf(Person.class)){
768
			collectors.add(CdmBase.deproxy(collector, Person.class));
769
		}else{
770
			throw new IllegalStateException("Unknown subclass of agentbase: " + collector.getClass().getName() );
771
		}
772
		for (Person person :collectors){
773
			if (primaryCollector.equalsIgnoreCase(person.getTitleCache())){
774
				return person;
775
			}
776
			if (primaryCollector.equalsIgnoreCase(person.getNomenclaturalTitle())){
777
				return person;
778
			}
779
		}
780
		String message = "Primary Agent '%s' could not be determined in collector(s) in line %d";
781
		message = String.format(message, primaryCollector, state.getCurrentLine());
782
		logger.warn(message);
783
		return null;
784
	}
785

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

    
789
		Team result = state.getTeam(key);
790
		if (result == null){
791
			result = Team.NewInstance();
792
			for (String member : agents){
793
				Person person = getOrMakePerson(state, member);
794
				result.addTeamMember(person);
795
			}
796
			state.putTeam(key, result);
797
		}
798
		return result;
799
	}
800

    
801
	private Person getOrMakePerson(SpecimenCdmExcelImportState state, String value) {
802
		Person result = state.getPerson(value);
803
		if (result == null){
804
			result = Person.NewInstance();
805
			result.setTitleCache(value, true);
806
			state.putPerson(value, result);
807
		}
808
		return result;
809
	}
810

    
811
	private Reference getOrMakeReference(SpecimenCdmExcelImportState state, String value) {
812
		Reference result = state.getReference(value);
813
		if (result == null){
814
			result = ReferenceFactory.newGeneric();
815
			result.setTitleCache(value, true);
816
			state.putReference(value, result);
817
		}
818
		return result;
819
	}
820

    
821

    
822

    
823
	private Collection getOrMakeCollection(SpecimenCdmExcelImportState state, String collectionCode, String collectionString) {
824
		Collection result = state.getCollection(collectionCode);
825
		if (result == null){
826
			result = Collection.NewInstance();
827
			result.setCode(collectionCode);
828
			result.setName(collectionString);
829
			state.putCollection(collectionCode, result);
830
		}
831
		return result;
832
	}
833

    
834

    
835
	private TaxonNameBase<?, ?> getTaxonName(SpecimenCdmExcelImportState state, String name) {
836
		TaxonNameBase<?,?> result = null;
837
		result = state.getName(name);
838
		if (result != null){
839
			return result;
840
		}
841
		List<TaxonNameBase<?,?>> list = getNameService().findNamesByTitle(name);
842
		//TODO better strategy to find best name, e.g. depending on the classification it is used in
843
		if (! list.isEmpty()){
844
			result = list.get(0);
845
		}
846
		if (result == null){
847
			NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
848
			NomenclaturalCode code = state.getConfig().getNomenclaturalCode();
849
			result = parser.parseFullName(name, code, null);
850

    
851
		}
852
		if (result != null){
853
			state.putName(name, result);
854
		}
855
		return result;
856
	}
857

    
858
	private SpecimenTypeDesignationStatus getSpecimenTypeStatus(SpecimenCdmExcelImportState state, String key)  {
859
		SpecimenTypeDesignationStatus result = null;
860
		try {
861
			result = state.getTransformer().getSpecimenTypeDesignationStatusByKey(key);
862
			if (result == null){
863
				String message = "Type status not recognized for %s in line %d";
864
				message = String.format(message, key, state.getCurrentLine());
865
				logger.warn(message);
866
			}
867
			return result;
868
		} catch (UndefinedTransformerMethodException e) {
869
			throw new RuntimeException("getSpecimenTypeDesignationStatusByKey not yet implemented");
870
		}
871

    
872

    
873
	}
874

    
875

    
876
	private void handleExactLocation(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
877

    
878
		//reference system
879
		ReferenceSystem refSys = null;
880
		if (StringUtils.isNotBlank(row.getReferenceSystem())){
881
			String strRefSys = row.getReferenceSystem().trim().replaceAll("\\s", "");
882
			UUID refUuid;
883
			try {
884
				refSys = state.getTransformer().getReferenceSystemByKey(strRefSys);
885
				if (refSys == null){
886
					//TODO we still need user defined Reference Systems here
887
					refUuid = state.getTransformer().getReferenceSystemUuid(strRefSys);
888
					if (refUuid == null){
889
						String message = "Unknown reference system %s in line %d";
890
						message = String.format(message, strRefSys, state.getCurrentLine());
891
						logger.warn(message);
892
					}
893
					refSys = getReferenceSystem(state, refUuid, strRefSys, strRefSys, strRefSys, null);
894
				}
895

    
896
			} catch (UndefinedTransformerMethodException e) {
897
				throw new RuntimeException(e);
898
			}
899
		}
900

    
901

    
902

    
903
		// lat/ long /error
904
		try {
905
			String longitude = row.getLongitude();
906
			String latitude = row.getLatitude();
907
			Integer errorRadius = null;
908
			if (StringUtils.isNotBlank(row.getErrorRadius())){
909
				try {
910
					errorRadius = Integer.valueOf(row.getErrorRadius());
911
				} catch (NumberFormatException e) {
912
					String message = "Error radius %s could not be transformed to Integer in line %d";
913
					message = String.format(message, row.getErrorRadius(), state.getCurrentLine());
914
					logger.warn(message);
915
				}
916
			}
917
			//all
918
			facade.setExactLocationByParsing(longitude, latitude, refSys, errorRadius);
919
		} catch (ParseException e) {
920
			String message = "Problems when parsing exact location for line %d";
921
			message = String.format(message, state.getCurrentLine());
922
			logger.warn(message);
923

    
924
		}
925

    
926

    
927

    
928
	}
929

    
930

    
931
	/*
932
	 * Set the current Country
933
	 * Search in the DB if the isoCode is known
934
	 * If not, search if the country name is in the DB
935
	 * If not, create a new Label with the Level Country
936
	 * @param iso: the country iso code
937
	 * @param fullName: the country's full name
938
	 * @param app: the CDM application controller
939
	 */
940
	private void handleCountry(DerivedUnitFacade facade, SpecimenRow row, SpecimenCdmExcelImportState state) {
941

    
942
		if (StringUtils.isNotBlank(row.getIsoCountry())){
943
			NamedArea country = getOccurrenceService().getCountryByIso(row.getIsoCountry());
944
			if (country != null){
945
				facade.setCountry(country);
946
				return;
947
			}
948
		}
949
		if (StringUtils.isNotBlank(row.getCountry())){
950
			List<Country> countries = getOccurrenceService().getCountryByName(row.getCountry());
951
			if (countries.size() >0){
952
				facade.setCountry(countries.get(0));
953
			}else{
954
				UUID uuid = UUID.randomUUID();
955
				String label = row.getCountry();
956
				String text = row.getCountry();
957
				String labelAbbrev = null;
958
				NamedAreaType areaType = NamedAreaType.ADMINISTRATION_AREA();
959
				NamedAreaLevel level = NamedAreaLevel.COUNTRY();
960
				NamedArea newCountry = this.getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level);
961
				facade.setCountry(newCountry);
962
			}
963
		}
964
	}
965

    
966
	@Override
967
	protected void secondPass(SpecimenCdmExcelImportState state) {
968
		//no second path defined yet
969
		return;
970
	}
971

    
972

    
973
	@Override
974
	protected String getWorksheetName() {
975
		return WORKSHEET_NAME;
976
	}
977

    
978
	@Override
979
	protected boolean needsNomenclaturalCode() {
980
		return false;
981
	}
982

    
983

    
984
	/* (non-Javadoc)
985
	 * @see eu.etaxonomy.cdm.io.excel.common.ExcelTaxonOrSpecimenImportBase#createDataHolderRow()
986
	 */
987
	@Override
988
	protected SpecimenRow createDataHolderRow() {
989
		return new SpecimenRow();
990
	}
991

    
992

    
993

    
994

    
995
	/* (non-Javadoc)
996
	 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doCheck(eu.etaxonomy.cdm.io.common.IoStateBase)
997
	 */
998
	@Override
999
	protected boolean doCheck(SpecimenCdmExcelImportState state) {
1000
		logger.warn("Validation not yet implemented for " + this.getClass().getSimpleName());
1001
		return true;
1002
	}
1003

    
1004

    
1005

    
1006
	@Override
1007
	protected boolean isIgnore(SpecimenCdmExcelImportState state) {
1008
		return !state.getConfig().isDoSpecimen();
1009
	}
1010

    
1011

    
1012
}
(5-5/12)