fixing #4179 (improve performance of portal/taxon/{uuid}/media)
[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, false);
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 //if(configurator.getPageSize() == null ){
686 List<UuidAndTitleCache<IdentifiableEntity>> commonNameResults = dao.getTaxaByCommonNameForEditor(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
687 if(commonNameResults != null){
688 results.addAll(commonNameResults);
689 }
690 // }
691 }
692 return results;
693 }
694
695 /* (non-Javadoc)
696 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaAndNames(eu.etaxonomy.cdm.api.service.config.ITaxonServiceConfigurator)
697 */
698 @Override
699 public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
700
701 List<IdentifiableEntity> results = new ArrayList<IdentifiableEntity>();
702 int numberOfResults = 0; // overall number of results (as opposed to number of results per page)
703 List<TaxonBase> taxa = null;
704
705 // Taxa and synonyms
706 long numberTaxaResults = 0L;
707
708
709 List<String> propertyPath = new ArrayList<String>();
710 if(configurator.getTaxonPropertyPath() != null){
711 propertyPath.addAll(configurator.getTaxonPropertyPath());
712 }
713
714
715 if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa()){
716 if(configurator.getPageSize() != null){ // no point counting if we need all anyway
717 numberTaxaResults =
718 dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
719 configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(),
720 configurator.getNamedAreas());
721 }
722
723 if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
724 taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
725 configurator.isDoMisappliedNames(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(),
726 configurator.getMatchMode(), configurator.getNamedAreas(),
727 configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
728 }
729 }
730
731 if (logger.isDebugEnabled()) { logger.debug(numberTaxaResults + " matching taxa counted"); }
732
733 if(taxa != null){
734 results.addAll(taxa);
735 }
736
737 numberOfResults += numberTaxaResults;
738
739 // Names without taxa
740 if (configurator.isDoNamesWithoutTaxa()) {
741 int numberNameResults = 0;
742
743 List<? extends TaxonNameBase<?,?>> names =
744 nameDao.findByName(configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
745 configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
746 if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
747 if (names.size() > 0) {
748 for (TaxonNameBase<?,?> taxonName : names) {
749 if (taxonName.getTaxonBases().size() == 0) {
750 results.add(taxonName);
751 numberNameResults++;
752 }
753 }
754 if (logger.isDebugEnabled()) { logger.debug(numberNameResults + " matching name(s) without taxa found"); }
755 numberOfResults += numberNameResults;
756 }
757 }
758
759 // Taxa from common names
760
761 if (configurator.isDoTaxaByCommonNames()) {
762 taxa = new ArrayList<TaxonBase>();
763 numberTaxaResults = 0;
764 if(configurator.getPageSize() != null){// no point counting if we need all anyway
765 numberTaxaResults = dao.countTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
766 }
767 if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){
768 List<Taxon> commonNameResults = dao.getTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas(), configurator.getPageSize(), configurator.getPageNumber(), configurator.getTaxonPropertyPath());
769 taxa.addAll(commonNameResults);
770 }
771 if(taxa != null){
772 results.addAll(taxa);
773 }
774 numberOfResults += numberTaxaResults;
775
776 }
777
778 return new DefaultPagerImpl<IdentifiableEntity>
779 (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
780 }
781
782 public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(){
783 return dao.getUuidAndTitleCache();
784 }
785
786 /* (non-Javadoc)
787 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllMedia(eu.etaxonomy.cdm.model.taxon.Taxon, int, int, int, java.lang.String[])
788 */
789 @Override
790 public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
791 List<MediaRepresentation> medRep = new ArrayList<MediaRepresentation>();
792 taxon = (Taxon)dao.load(taxon.getUuid());
793 Set<TaxonDescription> descriptions = taxon.getDescriptions();
794 for (TaxonDescription taxDesc: descriptions){
795 Set<DescriptionElementBase> elements = taxDesc.getElements();
796 for (DescriptionElementBase descElem: elements){
797 for(Media media : descElem.getMedia()){
798
799 //find the best matching representation
800 medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
801
802 }
803 }
804 }
805 return medRep;
806 }
807
808 /* (non-Javadoc)
809 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listTaxonDescriptionMedia(eu.etaxonomy.cdm.model.taxon.Taxon, boolean)
810 */
811 @Override
812 public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
813 return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
814 }
815
816
817 /* (non-Javadoc)
818 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listMedia(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.Set, boolean, java.util.List)
819 */
820 @Override
821 public List<Media> listMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships,
822 Boolean limitToGalleries, Boolean includeTaxonDescriptions, Boolean includeOccurrences,
823 Boolean includeTaxonNameDescriptions, List<String> propertyPath) {
824
825 // logger.setLevel(Level.TRACE);
826 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
827
828 logger.trace("listMedia() - START");
829
830 Set<Taxon> taxa = new HashSet<Taxon>();
831 List<Media> taxonMedia = new ArrayList<Media>();
832 List<Media> nonImageGalleryImages = new ArrayList<Media>();
833
834 if (limitToGalleries == null) {
835 limitToGalleries = false;
836 }
837
838 // --- resolve related taxa
839 if (includeRelationships != null && ! includeRelationships.isEmpty()) {
840 logger.trace("listMedia() - resolve related taxa");
841 taxa = listRelatedTaxa(taxon, includeRelationships, null, null, null, null);
842 }
843
844 taxa.add((Taxon) dao.load(taxon.getUuid()));
845
846 if(includeTaxonDescriptions != null && includeTaxonDescriptions){
847 logger.trace("listMedia() - includeTaxonDescriptions");
848 List<TaxonDescription> taxonDescriptions = new ArrayList<TaxonDescription>();
849 // --- TaxonDescriptions
850 for (Taxon t : taxa) {
851 taxonDescriptions.addAll(descriptionService.listTaxonDescriptions(t, null, null, null, null, propertyPath));
852 }
853 for (TaxonDescription taxonDescription : taxonDescriptions) {
854 if (!limitToGalleries || taxonDescription.isImageGallery()) {
855 for (DescriptionElementBase element : taxonDescription.getElements()) {
856 for (Media media : element.getMedia()) {
857 if(taxonDescription.isImageGallery()){
858 taxonMedia.add(media);
859 }
860 else{
861 nonImageGalleryImages.add(media);
862 }
863 }
864 }
865 }
866 }
867 //put images from image gallery first (#3242)
868 taxonMedia.addAll(nonImageGalleryImages);
869 }
870
871
872 if(includeOccurrences != null && includeOccurrences) {
873 logger.trace("listMedia() - includeOccurrences");
874 Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<SpecimenOrObservationBase>();
875 // --- Specimens
876 for (Taxon t : taxa) {
877 specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
878 }
879 for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
880
881 // direct media removed from specimen #3597
882 // taxonMedia.addAll(occurrence.getMedia());
883
884 // SpecimenDescriptions
885 Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
886 for (DescriptionBase specimenDescription : specimenDescriptions) {
887 if (!limitToGalleries || specimenDescription.isImageGallery()) {
888 Set<DescriptionElementBase> elements = specimenDescription.getElements();
889 for (DescriptionElementBase element : elements) {
890 for (Media media : element.getMedia()) {
891 taxonMedia.add(media);
892 }
893 }
894 }
895 }
896
897 // Collection
898 //TODO why may collections have media attached? #
899 if (occurrence.isInstanceOf(DerivedUnit.class)) {
900 DerivedUnit derivedUnit = CdmBase.deproxy(occurrence, DerivedUnit.class);
901 if (derivedUnit.getCollection() != null){
902 taxonMedia.addAll(derivedUnit.getCollection().getMedia());
903 }
904 }
905
906 // pherograms & gelPhotos
907 if (occurrence.isInstanceOf(DnaSample.class)) {
908 DnaSample dnaSample = CdmBase.deproxy(occurrence, DnaSample.class);
909 Set<Sequence> sequences = dnaSample.getSequences();
910 //we do show only those gelPhotos which lead to a consensus sequence
911 for (Sequence sequence : sequences) {
912 Set<Media> dnaRelatedMedia = new HashSet<Media>();
913 for (SingleRead singleRead : sequence.getSingleReads()){
914 AmplificationResult amplification = singleRead.getAmplificationResult();
915 dnaRelatedMedia.add(amplification.getGelPhoto());
916 dnaRelatedMedia.add(singleRead.getPherogram());
917 dnaRelatedMedia.remove(null);
918 }
919 taxonMedia.addAll(dnaRelatedMedia);
920 }
921 }
922
923 }
924 }
925
926 if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
927 logger.trace("listMedia() - includeTaxonNameDescriptions");
928 // --- TaxonNameDescription
929 Set<TaxonNameDescription> nameDescriptions = new HashSet<TaxonNameDescription>();
930 for (Taxon t : taxa) {
931 nameDescriptions .addAll(t.getName().getDescriptions());
932 }
933 for(TaxonNameDescription nameDescription: nameDescriptions){
934 if (!limitToGalleries || nameDescription.isImageGallery()) {
935 Set<DescriptionElementBase> elements = nameDescription.getElements();
936 for (DescriptionElementBase element : elements) {
937 for (Media media : element.getMedia()) {
938 taxonMedia.add(media);
939 }
940 }
941 }
942 }
943 }
944
945
946 logger.trace("listMedia() - initialize");
947 beanInitializer.initializeAll(taxonMedia, propertyPath);
948
949 logger.trace("listMedia() - END");
950
951 return taxonMedia;
952 }
953
954 /* (non-Javadoc)
955 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaByID(java.util.Set)
956 */
957 @Override
958 public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
959 return this.dao.listByIds(listOfIDs, null, null, null, null);
960 }
961
962 /* (non-Javadoc)
963 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxonByUuid(UUID uuid, List<String> propertyPaths)
964 */
965 @Override
966 public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
967 return this.dao.findByUuid(uuid, null ,propertyPaths);
968 }
969
970 /* (non-Javadoc)
971 * @see eu.etaxonomy.cdm.api.service.ITaxonService#countAllRelationships()
972 */
973 @Override
974 public int countAllRelationships() {
975 return this.dao.countAllRelationships();
976 }
977
978
979
980
981 /* (non-Javadoc)
982 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNames(java.util.List)
983 */
984 @Override
985 public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
986 return this.dao.findIdenticalTaxonNames(propertyPath);
987 }
988
989
990 /* (non-Javadoc)
991 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator)
992 */
993 @Override
994 public DeleteResult deleteTaxon(Taxon taxon, TaxonDeletionConfigurator config, Classification classification) {
995
996 if (config == null){
997 config = new TaxonDeletionConfigurator();
998 }
999
1000 DeleteResult result = isDeletable(taxon, config);
1001
1002 if (result.isOk()){
1003 // --- DeleteSynonymRelations
1004 if (config.isDeleteSynonymRelations()){
1005 boolean removeSynonymNameFromHomotypicalGroup = false;
1006 // use tmp Set to avoid concurrent modification
1007 Set<SynonymRelationship> synRelsToDelete = new HashSet<SynonymRelationship>();
1008 synRelsToDelete.addAll(taxon.getSynonymRelations());
1009 for (SynonymRelationship synRel : synRelsToDelete){
1010 Synonym synonym = synRel.getSynonym();
1011 // taxon.removeSynonymRelation will set the accepted taxon and the synonym to NULL
1012 // this will cause hibernate to delete the relationship since
1013 // the SynonymRelationship field on both is annotated with removeOrphan
1014 // so no further explicit deleting of the relationship should be done here
1015 taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
1016
1017 // --- DeleteSynonymsIfPossible
1018 if (config.isDeleteSynonymsIfPossible()){
1019 //TODO which value
1020 boolean newHomotypicGroupIfNeeded = true;
1021 SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
1022 deleteSynonym(synonym, taxon, synConfig);
1023 }
1024 // relationship will be deleted by hibernate automatically,
1025 // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797
1026 // else{
1027 // deleteSynonymRelationships(synonym, taxon);
1028 // }
1029 }
1030 }
1031
1032 // --- DeleteTaxonRelationships
1033 if (! config.isDeleteTaxonRelationships()){
1034 if (taxon.getTaxonRelations().size() > 0){
1035 String message = "Taxon can't be deleted as it is related to another taxon. " +
1036 "Remove taxon from all relations to other taxa prior to deletion.";
1037 // throw new ReferencedObjectUndeletableException(message);
1038 }
1039 } else{
1040 for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
1041 if (config.isDeleteMisappliedNamesAndInvalidDesignations()){
1042 if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){
1043 if (taxon.equals(taxRel.getToTaxon())){
1044 this.deleteTaxon(taxRel.getFromTaxon(), config, classification);
1045 }
1046 }
1047 }
1048 taxon.removeTaxonRelation(taxRel);
1049 /*if (taxFrom.equals(taxon)){
1050 try{
1051 this.deleteTaxon(taxTo, taxConf, classification);
1052 } catch(DataChangeNoRollbackException e){
1053 logger.debug("A related taxon will not be deleted." + e.getMessage());
1054 }
1055 } else {
1056 try{
1057 this.deleteTaxon(taxFrom, taxConf, classification);
1058 } catch(DataChangeNoRollbackException e){
1059 logger.debug("A related taxon will not be deleted." + e.getMessage());
1060 }
1061
1062 }*/
1063 }
1064 }
1065
1066 // TaxonDescription
1067 if (config.isDeleteDescriptions()){
1068 Set<TaxonDescription> descriptions = taxon.getDescriptions();
1069 List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1070 for (TaxonDescription desc: descriptions){
1071 //TODO use description delete configurator ?
1072 //FIXME check if description is ALWAYS deletable
1073 if (desc.getDescribedSpecimenOrObservation() != null){
1074 String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1075 " which also describes specimens or abservations";
1076 //throw new ReferencedObjectUndeletableException(message);
1077 }
1078 removeDescriptions.add(desc);
1079 descriptionService.delete(desc);
1080
1081 }
1082 for (TaxonDescription desc: removeDescriptions){
1083 taxon.removeDescription(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 nameResult = nameService.delete(name, config.getNameDeletionConfig());
1194 }
1195
1196 if (nameResult.isError()){
1197 //result.setError();
1198 result.addRelatedObject(name);
1199 result.addExceptions(nameResult.getExceptions());
1200 }
1201
1202 }
1203
1204 }
1205
1206 // TaxonDescription
1207 /* Set<TaxonDescription> descriptions = taxon.getDescriptions();
1208
1209 for (TaxonDescription desc: descriptions){
1210 if (config.isDeleteDescriptions()){
1211 //TODO use description delete configurator ?
1212 //FIXME check if description is ALWAYS deletable
1213 taxon.removeDescription(desc);
1214 descriptionService.delete(desc);
1215 }else{
1216 if (desc.getDescribedSpecimenOrObservations().size()>0){
1217 String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1218 " which also describes specimens or observations";
1219 throw new ReferencedObjectUndeletableException(message);
1220 }
1221 }
1222 }*/
1223
1224 if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) ){
1225 try{
1226 UUID uuid = dao.delete(taxon);
1227
1228 }catch(Exception e){
1229 result.addException(e);
1230 result.setError();
1231
1232 }
1233 } else {
1234 result.setError();
1235 result.addException(new Exception("The Taxon can't be deleted."));
1236
1237 }
1238 }
1239 // }else {
1240 // List<Exception> exceptions = new ArrayList<Exception>();
1241 // for (String message: referencedObjects){
1242 // ReferencedObjectUndeletableException exception = new ReferencedObjectUndeletableException(message);
1243 // exceptions.add(exception);
1244 // }
1245 // result.addExceptions(exceptions);
1246 // result.setError();
1247 //
1248 // }
1249 return result;
1250
1251 }
1252
1253 private String checkForReferences(Taxon taxon){
1254 Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1255 for (CdmBase referencingObject : referencingObjects){
1256 //IIdentificationKeys (Media, Polytomous, MultiAccess)
1257 if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1258 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";
1259
1260 return message;
1261 }
1262
1263
1264 /* //PolytomousKeyNode
1265 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1266 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1267 return message;
1268 }*/
1269
1270 //TaxonInteraction
1271 if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1272 String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1273 return message;
1274 }
1275
1276 //TaxonInteraction
1277 if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1278 String message = "Taxon can't be deleted as it is used in a determination event";
1279 return message;
1280 }
1281
1282 }
1283
1284 referencingObjects = null;
1285 return null;
1286 }
1287
1288 private boolean checkForPolytomousKeys(Taxon taxon){
1289 boolean result = false;
1290 List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1291 if (!list.isEmpty()) {
1292 result = true;
1293 }
1294 return result;
1295 }
1296
1297 @Transactional(readOnly = false)
1298 public UUID delete(Synonym syn){
1299 UUID result = syn.getUuid();
1300 this.deleteSynonym(syn, null);
1301 return result;
1302 }
1303
1304
1305
1306 /* (non-Javadoc)
1307 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1308 */
1309 @Transactional(readOnly = false)
1310 @Override
1311 public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1312 return deleteSynonym(synonym, null, config);
1313
1314 }
1315
1316
1317 /* (non-Javadoc)
1318 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1319 */
1320 @Transactional(readOnly = false)
1321 @Override
1322 public DeleteResult deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1323 DeleteResult result = new DeleteResult();
1324 if (synonym == null){
1325 result.setAbort();
1326 return result;
1327 }
1328
1329 if (config == null){
1330 config = new SynonymDeletionConfigurator();
1331 }
1332 result = isDeletable(synonym, config);
1333
1334
1335 if (result.isOk()){
1336 synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
1337
1338 //remove synonymRelationship
1339 Set<Taxon> taxonSet = new HashSet<Taxon>();
1340 if (taxon != null){
1341 taxonSet.add(taxon);
1342 }else{
1343 taxonSet.addAll(synonym.getAcceptedTaxa());
1344 }
1345 for (Taxon relatedTaxon : taxonSet){
1346 relatedTaxon = HibernateProxyHelper.deproxy(relatedTaxon, Taxon.class);
1347 relatedTaxon.removeSynonym(synonym, false);
1348 this.saveOrUpdate(relatedTaxon);
1349 }
1350 this.saveOrUpdate(synonym);
1351
1352 //TODO remove name from homotypical group?
1353
1354 //remove synonym (if necessary)
1355
1356 if (synonym.getSynonymRelations().isEmpty()){
1357 TaxonNameBase<?,?> name = synonym.getName();
1358 synonym.setName(null);
1359 dao.delete(synonym);
1360
1361 //remove name if possible (and required)
1362 if (name != null && config.isDeleteNameIfPossible()){
1363
1364 DeleteResult nameDeleteresult = nameService.delete(name, config.getNameDeletionConfig());
1365 if (nameDeleteresult.isAbort()){
1366 result.addExceptions(nameDeleteresult.getExceptions());
1367 result.addUpdatedObject(name);
1368 }
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
1395 /* (non-Javadoc)
1396 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNameIds(java.util.List)
1397 */
1398 @Override
1399 public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1400
1401 return this.dao.findIdenticalNamesNew(propertyPath);
1402 }
1403
1404 /* (non-Javadoc)
1405 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getPhylumName(eu.etaxonomy.cdm.model.name.TaxonNameBase)
1406 */
1407 @Override
1408 public String getPhylumName(TaxonNameBase name){
1409 return this.dao.getPhylumName(name);
1410 }
1411
1412 /* (non-Javadoc)
1413 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
1414 */
1415 @Override
1416 public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1417 return dao.deleteSynonymRelationships(syn, taxon);
1418 }
1419
1420 /* (non-Javadoc)
1421 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym)
1422 */
1423 @Override
1424 public long deleteSynonymRelationships(Synonym syn) {
1425 return dao.deleteSynonymRelationships(syn, null);
1426 }
1427
1428
1429 /* (non-Javadoc)
1430 * @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)
1431 */
1432 @Override
1433 public List<SynonymRelationship> listSynonymRelationships(
1434 TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1435 List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1436 Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1437
1438 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1439 if(numberOfResults > 0) { // no point checking again
1440 results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1441 }
1442 return results;
1443 }
1444
1445 /* (non-Javadoc)
1446 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingTaxon(java.lang.String)
1447 */
1448 @Override
1449 public Taxon findBestMatchingTaxon(String taxonName) {
1450 MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1451 config.setTaxonNameTitle(taxonName);
1452 return findBestMatchingTaxon(config);
1453 }
1454
1455
1456
1457 @Override
1458 public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1459
1460 Taxon bestCandidate = null;
1461 try{
1462 // 1. search for acceptet taxa
1463 List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1464 boolean bestCandidateMatchesSecUuid = false;
1465 boolean bestCandidateIsInClassification = false;
1466 int countEqualCandidates = 0;
1467 for(TaxonBase taxonBaseCandidate : taxonList){
1468 if(taxonBaseCandidate instanceof Taxon){
1469 Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1470 boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1471 if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1472 continue;
1473 }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1474 bestCandidate = newCanditate;
1475 countEqualCandidates = 1;
1476 bestCandidateMatchesSecUuid = true;
1477 continue;
1478 }
1479
1480 boolean newCandidateInClassification = isInClassification(newCanditate, config);
1481 if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1482 continue;
1483 }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1484 bestCandidate = newCanditate;
1485 countEqualCandidates = 1;
1486 bestCandidateIsInClassification = true;
1487 continue;
1488 }
1489 if (bestCandidate == null){
1490 bestCandidate = newCanditate;
1491 countEqualCandidates = 1;
1492 continue;
1493 }
1494
1495 }else{ //not Taxon.class
1496 continue;
1497 }
1498 countEqualCandidates++;
1499
1500 }
1501 if (bestCandidate != null){
1502 if(countEqualCandidates > 1){
1503 logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1504 return bestCandidate;
1505 } else {
1506 logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1507 return bestCandidate;
1508 }
1509 }
1510
1511
1512 // 2. search for synonyms
1513 if (config.isIncludeSynonyms()){
1514 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1515 for(TaxonBase taxonBase : synonymList){
1516 if(taxonBase instanceof Synonym){
1517 Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1518 Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1519 if(!acceptetdCandidates.isEmpty()){
1520 bestCandidate = acceptetdCandidates.iterator().next();
1521 if(acceptetdCandidates.size() == 1){
1522 logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1523 return bestCandidate;
1524 } else {
1525 logger.info("using accepted Taxon " + bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1526 return bestCandidate;
1527 }
1528 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1529 }
1530 }
1531 }
1532 }
1533
1534 } catch (Exception e){
1535 logger.error(e);
1536 e.printStackTrace();
1537 }
1538
1539 return bestCandidate;
1540 }
1541
1542 private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1543 UUID configClassificationUuid = config.getClassificationUuid();
1544 if (configClassificationUuid == null){
1545 return false;
1546 }
1547 for (TaxonNode node : taxon.getTaxonNodes()){
1548 UUID classUuid = node.getClassification().getUuid();
1549 if (configClassificationUuid.equals(classUuid)){
1550 return true;
1551 }
1552 }
1553 return false;
1554 }
1555
1556 private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1557 UUID configSecUuid = config.getSecUuid();
1558 if (configSecUuid == null){
1559 return false;
1560 }
1561 UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1562 return configSecUuid.equals(taxonSecUuid);
1563 }
1564
1565 /* (non-Javadoc)
1566 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingSynonym(java.lang.String)
1567 */
1568 @Override
1569 public Synonym findBestMatchingSynonym(String taxonName) {
1570 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1571 if(! synonymList.isEmpty()){
1572 Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1573 if(synonymList.size() == 1){
1574 logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1575 return result;
1576 } else {
1577 logger.info("Several matching synonyms found. Using first: " + result.getTitleCache());
1578 return result;
1579 }
1580 }
1581 return null;
1582 }
1583
1584
1585 /* (non-Javadoc)
1586 * @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)
1587 */
1588 @Override
1589 public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, Taxon newTaxon, boolean moveHomotypicGroup,
1590 SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
1591
1592 Synonym synonym = oldSynonymRelation.getSynonym();
1593 Taxon fromTaxon = oldSynonymRelation.getAcceptedTaxon();
1594 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1595 TaxonNameBase<?,?> synonymName = synonym.getName();
1596 TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1597 //set default relationship type
1598 if (newSynonymRelationshipType == null){
1599 newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1600 }
1601 boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1602
1603 HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1604 int hgSize = homotypicGroup.getTypifiedNames().size();
1605 boolean isSingleInGroup = !(hgSize > 1);
1606
1607 if (! isSingleInGroup){
1608 boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1609 boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1610 if (isHomotypicToAccepted){
1611 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.";
1612 String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1613 message = String.format(message, homotypicRelatives);
1614 throw new HomotypicalGroupChangeException(message);
1615 }
1616 if (! moveHomotypicGroup){
1617 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.";
1618 throw new HomotypicalGroupChangeException(message);
1619 }
1620 }else{
1621 moveHomotypicGroup = true; //single synonym always allows to moveCompleteGroup
1622 }
1623 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1624
1625 SynonymRelationship result = null;
1626 //move all synonyms to new taxon
1627 List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1628 for (Synonym syn: homotypicSynonyms){
1629 Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1630 for (SynonymRelationship synRelation : synRelations){
1631 if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1632 Reference<?> newReference = reference;
1633 if (newReference == null && keepReference){
1634 newReference = synRelation.getCitation();
1635 }
1636 String newRefDetail = referenceDetail;
1637 if (newRefDetail == null && keepReference){
1638 newRefDetail = synRelation.getCitationMicroReference();
1639 }
1640 newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
1641 fromTaxon = HibernateProxyHelper.deproxy(fromTaxon, Taxon.class);
1642 SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1643 fromTaxon.removeSynonymRelation(synRelation, false);
1644 //
1645 //change homotypic group of synonym if relType is 'homotypic'
1646 // if (newRelTypeIsHomotypic){
1647 // newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1648 // }
1649 //set result
1650 if (synRelation.equals(oldSynonymRelation)){
1651 result = newSynRelation;
1652 }
1653 }
1654 }
1655
1656 }
1657 saveOrUpdate(fromTaxon);
1658 saveOrUpdate(newTaxon);
1659 //Assert that there is a result
1660 if (result == null){
1661 String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1662 throw new IllegalStateException(message);
1663 }
1664 return result;
1665 }
1666
1667 /* (non-Javadoc)
1668 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheTaxon()
1669 */
1670 @Override
1671 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1672 return dao.getUuidAndTitleCacheTaxon();
1673 }
1674
1675 /* (non-Javadoc)
1676 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheSynonym()
1677 */
1678 @Override
1679 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1680 return dao.getUuidAndTitleCacheSynonym();
1681 }
1682
1683 /* (non-Javadoc)
1684 * @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)
1685 */
1686 @Override
1687 public Pager<SearchResult<TaxonBase>> findByFullText(
1688 Class<? extends TaxonBase> clazz, String queryString,
1689 Classification classification, List<Language> languages,
1690 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1691
1692
1693 LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1694
1695 // --- execute search
1696 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1697
1698 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1699 idFieldMap.put(CdmBaseType.TAXON, "id");
1700
1701 // --- initialize taxa, thighlight matches ....
1702 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1703 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1704 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1705
1706 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1707 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1708 }
1709
1710 @Override
1711 public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
1712 Classification classification,
1713 Integer pageSize, Integer pageNumber,
1714 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1715
1716 LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1717
1718 // --- execute search
1719 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1720
1721 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1722 idFieldMap.put(CdmBaseType.TAXON, "id");
1723
1724 // --- initialize taxa, thighlight matches ....
1725 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1726 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1727 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1728
1729 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1730 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1731 }
1732
1733 /**
1734 * @param clazz
1735 * @param queryString
1736 * @param classification
1737 * @param languages
1738 * @param highlightFragments
1739 * @param sortFields TODO
1740 * @param directorySelectClass
1741 * @return
1742 */
1743 protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1744 boolean highlightFragments, SortField[] sortFields) {
1745 BooleanQuery finalQuery = new BooleanQuery();
1746 BooleanQuery textQuery = new BooleanQuery();
1747
1748 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1749 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1750
1751 if(sortFields == null){
1752 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1753 }
1754 luceneSearch.setSortFields(sortFields);
1755
1756 // ---- search criteria
1757 luceneSearch.setCdmTypRestriction(clazz);
1758
1759 if(!queryString.isEmpty() && !queryString.equals("*") && !queryString.equals("?") ) {
1760 textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1761 textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1762 }
1763
1764 if(textQuery.getClauses().length > 0) {
1765 finalQuery.add(textQuery, Occur.MUST);
1766 }
1767
1768
1769 if(classification != null){
1770 finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1771 }
1772 luceneSearch.setQuery(finalQuery);
1773
1774 if(highlightFragments){
1775 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1776 }
1777 return luceneSearch;
1778 }
1779
1780 /**
1781 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1782 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1783 * drawback of requiring to do the join an indexing time.
1784 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1785 *
1786 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1787 * <ul>
1788 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1789 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1790 * <ul>
1791 * @param queryString
1792 * @param classification
1793 * @param languages
1794 * @param highlightFragments
1795 * @param sortFields TODO
1796 *
1797 * @return
1798 * @throws IOException
1799 */
1800 protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1801 boolean highlightFragments, SortField[] sortFields) throws IOException {
1802
1803 String fromField;
1804 String queryTermField;
1805 String toField = "id"; // TaxonBase.uuid
1806
1807 if(edge.isBidirectional()){
1808 throw new RuntimeException("Bidirectional joining not supported!");
1809 }
1810 if(edge.isEvers()){
1811 fromField = "relatedFrom.id";
1812 queryTermField = "relatedFrom.titleCache";
1813 } else if(edge.isInvers()) {
1814 fromField = "relatedTo.id";
1815 queryTermField = "relatedTo.titleCache";
1816 } else {
1817 throw new RuntimeException("Invalid direction: " + edge.getDirections());
1818 }
1819
1820 BooleanQuery finalQuery = new BooleanQuery();
1821
1822 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1823 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1824
1825 BooleanQuery joinFromQuery = new BooleanQuery();
1826 joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1827 joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1828 Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1829
1830 if(sortFields == null){
1831 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1832 }
1833 luceneSearch.setSortFields(sortFields);
1834
1835 finalQuery.add(joinQuery, Occur.MUST);
1836
1837 if(classification != null){
1838 finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1839 }
1840 luceneSearch.setQuery(finalQuery);
1841
1842 if(highlightFragments){
1843 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1844 }
1845 return luceneSearch;
1846 }
1847
1848
1849
1850
1851 /* (non-Javadoc)
1852 * @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)
1853 */
1854 @Override
1855 public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1856 EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1857 Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
1858 boolean highlightFragments, Integer pageSize,
1859 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1860 throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1861
1862 // FIXME: allow taxonomic ordering
1863 // 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";
1864 // this require building a special sort column by a special classBridge
1865 if(highlightFragments){
1866 logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1867 "currently not fully supported by this method and thus " +
1868 "may not work with common names and misapplied names.");
1869 }
1870
1871 // convert sets to lists
1872 List<NamedArea> namedAreaList = null;
1873 List<PresenceAbsenceTerm>distributionStatusList = null;
1874 if(namedAreas != null){
1875 namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1876 namedAreaList.addAll(namedAreas);
1877 }
1878 if(distributionStatus != null){
1879 distributionStatusList = new ArrayList<PresenceAbsenceTerm>(distributionStatus.size());
1880 distributionStatusList.addAll(distributionStatus);
1881 }
1882
1883 // set default if parameter is null
1884 if(searchModes == null){
1885 searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1886 }
1887
1888 // set sort order and thus override any sort orders which may have been
1889 // defindes by prepare*Search methods
1890 if(orderHints == null){
1891 orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER;
1892 }
1893 SortField[] sortFields = new SortField[orderHints.size()];
1894 int i = 0;
1895 for(OrderHint oh : orderHints){
1896 sortFields[i++] = oh.toSortField();
1897 }
1898 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1899 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1900
1901
1902 boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1903
1904 List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1905 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1906
1907 /*
1908 ======== filtering by distribution , HOWTO ========
1909
1910 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1911 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1912 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1913 which will be put into a FilteredQuersy in the end ?
1914
1915
1916 3. how does it work in spatial?
1917 see
1918 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1919 - http://www.infoq.com/articles/LuceneSpatialSupport
1920 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1921 ------------------------------------------------------------------------
1922
1923 filter strategies:
1924 A) use a separate distribution filter per index sub-query/search:
1925 - byTaxonSyonym (query TaxaonBase):
1926 use a join area filter (Distribution -> TaxonBase)
1927 - byCommonName (query DescriptionElementBase): use an area filter on
1928 DescriptionElementBase !!! PROBLEM !!!
1929 This cannot work since the distributions are different entities than the
1930 common names and thus these are different lucene documents.
1931 - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1932 use a join area filter (Distribution -> TaxonBase)
1933
1934 B) use a common distribution filter for all index sub-query/searches:
1935 - use a common join area filter (Distribution -> TaxonBase)
1936 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1937 PROBLEM in this case: we are losing the fragment highlighting for the
1938 common names, since the returned documents are always TaxonBases
1939 */
1940
1941 /* The QueryFactory for creating filter queries on Distributions should
1942 * The query factory used for the common names query cannot be reused
1943 * for this case, since we want to only record the text fields which are
1944 * actually used in the primary query
1945 */
1946 QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1947
1948 BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1949
1950
1951 // search for taxa or synonyms
1952 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1953 Class taxonBaseSubclass = TaxonBase.class;
1954 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1955 taxonBaseSubclass = Taxon.class;
1956 } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1957 taxonBaseSubclass = Synonym.class;
1958 }
1959 luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1960 idFieldMap.put(CdmBaseType.TAXON, "id");
1961 /* A) does not work!!!!
1962 if(addDistributionFilter){
1963 // in this case we need a filter which uses a join query
1964 // to get the TaxonBase documents for the DescriptionElementBase documents
1965 // which are matching the areas in question
1966 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1967 namedAreaList,
1968 distributionStatusList,
1969 distributionFilterQueryFactory
1970 );
1971 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1972 }
1973 */
1974 if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1975 // add additional area filter for synonyms
1976 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1977 String toField = "accTaxon.id"; // id in TaxonBase index
1978
1979 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1980
1981 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1982 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1983
1984 }
1985 }
1986
1987 // search by CommonTaxonName
1988 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1989 // B)
1990 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1991 Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1992 "inDescription.taxon.id",
1993 "id",
1994 QueryFactory.addTypeRestriction(
1995 createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
1996 , CommonTaxonName.class
1997 ),
1998 CommonTaxonName.class);
1999 logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
2000 LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2001 byCommonNameSearch.setCdmTypRestriction(Taxon.class);
2002 byCommonNameSearch.setQuery(byCommonNameJoinQuery);
2003 byCommonNameSearch.setSortFields(sortFields);
2004 idFieldMap.put(CdmBaseType.TAXON, "id");
2005
2006 luceneSearches.add(byCommonNameSearch);
2007
2008 /* A) does not work!!!!
2009 luceneSearches.add(
2010 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
2011 queryString, classification, null, languages, highlightFragments)
2012 );
2013 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2014 if(addDistributionFilter){
2015 // in this case we are able to use DescriptionElementBase documents
2016 // which are matching the areas in question directly
2017 BooleanQuery byDistributionQuery = createByDistributionQuery(
2018 namedAreaList,
2019 distributionStatusList,
2020 distributionFilterQueryFactory
2021 );
2022 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
2023 } */
2024 }
2025
2026 // search by misapplied names
2027 if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
2028 // NOTE:
2029 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
2030 // which allows doing query time joins
2031 // finds the misapplied name (Taxon B) which is an misapplication for
2032 // a related Taxon A.
2033 //
2034 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
2035 new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
2036 queryString, classification, languages, highlightFragments, sortFields));
2037 idFieldMap.put(CdmBaseType.TAXON, "id");
2038
2039 if(addDistributionFilter){
2040 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2041
2042 /*
2043 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
2044 * Maybe this is a but in java itself java.
2045 *
2046 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2047 * directly:
2048 *
2049 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2050 *
2051 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2052 * will execute as expected:
2053 *
2054 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2055 * String toField = "relation." + misappliedNameForUuid +".to.id";
2056 *
2057 * Comparing both strings by the String.equals method returns true, so both String are identical.
2058 *
2059 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2060 * 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)
2061 * The bug is persistent after a reboot of the development computer.
2062 */
2063 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2064 // String toField = "relation." + misappliedNameForUuid +".to.id";
2065 String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2066 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2067 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2068
2069 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2070 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2071 QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery);
2072
2073 // debug code for bug described above
2074 DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2075 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2076
2077 multiIndexByAreaFilter.add(filter, Occur.SHOULD);
2078 }
2079 }
2080
2081 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
2082 luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
2083
2084
2085 if(addDistributionFilter){
2086
2087 // B)
2088 // in this case we need a filter which uses a join query
2089 // to get the TaxonBase documents for the DescriptionElementBase documents
2090 // which are matching the areas in question
2091 //
2092 // for toTaxa, doByCommonName
2093 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2094 namedAreaList,
2095 distributionStatusList,
2096 distributionFilterQueryFactory
2097 );
2098 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
2099 }
2100
2101 if (addDistributionFilter){
2102 multiSearch.setFilter(multiIndexByAreaFilter);
2103 }
2104
2105
2106 // --- execute search
2107 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2108
2109 // --- initialize taxa, highlight matches ....
2110 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2111
2112
2113 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2114 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2115
2116 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2117 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2118 }
2119
2120 /**
2121 * @param namedAreaList at least one area must be in the list
2122 * @param distributionStatusList optional
2123 * @return
2124 * @throws IOException
2125 */
2126 protected Query createByDistributionJoinQuery(
2127 List<NamedArea> namedAreaList,
2128 List<PresenceAbsenceTerm> distributionStatusList,
2129 QueryFactory queryFactory
2130 ) throws IOException {
2131
2132 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2133 String toField = "id"; // id in TaxonBase index
2134
2135 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2136
2137 Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2138
2139 return taxonAreaJoinQuery;
2140 }
2141
2142 /**
2143 * @param namedAreaList
2144 * @param distributionStatusList
2145 * @param queryFactory
2146 * @return
2147 */
2148 private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2149 List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2150 BooleanQuery areaQuery = new BooleanQuery();
2151 // area field from Distribution
2152 areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2153
2154 // status field from Distribution
2155 if(distributionStatusList != null && distributionStatusList.size() > 0){
2156 areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2157 }
2158
2159 logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2160 return areaQuery;
2161 }
2162
2163 /**
2164 * This method has been primarily created for testing the area join query but might
2165 * also be useful in other situations
2166 *
2167 * @param namedAreaList
2168 * @param distributionStatusList
2169 * @param classification
2170 * @param highlightFragments
2171 * @return
2172 * @throws IOException
2173 */
2174 protected LuceneSearch prepareByDistributionSearch(
2175 List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
2176 Classification classification) throws IOException {
2177
2178 BooleanQuery finalQuery = new BooleanQuery();
2179
2180 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2181
2182 // FIXME is this query factory using the wrong type?
2183 QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2184
2185 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
2186 luceneSearch.setSortFields(sortFields);
2187
2188
2189 Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory);
2190
2191 finalQuery.add(byAreaQuery, Occur.MUST);
2192
2193 if(classification != null){
2194 finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2195 }
2196
2197 logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2198 luceneSearch.setQuery(finalQuery);
2199
2200 return luceneSearch;
2201 }
2202
2203
2204
2205 /* (non-Javadoc)
2206 * @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)
2207 */
2208 @Override
2209 public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2210 Class<? extends DescriptionElementBase> clazz, String queryString,
2211 Classification classification, List<Feature> features, List<Language> languages,
2212 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2213
2214
2215 LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2216
2217 // --- execute search
2218 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2219
2220 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2221 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2222
2223 // --- initialize taxa, highlight matches ....
2224 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2225 @SuppressWarnings("rawtypes")
2226 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2227 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2228
2229 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2230 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2231
2232 }
2233
2234
2235 @Override
2236 public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2237 Classification classification, List<Language> languages, boolean highlightFragments,
2238 Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2239
2240 LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2241 LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2242
2243 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2244
2245 // --- execute search
2246 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2247
2248 // --- initialize taxa, highlight matches ....
2249 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2250
2251 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2252 idFieldMap.put(CdmBaseType.TAXON, "id");
2253 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2254
2255 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2256 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2257
2258 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2259 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2260
2261 }
2262
2263
2264 /**
2265 * @param clazz
2266 * @param queryString
2267 * @param classification
2268 * @param features
2269 * @param languages
2270 * @param highlightFragments
2271 * @param directorySelectClass
2272 * @return
2273 */
2274 protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2275 String queryString, Classification classification, List<Feature> features,
2276 List<Language> languages, boolean highlightFragments) {
2277
2278 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2279 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2280
2281 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.STRING, false)};
2282
2283 BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2284 languages, descriptionElementQueryFactory);
2285
2286 luceneSearch.setSortFields(sortFields);
2287 luceneSearch.setCdmTypRestriction(clazz);
2288 luceneSearch.setQuery(finalQuery);
2289 if(highlightFragments){
2290 luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2291 }
2292
2293 return luceneSearch;
2294 }
2295
2296 /**
2297 * @param queryString
2298 * @param classification
2299 * @param features
2300 * @param languages
2301 * @param descriptionElementQueryFactory
2302 * @return
2303 */
2304 private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2305 List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2306 BooleanQuery finalQuery = new BooleanQuery();
2307 BooleanQuery textQuery = new BooleanQuery();
2308 textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2309
2310 // common name
2311 Query nameQuery;
2312 if(languages == null || languages.size() == 0){
2313 nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString);
2314 } else {
2315 nameQuery = new BooleanQuery();
2316 BooleanQuery languageSubQuery = new BooleanQuery();
2317 for(Language lang : languages){
2318 languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD);
2319 }
2320 ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2321 ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
2322 }
2323 textQuery.add(nameQuery, Occur.SHOULD);
2324
2325
2326 // text field from TextData
2327 textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2328
2329 // --- TermBase fields - by representation ----
2330 // state field from CategoricalData
2331 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2332
2333 // state field from CategoricalData
2334 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2335
2336 // area field from Distribution
2337 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2338
2339 // status field from Distribution
2340 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2341
2342 finalQuery.add(textQuery, Occur.MUST);
2343 // --- classification ----
2344
2345 if(classification != null){
2346 finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2347 }
2348
2349 // --- IdentifieableEntity fields - by uuid
2350 if(features != null && features.size() > 0 ){
2351 finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2352 }
2353
2354 // the description must be associated with a taxon
2355 finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2356
2357 logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2358 return finalQuery;
2359 }
2360
2361 /**
2362 * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2363 * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2364 * This method is a convenient means to retrieve a Lucene query string for such the fields.
2365 *
2366 * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2367 * or {@link MultilanguageTextFieldBridge }
2368 * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2369 * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2370 * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2371 *
2372 * TODO move to utiliy class !!!!!!!!
2373 */
2374 private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2375
2376 if(stringBuilder == null){
2377 stringBuilder = new StringBuilder();
2378 }
2379 if(languages == null || languages.size() == 0){
2380 stringBuilder.append(name + ".ALL:(%1$s) ");
2381 } else {
2382 for(Language lang : languages){
2383 stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2384 }
2385 }
2386 return stringBuilder;
2387 }
2388
2389 @Override
2390 public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2391 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2392 List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2393
2394 HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2395
2396
2397 UUID nameUuid= taxon.getName().getUuid();
2398 ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2399 String epithetOfTaxon = null;
2400 String infragenericEpithetOfTaxon = null;
2401 String infraspecificEpithetOfTaxon = null;
2402 if (taxonName.isSpecies()){
2403 epithetOfTaxon= taxonName.getSpecificEpithet();
2404 } else if (taxonName.isInfraGeneric()){
2405 infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2406 } else if (taxonName.isInfraSpecific()){
2407 infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2408 }
2409 String genusOfTaxon = taxonName.getGenusOrUninomial();
2410 Set<TaxonNode> nodes = taxon.getTaxonNodes();
2411 List<String> taxonNames = new ArrayList<String>();
2412
2413 for (TaxonNode node: nodes){
2414 // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2415 // List<String> synonymsEpithet = new ArrayList<String>();
2416
2417 if (node.getClassification().equals(classification)){
2418 if (!node.isTopmostNode()){
2419 TaxonNode parent = node.getParent();
2420 parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
2421 TaxonNameBase<?,?> parentName = parent.getTaxon().getName();
2422 ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2423 Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
2424 Rank rankOfTaxon = taxonName.getRank();
2425
2426
2427 //create inferred synonyms for species, subspecies
2428 if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2429
2430 Synonym inferredEpithet = null;
2431 Synonym inferredGenus = null;
2432 Synonym potentialCombination = null;
2433
2434 List<String> propertyPaths = new ArrayList<String>();
2435 propertyPaths.add("synonym");
2436 propertyPaths.add("synonym.name");
2437 List<OrderHint> orderHints = new ArrayList<OrderHint>();
2438 orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2439
2440 List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2441 List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2442
2443 List<TaxonRelationship> taxonRelListParent = null;
2444 List<TaxonRelationship> taxonRelListTaxon = null;
2445 if (doWithMisappliedNames){
2446 taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2447 taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2448 }
2449
2450
2451 if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2452
2453
2454 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2455 Synonym syn = synonymRelationOfParent.getSynonym();
2456
2457 inferredEpithet = createInferredEpithets(taxon,
2458 zooHashMap, taxonName, epithetOfTaxon,
2459 infragenericEpithetOfTaxon,
2460 infraspecificEpithetOfTaxon,
2461 taxonNames, parentName,
2462 syn);
2463
2464
2465 inferredSynonyms.add(inferredEpithet);
2466 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2467 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2468 }
2469
2470 if (doWithMisappliedNames){
2471
2472 for (TaxonRelationship taxonRelationship: taxonRelListParent){
2473 Taxon misappliedName = taxonRelationship.getFromTaxon();
2474
2475 inferredEpithet = createInferredEpithets(taxon,
2476 zooHashMap, taxonName, epithetOfTaxon,
2477 infragenericEpithetOfTaxon,
2478 infraspecificEpithetOfTaxon,
2479 taxonNames, parentName,
2480 misappliedName);
2481
2482 inferredSynonyms.add(inferredEpithet);
2483 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2484 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2485 }
2486 }
2487
2488 if (!taxonNames.isEmpty()){
2489 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2490 ZoologicalName name;
2491 if (!synNotInCDM.isEmpty()){
2492 inferredSynonymsToBeRemoved.clear();
2493
2494 for (Synonym syn :inferredSynonyms){
2495 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2496 if (!synNotInCDM.contains(name.getNameCache())){
2497 inferredSynonymsToBeRemoved.add(syn);
2498 }
2499 }
2500
2501 // Remove identified Synonyms from inferredSynonyms
2502 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2503 inferredSynonyms.remove(synonym);
2504 }
2505 }
2506 }
2507
2508 }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2509
2510
2511 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2512 TaxonNameBase synName;
2513 ZoologicalName inferredSynName;
2514
2515 Synonym syn = synonymRelationOfTaxon.getSynonym();
2516 inferredGenus = createInferredGenus(taxon,
2517 zooHashMap, taxonName, epithetOfTaxon,
2518 genusOfTaxon, taxonNames, zooParentName, syn);
2519
2520 inferredSynonyms.add(inferredGenus);
2521 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2522 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2523
2524
2525 }
2526
2527 if (doWithMisappliedNames){
2528
2529 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2530 Taxon misappliedName = taxonRelationship.getFromTaxon();
2531 inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName, misappliedName);
2532
2533 inferredSynonyms.add(inferredGenus);
2534 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2535 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2536 }
2537 }
2538
2539
2540 if (!taxonNames.isEmpty()){
2541 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2542 ZoologicalName name;
2543 if (!synNotInCDM.isEmpty()){
2544 inferredSynonymsToBeRemoved.clear();
2545
2546 for (Synonym syn :inferredSynonyms){
2547 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2548 if (!synNotInCDM.contains(name.getNameCache())){
2549 inferredSynonymsToBeRemoved.add(syn);
2550 }
2551 }
2552
2553 // Remove identified Synonyms from inferredSynonyms
2554 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2555 inferredSynonyms.remove(synonym);
2556 }
2557 }
2558 }
2559
2560 }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2561
2562 Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2563 ZoologicalName inferredSynName;
2564 //for all synonyms of the parent...
2565 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2566 TaxonNameBase synName;
2567 Synonym synParent = synonymRelationOfParent.getSynonym();
2568 synName = synParent.getName();
2569
2570 HibernateProxyHelper.deproxy(synParent);
2571
2572 // Set the sourceReference
2573 sourceReference = synParent.getSec();
2574
2575 // Determine the idInSource
2576 String idInSourceParent = getIdInSource(synParent);
2577
2578 ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2579 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2580 String synParentInfragenericName = null;
2581 String synParentSpecificEpithet = null;
2582
2583 if (parentSynZooName.isInfraGeneric()){
2584 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2585 }
2586 if (parentSynZooName.isSpecies()){
2587 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2588 }
2589
2590 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2591 synonymsGenus.put(synGenusName, idInSource);
2592 }*/
2593
2594 //for all synonyms of the taxon
2595
2596 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2597
2598 Synonym syn = synonymRelationOfTaxon.getSynonym();
2599 ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2600 potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2601 synParentGenus,
2602 synParentInfragenericName,
2603 synParentSpecificEpithet, syn, zooHashMap);
2604
2605 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2606 inferredSynonyms.add(potentialCombination);
2607 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2608 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2609
2610 }
2611
2612
2613 }
2614
2615 if (doWithMisappliedNames){
2616
2617 for (TaxonRelationship parentRelationship: taxonRelListParent){
2618
2619 TaxonNameBase misappliedParentName;
2620
2621 Taxon misappliedParent = parentRelationship.getFromTaxon();
2622 misappliedParentName = misappliedParent.getName();
2623
2624 HibernateProxyHelper.deproxy(misappliedParent);
2625
2626 // Set the sourceReference
2627 sourceReference = misappliedParent.getSec();
2628
2629 // Determine the idInSource
2630 String idInSourceParent = getIdInSource(misappliedParent);
2631
2632 ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2633 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2634 String synParentInfragenericName = null;
2635 String synParentSpecificEpithet = null;
2636
2637 if (parentSynZooName.isInfraGeneric()){
2638 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2639 }
2640 if (parentSynZooName.isSpecies()){
2641 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2642 }
2643
2644
2645 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2646 Taxon misappliedName = taxonRelationship.getFromTaxon();
2647 ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2648 potentialCombination = createPotentialCombination(
2649 idInSourceParent, parentSynZooName, zooMisappliedName,
2650 synParentGenus,
2651 synParentInfragenericName,
2652 synParentSpecificEpithet, misappliedName, zooHashMap);
2653
2654
2655 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2656 inferredSynonyms.add(potentialCombination);
2657 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2658 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2659 }
2660 }
2661 }
2662
2663 if (!taxonNames.isEmpty()){
2664 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2665 ZoologicalName name;
2666 if (!synNotInCDM.isEmpty()){
2667 inferredSynonymsToBeRemoved.clear();
2668 for (Synonym syn :inferredSynonyms){
2669 try{
2670 name = (ZoologicalName) syn.getName();
2671 }catch (ClassCastException e){
2672 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2673 }
2674 if (!synNotInCDM.contains(name.getNameCache())){
2675 inferredSynonymsToBeRemoved.add(syn);
2676 }
2677 }
2678 // Remove identified Synonyms from inferredSynonyms
2679 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2680 inferredSynonyms.remove(synonym);
2681 }
2682 }
2683 }
2684 }
2685 }else {
2686 logger.info("The synonymrelationship type is not defined.");
2687 return inferredSynonyms;
2688 }
2689 }
2690 }
2691
2692 }
2693
2694 return inferredSynonyms;
2695 }
2696
2697 private Synonym createPotentialCombination(String idInSourceParent,
2698 ZoologicalName parentSynZooName, ZoologicalName zooSynName, String synParentGenus,
2699 String synParentInfragenericName, String synParentSpecificEpithet,
2700 TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2701 Synonym potentialCombination;
2702 Reference sourceReference;
2703 ZoologicalName inferredSynName;
2704 HibernateProxyHelper.deproxy(syn);
2705
2706 // Set sourceReference
2707 sourceReference = syn.getSec();
2708 if (sourceReference == null){
2709 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2710 //TODO:Remove
2711 if (!parentSynZooName.getTaxa().isEmpty()){
2712 TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2713
2714 sourceReference = taxon.getSec();
2715 }
2716 }
2717 String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2718
2719 String synTaxonInfraSpecificName= null;
2720
2721 if (parentSynZooName.isSpecies()){
2722 synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2723 }
2724
2725 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2726 synonymsEpithet.add(epithetName);
2727 }*/
2728
2729 //create potential combinations...
2730 inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2731
2732 inferredSynName.setGenusOrUninomial(synParentGenus);
2733 if (zooSynName.isSpecies()){
2734 inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2735 if (parentSynZooName.isInfraGeneric()){
2736 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2737 }
2738 }
2739 if (zooSynName.isInfraSpecific()){
2740 inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2741 inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2742 }
2743 if (parentSynZooName.isInfraGeneric()){
2744 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2745 }
2746
2747
2748 potentialCombination = Synonym.NewInstance(inferredSynName, null);
2749
2750 // Set the sourceReference
2751 potentialCombination.setSec(sourceReference);
2752
2753
2754 // Determine the idInSource
2755 String idInSourceSyn= getIdInSource(syn);
2756
2757 if (idInSourceParent != null && idInSourceSyn != null) {
2758 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2759 inferredSynName.addSource(originalSource);
2760 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2761 potentialCombination.addSource(originalSource);
2762 }
2763
2764 return potentialCombination;
2765 }
2766
2767 private Synonym createInferredGenus(Taxon taxon,
2768 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2769 String epithetOfTaxon, String genusOfTaxon,
2770 List<String> taxonNames, ZoologicalName zooParentName,
2771 TaxonBase syn) {
2772
2773 Synonym inferredGenus;
2774 TaxonNameBase synName;
2775 ZoologicalName inferredSynName;
2776 synName =syn.getName();
2777 HibernateProxyHelper.deproxy(syn);
2778
2779 // Determine the idInSource
2780 String idInSourceSyn = getIdInSource(syn);
2781 String idInSourceTaxon = getIdInSource(taxon);
2782 // Determine the sourceReference
2783 Reference sourceReference = syn.getSec();
2784
2785 //logger.warn(sourceReference.getTitleCache());
2786
2787 synName = syn.getName();
2788 ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2789 String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2790 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2791 synonymsEpithet.add(synSpeciesEpithetName);
2792 }*/
2793
2794 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2795 //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...
2796
2797
2798 inferredSynName.setGenusOrUninomial(genusOfTaxon);
2799 if (zooParentName.isInfraGeneric()){
2800 inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2801 }
2802
2803 if (taxonName.isSpecies()){
2804 inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2805 }
2806 if (taxonName.isInfraSpecific()){
2807 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2808 inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2809 }
2810
2811
2812 inferredGenus = Synonym.NewInstance(inferredSynName, null);
2813
2814 // Set the sourceReference
2815 inferredGenus.setSec(sourceReference);
2816
2817 // Add the original source
2818 if (idInSourceSyn != null && idInSourceTaxon != null) {
2819 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2820 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2821 inferredGenus.addSource(originalSource);
2822
2823 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2824 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2825 inferredSynName.addSource(originalSource);
2826 originalSource = null;
2827
2828 }else{
2829 logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2830 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2831 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2832 inferredGenus.addSource(originalSource);
2833
2834 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2835 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2836 inferredSynName.addSource(originalSource);
2837 originalSource = null;
2838 }
2839
2840 taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2841
2842 return inferredGenus;
2843 }
2844
2845 private Synonym createInferredEpithets(Taxon taxon,
2846 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2847 String epithetOfTaxon, String infragenericEpithetOfTaxon,
2848 String infraspecificEpithetOfTaxon, List<String> taxonNames,
2849 TaxonNameBase parentName, TaxonBase syn) {
2850
2851 Synonym inferredEpithet;
2852 TaxonNameBase<?,?> synName;
2853 ZoologicalName inferredSynName;
2854 HibernateProxyHelper.deproxy(syn);
2855
2856 // Determine the idInSource
2857 String idInSourceSyn = getIdInSource(syn);
2858 String idInSourceTaxon = getIdInSource(taxon);
2859 // Determine the sourceReference
2860 Reference<?> sourceReference = syn.getSec();
2861
2862 if (sourceReference == null){
2863 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2864 sourceReference = taxon.getSec();
2865 }
2866
2867 synName = syn.getName();
2868 ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2869 String synGenusName = zooSynName.getGenusOrUninomial();
2870 String synInfraGenericEpithet = null;
2871 String synSpecificEpithet = null;
2872
2873 if (zooSynName.getInfraGenericEpithet() != null){
2874 synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2875 }
2876
2877 if (zooSynName.isInfraSpecific()){
2878 synSpecificEpithet = zooSynName.getSpecificEpithet();
2879 }
2880
2881 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2882 synonymsGenus.put(synGenusName, idInSource);
2883 }*/
2884
2885 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2886
2887 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2888 if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2889 logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2890 }
2891 inferredSynName.setGenusOrUninomial(synGenusName);
2892
2893 if (parentName.isInfraGeneric()){
2894 inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2895 }
2896 if (taxonName.isSpecies()){
2897 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2898 }else if (taxonName.isInfraSpecific()){
2899 inferredSynName.setSpecificEpithet(synSpecificEpithet);
2900 inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2901 }
2902
2903 inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2904
2905 // Set the sourceReference
2906 inferredEpithet.setSec(sourceReference);
2907
2908 /* Add the original source
2909 if (idInSource != null) {
2910 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2911
2912 // Add the citation
2913 Reference citation = getCitation(syn);
2914 if (citation != null) {
2915 originalSource.setCitation(citation);
2916 inferredEpithet.addSource(originalSource);
2917 }
2918 }*/
2919 String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2920
2921
2922 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2923 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2924
2925 inferredEpithet.addSource(originalSource);
2926
2927 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2928 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2929
2930 inferredSynName.addSource(originalSource);
2931
2932
2933
2934 taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2935
2936 return inferredEpithet;
2937 }
2938
2939 /**
2940 * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2941 * Very likely only useful for createInferredSynonyms().
2942 * @param uuid
2943 * @param zooHashMap
2944 * @return
2945 */
2946 private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2947 ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2948 if (taxonName == null) {
2949 taxonName = zooHashMap.get(uuid);
2950 }
2951 return taxonName;
2952 }
2953
2954 /**
2955 * Returns the idInSource for a given Synonym.
2956 * @param syn
2957 */
2958 private String getIdInSource(TaxonBase taxonBase) {
2959 String idInSource = null;
2960 Set<IdentifiableSource> sources = taxonBase.getSources();
2961 if (sources.size() == 1) {
2962 IdentifiableSource source = sources.iterator().next();
2963 if (source != null) {
2964 idInSource = source.getIdInSource();
2965 }
2966 } else if (sources.size() > 1) {
2967 int count = 1;
2968 idInSource = "";
2969 for (IdentifiableSource source : sources) {
2970 idInSource += source.getIdInSource();
2971 if (count < sources.size()) {
2972 idInSource += "; ";
2973 }
2974 count++;
2975 }
2976 } else if (sources.size() == 0){
2977 logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2978 }
2979
2980
2981 return idInSource;
2982 }
2983
2984
2985 /**
2986 * Returns the citation for a given Synonym.
2987 * @param syn
2988 */
2989 private Reference getCitation(Synonym syn) {
2990 Reference citation = null;
2991 Set<IdentifiableSource> sources = syn.getSources();
2992 if (sources.size() == 1) {
2993 IdentifiableSource source = sources.iterator().next();
2994 if (source != null) {
2995 citation = source.getCitation();
2996 }
2997 } else if (sources.size() > 1) {
2998 logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2999 }
3000
3001 return citation;
3002 }
3003
3004 @Override
3005 public List<Synonym> createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
3006 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
3007
3008 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
3009 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
3010 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
3011
3012 return inferredSynonyms;
3013 }
3014
3015 @Override
3016 public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
3017
3018 // TODO quickly implemented, create according dao !!!!
3019 Set<TaxonNode> nodes = new HashSet<TaxonNode>();
3020 Set<Classification> classifications = new HashSet<Classification>();
3021 List<Classification> list = new ArrayList<Classification>();
3022
3023 if (taxonBase == null) {
3024 return list;
3025 }
3026
3027 taxonBase = load(taxonBase.getUuid());
3028
3029 if (taxonBase instanceof Taxon) {
3030 nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
3031 } else {
3032 for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
3033 nodes.addAll(taxon.getTaxonNodes());
3034 }
3035 }
3036 for (TaxonNode node : nodes) {
3037 classifications.add(node.getClassification());
3038 }
3039 list.addAll(classifications);
3040 return list;
3041 }
3042
3043 @Override
3044 public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
3045 SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
3046 // Create new synonym using concept name
3047 TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
3048 Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
3049
3050 // Remove concept relation from taxon
3051 toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
3052
3053
3054
3055
3056 // Create a new synonym for the taxon
3057 SynonymRelationship synonymRelationship;
3058 if (synonymRelationshipType != null
3059 && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
3060 synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
3061 } else{
3062 synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
3063 }
3064
3065 this.saveOrUpdate(toTaxon);
3066 //TODO: configurator and classification
3067 TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
3068 config.setDeleteNameIfPossible(false);
3069 this.deleteTaxon(fromTaxon, config, null);
3070 return synonymRelationship.getSynonym();
3071
3072 }
3073 @Override
3074 public DeleteResult isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
3075 DeleteResult result = new DeleteResult();
3076 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
3077 if (taxonBase instanceof Taxon){
3078 TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3079 result = isDeletableForTaxon(references, taxonConfig);
3080 }else{
3081 SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3082 result = isDeletableForSynonym(references, synonymConfig);
3083 }
3084 return result;
3085 }
3086
3087 private DeleteResult isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3088 String message;
3089 DeleteResult result = new DeleteResult();
3090 for (CdmBase ref: references){
3091 if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase )){
3092 message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3093 result.addException(new ReferencedObjectUndeletableException(message));
3094 result.addRelatedObject(ref);
3095 result.setAbort();
3096 }
3097 }
3098
3099 return result;
3100 }
3101 private DeleteResult isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3102 String message = null;
3103 DeleteResult result = new DeleteResult();
3104 for (CdmBase ref: references){
3105 if (!(ref instanceof TaxonNameBase)){
3106 if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){
3107 message = "The Taxon can't be deleted as long as it has synonyms.";
3108
3109 }
3110 if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3111 message = "The Taxon can't be deleted as long as it has factual data.";
3112
3113 }
3114
3115 if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
3116 message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3117
3118 }
3119 if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonNode)){
3120 if (!config.isDeleteMisappliedNamesAndInvalidDesignations() && (((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())|| ((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR()))){
3121 message = "The Taxon can't be deleted as long as it has misapplied names or invalid designations.";
3122
3123 } else{
3124 message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3125
3126 }
3127 }
3128 if (ref instanceof PolytomousKeyNode){
3129 message = "The Taxon can't be deleted as long as it is referenced by a polytomous key node.";
3130
3131 }
3132
3133 if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){
3134 message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
3135
3136
3137 }
3138
3139
3140 /* //PolytomousKeyNode
3141 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3142 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3143 return message;
3144 }*/
3145
3146 //TaxonInteraction
3147 if (ref.isInstanceOf(TaxonInteraction.class)){
3148 message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3149
3150 }
3151
3152 //TaxonInteraction
3153 if (ref.isInstanceOf(DeterminationEvent.class)){
3154 message = "Taxon can't be deleted as it is used in a determination event";
3155
3156 }
3157
3158 }
3159 if (message != null){
3160 result.addException(new ReferencedObjectUndeletableException(message));
3161 result.addRelatedObject(ref);
3162 result.setAbort();
3163 }
3164 }
3165
3166 return result;
3167 }
3168
3169 @Override
3170 public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3171 IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3172
3173 //preliminary implementation
3174
3175 Set<Taxon> taxa = new HashSet<Taxon>();
3176 TaxonBase taxonBase = find(taxonUuid);
3177 if (taxonBase == null){
3178 return new IncludedTaxaDTO();
3179 }else if (taxonBase.isInstanceOf(Taxon.class)){
3180 Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3181 taxa.add(taxon);
3182 }else if (taxonBase.isInstanceOf(Synonym.class)){
3183 //TODO partial synonyms ??
3184 //TODO synonyms in general
3185 Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3186 taxa.addAll(syn.getAcceptedTaxa());
3187 }else{
3188 throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3189 }
3190
3191 Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3192 int i = 0;
3193 while((! related.isEmpty()) && i++ < 100){ //to avoid
3194 related = makeRelatedIncluded(related, result, config);
3195 }
3196
3197 return result;
3198 }
3199
3200 /**
3201 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3202 * data structure.
3203 * @return the set of conceptually related taxa for further use
3204 */
3205 /**
3206 * @param uncheckedTaxa
3207 * @param existingTaxa
3208 * @param config
3209 * @return
3210 */
3211 private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3212
3213 //children
3214 Set<TaxonNode> taxonNodes = new HashSet<TaxonNode>();
3215 for (Taxon taxon: uncheckedTaxa){
3216 taxonNodes.addAll(taxon.getTaxonNodes());
3217 }
3218
3219 Set<Taxon> children = new HashSet<Taxon>();
3220 if (! config.onlyCongruent){
3221 for (TaxonNode node: taxonNodes){
3222 List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, false);
3223 for (TaxonNode child : childNodes){
3224 children.add(child.getTaxon());
3225 }
3226 }
3227 children.remove(null); // just to be on the save side
3228 }
3229
3230 Iterator<Taxon> it = children.iterator();
3231 while(it.hasNext()){
3232 UUID uuid = it.next().getUuid();
3233 if (existingTaxa.contains(uuid)){
3234 it.remove();
3235 }else{
3236 existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3237 }
3238 }
3239
3240 //concept relations
3241 Set<Taxon> uncheckedAndChildren = new HashSet<Taxon>(uncheckedTaxa);
3242 uncheckedAndChildren.addAll(children);
3243
3244 Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3245
3246
3247 Set<Taxon> result = new HashSet<Taxon>(relatedTaxa);
3248 return result;
3249 }
3250
3251 /**
3252 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3253 * @return the set of these computed taxa
3254 */
3255 private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3256 Set<Taxon> result = new HashSet<Taxon>();
3257
3258 for (Taxon taxon : unchecked){
3259 Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3260 Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3261
3262 for (TaxonRelationship fromRel : fromRelations){
3263 if (config.includeDoubtful == false && fromRel.isDoubtful()){
3264 continue;
3265 }
3266 if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3267 !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
3268 !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3269 ){
3270 result.add(fromRel.getToTaxon());
3271 }
3272 }
3273
3274 for (TaxonRelationship toRel : toRelations){
3275 if (config.includeDoubtful == false && toRel.isDoubtful()){
3276 continue;
3277 }
3278 if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
3279 result.add(toRel.getFromTaxon());
3280 }
3281 }
3282 }
3283
3284 Iterator<Taxon> it = result.iterator();
3285 while(it.hasNext()){
3286 UUID uuid = it.next().getUuid();
3287 if (existingTaxa.contains(uuid)){
3288 it.remove();
3289 }else{
3290 existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3291 }
3292 }
3293 return result;
3294 }
3295
3296 @Override
3297 public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3298 List<TaxonBase> taxonList = dao.getTaxaByName(true, false, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, 0, config.getPropertyPath());
3299 return taxonList;
3300 }
3301
3302 @Override
3303 @Transactional(readOnly = true)
3304 public <S extends TaxonBase> Pager<FindByIdentifierDTO<S>> findByIdentifier(
3305 Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
3306 MatchMode matchmode, boolean includeEntity, Integer pageSize,
3307 Integer pageNumber, List<String> propertyPaths) {
3308 if (subtreeFilter == null){
3309 return findByIdentifier(clazz, identifier, identifierType, matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3310 }
3311
3312 Integer numberOfResults = dao.countByIdentifier(clazz, identifier, identifierType, subtreeFilter, matchmode);
3313 List<Object[]> daoResults = new ArrayList<Object[]>();
3314 if(numberOfResults > 0) { // no point checking again
3315 daoResults = dao.findByIdentifier(clazz, identifier, identifierType, subtreeFilter,
3316 matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3317 }
3318
3319 List<FindByIdentifierDTO<S>> result = new ArrayList<FindByIdentifierDTO<S>>();
3320 for (Object[] daoObj : daoResults){
3321 if (includeEntity){
3322 result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (S)daoObj[2]));
3323 }else{
3324 result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (UUID)daoObj[2], (String)daoObj[3]));
3325 }
3326 }
3327 return new DefaultPagerImpl<FindByIdentifierDTO<S>>(pageNumber, numberOfResults, pageSize, result);
3328 }
3329
3330 @Override
3331 public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, UUID newTaxonUUID, boolean moveHomotypicGroup,
3332 SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
3333
3334 Taxon newTaxon = (Taxon) dao.load(newTaxonUUID);
3335 return moveSynonymToAnotherTaxon(oldSynonymRelation, newTaxon, moveHomotypicGroup, newSynonymRelationshipType, reference, referenceDetail, keepReference);
3336 }
3337 }