include infrageneric/-specific taxon and combIned
[cdmlib-apps.git] / cdm-pesi / src / main / java / eu / etaxonomy / cdm / io / pesi / out / PesiRelTaxonExport.java
1 // $Id$
2 /**
3 * Copyright (C) 2009 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
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.
9 */
10 package eu.etaxonomy.cdm.io.pesi.out;
11
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;
18 import java.util.Set;
19
20 import org.apache.log4j.Logger;
21 import org.springframework.stereotype.Component;
22 import org.springframework.transaction.TransactionStatus;
23
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;
39
40 /**
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>.
43 * @author e.-m.lee
44 * @date 23.02.2010
45 *
46 */
47 @Component
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;
51
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;
61
62 public PesiRelTaxonExport() {
63 super();
64 }
65
66 /* (non-Javadoc)
67 * @see eu.etaxonomy.cdm.io.common.DbExportBase#getStandardMethodParameter()
68 */
69 @Override
70 public Class<? extends CdmBase> getStandardMethodParameter() {
71 return standardMethodParameter;
72 }
73
74 /* (non-Javadoc)
75 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doCheck(eu.etaxonomy.cdm.io.common.IoStateBase)
76 */
77 @Override
78 protected boolean doCheck(PesiExportState state) {
79 boolean result = true;
80 return result;
81 }
82
83 /* (non-Javadoc)
84 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doInvoke(eu.etaxonomy.cdm.io.common.IoStateBase)
85 */
86 @Override
87 protected void doInvoke(PesiExportState state) {
88 try {
89 logger.error("*** Started Making " + pluralString + " ...");
90
91 Connection connection = state.getConfig().getDestination().getConnection();
92 String synonymsSql = "UPDATE Taxon SET KingdomFk = ?, RankFk = ?, RankCache = ? WHERE TaxonId = ?";
93 synonymsStmt = connection.prepareStatement(synonymsSql);
94
95 // Stores whether this invoke was successful or not.
96 boolean success = true;
97
98 // PESI: Clear the database table RelTaxon.
99 doDelete(state);
100
101 // Get specific mappings: (CDM) Relationship -> (PESI) RelTaxon
102 mapping = getMapping();
103
104 // Initialize the db mapper
105 mapping.initialize(state);
106
107 TransactionStatus txStatus = null;
108 List<Classification> classificationList = null;
109
110 // Specify starting ranks for tree traversing
111 rankList.add(Rank.KINGDOM());
112 rankList.add(Rank.GENUS());
113
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
117
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.");
124
125 logger.info("Fetched " + classificationList.size() + " classification(s).");
126
127 for (Classification classification : classificationList) {
128 for (Rank rank : rankList) {
129
130 txStatus = startTransaction(true);
131 logger.info("Started transaction to fetch all rootNodes specific to Rank " + rank.getLabel() + " ...");
132
133 List<TaxonNode> rankSpecificRootNodes = getClassificationService().loadRankSpecificRootNodes(classification, rank, null);
134 logger.info("Fetched " + rankSpecificRootNodes.size() + " RootNodes for Rank " + rank.getLabel());
135
136 commitTransaction(txStatus);
137 logger.debug("Committed transaction.");
138
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() + " ...");
144 } else {
145 logger.info("Started transaction to traverse childNodes of rootNode (" + rootNode.getUuid() + ") till leaves are reached ...");
146 }
147
148 TaxonNode newNode = getTaxonNodeService().load(rootNode.getUuid());
149
150 TaxonNode parentNode = newNode.getParent();
151
152 success &=traverseTree(newNode, parentNode, rankMap.get(rank), state);
153
154 commitTransaction(txStatus);
155 logger.debug("Committed transaction.");
156
157 }
158 }
159 }
160
161 logger.error("*** Finished Making " + pluralString + " ..." + getSuccessString(success));
162
163 if (!success){
164 state.setUnsuccessfull();
165 }
166 return;
167 } catch (SQLException e) {
168 e.printStackTrace();
169 logger.error(e.getMessage());
170 state.setUnsuccessfull();
171 return;
172 }
173 }
174
175 /**
176 * Traverses the classification recursively and stores determined values for every Taxon.
177 * @param childNode
178 * @param parentNode
179 * @param treeIndex
180 * @param fetchLevel
181 * @param state
182 */
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)) {
193
194 success &= saveData(childNode, parentNode, state);
195
196 for (TaxonNode newNode : childNode.getChildNodes()) {
197 success &= traverseTree(newNode, childNode, fetchLevel, state);
198 }
199
200 } else {
201 // logger.error("Target Rank " + fetchLevel.getLabel() + " reached");
202 return success;
203 }
204 } else {
205 logger.error("Rank is NULL. FetchLevel can not be checked: " + childTaxon.getUuid() + " (" + childTaxon.getTitleCache() + ")");
206 }
207 } else {
208 logger.error("TaxonName is NULL for taxon: " + childTaxon.getUuid());
209 }
210 }else{
211 logger.debug("Taxon is not a PESI taxon: " + childTaxon.getUuid());
212 }
213
214 } else {
215 logger.error("Taxon is NULL for TaxonNode: " + childNode.getUuid());
216 }
217 return success;
218 }
219
220 /**
221 * Stores values in database for every recursive round.
222 * @param childNode
223 * @param parentNode
224 * @param treeIndex
225 * @param state
226 * @param currentTaxonFk
227 */
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);
234
235 if (childNodeTaxonName != null) {
236
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) {
243 try {
244 if (neededValuesNotNull(taxonRelationship, state)) {
245 doCount(count++, modCount, pluralString);
246 success &= mapping.invoke(taxonRelationship);
247 }
248 } catch (SQLException e) {
249 logger.error("TaxonRelationship could not be created for this TaxonRelation (" + taxonRelationship.getUuid() + "): " + e.getMessage());
250 }
251 }
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() + ")");
254 }
255
256 // TaxonNameRelationships
257 Set<NameRelationship> nameRelations = childNodeTaxonName.getRelationsFromThisName();
258 for (NameRelationship nameRelation : nameRelations) {
259 try {
260 if (neededValuesNotNull(nameRelation, state)) {
261 doCount(count++, modCount, pluralString);
262 success &= mapping.invoke(nameRelation);
263 }
264 } catch (SQLException e) {
265 logger.error("NameRelationship could not be created: " + e.getMessage());
266 }
267 }
268
269 }
270
271 // SynonymRelationships
272 Set<Synonym> synonyms = childNodeTaxon.getSynonyms(); // synonyms of accepted taxon
273 for (Synonym synonym : synonyms) {
274 TaxonNameBase<?,?> synonymTaxonName = synonym.getName();
275
276 // Store synonym data in Taxon table
277 invokeSynonyms(state, synonymTaxonName);
278
279 Set<SynonymRelationship> synonymRelations = synonym.getSynonymRelations();
280 for (SynonymRelationship synonymRelationship : synonymRelations) {
281 try {
282 if (neededValuesNotNull(synonymRelationship, state)) {
283 doCount(count++, modCount, pluralString);
284 success &= mapping.invoke(synonymRelationship);
285
286 }
287 } catch (SQLException e) {
288 logger.error("SynonymRelationship could not be created for this SynonymRelation (" + synonymRelationship.getUuid() + "): " + e.getMessage());
289 }
290 }
291
292 // SynonymNameRelationship
293 Set<NameRelationship> nameRelations = synonymTaxonName.getRelationsFromThisName();
294 for (NameRelationship nameRelation : nameRelations) {
295 try {
296 if (neededValuesNotNull(nameRelation, state)) {
297 doCount(count++, modCount, pluralString);
298 success &= mapping.invoke(nameRelation);
299 }
300 } catch (SQLException e) {
301 logger.error("NameRelationship could not be created for this NameRelation (" + nameRelation.getUuid() + "): " + e.getMessage());
302 }
303 }
304
305 }
306
307 }
308 return success;
309
310 }
311
312 /**
313 * Determines synonym related data and saves them.
314 * @param state
315 * @param sr
316 */
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);
321
322 saveSynonymData(synonymTaxonName, nomenclaturalCode, kingdomFk, synonymFk);
323 }
324
325 /**
326 * Stores synonym data.
327 * @param taxonName
328 * @param nomenclaturalCode
329 * @param kingdomFk
330 * @param synonymParentTaxonFk
331 * @param currentTaxonFk
332 */
333 private static boolean saveSynonymData(TaxonNameBase taxonName,
334 NomenclaturalCode nomenclaturalCode, Integer kingdomFk,
335 Integer currentSynonymFk) {
336 try {
337 if (kingdomFk != null) {
338 synonymsStmt.setInt(1, kingdomFk);
339 } else {
340 synonymsStmt.setObject(1, null);
341 }
342
343 Integer rankFk = getRankFk(taxonName, nomenclaturalCode);
344 if (rankFk != null) {
345 synonymsStmt.setInt(2, rankFk);
346 } else {
347 synonymsStmt.setObject(2, null);
348 }
349 synonymsStmt.setString(3, getRankCache(taxonName, nomenclaturalCode));
350
351 if (currentSynonymFk != null) {
352 synonymsStmt.setInt(4, currentSynonymFk);
353 } else {
354 synonymsStmt.setObject(4, null);
355 }
356 synonymsStmt.executeUpdate();
357 return true;
358 } catch (SQLException e) {
359 logger.error("SQLException during invoke for taxonName - " + taxonName.getUuid() + " (" + taxonName.getTitleCache() + "): " + e.getMessage());
360 e.printStackTrace();
361 return false;
362 }
363 }
364
365 /**
366 * Checks whether needed values for an entity are NULL.
367 * @return
368 */
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());
373 result = false;
374 }
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());
377 result = false;
378 }
379 return result;
380 }
381
382 /**
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.
386 */
387 protected boolean doDelete(PesiExportState state) {
388 PesiExportConfigurator pesiConfig = (PesiExportConfigurator) state.getConfig();
389
390 String sql;
391 Source destination = pesiConfig.getDestination();
392
393 // Clear RelTaxon
394 sql = "DELETE FROM " + dbTableName;
395 destination.setQuery(sql);
396 destination.update(sql);
397 return true;
398 }
399
400 /* (non-Javadoc)
401 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#isIgnore(eu.etaxonomy.cdm.io.common.IoStateBase)
402 */
403 @Override
404 protected boolean isIgnore(PesiExportState state) {
405 return ! state.getConfig().isDoRelTaxa();
406 }
407
408 /**
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.
413 * @see MethodMapper
414 */
415 private static Integer getTaxonFk1(RelationshipBase<?, ?, ?> relationship, PesiExportState state) {
416 return getObjectFk(relationship, state, true);
417 }
418
419 /**
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.
424 * @see MethodMapper
425 */
426 private static Integer getTaxonFk2(RelationshipBase<?, ?, ?> relationship, PesiExportState state) {
427 return getObjectFk(relationship, state, false);
428 }
429
430 /**
431 * Returns the <code>RelTaxonQualifierFk</code> attribute.
432 * @param relationship The {@link RelationshipBase Relationship}.
433 * @return The <code>RelTaxonQualifierFk</code> attribute.
434 * @see MethodMapper
435 */
436 @SuppressWarnings("unused")
437 private static Integer getRelTaxonQualifierFk(RelationshipBase<?, ?, ?> relationship) {
438 return PesiTransformer.taxonRelation2RelTaxonQualifierFk(relationship);
439 }
440
441 /**
442 * Returns the <code>RelQualifierCache</code> attribute.
443 * @param relationship The {@link RelationshipBase Relationship}.
444 * @return The <code>RelQualifierCache</code> attribute.
445 * @see MethodMapper
446 */
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);
453 } else {
454 result = PesiTransformer.taxonRelation2RelTaxonQualifierCache(relationship);
455 }
456 } else {
457 logger.error("NomenclaturalCode is NULL while creating the following relationship: " + relationship.getUuid());
458 }
459 return result;
460 }
461
462 /**
463 * Returns the <code>Notes</code> attribute.
464 * @param relationship The {@link RelationshipBase Relationship}.
465 * @return The <code>Notes</code> attribute.
466 * @see MethodMapper
467 */
468 @SuppressWarnings("unused")
469 private static String getNotes(RelationshipBase<?, ?, ?> relationship) {
470 // TODO
471 return null;
472 }
473
474 /**
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.
480 */
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);
493 }
494 if (taxon != null) {
495 return state.getDbId(taxon);
496 }
497 logger.warn("No taxon found in state for relationship: " + relationship.toString());
498 return null;
499 }
500
501 /**
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.
506 * @see MethodMapper
507 */
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() + ")");
513 }
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() + ")");
517 }
518 }
519 return result;
520 }
521
522 /**
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.
527 * @see MethodMapper
528 */
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));
533 }
534 return result;
535 }
536
537 /**
538 * Returns the CDM to PESI specific export mappings.
539 * @return The {@link PesiExportMapping PesiExportMapping}.
540 */
541 private PesiExportMapping getMapping() {
542 PesiExportMapping mapping = new PesiExportMapping(dbTableName);
543
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));
549
550 return mapping;
551 }
552
553 }