2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.io
.wfo
.out
;
12 import java
.util
.ArrayList
;
13 import java
.util
.HashSet
;
14 import java
.util
.Iterator
;
15 import java
.util
.List
;
18 import java
.util
.UUID
;
20 import org
.apache
.commons
.lang3
.StringUtils
;
21 import org
.joda
.time
.DateTime
;
22 import org
.joda
.time
.DateTimeFieldType
;
23 import org
.joda
.time
.Partial
;
24 import org
.joda
.time
.format
.DateTimeFormatter
;
25 import org
.joda
.time
.format
.DateTimeFormatterBuilder
;
26 import org
.springframework
.stereotype
.Component
;
28 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
29 import eu
.etaxonomy
.cdm
.common
.URI
;
30 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
31 import eu
.etaxonomy
.cdm
.filter
.TaxonNodeFilter
;
32 import eu
.etaxonomy
.cdm
.format
.reference
.NomenclaturalSourceFormatter
;
33 import eu
.etaxonomy
.cdm
.io
.common
.CdmExportBase
;
34 import eu
.etaxonomy
.cdm
.io
.common
.ExportResult
.ExportResultState
;
35 import eu
.etaxonomy
.cdm
.io
.common
.TaxonNodeOutStreamPartitioner
;
36 import eu
.etaxonomy
.cdm
.io
.common
.XmlExportState
;
37 import eu
.etaxonomy
.cdm
.io
.common
.mapping
.out
.IExportTransformer
;
38 import eu
.etaxonomy
.cdm
.model
.common
.AnnotatableEntity
;
39 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
40 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
41 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
42 import eu
.etaxonomy
.cdm
.model
.common
.ICdmBase
;
43 import eu
.etaxonomy
.cdm
.model
.common
.Identifier
;
44 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
45 import eu
.etaxonomy
.cdm
.model
.common
.LanguageString
;
46 import eu
.etaxonomy
.cdm
.model
.common
.TimePeriod
;
47 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
48 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
49 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
50 import eu
.etaxonomy
.cdm
.model
.description
.IDescribable
;
51 import eu
.etaxonomy
.cdm
.model
.description
.TextData
;
52 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
53 import eu
.etaxonomy
.cdm
.model
.name
.NameRelationship
;
54 import eu
.etaxonomy
.cdm
.model
.name
.NameRelationshipType
;
55 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
56 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
57 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceBase
;
58 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
59 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
60 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
61 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
62 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
63 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
64 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNodeStatus
;
65 import eu
.etaxonomy
.cdm
.model
.term
.IdentifierType
;
66 import eu
.etaxonomy
.cdm
.strategy
.cache
.agent
.TeamDefaultCacheStrategy
;
69 * Classification or taxon tree exporter into WFO Backbone format.
71 * @see https://dev.e-taxonomy.eu/redmine/issues/10446
81 public class WfoBackboneExport
82 extends CdmExportBase
<WfoBackboneExportConfigurator
,WfoBackboneExportState
,IExportTransformer
,File
>{
84 private static final long serialVersionUID
= -4560488499411723333L;
86 public WfoBackboneExport() {
87 this.ioName
= this.getClass().getSimpleName();
91 public long countSteps(WfoBackboneExportState state
) {
92 TaxonNodeFilter filter
= state
.getConfig().getTaxonNodeFilter();
93 return getTaxonNodeService().count(filter
);
97 protected void doInvoke(WfoBackboneExportState state
) {
100 IProgressMonitor monitor
= state
.getConfig().getProgressMonitor();
101 WfoBackboneExportConfigurator config
= state
.getConfig();
104 if (config
.getTaxonNodeFilter().hasClassificationFilter()) {
105 Classification classification
= getClassificationService()
106 .load(config
.getTaxonNodeFilter().getClassificationFilter().get(0).getUuid());
107 state
.setRootId(classification
.getRootNode().getUuid());
108 } else if (config
.getTaxonNodeFilter().hasSubtreeFilter()) {
109 state
.setRootId(config
.getTaxonNodeFilter().getSubtreeFilter().get(0).getUuid());
112 @SuppressWarnings({ "unchecked", "rawtypes" })
113 TaxonNodeOutStreamPartitioner
<XmlExportState
> partitioner
= TaxonNodeOutStreamPartitioner
.NewInstance(this,
114 state
, state
.getConfig().getTaxonNodeFilter(), 100, monitor
, null);
116 // handleMetaData(state); //FIXME metadata;
117 monitor
.subTask("Start partitioning");
120 String baseUrl
= state
.getConfig().getSourceLinkBaseUrl();
121 if (isBlank(baseUrl
)){
122 String message
= "No base url provided.";
123 state
.getResult().addWarning(message
);
124 } else if (!baseUrl
.startsWith("http://") && !baseUrl
.startsWith("https://")){
125 String message
= "Source link base url is not a http based url.";
126 state
.getResult().addWarning(message
);
130 TaxonNode node
= partitioner
.next();
131 while (node
!= null) {
132 handleTaxonNode(state
, node
);
133 node
= partitioner
.next();
136 state
.getProcessor().createFinalResult(state
);
137 } catch (Exception e
) {
138 state
.getResult().addException(e
,
139 "An unexpected error occurred in main method doInvoke() " + e
.getMessage());
144 private void handleTaxonNode(WfoBackboneExportState state
, TaxonNode taxonNode
) {
146 if (taxonNode
== null) {
147 // TODO 5 taxon node not found
148 String message
= "TaxonNode for given taxon node UUID not found.";
149 state
.getResult().addError(message
);
152 boolean exclude
= filterTaxon(state
, taxonNode
);
155 String parentWfoId
= getParentWfoId(state
, taxonNode
);
156 if (taxonNode
.hasTaxon()) {
158 state
.putTaxonNodeWfoId(taxonNode
, parentWfoId
); //always use parent instead
160 String wfoId
= handleTaxon(state
, taxonNode
, parentWfoId
);
161 state
.putTaxonNodeWfoId(taxonNode
, wfoId
);
164 } catch (Exception e
) {
165 state
.getResult().addException(e
, "An unexpected error occurred when handling taxonNode "
166 + taxonNode
.getUuid() + ": " + e
.getMessage() + e
.getStackTrace());
171 private String
getParentWfoId(WfoBackboneExportState state
, TaxonNode taxonNode
) {
172 TaxonNode parentNode
= taxonNode
.getParent();
173 if (parentNode
== null) {
176 String wfoId
= state
.getTaxonNodeWfoId(parentNode
);
180 wfoId
= parentNode
.getTaxon() == null ?
null
181 : getWfoId(state
, parentNode
.getTaxon().getName(), false);
183 state
.putTaxonNodeWfoId(parentNode
, wfoId
);
184 state
.putTaxonWfoId(parentNode
.getTaxon(), wfoId
);
185 state
.putNameWfoId(parentNode
.getTaxon().getName(), wfoId
);
192 private boolean filterTaxon(WfoBackboneExportState state
, TaxonNode taxonNode
) {
193 Taxon taxon
= taxonNode
.getTaxon();
197 TaxonName taxonName
= taxon
.getName();
198 if (taxonName
== null) {
200 }else if (taxonName
.isHybridFormula()) {
203 String wfoId
= getWfoId(state
, taxonName
, false);
209 Rank rank
= taxonName
.getRank();
211 //TODO 3 is missing rank handling correct?
214 if (rank
.isSpeciesAggregate()){
222 * @return the WFO-ID of the taxon
224 private String
handleTaxon(WfoBackboneExportState state
, TaxonNode taxonNode
, String parentWfoId
) {
227 if (taxonNode
== null) {
228 state
.getResult().addError("The taxonNode was null.", "handleTaxon");
229 state
.getResult().setState(ExportResultState
.INCOMPLETE_WITH_ERROR
);
233 if (taxonNode
.getTaxon() == null) {
234 state
.getResult().addError("There was a taxon node without a taxon: " + taxonNode
.getUuid(),
236 state
.getResult().setState(ExportResultState
.INCOMPLETE_WITH_ERROR
);
240 //deproxy, just in case
241 Taxon taxon
= CdmBase
.deproxy(taxonNode
.getTaxon());
247 //classification csvLine
248 WfoBackboneExportTable table
= WfoBackboneExportTable
.CLASSIFICATION
;
249 String
[] csvLine
= new String
[table
.getSize()];
252 TaxonName name
= taxon
.getName();
253 wfoId
= handleName(state
, table
, csvLine
, name
, null);
255 //... parentNameUsageID
256 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_PARENT_ID
)] = parentWfoId
;
259 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_SUBFAMILY
)] = null;
260 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_TRIBE
)] = null;
261 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_SUBTRIBE
)] = null;
262 //TODO 2 is subgenus handling correct?
263 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_SUBGENUS
)] = name
.isInfraGeneric()? name
.getInfraGenericEpithet() : null ;
265 //... tax status, TODO 2 are there other status for accepted or other reasons for being ambiguous
266 String taxonStatus
= taxon
.isDoubtful()?
"ambiguous" : "Accepted";
267 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_STATUS
)] = taxonStatus
;
270 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAXON_REMARKS
)] = getRemarks(state
, taxon
);
272 //TODO 7 URL to taxon, take it from a repository information (currently not yet possible, but maybe we could use a CDM preference instead)
273 if (isNotBlank(state
.getConfig().getSourceLinkBaseUrl())) {
274 String taxonSourceLink
= makeTaxonSourceLink(state
, taxon
);
275 csvLine
[table
.getIndex(WfoBackboneExportTable
.REFERENCES
)] = taxonSourceLink
;
279 csvLine
[table
.getIndex(WfoBackboneExportTable
.EXCLUDE
)] = makeExcluded(state
, taxonNode
);
281 handleTaxonBase(state
, table
, csvLine
, taxon
);
283 handleSynonyms(state
, taxon
);
286 state
.getProcessor().put(table
, taxon
, csvLine
);
290 } catch (Exception e
) {
292 state
.getResult().addException(e
,
293 "An unexpected problem occurred when trying to export taxon with id " + taxon
.getId() + " " + taxon
.getTitleCache());
294 state
.getResult().setState(ExportResultState
.INCOMPLETE_WITH_ERROR
);
299 private String
makeExcluded(@SuppressWarnings("unused") WfoBackboneExportState state
, TaxonNode taxonNode
) {
300 TaxonNodeStatus status
= taxonNode
.getStatus();
301 if (status
== null || (status
!= TaxonNodeStatus
.EXCLUDED
&& !status
.isKindOf(TaxonNodeStatus
.EXCLUDED
))) {
304 Language lang
= Language
.getDefaultLanguage(); //TODO 7 language for status note
306 String result
= status
== TaxonNodeStatus
.EXCLUDED ?
"Excluded" :
307 status
== TaxonNodeStatus
.EXCLUDED_TAX?
"Taxonomically out of scope" : status
.getLabel();
308 String note
= taxonNode
.preferredStatusNote(lang
);
309 result
= CdmUtils
.concat(": ", result
, note
);
314 private void handleTaxonBase(WfoBackboneExportState state
, WfoBackboneExportTable table
, String
[] csvLine
,
315 TaxonBase
<?
> taxonBase
) {
318 Reference secRef
= taxonBase
.getSec();
319 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_NAME_ACCORDING_TO_ID
)] = getId(state
, secRef
);
321 && (!state
.getReferenceStore().contains((secRef
.getUuid())))) {
322 handleReference(state
, taxonBase
.getSecSource());
326 csvLine
[table
.getIndex(WfoBackboneExportTable
.CREATED
)] = null;
329 csvLine
[table
.getIndex(WfoBackboneExportTable
.MODIFIED
)] = null;
333 private String
makeTaxonSourceLink(WfoBackboneExportState state
, Taxon taxon
) {
334 String baseUrl
= state
.getConfig().getSourceLinkBaseUrl();
335 if (!baseUrl
.endsWith("/")) {
338 String result
= baseUrl
+ "cdm_dataportal/taxon/" + taxon
.getUuid() ;
342 private String
makeSynonymSourceLink(WfoBackboneExportState state
, Synonym synonym
) {
343 String baseUrl
= state
.getConfig().getSourceLinkBaseUrl();
344 if (!baseUrl
.endsWith("/")) {
347 String result
= baseUrl
+ "cdm_dataportal/taxon/" +
348 synonym
.getAcceptedTaxon().getUuid()
349 + "/synonymy?highlite=" + synonym
.getUuid();
353 private void handleSynonyms(WfoBackboneExportState state
, Taxon taxon
) {
355 if (!state
.getConfig().isDoSynonyms()) {
359 //homotypic group / synonyms
360 HomotypicalGroup homotypicGroup
= taxon
.getHomotypicGroup();
361 handleHomotypicalGroup(state
, homotypicGroup
, taxon
);
362 for (Synonym syn
: taxon
.getSynonymsInGroup(homotypicGroup
)) {
363 handleSynonym(state
, syn
, true);
366 List
<HomotypicalGroup
> heterotypicHomotypicGroups
= taxon
.getHeterotypicSynonymyGroups();
367 for (HomotypicalGroup group
: heterotypicHomotypicGroups
){
368 handleHomotypicalGroup(state
, group
, taxon
);
369 for (Synonym syn
: taxon
.getSynonymsInGroup(group
)) {
370 handleSynonym(state
, syn
, false);
375 private boolean isUrl(String url
) {
377 if (url
.startsWith("http")) {
381 } catch (Exception e
) {
382 //exception should return false
387 private String
toIsoDate(TimePeriod mediaCreated
) {
388 //TODO 2 date, what if end or freetext exist?
389 Partial partial
= mediaCreated
.getStart();
390 if (partial
== null || !partial
.isSupported(DateTimeFieldType
.year())
391 || !partial
.isSupported(DateTimeFieldType
.monthOfYear()) && partial
.isSupported(DateTimeFieldType
.dayOfMonth())) {
392 //TODO 2 date, log warning, also if mediaCreated.getEnd() != null or so
395 DateTimeFormatter formatter
= new DateTimeFormatterBuilder()
396 .appendYear(4, 4).appendLiteral('-')
397 .appendMonthOfYear(2).appendLiteral('-')
400 return partial
.toString(formatter
);
405 * transforms the given date to an iso date
407 protected String
toIsoDate(DateTime dateTime
) {
408 if (dateTime
== null) {
411 DateTimeFormatter formatter
= new DateTimeFormatterBuilder()
412 .appendYear(4, 4).appendLiteral('-')
413 .appendMonthOfYear(2).appendLiteral('-')
416 return formatter
.print(dateTime
);
419 //TODO 4 is remark handling correct?
420 private String
getRemarks(WfoBackboneExportState state
, TaxonBase
<?
> taxonBase
) {
422 String remarks
= null;
424 Set
<UUID
> includedAnnotationTypes
= new HashSet
<>();
425 //TODO 5 make annotation types configurable
426 includedAnnotationTypes
.add(AnnotationType
.uuidEditorial
);
428 Set
<UUID
> includedTaxonFactTypes
= new HashSet
<>(); //make taxon remark facts configurable
429 includedTaxonFactTypes
.add(Feature
.uuidNotes
);
430 Set
<UUID
> includedNameFactTypes
= null; //TODO 7 make name facts configurable
433 String nameAnnotations
= getAnnotations(state
, taxonBase
.getName(), includedAnnotationTypes
);
434 String nameFacts
= getFacts(state
, taxonBase
.getName(), includedNameFactTypes
);
435 String taxonAnnotations
= getAnnotations(state
, taxonBase
, includedAnnotationTypes
);
436 String taxonFacts
= !taxonBase
.isInstanceOf(Taxon
.class)?
null :
437 getFacts(state
, CdmBase
.deproxy(taxonBase
, Taxon
.class), includedTaxonFactTypes
);
439 remarks
= CdmUtils
.concat("; ", nameAnnotations
, nameFacts
, taxonAnnotations
, taxonFacts
);
444 //TODO 4 move to a more general place for reusing and/or make it more performant
445 private String
getFacts(@SuppressWarnings("unused") WfoBackboneExportState state
,
446 IDescribable
<?
> entity
,
447 Set
<UUID
> includedFeatures
) {
449 if (entity
== null) {
452 String result
= null;
453 for (DescriptionBase
<?
> db
: entity
.getDescriptions()) {
454 if (db
.isPublish()) {
455 for(DescriptionElementBase deb
: db
.getElements()){
456 UUID featureUuid
= deb
.getFeature()==null ?
null: deb
.getFeature().getUuid();
457 if (includedFeatures
== null ||
458 includedFeatures
.contains(featureUuid
)){
459 //TODO 9 other fact types
460 if (deb
.isInstanceOf(TextData
.class)) {
461 TextData td
= CdmBase
.deproxy(deb
, TextData
.class);
462 //TODO 7 handle locale
463 LanguageString text
= td
.getPreferredLanguageString(Language
.DEFAULT());
465 result
= CdmUtils
.concat(";", result
, text
.getText());
475 private String
getAnnotations(@SuppressWarnings("unused") WfoBackboneExportState state
,
476 AnnotatableEntity entity
,
477 Set
<UUID
> includedAnnotationTypes
) {
479 if (entity
== null) {
482 String result
= null;
483 for (Annotation a
: entity
.getAnnotations()) {
484 UUID typeUuid
= a
.getAnnotationType()==null ?
null: a
.getAnnotationType().getUuid();
485 if (includedAnnotationTypes
== null ||
486 includedAnnotationTypes
.contains(typeUuid
)){
487 result
= CdmUtils
.concat(";", result
, a
.getText());
493 private String
createMultilanguageString(Map
<Language
, LanguageString
> multilanguageText
) {
495 int index
= multilanguageText
.size();
496 for (LanguageString langString
: multilanguageText
.values()) {
497 text
+= langString
.getText();
506 private String
createAnnotationsString(Set
<Annotation
> annotations
) {
507 StringBuffer strBuff
= new StringBuffer();
509 for (Annotation ann
: annotations
) {
510 if (ann
.getAnnotationType() == null || !ann
.getAnnotationType().equals(AnnotationType
.TECHNICAL())) {
511 strBuff
.append(ann
.getText());
512 strBuff
.append("; ");
516 if (strBuff
.length() > 2) {
517 return strBuff
.substring(0, strBuff
.length() - 2);
523 private String
getId(WfoBackboneExportState state
, ICdmBase cdmBase
) {
524 if (cdmBase
== null) {
527 // TODO 4 id type, make configurable
528 return cdmBase
.getUuid().toString();
531 private void handleSynonym(WfoBackboneExportState state
, Synonym synonym
,
532 boolean isHomotypic
) {
534 if (isUnpublished(state
.getConfig(), synonym
)) {
538 WfoBackboneExportTable table
= WfoBackboneExportTable
.CLASSIFICATION
;
539 String
[] csvLine
= new String
[table
.getSize()];
541 TaxonName name
= synonym
.getName();
544 String acceptedWfoId
= null;
545 if (synonym
.getAcceptedTaxon()!= null && synonym
.getAcceptedTaxon().getName() != null) {
546 TaxonName acceptedName
= synonym
.getAcceptedTaxon().getName();
547 acceptedWfoId
= getWfoId(state
, acceptedName
, false);
548 if (acceptedWfoId
== null) {
549 String message
= "WFO-ID for accepted name is missing. This should not happen. Synonym: " + name
.getTitleCache() + "; Accepted name: " + acceptedName
.getTitleCache();
550 state
.getResult().addError(message
, "handleName");
551 state
.getResult().setState(ExportResultState
.INCOMPLETE_WITH_ERROR
);
553 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_ACCEPTED_NAME_ID
)] = acceptedWfoId
;
556 String wfoId
= handleName(state
, table
, csvLine
, name
, acceptedWfoId
);
563 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_STATUS
)] = isHomotypic ?
"homotypicSynonym" : "heterotypicSynonym";
565 //TODO 5 URL to taxon, take it from a repository information (currently not yet possible, but maybe we could use a CDM preference instead)
566 if (isNotBlank(state
.getConfig().getSourceLinkBaseUrl())) {
567 String taxonSourceLink
= makeSynonymSourceLink(state
, synonym
);
568 csvLine
[table
.getIndex(WfoBackboneExportTable
.REFERENCES
)] = taxonSourceLink
;
571 handleTaxonBase(state
, table
, csvLine
, synonym
);
573 state
.getProcessor().put(table
, synonym
, csvLine
);
574 } catch (Exception e
) {
575 state
.getResult().addException(e
, "An unexpected error occurred when handling synonym "
576 + cdmBaseStr(synonym
) + ": " + e
.getMessage());
580 private String
handleName(WfoBackboneExportState state
, WfoBackboneExportTable table
, String
[] csvLine
,
581 TaxonName name
, String acceptedNameWfoId
) {
583 name
= CdmBase
.deproxy(name
);
584 if (name
== null || state
.getNameStore().containsKey(name
.getId())) {
586 state
.getResult().addError("No name was given for taxon.", "handleName");
587 state
.getResult().setState(ExportResultState
.INCOMPLETE_WITH_ERROR
);
594 Rank rank
= name
.getRank();
595 state
.getNameStore().put(name
.getId(), name
.getUuid());
598 wfoId
= getWfoId(state
, name
, false);
599 if (isBlank(wfoId
)) {
600 String message
= "No WFO-ID given for taxon name " + name
.getTitleCache() + ". Taxon/Synonym/Name ignored.";
601 state
.getResult().addError(message
);
602 state
.getResult().setState(ExportResultState
.INCOMPLETE_WITH_ERROR
);
605 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAXON_ID
)] = wfoId
;
608 //TODO 9 add IPNI ID if exists, scientific name ID
609 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_SCIENTIFIC_NAME_ID
)] = null;
612 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_LOCAL_ID
)] = getId(state
, name
);
615 if (name
.isProtectedTitleCache()) {
616 //TODO 7 make it configurable if we should always take titleCache if titleCache is protected, as nameCache may not necessarily
617 // have complete data if titleCache is protected as it is considered to be irrelevant or at least preliminary
619 if (StringUtils
.isNotEmpty(name
.getNameCache())) {
620 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_SCIENTIFIC_NAME
)] = name
.getNameCache();
621 message
= "ScientificName: Name cache " + name
.getNameCache() + " used for name with protected titleCache " + name
.getTitleCache();
623 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_SCIENTIFIC_NAME
)] = name
.getTitleCache();
624 message
= "ScientificName: Name has protected titleCache and no explicit nameCache: " + name
.getTitleCache();
626 state
.getResult().addWarning(message
); //TODO 7 add location to warning
628 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_SCIENTIFIC_NAME
)] = name
.getNameCache();
632 String rankStr
= state
.getTransformer().getCacheByRank(rank
);
633 if (rankStr
== null) {
634 String message
= rank
== null ?
"No rank" : ("Rank not supported by WFO:" + rank
.getLabel())
635 + "Taxon not handled in export: " + name
.getTitleCache();
636 state
.getResult().addWarning(message
); //TODO 2 warning sufficient for missing rank? + location
639 csvLine
[table
.getIndex(WfoBackboneExportTable
.RANK
)] = rankStr
;
642 //TODO 3 handle empty authorship cache warning
643 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_AUTHORSHIP
)]
644 = normalizedAuthor(state
, name
);
646 //family (use familystr if provided, otherwise try to compute from the family taxon
647 String familyStr
= state
.getFamilyStr();
648 if (StringUtils
.isBlank(familyStr
)){
649 if (Rank
.FAMILY().equals(name
.getRank())){
650 familyStr
= name
.getNameCache();
652 if (StringUtils
.isNotBlank(familyStr
)) {
653 state
.setFamilyStr(familyStr
);
655 String message
= "Obligatory family information is missing";
656 state
.getResult().addWarning(message
);
659 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_FAMILY
)] = state
.getFamilyStr();
662 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_GENUS
)] = name
.isSupraGeneric()?
null : name
.getGenusOrUninomial();
663 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_SPECIFIC_EPITHET
)] = name
.getSpecificEpithet();
664 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_INFRASPECIFIC_EPITHET
)] = name
.getInfraSpecificEpithet();
666 //TODO 3 verbatimTaxonRank, is this needed at all?
667 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_VERBATIM_RANK
)] = rankStr
;
670 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_STATUS
)] = makeNameStatus(state
, name
);
673 String nomRef
= NomenclaturalSourceFormatter
.INSTANCE().format(name
.getNomenclaturalSource());
674 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_PUBLISHED_IN
)] = nomRef
;
677 TaxonName originalName
= name
.getBasionym(); //TODO 5 basionym, order in case there are >1 basionyms
678 if (originalName
== null) {
679 originalName
= name
.getReplacedSynonyms().stream().findFirst().orElse(null);
682 if (originalName
!= null) {
683 if (!state
.getNameStore().containsKey(originalName
.getId())) {
684 //TODO 2 handle basionym is in file assertion
686 String basionymId
= getWfoId(state
, originalName
, false);
687 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_ORIGINAL_NAME_ID
)] = basionymId
;
691 TaxonName originalSpelling
= name
.getOriginalSpelling();
692 acceptedNameWfoId
= acceptedNameWfoId
== null? wfoId
: acceptedNameWfoId
;
693 if (originalSpelling
!= null) {
694 handleOrthographicVariants(state
, table
, originalSpelling
, name
, acceptedNameWfoId
);
698 Set
<TaxonName
> orthVars
= getOrthographicVariants(name
);
699 for (TaxonName orthVar
: orthVars
) {
700 handleOrthographicVariants(state
, table
, orthVar
, name
, acceptedNameWfoId
);
703 } catch (Exception e
) {
704 state
.getResult().addException(e
,
705 "An unexpected error occurred when handling the name " + cdmBaseStr(name
) + ": " + name
.getTitleCache() + ": " + e
.getMessage());
712 private String
normalizedAuthor(WfoBackboneExportState state
, TaxonName name
) {
715 } else if (state
.getConfig().isNormalizeAuthorsToIpniStandard()) {
716 return TeamDefaultCacheStrategy
.removeWhitespaces(name
.getAuthorshipCache());
718 String result
= name
.getAuthorshipCache();
719 if (result
== null) {
722 return result
.replaceAll("\\s+", " ").trim();
727 private Set
<TaxonName
> getOrthographicVariants(TaxonName name
) {
728 Set
<TaxonName
> result
= new HashSet
<>();
729 Set
<NameRelationship
> rels
= name
.getRelationsToThisName();
730 for(NameRelationship rel
: rels
) {
731 //TODO 2 handle also other type and other direction for orth. var.
732 if(rel
.getType().getUuid().equals(NameRelationshipType
.uuidOrthographicVariant
)) {
733 result
.add(rel
.getFromName());
740 * Handle names not being handled via taxonbase.
741 * @param acceptedNameWfoId
743 private void handleOrthographicVariants(WfoBackboneExportState state
, WfoBackboneExportTable table
,
744 TaxonName name
, TaxonName mainName
, String acceptedNameWfoId
) {
746 //TODO 1 names only check if implemented correctly
747 if (!name
.getTaxonBases().isEmpty()) {
748 //TODO 2 find a better way to guarantee that the name is not added as a taxonbase elsewhere
752 String
[] csvLine
= new String
[table
.getSize()];
753 String wfoID
= handleName(state
, table
, csvLine
, name
, acceptedNameWfoId
);
755 String message
= "Original spelling, orthographic variant or misspelling "
756 + "'" + name
+ "' for name '" + mainName
+"' does not have a WFO-ID"
757 + " and therefore can not be exported";
758 state
.getResult().addWarning(message
);
762 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_ACCEPTED_NAME_ID
)] = acceptedNameWfoId
;
764 //authorship, take from mainname if it does not exist
765 //TODO 3 take from csvLine of both names
766 if (isBlank(normalizedAuthor(state
, name
))) {
767 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_AUTHORSHIP
)]
768 = normalizedAuthor(state
, mainName
);
771 //nom. ref, take from main name if it does not exist
772 //TODO 3 take from csvLine of both names
773 String nomRef
= NomenclaturalSourceFormatter
.INSTANCE().format(name
.getNomenclaturalSource());
774 if (isBlank(nomRef
)) {
775 nomRef
= NomenclaturalSourceFormatter
.INSTANCE().format(mainName
.getNomenclaturalSource());
776 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_PUBLISHED_IN
)] = nomRef
;
779 //TODO 2 tax status correct?
780 csvLine
[table
.getIndex(WfoBackboneExportTable
.TAX_STATUS
)] = "Synonym";
782 csvLine
[table
.getIndex(WfoBackboneExportTable
.NAME_STATUS
)] = "orthografia";
784 //TODO 2 remarks, REFERENCES, family, taxonBase, created, modified
786 //process original spelling
787 state
.getProcessor().put(table
, name
, csvLine
); // TODO Auto-generated method stub
791 private String
getWfoId(WfoBackboneExportState state
, TaxonName name
, boolean warnIfNotExists
) {
792 Identifier wfoId
= name
.getIdentifier(IdentifierType
.uuidWfoNameIdentifier
);
793 if (wfoId
== null && warnIfNotExists
) {
794 String message
= "No wfo-id given for name: " + name
.getTitleCache()+"/"+ name
.getUuid();
795 state
.getResult().addWarning(message
); //TODO 5 data location
797 return wfoId
== null ?
null : wfoId
.getIdentifier();
800 private String
makeNameStatus(WfoBackboneExportState state
, TaxonName name
) {
803 //TODO 2 what is with dubium
804 if (name
.isLegitimate()) {
805 if (name
.isConserved()) {
810 } else if (name
.isRejected()) {
812 } else if (name
.isIllegitimate()) {
813 return "Illegitimate";
814 } else if (name
.isInvalid()) {
815 if (name
.isOrthographicVariant()) {
816 return "orthografia";
821 String message
= "Unhandled name status case for name: " + name
.getTitleCache() +
822 ". Status not handled correctly.";
823 state
.getResult().addWarning(message
);
826 } catch (Exception e
) {
827 state
.getResult().addException(e
, "An unexpected error occurred when extracting status string for "
828 + cdmBaseStr(name
) + ": " + e
.getMessage());
833 private void handleHomotypicalGroup(WfoBackboneExportState state
, HomotypicalGroup group
, Taxon acceptedTaxon
) {
836 List
<TaxonName
> typifiedNames
= new ArrayList
<>();
837 if (acceptedTaxon
!= null){
838 List
<Synonym
> synonymsInGroup
= acceptedTaxon
.getSynonymsInGroup(group
);
839 if (group
.equals(acceptedTaxon
.getHomotypicGroup())){
840 typifiedNames
.add(acceptedTaxon
.getName());
842 synonymsInGroup
.stream().forEach(synonym
-> typifiedNames
.add(CdmBase
.deproxy(synonym
.getName())));
846 TaxonName firstname
= null;
847 for (TaxonName name
: typifiedNames
){
848 Iterator
<Taxon
> taxa
= name
.getTaxa().iterator();
849 while(taxa
.hasNext()){
850 Taxon taxon
= taxa
.next();
851 if(!(taxon
.isMisapplication() || taxon
.isProparteSynonym())){
858 } catch (Exception e
) {
859 state
.getResult().addException(e
, "An unexpected error occurred when handling homotypic group "
860 + cdmBaseStr(group
) + ": " + e
.getMessage());
864 private void handleReference(WfoBackboneExportState state
, OriginalSourceBase source
) {
866 if (source
== null || source
.getCitation() == null) {
869 Reference reference
= source
.getCitation();
871 //TODO 5 allow handle sec detail in ref table (if allowed by the export guide)
872 // this is also important for adding external links which are source specific (see below)
874 if (state
.getReferenceStore().contains(reference
.getUuid())) {
877 reference
= CdmBase
.deproxy(reference
);
879 state
.addReferenceToStore(reference
);
880 WfoBackboneExportTable table
= WfoBackboneExportTable
.REFERENCE
;
881 String
[] csvLine
= new String
[table
.getSize()];
883 csvLine
[table
.getIndex(WfoBackboneExportTable
.IDENTIFIER
)] = getId(state
, reference
);
885 //TODO 2 correct?, ref biblio citation
886 csvLine
[table
.getIndex(WfoBackboneExportTable
.REF_BIBLIO_CITATION
)] = reference
.getCitation();
888 //ref uri TODO 6 make ref URIs configurable
889 if (reference
.getDoi() != null) {
890 csvLine
[table
.getIndex(WfoBackboneExportTable
.REF_URI
)] = reference
.getDoiString();
891 }else if (reference
.getUri() != null) {
892 csvLine
[table
.getIndex(WfoBackboneExportTable
.REF_URI
)] = reference
.getUri().toString();
894 //TODO 5 not yet added as it is source specific but uuid is reference specific
895 // String uri = null;
896 // for (ExternalLink link : source.getLinks()) {
897 // uri = CdmUtils.concat("; ", uri, link.getUri() == null ? null : link.getUri().toString());
899 // csvLine[table.getIndex(WfoBackboneExportTable.REF_URI)] = uri;
902 state
.getProcessor().put(table
, reference
, csvLine
);
903 } catch (Exception e
) {
905 state
.getResult().addException(e
, "An unexpected error occurred when handling reference "
906 + cdmBaseStr(reference
) + ": " + e
.getMessage());
911 * Returns a string representation of the {@link CdmBase cdmBase} object for
914 private String
cdmBaseStr(CdmBase cdmBase
) {
915 if (cdmBase
== null) {
916 return "-no object available-";
918 return cdmBase
.getClass().getSimpleName() + ": " + cdmBase
.getUuid();
923 protected boolean doCheck(WfoBackboneExportState state
) {
928 protected boolean isIgnore(WfoBackboneExportState state
) {