Project

General

Profile

Download (62.7 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.common;
11

    
12
import java.net.MalformedURLException;
13
import java.net.URISyntaxException;
14
import java.sql.ResultSet;
15
import java.sql.SQLException;
16
import java.util.ArrayList;
17
import java.util.Arrays;
18
import java.util.List;
19
import java.util.Set;
20
import java.util.UUID;
21

    
22
import org.apache.log4j.Logger;
23

    
24
import eu.etaxonomy.cdm.api.application.ICdmRepository;
25
import eu.etaxonomy.cdm.api.service.pager.Pager;
26
import eu.etaxonomy.cdm.common.URI;
27
import eu.etaxonomy.cdm.common.media.CdmImageInfo;
28
import eu.etaxonomy.cdm.io.common.mapping.IInputTransformer;
29
import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;
30
import eu.etaxonomy.cdm.io.markup.MarkupTransformer;
31
import eu.etaxonomy.cdm.model.agent.Person;
32
import eu.etaxonomy.cdm.model.agent.Team;
33
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
34
import eu.etaxonomy.cdm.model.common.AnnotationType;
35
import eu.etaxonomy.cdm.model.common.CdmBase;
36
import eu.etaxonomy.cdm.model.common.ExtensionType;
37
import eu.etaxonomy.cdm.model.common.ICdmBase;
38
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
39
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
40
import eu.etaxonomy.cdm.model.common.Language;
41
import eu.etaxonomy.cdm.model.common.Marker;
42
import eu.etaxonomy.cdm.model.common.MarkerType;
43
import eu.etaxonomy.cdm.model.description.DescriptionBase;
44
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
45
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
46
import eu.etaxonomy.cdm.model.description.Feature;
47
import eu.etaxonomy.cdm.model.description.MeasurementUnit;
48
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
49
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
50
import eu.etaxonomy.cdm.model.description.State;
51
import eu.etaxonomy.cdm.model.description.StatisticalMeasure;
52
import eu.etaxonomy.cdm.model.description.TaxonDescription;
53
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
54
import eu.etaxonomy.cdm.model.description.TextData;
55
import eu.etaxonomy.cdm.model.location.Country;
56
import eu.etaxonomy.cdm.model.location.NamedArea;
57
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
58
import eu.etaxonomy.cdm.model.location.NamedAreaType;
59
import eu.etaxonomy.cdm.model.location.ReferenceSystem;
60
import eu.etaxonomy.cdm.model.media.ImageFile;
61
import eu.etaxonomy.cdm.model.media.Media;
62
import eu.etaxonomy.cdm.model.media.MediaRepresentation;
63
import eu.etaxonomy.cdm.model.name.HybridRelationship;
64
import eu.etaxonomy.cdm.model.name.INonViralName;
65
import eu.etaxonomy.cdm.model.name.NameRelationship;
66
import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
67
import eu.etaxonomy.cdm.model.name.Rank;
68
import eu.etaxonomy.cdm.model.name.RankClass;
69
import eu.etaxonomy.cdm.model.name.TaxonName;
70
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
71
import eu.etaxonomy.cdm.model.reference.IOriginalSource;
72
import eu.etaxonomy.cdm.model.reference.ISourceable;
73
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
74
import eu.etaxonomy.cdm.model.reference.Reference;
75
import eu.etaxonomy.cdm.model.taxon.Classification;
76
import eu.etaxonomy.cdm.model.taxon.Synonym;
77
import eu.etaxonomy.cdm.model.taxon.Taxon;
78
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
79
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
80
import eu.etaxonomy.cdm.model.term.DefinedTerm;
81
import eu.etaxonomy.cdm.model.term.DefinedTermBase;
82
import eu.etaxonomy.cdm.model.term.OrderedTermVocabulary;
83
import eu.etaxonomy.cdm.model.term.Representation;
84
import eu.etaxonomy.cdm.model.term.TermType;
85
import eu.etaxonomy.cdm.model.term.TermVocabulary;
86

    
87
/**
88
 * @author a.mueller
89
 * @since 01.07.2008
90
 */
91
public abstract class CdmImportBase<CONFIG extends IImportConfigurator, STATE extends ImportStateBase>
92
            extends CdmIoBase<STATE, ImportResult>
93
            implements ICdmImport<CONFIG, STATE>{
94

    
95
    private static final long serialVersionUID = 8730012744209195616L;
96
    private static final Logger logger = Logger.getLogger(CdmImportBase.class);
97

    
98
	protected static final boolean CREATE = true;
99
	protected static final boolean IMAGE_GALLERY = true;
100
	protected static final boolean READ_MEDIA_DATA = true;
101

    
102
	public static final UUID uuidUserDefinedNamedAreaLevelVocabulary = UUID.fromString("255144da-8d95-457e-a327-9752a8f85e5a");
103
	public static final UUID uuidUserDefinedNamedAreaVocabulary = UUID.fromString("b2238399-a3af-4f6d-b7eb-ff5d0899bf1b");
104
	public static final UUID uuidUserDefinedExtensionTypeVocabulary = UUID.fromString("e28c1394-1be8-4847-8b81-ab44eb6d5bc8");
105
	public static final UUID uuidUserDefinedIdentifierTypeVocabulary = UUID.fromString("194b173b-e2c8-49f1-bbfa-d5d51556cf68");
106
	public static final UUID uuidUserDefinedReferenceSystemVocabulary = UUID.fromString("467591a3-10b4-4bf1-9239-f06ece33e90a");
107
	public static final UUID uuidUserDefinedFeatureVocabulary = UUID.fromString("fe5fccb3-a2f2-4b97-b199-6e2743cf1627");
108
	public static final UUID uuidUserDefinedMeasurementUnitVocabulary = UUID.fromString("d5e72bb7-f312-4080-bb86-c695d04a6e66");
109
	public static final UUID uuidUserDefinedStatisticalMeasureVocabulary = UUID.fromString("62a89836-c730-4b4f-a904-3d859dbfc400");
110
	public static final UUID uuidUserDefinedStateVocabulary = UUID.fromString("f7cddb49-8392-4db1-8640-65b48a0e6d13");
111
	public static final UUID uuidUserDefinedTaxonRelationshipTypeVocabulary = UUID.fromString("31a324dc-408d-4877-891f-098db21744c6");
112
	public static final UUID uuidUserDefinedAnnotationTypeVocabulary = UUID.fromString("cd9ecdd2-9cae-4890-9032-ad83293ae883");
113
	public static final UUID uuidUserDefinedMarkerTypeVocabulary = UUID.fromString("5f02a261-fd7d-4fce-bbe4-21472de8cd51");
114
	public static final UUID uuidUserDefinedRankVocabulary = UUID.fromString("4dc57931-38e2-46c3-974d-413b087646ba");
115
	public static final UUID uuidUserDefinedPresenceAbsenceVocabulary = UUID.fromString("6b8a2581-1471-4ea6-b8ad-b2d931cfbc23");
116
	public static final UUID uuidUserDefinedModifierVocabulary = UUID.fromString("2a8b3838-3a95-49ea-9ab2-3049614b5884");
117
	public static final UUID uuidUserDefinedKindOfUnitVocabulary = UUID.fromString("e7c5deb2-f485-4a66-9104-0c5398efd481");
118
	public static final UUID uuidUserDefinedLanguageVocabulary = UUID.fromString("463a96f1-20ba-4a4c-9133-854c1682bd9b");
119
	public static final UUID uuidUserDefinedNomenclaturalStatusTypeVocabulary = UUID.fromString("1a5c7745-5588-4151-bc43-9ca22561977b");
120

    
121

    
122
	private static final String UuidOnly = "UUIDOnly";
123
	private static final String UuidLabel = "UUID or label";
124
	private static final String UuidLabelAbbrev = "UUID, label or abbreviation";
125
	private static final String UuidAbbrev = "UUID or abbreviation";
126

    
127
	private final static String authorSeparator = ", ";
128
    private final static String lastAuthorSeparator = " & ";
129

    
130
    public enum TermMatchMode{
131
		UUID_ONLY(0, UuidOnly)
132
		,UUID_LABEL(1, UuidLabel)
133
		,UUID_LABEL_ABBREVLABEL(2, UuidLabelAbbrev)
134
		,UUID_ABBREVLABEL(3, UuidAbbrev)
135
		;
136

    
137
		private final int id;
138
		private final String representation;
139
		private TermMatchMode(int id, String representation){
140
			this.id = id;
141
			this.representation = representation;
142
		}
143
		public int getId() {
144
			return id;
145
		}
146
		public String getRepresentation() {
147
			return representation;
148
		}
149
		public TermMatchMode valueOf(int id){
150
			switch (id){
151
				case 0: return UUID_ONLY;
152
				case 1: return UUID_LABEL;
153
				case 2: return UUID_LABEL_ABBREVLABEL;
154
				case 3: return UUID_ABBREVLABEL;
155
				default: return UUID_ONLY;
156
			}
157
 		}
158
	}
159

    
160
    protected ICdmRepository repository;
161

    
162
    @Override
163
    public void setRepository(ICdmRepository repository){
164
        this.repository = repository;
165
    }
166

    
167
    @Override
168
    protected ImportResult getNoDataResult(STATE state) {
169
        return ImportResult.NewNoDataInstance();
170
    }
171

    
172
    @Override
173
    protected ImportResult getDefaultResult(STATE state) {
174
        return ImportResult.NewInstance();
175
    }
176

    
177
	protected Classification makeTree(STATE state, Reference reference){
178
		String treeName = "Classification (Import)";
179
		if (reference != null && isNotBlank(reference.getTitleCache())){
180
			treeName = reference.getTitleCache();
181
		}
182
		Classification tree = Classification.NewInstance(treeName);
183
		tree.setReference(reference);
184

    
185
		// use defined uuid for first tree
186
		CONFIG config = (CONFIG)state.getConfig();
187
		if (state.countTrees() < 1 ){
188
			tree.setUuid(config.getClassificationUuid());
189
		}
190
		getClassificationService().save(tree);
191
		state.putTree(reference, tree);
192
		return tree;
193
	}
194

    
195

    
196
	/**
197
	 * Alternative memory saving method variant of
198
	 * {@link #makeTree(STATE state, Reference ref)} which stores only the
199
	 * UUID instead of the full tree in the <code>ImportStateBase</code> by
200
	 * using <code>state.putTreeUuid(ref, tree);</code>
201
	 *
202
	 * @param state
203
	 * @param ref
204
	 * @return
205
	 */
206
	protected Classification makeTreeMemSave(STATE state, Reference ref){
207
		String treeName = "Classification (Import)";
208
		if (ref != null && isNotBlank(ref.getTitleCache())){
209
			treeName = ref.getTitleCache();
210
		}
211
		Classification tree = Classification.NewInstance(treeName);
212
		tree.setReference(ref);
213

    
214

    
215
		// use defined uuid for first tree
216
		CONFIG config = (CONFIG)state.getConfig();
217
		if (state.countTrees() < 1 ){
218
			tree.setUuid(config.getClassificationUuid());
219
		}
220
		getClassificationService().save(tree);
221
		state.putTreeUuid(ref, tree);
222
		return tree;
223
	}
224

    
225
	protected ExtensionType getExtensionType(STATE state, UUID uuid, String label, String text, String labelAbbrev){
226
		return getExtensionType(state, uuid, label, text, labelAbbrev, null);
227
	}
228
	protected ExtensionType getExtensionType(STATE state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<ExtensionType> voc){
229
		if (uuid == null){
230
			uuid = UUID.randomUUID();
231
		}
232
		ExtensionType extensionType = state.getExtensionType(uuid);
233
		if (extensionType == null){
234
			extensionType = (ExtensionType)getTermService().find(uuid);
235
			if (extensionType == null){
236
				extensionType = ExtensionType.NewInstance(text, label, labelAbbrev);
237
				extensionType.setUuid(uuid);
238
				if (voc == null){
239
					boolean isOrdered = false;
240
					voc = getVocabulary(state, TermType.ExtensionType, uuidUserDefinedExtensionTypeVocabulary, "User defined vocabulary for extension types", "User Defined Extension Types", null, null, isOrdered, extensionType);
241
				}
242
				voc.addTerm(extensionType);
243
				getTermService().saveOrUpdate(extensionType);
244
			}
245
			state.putExtensionType(extensionType);
246
		}
247
		return extensionType;
248
	}
249

    
250
	protected DefinedTerm getIdentiferType(STATE state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<DefinedTerm> voc){
251
		if (uuid == null){
252
			uuid = UUID.randomUUID();
253
		}
254
		DefinedTerm identifierType = state.getIdentifierType(uuid);
255
		if (identifierType == null){
256
			identifierType = (DefinedTerm)getTermService().find(uuid);
257
			if (identifierType == null){
258
				identifierType = DefinedTerm .NewIdentifierTypeInstance(text, label, labelAbbrev);
259
				identifierType.setUuid(uuid);
260
				if (voc == null){
261
					boolean isOrdered = false;
262
					voc = getVocabulary(state, TermType.IdentifierType, uuidUserDefinedIdentifierTypeVocabulary, "User defined vocabulary for identifier types", "User Defined Identifier Types", null, null, isOrdered, identifierType);
263
				}
264
				voc.addTerm(identifierType);
265
				getTermService().saveOrUpdate(identifierType);
266
			}
267
			state.putIdentifierType(identifierType);
268
		}
269
		return identifierType;
270
	}
271

    
272

    
273
	protected MarkerType getMarkerType(STATE state, String keyString) {
274
		IInputTransformer transformer = state.getTransformer();
275
		MarkerType markerType = null;
276
		try {
277
			markerType = transformer.getMarkerTypeByKey(keyString);
278
		} catch (UndefinedTransformerMethodException e) {
279
			logger.info("getMarkerTypeByKey not yet implemented for this import");
280
		}
281
		if (markerType == null ){
282
			UUID uuid;
283
			try {
284
				uuid = transformer.getMarkerTypeUuid(keyString);
285
				return getMarkerType(state, uuid, keyString, keyString, keyString);
286
			} catch (UndefinedTransformerMethodException e) {
287
				logger.warn("getMarkerTypeUuid not yet implemented for this import");
288
			}
289
		}
290
		return null;
291
	}
292

    
293
	/**
294
	 * @see #getMarkerType(ImportStateBase, UUID, String, String, String, TermVocabulary, Language)
295
	 */
296
	protected MarkerType getMarkerType(STATE state, UUID uuid, String label, String description, String labelAbbrev){
297
		return getMarkerType(state, uuid, label, description, labelAbbrev, null, null);
298
	}
299

    
300
	protected MarkerType getMarkerType(STATE state, UUID uuid, String label, String description, String labelAbbrev, TermVocabulary<MarkerType> voc){
301
	    return this.getMarkerType(state, uuid, label, description, labelAbbrev, voc, null);
302
	}
303

    
304

    
305
	/**
306
     * @see #getMarkerType(ImportStateBase, UUID, String, String, String, TermVocabulary, Language)
307
     */
308
	protected MarkerType getMarkerType(STATE state, UUID uuid, String label, String description, String labelAbbrev, TermVocabulary<MarkerType> voc, Language language){
309
		if (uuid == null){
310
			uuid = UUID.randomUUID();
311
		}
312
		MarkerType markerType = state.getMarkerType(uuid);
313
		if (markerType == null){
314
			markerType = (MarkerType)getTermService().find(uuid);
315
			if (markerType == null){
316
				markerType = MarkerType.NewInstance(description, label, labelAbbrev);
317
				if (language != null){
318
				    markerType.getRepresentations().iterator().next().setLanguage(language);
319
				}
320
				markerType.setUuid(uuid);
321
				if (voc == null){
322
					boolean isOrdered = false;
323
					voc = getVocabulary(state, TermType.MarkerType, uuidUserDefinedMarkerTypeVocabulary, "User defined vocabulary for marker types", "User Defined Marker Types", null, null, isOrdered, markerType);
324
				}
325
				voc.addTerm(markerType);
326
				getTermService().save(markerType);
327
			}
328
			state.putMarkerType(markerType);
329
		}
330
		return markerType;
331
	}
332

    
333
	protected AnnotationType getAnnotationType(STATE state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<AnnotationType> voc){
334
		if (uuid == null){
335
			uuid = UUID.randomUUID();
336
		}
337
		AnnotationType annotationType = state.getAnnotationType(uuid);
338
		if (annotationType == null){
339
			annotationType = (AnnotationType)getTermService().find(uuid);
340
			if (annotationType == null){
341
				annotationType = AnnotationType.NewInstance(label, text, labelAbbrev);
342
				annotationType.setUuid(uuid);
343
				if (voc == null){
344
					boolean isOrdered = false;
345
					voc = getVocabulary(state, TermType.AnnotationType, uuidUserDefinedAnnotationTypeVocabulary, "User defined vocabulary for annotation types", "User Defined Annotation Types", null, null, isOrdered, annotationType);
346
				}
347

    
348
				voc.addTerm(annotationType);
349
				getTermService().save(annotationType);
350
			}
351
			state.putAnnotationType(annotationType);
352
		}
353
		return annotationType;
354
	}
355

    
356

    
357
	protected ReferenceSystem getReferenceSystem(STATE state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary voc){
358
		if (uuid == null){
359
			uuid = UUID.randomUUID();
360
		}
361
		ReferenceSystem refSystem = state.getReferenceSystem(uuid);
362
		if (refSystem == null){
363
			refSystem = (ReferenceSystem)getTermService().find(uuid);
364
			if (refSystem == null){
365
				refSystem = ReferenceSystem.NewInstance(text, label, labelAbbrev);
366
				if (voc == null){
367
					boolean isOrdered = false;
368
					voc = getVocabulary(state, TermType.ReferenceSystem, uuidUserDefinedReferenceSystemVocabulary, "User defined vocabulary for reference systems", "User Defined Reference System", null, null, isOrdered, refSystem);
369
				}
370
				voc.addTerm(refSystem);
371
				refSystem.setUuid(uuid);
372
				getTermService().save(refSystem);
373
			}
374
			state.putReferenceSystem(refSystem);
375
		}
376
		return refSystem;
377

    
378
	}
379

    
380

    
381

    
382
	protected Rank getRank(STATE state, UUID uuid, String label, String text, String labelAbbrev,OrderedTermVocabulary<Rank> voc, Rank lowerRank, RankClass rankClass){
383
		if (uuid == null){
384
			uuid = UUID.randomUUID();
385
		}
386
		Rank rank = state.getRank(uuid);
387
		if (rank == null){
388
			rank = (Rank)getTermService().find(uuid);
389
			if (rank == null){
390
				rank = Rank.NewInstance(rankClass, text, label, labelAbbrev);
391
				if (voc == null){
392
					boolean isOrdered = true;
393
					voc = (OrderedTermVocabulary)getVocabulary(state, TermType.Rank, uuidUserDefinedRankVocabulary, "User defined vocabulary for ranks", "User Defined Reference System", null, null, isOrdered, rank);
394
				}
395
				if (lowerRank == null){
396
					voc.addTerm(rank);
397
				}else{
398
					voc.addTermAbove(rank, lowerRank);
399
				}
400
				rank.setUuid(uuid);
401
				getTermService().save(rank);
402
			}
403
			state.putRank(rank);
404
		}
405
		return rank;
406

    
407
	}
408

    
409
	/**
410
	 * Returns a named area for a given uuid by first . If the named area does not
411
	 */
412
	protected NamedArea getNamedArea(STATE state, UUID uuid, String label, String text, String labelAbbrev, NamedAreaType areaType, NamedAreaLevel level){
413
		return getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level, null, null);
414
	}
415

    
416
	protected NamedArea getNamedArea(STATE state, UUID uuid, String label, String text, String labelAbbrev, NamedAreaType areaType,
417
	        NamedAreaLevel level, TermVocabulary<?> voc, TermMatchMode matchMode){
418
		return getNamedArea(state, uuid, label, text, labelAbbrev, areaType, level, voc, matchMode, null);
419
	}
420

    
421
	protected NamedArea getNamedArea(STATE state, UUID uuid, String label, String text, String labelAbbrev, NamedAreaType areaType,
422
	        NamedAreaLevel level, TermVocabulary voc, TermMatchMode matchMode, List<TermVocabulary<NamedArea>> vocabularyPreference){
423
		Class<NamedArea> clazz = NamedArea.class;
424
		if (uuid == null){
425
			uuid = UUID.randomUUID();
426
		}
427
		if (matchMode == null){
428
			matchMode = TermMatchMode.UUID_ONLY;
429
		}
430
		NamedArea namedArea = state.getNamedArea(uuid);
431
		if (namedArea == null){
432
			DefinedTermBase<?> term = getTermService().find(uuid);
433
			namedArea = CdmBase.deproxy(term,NamedArea.class);
434

    
435
			if (vocabularyPreference == null){
436
				vocabularyPreference =  new ArrayList<>();
437
			}
438
			if (vocabularyPreference.isEmpty()){  //add TDWG vocabulary if preferences are empty
439
				vocabularyPreference.add(Country.GERMANY().getVocabulary());
440
				vocabularyPreference.add(TdwgAreaProvider.getAreaByTdwgAbbreviation("GER").getVocabulary());
441
			}
442

    
443
			//TODO matching still experimental
444
			if (namedArea == null && (matchMode.equals(TermMatchMode.UUID_LABEL) || matchMode.equals(TermMatchMode.UUID_LABEL_ABBREVLABEL ))){
445
				//TODO test
446
				Pager<NamedArea> areaPager = getTermService().findByTitleWithRestrictions(clazz, label, null, null, null, null, null, null);
447
				namedArea = findBestMatchingArea(areaPager, uuid, label, text, labelAbbrev, vocabularyPreference);
448
			}
449
			if (namedArea == null && (matchMode.equals(TermMatchMode.UUID_ABBREVLABEL) || matchMode.equals(TermMatchMode.UUID_LABEL_ABBREVLABEL))){
450
				Pager<NamedArea> areaPager = getTermService().findByRepresentationAbbreviation(labelAbbrev, clazz, null, null);
451
				namedArea = findBestMatchingArea(areaPager, uuid, label, text, labelAbbrev, vocabularyPreference);
452
			}
453

    
454
			if (namedArea == null){
455
				namedArea = NamedArea.NewInstance(text, label, labelAbbrev);
456
				if (voc == null){
457
					boolean isOrdered = true;
458
					voc = getVocabulary(state, TermType.NamedArea, uuidUserDefinedNamedAreaVocabulary, "User defined vocabulary for named areas", "User Defined Named Areas", null, null, isOrdered, namedArea);
459
				}
460
				voc.addTerm(namedArea);
461
				namedArea.setType(areaType);
462
				namedArea.setLevel(level);
463
				namedArea.setUuid(uuid);
464
				getTermService().saveOrUpdate(namedArea);
465
			}
466
			state.putNamedArea(namedArea);
467
		}
468
		return namedArea;
469
	}
470

    
471

    
472
	private NamedArea findBestMatchingArea(Pager<NamedArea> areaPager, UUID uuid, String label, String text, String abbrev, List<TermVocabulary<NamedArea>> vocabularyPreference) {
473
		// TODO preliminary implementation
474
		List<NamedArea> list = areaPager.getRecords();
475
		if (list.size() == 0){
476
			return null;
477
		}else if (list.size() == 1){
478
			return list.get(0);
479
		}else if (list.size() > 1){
480
			List<NamedArea> preferredList = new ArrayList<>();
481
			for (TermVocabulary<NamedArea> voc: vocabularyPreference){
482
				for (NamedArea area : list){
483
					if (voc.equals(area.getVocabulary())){
484
						preferredList.add(area);
485
					}
486
				}
487
				if (preferredList.size() > 0){
488
					break;
489
				}
490
			}
491
			if (preferredList.size() > 1 ){
492
				preferredList = getHighestLevelAreas(preferredList);
493
			}else if (preferredList.size() == 0 ){
494
				preferredList = list;
495
			}
496
			if (preferredList.size() == 1 ){
497
				return preferredList.get(0);
498
			}else if (preferredList.size() > 1 ){
499
				String message = "There is more than 1 matching area for %s, %s, %s. As a preliminary implementation I take the first";
500
				message = String.format(message, label, abbrev, text);
501
				logger.warn(message);
502
				return list.get(0);
503
			}
504
		}
505
		return null;
506
	}
507

    
508

    
509
	private List<NamedArea> getHighestLevelAreas(List<NamedArea> preferredList) {
510
		List<NamedArea> result = new ArrayList<>();
511
		for (NamedArea area : preferredList){
512
			if (result.isEmpty()){
513
				result.add(area);
514
			}else {
515
				int compare = compareAreaLevel(area, result.get(0));
516
				if (compare > 0){
517
					result = new ArrayList<>();
518
					result.add(area);
519
				}else if (compare == 0){
520
					result.add(area);
521
				}else{
522
					//do nothing
523
				}
524
			}
525
		}
526

    
527
		return result;
528
	}
529

    
530

    
531
	private int compareAreaLevel(NamedArea area1, NamedArea area2) {
532
		NamedAreaLevel level1 = area1.getLevel();
533
		NamedAreaLevel level2 = area2.getLevel();
534
		if (level1 == null){
535
			return (level2 == null)? 0 : 1;
536
		}else if (level2 == null){
537
			return -1;
538
		}else{
539
			return level1.compareTo(level2);
540
		}
541
	}
542

    
543

    
544
	protected NamedAreaLevel getNamedAreaLevel(STATE state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary<NamedAreaLevel> voc){
545
		if (uuid == null){
546
			uuid = UUID.randomUUID();
547
		}
548
		NamedAreaLevel namedAreaLevel = state.getNamedAreaLevel(uuid);
549
		if (namedAreaLevel == null){
550
			//TODO propPath just for testing
551
			List<String> propPath = Arrays.asList("vocabulary");
552
			DefinedTermBase<NamedAreaLevel> term = getTermService().load(uuid, propPath);
553
			namedAreaLevel = CdmBase.deproxy(term, NamedAreaLevel.class);
554
			if (namedAreaLevel == null){
555
				namedAreaLevel = NamedAreaLevel.NewInstance(text, label, labelAbbrev);
556
				if (voc == null){
557
					boolean isOrdered = true;
558
					voc = getVocabulary(state, TermType.NamedAreaLevel, uuidUserDefinedNamedAreaLevelVocabulary, "User defined vocabulary for named area levels", "User Defined Named Area Levels", null, null, isOrdered, namedAreaLevel);
559
				}
560
				//FIXME only for debugging
561
				Set<NamedAreaLevel> terms = voc.getTerms();
562
				for (NamedAreaLevel level : terms){
563
					TermVocabulary<NamedAreaLevel> levelVoc = level.getVocabulary();
564
					if (levelVoc == null){
565
						logger.error("ONLY FOR DEBUG: Level voc is null");
566
					}else{
567
						logger.info("ONLY FOR DEBUG: Level voc is not null");
568
					}
569
				}
570
				voc.addTerm(namedAreaLevel);
571
				namedAreaLevel.setUuid(uuid);
572
				getTermService().save(namedAreaLevel);
573
			}
574
			state.putNamedAreaLevel(namedAreaLevel);
575
		}
576
		return namedAreaLevel;
577
	}
578

    
579
	/**
580
	 * Returns a {@link State} if it exists. <code>null</code> otherwise.
581
	 * @param state
582
	 * @param uuid
583
	 * @return {@link State}
584
	 */
585
	protected State getStateTerm(STATE state, UUID uuid){
586
		return getStateTerm(state, uuid, null, null, null, null);
587
	}
588

    
589

    
590
	/**
591
	 * Returns a {@link State} for a given uuid by first checking if the uuid has already been used in this import, if not
592
	 * checking if the state exists in the database, if not creating it anew (with vocabulary etc.).
593
	 * If label, text and labelAbbrev are all <code>null</code> no state is created.
594
	 * @param importState
595
	 * @param uuid
596
	 * @param label
597
	 * @param text
598
	 * @param labelAbbrev
599
	 * @param voc
600
	 * @return
601
	 */
602
	protected State getStateTerm(STATE importState, UUID uuid, String label, String text, String labelAbbrev, OrderedTermVocabulary<State> voc) {
603
		if (uuid == null){
604
			return null;
605
		}
606
		State stateTerm = importState.getStateTerm(uuid);
607
		if (stateTerm == null){
608
			stateTerm = CdmBase.deproxy(getTermService().find(uuid), State.class);
609
			if (stateTerm == null && ! hasNoLabel(label, text, labelAbbrev)){
610
				stateTerm = State.NewInstance(text, label, labelAbbrev);
611
				stateTerm.setUuid(uuid);
612
				if (voc == null){
613
					boolean isOrdered = true;
614
					TermVocabulary<State> orderedVoc = getVocabulary(importState, TermType.State, uuidUserDefinedStateVocabulary, "User defined vocabulary for states used by Categorical Data", "User Defined States", null, null, isOrdered, stateTerm);
615
					voc = CdmBase.deproxy(orderedVoc, OrderedTermVocabulary.class);
616
				}
617
				voc.addTerm(stateTerm);
618
				getTermService().save(stateTerm);
619
			}else if (stateTerm == null){
620
                logger.warn("No label provided for new state with uuid " + uuid);
621
            }
622
			importState.putStateTerm(stateTerm);
623
		}
624
		return stateTerm;
625
	}
626

    
627
	/**
628
	 * Returns a feature if it exists, null otherwise.
629
	 * @see #getFeature(ImportStateBase, UUID, String, String, String, TermVocabulary)
630
	 * @param state
631
	 * @param uuid
632
	 * @return
633
	 */
634
	protected Feature getFeature(STATE state, UUID uuid){
635
		return getFeature(state, uuid, null, null, null, null);
636
	}
637

    
638
	/**
639
	 * Returns a feature for a given uuid by first checking if the uuid has already been used in this import, if not
640
	 * checking if the feature exists in the database, if not creating it anew (with vocabulary etc.).
641
	 * If label, text and labelAbbrev are all <code>null</code> no feature is created.
642
	 * @param state
643
	 * @param uuid
644
	 * @param label
645
	 * @param text
646
	 * @param labelAbbrev
647
	 * @return
648
	 */
649
	protected Feature getFeature(STATE state, UUID uuid, String label, String description, String labelAbbrev, TermVocabulary<Feature> voc){
650
		if (uuid == null){
651
			return null;
652
		}
653
		Feature feature = state.getFeature(uuid);
654
		if (feature == null){
655
			feature = (Feature)getTermService().find(uuid);
656
			if (feature == null && ! hasNoLabel(label, description, labelAbbrev)){
657
				feature = Feature.NewInstance(description, label, labelAbbrev);
658
				feature.setUuid(uuid);
659
				feature.setSupportsTextData(true);
660
				if (voc == null){
661
					boolean isOrdered = false;
662
					voc = getVocabulary(state, TermType.Feature, uuidUserDefinedFeatureVocabulary, "User defined vocabulary for features", "User Defined Features", null, null, isOrdered, feature);
663
				}
664
				voc.addTerm(feature);
665
				getTermService().save(feature);
666
				state.putFeature(feature);
667
			}
668
		}
669
		return feature;
670
	}
671

    
672
	/**
673
     * Returns a {@link NomenclaturalStatusType nomenclatural status type} for a given uuid by first
674
     * checking if the uuid has already been used in this import, if not
675
     * checking if the status type exists in the database, if not creating it anew (with vocabulary etc.).
676
     * If label, text and labelAbbrev are all <code>null</code> no status type is created.
677
     * @param state
678
     * @param uuid
679
     * @param label
680
     * @param text
681
     * @param language
682
     * @param labelAbbrev
683
     * @return
684
     */
685
    protected NomenclaturalStatusType getNomenclaturalStatusType(STATE state, UUID uuid, String label,
686
            String description, String labelAbbrev, Language language, TermVocabulary<NomenclaturalStatusType> voc){
687
        if (uuid == null){
688
            return null;
689
        }
690
        NomenclaturalStatusType nomStatusType = state.getNomenclaturalStatusType(uuid);
691
        if (nomStatusType == null){
692
            nomStatusType = (NomenclaturalStatusType)getTermService().find(uuid);
693
            if (nomStatusType == null && ! hasNoLabel(label, description, labelAbbrev)){
694
                if (language == null){
695
                    language = Language.UNKNOWN_LANGUAGE();
696
                }
697
                nomStatusType = NomenclaturalStatusType.NewInstance(description, label, labelAbbrev, language);
698
                nomStatusType.setUuid(uuid);
699
                if (voc == null){
700
                    boolean isOrdered = false;
701
                    voc = getVocabulary(state, TermType.NomenclaturalStatusType, uuidUserDefinedNomenclaturalStatusTypeVocabulary, "User defined vocabulary for nomenclatural status type", "User Defined NomenclaturalStatusTypes", null, null, isOrdered, nomStatusType);
702
                }
703
                voc.addTerm(nomStatusType);
704
                getTermService().save(nomStatusType);
705
                state.putNomenclaturalStatusType(nomStatusType);
706
            }
707
        }
708
        return nomStatusType;
709
    }
710

    
711
	protected DefinedTerm getKindOfUnit(STATE state, UUID uuid, String label, String description, String labelAbbrev, TermVocabulary<DefinedTerm> voc){
712
		if (uuid == null){
713
		    uuid = UUID.randomUUID();
714
		}
715
		DefinedTerm unit = state.getKindOfUnit(uuid);
716
		if (unit == null){
717
			unit = (DefinedTerm)getTermService().find(uuid);
718
			if (unit == null && ! hasNoLabel(label, description, labelAbbrev)){
719
				unit = DefinedTerm.NewKindOfUnitInstance(description, label, labelAbbrev);
720
				unit.setUuid(uuid);
721
				if (voc == null){
722
					boolean isOrdered = false;
723
					voc = getVocabulary(state, TermType.KindOfUnit, uuidUserDefinedKindOfUnitVocabulary, "User defined vocabulary for kind-of-units", "User Defined Measurement kind-of-units", null, null, isOrdered, unit);
724
				}
725
				voc.addTerm(unit);
726
				getTermService().save(unit);
727
			}
728
			state.putKindOfUnit(unit);
729
		}
730
		return unit;
731
	}
732

    
733
	/**
734
	 * Returns a {@link MeasurementUnit} for a given uuid by first checking if the uuid has already been used in this import, if not
735
	 * checking if the {@link MeasurementUnit} exists in the database, if not creating it anew (with vocabulary etc.).
736
	 * If label, text and labelAbbrev are all <code>null</code> no {@link MeasurementUnit} is created.
737
	 * @param state
738
	 * @param uuid
739
	 * @param label
740
	 * @param text
741
	 * @param labelAbbrev
742
	 * @return
743
	 */
744
	protected MeasurementUnit getMeasurementUnit(STATE state, UUID uuid, String label, String description, String labelAbbrev, TermVocabulary<MeasurementUnit> voc){
745
		if (uuid == null){
746
			return null;
747
		}
748
		MeasurementUnit unit = state.getMeasurementUnit(uuid);
749
		if (unit == null){
750
			unit = (MeasurementUnit)getTermService().find(uuid);
751
			if (unit == null && ! hasNoLabel(label, description, labelAbbrev)){
752
				unit = MeasurementUnit.NewInstance(description, label, labelAbbrev);
753
				unit.setUuid(uuid);
754
				if (voc == null){
755
					boolean isOrdered = false;
756
					voc = getVocabulary(state, TermType.MeasurementUnit, uuidUserDefinedMeasurementUnitVocabulary, "User defined vocabulary for measurement units", "User Defined Measurement Units", null, null, isOrdered, unit);
757
				}
758
				voc.addTerm(unit);
759
				getTermService().save(unit);
760
			}
761
			state.putMeasurementUnit(unit);
762
		}
763
		return unit;
764
	}
765

    
766
	/**
767
	 * Returns a {@link StatisticalMeasure} for a given uuid by first checking if the uuid has already been used in this import, if not
768
	 * checking if the {@link StatisticalMeasure} exists in the database, if not creating it anew (with vocabulary etc.).
769
	 * If label, text and labelAbbrev are all <code>null</code> no {@link StatisticalMeasure} is created.
770
	 * @param state
771
	 * @param uuid
772
	 * @param label
773
	 * @param text
774
	 * @param labelAbbrev
775
	 * @return
776
	 */
777
	protected StatisticalMeasure getStatisticalMeasure(STATE state, UUID uuid, String label, String description, String labelAbbrev, TermVocabulary<StatisticalMeasure> voc){
778
		if (uuid == null){
779
			return null;
780
		}
781
		StatisticalMeasure statisticalMeasure = state.getStatisticalMeasure(uuid);
782
		if (statisticalMeasure == null){
783
			statisticalMeasure = (StatisticalMeasure)getTermService().find(uuid);
784
			if (statisticalMeasure == null && ! hasNoLabel(label, description, labelAbbrev)){
785
				statisticalMeasure = StatisticalMeasure.NewInstance(description, label, labelAbbrev);
786
				statisticalMeasure.setUuid(uuid);
787
				if (voc == null){
788
					boolean isOrdered = false;
789
					voc = getVocabulary(state, TermType.StatisticalMeasure, uuidUserDefinedStatisticalMeasureVocabulary, "User defined vocabulary for statistical measures", "User Defined Statistical Measures", null, null, isOrdered, statisticalMeasure);
790
				}
791
				voc.addTerm(statisticalMeasure);
792
				getTermService().save(statisticalMeasure);
793
			}
794
			state.putStatisticalMeasure(statisticalMeasure);
795
		}
796
		return statisticalMeasure;
797
	}
798

    
799
	/**
800
	 * Returns a {@link Modifier} for a given uuid by first checking if the uuid has already been used in this import, if not
801
	 * checking if the {@link Modifier} exists in the database, if not creating it anew (with vocabulary etc.).
802
	 * If label, text and labelAbbrev are all <code>null</code> no {@link Modifier} is created.
803
	 * @param state
804
	 * @param uuid
805
	 * @param label
806
	 * @param text
807
	 * @param labelAbbrev
808
	 * @return
809
	 */
810
	protected DefinedTerm getModifier(STATE state, UUID uuid, String label, String description, String labelAbbrev, TermVocabulary<DefinedTerm> voc){
811
		if (uuid == null){
812
			return null;
813
		}
814
		DefinedTerm modifier = state.getModifier(uuid);
815
		if (modifier == null){
816
			modifier = (DefinedTerm)getTermService().find(uuid);
817
			if (modifier == null && ! hasNoLabel(label, description, labelAbbrev)){
818
				modifier = DefinedTerm.NewModifierInstance(description, label, labelAbbrev);
819
				modifier.setUuid(uuid);
820
				if (voc == null){
821
					boolean isOrdered = false;
822
					voc = getVocabulary(state, TermType.Modifier, uuidUserDefinedModifierVocabulary, "User defined vocabulary for modifier", "User Defined Modifier", null, null, isOrdered, modifier);
823
				}
824
				voc.addTerm(modifier);
825
				getTermService().save(modifier);
826
			}
827
			state.putModifier(modifier);
828
		}
829
		return modifier;
830
	}
831

    
832
	/**
833
	 * Returns a taxon relationship type for a given uuid by first checking if the uuid has already been used in this import, if not
834
	 * checking if the taxon relationship type exists in the database, if not creating it anew (with vocabulary etc.).
835
	 * If label, text and labelAbbrev are all <code>null</code> no taxon relationship type is created.
836
	 * @return
837
	 */
838
	public TaxonRelationshipType getTaxonRelationshipType(STATE state, UUID uuid, String label, String description, String labelAbbrev,
839
	        String reverseLabel, String reverseDescription, String reverseAbbrev, TermVocabulary<TaxonRelationshipType> voc){
840
		if (uuid == null){
841
			return null;
842
		}
843
		TaxonRelationshipType relType = state.getTaxonRelationshipType(uuid);
844
		if (relType == null){
845
			relType = (TaxonRelationshipType)getTermService().find(uuid);
846
			if (relType == null && ! hasNoLabel(label, description, labelAbbrev)){
847
				relType = TaxonRelationshipType.NewInstance(description, label, labelAbbrev, false, false);
848
				if (!hasNoLabel(reverseLabel, reverseDescription, reverseAbbrev)){
849
				    relType.addInverseRepresentation(Representation.NewInstance(reverseDescription, reverseLabel, reverseAbbrev, Language.DEFAULT()));
850
				}
851
				relType.setUuid(uuid);
852
				if (voc == null){
853
					boolean isOrdered = true;
854
					voc = getVocabulary(state, TermType.TaxonRelationshipType, uuidUserDefinedTaxonRelationshipTypeVocabulary, "User defined vocabulary for taxon relationship types", "User Defined Taxon Relationship Types", null, null, isOrdered, relType);
855
				}
856
				voc.addTerm(relType);
857
				getTermService().save(relType);
858
			}
859
			state.putTaxonRelationshipType(relType);
860
		}
861
		return relType;
862
	}
863

    
864
	private boolean hasNoLabel(String label, String text, String labelAbbrev) {
865
		return label == null && text == null && labelAbbrev == null;
866
	}
867

    
868
	protected PresenceAbsenceTerm getPresenceTerm(STATE state, UUID uuid, String label, String text, String labelAbbrev, boolean isAbsenceTerm){
869
	    return getPresenceTerm(state, uuid, label, text, labelAbbrev, isAbsenceTerm, null);
870
	}
871

    
872

    
873
	/**
874
	 * Returns a presence term for a given uuid by first ...
875
	 * @param state
876
	 * @param uuid
877
	 * @param label
878
	 * @param text
879
	 * @param labelAbbrev
880
	 * @return
881
	 */
882
	protected PresenceAbsenceTerm getPresenceTerm(STATE state, UUID uuid, String label, String text, String labelAbbrev, boolean isAbsenceTerm, TermVocabulary<PresenceAbsenceTerm> voc){
883
		if (uuid == null){
884
			return null;
885
		}
886
		PresenceAbsenceTerm presenceTerm = state.getPresenceAbsenceTerm(uuid);
887
		if (presenceTerm == null){
888
			presenceTerm = (PresenceAbsenceTerm)getTermService().find(uuid);
889
			if (presenceTerm == null){
890
				presenceTerm = PresenceAbsenceTerm.NewPresenceInstance(text, label, labelAbbrev);
891
				presenceTerm.setUuid(uuid);
892
				presenceTerm.setAbsenceTerm(isAbsenceTerm);
893
				//set vocabulary ; FIXME use another user-defined vocabulary
894
				if (voc == null){
895
                    boolean isOrdered = true;
896
                    voc = getVocabulary(state, TermType.PresenceAbsenceTerm, uuidUserDefinedPresenceAbsenceVocabulary, "User defined vocabulary for distribution status", "User Defined Distribution Status", null, null, isOrdered, presenceTerm);
897
                }
898
				voc.addTerm(presenceTerm);
899
				getTermService().save(presenceTerm);
900
			}
901
			state.putPresenceAbsenceTerm(presenceTerm);
902
		}
903
		return presenceTerm;
904
	}
905

    
906
    /**
907
     * Retrieves the language for the given uuid. If language does not exist
908
     * <code>null</code> is returned.
909
     */
910
    protected Language getLanguage(STATE state, UUID uuid){
911
        return getLanguage(state, uuid, null, null, null, null);
912
    }
913

    
914
	/**
915
	 * Returns a language for a given uuid by first ...
916
	 * @param state
917
	 * @param uuid
918
	 * @param label
919
	 * @param text
920
	 * @param labelAbbrev
921
	 * @return
922
	 */
923
	protected Language getLanguage(STATE state, UUID uuid, String label, String text, String labelAbbrev){
924
		return getLanguage(state, uuid, label, text, labelAbbrev, null);
925
	}
926

    
927
	protected Language getLanguage(STATE state, UUID uuid, String label, String text, String labelAbbrev, TermVocabulary voc){
928
		if (uuid == null){
929
			return null;
930
		}
931
		Language language = state.getLanguage(uuid);
932
		if (language == null){
933
			language = (Language)getTermService().find(uuid);
934
			if (language == null){
935
				if (text == null && label == null && labelAbbrev == null){
936
				    return null;
937
				}
938
			    language = Language.NewInstance(text, label, labelAbbrev);
939

    
940
				language.setUuid(uuid);
941
				if (voc == null){
942
					UUID uuidLanguageVoc = uuidUserDefinedLanguageVocabulary;
943
					boolean isOrdered = false;
944
					voc = getVocabulary(state, TermType.Language, uuidLanguageVoc, "User defined languages", "User defined languages", "User defined languages", null, isOrdered, language);
945
				}
946
				//set vocabulary ; FIXME use another user-defined vocabulary
947

    
948
				voc.addTerm(language);
949
				getTermService().save(language);
950
			}
951
			state.putLanguage(language);
952
		}
953
		return language;
954
	}
955

    
956
    protected <T extends DefinedTermBase> TermVocabulary<T> getVocabulary(STATE state, TermType termType, UUID uuid, String description, String label,
957
            String abbrev, URI termSourceUri, boolean isOrdered, T type) {
958

    
959
        TermVocabulary<T> voc = state != null? state.getTermedVocabulary(uuid): null;
960
        if (voc != null){
961
            return voc;
962
        }
963

    
964
        List<String> propPath = Arrays.asList(new String[]{"terms"});
965
        voc = getVocabularyService().load(uuid, propPath);
966
        Class<T> clazz = null;
967
        if (voc == null){
968
            if (isOrdered){
969
                voc = OrderedTermVocabulary.NewInstance(termType, description, label, abbrev, termSourceUri);
970
            }else{
971
                voc = TermVocabulary.NewInstance(termType, clazz, description, label, abbrev, termSourceUri);
972
            }
973
            voc.setUuid(uuid);
974
            getVocabularyService().save(voc);
975
        }
976
        state.putTermedVocabularyMap(voc);
977
        return voc;
978
    }
979

    
980
	/**
981
	 * Adds an orginal source to a sourceable objects (implemented for Identifiable entity and description element.
982
	 * If cdmBase is not sourceable nothing happens.
983
	 * TODO Move to DbImportBase once this exists.
984
	 * TODO also implemented in DbImportObjectCreationMapper (reduce redundance)
985
	 * @param rs
986
	 * @param cdmBase
987
	 * @param dbIdAttribute
988
	 * @param namespace
989
	 * @param citation
990
	 * @throws SQLException
991
	 */
992
	public void addOriginalSource(ICdmBase cdmBase, Object idAttributeValue, String namespace, Reference citation)  {
993
		if (cdmBase instanceof ISourceable ){
994
			IOriginalSource source;
995
			ISourceable sourceable = (ISourceable<?>)cdmBase;
996
			Object id = idAttributeValue;
997
			String strId = String.valueOf(id);
998
			String microCitation = null;
999
			OriginalSourceType type = OriginalSourceType.Import;
1000
			if (cdmBase instanceof IdentifiableEntity){
1001
				source = IdentifiableSource.NewInstance(type, strId, namespace, citation, microCitation);
1002
			}else if (cdmBase instanceof DescriptionElementBase){
1003
				source = DescriptionElementSource.NewInstance(type, strId, namespace, citation, microCitation);
1004
			}else{
1005
				logger.warn("ISourceable not beeing identifiable entities or description element base are not yet supported. CdmBase is of type " + cdmBase.getClass().getName() + ". Original source not added.");
1006
				return;
1007
			}
1008
			sourceable.addSource(source);
1009
		}else if (cdmBase != null){
1010
			logger.warn("Sourced object does not implement ISourceable: " + cdmBase.getClass() + "," + cdmBase.getUuid());
1011
		}else{
1012
			logger.warn("Sourced object is null");
1013
		}
1014
	}
1015

    
1016
	/**
1017
	 * @see #addOriginalSource(CdmBase, Object, String, Reference)
1018
	 * @param rs
1019
	 * @param cdmBase
1020
	 * @param dbIdAttribute
1021
	 * @param namespace
1022
	 * @param citation
1023
	 * @throws SQLException
1024
	 */
1025
	public void addOriginalSource(ResultSet rs, CdmBase cdmBase, String dbIdAttribute, String namespace, Reference citation) throws SQLException {
1026
		Object id = rs.getObject(dbIdAttribute);
1027
		addOriginalSource(cdmBase, id, namespace, citation);
1028
	}
1029

    
1030

    
1031
	/**
1032
	 * If the child taxon is missing genus or species epithet information and the rank is below <i>genus</i>
1033
	 * or <i>species</i> respectively the according epithets are taken from the parent taxon.
1034
	 * If the name is an autonym and has no combination author/basionym author the authors are taken from
1035
	 * the parent.
1036
	 * @param parentTaxon
1037
	 * @param childTaxon
1038
	 */
1039
	protected void fillMissingEpithetsForTaxa(Taxon parentTaxon, Taxon childTaxon) {
1040
		if (parentTaxon == null){
1041
			logger.warn("Parent taxon is null. Missing name parts can not be taken from parent");
1042
			return;
1043
		}
1044
		INonViralName parentName = parentTaxon.getName();
1045
		INonViralName childName = childTaxon.getName();
1046
		fillMissingEpithets(parentName, childName);
1047
	}
1048

    
1049
	/**
1050
	 * If the child name is missing genus or species epithet information and the rank is below <i>genus</i>
1051
	 * or <i>species</i> respectively the according epithets are taken from the parent name.
1052
	 * If the name is an autonym and has no combination author/basionym author the authors are taken from
1053
	 * the parent.
1054
	 * @param parentTaxon
1055
	 * @param childTaxon
1056
	 */
1057
	protected void fillMissingEpithets(INonViralName parentName, INonViralName childName) {
1058
		if (isBlank(childName.getGenusOrUninomial()) && childName.getRank().isLower(Rank.GENUS()) ){
1059
			childName.setGenusOrUninomial(parentName.getGenusOrUninomial());
1060
		}
1061

    
1062
		if (isBlank(childName.getSpecificEpithet()) && childName.getRank().isLower(Rank.SPECIES()) ){
1063
			childName.setSpecificEpithet(parentName.getSpecificEpithet());
1064
		}
1065
		if (childName.isAutonym() && childName.getCombinationAuthorship() == null && childName.getBasionymAuthorship() == null ){
1066
			childName.setCombinationAuthorship(parentName.getCombinationAuthorship());
1067
			childName.setBasionymAuthorship(parentName.getBasionymAuthorship());
1068
		}
1069
	}
1070

    
1071
	/**
1072
	 * Returns the taxon description for a taxon. If there are multiple taxon descriptions
1073
	 * an arbitrary one is chosen.
1074
	 * If no taxon description exists, a new one is created if <code>createNewIfNotExists</code>
1075
	 * is <code>true</code>.
1076
	 * @param createNewIfNotExists
1077
	 * @param isImageGallery if true only taxon description being image galleries are considered.
1078
	 * If false only taxon description being no image galleries are considered.
1079
	 * @return
1080
	 */
1081
	public TaxonNameDescription getTaxonNameDescription(TaxonName name, boolean isImageGallery, boolean createNewIfNotExists) {
1082
		Reference ref = null;
1083
		return getTaxonNameDescription(name, ref, isImageGallery, createNewIfNotExists);
1084
	}
1085

    
1086
	/**
1087
	 * Like {@link #getTaxonDescription(Taxon, boolean, boolean)}
1088
	 * Only matches a description if the given reference is a source of the description.<BR>
1089
	 * If a new description is created the given reference will be added as a source.
1090
	 *
1091
	 * @see #getTaxonDescription(Taxon, boolean, boolean)
1092
	 */
1093
	public TaxonNameDescription getTaxonNameDescription(TaxonName name, Reference ref, boolean isImageGallery, boolean createNewIfNotExists) {
1094
		TaxonNameDescription result = null;
1095
		Set<TaxonNameDescription> descriptions= name.getDescriptions();
1096
		for (TaxonNameDescription description : descriptions){
1097
			if (description.isImageGallery() == isImageGallery){
1098
				if (hasCorrespondingSource(ref, description)){
1099
					result = description;
1100
					break;
1101
				}
1102
			}
1103
		}
1104
		if (result == null && createNewIfNotExists){
1105
			result = TaxonNameDescription.NewInstance(name);
1106
			result.setImageGallery(isImageGallery);
1107
			if (ref != null){
1108
				result.addImportSource(null, null, ref, null);
1109
			}
1110
		}
1111
		return result;
1112
	}
1113

    
1114
	/**
1115
	 * Returns the taxon description for a taxon. If there are multiple taxon descriptions
1116
	 * an arbitrary one is chosen.
1117
	 * If no taxon description exists, a new one is created if <code>createNewIfNotExists</code>
1118
	 * is <code>true</code>.
1119
	 * @param createNewIfNotExists
1120
	 * @param isImageGallery if true only taxon description being image galleries are considered.
1121
	 * If false only taxon description being no image galleries are considered.
1122
	 * @return
1123
	 */
1124
	public TaxonDescription getTaxonDescription(Taxon taxon, boolean isImageGallery, boolean createNewIfNotExists) {
1125
		Reference ref = null;
1126
		return getTaxonDescription(taxon, ref, isImageGallery, createNewIfNotExists);
1127
	}
1128

    
1129
	/**
1130
	 * Like {@link #getTaxonDescription(Taxon, boolean, boolean)}
1131
	 * Only matches a description if the given reference is a source of the description.<BR>
1132
	 * If a new description is created the given reference will be added as a source.
1133
	 *
1134
	 * @see #getTaxonDescription(Taxon, boolean, boolean)
1135
	 * @see #getDefaultTaxonDescription(Taxon, boolean, boolean, Reference)
1136
	 */
1137
	public TaxonDescription getTaxonDescription(Taxon taxon, Reference ref, boolean isImageGallery,
1138
	        boolean createNewIfNotExists) {
1139
		TaxonDescription result = null;
1140
		Set<TaxonDescription> descriptions = taxon.getDescriptions();
1141
		for (TaxonDescription description : descriptions){
1142
			if (description.isImageGallery() == isImageGallery){
1143
				if (hasCorrespondingSource(ref, description)){
1144
					result = description;
1145
					break;
1146
				}
1147
			}
1148
		}
1149
		if (result == null && createNewIfNotExists){
1150
			result = TaxonDescription.NewInstance(taxon);
1151
			result.setImageGallery(isImageGallery);
1152
			if (ref != null){
1153
				result.addImportSource(null, null, ref, null);
1154
			}
1155
		}
1156
		return result;
1157
	}
1158

    
1159
	/**
1160
	 * Returns the default taxon description. If no default taxon description exists,
1161
	 * a new one is created, the default flag is set to true and, if a source is passed,
1162
	 * it is added to the new description. Otherwise source has no influence.
1163
	 * @param taxon
1164
	 * @param isImageGallery
1165
	 * @param createNewIfNotExists
1166
	 * @param source
1167
	 * @return the default description
1168
	 * @see #getTaxonDescription(Taxon, Reference, boolean, boolean)
1169
	 */
1170
	public TaxonDescription getDefaultTaxonDescription(Taxon taxon, boolean isImageGallery,
1171
            boolean createNewIfNotExists, Reference source) {
1172
        TaxonDescription result = null;
1173
        Set<TaxonDescription> descriptions= taxon.getDescriptions();
1174
        for (TaxonDescription description : descriptions){
1175
            if (description.isImageGallery() == isImageGallery){
1176
                if (description.isDefault()){
1177
                    result = description;
1178
                    break;
1179
                }
1180
            }
1181
        }
1182
        if (result == null && createNewIfNotExists){
1183
            result = TaxonDescription.NewInstance(taxon);
1184
            result.setImageGallery(isImageGallery);
1185
            result.setDefault(true);
1186
            if (source != null){
1187
                result.addImportSource(null, null, source, null);
1188
            }
1189
        }
1190
        return result;
1191
    }
1192

    
1193
    /**
1194
     * Returns the taxon description with marked as <code>true</code> with the given marker type.
1195
     * If createNewIfNotExists a new description is created if it does not yet exist.
1196
     * For the new description the source and the title are set if not <code>null</code>.
1197
     * @param taxon
1198
     * @param markerType
1199
     * @param isImageGallery
1200
     * @param createNewIfNotExists
1201
     * @param source
1202
     * @param title
1203
     * @return the existing or new taxon description
1204
     */
1205
   public TaxonDescription getMarkedTaxonDescription(Taxon taxon, MarkerType markerType, boolean isImageGallery,
1206
            boolean createNewIfNotExists, Reference source, String title) {
1207
        TaxonDescription result = null;
1208
        Set<TaxonDescription> descriptions= taxon.getDescriptions();
1209
        for (TaxonDescription description : descriptions){
1210
            if (description.isImageGallery() == isImageGallery){
1211
                if (description.hasMarker(markerType, true)){
1212
                    result = description;
1213
                    break;
1214
                }
1215
            }
1216
        }
1217
        if (result == null && createNewIfNotExists){
1218
            result = TaxonDescription.NewInstance(taxon);
1219
            result.setImageGallery(isImageGallery);
1220
            result.addMarker(Marker.NewInstance(markerType, true));
1221
            if (source != null){
1222
                result.addImportSource(null, null, source, null);
1223
            }
1224
            if (isNotBlank(title)){
1225
                result.setTitleCache(title, true);
1226
            }
1227
        }
1228
        return result;
1229
    }
1230

    
1231

    
1232
	/**
1233
	 * Returns the {@link SpecimenDescription specimen description} for a {@link SpecimenOrObservationBase specimen or observation}.
1234
	 * If there are multiple specimen descriptions an arbitrary one is chosen.
1235
	 * If no specimen description exists, a new one is created if <code>createNewIfNotExists</code> is <code>true</code>.
1236
	 * @param createNewIfNotExists
1237
	 * @param isImageGallery if true only specimen description being image galleries are considered.
1238
	 * If false only specimen description being no image galleries are considered.
1239
	 * @return
1240
	 */
1241
	public SpecimenDescription getSpecimenDescription(SpecimenOrObservationBase specimen, boolean isImageGallery, boolean createNewIfNotExists) {
1242
		Reference ref = null;
1243
		return getSpecimenDescription(specimen, ref, isImageGallery, createNewIfNotExists);
1244
	}
1245

    
1246
	/**
1247
	 * Like {@link #getSpecimenDescription(SpecimenOrObservationBase, boolean, boolean)}
1248
	 * Only matches a description if the given reference is a source of the description.<BR>
1249
	 * If a new description is created the given reference will be added as a source.
1250
	 *
1251
	 * @see #getTaxonDescription(Taxon, boolean, boolean)
1252
	 */
1253
	public SpecimenDescription getSpecimenDescription(SpecimenOrObservationBase specimen, Reference ref, boolean isImageGallery, boolean createNewIfNotExists) {
1254
		SpecimenDescription result = null;
1255
		@SuppressWarnings("unchecked")
1256
        Set<SpecimenDescription> descriptions= specimen.getDescriptions();
1257
		for (SpecimenDescription description : descriptions){
1258
			if (description.isImageGallery() == isImageGallery){
1259
				if (hasCorrespondingSource(ref, description)){
1260
					result = description;
1261
					break;
1262
				}
1263
			}
1264
		}
1265
		if (result == null && createNewIfNotExists){
1266
			result = SpecimenDescription.NewInstance(specimen);
1267
			result.setImageGallery(isImageGallery);
1268
			if (ref != null){
1269
				result.addImportSource(null, null, ref, null);
1270
			}
1271
		}
1272
		return result;
1273
	}
1274

    
1275

    
1276
	/**
1277
	 * Returns the textdata that holds general information about a feature for a taxon description.
1278
	 * This is mainly necessary for descriptions that have more than one description element for
1279
	 * a given feature such as 'distribution', 'description' or 'common name'. It may also hold
1280
	 * for hierarchical features where no description element exists for a higher hierarchy level.
1281
	 * Example: the description feature has subfeatures. But some information like authorship, figures,
1282
	 * sources need to be added to the description itself.
1283
	 * Currently a feature placeholder is marked by a marker of type 'feature placeholder'. Maybe in future
1284
	 * there will be a boolean marker in the TextData class itself.
1285
	 *
1286
	 * @param state
1287
	 * @param feature
1288
	 * @param taxon
1289
	 * @param ref
1290
	 * @param createIfNotExists
1291
	 * @return
1292
	 */
1293
	protected TextData getFeaturePlaceholder(STATE state, DescriptionBase<?> description, Feature feature, boolean createIfNotExists) {
1294
		UUID featurePlaceholderUuid = MarkupTransformer.uuidMarkerFeaturePlaceholder;
1295
		for (DescriptionElementBase element : description.getElements()){
1296
			if (element.isInstanceOf(TextData.class)){
1297
				TextData textData = CdmBase.deproxy(element, TextData.class);
1298
				if (textData.getFeature() == null || ! textData.getFeature().equals(feature)){
1299
					continue;
1300
				}
1301
				for (Marker marker : textData.getMarkers()){
1302
					MarkerType markerType = marker.getMarkerType();
1303
					if (markerType != null &&
1304
							markerType.getUuid().equals(featurePlaceholderUuid) &&
1305
							marker.getValue() == true){
1306
						return textData;
1307
					}
1308
				}
1309
			}
1310
		}
1311
		if (createIfNotExists){
1312
			TextData newPlaceholder = TextData.NewInstance(feature);
1313
			MarkerType placeholderMarkerType = getMarkerType(state, featurePlaceholderUuid, "Feature Placeholder", "Feature Placeholder", null);
1314
			Marker marker = Marker.NewInstance(placeholderMarkerType, true);
1315
			newPlaceholder.addMarker(marker);
1316
			description.addElement(newPlaceholder);
1317
			return newPlaceholder;
1318
		}else{
1319
			return null;
1320
		}
1321
	}
1322

    
1323

    
1324

    
1325
	/**
1326
	 * Returns true, if this description has a source with a citation equal to the given reference.
1327
	 * Returns true if the given reference is null.
1328
	 * @param ref
1329
	 * @param description
1330
	 */
1331
	private boolean hasCorrespondingSource(Reference ref, DescriptionBase<?> description) {
1332
		if (ref != null){
1333
			for (IdentifiableSource source : description.getSources()){
1334
				if (ref.equals(source.getCitation())){
1335
					return true;
1336
				}
1337
			}
1338
			return false;
1339
		}
1340
		return true;
1341

    
1342
	}
1343

    
1344

    
1345
	/**
1346
	 * Returns the accepted taxon of a {@link TaxonBase taxon base}. <BR>
1347
	 * If taxonBase is of type taxon the same object is returned. If taxonBase is of type
1348
	 * synonym the accepted taxon is returned if one exists. If no accepted taxon exists
1349
	 * <code>null</code> is returned. If multiple accepted taxa exist the one taxon with the
1350
	 * same secundum reference is returned. If no such single taxon exists an
1351
	 * {@link IllegalStateException illegal state exception} is thrown.
1352
	 * @param taxonBase
1353
	 * @return
1354
	 */
1355
	protected Taxon getAcceptedTaxon(TaxonBase<?> taxonBase) {
1356
		if (taxonBase == null){
1357
			return null;
1358
		}else if(taxonBase.isInstanceOf(Taxon.class)){
1359
			return CdmBase.deproxy(taxonBase, Taxon.class);
1360
		}else if(taxonBase.isInstanceOf(Synonym.class)){
1361
			Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1362
			Taxon acceptedTaxon = synonym.getAcceptedTaxon();
1363
			return acceptedTaxon;
1364
		}else{
1365
			throw new IllegalStateException("Unknown TaxonBase subclass: " + taxonBase.getClass().getName());
1366
		}
1367
	}
1368

    
1369
	protected Media getImageMedia(String uriString, boolean readMediaData) throws MalformedURLException {
1370
	    return getImageMedia(uriString, null, readMediaData);
1371
	}
1372

    
1373
	/**
1374
	 * Creates
1375
	 * @param uriString
1376
	 * @param readDataFromUrl
1377
	 * @see #READ_MEDIA_DATA
1378
	 * @return
1379
	 * @throws MalformedURLException
1380
	 */
1381
	protected Media getImageMedia(String uriString, String uriStrThumb, boolean readMediaData) throws MalformedURLException {
1382
	    return getImageMedia(uriString, null, uriStrThumb, readMediaData);
1383
	}
1384

    
1385
	protected Media getImageMedia(String uriString, String mediumUriString, String uriStrThumb, boolean readMediaData) throws MalformedURLException {
1386

    
1387
	    if( uriString == null){
1388
			return null;
1389
		} else {
1390

    
1391
			try {
1392
			    Media media = Media.NewInstance();
1393

    
1394
			    MediaRepresentation representation = makeMediaRepresentation(uriString, readMediaData);
1395
		        media.addRepresentation(representation);
1396

    
1397

    
1398
                //thumb
1399
				if (uriStrThumb != null){
1400
				    MediaRepresentation thumbRepresentation = makeMediaRepresentation(uriStrThumb, readMediaData);
1401
	                media.addRepresentation(thumbRepresentation);
1402
				}
1403

    
1404
				if (isNotBlank(mediumUriString) ){
1405
                    MediaRepresentation mediumRepresentation = makeMediaRepresentation(mediumUriString, readMediaData);
1406
                    media.addRepresentation(mediumRepresentation);
1407
				}
1408
                return media;
1409

    
1410
			} catch (URISyntaxException e1) {
1411
				String message = "An URISyntaxException occurred when trying to create uri from multimedia objcet string: " +  uriString;
1412
				logger.warn(message);
1413
				fireWarningEvent(message, "unknown location", 4, 0);
1414
				return null;
1415
			}
1416
		}
1417
	}
1418

    
1419
    /**
1420
     * @param uriString
1421
     * @param readMediaData
1422
     * @return
1423
     * @throws URISyntaxException
1424
     */
1425
    private MediaRepresentation makeMediaRepresentation(String uriString, boolean readMediaData) throws URISyntaxException {
1426
        uriString = uriString.replace(" ", "%20");  //replace whitespace
1427
        CdmImageInfo cdmImageInfo = null;
1428
        URI uri = new URI(uriString);
1429

    
1430
        try {
1431
        	if (readMediaData){
1432
        		logger.info("Read media data from: " + uri);
1433
        		cdmImageInfo = CdmImageInfo.NewInstance(uri, 0);
1434
        	}
1435
        } catch (Exception e) {
1436
        	String message = "An error occurred when trying to read image meta data for " + uri.toString() + ": " +  e.getMessage();
1437
        	logger.warn(message);
1438
        	fireWarningEvent(message, "unknown location", 2, 0);
1439
        }
1440
        ImageFile imageFile = ImageFile.NewInstance(uri, null, cdmImageInfo);
1441

    
1442
        MediaRepresentation representation = MediaRepresentation.NewInstance();
1443

    
1444
        if(cdmImageInfo != null){
1445
        	representation.setMimeType(cdmImageInfo.getMimeType());
1446
        	representation.setSuffix(cdmImageInfo.getSuffix());
1447
        }
1448
        representation.addRepresentationPart(imageFile);
1449
        return representation;
1450
    }
1451

    
1452

    
1453
	/**
1454
	 * Retrieves an Integer value from a result set. If the value is NULL null is returned.
1455
	 * ResultSet.getInt() returns 0 therefore we need a special handling for this case.
1456
	 * @param rs
1457
	 * @param columnName
1458
	 * @return
1459
	 * @throws SQLException
1460
	 */
1461
	protected Integer nullSafeInt(ResultSet rs, String columnName) throws SQLException {
1462
		Object intObject = rs.getObject(columnName);
1463
		if (intObject == null){
1464
			return null;
1465
		}else{
1466
			return Integer.valueOf(intObject.toString());
1467
		}
1468
	}
1469

    
1470
	protected Boolean nullSafeBoolean(ResultSet rs, String columnName) throws SQLException {
1471
		Object bitObject = rs.getObject(columnName);
1472
		if (bitObject == null){
1473
			return null;
1474
		}else{
1475
			return Boolean.valueOf(bitObject.toString());
1476
		}
1477
	}
1478

    
1479
	protected Double nullSafeDouble(ResultSet rs, String columnName) throws SQLException {
1480
		Object doubleObject = rs.getObject(columnName);
1481
		if (doubleObject == null){
1482
			return null;
1483
		}else{
1484
			return Double.valueOf(doubleObject.toString());
1485
		}
1486
	}
1487

    
1488
	protected Float nullSafeFloat(ResultSet rs, String columnName) throws SQLException {
1489
		Object doubleObject = rs.getObject(columnName);
1490
		if (doubleObject == null){
1491
			return null;
1492
		}else{
1493
			return Float.valueOf(doubleObject.toString());
1494
		}
1495
	}
1496

    
1497

    
1498
    /**
1499
     * Converts a given string into an integer. If this is not possible
1500
     * an error is logged in the import result with record location and attribute name.
1501
     *
1502
     * @param state
1503
     * @param strToConvert
1504
     * @param recordLocation
1505
     * @param attributeName
1506
     * @return the converted integer
1507
     */
1508
    protected Integer intFromString(STATE state,
1509
            String strToConvert,
1510
            String recordLocation,
1511
            String attributeName) {
1512

    
1513
        if (strToConvert == null){
1514
            return null;
1515
        }
1516
        try {
1517
            Integer result = Integer.valueOf(strToConvert);
1518
            return result;
1519
        } catch (NumberFormatException e) {
1520
            String message = "Text '%s' could not be transformed into integer number for attribute %s";
1521
            message = String.format(message, strToConvert, attributeName);
1522
            state.getResult().addError(message, e, null, recordLocation);
1523
        }
1524
        return null;
1525
    }
1526

    
1527
    /**
1528
     * Converts a given string into a {@link Double}. If this is not possible
1529
     * an error is logged in the import result with record location and attribute name.
1530
     *
1531
     * @param state
1532
     * @param strToConvert
1533
     * @param recordLocation
1534
     * @param attributeName
1535
     * @return the converted integer
1536
     */
1537
    protected Double doubleFromString(STATE state,
1538
            String strToConvert,
1539
            String recordLocation,
1540
            String attributeName) {
1541

    
1542
        if (strToConvert == null){
1543
            return null;
1544
        }
1545
        try {
1546
            Double result = Double.valueOf(strToConvert);
1547
            return result;
1548
        } catch (NumberFormatException e) {
1549
            String message = "Text '%s' could not be transformed into number of type Double for attribute %s";
1550
            message = String.format(message, strToConvert, attributeName);
1551
            state.getResult().addError(message, e, null, recordLocation);
1552
        }
1553
        return null;
1554
    }
1555

    
1556

    
1557
	/**
1558
	 * Returns <code>null</code> for all blank strings. Identity function otherwise.
1559
	 * @param str
1560
	 * @return
1561
	 */
1562
	protected String NB(String str) {
1563
		if (isBlank(str)){
1564
			return null;
1565
		}else{
1566
			return str;
1567
		}
1568
	}
1569

    
1570
	@Override
1571
    public byte[] getByteArray() {
1572
        // TODO Auto-generated method stub
1573
        return null;
1574
    }
1575

    
1576
	public static TeamOrPersonBase<?> parseAuthorString(String authorName){
1577
        TeamOrPersonBase<?> author = null;
1578
        String[] teamMembers = authorName.split(authorSeparator);
1579
        String lastMember;
1580
        String[] lastMembers;
1581
        Person teamMember;
1582
        if (teamMembers.length>1){
1583
            lastMember = teamMembers[teamMembers.length -1];
1584
            lastMembers = lastMember.split(lastAuthorSeparator);
1585
            teamMembers[teamMembers.length -1] = "";
1586
            author = Team.NewInstance();
1587
            for(String member:teamMembers){
1588
                if (!member.equals("")){
1589
                    teamMember = Person.NewInstance();
1590
                    teamMember.setTitleCache(member, true);
1591
                   ((Team)author).addTeamMember(teamMember);
1592
                }
1593
            }
1594
            if (lastMembers != null){
1595
                for(String member:lastMembers){
1596
                   teamMember = Person.NewInstance();
1597
                   teamMember.setTitleCache(member, true);
1598
                   ((Team)author).addTeamMember(teamMember);
1599
                }
1600
            }
1601

    
1602
        } else {
1603
            teamMembers = authorName.split(lastAuthorSeparator);
1604
            if (teamMembers.length>1){
1605
                author = Team.NewInstance();
1606
                for(String member:teamMembers){
1607
                  teamMember = Person.NewInstance();
1608
                  teamMember.setTitleCache(member, true);
1609
                  ((Team)author).addTeamMember(teamMember);
1610

    
1611
                }
1612
            }else{
1613
                if (isNotBlank(authorName)){
1614
                    author = Person.NewInstance();
1615
                    author.setTitleCache(authorName, true);
1616
                }else{
1617
                    return null;
1618
                }
1619

    
1620
            }
1621
        }
1622
        author.getTitleCache();
1623
        return author;
1624
    }
1625

    
1626
    /**
1627
     * Saves name relations. Needed if a name was parsed and has hybrid parents
1628
     * which will not be saved via cascade.
1629
     */
1630
    protected void saveNameRelations(TaxonName name) {
1631
        for (HybridRelationship rel: name.getHybridChildRelations()){
1632
            getNameService().saveOrUpdate(rel.getParentName());
1633
        }
1634
        for (NameRelationship rel: name.getNameRelations()){
1635
            getNameService().saveOrUpdate(rel.getFromName());
1636
            getNameService().saveOrUpdate(rel.getToName());
1637
        }
1638
    }
1639
}
(7-7/65)