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
.RelationshipBase
;
28 import eu
.etaxonomy
.cdm
.model
.name
.NameRelationship
;
29 import eu
.etaxonomy
.cdm
.model
.name
.NomenclaturalCode
;
30 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
31 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameBase
;
32 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
33 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
34 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymRelationship
;
35 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
36 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
37 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
38 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
41 * The export class for relations between {@link eu.etaxonomy.cdm.model.taxon.TaxonBase TaxonBases}.<p>
42 * Inserts into DataWarehouse database table <code>RelTaxon</code>.
48 public class PesiRelTaxonExport
extends PesiExportBase
{
49 private static final Logger logger
= Logger
.getLogger(PesiRelTaxonExport
.class);
50 private static final Class
<?
extends CdmBase
> standardMethodParameter
= RelationshipBase
.class;
52 private static int modCount
= 1000;
53 private static final String dbTableName
= "RelTaxon";
54 private static final String pluralString
= "Relationships";
55 private static PreparedStatement synonymsStmt
;
56 private HashMap
<Rank
, Rank
> rankMap
= new HashMap
<Rank
, Rank
>();
57 private List
<Rank
> rankList
= new ArrayList
<Rank
>();
58 private PesiExportMapping mapping
;
59 private int count
= 0;
60 private static NomenclaturalCode nomenclaturalCode
;
62 public PesiRelTaxonExport() {
67 * @see eu.etaxonomy.cdm.io.common.DbExportBase#getStandardMethodParameter()
70 public Class
<?
extends CdmBase
> getStandardMethodParameter() {
71 return standardMethodParameter
;
75 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doCheck(eu.etaxonomy.cdm.io.common.IoStateBase)
78 protected boolean doCheck(PesiExportState state
) {
79 boolean result
= true;
84 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doInvoke(eu.etaxonomy.cdm.io.common.IoStateBase)
87 protected void doInvoke(PesiExportState state
) {
89 logger
.error("*** Started Making " + pluralString
+ " ...");
91 Connection connection
= state
.getConfig().getDestination().getConnection();
92 String synonymsSql
= "UPDATE Taxon SET KingdomFk = ?, RankFk = ?, RankCache = ? WHERE TaxonId = ?";
93 synonymsStmt
= connection
.prepareStatement(synonymsSql
);
95 // Stores whether this invoke was successful or not.
96 boolean success
= true;
98 // PESI: Clear the database table RelTaxon.
101 // Get specific mappings: (CDM) Relationship -> (PESI) RelTaxon
102 mapping
= getMapping();
104 // Initialize the db mapper
105 mapping
.initialize(state
);
107 TransactionStatus txStatus
= null;
108 List
<Classification
> classificationList
= null;
110 // Specify starting ranks for tree traversing
111 rankList
.add(Rank
.KINGDOM());
112 rankList
.add(Rank
.GENUS());
114 // Specify where to stop traversing (value) when starting at a specific Rank (key)
115 rankMap
.put(Rank
.GENUS(), null); // Since NULL does not match an existing Rank, traverse all the way down to the leaves
116 rankMap
.put(Rank
.KINGDOM(), Rank
.GENUS()); // excludes rank genus
118 // Retrieve list of classifications
119 txStatus
= startTransaction(true);
120 logger
.info("Started transaction. Fetching all classifications...");
121 classificationList
= getClassificationService().listClassifications(null, 0, null, null);
122 commitTransaction(txStatus
);
123 logger
.debug("Committed transaction.");
125 logger
.info("Fetched " + classificationList
.size() + " classification(s).");
127 for (Classification classification
: classificationList
) {
128 for (Rank rank
: rankList
) {
130 txStatus
= startTransaction(true);
131 logger
.info("Started transaction to fetch all rootNodes specific to Rank " + rank
.getLabel() + " ...");
133 List
<TaxonNode
> rankSpecificRootNodes
= getClassificationService().loadRankSpecificRootNodes(classification
, rank
, null);
134 logger
.info("Fetched " + rankSpecificRootNodes
.size() + " RootNodes for Rank " + rank
.getLabel());
136 commitTransaction(txStatus
);
137 logger
.debug("Committed transaction.");
139 for (TaxonNode rootNode
: rankSpecificRootNodes
) {
140 txStatus
= startTransaction(false);
141 Rank endRank
= rankMap
.get(rank
);
142 if (endRank
!= null) {
143 logger
.info("Started transaction to traverse childNodes of rootNode (" + rootNode
.getUuid() + ") till Rank " + endRank
.getLabel() + " ...");
145 logger
.info("Started transaction to traverse childNodes of rootNode (" + rootNode
.getUuid() + ") till leaves are reached ...");
148 TaxonNode newNode
= getTaxonNodeService().load(rootNode
.getUuid());
150 TaxonNode parentNode
= newNode
.getParent();
152 success
&=traverseTree(newNode
, parentNode
, rankMap
.get(rank
), state
);
154 commitTransaction(txStatus
);
155 logger
.debug("Committed transaction.");
161 logger
.error("*** Finished Making " + pluralString
+ " ..." + getSuccessString(success
));
164 state
.setUnsuccessfull();
167 } catch (SQLException e
) {
169 logger
.error(e
.getMessage());
170 state
.setUnsuccessfull();
176 * Traverses the classification recursively and stores determined values for every Taxon.
183 private boolean traverseTree(TaxonNode childNode
, TaxonNode parentNode
, Rank fetchLevel
, PesiExportState state
) {
184 boolean success
= true;
185 // Traverse all branches from this childNode until specified fetchLevel is reached.
186 TaxonBase
<?
> childTaxon
= childNode
.getTaxon();
187 if (childTaxon
!= null) {
188 if (isPesiTaxon(childTaxon
)){
189 if (childTaxon
.getName() != null) {
190 Rank childTaxonNameRank
= childTaxon
.getName().getRank();
191 if (childTaxonNameRank
!= null) {
192 if (! childTaxonNameRank
.equals(fetchLevel
)) {
194 success
&= saveData(childNode
, parentNode
, state
);
196 for (TaxonNode newNode
: childNode
.getChildNodes()) {
197 success
&= traverseTree(newNode
, childNode
, fetchLevel
, state
);
201 // logger.error("Target Rank " + fetchLevel.getLabel() + " reached");
205 logger
.error("Rank is NULL. FetchLevel can not be checked: " + childTaxon
.getUuid() + " (" + childTaxon
.getTitleCache() + ")");
208 logger
.error("TaxonName is NULL for taxon: " + childTaxon
.getUuid());
211 logger
.debug("Taxon is not a PESI taxon: " + childTaxon
.getUuid());
215 logger
.error("Taxon is NULL for TaxonNode: " + childNode
.getUuid());
221 * Stores values in database for every recursive round.
226 * @param currentTaxonFk
228 private boolean saveData(TaxonNode childNode
, TaxonNode parentNode
, PesiExportState state
) {
229 boolean success
= true;
230 Taxon childNodeTaxon
= childNode
.getTaxon();
231 if (childNodeTaxon
!= null) {
232 TaxonNameBase
<?
,?
> childNodeTaxonName
= childNodeTaxon
.getName();
233 nomenclaturalCode
= PesiTransformer
.getNomenclaturalCode(childNodeTaxonName
);
235 if (childNodeTaxonName
!= null) {
237 // TaxonRelationships
238 Set
<Taxon
> taxa
= childNodeTaxonName
.getTaxa(); // accepted taxa
239 if (taxa
.size() == 1) {
240 Taxon taxon
=taxa
.iterator().next();
241 Set
<TaxonRelationship
> taxonRelations
= taxon
.getRelationsToThisTaxon();
242 for (TaxonRelationship taxonRelationship
: taxonRelations
) {
244 if (neededValuesNotNull(taxonRelationship
, state
)) {
245 doCount(count
++, modCount
, pluralString
);
246 success
&= mapping
.invoke(taxonRelationship
);
248 } catch (SQLException e
) {
249 logger
.error("TaxonRelationship could not be created for this TaxonRelation (" + taxonRelationship
.getUuid() + "): " + e
.getMessage());
252 } else if (taxa
.size() > 1) {
253 logger
.error("TaxonRelationship could not be created. This TaxonNode's taxon name has " + taxa
.size() + " Taxa: " + childNodeTaxon
.getUuid() + " (" + childNodeTaxon
.getTitleCache() + ")");
256 // TaxonNameRelationships
257 Set
<NameRelationship
> nameRelations
= childNodeTaxonName
.getRelationsFromThisName();
258 for (NameRelationship nameRelation
: nameRelations
) {
260 if (neededValuesNotNull(nameRelation
, state
)) {
261 doCount(count
++, modCount
, pluralString
);
262 success
&= mapping
.invoke(nameRelation
);
264 } catch (SQLException e
) {
265 logger
.error("NameRelationship could not be created: " + e
.getMessage());
271 // SynonymRelationships
272 Set
<Synonym
> synonyms
= childNodeTaxon
.getSynonyms(); // synonyms of accepted taxon
273 for (Synonym synonym
: synonyms
) {
274 TaxonNameBase
<?
,?
> synonymTaxonName
= synonym
.getName();
276 // Store synonym data in Taxon table
277 invokeSynonyms(state
, synonymTaxonName
);
279 Set
<SynonymRelationship
> synonymRelations
= synonym
.getSynonymRelations();
280 for (SynonymRelationship synonymRelationship
: synonymRelations
) {
282 if (neededValuesNotNull(synonymRelationship
, state
)) {
283 doCount(count
++, modCount
, pluralString
);
284 success
&= mapping
.invoke(synonymRelationship
);
287 } catch (SQLException e
) {
288 logger
.error("SynonymRelationship could not be created for this SynonymRelation (" + synonymRelationship
.getUuid() + "): " + e
.getMessage());
292 // SynonymNameRelationship
293 Set
<NameRelationship
> nameRelations
= synonymTaxonName
.getRelationsFromThisName();
294 for (NameRelationship nameRelation
: nameRelations
) {
296 if (neededValuesNotNull(nameRelation
, state
)) {
297 doCount(count
++, modCount
, pluralString
);
298 success
&= mapping
.invoke(nameRelation
);
300 } catch (SQLException e
) {
301 logger
.error("NameRelationship could not be created for this NameRelation (" + nameRelation
.getUuid() + "): " + e
.getMessage());
313 * Determines synonym related data and saves them.
317 private static void invokeSynonyms(PesiExportState state
, TaxonNameBase synonymTaxonName
) {
318 // Store KingdomFk and Rank information in Taxon table
319 Integer kingdomFk
= PesiTransformer
.nomenClaturalCode2Kingdom(nomenclaturalCode
);
320 Integer synonymFk
= state
.getDbId(synonymTaxonName
);
322 saveSynonymData(synonymTaxonName
, nomenclaturalCode
, kingdomFk
, synonymFk
);
326 * Stores synonym data.
328 * @param nomenclaturalCode
330 * @param synonymParentTaxonFk
331 * @param currentTaxonFk
333 private static boolean saveSynonymData(TaxonNameBase taxonName
,
334 NomenclaturalCode nomenclaturalCode
, Integer kingdomFk
,
335 Integer currentSynonymFk
) {
337 if (kingdomFk
!= null) {
338 synonymsStmt
.setInt(1, kingdomFk
);
340 synonymsStmt
.setObject(1, null);
343 Integer rankFk
= getRankFk(taxonName
, nomenclaturalCode
);
344 if (rankFk
!= null) {
345 synonymsStmt
.setInt(2, rankFk
);
347 synonymsStmt
.setObject(2, null);
349 synonymsStmt
.setString(3, getRankCache(taxonName
, nomenclaturalCode
));
351 if (currentSynonymFk
!= null) {
352 synonymsStmt
.setInt(4, currentSynonymFk
);
354 synonymsStmt
.setObject(4, null);
356 synonymsStmt
.executeUpdate();
358 } catch (SQLException e
) {
359 logger
.error("SQLException during invoke for taxonName - " + taxonName
.getUuid() + " (" + taxonName
.getTitleCache() + "): " + e
.getMessage());
366 * Checks whether needed values for an entity are NULL.
369 private boolean neededValuesNotNull(RelationshipBase
<?
, ?
, ?
> relationship
, PesiExportState state
) {
370 boolean result
= true;
371 if (getTaxonFk1(relationship
, state
) == null) {
372 logger
.error("TaxonFk1 is NULL, but is not allowed to be. Therefore no record was written to export database for this relationship: " + relationship
.getUuid());
375 if (getTaxonFk2(relationship
, state
) == null) {
376 logger
.error("TaxonFk2 is NULL, but is not allowed to be. Therefore no record was written to export database for this relationship: " + relationship
.getUuid());
383 * Deletes all entries of database tables related to <code>RelTaxon</code>.
384 * @param state The {@link PesiExportState PesiExportState}.
385 * @return Whether the delete operation was successful or not.
387 protected boolean doDelete(PesiExportState state
) {
388 PesiExportConfigurator pesiConfig
= (PesiExportConfigurator
) state
.getConfig();
391 Source destination
= pesiConfig
.getDestination();
394 sql
= "DELETE FROM " + dbTableName
;
395 destination
.setQuery(sql
);
396 destination
.update(sql
);
401 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#isIgnore(eu.etaxonomy.cdm.io.common.IoStateBase)
404 protected boolean isIgnore(PesiExportState state
) {
405 return ! state
.getConfig().isDoRelTaxa();
409 * Returns the <code>TaxonFk1</code> attribute. It corresponds to a CDM <code>TaxonRelationship</code>.
410 * @param relationship The {@link RelationshipBase Relationship}.
411 * @param state The {@link PesiExportState PesiExportState}.
412 * @return The <code>TaxonFk1</code> attribute.
415 private static Integer
getTaxonFk1(RelationshipBase
<?
, ?
, ?
> relationship
, PesiExportState state
) {
416 return getObjectFk(relationship
, state
, true);
420 * Returns the <code>TaxonFk2</code> attribute. It corresponds to a CDM <code>SynonymRelationship</code>.
421 * @param relationship The {@link RelationshipBase Relationship}.
422 * @param state The {@link PesiExportState PesiExportState}.
423 * @return The <code>TaxonFk2</code> attribute.
426 private static Integer
getTaxonFk2(RelationshipBase
<?
, ?
, ?
> relationship
, PesiExportState state
) {
427 return getObjectFk(relationship
, state
, false);
431 * Returns the <code>RelTaxonQualifierFk</code> attribute.
432 * @param relationship The {@link RelationshipBase Relationship}.
433 * @return The <code>RelTaxonQualifierFk</code> attribute.
436 @SuppressWarnings("unused")
437 private static Integer
getRelTaxonQualifierFk(RelationshipBase
<?
, ?
, ?
> relationship
) {
438 return PesiTransformer
.taxonRelation2RelTaxonQualifierFk(relationship
);
442 * Returns the <code>RelQualifierCache</code> attribute.
443 * @param relationship The {@link RelationshipBase Relationship}.
444 * @return The <code>RelQualifierCache</code> attribute.
447 @SuppressWarnings("unused")
448 private static String
getRelQualifierCache(RelationshipBase
<?
, ?
, ?
> relationship
) {
449 String result
= null;
450 if (nomenclaturalCode
!= null) {
451 if (nomenclaturalCode
.equals(NomenclaturalCode
.ICZN
)) {
452 result
= PesiTransformer
.zoologicalTaxonRelation2RelTaxonQualifierCache(relationship
);
454 result
= PesiTransformer
.taxonRelation2RelTaxonQualifierCache(relationship
);
457 logger
.error("NomenclaturalCode is NULL while creating the following relationship: " + relationship
.getUuid());
463 * Returns the <code>Notes</code> attribute.
464 * @param relationship The {@link RelationshipBase Relationship}.
465 * @return The <code>Notes</code> attribute.
468 @SuppressWarnings("unused")
469 private static String
getNotes(RelationshipBase
<?
, ?
, ?
> relationship
) {
475 * Returns the database key of an object in the given relationship.
476 * @param relationship {@link RelationshipBase RelationshipBase}.
477 * @param state {@link PesiExportState PesiExportState}.
478 * @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.
479 * @return The database key of an object in the given relationship.
481 private static Integer
getObjectFk(RelationshipBase
<?
, ?
, ?
> relationship
, PesiExportState state
, boolean isFrom
) {
482 TaxonBase
<?
> taxon
= null;
483 if (relationship
.isInstanceOf(TaxonRelationship
.class)) {
484 TaxonRelationship tr
= (TaxonRelationship
)relationship
;
485 taxon
= (isFrom
) ? tr
.getFromTaxon(): tr
.getToTaxon();
486 } else if (relationship
.isInstanceOf(SynonymRelationship
.class)) {
487 SynonymRelationship sr
= (SynonymRelationship
)relationship
;
488 taxon
= (isFrom
) ? sr
.getSynonym() : sr
.getAcceptedTaxon();
489 } else if (relationship
.isInstanceOf(NameRelationship
.class)) {
490 NameRelationship nr
= (NameRelationship
)relationship
;
491 TaxonNameBase taxonName
= (isFrom
) ? nr
.getFromName() : nr
.getToName();
492 return state
.getDbId(taxonName
);
495 return state
.getDbId(taxon
);
497 logger
.warn("No taxon found in state for relationship: " + relationship
.toString());
502 * Returns the <code>RankFk</code> attribute.
503 * @param taxonName The {@link TaxonNameBase TaxonName}.
504 * @param nomenclaturalCode The {@link NomenclaturalCode NomenclaturalCode}.
505 * @return The <code>RankFk</code> attribute.
508 private static Integer
getRankFk(TaxonNameBase taxonName
, NomenclaturalCode nomenclaturalCode
) {
509 Integer result
= null;
510 if (nomenclaturalCode
!= null) {
511 if (taxonName
!= null && taxonName
.getRank() == null) {
512 logger
.warn("Rank is null: " + taxonName
.getUuid() + " (" + taxonName
.getTitleCache() + ")");
514 result
= PesiTransformer
.rank2RankId(taxonName
.getRank(), PesiTransformer
.nomenClaturalCode2Kingdom(nomenclaturalCode
));
515 if (result
== null) {
516 logger
.warn("Rank could not be determined for PESI-Kingdom-Id " + PesiTransformer
.nomenClaturalCode2Kingdom(nomenclaturalCode
) + " and TaxonName " + taxonName
.getUuid() + " (" + taxonName
.getTitleCache() + ")");
523 * Returns the <code>RankCache</code> attribute.
524 * @param taxonName The {@link TaxonNameBase TaxonName}.
525 * @param nomenclaturalCode The {@link NomenclaturalCode NomenclaturalCode}.
526 * @return The <code>RankCache</code> attribute.
529 private static String
getRankCache(TaxonNameBase taxonName
, NomenclaturalCode nomenclaturalCode
) {
530 String result
= null;
531 if (nomenclaturalCode
!= null) {
532 result
= PesiTransformer
.rank2RankCache(taxonName
.getRank(), PesiTransformer
.nomenClaturalCode2Kingdom(nomenclaturalCode
));
538 * Returns the CDM to PESI specific export mappings.
539 * @return The {@link PesiExportMapping PesiExportMapping}.
541 private PesiExportMapping
getMapping() {
542 PesiExportMapping mapping
= new PesiExportMapping(dbTableName
);
544 mapping
.addMapper(MethodMapper
.NewInstance("TaxonFk1", this.getClass(), "getTaxonFk1", standardMethodParameter
, PesiExportState
.class));
545 mapping
.addMapper(MethodMapper
.NewInstance("TaxonFk2", this.getClass(), "getTaxonFk2", standardMethodParameter
, PesiExportState
.class));
546 mapping
.addMapper(MethodMapper
.NewInstance("RelTaxonQualifierFk", this));
547 mapping
.addMapper(MethodMapper
.NewInstance("RelQualifierCache", this));
548 mapping
.addMapper(MethodMapper
.NewInstance("Notes", this));