3 * Copyright (C) 2009 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.io
.pesi
.out
;
12 import java
.sql
.Connection
;
13 import java
.sql
.PreparedStatement
;
14 import java
.sql
.SQLException
;
15 import java
.util
.ArrayList
;
16 import java
.util
.HashMap
;
17 import java
.util
.List
;
20 import org
.apache
.log4j
.Logger
;
21 import org
.springframework
.stereotype
.Component
;
22 import org
.springframework
.transaction
.TransactionStatus
;
24 import eu
.etaxonomy
.cdm
.io
.common
.Source
;
25 import eu
.etaxonomy
.cdm
.io
.common
.mapping
.out
.MethodMapper
;
26 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
27 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
28 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
;
29 import eu
.etaxonomy
.cdm
.model
.name
.NameRelationship
;
30 import eu
.etaxonomy
.cdm
.model
.name
.NomenclaturalCode
;
31 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
32 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameBase
;
33 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
34 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
35 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymRelationship
;
36 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
37 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
38 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
39 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
42 * The export class for relations between {@link eu.etaxonomy.cdm.model.taxon.TaxonBase TaxonBases}.<p>
43 * Inserts into DataWarehouse database table <code>RelTaxon</code>.
49 public class PesiRelTaxonExport
extends PesiExportBase
{
50 private static final Logger logger
= Logger
.getLogger(PesiRelTaxonExport
.class);
51 private static final Class
<?
extends CdmBase
> standardMethodParameter
= RelationshipBase
.class;
53 private static int modCount
= 1000;
54 private static final String dbTableName
= "RelTaxon";
55 private static final String pluralString
= "Relationships";
56 private static PreparedStatement synonymsStmt
;
57 private HashMap
<Rank
, Rank
> rank2endRankMap
= new HashMap
<Rank
, Rank
>();
58 private List
<Rank
> rankList
= new ArrayList
<Rank
>();
59 private PesiExportMapping mapping
;
60 private int count
= 0;
61 private static NomenclaturalCode nomenclaturalCode2
;
63 public PesiRelTaxonExport() {
68 * @see eu.etaxonomy.cdm.io.common.DbExportBase#getStandardMethodParameter()
71 public Class
<?
extends CdmBase
> getStandardMethodParameter() {
72 return standardMethodParameter
;
76 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doCheck(eu.etaxonomy.cdm.io.common.IoStateBase)
79 protected boolean doCheck(PesiExportState state
) {
80 boolean result
= true;
85 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doInvoke(eu.etaxonomy.cdm.io.common.IoStateBase)
88 protected void doInvoke(PesiExportState state
) {
90 logger
.info("*** Started Making " + pluralString
+ " ...");
92 Connection connection
= state
.getConfig().getDestination().getConnection();
93 String synonymsSql
= "UPDATE Taxon SET KingdomFk = ?, RankFk = ?, RankCache = ? WHERE TaxonId = ?";
94 synonymsStmt
= connection
.prepareStatement(synonymsSql
);
96 // Stores whether this invoke was successful or not.
97 boolean success
= true;
99 // PESI: Clear the database table RelTaxon.
102 // Get specific mappings: (CDM) Relationship -> (PESI) RelTaxon
103 mapping
= getMapping();
105 // Initialize the db mapper
106 mapping
.initialize(state
);
108 TransactionStatus txStatus
= null;
109 List
<Classification
> classificationList
= null;
111 // Specify starting ranks for tree traversing
112 rankList
.add(Rank
.KINGDOM());
113 rankList
.add(Rank
.GENUS());
115 // Specify where to stop traversing (value) when starting at a specific Rank (key)
116 rank2endRankMap
.put(Rank
.GENUS(), null); // Since NULL does not match an existing Rank, traverse all the way down to the leaves
117 rank2endRankMap
.put(Rank
.KINGDOM(), Rank
.GENUS()); // excludes rank genus
119 // Retrieve list of classifications
120 txStatus
= startTransaction(true);
121 logger
.info("Started transaction. Fetching all classifications...");
122 classificationList
= getClassificationService().listClassifications(null, 0, null, null);
123 commitTransaction(txStatus
);
124 logger
.debug("Committed transaction.");
126 logger
.info("Fetched " + classificationList
.size() + " classification(s).");
128 for (Classification classification
: classificationList
) {
129 for (Rank rank
: rankList
) {
131 txStatus
= startTransaction(true);
132 logger
.info("Started transaction to fetch all rootNodes specific to Rank " + rank
.getLabel() + " ...");
134 List
<TaxonNode
> rankSpecificRootNodes
= getClassificationService().loadRankSpecificRootNodes(classification
, rank
, null);
135 logger
.info("Fetched " + rankSpecificRootNodes
.size() + " RootNodes for Rank " + rank
.getLabel());
137 commitTransaction(txStatus
);
138 logger
.debug("Committed transaction.");
140 for (TaxonNode rootNode
: rankSpecificRootNodes
) {
141 txStatus
= startTransaction(false);
142 Rank endRank
= rank2endRankMap
.get(rank
);
143 if (endRank
!= null) {
144 logger
.info("Started transaction to traverse childNodes of rootNode (" + rootNode
.getUuid() + ") till Rank " + endRank
.getLabel() + " ...");
146 logger
.info("Started transaction to traverse childNodes of rootNode (" + rootNode
.getUuid() + ") till leaves are reached ...");
149 TaxonNode newNode
= getTaxonNodeService().load(rootNode
.getUuid());
151 if (isPesiTaxon(newNode
.getTaxon())){
153 TaxonNode parentNode
= newNode
.getParent();
155 success
&=traverseTree(newNode
, parentNode
, endRank
, state
);
157 commitTransaction(txStatus
);
158 logger
.debug("Committed transaction.");
160 logger
.debug("Taxon is not in PESI");
167 logger
.warn("*** Finished Making " + pluralString
+ " ..." + getSuccessString(success
));
170 state
.setUnsuccessfull();
173 } catch (SQLException e
) {
175 logger
.error(e
.getMessage());
176 state
.setUnsuccessfull();
182 * Traverses the classification recursively and stores determined values for every Taxon.
189 private boolean traverseTree(TaxonNode childNode
, TaxonNode parentNode
, Rank fetchLevel
, PesiExportState state
) {
190 boolean success
= true;
191 // Traverse all branches from this childNode until specified fetchLevel is reached.
192 TaxonBase
<?
> childTaxon
= childNode
.getTaxon();
193 if (childTaxon
!= null) {
194 if (isPesiTaxon(childTaxon
)){
195 if (childTaxon
.getName() != null) {
196 Rank childTaxonNameRank
= childTaxon
.getName().getRank();
197 if (childTaxonNameRank
!= null) {
198 if (! childTaxonNameRank
.equals(fetchLevel
)) {
200 success
&= saveData(childNode
, parentNode
, state
);
202 for (TaxonNode newNode
: childNode
.getChildNodes()) {
203 success
&= traverseTree(newNode
, childNode
, fetchLevel
, state
);
207 // logger.error("Target Rank " + fetchLevel.getLabel() + " reached");
211 logger
.warn("Rank is NULL. FetchLevel can not be checked: " + childTaxon
.getUuid() + " (" + childTaxon
.getTitleCache() + ")");
214 logger
.error("TaxonName is NULL for taxon: " + childTaxon
.getUuid());
217 logger
.debug("Taxon is not a PESI taxon: " + childTaxon
.getUuid());
221 logger
.error("Taxon is NULL for TaxonNode: " + childNode
.getUuid());
227 * Stores values in database for every recursive round.
232 * @param currentTaxonFk
234 private boolean saveData(TaxonNode childNode
, TaxonNode parentNode
, PesiExportState state
) {
235 boolean success
= true;
236 Taxon childNodeTaxon
= childNode
.getTaxon();
237 if (childNodeTaxon
!= null) {
238 // TaxonRelationships
239 success
&= saveTaxonRelationships(state
, childNodeTaxon
);
240 // TaxonNameRelationships
241 success
&= saveNameRelationships(state
, childNodeTaxon
);
242 // SynonymRelationships
243 success
&= saveSynonymAndSynNameRelationships(state
, childNodeTaxon
);
249 private boolean saveSynonymAndSynNameRelationships(PesiExportState state
, Taxon childNodeTaxon
) {
250 boolean success
= true;
251 for (SynonymRelationship synRel
: childNodeTaxon
.getSynonymRelations()) { // synonyms of accepted taxon
252 Synonym synonym
= synRel
.getSynonym();
253 TaxonNameBase
<?
,?
> synonymTaxonName
= synonym
.getName();
254 if (! isPesiTaxon(synonym
)){
255 logger
.warn("Synonym " + synonym
.getId() + " of synonym relation " + synRel
.getId() + " is not a PESI taxon. Can't export relationship");
259 // Store synonym data in Taxon table
260 invokeSynonyms(state
, synonymTaxonName
);
264 Set
<SynonymRelationship
> synonymRelations
= synonym
.getSynonymRelations();
265 state
.setCurrentFromObject(synonym
);
266 for (SynonymRelationship synonymRelationship
: synonymRelations
) { //needed? Maybe to make sure that there are no partial synonym relations missed ??
268 if (neededValuesNotNull(synonymRelationship
, state
)) {
269 doCount(count
++, modCount
, pluralString
);
270 success
&= mapping
.invoke(synonymRelationship
);
273 } catch (SQLException e
) {
274 logger
.error("SynonymRelationship (" + synonymRelationship
.getUuid() + ") could not be stored : " + e
.getMessage());
278 // SynonymNameRelationship
279 success
&= saveNameRelationships(state
, synonym
);
284 private boolean saveNameRelationships(PesiExportState state
, TaxonBase taxonBase
) {
285 boolean success
= true;
286 TaxonNameBase
<?
,?
> childNodeTaxonName
= taxonBase
.getName();
289 Set
<NameRelationship
> nameRelations
= childNodeTaxonName
.getRelationsFromThisName();
290 state
.setCurrentFromObject(taxonBase
);
291 boolean isFrom
= true;
292 success
&= saveOneSideNameRelation(state
, isFrom
, nameRelations
);
295 nameRelations
= childNodeTaxonName
.getRelationsToThisName();
296 state
.setCurrentToObject(taxonBase
);
298 success
&= saveOneSideNameRelation(state
, isFrom
, nameRelations
);
303 private boolean saveOneSideNameRelation(PesiExportState state
, boolean isFrom
, Set
<NameRelationship
> nameRelations
) {
304 boolean success
= true;
305 for (NameRelationship nameRelation
: nameRelations
) {
307 TaxonNameBase
<?
,?
> relatedName
= isFrom ? nameRelation
.getToName(): nameRelation
.getFromName();
308 if ( isPurePesiName(relatedName
)){
309 success
&= checkAndInvokeNameRelation(state
, nameRelation
, relatedName
, isFrom
);
311 for (TaxonBase
<?
> relatedTaxon
: getPesiTaxa(relatedName
)){
312 success
&= checkAndInvokeNameRelation(state
, nameRelation
, relatedTaxon
, isFrom
);
315 } catch (SQLException e
) {
316 logger
.error("NameRelationship " + nameRelation
.getUuid() + " for " + nameRelation
.getFromName().getTitleCache() + " and " + nameRelation
.getToName().getTitleCache() + " could not be created: " + e
.getMessage());
323 private boolean checkAndInvokeNameRelation(PesiExportState state
, NameRelationship nameRelation
, IdentifiableEntity
<?
> relatedObject
, boolean isFrom
) throws SQLException
{
324 boolean success
= true;
326 state
.setCurrentToObject(relatedObject
);
328 state
.setCurrentFromObject(relatedObject
);
330 if (neededValuesNotNull(nameRelation
, state
)) {
331 doCount(count
++, modCount
, pluralString
);
332 success
&= mapping
.invoke(nameRelation
);
337 private boolean saveTaxonRelationships(PesiExportState state
, Taxon childNodeTaxon
) {
338 boolean success
= true;
339 Taxon taxon
= childNodeTaxon
;
340 Set
<TaxonRelationship
> taxonRelations
= taxon
.getRelationsToThisTaxon();
341 for (TaxonRelationship taxonRelationship
: taxonRelations
) {
343 if (neededValuesNotNull(taxonRelationship
, state
)) {
344 doCount(count
++, modCount
, pluralString
);
345 success
&= mapping
.invoke(taxonRelationship
);
347 } catch (SQLException e
) {
348 logger
.error("TaxonRelationship could not be created for this TaxonRelation (" + taxonRelationship
.getUuid() + "): " + e
.getMessage());
355 * Determines synonym related data and saves them.
359 private static void invokeSynonyms(PesiExportState state
, TaxonNameBase synonymTaxonName
) {
360 // Store KingdomFk and Rank information in Taxon table
361 Integer kingdomFk
= PesiTransformer
.nomenClaturalCode2Kingdom(synonymTaxonName
.getNomenclaturalCode());
362 Integer synonymFk
= state
.getDbId(synonymTaxonName
);
364 saveSynonymData(synonymTaxonName
, synonymTaxonName
.getNomenclaturalCode(), kingdomFk
, synonymFk
);
368 * Stores synonym data.
370 * @param nomenclaturalCode
372 * @param synonymParentTaxonFk
373 * @param currentTaxonFk
375 private static boolean saveSynonymData(TaxonNameBase taxonName
,
376 NomenclaturalCode nomenclaturalCode
, Integer kingdomFk
,
377 Integer currentSynonymFk
) {
379 if (kingdomFk
!= null) {
380 synonymsStmt
.setInt(1, kingdomFk
);
382 synonymsStmt
.setObject(1, null);
385 Integer rankFk
= getRankFk(taxonName
, nomenclaturalCode
);
386 if (rankFk
!= null) {
387 synonymsStmt
.setInt(2, rankFk
);
389 synonymsStmt
.setObject(2, null);
391 synonymsStmt
.setString(3, getRankCache(taxonName
, nomenclaturalCode
));
393 if (currentSynonymFk
!= null) {
394 synonymsStmt
.setInt(4, currentSynonymFk
);
396 synonymsStmt
.setObject(4, null);
398 synonymsStmt
.executeUpdate();
400 } catch (SQLException e
) {
401 logger
.error("SQLException during invoke for taxonName - " + taxonName
.getUuid() + " (" + taxonName
.getTitleCache() + "): " + e
.getMessage());
408 * Checks whether needed values for an entity are NULL.
411 private boolean neededValuesNotNull(RelationshipBase
<?
, ?
, ?
> relationship
, PesiExportState state
) {
412 boolean result
= true;
413 if (getTaxonFk1(relationship
, state
) == null) {
414 logger
.warn("TaxonFk1 is NULL, but is not allowed to be. Therefore no record was written to export database for this relationship: " + relationship
.getUuid());
417 if (getTaxonFk2(relationship
, state
) == null) {
418 logger
.warn("TaxonFk2 is NULL, but is not allowed to be. Therefore no record was written to export database for this relationship: " + relationship
.getUuid());
425 * Deletes all entries of database tables related to <code>RelTaxon</code>.
426 * @param state The {@link PesiExportState PesiExportState}.
427 * @return Whether the delete operation was successful or not.
429 protected boolean doDelete(PesiExportState state
) {
430 PesiExportConfigurator pesiConfig
= (PesiExportConfigurator
) state
.getConfig();
433 Source destination
= pesiConfig
.getDestination();
436 sql
= "DELETE FROM " + dbTableName
;
437 destination
.setQuery(sql
);
438 destination
.update(sql
);
443 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#isIgnore(eu.etaxonomy.cdm.io.common.IoStateBase)
446 protected boolean isIgnore(PesiExportState state
) {
447 return ! state
.getConfig().isDoRelTaxa();
451 * Returns the <code>TaxonFk1</code> attribute. It corresponds to a CDM <code>TaxonRelationship</code>.
452 * @param relationship The {@link RelationshipBase Relationship}.
453 * @param state The {@link PesiExportState PesiExportState}.
454 * @return The <code>TaxonFk1</code> attribute.
457 private static Integer
getTaxonFk1(RelationshipBase
<?
, ?
, ?
> relationship
, PesiExportState state
) {
458 return getObjectFk(relationship
, state
, true);
462 * Returns the <code>TaxonFk2</code> attribute. It corresponds to a CDM <code>SynonymRelationship</code>.
463 * @param relationship The {@link RelationshipBase Relationship}.
464 * @param state The {@link PesiExportState PesiExportState}.
465 * @return The <code>TaxonFk2</code> attribute.
468 private static Integer
getTaxonFk2(RelationshipBase
<?
, ?
, ?
> relationship
, PesiExportState state
) {
469 return getObjectFk(relationship
, state
, false);
473 * Returns the <code>RelTaxonQualifierFk</code> attribute.
474 * @param relationship The {@link RelationshipBase Relationship}.
475 * @return The <code>RelTaxonQualifierFk</code> attribute.
478 @SuppressWarnings("unused")
479 private static Integer
getRelTaxonQualifierFk(RelationshipBase
<?
, ?
, ?
> relationship
) {
480 return PesiTransformer
.taxonRelation2RelTaxonQualifierFk(relationship
);
484 * Returns the <code>RelQualifierCache</code> attribute.
485 * @param relationship The {@link RelationshipBase Relationship}.
486 * @return The <code>RelQualifierCache</code> attribute.
489 @SuppressWarnings("unused")
490 private static String
getRelQualifierCache(RelationshipBase
<?
, ?
, ?
> relationship
) {
491 String result
= null;
492 NomenclaturalCode code
= null;
493 if (relationship
.isInstanceOf(TaxonRelationship
.class)){
494 code
= CdmBase
.deproxy(relationship
, TaxonRelationship
.class).getToTaxon().getName().getNomenclaturalCode();
495 }else if (relationship
.isInstanceOf(SynonymRelationship
.class)){
496 code
= CdmBase
.deproxy(relationship
, SynonymRelationship
.class).getAcceptedTaxon().getName().getNomenclaturalCode();
497 }else if (relationship
.isInstanceOf(NameRelationship
.class)){
498 code
= CdmBase
.deproxy(relationship
, NameRelationship
.class).getFromName().getNomenclaturalCode();
501 result
= PesiTransformer
.taxonRelation2RelTaxonQualifierCache(relationship
, code
);
503 logger
.error("NomenclaturalCode is NULL while creating the following relationship: " + relationship
.getUuid());
509 * Returns the <code>Notes</code> attribute.
510 * @param relationship The {@link RelationshipBase Relationship}.
511 * @return The <code>Notes</code> attribute.
514 @SuppressWarnings("unused")
515 private static String
getNotes(RelationshipBase
<?
, ?
, ?
> relationship
) {
521 * Returns the database key of an object in the given relationship.
522 * @param relationship {@link RelationshipBase RelationshipBase}.
523 * @param state {@link PesiExportState PesiExportState}.
524 * @param isFrom A boolean value indicating whether the database key of the parent or child in this relationship is searched. <code>true</code> means the child is searched. <code>false</code> means the parent is searched.
525 * @return The database key of an object in the given relationship.
527 private static Integer
getObjectFk(RelationshipBase
<?
, ?
, ?
> relationship
, PesiExportState state
, boolean isFrom
) {
528 TaxonBase
<?
> taxonBase
= null;
529 if (relationship
.isInstanceOf(TaxonRelationship
.class)) {
530 TaxonRelationship tr
= (TaxonRelationship
)relationship
;
531 taxonBase
= (isFrom
) ? tr
.getFromTaxon(): tr
.getToTaxon();
532 } else if (relationship
.isInstanceOf(SynonymRelationship
.class)) {
533 SynonymRelationship sr
= (SynonymRelationship
)relationship
;
534 taxonBase
= (isFrom
) ? sr
.getSynonym() : sr
.getAcceptedTaxon();
535 } else if (relationship
.isInstanceOf(NameRelationship
.class)) {
537 return state
.getDbId(state
.getCurrentFromObject());
539 return state
.getDbId(state
.getCurrentToObject());
542 if (taxonBase
!= null) {
543 if (! isPesiTaxon(taxonBase
)){
544 logger
.warn("Related taxonBase is not a PESI taxon. Taxon: " + taxonBase
.getId() + "/" + taxonBase
.getUuid() + "; TaxonRel: " + relationship
.getId() + "(" + relationship
.getType().getTitleCache() + ")");
547 return state
.getDbId(taxonBase
);
551 logger
.warn("No taxon found in state for relationship: " + relationship
.toString());
556 * Returns the <code>RankFk</code> attribute.
557 * @param taxonName The {@link TaxonNameBase TaxonName}.
558 * @param nomenclaturalCode The {@link NomenclaturalCode NomenclaturalCode}.
559 * @return The <code>RankFk</code> attribute.
562 private static Integer
getRankFk(TaxonNameBase taxonName
, NomenclaturalCode nomenclaturalCode
) {
563 Integer result
= null;
564 if (nomenclaturalCode
!= null) {
565 if (taxonName
!= null && taxonName
.getRank() == null) {
566 logger
.warn("Rank is null: " + taxonName
.getUuid() + " (" + taxonName
.getTitleCache() + ")");
568 result
= PesiTransformer
.rank2RankId(taxonName
.getRank(), PesiTransformer
.nomenClaturalCode2Kingdom(nomenclaturalCode
));
569 if (result
== null) {
570 logger
.warn("Rank could not be determined for PESI-Kingdom-Id " + PesiTransformer
.nomenClaturalCode2Kingdom(nomenclaturalCode
) + " and TaxonName " + taxonName
.getUuid() + " (" + taxonName
.getTitleCache() + ")");
577 * Returns the <code>RankCache</code> attribute.
578 * @param taxonName The {@link TaxonNameBase TaxonName}.
579 * @param nomenclaturalCode The {@link NomenclaturalCode NomenclaturalCode}.
580 * @return The <code>RankCache</code> attribute.
583 private static String
getRankCache(TaxonNameBase taxonName
, NomenclaturalCode nomenclaturalCode
) {
584 String result
= null;
585 if (nomenclaturalCode
!= null) {
586 result
= PesiTransformer
.rank2RankCache(taxonName
.getRank(), PesiTransformer
.nomenClaturalCode2Kingdom(nomenclaturalCode
));
592 * Returns the CDM to PESI specific export mappings.
593 * @return The {@link PesiExportMapping PesiExportMapping}.
595 private PesiExportMapping
getMapping() {
596 PesiExportMapping mapping
= new PesiExportMapping(dbTableName
);
598 mapping
.addMapper(MethodMapper
.NewInstance("TaxonFk1", this.getClass(), "getTaxonFk1", standardMethodParameter
, PesiExportState
.class));
599 mapping
.addMapper(MethodMapper
.NewInstance("TaxonFk2", this.getClass(), "getTaxonFk2", standardMethodParameter
, PesiExportState
.class));
600 mapping
.addMapper(MethodMapper
.NewInstance("RelTaxonQualifierFk", this));
601 mapping
.addMapper(MethodMapper
.NewInstance("RelQualifierCache", this));
602 mapping
.addMapper(MethodMapper
.NewInstance("Notes", this));