ICdmCacher, ICdmUuidCacher, CdmCacher, CdmTermCacher : refactored and added load...
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / TaxonServiceImpl.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 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
11 package eu.etaxonomy.cdm.api.service;
12
13 import java.io.IOException;
14 import java.util.ArrayList;
15 import java.util.EnumSet;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.UUID;
23
24 import javax.persistence.EntityNotFoundException;
25
26 import org.apache.log4j.Logger;
27 import org.apache.lucene.index.CorruptIndexException;
28 import org.apache.lucene.queryParser.ParseException;
29 import org.apache.lucene.search.BooleanClause.Occur;
30 import org.apache.lucene.search.BooleanFilter;
31 import org.apache.lucene.search.BooleanQuery;
32 import org.apache.lucene.search.DocIdSet;
33 import org.apache.lucene.search.Query;
34 import org.apache.lucene.search.QueryWrapperFilter;
35 import org.apache.lucene.search.SortField;
36 import org.springframework.beans.factory.annotation.Autowired;
37 import org.springframework.stereotype.Service;
38 import org.springframework.transaction.annotation.Transactional;
39
40 import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
41 import eu.etaxonomy.cdm.api.service.config.IFindTaxaAndNamesConfigurator;
42 import eu.etaxonomy.cdm.api.service.config.IncludedTaxonConfiguration;
43 import eu.etaxonomy.cdm.api.service.config.MatchingTaxonConfigurator;
44 import eu.etaxonomy.cdm.api.service.config.SynonymDeletionConfigurator;
45 import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
46 import eu.etaxonomy.cdm.api.service.config.TaxonNodeDeletionConfigurator.ChildHandling;
47 import eu.etaxonomy.cdm.api.service.dto.FindByIdentifierDTO;
48 import eu.etaxonomy.cdm.api.service.dto.IncludedTaxaDTO;
49 import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
50 import eu.etaxonomy.cdm.api.service.exception.HomotypicalGroupChangeException;
51 import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
52 import eu.etaxonomy.cdm.api.service.pager.Pager;
53 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
54 import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
55 import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
56 import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearch;
57 import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearchException;
58 import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
59 import eu.etaxonomy.cdm.api.service.search.LuceneSearch.TopGroupsWithMaxScore;
60 import eu.etaxonomy.cdm.api.service.search.QueryFactory;
61 import eu.etaxonomy.cdm.api.service.search.SearchResult;
62 import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
63 import eu.etaxonomy.cdm.api.service.util.TaxonRelationshipEdge;
64 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
65 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
66 import eu.etaxonomy.cdm.hibernate.search.DefinedTermBaseClassBridge;
67 import eu.etaxonomy.cdm.hibernate.search.GroupByTaxonClassBridge;
68 import eu.etaxonomy.cdm.hibernate.search.MultilanguageTextFieldBridge;
69 import eu.etaxonomy.cdm.model.CdmBaseType;
70 import eu.etaxonomy.cdm.model.common.CdmBase;
71 import eu.etaxonomy.cdm.model.common.DefinedTerm;
72 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
73 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
74 import eu.etaxonomy.cdm.model.common.Language;
75 import eu.etaxonomy.cdm.model.common.OrderedTermVocabulary;
76 import eu.etaxonomy.cdm.model.common.OriginalSourceType;
77 import eu.etaxonomy.cdm.model.common.RelationshipBase;
78 import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
79 import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
80 import eu.etaxonomy.cdm.model.description.CommonTaxonName;
81 import eu.etaxonomy.cdm.model.description.DescriptionBase;
82 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
83 import eu.etaxonomy.cdm.model.description.Distribution;
84 import eu.etaxonomy.cdm.model.description.Feature;
85 import eu.etaxonomy.cdm.model.description.IIdentificationKey;
86 import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
87 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
88 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
89 import eu.etaxonomy.cdm.model.description.TaxonDescription;
90 import eu.etaxonomy.cdm.model.description.TaxonInteraction;
91 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
92 import eu.etaxonomy.cdm.model.location.NamedArea;
93 import eu.etaxonomy.cdm.model.media.Media;
94 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
95 import eu.etaxonomy.cdm.model.media.MediaUtils;
96 import eu.etaxonomy.cdm.model.molecular.AmplificationResult;
97 import eu.etaxonomy.cdm.model.molecular.DnaSample;
98 import eu.etaxonomy.cdm.model.molecular.Sequence;
99 import eu.etaxonomy.cdm.model.molecular.SingleRead;
100 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
101 import eu.etaxonomy.cdm.model.name.NameRelationship;
102 import eu.etaxonomy.cdm.model.name.Rank;
103 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
104 import eu.etaxonomy.cdm.model.name.ZoologicalName;
105 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
106 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
107 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
108 import eu.etaxonomy.cdm.model.reference.Reference;
109 import eu.etaxonomy.cdm.model.taxon.Classification;
110 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
111 import eu.etaxonomy.cdm.model.taxon.Synonym;
112 import eu.etaxonomy.cdm.model.taxon.SynonymRelationship;
113 import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;
114 import eu.etaxonomy.cdm.model.taxon.Taxon;
115 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
116 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
117 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
118 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
119 import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
120 import eu.etaxonomy.cdm.persistence.dao.common.IOrderedTermVocabularyDao;
121 import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
122 import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
123 import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
124 import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
125 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
126 import eu.etaxonomy.cdm.persistence.fetch.CdmFetch;
127 import eu.etaxonomy.cdm.persistence.query.MatchMode;
128 import eu.etaxonomy.cdm.persistence.query.OrderHint;
129 import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
130 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
131
132
133 /**
134 * @author a.kohlbecker
135 * @date 10.09.2010
136 *
137 */
138 @Service
139 @Transactional(readOnly = true)
140 public class TaxonServiceImpl extends IdentifiableServiceBase<TaxonBase,ITaxonDao> implements ITaxonService{
141 private static final Logger logger = Logger.getLogger(TaxonServiceImpl.class);
142
143 public static final String POTENTIAL_COMBINATION_NAMESPACE = "Potential combination";
144
145 public static final String INFERRED_EPITHET_NAMESPACE = "Inferred epithet";
146
147 public static final String INFERRED_GENUS_NAMESPACE = "Inferred genus";
148
149
150 @Autowired
151 private ITaxonNameDao nameDao;
152
153 @Autowired
154 private INameService nameService;
155
156 @Autowired
157 private ITaxonNodeService nodeService;
158
159 @Autowired
160 private ICdmGenericDao genericDao;
161
162 @Autowired
163 private IDescriptionService descriptionService;
164
165 @Autowired
166 private IOrderedTermVocabularyDao orderedVocabularyDao;
167
168 @Autowired
169 private IOccurrenceDao occurrenceDao;
170
171 @Autowired
172 private IClassificationDao classificationDao;
173
174 @Autowired
175 private AbstractBeanInitializer beanInitializer;
176
177 @Autowired
178 private ILuceneIndexToolProvider luceneIndexToolProvider;
179
180 /**
181 * Constructor
182 */
183 public TaxonServiceImpl(){
184 if (logger.isDebugEnabled()) { logger.debug("Load TaxonService Bean"); }
185 }
186
187 /**
188 * FIXME Candidate for harmonization
189 * rename searchByName ?
190 */
191 @Override
192 public List<TaxonBase> searchTaxaByName(String name, Reference sec) {
193 return dao.getTaxaByName(name, sec);
194 }
195
196 /**
197 * FIXME Candidate for harmonization
198 * merge with getRootTaxa(Reference sec, ..., ...)
199 * (non-Javadoc)
200 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getRootTaxa(eu.etaxonomy.cdm.model.reference.Reference, boolean)
201 */
202 @Override
203 public List<Taxon> getRootTaxa(Reference sec, CdmFetch cdmFetch, boolean onlyWithChildren) {
204 if (cdmFetch == null){
205 cdmFetch = CdmFetch.NO_FETCH();
206 }
207 return dao.getRootTaxa(sec, cdmFetch, onlyWithChildren, false);
208 }
209
210 @Override
211 public List<Taxon> getRootTaxa(Rank rank, Reference sec, boolean onlyWithChildren,boolean withMisapplications, List<String> propertyPaths) {
212 return dao.getRootTaxa(rank, sec, null, onlyWithChildren, withMisapplications, propertyPaths);
213 }
214
215 @Override
216 public List<RelationshipBase> getAllRelationships(int limit, int start){
217 return dao.getAllRelationships(limit, start);
218 }
219
220 /**
221 * FIXME Candidate for harmonization
222 * is this the same as termService.getVocabulary(VocabularyEnum.TaxonRelationshipType) ?
223 */
224 @Override
225 @Deprecated
226 public OrderedTermVocabulary<TaxonRelationshipType> getTaxonRelationshipTypeVocabulary() {
227
228 String taxonRelTypeVocabularyId = "15db0cf7-7afc-4a86-a7d4-221c73b0c9ac";
229 UUID uuid = UUID.fromString(taxonRelTypeVocabularyId);
230 OrderedTermVocabulary<TaxonRelationshipType> taxonRelTypeVocabulary =
231 (OrderedTermVocabulary)orderedVocabularyDao.findByUuid(uuid);
232 return taxonRelTypeVocabulary;
233 }
234
235
236
237 /*
238 * (non-Javadoc)
239 * @see eu.etaxonomy.cdm.api.service.ITaxonService#swapSynonymWithAcceptedTaxon(eu.etaxonomy.cdm.model.taxon.Synonym)
240 */
241 @Override
242 @Transactional(readOnly = false)
243 public void swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon){
244
245 TaxonNameBase<?,?> synonymName = synonym.getName();
246 synonymName.removeTaxonBase(synonym);
247 TaxonNameBase<?,?> taxonName = acceptedTaxon.getName();
248 taxonName.removeTaxonBase(acceptedTaxon);
249
250 synonym.setName(taxonName);
251 acceptedTaxon.setName(synonymName);
252
253 // the accepted taxon needs a new uuid because the concept has changed
254 // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
255 //acceptedTaxon.setUuid(UUID.randomUUID());
256 }
257
258
259 /* (non-Javadoc)
260 * @see eu.etaxonomy.cdm.api.service.ITaxonService#changeSynonymToAcceptedTaxon(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
261 */
262
263 @Override
264 @Transactional(readOnly = false)
265 public Taxon changeSynonymToAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean deleteSynonym, boolean copyCitationInfo, Reference citation, String microCitation) throws HomotypicalGroupChangeException{
266
267 TaxonNameBase<?,?> acceptedName = acceptedTaxon.getName();
268 TaxonNameBase<?,?> synonymName = synonym.getName();
269 HomotypicalGroup synonymHomotypicGroup = synonymName.getHomotypicalGroup();
270
271 //check synonym is not homotypic
272 if (acceptedName.getHomotypicalGroup().equals(synonymHomotypicGroup)){
273 String message = "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
274 throw new HomotypicalGroupChangeException(message);
275 }
276
277 Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, acceptedTaxon.getSec());
278
279 SynonymRelationshipType relTypeForGroup = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
280 List<Synonym> heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup);
281 Set<NameRelationship> basionymsAndReplacedSynonyms = synonymHomotypicGroup.getBasionymAndReplacedSynonymRelations();
282
283 for (Synonym heteroSynonym : heteroSynonyms){
284 if (synonym.equals(heteroSynonym)){
285 acceptedTaxon.removeSynonym(heteroSynonym, false);
286
287 }else{
288 //move synonyms in same homotypic group to new accepted taxon
289 heteroSynonym.replaceAcceptedTaxon(newAcceptedTaxon, relTypeForGroup, copyCitationInfo, citation, microCitation);
290 }
291 }
292
293 //synonym.getName().removeTaxonBase(synonym);
294
295 if (deleteSynonym){
296 // deleteSynonym(synonym, taxon, false);
297 try {
298 this.dao.flush();
299 SynonymDeletionConfigurator config = new SynonymDeletionConfigurator();
300 config.setDeleteNameIfPossible(false);
301 this.deleteSynonym(synonym, acceptedTaxon, config);
302
303 } catch (Exception e) {
304 logger.info("Can't delete old synonym from database");
305 }
306 }
307
308 return newAcceptedTaxon;
309 }
310
311
312 @Override
313 public Taxon changeSynonymToRelatedTaxon(Synonym synonym, Taxon toTaxon, TaxonRelationshipType taxonRelationshipType, Reference citation, String microcitation){
314
315 // Get name from synonym
316 TaxonNameBase<?, ?> synonymName = synonym.getName();
317
318 /* // remove synonym from taxon
319 toTaxon.removeSynonym(synonym);
320 */
321 // Create a taxon with synonym name
322 Taxon fromTaxon = Taxon.NewInstance(synonymName, null);
323
324 // Add taxon relation
325 fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
326
327 // since we are swapping names, we have to detach the name from the synonym completely.
328 // Otherwise the synonym will still be in the list of typified names.
329 // synonym.getName().removeTaxonBase(synonym);
330 this.deleteSynonym(synonym, null);
331
332 return fromTaxon;
333 }
334
335
336 /* (non-Javadoc)
337 * @see eu.etaxonomy.cdm.api.service.ITaxonService#changeHomotypicalGroupOfSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.name.HomotypicalGroup, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
338 */
339 @Transactional(readOnly = false)
340 @Override
341 public void changeHomotypicalGroupOfSynonym(Synonym synonym, HomotypicalGroup newHomotypicalGroup, Taxon targetTaxon,
342 boolean removeFromOtherTaxa, boolean setBasionymRelationIfApplicable){
343 // Get synonym name
344 TaxonNameBase synonymName = synonym.getName();
345 HomotypicalGroup oldHomotypicalGroup = synonymName.getHomotypicalGroup();
346
347
348 // Switch groups
349 oldHomotypicalGroup.removeTypifiedName(synonymName);
350 newHomotypicalGroup.addTypifiedName(synonymName);
351
352 //remove existing basionym relationships
353 synonymName.removeBasionyms();
354
355 //add basionym relationship
356 if (setBasionymRelationIfApplicable){
357 Set<TaxonNameBase> basionyms = newHomotypicalGroup.getBasionyms();
358 for (TaxonNameBase basionym : basionyms){
359 synonymName.addBasionym(basionym);
360 }
361 }
362
363 //set synonym relationship correctly
364 // SynonymRelationship relToTaxon = null;
365 boolean relToTargetTaxonExists = false;
366 Set<SynonymRelationship> existingRelations = synonym.getSynonymRelations();
367 for (SynonymRelationship rel : existingRelations){
368 Taxon acceptedTaxon = rel.getAcceptedTaxon();
369 boolean isTargetTaxon = acceptedTaxon != null && acceptedTaxon.equals(targetTaxon);
370 HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
371 boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
372 SynonymRelationshipType newRelationType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
373 rel.setType(newRelationType);
374 //TODO handle citation and microCitation
375
376 if (isTargetTaxon){
377 relToTargetTaxonExists = true;
378 }else{
379 if (removeFromOtherTaxa){
380 acceptedTaxon.removeSynonym(synonym, false);
381 }else{
382 //do nothing
383 }
384 }
385 }
386 if (targetTaxon != null && ! relToTargetTaxonExists ){
387 Taxon acceptedTaxon = targetTaxon;
388 HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
389 boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
390 SynonymRelationshipType relType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
391 //TODO handle citation and microCitation
392 Reference citation = null;
393 String microCitation = null;
394 acceptedTaxon.addSynonym(synonym, relType, citation, microCitation);
395 }
396
397 }
398
399
400 /* (non-Javadoc)
401 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache(java.lang.Integer, eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy)
402 */
403 @Override
404 @Transactional(readOnly = false)
405 public void updateTitleCache(Class<? extends TaxonBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonBase> cacheStrategy, IProgressMonitor monitor) {
406 if (clazz == null){
407 clazz = TaxonBase.class;
408 }
409 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
410 }
411
412 @Override
413 @Autowired
414 protected void setDao(ITaxonDao dao) {
415 this.dao = dao;
416 }
417
418 @Override
419 public Pager<TaxonBase> findTaxaByName(Class<? extends TaxonBase> clazz, String uninomial, String infragenericEpithet, String specificEpithet, String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
420 Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
421
422 List<TaxonBase> results = new ArrayList<TaxonBase>();
423 if(numberOfResults > 0) { // no point checking again
424 results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
425 }
426
427 return new DefaultPagerImpl<TaxonBase>(pageNumber, numberOfResults, pageSize, results);
428 }
429
430 @Override
431 public List<TaxonBase> listTaxaByName(Class<? extends TaxonBase> clazz, String uninomial, String infragenericEpithet, String specificEpithet, String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
432 Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
433
434 List<TaxonBase> results = new ArrayList<TaxonBase>();
435 if(numberOfResults > 0) { // no point checking again
436 results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
437 }
438
439 return results;
440 }
441
442 @Override
443 public List<TaxonRelationship> listToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
444 Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
445
446 List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
447 if(numberOfResults > 0) { // no point checking again
448 results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
449 }
450 return results;
451 }
452
453 @Override
454 public Pager<TaxonRelationship> pageToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
455 Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
456
457 List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
458 if(numberOfResults > 0) { // no point checking again
459 results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
460 }
461 return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
462 }
463
464 @Override
465 public List<TaxonRelationship> listFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
466 Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
467
468 List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
469 if(numberOfResults > 0) { // no point checking again
470 results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
471 }
472 return results;
473 }
474
475 @Override
476 public Pager<TaxonRelationship> pageFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
477 Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
478
479 List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
480 if(numberOfResults > 0) { // no point checking again
481 results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
482 }
483 return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
484 }
485
486 @Override
487 public List<Taxon> listAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber,
488 List<OrderHint> orderHints, List<String> propertyPaths){
489 return pageAcceptedTaxaFor(synonymUuid, classificationUuid, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
490 }
491
492 @Override
493 public Pager<Taxon> pageAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber,
494 List<OrderHint> orderHints, List<String> propertyPaths){
495
496 List<Taxon> list = new ArrayList<Taxon>();
497 Long count = 0l;
498
499 Synonym synonym = null;
500
501 try {
502 synonym = (Synonym) dao.load(synonymUuid);
503 } catch (ClassCastException e){
504 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid + " is not a Synonmy");
505 } catch (NullPointerException e){
506 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid);
507 }
508
509 Classification classificationFilter = null;
510 if(classificationUuid != null){
511 try {
512 classificationFilter = classificationDao.load(classificationUuid);
513 } catch (NullPointerException e){
514 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid);
515 }
516 if(classificationFilter == null){
517
518 }
519 }
520
521 count = dao.countAcceptedTaxaFor(synonym, classificationFilter) ;
522 if(count > (pageSize * pageNumber)){
523 list = dao.listAcceptedTaxaFor(synonym, classificationFilter, pageSize, pageNumber, orderHints, propertyPaths);
524 }
525
526 return new DefaultPagerImpl<Taxon>(pageNumber, count.intValue(), pageSize, list);
527 }
528
529
530 @Override
531 public Set<Taxon> listRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Integer maxDepth,
532 Integer limit, Integer start, List<String> propertyPaths) {
533
534 Set<Taxon> relatedTaxa = collectRelatedTaxa(taxon, includeRelationships, new HashSet<Taxon>(), maxDepth);
535 relatedTaxa.remove(taxon);
536 beanInitializer.initializeAll(relatedTaxa, propertyPaths);
537 return relatedTaxa;
538 }
539
540
541 /**
542 * recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
543 * <code>taxon</code> supplied as parameter.
544 *
545 * @param taxon
546 * @param includeRelationships
547 * @param taxa
548 * @param maxDepth can be <code>null</code> for infinite depth
549 * @return
550 */
551 private Set<Taxon> collectRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Set<Taxon> taxa, Integer maxDepth) {
552
553 if(taxa.isEmpty()) {
554 taxa.add(taxon);
555 }
556
557 if(includeRelationships.isEmpty()){
558 return taxa;
559 }
560
561 if(maxDepth != null) {
562 maxDepth--;
563 }
564 if(logger.isDebugEnabled()){
565 logger.debug("collecting related taxa for " + taxon + " with maxDepth=" + maxDepth);
566 }
567 List<TaxonRelationship> taxonRelationships = dao.getTaxonRelationships(taxon, null, null, null, null, null, null);
568 for (TaxonRelationship taxRel : taxonRelationships) {
569
570 // skip invalid data
571 if (taxRel.getToTaxon() == null || taxRel.getFromTaxon() == null || taxRel.getType() == null) {
572 continue;
573 }
574 // filter by includeRelationships
575 for (TaxonRelationshipEdge relationshipEdgeFilter : includeRelationships) {
576 if ( relationshipEdgeFilter.getTaxonRelationshipType().equals(taxRel.getType()) ) {
577 if (relationshipEdgeFilter.getDirections().contains(Direction.relatedTo) && !taxa.contains(taxRel.getToTaxon())) {
578 if(logger.isDebugEnabled()){
579 logger.debug(maxDepth + ": " + taxon.getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxRel.getToTaxon().getTitleCache());
580 }
581 taxa.add(taxRel.getToTaxon());
582 if(maxDepth == null || maxDepth > 0) {
583 taxa.addAll(collectRelatedTaxa(taxRel.getToTaxon(), includeRelationships, taxa, maxDepth));
584 }
585 }
586 if(relationshipEdgeFilter.getDirections().contains(Direction.relatedFrom) && !taxa.contains(taxRel.getFromTaxon())) {
587 taxa.add(taxRel.getFromTaxon());
588 if(logger.isDebugEnabled()){
589 logger.debug(maxDepth + ": " +taxRel.getFromTaxon().getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxon.getTitleCache() );
590 }
591 if(maxDepth == null || maxDepth > 0) {
592 taxa.addAll(collectRelatedTaxa(taxRel.getFromTaxon(), includeRelationships, taxa, maxDepth));
593 }
594 }
595 }
596 }
597 }
598 return taxa;
599 }
600
601 /* (non-Javadoc)
602 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getSynonyms(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)
603 */
604 @Override
605 public Pager<SynonymRelationship> getSynonyms(Taxon taxon, SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
606 Integer numberOfResults = dao.countSynonyms(taxon, type);
607
608 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
609 if(numberOfResults > 0) { // no point checking again
610 results = dao.getSynonyms(taxon, type, pageSize, pageNumber, orderHints, propertyPaths);
611 }
612
613 return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
614 }
615
616 /* (non-Javadoc)
617 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getSynonyms(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)
618 */
619 @Override
620 public Pager<SynonymRelationship> getSynonyms(Synonym synonym, SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
621 Integer numberOfResults = dao.countSynonyms(synonym, type);
622
623 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
624 if(numberOfResults > 0) { // no point checking again
625 results = dao.getSynonyms(synonym, type, pageSize, pageNumber, orderHints, propertyPaths);
626 }
627
628 return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
629 }
630
631 /* (non-Javadoc)
632 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHomotypicSynonymsByHomotypicGroup(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
633 */
634 @Override
635 public List<List<Synonym>> getSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
636 List<List<Synonym>> result = new ArrayList<List<Synonym>>();
637 Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
638
639 //homotypic
640 result.add(t.getHomotypicSynonymsByHomotypicGroup());
641
642 //heterotypic
643 List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
644 for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
645 result.add(t.getSynonymsInGroup(homotypicalGroup));
646 }
647
648 return result;
649
650 }
651
652 /* (non-Javadoc)
653 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHomotypicSynonymsByHomotypicGroup(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
654 */
655 @Override
656 public List<Synonym> getHomotypicSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
657 Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
658 return t.getHomotypicSynonymsByHomotypicGroup();
659 }
660
661 /* (non-Javadoc)
662 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHeterotypicSynonymyGroups(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
663 */
664 @Override
665 public List<List<Synonym>> getHeterotypicSynonymyGroups(Taxon taxon, List<String> propertyPaths){
666 Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
667 List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
668 List<List<Synonym>> heterotypicSynonymyGroups = new ArrayList<List<Synonym>>(homotypicalGroups.size());
669 for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
670 heterotypicSynonymyGroups.add(t.getSynonymsInGroup(homotypicalGroup));
671 }
672 return heterotypicSynonymyGroups;
673 }
674
675 @Override
676 public List<UuidAndTitleCache<IdentifiableEntity>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator){
677
678 List<UuidAndTitleCache<IdentifiableEntity>> results = new ArrayList<UuidAndTitleCache<IdentifiableEntity>>();
679
680
681 if (configurator.isDoSynonyms() || configurator.isDoTaxa() || configurator.isDoNamesWithoutTaxa()){
682 results = dao.getTaxaByNameForEditor(configurator.isDoTaxa(), configurator.isDoSynonyms(), configurator.isDoNamesWithoutTaxa(), configurator.isDoMisappliedNames(),configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
683 }
684 if (configurator.isDoTaxaByCommonNames()) {
685
686 if(configurator.getPageSize() == null ){
687 List<UuidAndTitleCache<IdentifiableEntity>> commonNameResults = dao.getTaxaByCommonNameForEditor(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
688 if(commonNameResults != null){
689 results.addAll(commonNameResults);
690 }
691 }
692 }
693 return results;
694 }
695
696 /* (non-Javadoc)
697 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaAndNames(eu.etaxonomy.cdm.api.service.config.ITaxonServiceConfigurator)
698 */
699 @Override
700 public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
701
702 List<IdentifiableEntity> results = new ArrayList<IdentifiableEntity>();
703 int numberOfResults = 0; // overall number of results (as opposed to number of results per page)
704 List<TaxonBase> taxa = null;
705
706 // Taxa and synonyms
707 long numberTaxaResults = 0L;
708
709
710 List<String> propertyPath = new ArrayList<String>();
711 if(configurator.getTaxonPropertyPath() != null){
712 propertyPath.addAll(configurator.getTaxonPropertyPath());
713 }
714
715
716 if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa()){
717 if(configurator.getPageSize() != null){ // no point counting if we need all anyway
718 numberTaxaResults =
719 dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
720 configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(),
721 configurator.getNamedAreas());
722 }
723
724 if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
725 taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
726 configurator.isDoMisappliedNames(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(),
727 configurator.getMatchMode(), configurator.getNamedAreas(),
728 configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
729 }
730 }
731
732 if (logger.isDebugEnabled()) { logger.debug(numberTaxaResults + " matching taxa counted"); }
733
734 if(taxa != null){
735 results.addAll(taxa);
736 }
737
738 numberOfResults += numberTaxaResults;
739
740 // Names without taxa
741 if (configurator.isDoNamesWithoutTaxa()) {
742 int numberNameResults = 0;
743
744 List<? extends TaxonNameBase<?,?>> names =
745 nameDao.findByName(configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
746 configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
747 if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
748 if (names.size() > 0) {
749 for (TaxonNameBase<?,?> taxonName : names) {
750 if (taxonName.getTaxonBases().size() == 0) {
751 results.add(taxonName);
752 numberNameResults++;
753 }
754 }
755 if (logger.isDebugEnabled()) { logger.debug(numberNameResults + " matching name(s) without taxa found"); }
756 numberOfResults += numberNameResults;
757 }
758 }
759
760 // Taxa from common names
761
762 if (configurator.isDoTaxaByCommonNames()) {
763 taxa = new ArrayList<TaxonBase>();
764 numberTaxaResults = 0;
765 if(configurator.getPageSize() != null){// no point counting if we need all anyway
766 numberTaxaResults = dao.countTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
767 }
768 if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){
769 List<Taxon> commonNameResults = dao.getTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas(), configurator.getPageSize(), configurator.getPageNumber(), configurator.getTaxonPropertyPath());
770 taxa.addAll(commonNameResults);
771 }
772 if(taxa != null){
773 results.addAll(taxa);
774 }
775 numberOfResults += numberTaxaResults;
776
777 }
778
779 return new DefaultPagerImpl<IdentifiableEntity>
780 (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
781 }
782
783 public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(){
784 return dao.getUuidAndTitleCache();
785 }
786
787 /* (non-Javadoc)
788 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllMedia(eu.etaxonomy.cdm.model.taxon.Taxon, int, int, int, java.lang.String[])
789 */
790 @Override
791 public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
792 List<MediaRepresentation> medRep = new ArrayList<MediaRepresentation>();
793 taxon = (Taxon)dao.load(taxon.getUuid());
794 Set<TaxonDescription> descriptions = taxon.getDescriptions();
795 for (TaxonDescription taxDesc: descriptions){
796 Set<DescriptionElementBase> elements = taxDesc.getElements();
797 for (DescriptionElementBase descElem: elements){
798 for(Media media : descElem.getMedia()){
799
800 //find the best matching representation
801 medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
802
803 }
804 }
805 }
806 return medRep;
807 }
808
809 /* (non-Javadoc)
810 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listTaxonDescriptionMedia(eu.etaxonomy.cdm.model.taxon.Taxon, boolean)
811 */
812 @Override
813 public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
814 return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
815 }
816
817
818 /* (non-Javadoc)
819 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listMedia(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.Set, boolean, java.util.List)
820 */
821 @Override
822 public List<Media> listMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships,
823 Boolean limitToGalleries, Boolean includeTaxonDescriptions, Boolean includeOccurrences,
824 Boolean includeTaxonNameDescriptions, List<String> propertyPath) {
825
826 logger.trace("listMedia() - START");
827
828 Set<Taxon> taxa = new HashSet<Taxon>();
829 List<Media> taxonMedia = new ArrayList<Media>();
830 List<Media> nonImageGalleryImages = new ArrayList<Media>();
831
832 if (limitToGalleries == null) {
833 limitToGalleries = false;
834 }
835
836 // --- resolve related taxa
837 if (includeRelationships != null && ! includeRelationships.isEmpty()) {
838 logger.trace("listMedia() - resolve related taxa");
839 taxa = listRelatedTaxa(taxon, includeRelationships, null, null, null, null);
840 }
841
842 taxa.add((Taxon) dao.load(taxon.getUuid()));
843
844 if(includeTaxonDescriptions != null && includeTaxonDescriptions){
845 logger.trace("listMedia() - includeTaxonDescriptions");
846 List<TaxonDescription> taxonDescriptions = new ArrayList<TaxonDescription>();
847 // --- TaxonDescriptions
848 for (Taxon t : taxa) {
849 taxonDescriptions.addAll(descriptionService.listTaxonDescriptions(t, null, null, null, null, propertyPath));
850 }
851 for (TaxonDescription taxonDescription : taxonDescriptions) {
852 if (!limitToGalleries || taxonDescription.isImageGallery()) {
853 for (DescriptionElementBase element : taxonDescription.getElements()) {
854 for (Media media : element.getMedia()) {
855 if(taxonDescription.isImageGallery()){
856 taxonMedia.add(media);
857 }
858 else{
859 nonImageGalleryImages.add(media);
860 }
861 }
862 }
863 }
864 }
865 //put images from image gallery first (#3242)
866 taxonMedia.addAll(nonImageGalleryImages);
867 }
868
869
870 if(includeOccurrences != null && includeOccurrences) {
871 logger.trace("listMedia() - includeOccurrences");
872 Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<SpecimenOrObservationBase>();
873 // --- Specimens
874 for (Taxon t : taxa) {
875 specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
876 }
877 for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
878
879 // direct media removed from specimen #3597
880 // taxonMedia.addAll(occurrence.getMedia());
881
882 // SpecimenDescriptions
883 Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
884 for (DescriptionBase specimenDescription : specimenDescriptions) {
885 if (!limitToGalleries || specimenDescription.isImageGallery()) {
886 Set<DescriptionElementBase> elements = specimenDescription.getElements();
887 for (DescriptionElementBase element : elements) {
888 for (Media media : element.getMedia()) {
889 taxonMedia.add(media);
890 }
891 }
892 }
893 }
894
895 // Collection
896 //TODO why may collections have media attached? #
897 if (occurrence.isInstanceOf(DerivedUnit.class)) {
898 DerivedUnit derivedUnit = CdmBase.deproxy(occurrence, DerivedUnit.class);
899 if (derivedUnit.getCollection() != null){
900 taxonMedia.addAll(derivedUnit.getCollection().getMedia());
901 }
902 }
903
904 // pherograms & gelPhotos
905 if (occurrence.isInstanceOf(DnaSample.class)) {
906 DnaSample dnaSample = CdmBase.deproxy(occurrence, DnaSample.class);
907 Set<Sequence> sequences = dnaSample.getSequences();
908 //we do show only those gelPhotos which lead to a consensus sequence
909 for (Sequence sequence : sequences) {
910 Set<Media> dnaRelatedMedia = new HashSet<Media>();
911 for (SingleRead singleRead : sequence.getSingleReads()){
912 AmplificationResult amplification = singleRead.getAmplificationResult();
913 dnaRelatedMedia.add(amplification.getGelPhoto());
914 dnaRelatedMedia.add(singleRead.getPherogram());
915 dnaRelatedMedia.remove(null);
916 }
917 taxonMedia.addAll(dnaRelatedMedia);
918 }
919 }
920
921 }
922 }
923
924 if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
925 logger.trace("listMedia() - includeTaxonNameDescriptions");
926 // --- TaxonNameDescription
927 Set<TaxonNameDescription> nameDescriptions = new HashSet<TaxonNameDescription>();
928 for (Taxon t : taxa) {
929 nameDescriptions .addAll(t.getName().getDescriptions());
930 }
931 for(TaxonNameDescription nameDescription: nameDescriptions){
932 if (!limitToGalleries || nameDescription.isImageGallery()) {
933 Set<DescriptionElementBase> elements = nameDescription.getElements();
934 for (DescriptionElementBase element : elements) {
935 for (Media media : element.getMedia()) {
936 taxonMedia.add(media);
937 }
938 }
939 }
940 }
941 }
942
943
944 logger.trace("listMedia() - initialize");
945 beanInitializer.initializeAll(taxonMedia, propertyPath);
946
947 logger.trace("listMedia() - END");
948
949 return taxonMedia;
950 }
951
952 /* (non-Javadoc)
953 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaByID(java.util.Set)
954 */
955 @Override
956 public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
957 return this.dao.listByIds(listOfIDs, null, null, null, null);
958 }
959
960 /* (non-Javadoc)
961 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxonByUuid(UUID uuid, List<String> propertyPaths)
962 */
963 @Override
964 public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
965 return this.dao.findByUuid(uuid, null ,propertyPaths);
966 }
967
968 /* (non-Javadoc)
969 * @see eu.etaxonomy.cdm.api.service.ITaxonService#countAllRelationships()
970 */
971 @Override
972 public int countAllRelationships() {
973 return this.dao.countAllRelationships();
974 }
975
976
977
978
979 /* (non-Javadoc)
980 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNames(java.util.List)
981 */
982 @Override
983 public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
984 return this.dao.findIdenticalTaxonNames(propertyPath);
985 }
986
987
988 /* (non-Javadoc)
989 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator)
990 */
991 @Override
992 public DeleteResult deleteTaxon(Taxon taxon, TaxonDeletionConfigurator config, Classification classification) {
993
994 if (config == null){
995 config = new TaxonDeletionConfigurator();
996 }
997
998 DeleteResult result = isDeletable(taxon, config);
999
1000 if (result.isOk()){
1001 // --- DeleteSynonymRelations
1002 if (config.isDeleteSynonymRelations()){
1003 boolean removeSynonymNameFromHomotypicalGroup = false;
1004 // use tmp Set to avoid concurrent modification
1005 Set<SynonymRelationship> synRelsToDelete = new HashSet<SynonymRelationship>();
1006 synRelsToDelete.addAll(taxon.getSynonymRelations());
1007 for (SynonymRelationship synRel : synRelsToDelete){
1008 Synonym synonym = synRel.getSynonym();
1009 // taxon.removeSynonymRelation will set the accepted taxon and the synonym to NULL
1010 // this will cause hibernate to delete the relationship since
1011 // the SynonymRelationship field on both is annotated with removeOrphan
1012 // so no further explicit deleting of the relationship should be done here
1013 taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
1014
1015 // --- DeleteSynonymsIfPossible
1016 if (config.isDeleteSynonymsIfPossible()){
1017 //TODO which value
1018 boolean newHomotypicGroupIfNeeded = true;
1019 SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
1020 deleteSynonym(synonym, taxon, synConfig);
1021 }
1022 // relationship will be deleted by hibernate automatically,
1023 // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797
1024 // else{
1025 // deleteSynonymRelationships(synonym, taxon);
1026 // }
1027 }
1028 }
1029
1030 // --- DeleteTaxonRelationships
1031 if (! config.isDeleteTaxonRelationships()){
1032 if (taxon.getTaxonRelations().size() > 0){
1033 String message = "Taxon can't be deleted as it is related to another taxon. " +
1034 "Remove taxon from all relations to other taxa prior to deletion.";
1035 // throw new ReferencedObjectUndeletableException(message);
1036 }
1037 } else{
1038 for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
1039 if (config.isDeleteMisappliedNamesAndInvalidDesignations()){
1040 if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){
1041 if (taxon.equals(taxRel.getToTaxon())){
1042 this.deleteTaxon(taxRel.getFromTaxon(), config, classification);
1043 }
1044 }
1045 }
1046 taxon.removeTaxonRelation(taxRel);
1047 /*if (taxFrom.equals(taxon)){
1048 try{
1049 this.deleteTaxon(taxTo, taxConf, classification);
1050 } catch(DataChangeNoRollbackException e){
1051 logger.debug("A related taxon will not be deleted." + e.getMessage());
1052 }
1053 } else {
1054 try{
1055 this.deleteTaxon(taxFrom, taxConf, classification);
1056 } catch(DataChangeNoRollbackException e){
1057 logger.debug("A related taxon will not be deleted." + e.getMessage());
1058 }
1059
1060 }*/
1061 }
1062 }
1063
1064 // TaxonDescription
1065 if (config.isDeleteDescriptions()){
1066 Set<TaxonDescription> descriptions = taxon.getDescriptions();
1067 List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1068 for (TaxonDescription desc: descriptions){
1069 //TODO use description delete configurator ?
1070 //FIXME check if description is ALWAYS deletable
1071 if (desc.getDescribedSpecimenOrObservation() != null){
1072 String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1073 " which also describes specimens or abservations";
1074 //throw new ReferencedObjectUndeletableException(message);
1075 }
1076
1077 removeDescriptions.add(desc);
1078
1079
1080 }
1081 for (TaxonDescription desc: removeDescriptions){
1082 taxon.removeDescription(desc);
1083 descriptionService.delete(desc);
1084 }
1085 }
1086
1087
1088 /* //check references with only reverse mapping
1089 String message = checkForReferences(taxon);
1090 if (message != null){
1091 //throw new ReferencedObjectUndeletableException(message.toString());
1092 }*/
1093
1094 if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null )){
1095 //if (taxon.getTaxonNodes().size() > 0){
1096 // message = "Taxon can't be deleted as it is used in a classification node. Remove taxon from all classifications prior to deletion or define a classification where it should be deleted or adapt the taxon deletion configurator.";
1097 // throw new ReferencedObjectUndeletableException(message);
1098 //}
1099 }else{
1100 if (taxon.getTaxonNodes().size() != 0){
1101 Set<TaxonNode> nodes = taxon.getTaxonNodes();
1102 Iterator<TaxonNode> iterator = nodes.iterator();
1103 TaxonNode node = null;
1104 boolean deleteChildren;
1105 if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1106 deleteChildren = true;
1107 }else {
1108 deleteChildren = false;
1109 }
1110 boolean success = true;
1111 if (!config.isDeleteInAllClassifications() && !(classification == null)){
1112 while (iterator.hasNext()){
1113 node = iterator.next();
1114 if (node.getClassification().equals(classification)){
1115 break;
1116 }
1117 node = null;
1118 }
1119 if (node != null){
1120 success =taxon.removeTaxonNode(node, deleteChildren);
1121 nodeService.delete(node);
1122 } else {
1123 result.setError();
1124 result.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1125 }
1126 } else if (config.isDeleteInAllClassifications()){
1127 Set<ITaxonTreeNode> nodesList = new HashSet<ITaxonTreeNode>();
1128 nodesList.addAll(taxon.getTaxonNodes());
1129
1130 for (ITaxonTreeNode treeNode: nodesList){
1131 TaxonNode taxonNode = (TaxonNode) treeNode;
1132 if(!deleteChildren){
1133 /* Object[] childNodes = taxonNode.getChildNodes().toArray();
1134 //nodesList.addAll(taxonNode.getChildNodes());
1135 for (Object childNode: childNodes){
1136 TaxonNode childNodeCast = (TaxonNode) childNode;
1137 deleteTaxon(childNodeCast.getTaxon(), config, classification);
1138
1139 }
1140
1141 /*for (TaxonNode childNode: taxonNode.getChildNodes()){
1142 deleteTaxon(childNode.getTaxon(), config, classification);
1143
1144 }
1145 // taxon.removeTaxonNode(taxonNode);
1146 //nodeService.delete(taxonNode);
1147 } else{
1148 */
1149 Object[] childNodes = taxonNode.getChildNodes().toArray();
1150 for (Object childNode: childNodes){
1151 TaxonNode childNodeCast = (TaxonNode) childNode;
1152 taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1153 }
1154
1155 //taxon.removeTaxonNode(taxonNode);
1156 }
1157 }
1158 config.getTaxonNodeConfig().setDeleteTaxon(false);
1159 DeleteResult resultNodes = nodeService.deleteTaxonNodes(nodesList, config);
1160 if (!resultNodes.isOk()){
1161 result.addExceptions(resultNodes.getExceptions());
1162 result.setStatus(resultNodes.getStatus());
1163 }
1164 }
1165 if (!success){
1166 result.setError();
1167 result.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1168 }
1169 }
1170 }
1171
1172
1173 //PolytomousKey TODO
1174
1175
1176 //TaxonNameBase
1177 if (config.isDeleteNameIfPossible()){
1178
1179
1180 //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1181 TaxonNameBase name = (TaxonNameBase)HibernateProxyHelper.deproxy(taxon.getName());
1182 //check whether taxon will be deleted or not
1183 if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){
1184 taxon = (Taxon) HibernateProxyHelper.deproxy(taxon);
1185 //name.removeTaxonBase(taxon);
1186 //nameService.saveOrUpdate(name);
1187 taxon.setName(null);
1188 //dao.delete(taxon);
1189 DeleteResult nameResult = new DeleteResult();
1190
1191 //remove name if possible (and required)
1192 if (name != null && config.isDeleteNameIfPossible()){
1193
1194 nameResult = nameService.delete(name, config.getNameDeletionConfig());
1195
1196 }
1197
1198
1199
1200 if (nameResult.isError()){
1201 //result.setError();
1202 result.addRelatedObject(name);
1203 result.addExceptions(nameResult.getExceptions());
1204 }
1205
1206 }
1207
1208 }
1209
1210 // TaxonDescription
1211 /* Set<TaxonDescription> descriptions = taxon.getDescriptions();
1212
1213 for (TaxonDescription desc: descriptions){
1214 if (config.isDeleteDescriptions()){
1215 //TODO use description delete configurator ?
1216 //FIXME check if description is ALWAYS deletable
1217 taxon.removeDescription(desc);
1218 descriptionService.delete(desc);
1219 }else{
1220 if (desc.getDescribedSpecimenOrObservations().size()>0){
1221 String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1222 " which also describes specimens or observations";
1223 throw new ReferencedObjectUndeletableException(message);
1224 }
1225 }
1226 }*/
1227
1228 if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) ){
1229 try{
1230 UUID uuid = dao.delete(taxon);
1231
1232 }catch(Exception e){
1233 result.addException(e);
1234 result.setError();
1235
1236 }
1237 } else {
1238 result.setError();
1239 result.addException(new Exception("The Taxon can't be deleted."));
1240
1241 }
1242 }
1243 // }else {
1244 // List<Exception> exceptions = new ArrayList<Exception>();
1245 // for (String message: referencedObjects){
1246 // ReferencedObjectUndeletableException exception = new ReferencedObjectUndeletableException(message);
1247 // exceptions.add(exception);
1248 // }
1249 // result.addExceptions(exceptions);
1250 // result.setError();
1251 //
1252 // }
1253 return result;
1254
1255 }
1256
1257 private String checkForReferences(Taxon taxon){
1258 Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1259 for (CdmBase referencingObject : referencingObjects){
1260 //IIdentificationKeys (Media, Polytomous, MultiAccess)
1261 if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1262 String message = "Taxon" + taxon.getTitleCache() + "can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
1263
1264 return message;
1265 }
1266
1267
1268 /* //PolytomousKeyNode
1269 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1270 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1271 return message;
1272 }*/
1273
1274 //TaxonInteraction
1275 if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1276 String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1277 return message;
1278 }
1279
1280 //TaxonInteraction
1281 if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1282 String message = "Taxon can't be deleted as it is used in a determination event";
1283 return message;
1284 }
1285
1286 }
1287
1288 referencingObjects = null;
1289 return null;
1290 }
1291
1292 private boolean checkForPolytomousKeys(Taxon taxon){
1293 boolean result = false;
1294 List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1295 if (!list.isEmpty()) {
1296 result = true;
1297 }
1298 return result;
1299 }
1300
1301 @Transactional(readOnly = false)
1302 public UUID delete(Synonym syn){
1303 UUID result = syn.getUuid();
1304 this.deleteSynonym(syn, null);
1305 return result;
1306 }
1307
1308
1309
1310 /* (non-Javadoc)
1311 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1312 */
1313 @Transactional(readOnly = false)
1314 @Override
1315 public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1316 return deleteSynonym(synonym, null, config);
1317
1318 }
1319
1320
1321 /* (non-Javadoc)
1322 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1323 */
1324 @Transactional(readOnly = false)
1325 @Override
1326 public DeleteResult deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1327 DeleteResult result = new DeleteResult();
1328 if (synonym == null){
1329 result.setAbort();
1330 return result;
1331 }
1332
1333 if (config == null){
1334 config = new SynonymDeletionConfigurator();
1335 }
1336 result = isDeletable(synonym, config);
1337
1338
1339 if (result.isOk()){
1340 synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
1341
1342 //remove synonymRelationship
1343 Set<Taxon> taxonSet = new HashSet<Taxon>();
1344 if (taxon != null){
1345 taxonSet.add(taxon);
1346 }else{
1347 taxonSet.addAll(synonym.getAcceptedTaxa());
1348 }
1349 for (Taxon relatedTaxon : taxonSet){
1350 // dao.deleteSynonymRelationships(synonym, relatedTaxon);
1351 relatedTaxon.removeSynonym(synonym, config.isNewHomotypicGroupIfNeeded());
1352 }
1353 this.saveOrUpdate(synonym);
1354
1355 //TODO remove name from homotypical group?
1356
1357 //remove synonym (if necessary)
1358
1359 UUID uuid = null;
1360 if (synonym.getSynonymRelations().isEmpty()){
1361 TaxonNameBase<?,?> name = synonym.getName();
1362 synonym.setName(null);
1363 uuid = dao.delete(synonym);
1364
1365 //remove name if possible (and required)
1366 if (name != null && config.isDeleteNameIfPossible()){
1367
1368 nameService.delete(name, config.getNameDeletionConfig());
1369
1370 }
1371
1372 }else {
1373 result.setError();
1374 result.addException(new ReferencedObjectUndeletableException("Synonym can not be deleted it is used in a synonymRelationship."));
1375 return result;
1376 }
1377
1378
1379 }
1380 return result;
1381 // else{
1382 // List<Exception> exceptions = new ArrayList<Exception>();
1383 // for (String message :messages){
1384 // exceptions.add(new ReferencedObjectUndeletableException(message));
1385 // }
1386 // result.setError();
1387 // result.addExceptions(exceptions);
1388 // return result;
1389 // }
1390
1391
1392 }
1393
1394 @Transactional(readOnly = false)
1395 @Override
1396 public DeleteResult deleteSynonym(UUID synonymUuid, UUID taxonUuid, SynonymDeletionConfigurator config) {
1397 Synonym synonym = HibernateProxyHelper.deproxy(dao.load(synonymUuid), Synonym.class);
1398 Taxon taxon = HibernateProxyHelper.deproxy(dao.load(taxonUuid), Taxon.class);
1399 return deleteSynonym(synonym, taxon, config);
1400 }
1401
1402 /* (non-Javadoc)
1403 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNameIds(java.util.List)
1404 */
1405 @Override
1406 public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1407
1408 return this.dao.findIdenticalNamesNew(propertyPath);
1409 }
1410
1411 /* (non-Javadoc)
1412 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getPhylumName(eu.etaxonomy.cdm.model.name.TaxonNameBase)
1413 */
1414 @Override
1415 public String getPhylumName(TaxonNameBase name){
1416 return this.dao.getPhylumName(name);
1417 }
1418
1419 /* (non-Javadoc)
1420 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
1421 */
1422 @Override
1423 public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1424 return dao.deleteSynonymRelationships(syn, taxon);
1425 }
1426
1427 /* (non-Javadoc)
1428 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym)
1429 */
1430 @Override
1431 public long deleteSynonymRelationships(Synonym syn) {
1432 return dao.deleteSynonymRelationships(syn, null);
1433 }
1434
1435
1436 /* (non-Javadoc)
1437 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listSynonymRelationships(eu.etaxonomy.cdm.model.taxon.TaxonBase, eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List, eu.etaxonomy.cdm.model.common.RelationshipBase.Direction)
1438 */
1439 @Override
1440 public List<SynonymRelationship> listSynonymRelationships(
1441 TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1442 List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1443 Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1444
1445 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1446 if(numberOfResults > 0) { // no point checking again
1447 results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1448 }
1449 return results;
1450 }
1451
1452 /* (non-Javadoc)
1453 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingTaxon(java.lang.String)
1454 */
1455 @Override
1456 public Taxon findBestMatchingTaxon(String taxonName) {
1457 MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1458 config.setTaxonNameTitle(taxonName);
1459 return findBestMatchingTaxon(config);
1460 }
1461
1462
1463
1464 @Override
1465 public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1466
1467 Taxon bestCandidate = null;
1468 try{
1469 // 1. search for acceptet taxa
1470 List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1471 boolean bestCandidateMatchesSecUuid = false;
1472 boolean bestCandidateIsInClassification = false;
1473 int countEqualCandidates = 0;
1474 for(TaxonBase taxonBaseCandidate : taxonList){
1475 if(taxonBaseCandidate instanceof Taxon){
1476 Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1477 boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1478 if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1479 continue;
1480 }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1481 bestCandidate = newCanditate;
1482 countEqualCandidates = 1;
1483 bestCandidateMatchesSecUuid = true;
1484 continue;
1485 }
1486
1487 boolean newCandidateInClassification = isInClassification(newCanditate, config);
1488 if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1489 continue;
1490 }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1491 bestCandidate = newCanditate;
1492 countEqualCandidates = 1;
1493 bestCandidateIsInClassification = true;
1494 continue;
1495 }
1496 if (bestCandidate == null){
1497 bestCandidate = newCanditate;
1498 countEqualCandidates = 1;
1499 continue;
1500 }
1501
1502 }else{ //not Taxon.class
1503 continue;
1504 }
1505 countEqualCandidates++;
1506
1507 }
1508 if (bestCandidate != null){
1509 if(countEqualCandidates > 1){
1510 logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1511 return bestCandidate;
1512 } else {
1513 logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1514 return bestCandidate;
1515 }
1516 }
1517
1518
1519 // 2. search for synonyms
1520 if (config.isIncludeSynonyms()){
1521 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1522 for(TaxonBase taxonBase : synonymList){
1523 if(taxonBase instanceof Synonym){
1524 Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1525 Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1526 if(!acceptetdCandidates.isEmpty()){
1527 bestCandidate = acceptetdCandidates.iterator().next();
1528 if(acceptetdCandidates.size() == 1){
1529 logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1530 return bestCandidate;
1531 } else {
1532 logger.info("using accepted Taxon " + bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1533 return bestCandidate;
1534 }
1535 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1536 }
1537 }
1538 }
1539 }
1540
1541 } catch (Exception e){
1542 logger.error(e);
1543 e.printStackTrace();
1544 }
1545
1546 return bestCandidate;
1547 }
1548
1549 private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1550 UUID configClassificationUuid = config.getClassificationUuid();
1551 if (configClassificationUuid == null){
1552 return false;
1553 }
1554 for (TaxonNode node : taxon.getTaxonNodes()){
1555 UUID classUuid = node.getClassification().getUuid();
1556 if (configClassificationUuid.equals(classUuid)){
1557 return true;
1558 }
1559 }
1560 return false;
1561 }
1562
1563 private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1564 UUID configSecUuid = config.getSecUuid();
1565 if (configSecUuid == null){
1566 return false;
1567 }
1568 UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1569 return configSecUuid.equals(taxonSecUuid);
1570 }
1571
1572 /* (non-Javadoc)
1573 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingSynonym(java.lang.String)
1574 */
1575 @Override
1576 public Synonym findBestMatchingSynonym(String taxonName) {
1577 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1578 if(! synonymList.isEmpty()){
1579 Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1580 if(synonymList.size() == 1){
1581 logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1582 return result;
1583 } else {
1584 logger.info("Several matching synonyms found. Using first: " + result.getTitleCache());
1585 return result;
1586 }
1587 }
1588 return null;
1589 }
1590
1591
1592 /* (non-Javadoc)
1593 * @see eu.etaxonomy.cdm.api.service.ITaxonService#moveSynonymToAnotherTaxon(eu.etaxonomy.cdm.model.taxon.SynonymRelationship, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType, eu.etaxonomy.cdm.model.reference.Reference, java.lang.String, boolean)
1594 */
1595 @Override
1596 public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, Taxon newTaxon, boolean moveHomotypicGroup,
1597 SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
1598
1599 Synonym synonym = oldSynonymRelation.getSynonym();
1600 Taxon fromTaxon = oldSynonymRelation.getAcceptedTaxon();
1601 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1602 TaxonNameBase<?,?> synonymName = synonym.getName();
1603 TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1604 //set default relationship type
1605 if (newSynonymRelationshipType == null){
1606 newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1607 }
1608 boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1609
1610 HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1611 int hgSize = homotypicGroup.getTypifiedNames().size();
1612 boolean isSingleInGroup = !(hgSize > 1);
1613
1614 if (! isSingleInGroup){
1615 boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1616 boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1617 if (isHomotypicToAccepted){
1618 String message = "Synonym is in homotypic group with accepted taxon%s. First remove synonym from homotypic group of accepted taxon before moving to other taxon.";
1619 String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1620 message = String.format(message, homotypicRelatives);
1621 throw new HomotypicalGroupChangeException(message);
1622 }
1623 if (! moveHomotypicGroup){
1624 String message = "Synonym is in homotypic group with other synonym(s). Either move complete homotypic group or remove synonym from homotypic group prior to moving to other taxon.";
1625 throw new HomotypicalGroupChangeException(message);
1626 }
1627 }else{
1628 moveHomotypicGroup = true; //single synonym always allows to moveCompleteGroup
1629 }
1630 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1631
1632 SynonymRelationship result = null;
1633 //move all synonyms to new taxon
1634 List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1635 for (Synonym syn: homotypicSynonyms){
1636 Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1637 for (SynonymRelationship synRelation : synRelations){
1638 if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1639 Reference<?> newReference = reference;
1640 if (newReference == null && keepReference){
1641 newReference = synRelation.getCitation();
1642 }
1643 String newRefDetail = referenceDetail;
1644 if (newRefDetail == null && keepReference){
1645 newRefDetail = synRelation.getCitationMicroReference();
1646 }
1647 newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
1648 fromTaxon = HibernateProxyHelper.deproxy(fromTaxon, Taxon.class);
1649 SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1650 fromTaxon.removeSynonymRelation(synRelation, false);
1651 //
1652 //change homotypic group of synonym if relType is 'homotypic'
1653 // if (newRelTypeIsHomotypic){
1654 // newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1655 // }
1656 //set result
1657 if (synRelation.equals(oldSynonymRelation)){
1658 result = newSynRelation;
1659 }
1660 }
1661 }
1662
1663 }
1664 saveOrUpdate(fromTaxon);
1665 saveOrUpdate(newTaxon);
1666 //Assert that there is a result
1667 if (result == null){
1668 String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1669 throw new IllegalStateException(message);
1670 }
1671 return result;
1672 }
1673
1674 /* (non-Javadoc)
1675 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheTaxon()
1676 */
1677 @Override
1678 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1679 return dao.getUuidAndTitleCacheTaxon();
1680 }
1681
1682 /* (non-Javadoc)
1683 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheSynonym()
1684 */
1685 @Override
1686 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1687 return dao.getUuidAndTitleCacheSynonym();
1688 }
1689
1690 /* (non-Javadoc)
1691 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findByFullText(java.lang.Class, java.lang.String, eu.etaxonomy.cdm.model.taxon.Classification, java.util.List, boolean, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)
1692 */
1693 @Override
1694 public Pager<SearchResult<TaxonBase>> findByFullText(
1695 Class<? extends TaxonBase> clazz, String queryString,
1696 Classification classification, List<Language> languages,
1697 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1698
1699
1700 LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1701
1702 // --- execute search
1703 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1704
1705 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1706 idFieldMap.put(CdmBaseType.TAXON, "id");
1707
1708 // --- initialize taxa, thighlight matches ....
1709 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1710 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1711 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1712
1713 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1714 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1715 }
1716
1717 @Override
1718 public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
1719 Classification classification,
1720 Integer pageSize, Integer pageNumber,
1721 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1722
1723 LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1724
1725 // --- execute search
1726 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1727
1728 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1729 idFieldMap.put(CdmBaseType.TAXON, "id");
1730
1731 // --- initialize taxa, thighlight matches ....
1732 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1733 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1734 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1735
1736 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1737 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1738 }
1739
1740 /**
1741 * @param clazz
1742 * @param queryString
1743 * @param classification
1744 * @param languages
1745 * @param highlightFragments
1746 * @param sortFields TODO
1747 * @param directorySelectClass
1748 * @return
1749 */
1750 protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1751 boolean highlightFragments, SortField[] sortFields) {
1752 BooleanQuery finalQuery = new BooleanQuery();
1753 BooleanQuery textQuery = new BooleanQuery();
1754
1755 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1756 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1757
1758 if(sortFields == null){
1759 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1760 }
1761 luceneSearch.setSortFields(sortFields);
1762
1763 // ---- search criteria
1764 luceneSearch.setCdmTypRestriction(clazz);
1765
1766 textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1767 textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1768
1769 finalQuery.add(textQuery, Occur.MUST);
1770
1771 if(classification != null){
1772 finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1773 }
1774 luceneSearch.setQuery(finalQuery);
1775
1776 if(highlightFragments){
1777 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1778 }
1779 return luceneSearch;
1780 }
1781
1782 /**
1783 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1784 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1785 * drawback of requiring to do the join an indexing time.
1786 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1787 *
1788 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1789 * <ul>
1790 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1791 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1792 * <ul>
1793 * @param queryString
1794 * @param classification
1795 * @param languages
1796 * @param highlightFragments
1797 * @param sortFields TODO
1798 *
1799 * @return
1800 * @throws IOException
1801 */
1802 protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1803 boolean highlightFragments, SortField[] sortFields) throws IOException {
1804
1805 String fromField;
1806 String queryTermField;
1807 String toField = "id"; // TaxonBase.uuid
1808
1809 if(edge.isBidirectional()){
1810 throw new RuntimeException("Bidirectional joining not supported!");
1811 }
1812 if(edge.isEvers()){
1813 fromField = "relatedFrom.id";
1814 queryTermField = "relatedFrom.titleCache";
1815 } else if(edge.isInvers()) {
1816 fromField = "relatedTo.id";
1817 queryTermField = "relatedTo.titleCache";
1818 } else {
1819 throw new RuntimeException("Invalid direction: " + edge.getDirections());
1820 }
1821
1822 BooleanQuery finalQuery = new BooleanQuery();
1823
1824 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1825 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1826
1827 BooleanQuery joinFromQuery = new BooleanQuery();
1828 joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1829 joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1830 Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1831
1832 if(sortFields == null){
1833 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1834 }
1835 luceneSearch.setSortFields(sortFields);
1836
1837 finalQuery.add(joinQuery, Occur.MUST);
1838
1839 if(classification != null){
1840 finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1841 }
1842 luceneSearch.setQuery(finalQuery);
1843
1844 if(highlightFragments){
1845 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1846 }
1847 return luceneSearch;
1848 }
1849
1850
1851
1852
1853 /* (non-Javadoc)
1854 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaAndNamesByFullText(java.util.EnumSet, java.lang.String, eu.etaxonomy.cdm.model.taxon.Classification, java.util.Set, java.util.List, boolean, java.lang.Integer, java.lang.Integer, java.util.List, java.util.Map)
1855 */
1856 @Override
1857 public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1858 EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1859 Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
1860 boolean highlightFragments, Integer pageSize,
1861 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1862 throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1863
1864 // FIXME: allow taxonomic ordering
1865 // hql equivalent: order by t.name.genusOrUninomial, case when t.name.specificEpithet like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache";
1866 // this require building a special sort column by a special classBridge
1867 if(highlightFragments){
1868 logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1869 "currently not fully supported by this method and thus " +
1870 "may not work with common names and misapplied names.");
1871 }
1872
1873 // convert sets to lists
1874 List<NamedArea> namedAreaList = null;
1875 List<PresenceAbsenceTerm>distributionStatusList = null;
1876 if(namedAreas != null){
1877 namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1878 namedAreaList.addAll(namedAreas);
1879 }
1880 if(distributionStatus != null){
1881 distributionStatusList = new ArrayList<PresenceAbsenceTerm>(distributionStatus.size());
1882 distributionStatusList.addAll(distributionStatus);
1883 }
1884
1885 // set default if parameter is null
1886 if(searchModes == null){
1887 searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1888 }
1889
1890 // set sort order and thus override any sort orders which may have been
1891 // defindes by prepare*Search methods
1892 if(orderHints == null){
1893 orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER;
1894 }
1895 SortField[] sortFields = new SortField[orderHints.size()];
1896 int i = 0;
1897 for(OrderHint oh : orderHints){
1898 sortFields[i++] = oh.toSortField();
1899 }
1900 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1901 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1902
1903
1904 boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1905
1906 List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1907 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1908
1909 /*
1910 ======== filtering by distribution , HOWTO ========
1911
1912 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1913 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1914 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1915 which will be put into a FilteredQuersy in the end ?
1916
1917
1918 3. how does it work in spatial?
1919 see
1920 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1921 - http://www.infoq.com/articles/LuceneSpatialSupport
1922 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1923 ------------------------------------------------------------------------
1924
1925 filter strategies:
1926 A) use a separate distribution filter per index sub-query/search:
1927 - byTaxonSyonym (query TaxaonBase):
1928 use a join area filter (Distribution -> TaxonBase)
1929 - byCommonName (query DescriptionElementBase): use an area filter on
1930 DescriptionElementBase !!! PROBLEM !!!
1931 This cannot work since the distributions are different entities than the
1932 common names and thus these are different lucene documents.
1933 - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1934 use a join area filter (Distribution -> TaxonBase)
1935
1936 B) use a common distribution filter for all index sub-query/searches:
1937 - use a common join area filter (Distribution -> TaxonBase)
1938 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1939 PROBLEM in this case: we are losing the fragment highlighting for the
1940 common names, since the returned documents are always TaxonBases
1941 */
1942
1943 /* The QueryFactory for creating filter queries on Distributions should
1944 * The query factory used for the common names query cannot be reused
1945 * for this case, since we want to only record the text fields which are
1946 * actually used in the primary query
1947 */
1948 QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1949
1950 BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1951
1952
1953 // search for taxa or synonyms
1954 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1955 Class taxonBaseSubclass = TaxonBase.class;
1956 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1957 taxonBaseSubclass = Taxon.class;
1958 } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1959 taxonBaseSubclass = Synonym.class;
1960 }
1961 luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1962 idFieldMap.put(CdmBaseType.TAXON, "id");
1963 /* A) does not work!!!!
1964 if(addDistributionFilter){
1965 // in this case we need a filter which uses a join query
1966 // to get the TaxonBase documents for the DescriptionElementBase documents
1967 // which are matching the areas in question
1968 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1969 namedAreaList,
1970 distributionStatusList,
1971 distributionFilterQueryFactory
1972 );
1973 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1974 }
1975 */
1976 if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1977 // add additional area filter for synonyms
1978 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1979 String toField = "accTaxon.id"; // id in TaxonBase index
1980
1981 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1982
1983 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1984 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1985
1986 }
1987 }
1988
1989 // search by CommonTaxonName
1990 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1991 // B)
1992 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1993 Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1994 "inDescription.taxon.id",
1995 "id",
1996 QueryFactory.addTypeRestriction(
1997 createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
1998 , CommonTaxonName.class
1999 ),
2000 CommonTaxonName.class);
2001 logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
2002 LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2003 byCommonNameSearch.setCdmTypRestriction(Taxon.class);
2004 byCommonNameSearch.setQuery(byCommonNameJoinQuery);
2005 byCommonNameSearch.setSortFields(sortFields);
2006 idFieldMap.put(CdmBaseType.TAXON, "id");
2007
2008 luceneSearches.add(byCommonNameSearch);
2009
2010 /* A) does not work!!!!
2011 luceneSearches.add(
2012 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
2013 queryString, classification, null, languages, highlightFragments)
2014 );
2015 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2016 if(addDistributionFilter){
2017 // in this case we are able to use DescriptionElementBase documents
2018 // which are matching the areas in question directly
2019 BooleanQuery byDistributionQuery = createByDistributionQuery(
2020 namedAreaList,
2021 distributionStatusList,
2022 distributionFilterQueryFactory
2023 );
2024 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
2025 } */
2026 }
2027
2028 // search by misapplied names
2029 if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
2030 // NOTE:
2031 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
2032 // which allows doing query time joins
2033 // finds the misapplied name (Taxon B) which is an misapplication for
2034 // a related Taxon A.
2035 //
2036 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
2037 new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
2038 queryString, classification, languages, highlightFragments, sortFields));
2039 idFieldMap.put(CdmBaseType.TAXON, "id");
2040
2041 if(addDistributionFilter){
2042 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2043
2044 /*
2045 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
2046 * Maybe this is a but in java itself java.
2047 *
2048 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2049 * directly:
2050 *
2051 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2052 *
2053 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2054 * will execute as expected:
2055 *
2056 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2057 * String toField = "relation." + misappliedNameForUuid +".to.id";
2058 *
2059 * Comparing both strings by the String.equals method returns true, so both String are identical.
2060 *
2061 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2062 * dependent from a specific jvm (openjdk6 6b27-1.12.6-1ubuntu0.13.04.2, openjdk7 7u25-2.3.10-1ubuntu0.13.04.2, oracle jdk1.7.0_25 tested)
2063 * The bug is persistent after a reboot of the development computer.
2064 */
2065 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2066 // String toField = "relation." + misappliedNameForUuid +".to.id";
2067 String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2068 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2069 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2070
2071 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2072 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2073 QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery);
2074
2075 // debug code for bug described above
2076 DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2077 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2078
2079 multiIndexByAreaFilter.add(filter, Occur.SHOULD);
2080 }
2081 }
2082
2083 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
2084 luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
2085
2086
2087 if(addDistributionFilter){
2088
2089 // B)
2090 // in this case we need a filter which uses a join query
2091 // to get the TaxonBase documents for the DescriptionElementBase documents
2092 // which are matching the areas in question
2093 //
2094 // for toTaxa, doByCommonName
2095 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2096 namedAreaList,
2097 distributionStatusList,
2098 distributionFilterQueryFactory
2099 );
2100 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
2101 }
2102
2103 if (addDistributionFilter){
2104 multiSearch.setFilter(multiIndexByAreaFilter);
2105 }
2106
2107
2108 // --- execute search
2109 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2110
2111 // --- initialize taxa, highlight matches ....
2112 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2113
2114
2115 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2116 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2117
2118 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2119 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2120 }
2121
2122 /**
2123 * @param namedAreaList at least one area must be in the list
2124 * @param distributionStatusList optional
2125 * @return
2126 * @throws IOException
2127 */
2128 protected Query createByDistributionJoinQuery(
2129 List<NamedArea> namedAreaList,
2130 List<PresenceAbsenceTerm> distributionStatusList,
2131 QueryFactory queryFactory
2132 ) throws IOException {
2133
2134 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2135 String toField = "id"; // id in TaxonBase index
2136
2137 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2138
2139 Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2140
2141 return taxonAreaJoinQuery;
2142 }
2143
2144 /**
2145 * @param namedAreaList
2146 * @param distributionStatusList
2147 * @param queryFactory
2148 * @return
2149 */
2150 private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2151 List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2152 BooleanQuery areaQuery = new BooleanQuery();
2153 // area field from Distribution
2154 areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2155
2156 // status field from Distribution
2157 if(distributionStatusList != null && distributionStatusList.size() > 0){
2158 areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2159 }
2160
2161 logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2162 return areaQuery;
2163 }
2164
2165 /**
2166 * This method has been primarily created for testing the area join query but might
2167 * also be useful in other situations
2168 *
2169 * @param namedAreaList
2170 * @param distributionStatusList
2171 * @param classification
2172 * @param highlightFragments
2173 * @return
2174 * @throws IOException
2175 */
2176 protected LuceneSearch prepareByDistributionSearch(
2177 List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
2178 Classification classification) throws IOException {
2179
2180 BooleanQuery finalQuery = new BooleanQuery();
2181
2182 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2183
2184 // FIXME is this query factory using the wrong type?
2185 QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2186
2187 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
2188 luceneSearch.setSortFields(sortFields);
2189
2190
2191 Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory);
2192
2193 finalQuery.add(byAreaQuery, Occur.MUST);
2194
2195 if(classification != null){
2196 finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2197 }
2198
2199 logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2200 luceneSearch.setQuery(finalQuery);
2201
2202 return luceneSearch;
2203 }
2204
2205
2206
2207 /* (non-Javadoc)
2208 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findByDescriptionElementFullText(java.lang.Class, java.lang.String, eu.etaxonomy.cdm.model.taxon.Classification, java.util.List, java.util.List, boolean, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)
2209 */
2210 @Override
2211 public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2212 Class<? extends DescriptionElementBase> clazz, String queryString,
2213 Classification classification, List<Feature> features, List<Language> languages,
2214 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2215
2216
2217 LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2218
2219 // --- execute search
2220 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2221
2222 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2223 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2224
2225 // --- initialize taxa, highlight matches ....
2226 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2227 @SuppressWarnings("rawtypes")
2228 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2229 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2230
2231 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2232 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2233
2234 }
2235
2236
2237 @Override
2238 public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2239 Classification classification, List<Language> languages, boolean highlightFragments,
2240 Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2241
2242 LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2243 LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2244
2245 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2246
2247 // --- execute search
2248 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2249
2250 // --- initialize taxa, highlight matches ....
2251 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2252
2253 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2254 idFieldMap.put(CdmBaseType.TAXON, "id");
2255 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2256
2257 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2258 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2259
2260 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2261 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2262
2263 }
2264
2265
2266 /**
2267 * @param clazz
2268 * @param queryString
2269 * @param classification
2270 * @param features
2271 * @param languages
2272 * @param highlightFragments
2273 * @param directorySelectClass
2274 * @return
2275 */
2276 protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2277 String queryString, Classification classification, List<Feature> features,
2278 List<Language> languages, boolean highlightFragments) {
2279
2280 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2281 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2282
2283 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.STRING, false)};
2284
2285 BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2286 languages, descriptionElementQueryFactory);
2287
2288 luceneSearch.setSortFields(sortFields);
2289 luceneSearch.setCdmTypRestriction(clazz);
2290 luceneSearch.setQuery(finalQuery);
2291 if(highlightFragments){
2292 luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2293 }
2294
2295 return luceneSearch;
2296 }
2297
2298 /**
2299 * @param queryString
2300 * @param classification
2301 * @param features
2302 * @param languages
2303 * @param descriptionElementQueryFactory
2304 * @return
2305 */
2306 private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2307 List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2308 BooleanQuery finalQuery = new BooleanQuery();
2309 BooleanQuery textQuery = new BooleanQuery();
2310 textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2311
2312 // common name
2313 Query nameQuery;
2314 if(languages == null || languages.size() == 0){
2315 nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString);
2316 } else {
2317 nameQuery = new BooleanQuery();
2318 BooleanQuery languageSubQuery = new BooleanQuery();
2319 for(Language lang : languages){
2320 languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD);
2321 }
2322 ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2323 ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
2324 }
2325 textQuery.add(nameQuery, Occur.SHOULD);
2326
2327
2328 // text field from TextData
2329 textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2330
2331 // --- TermBase fields - by representation ----
2332 // state field from CategoricalData
2333 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2334
2335 // state field from CategoricalData
2336 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2337
2338 // area field from Distribution
2339 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2340
2341 // status field from Distribution
2342 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2343
2344 finalQuery.add(textQuery, Occur.MUST);
2345 // --- classification ----
2346
2347 if(classification != null){
2348 finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2349 }
2350
2351 // --- IdentifieableEntity fields - by uuid
2352 if(features != null && features.size() > 0 ){
2353 finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2354 }
2355
2356 // the description must be associated with a taxon
2357 finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2358
2359 logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2360 return finalQuery;
2361 }
2362
2363 /**
2364 * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2365 * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2366 * This method is a convenient means to retrieve a Lucene query string for such the fields.
2367 *
2368 * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2369 * or {@link MultilanguageTextFieldBridge }
2370 * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2371 * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2372 * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2373 *
2374 * TODO move to utiliy class !!!!!!!!
2375 */
2376 private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2377
2378 if(stringBuilder == null){
2379 stringBuilder = new StringBuilder();
2380 }
2381 if(languages == null || languages.size() == 0){
2382 stringBuilder.append(name + ".ALL:(%1$s) ");
2383 } else {
2384 for(Language lang : languages){
2385 stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2386 }
2387 }
2388 return stringBuilder;
2389 }
2390
2391 @Override
2392 public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2393 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2394 List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2395
2396 HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2397
2398
2399 UUID nameUuid= taxon.getName().getUuid();
2400 ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2401 String epithetOfTaxon = null;
2402 String infragenericEpithetOfTaxon = null;
2403 String infraspecificEpithetOfTaxon = null;
2404 if (taxonName.isSpecies()){
2405 epithetOfTaxon= taxonName.getSpecificEpithet();
2406 } else if (taxonName.isInfraGeneric()){
2407 infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2408 } else if (taxonName.isInfraSpecific()){
2409 infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2410 }
2411 String genusOfTaxon = taxonName.getGenusOrUninomial();
2412 Set<TaxonNode> nodes = taxon.getTaxonNodes();
2413 List<String> taxonNames = new ArrayList<String>();
2414
2415 for (TaxonNode node: nodes){
2416 // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2417 // List<String> synonymsEpithet = new ArrayList<String>();
2418
2419 if (node.getClassification().equals(classification)){
2420 if (!node.isTopmostNode()){
2421 TaxonNode parent = node.getParent();
2422 parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
2423 TaxonNameBase<?,?> parentName = parent.getTaxon().getName();
2424 ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2425 Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
2426 Rank rankOfTaxon = taxonName.getRank();
2427
2428
2429 //create inferred synonyms for species, subspecies
2430 if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2431
2432 Synonym inferredEpithet = null;
2433 Synonym inferredGenus = null;
2434 Synonym potentialCombination = null;
2435
2436 List<String> propertyPaths = new ArrayList<String>();
2437 propertyPaths.add("synonym");
2438 propertyPaths.add("synonym.name");
2439 List<OrderHint> orderHints = new ArrayList<OrderHint>();
2440 orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2441
2442 List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2443 List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2444
2445 List<TaxonRelationship> taxonRelListParent = null;
2446 List<TaxonRelationship> taxonRelListTaxon = null;
2447 if (doWithMisappliedNames){
2448 taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2449 taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2450 }
2451
2452
2453 if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2454
2455
2456 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2457 Synonym syn = synonymRelationOfParent.getSynonym();
2458
2459 inferredEpithet = createInferredEpithets(taxon,
2460 zooHashMap, taxonName, epithetOfTaxon,
2461 infragenericEpithetOfTaxon,
2462 infraspecificEpithetOfTaxon,
2463 taxonNames, parentName,
2464 syn);
2465
2466
2467 inferredSynonyms.add(inferredEpithet);
2468 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2469 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2470 }
2471
2472 if (doWithMisappliedNames){
2473
2474 for (TaxonRelationship taxonRelationship: taxonRelListParent){
2475 Taxon misappliedName = taxonRelationship.getFromTaxon();
2476
2477 inferredEpithet = createInferredEpithets(taxon,
2478 zooHashMap, taxonName, epithetOfTaxon,
2479 infragenericEpithetOfTaxon,
2480 infraspecificEpithetOfTaxon,
2481 taxonNames, parentName,
2482 misappliedName);
2483
2484 inferredSynonyms.add(inferredEpithet);
2485 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2486 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2487 }
2488 }
2489
2490 if (!taxonNames.isEmpty()){
2491 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2492 ZoologicalName name;
2493 if (!synNotInCDM.isEmpty()){
2494 inferredSynonymsToBeRemoved.clear();
2495
2496 for (Synonym syn :inferredSynonyms){
2497 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2498 if (!synNotInCDM.contains(name.getNameCache())){
2499 inferredSynonymsToBeRemoved.add(syn);
2500 }
2501 }
2502
2503 // Remove identified Synonyms from inferredSynonyms
2504 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2505 inferredSynonyms.remove(synonym);
2506 }
2507 }
2508 }
2509
2510 }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2511
2512
2513 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2514 TaxonNameBase synName;
2515 ZoologicalName inferredSynName;
2516
2517 Synonym syn = synonymRelationOfTaxon.getSynonym();
2518 inferredGenus = createInferredGenus(taxon,
2519 zooHashMap, taxonName, epithetOfTaxon,
2520 genusOfTaxon, taxonNames, zooParentName, syn);
2521
2522 inferredSynonyms.add(inferredGenus);
2523 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2524 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2525
2526
2527 }
2528
2529 if (doWithMisappliedNames){
2530
2531 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2532 Taxon misappliedName = taxonRelationship.getFromTaxon();
2533 inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName, misappliedName);
2534
2535 inferredSynonyms.add(inferredGenus);
2536 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2537 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2538 }
2539 }
2540
2541
2542 if (!taxonNames.isEmpty()){
2543 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2544 ZoologicalName name;
2545 if (!synNotInCDM.isEmpty()){
2546 inferredSynonymsToBeRemoved.clear();
2547
2548 for (Synonym syn :inferredSynonyms){
2549 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2550 if (!synNotInCDM.contains(name.getNameCache())){
2551 inferredSynonymsToBeRemoved.add(syn);
2552 }
2553 }
2554
2555 // Remove identified Synonyms from inferredSynonyms
2556 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2557 inferredSynonyms.remove(synonym);
2558 }
2559 }
2560 }
2561
2562 }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2563
2564 Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2565 ZoologicalName inferredSynName;
2566 //for all synonyms of the parent...
2567 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2568 TaxonNameBase synName;
2569 Synonym synParent = synonymRelationOfParent.getSynonym();
2570 synName = synParent.getName();
2571
2572 HibernateProxyHelper.deproxy(synParent);
2573
2574 // Set the sourceReference
2575 sourceReference = synParent.getSec();
2576
2577 // Determine the idInSource
2578 String idInSourceParent = getIdInSource(synParent);
2579
2580 ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2581 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2582 String synParentInfragenericName = null;
2583 String synParentSpecificEpithet = null;
2584
2585 if (parentSynZooName.isInfraGeneric()){
2586 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2587 }
2588 if (parentSynZooName.isSpecies()){
2589 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2590 }
2591
2592 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2593 synonymsGenus.put(synGenusName, idInSource);
2594 }*/
2595
2596 //for all synonyms of the taxon
2597
2598 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2599
2600 Synonym syn = synonymRelationOfTaxon.getSynonym();
2601 ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2602 potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2603 synParentGenus,
2604 synParentInfragenericName,
2605 synParentSpecificEpithet, syn, zooHashMap);
2606
2607 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2608 inferredSynonyms.add(potentialCombination);
2609 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2610 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2611
2612 }
2613
2614
2615 }
2616
2617 if (doWithMisappliedNames){
2618
2619 for (TaxonRelationship parentRelationship: taxonRelListParent){
2620
2621 TaxonNameBase misappliedParentName;
2622
2623 Taxon misappliedParent = parentRelationship.getFromTaxon();
2624 misappliedParentName = misappliedParent.getName();
2625
2626 HibernateProxyHelper.deproxy(misappliedParent);
2627
2628 // Set the sourceReference
2629 sourceReference = misappliedParent.getSec();
2630
2631 // Determine the idInSource
2632 String idInSourceParent = getIdInSource(misappliedParent);
2633
2634 ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2635 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2636 String synParentInfragenericName = null;
2637 String synParentSpecificEpithet = null;
2638
2639 if (parentSynZooName.isInfraGeneric()){
2640 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2641 }
2642 if (parentSynZooName.isSpecies()){
2643 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2644 }
2645
2646
2647 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2648 Taxon misappliedName = taxonRelationship.getFromTaxon();
2649 ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2650 potentialCombination = createPotentialCombination(
2651 idInSourceParent, parentSynZooName, zooMisappliedName,
2652 synParentGenus,
2653 synParentInfragenericName,
2654 synParentSpecificEpithet, misappliedName, zooHashMap);
2655
2656
2657 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2658 inferredSynonyms.add(potentialCombination);
2659 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2660 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2661 }
2662 }
2663 }
2664
2665 if (!taxonNames.isEmpty()){
2666 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2667 ZoologicalName name;
2668 if (!synNotInCDM.isEmpty()){
2669 inferredSynonymsToBeRemoved.clear();
2670 for (Synonym syn :inferredSynonyms){
2671 try{
2672 name = (ZoologicalName) syn.getName();
2673 }catch (ClassCastException e){
2674 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2675 }
2676 if (!synNotInCDM.contains(name.getNameCache())){
2677 inferredSynonymsToBeRemoved.add(syn);
2678 }
2679 }
2680 // Remove identified Synonyms from inferredSynonyms
2681 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2682 inferredSynonyms.remove(synonym);
2683 }
2684 }
2685 }
2686 }
2687 }else {
2688 logger.info("The synonymrelationship type is not defined.");
2689 return inferredSynonyms;
2690 }
2691 }
2692 }
2693
2694 }
2695
2696 return inferredSynonyms;
2697 }
2698
2699 private Synonym createPotentialCombination(String idInSourceParent,
2700 ZoologicalName parentSynZooName, ZoologicalName zooSynName, String synParentGenus,
2701 String synParentInfragenericName, String synParentSpecificEpithet,
2702 TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2703 Synonym potentialCombination;
2704 Reference sourceReference;
2705 ZoologicalName inferredSynName;
2706 HibernateProxyHelper.deproxy(syn);
2707
2708 // Set sourceReference
2709 sourceReference = syn.getSec();
2710 if (sourceReference == null){
2711 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2712 //TODO:Remove
2713 if (!parentSynZooName.getTaxa().isEmpty()){
2714 TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2715
2716 sourceReference = taxon.getSec();
2717 }
2718 }
2719 String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2720
2721 String synTaxonInfraSpecificName= null;
2722
2723 if (parentSynZooName.isSpecies()){
2724 synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2725 }
2726
2727 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2728 synonymsEpithet.add(epithetName);
2729 }*/
2730
2731 //create potential combinations...
2732 inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2733
2734 inferredSynName.setGenusOrUninomial(synParentGenus);
2735 if (zooSynName.isSpecies()){
2736 inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2737 if (parentSynZooName.isInfraGeneric()){
2738 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2739 }
2740 }
2741 if (zooSynName.isInfraSpecific()){
2742 inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2743 inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2744 }
2745 if (parentSynZooName.isInfraGeneric()){
2746 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2747 }
2748
2749
2750 potentialCombination = Synonym.NewInstance(inferredSynName, null);
2751
2752 // Set the sourceReference
2753 potentialCombination.setSec(sourceReference);
2754
2755
2756 // Determine the idInSource
2757 String idInSourceSyn= getIdInSource(syn);
2758
2759 if (idInSourceParent != null && idInSourceSyn != null) {
2760 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2761 inferredSynName.addSource(originalSource);
2762 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2763 potentialCombination.addSource(originalSource);
2764 }
2765
2766 return potentialCombination;
2767 }
2768
2769 private Synonym createInferredGenus(Taxon taxon,
2770 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2771 String epithetOfTaxon, String genusOfTaxon,
2772 List<String> taxonNames, ZoologicalName zooParentName,
2773 TaxonBase syn) {
2774
2775 Synonym inferredGenus;
2776 TaxonNameBase synName;
2777 ZoologicalName inferredSynName;
2778 synName =syn.getName();
2779 HibernateProxyHelper.deproxy(syn);
2780
2781 // Determine the idInSource
2782 String idInSourceSyn = getIdInSource(syn);
2783 String idInSourceTaxon = getIdInSource(taxon);
2784 // Determine the sourceReference
2785 Reference sourceReference = syn.getSec();
2786
2787 //logger.warn(sourceReference.getTitleCache());
2788
2789 synName = syn.getName();
2790 ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2791 String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2792 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2793 synonymsEpithet.add(synSpeciesEpithetName);
2794 }*/
2795
2796 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2797 //TODO:differ between parent is genus and taxon is species, parent is subgenus and taxon is species, parent is species and taxon is subspecies and parent is genus and taxon is subgenus...
2798
2799
2800 inferredSynName.setGenusOrUninomial(genusOfTaxon);
2801 if (zooParentName.isInfraGeneric()){
2802 inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2803 }
2804
2805 if (taxonName.isSpecies()){
2806 inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2807 }
2808 if (taxonName.isInfraSpecific()){
2809 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2810 inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2811 }
2812
2813
2814 inferredGenus = Synonym.NewInstance(inferredSynName, null);
2815
2816 // Set the sourceReference
2817 inferredGenus.setSec(sourceReference);
2818
2819 // Add the original source
2820 if (idInSourceSyn != null && idInSourceTaxon != null) {
2821 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2822 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2823 inferredGenus.addSource(originalSource);
2824
2825 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2826 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2827 inferredSynName.addSource(originalSource);
2828 originalSource = null;
2829
2830 }else{
2831 logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2832 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2833 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2834 inferredGenus.addSource(originalSource);
2835
2836 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2837 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2838 inferredSynName.addSource(originalSource);
2839 originalSource = null;
2840 }
2841
2842 taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2843
2844 return inferredGenus;
2845 }
2846
2847 private Synonym createInferredEpithets(Taxon taxon,
2848 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2849 String epithetOfTaxon, String infragenericEpithetOfTaxon,
2850 String infraspecificEpithetOfTaxon, List<String> taxonNames,
2851 TaxonNameBase parentName, TaxonBase syn) {
2852
2853 Synonym inferredEpithet;
2854 TaxonNameBase<?,?> synName;
2855 ZoologicalName inferredSynName;
2856 HibernateProxyHelper.deproxy(syn);
2857
2858 // Determine the idInSource
2859 String idInSourceSyn = getIdInSource(syn);
2860 String idInSourceTaxon = getIdInSource(taxon);
2861 // Determine the sourceReference
2862 Reference<?> sourceReference = syn.getSec();
2863
2864 if (sourceReference == null){
2865 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2866 sourceReference = taxon.getSec();
2867 }
2868
2869 synName = syn.getName();
2870 ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2871 String synGenusName = zooSynName.getGenusOrUninomial();
2872 String synInfraGenericEpithet = null;
2873 String synSpecificEpithet = null;
2874
2875 if (zooSynName.getInfraGenericEpithet() != null){
2876 synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2877 }
2878
2879 if (zooSynName.isInfraSpecific()){
2880 synSpecificEpithet = zooSynName.getSpecificEpithet();
2881 }
2882
2883 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2884 synonymsGenus.put(synGenusName, idInSource);
2885 }*/
2886
2887 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2888
2889 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2890 if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2891 logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2892 }
2893 inferredSynName.setGenusOrUninomial(synGenusName);
2894
2895 if (parentName.isInfraGeneric()){
2896 inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2897 }
2898 if (taxonName.isSpecies()){
2899 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2900 }else if (taxonName.isInfraSpecific()){
2901 inferredSynName.setSpecificEpithet(synSpecificEpithet);
2902 inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2903 }
2904
2905 inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2906
2907 // Set the sourceReference
2908 inferredEpithet.setSec(sourceReference);
2909
2910 /* Add the original source
2911 if (idInSource != null) {
2912 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2913
2914 // Add the citation
2915 Reference citation = getCitation(syn);
2916 if (citation != null) {
2917 originalSource.setCitation(citation);
2918 inferredEpithet.addSource(originalSource);
2919 }
2920 }*/
2921 String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2922
2923
2924 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2925 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2926
2927 inferredEpithet.addSource(originalSource);
2928
2929 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2930 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2931
2932 inferredSynName.addSource(originalSource);
2933
2934
2935
2936 taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2937
2938 return inferredEpithet;
2939 }
2940
2941 /**
2942 * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2943 * Very likely only useful for createInferredSynonyms().
2944 * @param uuid
2945 * @param zooHashMap
2946 * @return
2947 */
2948 private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2949 ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2950 if (taxonName == null) {
2951 taxonName = zooHashMap.get(uuid);
2952 }
2953 return taxonName;
2954 }
2955
2956 /**
2957 * Returns the idInSource for a given Synonym.
2958 * @param syn
2959 */
2960 private String getIdInSource(TaxonBase taxonBase) {
2961 String idInSource = null;
2962 Set<IdentifiableSource> sources = taxonBase.getSources();
2963 if (sources.size() == 1) {
2964 IdentifiableSource source = sources.iterator().next();
2965 if (source != null) {
2966 idInSource = source.getIdInSource();
2967 }
2968 } else if (sources.size() > 1) {
2969 int count = 1;
2970 idInSource = "";
2971 for (IdentifiableSource source : sources) {
2972 idInSource += source.getIdInSource();
2973 if (count < sources.size()) {
2974 idInSource += "; ";
2975 }
2976 count++;
2977 }
2978 } else if (sources.size() == 0){
2979 logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2980 }
2981
2982
2983 return idInSource;
2984 }
2985
2986
2987 /**
2988 * Returns the citation for a given Synonym.
2989 * @param syn
2990 */
2991 private Reference getCitation(Synonym syn) {
2992 Reference citation = null;
2993 Set<IdentifiableSource> sources = syn.getSources();
2994 if (sources.size() == 1) {
2995 IdentifiableSource source = sources.iterator().next();
2996 if (source != null) {
2997 citation = source.getCitation();
2998 }
2999 } else if (sources.size() > 1) {
3000 logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
3001 }
3002
3003 return citation;
3004 }
3005
3006 @Override
3007 public List<Synonym> createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
3008 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
3009
3010 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
3011 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
3012 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
3013
3014 return inferredSynonyms;
3015 }
3016
3017 @Override
3018 public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
3019
3020 // TODO quickly implemented, create according dao !!!!
3021 Set<TaxonNode> nodes = new HashSet<TaxonNode>();
3022 Set<Classification> classifications = new HashSet<Classification>();
3023 List<Classification> list = new ArrayList<Classification>();
3024
3025 if (taxonBase == null) {
3026 return list;
3027 }
3028
3029 taxonBase = load(taxonBase.getUuid());
3030
3031 if (taxonBase instanceof Taxon) {
3032 nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
3033 } else {
3034 for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
3035 nodes.addAll(taxon.getTaxonNodes());
3036 }
3037 }
3038 for (TaxonNode node : nodes) {
3039 classifications.add(node.getClassification());
3040 }
3041 list.addAll(classifications);
3042 return list;
3043 }
3044
3045 @Override
3046 public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
3047 SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
3048 // Create new synonym using concept name
3049 TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
3050 Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
3051
3052 // Remove concept relation from taxon
3053 toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
3054
3055
3056
3057
3058 // Create a new synonym for the taxon
3059 SynonymRelationship synonymRelationship;
3060 if (synonymRelationshipType != null
3061 && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
3062 synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
3063 } else{
3064 synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
3065 }
3066
3067 this.saveOrUpdate(toTaxon);
3068 //TODO: configurator and classification
3069 TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
3070 config.setDeleteNameIfPossible(false);
3071 this.deleteTaxon(fromTaxon, config, null);
3072 return synonymRelationship.getSynonym();
3073
3074 }
3075 @Override
3076 public DeleteResult isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
3077 DeleteResult result = new DeleteResult();
3078 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
3079 if (taxonBase instanceof Taxon){
3080 TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3081 result = isDeletableForTaxon(references, taxonConfig);
3082 }else{
3083 SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3084 result = isDeletableForSynonym(references, synonymConfig);
3085 }
3086 return result;
3087 }
3088
3089 private DeleteResult isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3090 String message;
3091 DeleteResult result = new DeleteResult();
3092 for (CdmBase ref: references){
3093 if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase )){
3094 message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3095 result.addException(new ReferencedObjectUndeletableException(message));
3096 result.addRelatedObject(ref);
3097 result.setAbort();
3098 }
3099 }
3100
3101 return result;
3102 }
3103 private DeleteResult isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3104 String message = null;
3105 DeleteResult result = new DeleteResult();
3106 for (CdmBase ref: references){
3107 if (!(ref instanceof TaxonNameBase)){
3108 if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){
3109 message = "The Taxon can't be deleted as long as it has synonyms.";
3110
3111 }
3112 if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3113 message = "The Taxon can't be deleted as long as it has factual data.";
3114
3115 }
3116
3117 if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
3118 message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3119
3120 }
3121 if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonNode)){
3122 if (!config.isDeleteMisappliedNamesAndInvalidDesignations() && (((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())|| ((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR()))){
3123 message = "The Taxon can't be deleted as long as it has misapplied names or invalid designations.";
3124
3125 } else{
3126 message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3127
3128 }
3129 }
3130 if (ref instanceof PolytomousKeyNode){
3131 message = "The Taxon can't be deleted as long as it is referenced by a polytomous key node.";
3132
3133 }
3134
3135 if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){
3136 message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
3137
3138
3139 }
3140
3141
3142 /* //PolytomousKeyNode
3143 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3144 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3145 return message;
3146 }*/
3147
3148 //TaxonInteraction
3149 if (ref.isInstanceOf(TaxonInteraction.class)){
3150 message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3151
3152 }
3153
3154 //TaxonInteraction
3155 if (ref.isInstanceOf(DeterminationEvent.class)){
3156 message = "Taxon can't be deleted as it is used in a determination event";
3157
3158 }
3159
3160 }
3161 if (message != null){
3162 result.addException(new ReferencedObjectUndeletableException(message));
3163 result.addRelatedObject(ref);
3164 result.setAbort();
3165 }
3166 }
3167
3168 return result;
3169 }
3170
3171 @Override
3172 public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3173 IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3174
3175 //preliminary implementation
3176
3177 Set<Taxon> taxa = new HashSet<Taxon>();
3178 TaxonBase taxonBase = find(taxonUuid);
3179 if (taxonBase == null){
3180 return new IncludedTaxaDTO();
3181 }else if (taxonBase.isInstanceOf(Taxon.class)){
3182 Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3183 taxa.add(taxon);
3184 }else if (taxonBase.isInstanceOf(Synonym.class)){
3185 //TODO partial synonyms ??
3186 //TODO synonyms in general
3187 Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3188 taxa.addAll(syn.getAcceptedTaxa());
3189 }else{
3190 throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3191 }
3192
3193 Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3194 int i = 0;
3195 while((! related.isEmpty()) && i++ < 100){ //to avoid
3196 related = makeRelatedIncluded(related, result, config);
3197 }
3198
3199 return result;
3200 }
3201
3202 /**
3203 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3204 * data structure.
3205 * @return the set of conceptually related taxa for further use
3206 */
3207 /**
3208 * @param uncheckedTaxa
3209 * @param existingTaxa
3210 * @param config
3211 * @return
3212 */
3213 private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3214
3215 //children
3216 Set<TaxonNode> taxonNodes = new HashSet<TaxonNode>();
3217 for (Taxon taxon: uncheckedTaxa){
3218 taxonNodes.addAll(taxon.getTaxonNodes());
3219 }
3220
3221 Set<Taxon> children = new HashSet<Taxon>();
3222 if (! config.onlyCongruent){
3223 for (TaxonNode node: taxonNodes){
3224 List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, false);
3225 for (TaxonNode child : childNodes){
3226 children.add(child.getTaxon());
3227 }
3228 }
3229 children.remove(null); // just to be on the save side
3230 }
3231
3232 Iterator<Taxon> it = children.iterator();
3233 while(it.hasNext()){
3234 UUID uuid = it.next().getUuid();
3235 if (existingTaxa.contains(uuid)){
3236 it.remove();
3237 }else{
3238 existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3239 }
3240 }
3241
3242 //concept relations
3243 Set<Taxon> uncheckedAndChildren = new HashSet<Taxon>(uncheckedTaxa);
3244 uncheckedAndChildren.addAll(children);
3245
3246 Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3247
3248
3249 Set<Taxon> result = new HashSet<Taxon>(relatedTaxa);
3250 return result;
3251 }
3252
3253 /**
3254 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3255 * @return the set of these computed taxa
3256 */
3257 private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3258 Set<Taxon> result = new HashSet<Taxon>();
3259
3260 for (Taxon taxon : unchecked){
3261 Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3262 Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3263
3264 for (TaxonRelationship fromRel : fromRelations){
3265 if (config.includeDoubtful == false && fromRel.isDoubtful()){
3266 continue;
3267 }
3268 if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3269 !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
3270 !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3271 ){
3272 result.add(fromRel.getToTaxon());
3273 }
3274 }
3275
3276 for (TaxonRelationship toRel : toRelations){
3277 if (config.includeDoubtful == false && toRel.isDoubtful()){
3278 continue;
3279 }
3280 if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
3281 result.add(toRel.getFromTaxon());
3282 }
3283 }
3284 }
3285
3286 Iterator<Taxon> it = result.iterator();
3287 while(it.hasNext()){
3288 UUID uuid = it.next().getUuid();
3289 if (existingTaxa.contains(uuid)){
3290 it.remove();
3291 }else{
3292 existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3293 }
3294 }
3295 return result;
3296 }
3297
3298 @Override
3299 public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3300 List<TaxonBase> taxonList = dao.getTaxaByName(true, false, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, 0, config.getPropertyPath());
3301 return taxonList;
3302 }
3303
3304 @Override
3305 @Transactional(readOnly = true)
3306 public <S extends TaxonBase> Pager<FindByIdentifierDTO<S>> findByIdentifier(
3307 Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
3308 MatchMode matchmode, boolean includeEntity, Integer pageSize,
3309 Integer pageNumber, List<String> propertyPaths) {
3310 if (subtreeFilter == null){
3311 return findByIdentifier(clazz, identifier, identifierType, matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3312 }
3313
3314 Integer numberOfResults = dao.countByIdentifier(clazz, identifier, identifierType, subtreeFilter, matchmode);
3315 List<Object[]> daoResults = new ArrayList<Object[]>();
3316 if(numberOfResults > 0) { // no point checking again
3317 daoResults = dao.findByIdentifier(clazz, identifier, identifierType, subtreeFilter,
3318 matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3319 }
3320
3321 List<FindByIdentifierDTO<S>> result = new ArrayList<FindByIdentifierDTO<S>>();
3322 for (Object[] daoObj : daoResults){
3323 if (includeEntity){
3324 result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (S)daoObj[2]));
3325 }else{
3326 result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (UUID)daoObj[2], (String)daoObj[3]));
3327 }
3328 }
3329 return new DefaultPagerImpl<FindByIdentifierDTO<S>>(pageNumber, numberOfResults, pageSize, result);
3330 }
3331
3332
3333 }