View Javadoc

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.HashMap;
16  import java.util.List;
17  import java.util.Map;
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.berlinModel.out.mapper.MethodMapper;
25  import eu.etaxonomy.cdm.io.common.DbExportStateBase;
26  import eu.etaxonomy.cdm.io.common.Source;
27  import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
28  import eu.etaxonomy.cdm.model.common.CdmBase;
29  import eu.etaxonomy.cdm.model.common.DescriptionElementSource;
30  import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
31  import eu.etaxonomy.cdm.model.description.Distribution;
32  import eu.etaxonomy.cdm.model.description.TaxonDescription;
33  import eu.etaxonomy.cdm.model.location.NamedArea;
34  import eu.etaxonomy.cdm.model.reference.ReferenceBase;
35  import eu.etaxonomy.cdm.model.taxon.Taxon;
36  import eu.etaxonomy.cdm.model.taxon.TaxonBase;
37  import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
38  
39  /**
40   * The export class for {@link eu.etaxonomy.cdm.model.description.Distribution Distributions}.<p>
41   * Inserts into DataWarehouse database table <code>Occurrence</code>.
42   * @author e.-m.lee
43   * @date 02.03.2010
44   *
45   */
46  @Component
47  @SuppressWarnings("unchecked")
48  public class PesiOccurrenceExport extends PesiExportBase {
49  	private static final Logger logger = Logger.getLogger(PesiOccurrenceExport.class);
50  	private static final Class<? extends CdmBase> standardMethodParameter = AnnotatableEntity.class;
51  
52  	private static int modCount = 1000;
53  	private static final String dbTableName = "Occurrence";
54  	private static final String pluralString = "Occurrences";
55  	private static final String parentPluralString = "Taxa";
56  	private static Taxon taxon = null;
57  	private static Map<Integer, Integer> sourceId2OccurenceIdMap = new HashMap<Integer, Integer>();
58  	private static NamedArea namedArea = null;
59  	private static Distribution distribution = null;
60  
61  	public PesiOccurrenceExport() {
62  		super();
63  	}
64  
65  	/* (non-Javadoc)
66  	 * @see eu.etaxonomy.cdm.io.common.DbExportBase#getStandardMethodParameter()
67  	 */
68  	@Override
69  	public Class<? extends CdmBase> getStandardMethodParameter() {
70  		return standardMethodParameter;
71  	}
72  
73  	/* (non-Javadoc)
74  	 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doCheck(eu.etaxonomy.cdm.io.common.IoStateBase)
75  	 */
76  	@Override
77  	protected boolean doCheck(PesiExportState state) {
78  		boolean result = true;
79  		return result;
80  	}
81  
82  	/* (non-Javadoc)
83  	 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#doInvoke(eu.etaxonomy.cdm.io.common.IoStateBase)
84  	 */
85  	@Override
86  	protected boolean doInvoke(PesiExportState state) {
87  		try {
88  			logger.error("*** Started Making " + pluralString + " ...");
89  	
90  			// Get the limit for objects to save within a single transaction.
91  			int limit = state.getConfig().getLimitSave();
92  
93  			// Stores whether this invoke was successful or not.
94  			boolean success = true;
95  
96  			// PESI: Clear the database table Occurrence.
97  //			doDelete(state);
98  	
99  			// Get specific mappings: (CDM) Occurrence -> (PESI) Occurrence
100 			PesiExportMapping mapping = getMapping();
101 
102 			// Initialize the db mapper
103 			mapping.initialize(state);
104 
105 			// PESI: Create the Occurrences
106 			int count = 0;
107 			int taxonCount = 0;
108 			int pastCount = 0;
109 			TransactionStatus txStatus = null;
110 			List<TaxonBase> list = null;
111 
112 			// Start transaction
113 			txStatus = startTransaction(true);
114 			logger.error("Started new transaction. Fetching some " + parentPluralString + " first (max: " + limit + ") ...");
115 			while ((list = getTaxonService().list(null, limit, taxonCount, null, null)).size() > 0) {
116 
117 				taxonCount += list.size();
118 				logger.error("Fetched " + list.size() + " " + parentPluralString + ".");
119 				for (TaxonBase taxonBase : list) {
120 					if (taxonBase.isInstanceOf(Taxon.class)) {
121 						
122 						// Set the current Taxon
123 						taxon = CdmBase.deproxy(taxonBase, Taxon.class);
124 
125 						// Determine the TaxonDescriptions
126 						Set<TaxonDescription> taxonDescriptions = taxon.getDescriptions();
127 
128 						// Determine the DescriptionElements (Citations) for the current Taxon
129 						for (TaxonDescription taxonDescription : taxonDescriptions) {
130 							Set<DescriptionElementBase> descriptionElements = taxonDescription.getElements();
131 							for (DescriptionElementBase descriptionElement : descriptionElements) {
132 								Set<DescriptionElementSource> elementSources = descriptionElement.getSources();
133 								
134 								if (descriptionElement.isInstanceOf(Distribution.class)) {
135 									Distribution distribution = CdmBase.deproxy(descriptionElement, Distribution.class);
136 									setNamedArea(distribution.getArea());
137 									setDistribution(distribution);
138 
139 									// Differentiate between descriptionElements with and without sources.
140 									if (elementSources.size() == 0 && state.getDbId(descriptionElement) != null) {
141 										if (neededValuesNotNull(descriptionElement, state)) {
142 											doCount(count++, modCount, pluralString);
143 											success &= mapping.invoke(descriptionElement);
144 										}
145 									} else {
146 										for (DescriptionElementSource elementSource : elementSources) {
147 											ReferenceBase reference = elementSource.getCitation();
148 	
149 											// Citations can be empty (null): Is it wrong data or just a normal case?
150 											if (reference != null && state.getDbId(reference) != null) {
151 												if (neededValuesNotNull(reference, state)) {
152 													doCount(count++, modCount, pluralString);
153 													success &= mapping.invoke(reference);
154 												}
155 											}
156 										}
157 										
158 									}
159 									
160 									setDistribution(null);
161 								}
162 								
163 							}
164 						}
165 					}
166 				}
167 				
168 				// Commit transaction
169 				commitTransaction(txStatus);
170 				logger.error("Committed transaction.");
171 				logger.error("Exported " + (count - pastCount) + " " + pluralString + ". Total: " + count);
172 				pastCount = count;
173 
174 				// Start transaction
175 				txStatus = startTransaction(true);
176 				logger.error("Started new transaction. Fetching some " + parentPluralString + " first (max: " + limit + ") ...");
177 			}
178 			if (list.size() == 0) {
179 				logger.error("No " + parentPluralString + " left to fetch.");
180 			}
181 			// Commit transaction
182 			commitTransaction(txStatus);
183 			logger.error("Committed transaction.");
184 
185 			logger.error("*** Finished Making " + pluralString + " ..." + getSuccessString(success));
186 			
187 			return success;
188 		} catch (SQLException e) {
189 			e.printStackTrace();
190 			logger.error(e.getMessage());
191 			return false;
192 		}
193 	}
194 
195 	/**
196 	 * Checks whether needed values for an entity are NULL.
197 	 * @return
198 	 */
199 	private boolean neededValuesNotNull(AnnotatableEntity entity, PesiExportState state) {
200 		boolean result = true;
201 		if (getTaxonFk(entity, state) == null) {
202 			logger.error("TaxonFk is NULL, but is not allowed to be. Therefore no record was written to export database for this entity: " + entity.getUuid());
203 			result = false;
204 		}
205 		if (getAreaFk(entity) == null) {
206 			logger.error("AreaFk is NULL, but is not allowed to be. Therefore no record was written to export database for this entity: " + entity.getUuid());
207 			result = false;
208 		}
209 		if (getOccurrenceStatusFk(entity) == null) {
210 			logger.error("OccurrenceStatusFk is NULL, but is not allowed to be. Therefore no record was written to export database for this entity: " + entity.getUuid());
211 			result = false;
212 		}
213 		return result;
214 	}
215 
216 	/**
217 	 * Creates the entries for the database table 'OccurrenceSource'.
218 	 * @param entity
219 	 * @param state
220 	 * @return
221 	 */
222 	protected boolean invokeOccurrenceSource(AnnotatableEntity entity, PesiExportState state) {
223 		if (entity == null) {
224 			return true;
225 		} else {
226 			// Create OccurrenceSource database table entry
227 			String lastStoredRecordSql = "Insert Into OccurrenceSource (OccurrenceFk, SourceFk, SourceNameCache, OldTaxonName) " +
228 					"values(?, ?, ?, ?)";
229 			Connection con = state.getConfig().getDestination().getConnection();
230 
231 			try {
232 				PreparedStatement stmt = con.prepareStatement(lastStoredRecordSql);
233 				Integer sourceFk = getSourceFk(entity, state);
234 				stmt.setInt(1, sourceId2OccurenceIdMap.get(sourceFk));
235 				stmt.setInt(2, sourceFk);
236 				stmt.setString(3, getSourceCache(entity));
237 				stmt.setString(4, null); // Which taxon are we talking about?
238 				stmt.executeUpdate();
239 				return true;
240 			} catch (SQLException e) {
241 				logger.error("SQLException during getOccurrenceId invoke...");
242 				e.printStackTrace();
243 				return false;
244 			}
245 		}
246 	}
247 
248 	/**
249 	 * Deletes all entries of database tables related to <code>Occurrence</code>.
250 	 * @param state The {@link PesiExportState PesiExportState}.
251 	 * @return Whether the delete operation was successful or not.
252 	 */
253 	protected boolean doDelete(PesiExportState state) {
254 		PesiExportConfigurator pesiConfig = (PesiExportConfigurator) state.getConfig();
255 		
256 		String sql;
257 		Source destination =  pesiConfig.getDestination();
258 
259 		// Clear Occurrence
260 		sql = "DELETE FROM " + dbTableName;
261 		destination.setQuery(sql);
262 		destination.update(sql);
263 		return true;
264 	}
265 
266 	/* (non-Javadoc)
267 	 * @see eu.etaxonomy.cdm.io.common.CdmIoBase#isIgnore(eu.etaxonomy.cdm.io.common.IoStateBase)
268 	 */
269 	@Override
270 	protected boolean isIgnore(PesiExportState state) {
271 		return ! state.getConfig().isDoOccurrence();
272 	}
273 
274 	/**
275 	 * Returns the <code>TaxonFk</code> attribute.
276 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
277 	 * @param state The {@link PesiExportState PesiExportState}.
278 	 * @return The <code>TaxonFk</code> attribute.
279 	 * @see MethodMapper
280 	 */
281 	private static Integer getTaxonFk(AnnotatableEntity entity, PesiExportState state) {
282 		// AnnotatableEntity parameter isn't needed, but the DbSingleAttributeExportMapperBase throws a type mismatch exception otherwise
283 		// since it awaits two parameters if one of them is of instance DbExportStateBase.
284 		Integer result = null;
285 		if (state != null && taxon != null) {
286 			result = state.getDbId(taxon.getName());
287 		}
288 		return result;
289 	}
290 
291 	/**
292 	 * Returns the <code>TaxonFullNameCache</code> attribute.
293 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
294 	 * @return The <code>TaxonFullNameCache</code> attribute.
295 	 * @see MethodMapper
296 	 */
297 	@SuppressWarnings("unused")
298 	private static String getTaxonFullNameCache(AnnotatableEntity entity) {
299 		String result = null;
300 		result = taxon.getName().getTitleCache();
301 		return result;
302 	}
303 
304 	/**
305 	 * Returns the <code>AreaFk</code> attribute.
306 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
307 	 * @return The <code>AreaFk</code> attribute.
308 	 * @see MethodMapper
309 	 */
310 	private static Integer getAreaFk(AnnotatableEntity entity) {
311 		Integer result = null;
312 		if (getNamedArea() != null) {
313 			result = PesiTransformer.area2AreaId(namedArea);
314 		} else {
315 			logger.warn("This should never happen, but a NamedArea could not be found for entity: " + entity.getUuid());
316 		}
317 		return result;
318 	}
319 
320 	/**
321 	 * Returns the <code>AreaNameCache</code> attribute.
322 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
323 	 * @return The <code>AreaNameCache</code> attribute.
324 	 * @see MethodMapper
325 	 */
326 	@SuppressWarnings("unused")
327 	private static String getAreaNameCache(AnnotatableEntity entity) {
328 		String result = null;
329 		if (getNamedArea() != null) {
330 			result = PesiTransformer.area2AreaCache(namedArea);
331 		} else {
332 			logger.warn("This should never happen, but a NamedArea could not be found for entity: " + entity.getUuid());
333 		}
334 		return result;
335 	}
336 
337 	/**
338 	 * @return the distribution
339 	 */
340 	public static Distribution getDistribution() {
341 		return distribution;
342 	}
343 
344 	/**
345 	 * @param distribution the distribution to set
346 	 */
347 	public static void setDistribution(Distribution distribution) {
348 		PesiOccurrenceExport.distribution = distribution;
349 	}
350 
351 	/**
352 	 * @return the namedArea
353 	 */
354 	public static NamedArea getNamedArea() {
355 		return namedArea;
356 	}
357 
358 	/**
359 	 * @param namedArea the namedArea to set
360 	 */
361 	public static void setNamedArea(NamedArea namedArea) {
362 		PesiOccurrenceExport.namedArea = namedArea;
363 	}
364 
365 	/**
366 	 * Returns the <code>OccurrenceStatusFk</code> attribute.
367 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
368 	 * @return The <code>OccurrenceStatusFk</code> attribute.
369 	 * @throws UnknownCdmTypeException 
370 	 * @see MethodMapper
371 	 */
372 	private static Integer getOccurrenceStatusFk(AnnotatableEntity entity) {
373 		Integer result = null;
374 		if (getDistribution() != null) {
375 			result = PesiTransformer.presenceAbsenceTerm2OccurrenceStatusId(getDistribution().getStatus());
376 		}
377 		return result;
378 	}
379 
380 	/**
381 	 * Returns the <code>OccurrenceStatusCache</code> attribute.
382 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
383 	 * @return The <code>OccurrenceStatusCache</code> attribute.
384 	 * @throws UnknownCdmTypeException 
385 	 * @see MethodMapper
386 	 */
387 	@SuppressWarnings("unused")
388 	private static String getOccurrenceStatusCache(AnnotatableEntity entity) {
389 		String result = null;
390 		if (getDistribution() != null) {
391 			result = PesiTransformer.presenceAbsenceTerm2OccurrenceStatusCache(getDistribution().getStatus());
392 		}
393 		return result;
394 	}
395 
396 	/**
397 	 * Returns the <code>SourceFk</code> attribute.
398 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
399 	 * @param state The {@link PesiExportState PesiExportState}.
400 	 * @return The <code>SourceFk</code> attribute.
401 	 * @see MethodMapper
402 	 */
403 	private static Integer getSourceFk(AnnotatableEntity entity, PesiExportState state) {
404 		Integer result = null;
405 		if (state != null && entity != null && entity.isInstanceOf(Distribution.class)) {
406 			Distribution distribution = CdmBase.deproxy(entity, Distribution.class);
407 			Set<DescriptionElementSource> sources = distribution.getSources();
408 			if (sources.size() == 1) {
409 				DescriptionElementSource source = sources.iterator().next();
410 				result = state.getDbId(source.getCitation());
411 			} else if (sources.size() > 1) {
412 				logger.warn("Found Distribution with " + sources.size() + " sources.");
413 			}
414 		}
415 		return result;
416 	}
417 
418 	/**
419 	 * Returns the <code>SourceCache</code> attribute.
420 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
421 	 * @return The <code>SourceCache</code> attribute.
422 	 * @see MethodMapper
423 	 */
424 	private static String getSourceCache(AnnotatableEntity entity) {
425 		String result = null;
426 		ReferenceBase reference;
427 		if (entity != null && entity.isInstanceOf(Distribution.class)) {
428 			Distribution distribution = CdmBase.deproxy(entity, Distribution.class);
429 			Set<DescriptionElementSource> sources = distribution.getSources();
430 			if (sources.size() == 1) {
431 				DescriptionElementSource source = sources.iterator().next();
432 				reference = source.getCitation();
433 				if (reference != null) {
434 					result = reference.getTitle();
435 				}
436 			} else if (sources.size() > 1) {
437 				logger.warn("Found Distribution with " + sources.size() + " sources.");
438 			}
439 		}
440 		return result;
441 	}
442 
443 	/**
444 	 * Returns the <code>Notes</code> attribute.
445 	 * @param entity An {@link AnnotatableEntity AnnotatableEntity}.
446 	 * @return The <code>Notes</code> attribute.
447 	 * @see MethodMapper
448 	 */
449 	@SuppressWarnings("unused")
450 	private static String getNotes(AnnotatableEntity entity) {
451 		// TODO
452 		return null;
453 	}
454 
455 	/**
456 	 * Returns the CDM to PESI specific export mappings.
457 	 * @return The {@link PesiExportMapping PesiExportMapping}.
458 	 */
459 	private PesiExportMapping getMapping() {
460 		PesiExportMapping mapping = new PesiExportMapping(dbTableName);
461 		
462 		mapping.addMapper(MethodMapper.NewInstance("TaxonFk", this.getClass(), "getTaxonFk", standardMethodParameter, PesiExportState.class));
463 		mapping.addMapper(MethodMapper.NewInstance("AreaFk", this));
464 		mapping.addMapper(MethodMapper.NewInstance("TaxonFullNameCache", this));
465 		mapping.addMapper(MethodMapper.NewInstance("AreaNameCache", this));
466 		mapping.addMapper(MethodMapper.NewInstance("OccurrenceStatusFk", this));
467 		mapping.addMapper(MethodMapper.NewInstance("OccurrenceStatusCache", this));
468 		mapping.addMapper(MethodMapper.NewInstance("SourceFk", this.getClass(), "getSourceFk", standardMethodParameter, PesiExportState.class));
469 		mapping.addMapper(MethodMapper.NewInstance("SourceCache", this));
470 		mapping.addMapper(MethodMapper.NewInstance("Notes", this));
471 
472 		return mapping;
473 	}
474 
475 }