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