ref #10446 handle excluded for WFO export and others
[cdmlib.git] / cdmlib-io / src / main / java / eu / etaxonomy / cdm / io / wfo / out / WfoBackboneExport.java
1 /**
2 * Copyright (C) 2017 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 package eu.etaxonomy.cdm.io.wfo.out;
10
11 import java.io.File;
12 import java.util.ArrayList;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Set;
17
18 import org.apache.commons.lang3.StringUtils;
19 import org.joda.time.DateTime;
20 import org.joda.time.DateTimeFieldType;
21 import org.joda.time.Partial;
22 import org.joda.time.format.DateTimeFormatter;
23 import org.joda.time.format.DateTimeFormatterBuilder;
24 import org.springframework.stereotype.Component;
25
26 import eu.etaxonomy.cdm.common.CdmUtils;
27 import eu.etaxonomy.cdm.common.URI;
28 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
29 import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
30 import eu.etaxonomy.cdm.format.reference.NomenclaturalSourceFormatter;
31 import eu.etaxonomy.cdm.io.common.CdmExportBase;
32 import eu.etaxonomy.cdm.io.common.ExportResult.ExportResultState;
33 import eu.etaxonomy.cdm.io.common.TaxonNodeOutStreamPartitioner;
34 import eu.etaxonomy.cdm.io.common.XmlExportState;
35 import eu.etaxonomy.cdm.io.common.mapping.out.IExportTransformer;
36 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
37 import eu.etaxonomy.cdm.model.common.Annotation;
38 import eu.etaxonomy.cdm.model.common.AnnotationType;
39 import eu.etaxonomy.cdm.model.common.CdmBase;
40 import eu.etaxonomy.cdm.model.common.ICdmBase;
41 import eu.etaxonomy.cdm.model.common.IIdentifiableEntity;
42 import eu.etaxonomy.cdm.model.common.Identifier;
43 import eu.etaxonomy.cdm.model.common.Language;
44 import eu.etaxonomy.cdm.model.common.LanguageString;
45 import eu.etaxonomy.cdm.model.common.TimePeriod;
46 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
47 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
48 import eu.etaxonomy.cdm.model.name.Rank;
49 import eu.etaxonomy.cdm.model.name.TaxonName;
50 import eu.etaxonomy.cdm.model.reference.Reference;
51 import eu.etaxonomy.cdm.model.taxon.Classification;
52 import eu.etaxonomy.cdm.model.taxon.Synonym;
53 import eu.etaxonomy.cdm.model.taxon.Taxon;
54 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
55 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
56 import eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus;
57 import eu.etaxonomy.cdm.model.term.IdentifierType;
58
59 /**
60 * Classification or taxon tree exporter into WFO Backbone format.
61 *
62 * @see https://dev.e-taxonomy.eu/redmine/issues/10446
63 *
64 * @author a.mueller
65 * @since 2023-12-08
66 */
67 @Component
68 public class WfoBackboneExport
69 extends CdmExportBase<WfoBackboneExportConfigurator,WfoBackboneExportState,IExportTransformer,File>{
70
71 private static final long serialVersionUID = -4560488499411723333L;
72
73 public WfoBackboneExport() {
74 this.ioName = this.getClass().getSimpleName();
75 }
76
77 @Override
78 public long countSteps(WfoBackboneExportState state) {
79 TaxonNodeFilter filter = state.getConfig().getTaxonNodeFilter();
80 return getTaxonNodeService().count(filter);
81 }
82
83 @Override
84 protected void doInvoke(WfoBackboneExportState state) {
85
86 try {
87 IProgressMonitor monitor = state.getConfig().getProgressMonitor();
88 WfoBackboneExportConfigurator config = state.getConfig();
89
90 //set root node
91 if (config.getTaxonNodeFilter().hasClassificationFilter()) {
92 Classification classification = getClassificationService()
93 .load(config.getTaxonNodeFilter().getClassificationFilter().get(0).getUuid());
94 state.setRootId(classification.getRootNode().getUuid());
95 } else if (config.getTaxonNodeFilter().hasSubtreeFilter()) {
96 state.setRootId(config.getTaxonNodeFilter().getSubtreeFilter().get(0).getUuid());
97 }
98
99 @SuppressWarnings({ "unchecked", "rawtypes" })
100 TaxonNodeOutStreamPartitioner<XmlExportState> partitioner = TaxonNodeOutStreamPartitioner.NewInstance(this,
101 state, state.getConfig().getTaxonNodeFilter(), 100, monitor, null);
102
103 // handleMetaData(state); //FIXME metadata;
104 monitor.subTask("Start partitioning");
105
106 //test configurator
107 String baseUrl = state.getConfig().getSourceLinkBaseUrl();
108 if (baseUrl != null && !baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")){
109 String message = "Source link base url is not a http based url.";
110 state.getResult().addWarning(message);
111 }
112
113 //taxon nodes
114 TaxonNode node = partitioner.next();
115 while (node != null) {
116 handleTaxonNode(state, node);
117 node = partitioner.next();
118 }
119
120 state.getProcessor().createFinalResult(state);
121 } catch (Exception e) {
122 state.getResult().addException(e,
123 "An unexpected error occurred in main method doInvoke() " + e.getMessage());
124 e.printStackTrace();
125 }
126 }
127
128 private void handleTaxonNode(WfoBackboneExportState state, TaxonNode taxonNode) {
129
130 if (taxonNode == null) {
131 // TODO 5 taxon node not found
132 String message = "TaxonNode for given taxon node UUID not found.";
133 state.getResult().addError(message);
134 } else {
135 try {
136 boolean exclude = filterTaxon(state, taxonNode);
137
138 //handle taxon
139 String parentWfoId = getParentWfoId(state, taxonNode);
140 if (taxonNode.hasTaxon()) {
141 if (exclude) {
142 state.putTaxonNodeWfoId(taxonNode, parentWfoId); //always use parent instead
143 } else {
144 String wfoId = handleTaxon(state, taxonNode, parentWfoId);
145 state.putTaxonNodeWfoId(taxonNode, wfoId);
146 }
147 }
148 } catch (Exception e) {
149 state.getResult().addException(e, "An unexpected error occurred when handling taxonNode "
150 + taxonNode.getUuid() + ": " + e.getMessage() + e.getStackTrace());
151 }
152 }
153 }
154
155 private String getParentWfoId(WfoBackboneExportState state, TaxonNode taxonNode) {
156 TaxonNode parentNode = taxonNode.getParent();
157 if (parentNode == null) {
158 return null;
159 }
160 String wfoId = state.getTaxonNodeWfoId(parentNode);
161 if (wfoId != null) {
162 return wfoId;
163 }else {
164 wfoId = parentNode.getTaxon() == null ? null
165 : getWfoId(state, parentNode.getTaxon().getName(), true);
166 if (wfoId != null) {
167 state.putTaxonNodeWfoId(parentNode, wfoId);
168 state.putTaxonWfoId(parentNode.getTaxon(), wfoId);
169 state.putNameWfoId(parentNode.getTaxon().getName(), wfoId);
170 }
171
172 return wfoId;
173 }
174 }
175
176 private boolean filterTaxon(WfoBackboneExportState state, TaxonNode taxonNode) {
177 Taxon taxon = taxonNode.getTaxon();
178 if (taxon == null) {
179 return true;
180 }
181 TaxonName taxonName = taxon.getName();
182 if (taxonName == null) {
183 return true;
184 }else if (taxonName.isHybridFormula()) {
185 return true;
186 }else {
187 String wfoId = getWfoId(state, taxonName, false);
188 if (wfoId == null) {
189 return true;
190 }
191 }
192
193 Rank rank = taxonName.getRank();
194 if (rank == null) {
195 //TODO 3 is missing rank handling correct?
196 return true;
197 }else {
198 if (rank.isSpeciesAggregate()){
199 return true;
200 }
201 }
202 return false;
203 }
204
205 /**
206 * @return the WFO-ID of the taxon
207 */
208 private String handleTaxon(WfoBackboneExportState state, TaxonNode taxonNode, String parentWfoId) {
209
210 //check null
211 if (taxonNode == null) {
212 state.getResult().addError("The taxonNode was null.", "handleTaxon");
213 state.getResult().setState(ExportResultState.INCOMPLETE_WITH_ERROR);
214 return null;
215 }
216 //check no taxon
217 if (taxonNode.getTaxon() == null) {
218 state.getResult().addError("There was a taxon node without a taxon: " + taxonNode.getUuid(),
219 "handleTaxon");
220 state.getResult().setState(ExportResultState.INCOMPLETE_WITH_ERROR);
221 return null;
222 }
223
224 //deproxy, just in case
225 Taxon taxon = CdmBase.deproxy(taxonNode.getTaxon());
226 String wfoId = null;
227
228 //handle taxon
229 try {
230
231 //classification csvLine
232 WfoBackboneExportTable table = WfoBackboneExportTable.CLASSIFICATION;
233 String[] csvLine = new String[table.getSize()];
234
235 //accepted name
236 TaxonName name = taxon.getName();
237 wfoId = handleName(state, table, csvLine, name);
238
239 //... parentNameUsageID
240 csvLine[table.getIndex(WfoBackboneExportTable.TAX_PARENT_ID)] = parentWfoId;
241
242 //... higher taxa
243 csvLine[table.getIndex(WfoBackboneExportTable.TAX_SUBFAMILY)] = null;
244 csvLine[table.getIndex(WfoBackboneExportTable.TAX_TRIBE)] = null;
245 csvLine[table.getIndex(WfoBackboneExportTable.TAX_SUBTRIBE)] = null;
246 //TODO 2 is subgenus handling correct?
247 csvLine[table.getIndex(WfoBackboneExportTable.TAX_SUBGENUS)] = name.isInfraGeneric()? name.getInfraGenericEpithet() : null ;
248
249 //... tax status, TODO 2 are there other status for accepted or other reasons for being ambiguous
250 String taxonStatus = taxon.isDoubtful()? "ambiguous" : "Accepted";
251 csvLine[table.getIndex(WfoBackboneExportTable.TAX_STATUS)] = taxonStatus;
252
253 //TODO 2 remarks, what exactly
254 csvLine[table.getIndex(WfoBackboneExportTable.TAXON_REMARKS)] = getRemarks(name);
255
256 //TODO 7 URL to taxon, take it from a repository information (currently not yet possible, but maybe we could use a CDM preference instead)
257 if (isNotBlank(state.getConfig().getSourceLinkBaseUrl())) {
258 String taxonSourceLink = makeTaxonSourceLink(state, taxon);
259 csvLine[table.getIndex(WfoBackboneExportTable.REFERENCES)] = taxonSourceLink;
260 }
261
262 //TODO 1 excluded info
263 csvLine[table.getIndex(WfoBackboneExportTable.EXCLUDE)] = makeExcluded(state, taxonNode);
264
265 handleTaxonBase(state, table, csvLine, taxon);
266
267 handleSynonyms(state, taxon);
268
269 //process taxon line
270 state.getProcessor().put(table, taxon, csvLine);
271
272 return wfoId;
273
274 } catch (Exception e) {
275 e.printStackTrace();
276 state.getResult().addException(e,
277 "An unexpected problem occurred when trying to export taxon with id " + taxon.getId() + " " + taxon.getTitleCache());
278 state.getResult().setState(ExportResultState.INCOMPLETE_WITH_ERROR);
279 return null;
280 }
281 }
282
283 private String makeExcluded(WfoBackboneExportState state, TaxonNode taxonNode) {
284 TaxonNodeStatus status = taxonNode.getStatus();
285 if (status == null || (status != TaxonNodeStatus.EXCLUDED && !status.isKindOf(TaxonNodeStatus.EXCLUDED))) {
286 return null;
287 }else {
288 Language lang = Language.getDefaultLanguage(); //TODO 7 language for status note
289
290 String result = status == TaxonNodeStatus.EXCLUDED ? "Excluded" : status.getLabel();
291 String note = taxonNode.preferredStatusNote(lang);
292 result = CdmUtils.concat(": ", result, note);
293 return result;
294 }
295 }
296
297 private void handleTaxonBase(WfoBackboneExportState state, WfoBackboneExportTable table, String[] csvLine,
298 TaxonBase<?> taxonBase) {
299
300 //secundum reference
301 Reference secRef = taxonBase.getSec();
302 csvLine[table.getIndex(WfoBackboneExportTable.TAX_NAME_ACCORDING_TO_ID)] = getId(state, secRef);
303 if (secRef != null
304 && (!state.getReferenceStore().contains((secRef.getUuid())))) {
305 handleReference(state, secRef);
306 }
307
308 //TODO 2 created
309 csvLine[table.getIndex(WfoBackboneExportTable.CREATED)] = null;
310
311 //TODO 2 modified
312 csvLine[table.getIndex(WfoBackboneExportTable.MODIFIED)] = null;
313
314 }
315
316 private String makeTaxonSourceLink(WfoBackboneExportState state, Taxon taxon) {
317 String baseUrl = state.getConfig().getSourceLinkBaseUrl();
318 if (!baseUrl.endsWith("/")) {
319 baseUrl += "/";
320 }
321 String result = baseUrl + "cdm_dataportal/taxon/" + taxon.getUuid() ;
322 return result;
323 }
324
325 private String makeSynonymSourceLink(WfoBackboneExportState state, Synonym synonym) {
326 String baseUrl = state.getConfig().getSourceLinkBaseUrl();
327 if (!baseUrl.endsWith("/")) {
328 baseUrl += "/";
329 }
330 String result = baseUrl + "cdm_dataportal/taxon/" +
331 synonym.getAcceptedTaxon().getUuid()
332 + "/synonymy?highlite=" + synonym.getUuid();
333 return result;
334 }
335
336 private void handleSynonyms(WfoBackboneExportState state, Taxon taxon) {
337
338 if (!state.getConfig().isDoSynonyms()) {
339 return;
340 }
341
342 //homotypic group / synonyms
343 HomotypicalGroup homotypicGroup = taxon.getHomotypicGroup();
344 handleHomotypicalGroup(state, homotypicGroup, taxon);
345 for (Synonym syn : taxon.getSynonymsInGroup(homotypicGroup)) {
346 handleSynonym(state, syn, true);
347 }
348
349 List<HomotypicalGroup> heterotypicHomotypicGroups = taxon.getHeterotypicSynonymyGroups();
350 for (HomotypicalGroup group: heterotypicHomotypicGroups){
351 handleHomotypicalGroup(state, group, taxon);
352 for (Synonym syn : taxon.getSynonymsInGroup(group)) {
353 handleSynonym(state, syn, false);
354 }
355 }
356 }
357
358 private boolean isUrl(String url) {
359 try {
360 if (url.startsWith("http")) {
361 URI.fromString(url);
362 return true;
363 }
364 } catch (Exception e) {
365 //exception should return false
366 }
367 return false;
368 }
369
370 private String toIsoDate(TimePeriod mediaCreated) {
371 //TODO 2 date, what if end or freetext exist?
372 Partial partial = mediaCreated.getStart();
373 if (partial == null || !partial.isSupported(DateTimeFieldType.year())
374 || !partial.isSupported(DateTimeFieldType.monthOfYear()) && partial.isSupported(DateTimeFieldType.dayOfMonth())) {
375 //TODO 2 date, log warning, also if mediaCreated.getEnd() != null or so
376 return null;
377 } else {
378 DateTimeFormatter formatter = new DateTimeFormatterBuilder()
379 .appendYear(4, 4).appendLiteral('-')
380 .appendMonthOfYear(2).appendLiteral('-')
381 .appendDayOfMonth(2)
382 .toFormatter();
383 return partial.toString(formatter);
384 }
385 }
386
387 /**
388 * transforms the given date to an iso date
389 */
390 protected String toIsoDate(DateTime dateTime) {
391 if (dateTime == null) {
392 return null;
393 }
394 DateTimeFormatter formatter = new DateTimeFormatterBuilder()
395 .appendYear(4, 4).appendLiteral('-')
396 .appendMonthOfYear(2).appendLiteral('-')
397 .appendDayOfMonth(2)
398 .toFormatter();
399 return formatter.print(dateTime);
400 }
401
402 private String getRemarks(AnnotatableEntity entity) {
403 String remarks = null;
404 for (Annotation a : entity.getAnnotations()) {
405 //TODO 3 handle other annotation types
406 if (AnnotationType.EDITORIAL().equals(a.getAnnotationType())
407 && CdmUtils.isNotBlank(a.getText())){
408 remarks = CdmUtils.concat(";", remarks, a.getText());
409 }
410 }
411 return remarks;
412 }
413
414 // private void handleMetaData(ColDpExportState state) {
415 // ColDpExportTable table = ColDpExportTable.METADATA;
416 // String[] csvLine = new String[table.getSize()];
417 //// csvLine[table.getIndex(CdmLightExportTable.INSTANCE_ID)] = state.getConfig().getInctanceId();
418 //// csvLine[table.getIndex(CdmLightExportTable.INSTANCE_NAME)] = state.getConfig().getInstanceName();
419 // csvLine[table.getIndex(ColDpExportTable.DATASET_BASE_URL)] = state.getConfig().getBase_url();
420 // csvLine[table.getIndex(ColDpExportTable.DATASET_CONTRIBUTOR)] = state.getConfig().getContributor();
421 // csvLine[table.getIndex(ColDpExportTable.DATASET_CREATOR)] = state.getConfig().getCreator();
422 // csvLine[table.getIndex(ColDpExportTable.DATASET_DESCRIPTION)] = state.getConfig().getDescription();
423 // csvLine[table.getIndex(ColDpExportTable.DATASET_DOWNLOAD_LINK)] = state.getConfig().getDataset_download_link();
424 // csvLine[table.getIndex(ColDpExportTable.DATASET_KEYWORDS)] = state.getConfig().getKeywords();
425 // csvLine[table.getIndex(ColDpExportTable.DATASET_LANDINGPAGE)] = state.getConfig().getDataSet_landing_page();
426 //
427 // csvLine[table.getIndex(ColDpExportTable.DATASET_LANGUAGE)] = state.getConfig().getLanguage() != null? state.getConfig().getLanguage().getLabel(): null;
428 // csvLine[table.getIndex(ColDpExportTable.DATASET_LICENCE)] = state.getConfig().getLicence();
429 // csvLine[table.getIndex(ColDpExportTable.DATASET_LOCATION)] = state.getConfig().getLocation();
430 // csvLine[table.getIndex(ColDpExportTable.DATASET_RECOMMENDED_CITATTION)] = state.getConfig().getRecommended_citation();
431 // csvLine[table.getIndex(ColDpExportTable.DATASET_TITLE)] = state.getConfig().getTitle();
432 // state.getProcessor().put(table, "", csvLine);
433 // }
434
435 private String createMultilanguageString(Map<Language, LanguageString> multilanguageText) {
436 String text = "";
437 int index = multilanguageText.size();
438 for (LanguageString langString : multilanguageText.values()) {
439 text += langString.getText();
440 if (index > 1) {
441 text += "; ";
442 }
443 index--;
444 }
445 return text;
446 }
447
448 private String createAnnotationsString(Set<Annotation> annotations) {
449 StringBuffer strBuff = new StringBuffer();
450
451 for (Annotation ann : annotations) {
452 if (ann.getAnnotationType() == null || !ann.getAnnotationType().equals(AnnotationType.TECHNICAL())) {
453 strBuff.append(ann.getText());
454 strBuff.append("; ");
455 }
456 }
457
458 if (strBuff.length() > 2) {
459 return strBuff.substring(0, strBuff.length() - 2);
460 } else {
461 return null;
462 }
463 }
464
465 private void handleSource(WfoBackboneExportState state, DescriptionElementBase element,
466 WfoBackboneExportTable factsTable) {
467 // ColDpExportTable table = ColDpExportTable.FACT_SOURCES;
468 // try {
469 // Set<DescriptionElementSource> sources = element.getSources();
470 //
471 // for (DescriptionElementSource source : sources) {
472 // if (!(source.getType().equals(OriginalSourceType.Import)
473 // && state.getConfig().isExcludeImportSources())) {
474 // String[] csvLine = new String[table.getSize()];
475 // Reference ref = source.getCitation();
476 // if ((ref == null) && (source.getNameUsedInSource() == null)) {
477 // continue;
478 // }
479 // if (ref != null) {
480 // if (!state.getReferenceStore().contains(ref.getUuid())) {
481 // handleReference(state, ref);
482 //
483 // }
484 // csvLine[table.getIndex(ColDpExportTable.REFERENCE_FK)] = getId(state, ref);
485 // }
486 // csvLine[table.getIndex(ColDpExportTable.FACT_FK)] = getId(state, element);
487 //
488 // csvLine[table.getIndex(ColDpExportTable.NAME_IN_SOURCE_FK)] = getId(state,
489 // source.getNameUsedInSource());
490 // csvLine[table.getIndex(ColDpExportTable.FACT_TYPE)] = factsTable.getTableName();
491 // if (StringUtils.isBlank(csvLine[table.getIndex(ColDpExportTable.REFERENCE_FK)])
492 // && StringUtils.isBlank(csvLine[table.getIndex(ColDpExportTable.NAME_IN_SOURCE_FK)])) {
493 // continue;
494 // }
495 // state.getProcessor().put(table, source, csvLine);
496 // }
497 // }
498 // } catch (Exception e) {
499 // state.getResult().addException(e, "An unexpected error occurred when handling single source "
500 // + cdmBaseStr(element) + ": " + e.getMessage());
501 // }
502 }
503
504 private String getTitleCache(IIdentifiableEntity identEntity) {
505 if (identEntity == null) {
506 return "";
507 }
508 // TODO 3 titleCache refresh?
509 return identEntity.getTitleCache();
510 }
511
512 private String getId(WfoBackboneExportState state, ICdmBase cdmBase) {
513 if (cdmBase == null) {
514 return "";
515 }
516 // TODO 4 id type, make configurable
517 return cdmBase.getUuid().toString();
518 }
519
520 private void handleSynonym(WfoBackboneExportState state, Synonym synonym, boolean isHomotypic) {
521 try {
522 if (isUnpublished(state.getConfig(), synonym)) {
523 return;
524 }
525
526 WfoBackboneExportTable table = WfoBackboneExportTable.CLASSIFICATION;
527 String[] csvLine = new String[table.getSize()];
528
529 TaxonName name = synonym.getName();
530 handleName(state, table, csvLine, name);
531
532 //accepted name id
533 if (synonym.getAcceptedTaxon()!= null && synonym.getAcceptedTaxon().getName() != null) {
534 TaxonName acceptedName = synonym.getAcceptedTaxon().getName();
535 String acceptedWfoId = getWfoId(state, acceptedName, false);
536 if (acceptedWfoId == null) {
537 String message = "WFO-ID for accepted name is missing. This should not happen. Synonym: " + name.getTitleCache() + "; Accepted name: " + acceptedName.getTitleCache();
538 state.getResult().addError(message, "handleName");
539 state.getResult().setState(ExportResultState.INCOMPLETE_WITH_ERROR);
540 }
541 csvLine[table.getIndex(WfoBackboneExportTable.TAX_ACCEPTED_NAME_ID)] = acceptedWfoId;
542 }
543
544 //status
545 csvLine[table.getIndex(WfoBackboneExportTable.TAX_STATUS)] = isHomotypic ? "homotypicSynonym" : "heterotypicSynonym";
546
547 //TODO 7 URL to taxon, take it from a repository information (currently not yet possible, but maybe we could use a CDM preference instead)
548 if (isNotBlank(state.getConfig().getSourceLinkBaseUrl())) {
549 String taxonSourceLink = makeSynonymSourceLink(state, synonym);
550 csvLine[table.getIndex(WfoBackboneExportTable.REFERENCES)] = taxonSourceLink;
551 }
552
553 handleTaxonBase(state, table, csvLine, synonym);
554
555 state.getProcessor().put(table, synonym, csvLine);
556 } catch (Exception e) {
557 state.getResult().addException(e, "An unexpected error occurred when handling synonym "
558 + cdmBaseStr(synonym) + ": " + e.getMessage());
559 }
560 }
561
562 private String handleName(WfoBackboneExportState state, WfoBackboneExportTable table, String[] csvLine,
563 TaxonName name) {
564
565 name = CdmBase.deproxy(name);
566 if (name == null || state.getNameStore().containsKey(name.getId())) {
567 if (name == null) {
568 state.getResult().addError("No name was given for taxon.", "handleName");
569 state.getResult().setState(ExportResultState.INCOMPLETE_WITH_ERROR);
570 }
571 return null;
572 }
573 String wfoId = null;
574 try {
575
576 Rank rank = name.getRank();
577 state.getNameStore().put(name.getId(), name.getUuid());
578
579 //taxon ID
580 wfoId = getWfoId(state, name, true);
581 if (isBlank(wfoId)) {
582 String message = "No WFO-ID given for taxon " + name.getTitleCache() + ". Taxon ignored";
583 state.getResult().addError(message);
584 state.getResult().setState(ExportResultState.INCOMPLETE_WITH_ERROR);
585 return null;
586 }else {
587 csvLine[table.getIndex(WfoBackboneExportTable.TAXON_ID)] = wfoId;
588 }
589
590 //TODO 9 add IPNI ID if exists, scientific name ID
591 csvLine[table.getIndex(WfoBackboneExportTable.NAME_SCIENTIFIC_NAME_ID)] = null;
592
593 //localID
594 csvLine[table.getIndex(WfoBackboneExportTable.NAME_LOCAL_ID)] = getId(state, name);
595
596 //scientificName
597 if (name.isProtectedTitleCache()) {
598 //TODO 7 make it configurable if we should always take titleCache if titleCache is protected, as nameCache may not necessarily
599 // have complete data if titleCache is protected as it is considered to be irrelevant or at least preliminary
600 String message = "";
601 if (StringUtils.isNotEmpty(name.getNameCache())) {
602 csvLine[table.getIndex(WfoBackboneExportTable.NAME_SCIENTIFIC_NAME)] = name.getNameCache();
603 message = "ScientificName: Name cache " + name.getNameCache() + " used for name with protected titleCache " + name.getTitleCache();
604 }else {
605 csvLine[table.getIndex(WfoBackboneExportTable.NAME_SCIENTIFIC_NAME)] = name.getTitleCache();
606 message = "ScientificName: Name has protected titleCache and no explicit nameCache: " + name.getTitleCache();
607 }
608 state.getResult().addWarning(message); //TODO 7 add location to warning
609 } else {
610 csvLine[table.getIndex(WfoBackboneExportTable.NAME_SCIENTIFIC_NAME)] = name.getNameCache();
611 }
612
613 //rank
614 String rankStr = state.getTransformer().getCacheByRank(rank);
615 if (rankStr == null) {
616 String message = rank == null ? "No rank" : ("Rank not supported by WFO:" + rank.getLabel())
617 + "Taxon not handled in export: " + name.getTitleCache();
618 state.getResult().addWarning(message); //TODO 2 warning sufficient for missing rank? + location
619 return wfoId;
620 }
621 csvLine[table.getIndex(WfoBackboneExportTable.RANK)] = rankStr;
622
623 //authorship
624 //TODO 3 handle empty authorship cache warning
625 csvLine[table.getIndex(WfoBackboneExportTable.NAME_AUTHORSHIP)] = name.getAuthorshipCache();
626
627 //family (use familystr if provided, otherwise try to compute from the family taxon
628 String familyStr = state.getFamilyStr();
629 if (StringUtils.isBlank(familyStr)){
630 if (Rank.FAMILY().equals(name.getRank())){
631 familyStr = name.getNameCache();
632 }
633 if (StringUtils.isNotBlank(familyStr)) {
634 state.setFamilyStr(familyStr);
635 }else {
636 String message = "Obligatory family information is missing";
637 state.getResult().addWarning(message);
638 }
639 }
640 csvLine[table.getIndex(WfoBackboneExportTable.TAX_FAMILY)] = state.getFamilyStr();
641
642 //name parts
643 csvLine[table.getIndex(WfoBackboneExportTable.TAX_GENUS)] = name.isSupraGeneric()? null : name.getGenusOrUninomial();
644 csvLine[table.getIndex(WfoBackboneExportTable.NAME_SPECIFIC_EPITHET)] = name.getSpecificEpithet();
645 csvLine[table.getIndex(WfoBackboneExportTable.NAME_INFRASPECIFIC_EPITHET)] = name.getInfraSpecificEpithet();
646
647 //TODO 3 verbatimTaxonRank, is this needed at all?
648 csvLine[table.getIndex(WfoBackboneExportTable.NAME_VERBATIM_RANK)] = rankStr;
649
650 //name status
651 csvLine[table.getIndex(WfoBackboneExportTable.NAME_STATUS)] = makeNameStatus(state, name);
652
653 //nom. ref
654 String nomRef = NomenclaturalSourceFormatter.INSTANCE().format(name.getNomenclaturalSource());
655 csvLine[table.getIndex(WfoBackboneExportTable.NAME_PUBLISHED_IN)] = nomRef;
656
657 //originalNameID
658 TaxonName originalName = name.getBasionym(); //TODO 5 basionym, order in case there are >1 basionyms
659 if (originalName == null) {
660 originalName = name.getReplacedSynonyms().stream().findFirst().orElse(null);
661 }
662
663 if (originalName != null) {
664 if (!state.getNameStore().containsKey(originalName.getId())) {
665 //TODO 1 handle basionym is in file assertion
666 }
667 String basionymId = getWfoId(state, originalName, false);
668 csvLine[table.getIndex(WfoBackboneExportTable.NAME_ORIGINAL_NAME_ID)] = basionymId;
669 }
670
671 //TODO 1 related names like orth. var., original spelling,
672
673 } catch (Exception e) {
674 state.getResult().addException(e,
675 "An unexpected error occurred when handling the name " + cdmBaseStr(name) + ": " + name.getTitleCache() + ": " + e.getMessage());
676
677 e.printStackTrace();
678 }
679
680 return wfoId;
681 }
682
683 private String getWfoId(WfoBackboneExportState state, TaxonName name, boolean warnIfNotExists) {
684 Identifier wfoId = name.getIdentifier(IdentifierType.uuidWfoNameIdentifier);
685 if (wfoId == null && warnIfNotExists) {
686 String message = "No wfo-id given for name: " + name.getTitleCache()+"/"+ name.getUuid();
687 state.getResult().addWarning(message); //TODO 5 data location
688 }
689 return wfoId == null ? null : wfoId.getIdentifier();
690 }
691
692 private String makeNameStatus(WfoBackboneExportState state, TaxonName name) {
693 try {
694
695 //TODO 1 what is with dubium
696 if (name.isLegitimate()) {
697 if (name.isConserved()) {
698 return "Conserved";
699 }else {
700 return "Valid";
701 }
702 } else if (name.isRejected()) {
703 return "Rejected";
704 } else if (name.isIllegitimate()) {
705 return "Illegitimate";
706 } else if (name.isInvalid()) {
707 //TODO 1 handle original spellings for name status
708 if (name.isOrthographicVariant()) {
709 return "orthografia";
710 }else {
711 return "invalid";
712 }
713 } else {
714 String message = "Unhandled name status case for name: " + name.getTitleCache() +
715 ". Status not handled correctly.";
716 state.getResult().addWarning(message);
717 return "undefined";
718 }
719 } catch (Exception e) {
720 state.getResult().addException(e, "An unexpected error occurred when extracting status string for "
721 + cdmBaseStr(name) + ": " + e.getMessage());
722 return "";
723 }
724 }
725
726 private void handleHomotypicalGroup(WfoBackboneExportState state, HomotypicalGroup group, Taxon acceptedTaxon) {
727 try {
728
729 List<TaxonName> typifiedNames = new ArrayList<>();
730 if (acceptedTaxon != null){
731 List<Synonym> synonymsInGroup = acceptedTaxon.getSynonymsInGroup(group);
732 if (group.equals(acceptedTaxon.getHomotypicGroup())){
733 typifiedNames.add(acceptedTaxon.getName());
734 }
735 synonymsInGroup.stream().forEach(synonym -> typifiedNames.add(CdmBase.deproxy(synonym.getName())));
736 }
737
738
739 TaxonName firstname = null;
740 for (TaxonName name: typifiedNames){
741 Iterator<Taxon> taxa = name.getTaxa().iterator();
742 while(taxa.hasNext()){
743 Taxon taxon = taxa.next();
744 if(!(taxon.isMisapplication() || taxon.isProparteSynonym())){
745 firstname = name;
746 break;
747 }
748 }
749 }
750
751 } catch (Exception e) {
752 state.getResult().addException(e, "An unexpected error occurred when handling homotypic group "
753 + cdmBaseStr(group) + ": " + e.getMessage());
754 }
755 }
756
757 private void handleReference(WfoBackboneExportState state, Reference reference) {
758 try {
759 if (state.getReferenceStore().contains(reference.getUuid())) {
760 return;
761 }
762 reference = CdmBase.deproxy(reference);
763
764 state.addReferenceToStore(reference);
765 WfoBackboneExportTable table = WfoBackboneExportTable.REFERENCE;
766 String[] csvLine = new String[table.getSize()];
767
768 csvLine[table.getIndex(WfoBackboneExportTable.IDENTIFIER)] = getId(state, reference);
769
770 //TODO 2 correct?, ref biblio citation
771 csvLine[table.getIndex(WfoBackboneExportTable.REF_BIBLIO_CITATION)] = reference.getCitation();
772
773 //ref uri
774 if (reference.getDoi() != null) {
775 csvLine[table.getIndex(WfoBackboneExportTable.REF_URI)] = reference.getDoiString();
776 }else {
777 //TODO 2 handle other reference links, e.g. external links
778 }
779 //// csvLine[table.getIndex(ColDpExportTable.LINK)] = null;
780 // if (reference.getUri() != null) {
781 // csvLine[table.getIndex(WfoBackboneExportTable.LINK)] = reference.getUri().toString();
782 // }
783
784 state.getProcessor().put(table, reference, csvLine);
785 } catch (Exception e) {
786 e.printStackTrace();
787 state.getResult().addException(e, "An unexpected error occurred when handling reference "
788 + cdmBaseStr(reference) + ": " + e.getMessage());
789 }
790 }
791
792 /**
793 * Returns a string representation of the {@link CdmBase cdmBase} object for
794 * result messages.
795 */
796 private String cdmBaseStr(CdmBase cdmBase) {
797 if (cdmBase == null) {
798 return "-no object available-";
799 } else {
800 return cdmBase.getClass().getSimpleName() + ": " + cdmBase.getUuid();
801 }
802 }
803
804 @Override
805 protected boolean doCheck(WfoBackboneExportState state) {
806 return false;
807 }
808
809 @Override
810 protected boolean isIgnore(WfoBackboneExportState state) {
811 return false;
812 }
813 }