2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.api
.service
;
12 import java
.io
.IOException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collections
;
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
;
22 import java
.util
.UUID
;
24 import javax
.persistence
.EntityNotFoundException
;
26 import org
.apache
.commons
.lang3
.StringUtils
;
27 import org
.apache
.log4j
.Logger
;
28 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
29 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
30 import org
.apache
.lucene
.search
.BooleanQuery
;
31 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
32 import org
.apache
.lucene
.search
.Query
;
33 import org
.apache
.lucene
.search
.SortField
;
34 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
35 import org
.apache
.lucene
.search
.join
.ScoreMode
;
36 import org
.apache
.lucene
.util
.BytesRef
;
37 import org
.hibernate
.criterion
.Criterion
;
38 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
39 import org
.springframework
.stereotype
.Service
;
40 import org
.springframework
.transaction
.annotation
.Transactional
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
47 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
48 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
51 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
52 import eu
.etaxonomy
.cdm
.api
.service
.dto
.TaxonRelationshipsDTO
;
53 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
54 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
55 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
56 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
57 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.AbstractPagerImpl
;
58 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
64 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
65 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
66 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
67 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
68 import eu
.etaxonomy
.cdm
.api
.util
.TaxonRelationshipEdge
;
69 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
70 import eu
.etaxonomy
.cdm
.compare
.taxon
.HomotypicGroupTaxonComparator
;
71 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonComparator
;
72 import eu
.etaxonomy
.cdm
.exception
.UnpublishedException
;
73 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
74 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
75 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
76 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
77 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
78 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
79 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
80 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
81 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
82 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
83 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
84 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
85 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
86 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
87 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
88 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementSource
;
89 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
90 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
91 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
92 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
93 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
94 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
95 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
96 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
97 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
98 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
99 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
100 import eu
.etaxonomy
.cdm
.model
.metadata
.SecReferenceHandlingEnum
;
101 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
102 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
103 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
104 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
105 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
106 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
107 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
108 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
109 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
110 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.SecundumSource
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
117 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
118 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
119 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
120 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
121 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
122 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.Restriction
;
123 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
124 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
125 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
126 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
127 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
128 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
129 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
130 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
131 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
132 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
133 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
134 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
138 * @author a.kohlbecker
142 @Transactional(readOnly
= true)
143 public class TaxonServiceImpl
144 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
145 implements ITaxonService
{
147 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
149 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
151 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
153 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
156 private ITaxonNodeDao taxonNodeDao
;
159 private ITaxonNameDao nameDao
;
162 private INameService nameService
;
165 private IOccurrenceService occurrenceService
;
168 private ITaxonNodeService nodeService
;
171 private IDescriptionService descriptionService
;
174 private IReferenceService referenceService
;
177 // private IOrderedTermVocabularyDao orderedVocabularyDao;
180 private IOccurrenceDao occurrenceDao
;
183 private IClassificationDao classificationDao
;
186 private AbstractBeanInitializer beanInitializer
;
189 private ILuceneIndexToolProvider luceneIndexToolProvider
;
191 //************************ CONSTRUCTOR ****************************/
192 public TaxonServiceImpl(){
193 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
196 // ****************************** METHODS ********************************/
199 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
200 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
204 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
205 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
209 @Transactional(readOnly
= false)
210 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
){
211 if (newUuidForAcceptedTaxon
){
212 return swapSynonymAndAcceptedTaxonNewUuid(synonym
, acceptedTaxon
, setNameInSource
);
214 return swapSynonymAndAcceptedTaxon(synonym
, acceptedTaxon
, setNameInSource
);
218 private UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
219 UpdateResult result
= new UpdateResult();
220 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
222 TaxonName synonymName
= synonym
.getName();
223 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
225 acceptedTaxon
.setName(synonymName
);
226 synonym
.setName(taxonName
);
229 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, acceptedTaxon
.getDescriptions());
231 saveOrUpdate(acceptedTaxon
);
232 saveOrUpdate(synonym
);
233 result
.setCdmEntity(acceptedTaxon
);
234 result
.addUpdatedObject(synonym
);
239 private UpdateResult
swapSynonymAndAcceptedTaxonNewUuid(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
240 UpdateResult result
= new UpdateResult();
241 acceptedTaxon
.removeSynonym(synonym
);
242 TaxonName synonymName
= synonym
.getName();
243 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
244 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
246 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(taxonName
.getHomotypicalGroup());
247 synonymName
.removeTaxonBase(synonym
);
249 List
<Synonym
> synonyms
= new ArrayList
<>();
250 for (Synonym syn
: acceptedTaxon
.getSynonyms()){
251 syn
= HibernateProxyHelper
.deproxy(syn
, Synonym
.class);
254 for (Synonym syn
: synonyms
){
255 acceptedTaxon
.removeSynonym(syn
);
257 Taxon newTaxon
= acceptedTaxon
.clone(true, true, false, true);
260 Set
<TaxonDescription
> descriptionsToCopy
= new HashSet
<>(acceptedTaxon
.getDescriptions());
261 for (TaxonDescription description
: descriptionsToCopy
){
262 newTaxon
.addDescription(description
);
265 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, newTaxon
.getDescriptions());
267 newTaxon
.setName(synonymName
);
269 newTaxon
.setPublish(synonym
.isPublish());
270 for (Synonym syn
: synonyms
){
271 if (!syn
.getName().equals(newTaxon
.getName())){
272 newTaxon
.addSynonym(syn
, syn
.getType());
276 //move all data to new taxon
277 //Move Taxon RelationShips to new Taxon
278 for(TaxonRelationship taxonRelationship
: newTaxon
.getTaxonRelations()){
279 newTaxon
.removeTaxonRelation(taxonRelationship
);
282 for(TaxonRelationship taxonRelationship
: acceptedTaxon
.getTaxonRelations()){
283 Taxon fromTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getFromTaxon());
284 Taxon toTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getToTaxon());
285 if (fromTaxon
== acceptedTaxon
){
286 newTaxon
.addTaxonRelation(taxonRelationship
.getToTaxon(), taxonRelationship
.getType(),
287 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
289 }else if(toTaxon
== acceptedTaxon
){
290 fromTaxon
.addTaxonRelation(newTaxon
, taxonRelationship
.getType(),
291 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
292 saveOrUpdate(fromTaxon
);
295 logger
.warn("Taxon is not part of its own Taxonrelationship");
298 // Remove old relationships
299 fromTaxon
.removeTaxonRelation(taxonRelationship
);
300 toTaxon
.removeTaxonRelation(taxonRelationship
);
301 taxonRelationship
.setToTaxon(null);
302 taxonRelationship
.setFromTaxon(null);
306 List
<TaxonNode
> nodes
= new ArrayList
<>(acceptedTaxon
.getTaxonNodes());
307 for (TaxonNode node
: nodes
){
308 node
= HibernateProxyHelper
.deproxy(node
);
309 TaxonNode parent
= node
.getParent();
310 acceptedTaxon
.removeTaxonNode(node
);
311 node
.setTaxon(newTaxon
);
313 parent
.addChildNode(node
, null, null);
318 Synonym newSynonym
= synonym
.clone();
319 newSynonym
.setName(taxonName
);
320 newSynonym
.setPublish(acceptedTaxon
.isPublish());
321 if (sameHomotypicGroup
){
322 newTaxon
.addSynonym(newSynonym
, SynonymType
.HOMOTYPIC_SYNONYM_OF());
324 newTaxon
.addSynonym(newSynonym
, SynonymType
.HETEROTYPIC_SYNONYM_OF());
328 TaxonDeletionConfigurator conf
= new TaxonDeletionConfigurator();
329 conf
.setDeleteNameIfPossible(false);
330 SynonymDeletionConfigurator confSyn
= new SynonymDeletionConfigurator();
331 confSyn
.setDeleteNameIfPossible(false);
332 result
.setCdmEntity(newTaxon
);
334 DeleteResult deleteResult
= deleteTaxon(acceptedTaxon
.getUuid(), conf
, null);
335 if (synonym
.isPersited()){
336 synonym
.setSecSource(null);
337 deleteResult
.includeResult(deleteSynonym(synonym
.getUuid(), confSyn
));
339 result
.includeResult(deleteResult
);
344 private void handleNameUsedInSourceForSwap(boolean setNameInSource
, TaxonName taxonName
, String oldTaxonTitleCache
,
345 Set
<TaxonDescription
> descriptions
) {
346 for(TaxonDescription description
: descriptions
){
347 String message
= "Description copied from former accepted taxon: %s (Old title: %s)";
348 message
= String
.format(message
, oldTaxonTitleCache
, description
.getTitleCache());
349 description
.setTitleCache(message
, true);
351 for (DescriptionElementBase element
: description
.getElements()){
352 for (DescriptionElementSource source
: element
.getSources()){
353 if (source
.getNameUsedInSource() == null){
354 source
.setNameUsedInSource(taxonName
);
363 @Transactional(readOnly
= false)
364 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, Reference newSecRef
, String microRef
, SecReferenceHandlingEnum secHandling
, boolean deleteSynonym
) {
365 UpdateResult result
= new UpdateResult();
366 TaxonName acceptedName
= acceptedTaxon
.getName();
367 TaxonName synonymName
= synonym
.getName();
368 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
370 //check synonym is not homotypic
371 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
372 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
373 result
.addException(new HomotypicalGroupChangeException(message
));
377 if (secHandling
!= null && secHandling
.equals(SecReferenceHandlingEnum
.KeepAlways
)){
378 newSecRef
= synonym
.getSec();
380 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, newSecRef
, microRef
);
381 newAcceptedTaxon
.setPublish(synonym
.isPublish());
382 dao
.save(newAcceptedTaxon
);
383 result
.setCdmEntity(newAcceptedTaxon
);
384 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
385 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
387 for (Synonym heteroSynonym
: heteroSynonyms
){
388 if (secHandling
== null || !secHandling
.equals(SecReferenceHandlingEnum
.KeepAlways
)){
389 heteroSynonym
.setSec(newSecRef
);
391 if (synonym
.equals(heteroSynonym
)){
392 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
394 //move synonyms in same homotypic group to new accepted taxon
395 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
398 dao
.saveOrUpdate(acceptedTaxon
);
399 result
.addUpdatedObject(acceptedTaxon
);
404 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
405 config
.setDeleteNameIfPossible(false);
406 this.deleteSynonym(synonym
, config
);
408 } catch (Exception e
) {
409 result
.addException(e
);
417 @Transactional(readOnly
= false)
418 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
419 UUID acceptedTaxonUuid
,
420 UUID newParentNodeUuid
,
422 String microReference
,
423 SecReferenceHandlingEnum secHandling
,
424 boolean deleteSynonym
) {
425 UpdateResult result
= new UpdateResult();
426 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
427 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
428 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
429 Reference newSecRef
= null;
430 switch (secHandling
){
436 case UseNewParentSec
:
437 newSecRef
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
440 Reference parentSec
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
441 Reference synSec
= synonym
.getSec();
442 if (parentSec
!= null && synSec
!= null && parentSec
.equals(synSec
)){
443 newSecRef
= synonym
.getSec();
445 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
449 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
455 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, newSecRef
, microReference
, secHandling
, deleteSynonym
);
456 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
458 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
459 taxonNodeDao
.save(newNode
);
460 result
.addUpdatedObject(newTaxon
);
461 result
.addUpdatedObject(acceptedTaxon
);
462 result
.setCdmEntity(newNode
);
467 @Transactional(readOnly
= false)
468 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
470 TaxonRelationshipType taxonRelationshipType
,
472 String microcitation
){
474 UpdateResult result
= new UpdateResult();
475 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
476 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
477 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
478 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
479 // result.setCdmEntity(relatedTaxon);
480 result
.addUpdatedObject(relatedTaxon
);
481 result
.addUpdatedObject(toTaxon
);
486 @Transactional(readOnly
= false)
487 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
488 // Get name from synonym
489 if (synonym
== null){
493 UpdateResult result
= new UpdateResult();
495 TaxonName synonymName
= synonym
.getName();
497 /* // remove synonym from taxon
498 toTaxon.removeSynonym(synonym);
500 // Create a taxon with synonym name
501 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
502 fromTaxon
.setPublish(synonym
.isPublish());
504 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
506 // Add taxon relation
507 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
508 result
.setCdmEntity(fromTaxon
);
509 // since we are swapping names, we have to detach the name from the synonym completely.
510 // Otherwise the synonym will still be in the list of typified names.
511 // synonym.getName().removeTaxonBase(synonym);
512 result
.includeResult(this.deleteSynonym(synonym
, null));
517 @Transactional(readOnly
= false)
519 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
520 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
522 TaxonName synonymName
= synonym
.getName();
523 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
526 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
527 newHomotypicalGroup
.addTypifiedName(synonymName
);
529 //remove existing basionym relationships
530 synonymName
.removeBasionyms();
532 //add basionym relationship
533 if (setBasionymRelationIfApplicable
){
534 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
535 for (TaxonName basionym
: basionyms
){
536 synonymName
.addBasionym(basionym
);
540 //set synonym relationship correctly
541 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
543 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
544 if (acceptedTaxon
!= null){
546 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
547 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
548 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
549 synonym
.setType(newRelationType
);
551 if (hasNewTargetTaxon
){
552 acceptedTaxon
.removeSynonym(synonym
, false);
555 if (hasNewTargetTaxon
){
556 @SuppressWarnings("null")
557 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
558 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
559 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
560 targetTaxon
.addSynonym(synonym
, relType
);
565 @Transactional(readOnly
= false)
566 public UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
568 clazz
= TaxonBase
.class;
570 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
575 protected void setDao(ITaxonDao dao
) {
580 public <T
extends TaxonBase
> Pager
<T
> findTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
581 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
582 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
);
584 List
<T
> results
= new ArrayList
<>();
585 if(numberOfResults
> 0) { // no point checking again
586 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
,
587 pageSize
, pageNumber
, propertyPaths
);
590 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
594 public <T
extends TaxonBase
> List
<T
> listTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
595 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
597 return findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infragenericEpithet
, authorshipCache
, rank
,
598 pageSize
, pageNumber
, propertyPaths
).getRecords();
602 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
603 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
604 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
606 List
<TaxonRelationship
> results
= new ArrayList
<>();
607 if(numberOfResults
> 0) { // no point checking again
608 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
614 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
615 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
616 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
618 List
<TaxonRelationship
> results
= new ArrayList
<>();
619 if(numberOfResults
> 0) { // no point checking again
620 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
622 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
626 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
627 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
628 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
630 List
<TaxonRelationship
> results
= new ArrayList
<>();
631 if(numberOfResults
> 0) { // no point checking again
632 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
638 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
639 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
640 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
642 List
<TaxonRelationship
> results
= new ArrayList
<>();
643 if(numberOfResults
> 0) { // no point checking again
644 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
646 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
650 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
651 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
653 Long numberOfResults
= dao
.countTaxonRelationships(types
);
654 List
<TaxonRelationship
> results
= new ArrayList
<>();
655 if(numberOfResults
> 0) {
656 results
= dao
.getTaxonRelationships(types
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
662 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
663 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
664 return page(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, INCLUDE_UNPUBLISHED
);
668 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
669 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
, boolean includeUnpublished
) {
672 long resultSize
= dao
.count(clazz
, restrictions
);
673 if(AbstractPagerImpl
.hasResultsInRange(resultSize
, pageIndex
, pageSize
)){
674 records
= dao
.list(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, includeUnpublished
);
676 records
= new ArrayList
<>();
678 Pager
<S
> pager
= new DefaultPagerImpl
<>(pageIndex
, resultSize
, pageSize
, records
);
683 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
684 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
686 Synonym synonym
= null;
689 synonym
= (Synonym
) dao
.load(synonymUuid
);
690 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
691 } catch (ClassCastException e
){
692 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
693 } catch (NullPointerException e
){
694 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
697 Classification classificationFilter
= null;
698 if(classificationUuid
!= null){
700 classificationFilter
= classificationDao
.load(classificationUuid
);
701 } catch (NullPointerException e
){
702 //TODO not sure, why an NPE should be thrown in the above load method
703 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
705 if(classificationFilter
== null){
706 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
710 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
712 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
713 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
721 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
722 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
724 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
725 relatedTaxa
.remove(taxon
);
726 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
731 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
732 * <code>taxon</code> supplied as parameter.
735 * @param includeRelationships
737 * @param maxDepth can be <code>null</code> for infinite depth
740 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
741 boolean includeUnpublished
, Integer maxDepth
) {
747 if(includeRelationships
.isEmpty()){
751 if(maxDepth
!= null) {
754 if(logger
.isDebugEnabled()){
755 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
757 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
758 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
759 for (TaxonRelationship taxRel
: taxonRelationships
) {
762 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
765 // filter by includeRelationships
766 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
767 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
768 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
769 if(logger
.isDebugEnabled()){
770 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
772 taxa
.add(taxRel
.getToTaxon());
773 if(maxDepth
== null || maxDepth
> 0) {
774 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
777 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
778 taxa
.add(taxRel
.getFromTaxon());
779 if(logger
.isDebugEnabled()){
780 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
782 if(maxDepth
== null || maxDepth
> 0) {
783 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
793 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
794 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
796 List
<Synonym
> results
= new ArrayList
<>();
797 if(numberOfResults
> 0) { // no point checking again
798 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
801 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
805 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
806 List
<List
<Synonym
>> result
= new ArrayList
<>();
807 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
808 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
811 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
814 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
815 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
816 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
823 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
824 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
825 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
827 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
831 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
832 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
833 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
834 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
835 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
836 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
838 return heterotypicSynonymyGroups
;
842 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
844 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
845 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
846 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(), config
.isDoIncludeAuthors(),
847 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
848 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
850 return new ArrayList
<>();
855 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
857 @SuppressWarnings("rawtypes")
858 List
<IdentifiableEntity
> results
= new ArrayList
<>();
859 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
860 List
<TaxonBase
> taxa
= null;
863 long numberTaxaResults
= 0L;
865 List
<String
> propertyPath
= new ArrayList
<>();
866 if(configurator
.getTaxonPropertyPath() != null){
867 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
870 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
871 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
873 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
874 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
875 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
876 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
879 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
880 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
881 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
882 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
883 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
884 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
888 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
891 results
.addAll(taxa
);
894 numberOfResults
+= numberTaxaResults
;
896 // Names without taxa
897 if (configurator
.isDoNamesWithoutTaxa()) {
898 int numberNameResults
= 0;
900 List
<TaxonName
> names
=
901 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
902 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
903 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
904 if (names
.size() > 0) {
905 for (TaxonName taxonName
: names
) {
906 if (taxonName
.getTaxonBases().size() == 0) {
907 results
.add(taxonName
);
911 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
912 numberOfResults
+= numberNameResults
;
916 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
919 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
920 return dao
.getUuidAndTitleCache(limit
, pattern
);
924 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
925 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
929 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
930 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
931 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
934 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
936 // logger.setLevel(Level.TRACE);
937 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
939 logger
.trace("listMedia() - START");
941 Set
<Taxon
> taxa
= new HashSet
<>();
942 List
<Media
> taxonMedia
= new ArrayList
<>();
943 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
945 if (limitToGalleries
== null) {
946 limitToGalleries
= false;
949 // --- resolve related taxa
950 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
951 logger
.trace("listMedia() - resolve related taxa");
952 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
955 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
957 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
958 logger
.trace("listMedia() - includeTaxonDescriptions");
959 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
960 // --- TaxonDescriptions
961 for (Taxon t
: taxa
) {
962 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
964 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
965 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
966 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
967 for (Media media
: element
.getMedia()) {
968 if(taxonDescription
.isImageGallery()){
969 taxonMedia
.add(media
);
972 nonImageGalleryImages
.add(media
);
978 //put images from image gallery first (#3242)
979 taxonMedia
.addAll(nonImageGalleryImages
);
983 if(includeOccurrences
!= null && includeOccurrences
) {
984 logger
.trace("listMedia() - includeOccurrences");
985 @SuppressWarnings("rawtypes")
986 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
988 for (Taxon t
: taxa
) {
989 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
991 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
993 // direct media removed from specimen #3597
994 // taxonMedia.addAll(occurrence.getMedia());
996 // SpecimenDescriptions
997 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
998 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
999 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
1000 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
1001 for (DescriptionElementBase element
: elements
) {
1002 for (Media media
: element
.getMedia()) {
1003 taxonMedia
.add(media
);
1009 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
1010 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
1012 //TODO why may collections have media attached? #
1013 if (derivedUnit
.getCollection() != null){
1014 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
1017 //media in hierarchy
1018 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
1022 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
1023 logger
.trace("listMedia() - includeTaxonNameDescriptions");
1024 // --- TaxonNameDescription
1025 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
1026 for (Taxon t
: taxa
) {
1027 nameDescriptions
.addAll(t
.getName().getDescriptions());
1029 for(TaxonNameDescription nameDescription
: nameDescriptions
){
1030 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
1031 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
1032 for (DescriptionElementBase element
: elements
) {
1033 for (Media media
: element
.getMedia()) {
1034 taxonMedia
.add(media
);
1041 logger
.trace("listMedia() - initialize");
1042 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
1044 logger
.trace("listMedia() - END");
1050 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
1051 return this.dao
.loadList(listOfIDs
, null, null);
1055 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
1056 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
1060 public long countSynonyms(boolean onlyAttachedToTaxon
){
1061 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
1065 @Transactional(readOnly
=false)
1066 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
1068 if (config
== null){
1069 config
= new TaxonDeletionConfigurator();
1071 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
1072 DeleteResult result
= new DeleteResult();
1075 result
.addException(new Exception ("The taxon was already deleted."));
1078 taxon
= HibernateProxyHelper
.deproxy(taxon
);
1079 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
1080 config
.setClassificationUuid(classificationUuid
);
1081 result
= isDeletable(taxonUUID
, config
);
1084 // --- DeleteSynonymRelations
1085 if (config
.isDeleteSynonymRelations()){
1086 boolean removeSynonymNameFromHomotypicalGroup
= false;
1087 // use tmp Set to avoid concurrent modification
1088 Set
<Synonym
> synsToDelete
= new HashSet
<>();
1089 synsToDelete
.addAll(taxon
.getSynonyms());
1090 for (Synonym synonym
: synsToDelete
){
1091 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
1093 // --- DeleteSynonymsIfPossible
1094 if (config
.isDeleteSynonymsIfPossible()){
1096 boolean newHomotypicGroupIfNeeded
= true;
1097 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
1098 result
.includeResult(deleteSynonym(synonym
, synConfig
));
1103 // --- DeleteTaxonRelationships
1104 if (! config
.isDeleteTaxonRelationships()){
1105 if (taxon
.getTaxonRelations().size() > 0){
1107 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1108 "Remove taxon from all relations to other taxa prior to deletion."));
1111 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
1112 configRelTaxon
.setDeleteTaxonNodes(false);
1113 configRelTaxon
.setDeleteConceptRelationships(true);
1115 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
1116 if (config
.isDeleteMisappliedNames()
1117 && taxRel
.getType().isMisappliedName()
1118 && taxon
.equals(taxRel
.getToTaxon())){
1119 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
1120 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
1122 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
1123 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1124 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
1125 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1128 taxon
.removeTaxonRelation(taxRel
);
1133 if (config
.isDeleteDescriptions()){
1134 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
1135 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
1136 for (TaxonDescription desc
: descriptions
){
1137 //TODO use description delete configurator ?
1138 //FIXME check if description is ALWAYS deletable
1139 if (desc
.getDescribedSpecimenOrObservation() != null){
1141 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1142 " which also describes specimens or observations"));
1145 removeDescriptions
.add(desc
);
1148 for (TaxonDescription desc
: removeDescriptions
){
1149 taxon
.removeDescription(desc
);
1150 descriptionService
.delete(desc
);
1157 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1158 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1160 if (taxon
.getTaxonNodes().size() != 0){
1161 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1162 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1163 TaxonNode node
= null;
1164 boolean deleteChildren
;
1165 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1166 deleteChildren
= true;
1168 deleteChildren
= false;
1170 boolean success
= true;
1171 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1172 while (iterator
.hasNext()){
1173 node
= iterator
.next();
1174 if (node
.getClassification().equals(classification
)){
1180 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1181 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1182 nodeService
.delete(node
);
1183 result
.addDeletedObject(node
);
1186 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1188 } else if (config
.isDeleteInAllClassifications()){
1189 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1190 nodesList
.addAll(taxon
.getTaxonNodes());
1191 for (ITaxonTreeNode treeNode
: nodesList
){
1192 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1193 if(!deleteChildren
){
1194 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1195 for (Object childNode
: childNodes
){
1196 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1197 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1201 config
.getTaxonNodeConfig().setDeleteElement(false);
1202 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1203 if (!resultNodes
.isOk()){
1204 result
.addExceptions(resultNodes
.getExceptions());
1205 result
.setStatus(resultNodes
.getStatus());
1207 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1212 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1216 TaxonName name
= taxon
.getName();
1217 taxon
.setName(null);
1218 this.saveOrUpdate(taxon
);
1220 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1223 result
.addDeletedObject(taxon
);
1224 }catch(Exception e
){
1225 result
.addException(e
);
1230 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1234 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1235 DeleteResult nameResult
= new DeleteResult();
1236 //remove name if possible (and required)
1238 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1240 if (nameResult
.isError() || nameResult
.isAbort()){
1241 result
.addRelatedObject(name
);
1242 result
.addExceptions(nameResult
.getExceptions());
1244 result
.includeResult(nameResult
);
1253 @Transactional(readOnly
= false)
1254 public DeleteResult
delete(UUID synUUID
){
1255 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1256 return this.deleteSynonym(syn
, null);
1260 @Transactional(readOnly
= false)
1261 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1262 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1266 @Transactional(readOnly
= false)
1267 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1268 DeleteResult result
= new DeleteResult();
1269 if (synonym
== null){
1271 result
.addException(new Exception("The synonym was already deleted."));
1275 if (config
== null){
1276 config
= new SynonymDeletionConfigurator();
1279 result
= isDeletable(synonym
.getUuid(), config
);
1283 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1286 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1288 if (accTaxon
!= null){
1289 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1290 accTaxon
.removeSynonym(synonym
, false);
1291 this.saveOrUpdate(accTaxon
);
1292 result
.addUpdatedObject(accTaxon
);
1294 this.saveOrUpdate(synonym
);
1298 TaxonName name
= synonym
.getName();
1299 synonym
.setName(null);
1301 dao
.delete(synonym
);
1302 result
.addDeletedObject(synonym
);
1304 //remove name if possible (and required)
1305 if (name
!= null && config
.isDeleteNameIfPossible()){
1307 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1308 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1309 result
.addExceptions(nameDeleteResult
.getExceptions());
1310 result
.addRelatedObject(name
);
1311 result
.addUpdatedObject(name
);
1313 result
.addDeletedObject(name
);
1321 public Map
<String
, Map
<UUID
,Set
<TaxonName
>>> findIdenticalTaxonNames(List
<UUID
> sourceRefUuids
, List
<String
> propertyPaths
) {
1322 return this.dao
.findIdenticalNames(sourceRefUuids
, propertyPaths
);
1326 public Taxon
findBestMatchingTaxon(String taxonName
) {
1327 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1328 config
.setTaxonNameTitle(taxonName
);
1329 return findBestMatchingTaxon(config
);
1333 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1335 Taxon bestCandidate
= null;
1337 // 1. search for accepted taxa
1338 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1339 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1340 boolean bestCandidateMatchesSecUuid
= false;
1341 boolean bestCandidateIsInClassification
= false;
1342 int countEqualCandidates
= 0;
1343 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1344 if(taxonBaseCandidate
instanceof Taxon
){
1345 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1346 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1347 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1349 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1350 bestCandidate
= newCanditate
;
1351 countEqualCandidates
= 1;
1352 bestCandidateMatchesSecUuid
= true;
1356 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1357 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1359 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1360 bestCandidate
= newCanditate
;
1361 countEqualCandidates
= 1;
1362 bestCandidateIsInClassification
= true;
1365 if (bestCandidate
== null){
1366 bestCandidate
= newCanditate
;
1367 countEqualCandidates
= 1;
1370 }else{ //not Taxon.class
1373 countEqualCandidates
++;
1376 if (bestCandidate
!= null){
1377 if(countEqualCandidates
> 1){
1378 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1379 return bestCandidate
;
1381 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1382 return bestCandidate
;
1386 // 2. search for synonyms
1387 if (config
.isIncludeSynonyms()){
1388 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1389 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1390 for(TaxonBase taxonBase
: synonymList
){
1391 if(taxonBase
instanceof Synonym
){
1392 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1393 bestCandidate
= synonym
.getAcceptedTaxon();
1394 if(bestCandidate
!= null){
1395 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1396 return bestCandidate
;
1398 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1403 } catch (Exception e
){
1405 e
.printStackTrace();
1408 return bestCandidate
;
1411 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1412 UUID configClassificationUuid
= config
.getClassificationUuid();
1413 if (configClassificationUuid
== null){
1416 for (TaxonNode node
: taxon
.getTaxonNodes()){
1417 UUID classUuid
= node
.getClassification().getUuid();
1418 if (configClassificationUuid
.equals(classUuid
)){
1425 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1426 UUID configSecUuid
= config
.getSecUuid();
1427 if (configSecUuid
== null){
1430 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1431 return configSecUuid
.equals(taxonSecUuid
);
1435 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1436 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1437 if(! synonymList
.isEmpty()){
1438 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1439 if(synonymList
.size() == 1){
1440 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1443 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1451 @Transactional(readOnly
= false)
1452 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
1453 SynonymType newSynonymType
, UUID newSecundumUuid
, String newSecundumDetail
,
1454 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1456 UpdateResult result
= new UpdateResult();
1457 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
), Taxon
.class);
1458 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
1459 newSecundumUuid
, newSecundumDetail
, keepSecundumIfUndefined
);
1465 @Transactional(readOnly
= false)
1466 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1468 boolean moveHomotypicGroup
,
1469 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1470 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1472 oldSynonym
.getSec()!= null? oldSynonym
.getSec().getUuid(): null,
1473 oldSynonym
.getSecMicroReference(),
1478 @Transactional(readOnly
= false)
1479 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1481 boolean moveHomotypicGroup
,
1482 SynonymType newSynonymType
,
1483 UUID newSecundumUuid
,
1484 String newSecundumDetail
,
1485 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1487 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1488 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1489 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1490 TaxonName synonymName
= synonym
.getName();
1491 TaxonName fromTaxonName
= oldTaxon
.getName();
1492 //set default relationship type
1493 if (newSynonymType
== null){
1494 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1496 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1498 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1499 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1500 boolean isSingleInGroup
= !(hgSize
> 1);
1502 if (! isSingleInGroup
){
1503 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1504 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1505 if (isHomotypicToAccepted
){
1506 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.";
1507 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1508 message
= String
.format(message
, homotypicRelatives
);
1509 throw new HomotypicalGroupChangeException(message
);
1511 if (! moveHomotypicGroup
){
1512 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.";
1513 throw new HomotypicalGroupChangeException(message
);
1516 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1518 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1520 UpdateResult result
= new UpdateResult();
1521 //move all synonyms to new taxon
1522 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1523 Reference newSecundum
= referenceService
.load(newSecundumUuid
);
1524 for (Synonym synRelation
: homotypicSynonyms
){
1526 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1527 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1528 oldTaxon
.removeSynonym(synRelation
, false);
1529 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1531 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1532 synRelation
.setSec(newSecundum
);
1534 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1535 synRelation
.setSecMicroReference(newSecundumDetail
);
1538 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1539 if (!synRelation
.equals(oldSynonym
)){
1544 result
.addUpdatedObject(oldTaxon
);
1545 result
.addUpdatedObject(newTaxon
);
1546 saveOrUpdate(oldTaxon
);
1547 saveOrUpdate(newTaxon
);
1553 public <T
extends TaxonBase
>List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1555 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1559 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1560 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1561 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1562 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1563 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1565 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1566 null, includeUnpublished
, languages
, highlightFragments
, null);
1568 // --- execute search
1569 TopGroups
<BytesRef
> topDocsResultSet
;
1571 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1572 } catch (ParseException e
) {
1573 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1574 luceneParseException
.setStackTrace(e
.getStackTrace());
1575 throw luceneParseException
;
1578 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1579 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1581 // --- initialize taxa, thighlight matches ....
1582 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1583 @SuppressWarnings("rawtypes")
1584 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1585 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1587 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1588 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1591 @Transactional(readOnly
= true)
1593 public <S
extends TaxonBase
> Pager
<S
> findByTitleWithRestrictions(Class
<S
> clazz
, String queryString
, MatchMode matchmode
, List
<Restriction
<?
>> restrictions
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
1594 long numberOfResults
= dao
.countByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
);
1596 long numberOfResults_doubtful
= dao
.countByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
);
1597 List
<S
> results
= new ArrayList
<>();
1598 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1600 results
= dao
.findByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1601 results
.addAll(dao
.findByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1603 Collections
.sort(results
, new TaxonComparator());
1604 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1607 @Transactional(readOnly
= true)
1609 public <S
extends TaxonBase
> Pager
<S
> findByTitle(Class
<S
> clazz
, String queryString
,MatchMode matchmode
, List
<Criterion
> criteria
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
1610 long numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
1611 //check whether there are doubtful taxa matching
1612 long numberOfResults_doubtful
= dao
.countByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
);
1613 List
<S
> results
= new ArrayList
<>();
1614 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1615 if (numberOfResults
> 0){
1616 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1618 results
= new ArrayList
<>();
1620 if (numberOfResults_doubtful
> 0){
1621 results
.addAll(dao
.findByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1624 Collections
.sort(results
, new TaxonComparator());
1625 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1629 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1630 Classification classification
, TaxonNode subtree
,
1631 Integer pageSize
, Integer pageNumber
,
1632 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1634 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
1636 // --- execute search
1637 TopGroups
<BytesRef
> topDocsResultSet
;
1639 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1640 } catch (ParseException e
) {
1641 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1642 luceneParseException
.setStackTrace(e
.getStackTrace());
1643 throw luceneParseException
;
1646 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1647 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1649 // --- initialize taxa, thighlight matches ....
1650 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1651 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1652 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1654 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1655 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1660 * @param queryString
1661 * @param classification
1662 * @param includeUnpublished
1664 * @param highlightFragments
1665 * @param sortFields TODO
1666 * @param directorySelectClass
1669 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1670 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1671 boolean highlightFragments
, SortField
[] sortFields
) {
1673 Builder finalQueryBuilder
= new Builder();
1674 Builder textQueryBuilder
= new Builder();
1676 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1677 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1679 if(sortFields
== null){
1680 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1682 luceneSearch
.setSortFields(sortFields
);
1684 // ---- search criteria
1685 luceneSearch
.setCdmTypRestriction(clazz
);
1687 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1688 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1689 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1691 if(className
!= null){
1692 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1695 BooleanQuery textQuery
= textQueryBuilder
.build();
1696 if(textQuery
.clauses().size() > 0) {
1697 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1700 if(classification
!= null){
1701 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1703 if(subtree
!= null){
1704 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1706 if(!includeUnpublished
) {
1707 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1708 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1709 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1712 luceneSearch
.setQuery(finalQueryBuilder
.build());
1714 if(highlightFragments
){
1715 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1717 return luceneSearch
;
1721 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1722 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1723 * drawback of requiring to do the join an indexing time.
1724 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1726 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1728 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1729 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1731 * @param queryString
1732 * @param classification
1734 * @param highlightFragments
1735 * @param sortFields TODO
1738 * @throws IOException
1740 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1741 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1742 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1745 String queryTermField
;
1746 String toField
= "id"; // TaxonBase.uuid
1747 String publishField
;
1748 String publishFieldInvers
;
1750 if(edge
.isBidirectional()){
1751 throw new RuntimeException("Bidirectional joining not supported!");
1754 fromField
= "relatedFrom.id";
1755 queryTermField
= "relatedFrom.titleCache";
1756 publishField
= "relatedFrom.publish";
1757 publishFieldInvers
= "relatedTo.publish";
1758 } else if(edge
.isInvers()) {
1759 fromField
= "relatedTo.id";
1760 queryTermField
= "relatedTo.titleCache";
1761 publishField
= "relatedTo.publish";
1762 publishFieldInvers
= "relatedFrom.publish";
1764 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1767 Builder finalQueryBuilder
= new Builder();
1769 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1770 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1772 Builder joinFromQueryBuilder
= new Builder();
1773 if(!StringUtils
.isEmpty(queryString
)){
1774 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1776 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1777 if(!includeUnpublished
){
1778 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1779 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1782 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1784 if(sortFields
== null){
1785 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1787 luceneSearch
.setSortFields(sortFields
);
1789 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1791 if(classification
!= null){
1792 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1794 if(subtree
!= null){
1795 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1798 luceneSearch
.setQuery(finalQueryBuilder
.build());
1800 if(highlightFragments
){
1801 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1803 return luceneSearch
;
1807 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1808 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1809 Classification classification
, TaxonNode subtree
,
1810 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1811 boolean highlightFragments
, Integer pageSize
,
1812 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1813 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1815 // FIXME: allow taxonomic ordering
1816 // 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";
1817 // this require building a special sort column by a special classBridge
1818 if(highlightFragments
){
1819 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1820 "currently not fully supported by this method and thus " +
1821 "may not work with common names and misapplied names.");
1824 // convert sets to lists
1825 List
<NamedArea
> namedAreaList
= null;
1826 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1827 if(namedAreas
!= null){
1828 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1829 namedAreaList
.addAll(namedAreas
);
1831 if(distributionStatus
!= null){
1832 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1833 distributionStatusList
.addAll(distributionStatus
);
1836 // set default if parameter is null
1837 if(searchModes
== null){
1838 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1841 // set sort order and thus override any sort orders which may have been
1842 // defined by prepare*Search methods
1843 if(orderHints
== null){
1844 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1846 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1848 for(OrderHint oh
: orderHints
){
1849 sortFields
[i
++] = oh
.toSortField();
1851 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1852 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1854 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1856 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1857 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1860 ======== filtering by distribution , HOWTO ========
1862 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1863 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1864 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1865 which will be put into a FilteredQuersy in the end ?
1868 3. how does it work in spatial?
1870 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1871 - http://www.infoq.com/articles/LuceneSpatialSupport
1872 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1873 ------------------------------------------------------------------------
1876 A) use a separate distribution filter per index sub-query/search:
1877 - byTaxonSyonym (query TaxaonBase):
1878 use a join area filter (Distribution -> TaxonBase)
1879 - byCommonName (query DescriptionElementBase): use an area filter on
1880 DescriptionElementBase !!! PROBLEM !!!
1881 This cannot work since the distributions are different entities than the
1882 common names and thus these are different lucene documents.
1883 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1884 use a join area filter (Distribution -> TaxonBase)
1886 B) use a common distribution filter for all index sub-query/searches:
1887 - use a common join area filter (Distribution -> TaxonBase)
1888 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1889 PROBLEM in this case: we are losing the fragment highlighting for the
1890 common names, since the returned documents are always TaxonBases
1893 /* The QueryFactory for creating filter queries on Distributions should
1894 * The query factory used for the common names query cannot be reused
1895 * for this case, since we want to only record the text fields which are
1896 * actually used in the primary query
1898 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1900 Builder multiIndexByAreaFilterBuilder
= new Builder();
1901 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1903 // search for taxa or synonyms
1904 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1905 @SuppressWarnings("rawtypes")
1906 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1907 String className
= null;
1908 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1909 taxonBaseSubclass
= Taxon
.class;
1910 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1911 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1913 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1914 queryString
, classification
, subtree
, className
,
1915 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1916 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1917 /* A) does not work!!!!
1918 if(addDistributionFilter){
1919 // in this case we need a filter which uses a join query
1920 // to get the TaxonBase documents for the DescriptionElementBase documents
1921 // which are matching the areas in question
1922 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1924 distributionStatusList,
1925 distributionFilterQueryFactory
1927 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1930 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1931 // add additional area filter for synonyms
1932 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1933 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1935 //TODO replace by createByDistributionJoinQuery
1936 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1937 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1938 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1943 // search by CommonTaxonName
1944 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1946 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1947 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1948 CommonTaxonName
.class,
1949 "inDescription.taxon.id",
1951 QueryFactory
.addTypeRestriction(
1952 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
1953 , CommonTaxonName
.class
1954 ).build(), "id", null, ScoreMode
.Max
);
1955 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1956 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1957 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1958 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1959 Builder builder
= new BooleanQuery
.Builder();
1960 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1961 if(!includeUnpublished
) {
1962 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1963 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1965 byCommonNameSearch
.setQuery(builder
.build());
1966 byCommonNameSearch
.setSortFields(sortFields
);
1968 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1970 luceneSearches
.add(byCommonNameSearch
);
1972 /* A) does not work!!!!
1974 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1975 queryString, classification, null, languages, highlightFragments)
1977 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1978 if(addDistributionFilter){
1979 // in this case we are able to use DescriptionElementBase documents
1980 // which are matching the areas in question directly
1981 BooleanQuery byDistributionQuery = createByDistributionQuery(
1983 distributionStatusList,
1984 distributionFilterQueryFactory
1986 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1991 // search by misapplied names
1992 //TODO merge with pro parte synonym search once #7487 is fixed
1993 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1995 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1996 // which allows doing query time joins
1997 // finds the misapplied name (Taxon B) which is an misapplication for
1998 // a related Taxon A.
2000 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2001 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
2002 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
2004 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
2005 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
2008 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2009 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2010 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2011 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2013 if(addDistributionFilter
){
2014 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2017 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
2018 * Maybe this is a bug in java itself.
2020 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2023 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2025 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2026 * will execute as expected:
2028 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2029 * String toField = "relation." + misappliedNameForUuid +".to.id";
2031 * Comparing both strings by the String.equals method returns true, so both String are identical.
2033 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2034 * 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)
2035 * The bug is persistent after a reboot of the development computer.
2037 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2038 // String toField = "relation." + misappliedNameForUuid +".to.id";
2039 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2040 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2041 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2043 //TODO replace by createByDistributionJoinQuery
2044 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2045 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2046 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2048 // debug code for bug described above
2049 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
2050 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2051 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2053 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2057 // search by pro parte synonyms
2058 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
2059 //TODO merge with misapplied name search once #7487 is fixed
2060 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2061 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
2063 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2064 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2065 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2066 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2068 if(addDistributionFilter
){
2069 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2070 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
2071 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2072 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2073 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2074 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2076 }//end pro parte synonyms
2078 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
2079 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
2081 if(addDistributionFilter
){
2084 // in this case we need a filter which uses a join query
2085 // to get the TaxonBase documents for the DescriptionElementBase documents
2086 // which are matching the areas in question
2088 // for doTaxa, doByCommonName
2089 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
2091 distributionStatusList
,
2092 distributionFilterQueryFactory
,
2095 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2098 if (addDistributionFilter
){
2099 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
2103 // --- execute search
2104 TopGroups
<BytesRef
> topDocsResultSet
;
2106 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2107 } catch (ParseException e
) {
2108 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2109 luceneParseException
.setStackTrace(e
.getStackTrace());
2110 throw luceneParseException
;
2113 // --- initialize taxa, highlight matches ....
2114 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2117 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2118 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2120 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
2121 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2125 * @param namedAreaList at least one area must be in the list
2126 * @param distributionStatusList optional
2127 * @param toType toType
2128 * Optional parameter. Only used for debugging to print the toType documents
2129 * @param asFilter TODO
2131 * @throws IOException
2133 protected Query
createByDistributionJoinQuery(
2134 List
<NamedArea
> namedAreaList
,
2135 List
<PresenceAbsenceTerm
> distributionStatusList
,
2136 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
2137 ) throws IOException
{
2139 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2140 String toField
= "id"; // id in toType usually this is the TaxonBase index
2142 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
2144 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
2146 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
2148 return taxonAreaJoinQuery
;
2152 * @param namedAreaList
2153 * @param distributionStatusList
2154 * @param queryFactory
2157 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
2158 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
2159 Builder areaQueryBuilder
= new Builder();
2160 // area field from Distribution
2161 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2163 // status field from Distribution
2164 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2165 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2168 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2169 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2174 * This method has been primarily created for testing the area join query but might
2175 * also be useful in other situations
2177 * @param namedAreaList
2178 * @param distributionStatusList
2179 * @param classification
2180 * @param highlightFragments
2182 * @throws IOException
2184 protected LuceneSearch
prepareByDistributionSearch(
2185 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2186 Classification classification
, TaxonNode subtree
) throws IOException
{
2188 Builder finalQueryBuilder
= new Builder();
2190 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2192 // FIXME is this query factory using the wrong type?
2193 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2195 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2196 luceneSearch
.setSortFields(sortFields
);
2199 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2201 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2203 if(classification
!= null){
2204 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
2206 if(subtree
!= null){
2207 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
2209 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2210 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2211 luceneSearch
.setQuery(finalQuery
);
2213 return luceneSearch
;
2217 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2218 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2219 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2220 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2222 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2224 // --- execute search
2225 TopGroups
<BytesRef
> topDocsResultSet
;
2227 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2228 } catch (ParseException e
) {
2229 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2230 luceneParseException
.setStackTrace(e
.getStackTrace());
2231 throw luceneParseException
;
2234 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2235 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2237 // --- initialize taxa, highlight matches ....
2238 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2239 @SuppressWarnings("rawtypes")
2240 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2241 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2243 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2244 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2248 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2249 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2250 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2252 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2253 classification
, subtree
,
2254 null, languages
, highlightFragments
);
2255 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2256 includeUnpublished
, languages
, highlightFragments
, null);
2258 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2260 // --- execute search
2261 TopGroups
<BytesRef
> topDocsResultSet
;
2263 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2264 } catch (ParseException e
) {
2265 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2266 luceneParseException
.setStackTrace(e
.getStackTrace());
2267 throw luceneParseException
;
2270 // --- initialize taxa, highlight matches ....
2271 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2273 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2274 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2275 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2277 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2278 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2280 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2281 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2286 * @param queryString
2287 * @param classification
2290 * @param highlightFragments
2291 * @param directorySelectClass
2294 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2295 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2296 List
<Language
> languages
, boolean highlightFragments
) {
2298 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2299 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2301 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2303 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2304 languages
, descriptionElementQueryFactory
);
2306 luceneSearch
.setSortFields(sortFields
);
2307 luceneSearch
.setCdmTypRestriction(clazz
);
2308 luceneSearch
.setQuery(finalQuery
);
2309 if(highlightFragments
){
2310 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2313 return luceneSearch
;
2317 * @param queryString
2318 * @param classification
2321 * @param descriptionElementQueryFactory
2324 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2325 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2326 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2328 Builder finalQueryBuilder
= new Builder();
2329 Builder textQueryBuilder
= new Builder();
2331 if(!StringUtils
.isEmpty(queryString
)){
2333 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2336 Builder nameQueryBuilder
= new Builder();
2337 if(languages
== null || languages
.size() == 0){
2338 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2340 Builder languageSubQueryBuilder
= new Builder();
2341 for(Language lang
: languages
){
2342 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2344 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2345 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2347 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2350 // text field from TextData
2351 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2353 // --- TermBase fields - by representation ----
2354 // state field from CategoricalData
2355 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2357 // state field from CategoricalData
2358 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2360 // area field from Distribution
2361 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2363 // status field from Distribution
2364 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2366 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2369 // --- classification ----
2372 if(classification
!= null){
2373 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2375 if(subtree
!= null){
2376 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2379 // --- IdentifieableEntity fields - by uuid
2380 if(features
!= null && features
.size() > 0 ){
2381 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2384 // the description must be associated with a taxon
2385 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2387 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2388 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2393 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2395 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2396 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2398 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2399 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2401 UUID nameUuid
= taxon
.getName().getUuid();
2402 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2403 String epithetOfTaxon
= null;
2404 String infragenericEpithetOfTaxon
= null;
2405 String infraspecificEpithetOfTaxon
= null;
2406 if (taxonName
.isSpecies()){
2407 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2408 } else if (taxonName
.isInfraGeneric()){
2409 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2410 } else if (taxonName
.isInfraSpecific()){
2411 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2413 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2414 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2415 List
<String
> taxonNames
= new ArrayList
<>();
2417 for (TaxonNode node
: nodes
){
2418 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2419 // List<String> synonymsEpithet = new ArrayList<>();
2421 if (node
.getClassification().equals(classification
)){
2422 if (!node
.isTopmostNode()){
2423 TaxonNode parent
= node
.getParent();
2424 parent
= CdmBase
.deproxy(parent
);
2425 TaxonName parentName
= parent
.getTaxon().getName();
2426 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2427 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2429 //create inferred synonyms for species, subspecies
2430 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2432 Synonym inferredEpithet
= null;
2433 Synonym inferredGenus
= null;
2434 Synonym potentialCombination
= null;
2436 List
<String
> propertyPaths
= new ArrayList
<>();
2437 propertyPaths
.add("synonym");
2438 propertyPaths
.add("synonym.name");
2439 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2440 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2442 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2443 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2444 null, null,orderHintsSynonyms
,propertyPaths
);
2446 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2447 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2448 if (doWithMisappliedNames
){
2449 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2450 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2451 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2452 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2453 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2454 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2457 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2458 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2460 inferredEpithet
= createInferredEpithets(taxon
,
2461 zooHashMap
, taxonName
, epithetOfTaxon
,
2462 infragenericEpithetOfTaxon
,
2463 infraspecificEpithetOfTaxon
,
2464 taxonNames
, parentName
,
2465 synonymRelationOfParent
);
2467 inferredSynonyms
.add(inferredEpithet
);
2468 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2469 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2472 if (doWithMisappliedNames
){
2474 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2475 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2477 inferredEpithet
= createInferredEpithets(taxon
,
2478 zooHashMap
, taxonName
, epithetOfTaxon
,
2479 infragenericEpithetOfTaxon
,
2480 infraspecificEpithetOfTaxon
,
2481 taxonNames
, parentName
,
2484 inferredSynonyms
.add(inferredEpithet
);
2485 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2486 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2490 if (!taxonNames
.isEmpty()){
2491 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2492 if (!synNotInCDM
.isEmpty()){
2493 inferredSynonymsToBeRemoved
.clear();
2495 for (Synonym syn
:inferredSynonyms
){
2496 IZoologicalName name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2497 if (!synNotInCDM
.contains(name
.getNameCache())){
2498 inferredSynonymsToBeRemoved
.add(syn
);
2502 // Remove identified Synonyms from inferredSynonyms
2503 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2504 inferredSynonyms
.remove(synonym
);
2509 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2511 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2513 inferredGenus
= createInferredGenus(taxon
,
2514 zooHashMap
, taxonName
, epithetOfTaxon
,
2515 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2517 inferredSynonyms
.add(inferredGenus
);
2518 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2519 taxonNames
.add(inferredGenus
.getName().getNameCache());
2522 if (doWithMisappliedNames
){
2524 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2525 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2526 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2528 inferredSynonyms
.add(inferredGenus
);
2529 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2530 taxonNames
.add(inferredGenus
.getName().getNameCache());
2535 if (!taxonNames
.isEmpty()){
2536 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2537 IZoologicalName name
;
2538 if (!synNotInCDM
.isEmpty()){
2539 inferredSynonymsToBeRemoved
.clear();
2541 for (Synonym syn
:inferredSynonyms
){
2542 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2543 if (!synNotInCDM
.contains(name
.getNameCache())){
2544 inferredSynonymsToBeRemoved
.add(syn
);
2548 // Remove identified Synonyms from inferredSynonyms
2549 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2550 inferredSynonyms
.remove(synonym
);
2555 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2557 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2558 //for all synonyms of the parent...
2559 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2561 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2563 synName
= synonymRelationOfParent
.getName();
2565 // Set the sourceReference
2566 sourceReference
= synonymRelationOfParent
.getSec();
2568 // Determine the idInSource
2569 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2571 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2572 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2573 String synParentInfragenericName
= null;
2574 String synParentSpecificEpithet
= null;
2576 if (parentSynZooName
.isInfraGeneric()){
2577 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2579 if (parentSynZooName
.isSpecies()){
2580 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2583 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2584 synonymsGenus.put(synGenusName, idInSource);
2587 //for all synonyms of the taxon
2589 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2591 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2592 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2594 synParentInfragenericName
,
2595 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2597 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2598 inferredSynonyms
.add(potentialCombination
);
2599 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2600 taxonNames
.add(potentialCombination
.getName().getNameCache());
2604 if (doWithMisappliedNames
){
2606 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2608 TaxonName misappliedParentName
;
2610 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2611 misappliedParentName
= misappliedParent
.getName();
2613 HibernateProxyHelper
.deproxy(misappliedParent
);
2615 // Set the sourceReference
2616 sourceReference
= misappliedParent
.getSec();
2618 // Determine the idInSource
2619 String idInSourceParent
= getIdInSource(misappliedParent
);
2621 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2622 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2623 String synParentInfragenericName
= null;
2624 String synParentSpecificEpithet
= null;
2626 if (parentSynZooName
.isInfraGeneric()){
2627 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2629 if (parentSynZooName
.isSpecies()){
2630 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2633 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2634 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2635 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2636 potentialCombination
= createPotentialCombination(
2637 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2639 synParentInfragenericName
,
2640 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2642 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2643 inferredSynonyms
.add(potentialCombination
);
2644 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2645 taxonNames
.add(potentialCombination
.getName().getNameCache());
2650 if (!taxonNames
.isEmpty()){
2651 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2652 IZoologicalName name
;
2653 if (!synNotInCDM
.isEmpty()){
2654 inferredSynonymsToBeRemoved
.clear();
2655 for (Synonym syn
:inferredSynonyms
){
2657 name
= syn
.getName();
2658 }catch (ClassCastException e
){
2659 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2661 if (!synNotInCDM
.contains(name
.getNameCache())){
2662 inferredSynonymsToBeRemoved
.add(syn
);
2665 // Remove identified Synonyms from inferredSynonyms
2666 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2667 inferredSynonyms
.remove(synonym
);
2673 logger
.info("The synonym type is not defined.");
2674 return inferredSynonyms
;
2680 return inferredSynonyms
;
2683 private Synonym
createPotentialCombination(String idInSourceParent
,
2684 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2685 String synParentInfragenericName
, String synParentSpecificEpithet
,
2686 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2687 Synonym potentialCombination
;
2688 Reference sourceReference
;
2689 IZoologicalName inferredSynName
;
2690 HibernateProxyHelper
.deproxy(syn
);
2692 // Set sourceReference
2693 sourceReference
= syn
.getSec();
2694 if (sourceReference
== null){
2695 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2697 if (!parentSynZooName
.getTaxa().isEmpty()){
2698 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2700 sourceReference
= taxon
.getSec();
2703 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2705 String synTaxonInfraSpecificName
= null;
2707 if (parentSynZooName
.isSpecies()){
2708 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2711 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2712 synonymsEpithet.add(epithetName);
2715 //create potential combinations...
2716 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2718 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2719 if (zooSynName
.isSpecies()){
2720 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2721 if (parentSynZooName
.isInfraGeneric()){
2722 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2725 if (zooSynName
.isInfraSpecific()){
2726 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2727 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2729 if (parentSynZooName
.isInfraGeneric()){
2730 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2733 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2735 // Set the sourceReference
2736 potentialCombination
.setSec(sourceReference
);
2739 // Determine the idInSource
2740 String idInSourceSyn
= getIdInSource(syn
);
2742 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2743 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2744 inferredSynName
.addSource(originalSource
);
2745 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2746 potentialCombination
.addSource(originalSource
);
2749 return potentialCombination
;
2752 private Synonym
createInferredGenus(Taxon taxon
,
2753 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2754 String epithetOfTaxon
, String genusOfTaxon
,
2755 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2758 Synonym inferredGenus
;
2760 IZoologicalName inferredSynName
;
2761 synName
=syn
.getName();
2762 HibernateProxyHelper
.deproxy(syn
);
2764 // Determine the idInSource
2765 String idInSourceSyn
= getIdInSource(syn
);
2766 String idInSourceTaxon
= getIdInSource(taxon
);
2767 // Determine the sourceReference
2768 Reference sourceReference
= syn
.getSec();
2770 //logger.warn(sourceReference.getTitleCache());
2772 synName
= syn
.getName();
2773 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2774 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2775 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2776 synonymsEpithet.add(synSpeciesEpithetName);
2779 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2780 //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...
2782 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2783 if (zooParentName
.isInfraGeneric()){
2784 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2787 if (taxonName
.isSpecies()){
2788 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2790 if (taxonName
.isInfraSpecific()){
2791 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2792 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2795 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2797 // Set the sourceReference
2798 inferredGenus
.setSec(sourceReference
);
2800 // Add the original source
2801 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2802 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2803 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2804 inferredGenus
.addSource(originalSource
);
2806 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2807 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2808 inferredSynName
.addSource(originalSource
);
2809 originalSource
= null;
2812 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2813 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2814 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2815 inferredGenus
.addSource(originalSource
);
2817 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2818 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2819 inferredSynName
.addSource(originalSource
);
2820 originalSource
= null;
2823 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2825 return inferredGenus
;
2828 private Synonym
createInferredEpithets(Taxon taxon
,
2829 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2830 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2831 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2832 TaxonName parentName
, TaxonBase
<?
> syn
) {
2834 Synonym inferredEpithet
;
2836 IZoologicalName inferredSynName
;
2837 HibernateProxyHelper
.deproxy(syn
);
2839 // Determine the idInSource
2840 String idInSourceSyn
= getIdInSource(syn
);
2841 String idInSourceTaxon
= getIdInSource(taxon
);
2842 // Determine the sourceReference
2843 Reference sourceReference
= syn
.getSec();
2845 if (sourceReference
== null){
2846 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2847 sourceReference
= taxon
.getSec();
2850 synName
= syn
.getName();
2851 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2852 String synGenusName
= zooSynName
.getGenusOrUninomial();
2853 String synInfraGenericEpithet
= null;
2854 String synSpecificEpithet
= null;
2856 if (zooSynName
.getInfraGenericEpithet() != null){
2857 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2860 if (zooSynName
.isInfraSpecific()){
2861 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2864 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2865 synonymsGenus.put(synGenusName, idInSource);
2868 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2870 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2871 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2872 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2874 inferredSynName
.setGenusOrUninomial(synGenusName
);
2876 if (parentName
.isInfraGeneric()){
2877 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2879 if (taxonName
.isSpecies()){
2880 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2881 }else if (taxonName
.isInfraSpecific()){
2882 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2883 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2886 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2888 // Set the sourceReference
2889 inferredEpithet
.setSec(sourceReference
);
2891 /* Add the original source
2892 if (idInSource != null) {
2893 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2896 Reference citation = getCitation(syn);
2897 if (citation != null) {
2898 originalSource.setCitation(citation);
2899 inferredEpithet.addSource(originalSource);
2902 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2905 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2906 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2908 inferredEpithet
.addSource(originalSource
);
2910 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2911 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2913 inferredSynName
.addSource(originalSource
);
2915 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2917 return inferredEpithet
;
2921 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2922 * Very likely only useful for createInferredSynonyms().
2927 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2928 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2929 if (taxonName
== null) {
2930 taxonName
= zooHashMap
.get(uuid
);
2936 * Returns the idInSource for a given Synonym.
2939 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2940 String idInSource
= null;
2941 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2942 if (sources
.size() == 1) {
2943 IdentifiableSource source
= sources
.iterator().next();
2944 if (source
!= null) {
2945 idInSource
= source
.getIdInSource();
2947 } else if (sources
.size() > 1) {
2950 for (IdentifiableSource source
: sources
) {
2951 idInSource
+= source
.getIdInSource();
2952 if (count
< sources
.size()) {
2957 } else if (sources
.size() == 0){
2958 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2965 * Returns the citation for a given Synonym.
2968 private Reference
getCitation(Synonym syn
) {
2969 Reference citation
= null;
2970 Set
<IdentifiableSource
> sources
= syn
.getSources();
2971 if (sources
.size() == 1) {
2972 IdentifiableSource source
= sources
.iterator().next();
2973 if (source
!= null) {
2974 citation
= source
.getCitation();
2976 } else if (sources
.size() > 1) {
2977 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2984 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2985 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2987 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2988 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2989 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2991 return inferredSynonyms
;
2995 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2997 // TODO quickly implemented, create according dao !!!!
2998 Set
<TaxonNode
> nodes
= new HashSet
<>();
2999 Set
<Classification
> classifications
= new HashSet
<>();
3000 List
<Classification
> list
= new ArrayList
<>();
3002 if (taxonBase
== null) {
3006 taxonBase
= load(taxonBase
.getUuid());
3008 if (taxonBase
instanceof Taxon
) {
3009 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
3011 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
3013 nodes
.addAll(taxon
.getTaxonNodes());
3016 for (TaxonNode node
: nodes
) {
3017 classifications
.add(node
.getClassification());
3019 list
.addAll(classifications
);
3024 @Transactional(readOnly
= false)
3025 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
3027 TaxonRelationshipType oldRelationshipType
,
3028 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3029 UpdateResult result
= new UpdateResult();
3030 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
3031 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3032 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
3034 result
.addUpdatedObject(toTaxon
);
3035 result
.addUpdatedObject(result
.getCdmEntity());
3041 @Transactional(readOnly
= false)
3042 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
3043 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3045 UpdateResult result
= new UpdateResult();
3046 // Create new synonym using concept name
3047 TaxonName synonymName
= fromTaxon
.getName();
3049 // Remove concept relation from taxon
3050 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
3052 // Create a new synonym for the taxon
3054 if (synonymType
!= null
3055 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
3056 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
3057 toTaxon
.addHomotypicSynonym(synonym
);
3059 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
3061 //keep the publish flag
3062 synonym
.setPublish(fromTaxon
.isPublish());
3063 this.saveOrUpdate(toTaxon
);
3064 //TODO: configurator and classification
3065 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
3066 config
.setDeleteNameIfPossible(false);
3067 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
3068 result
.setCdmEntity(synonym
);
3069 result
.addUpdatedObject(toTaxon
);
3070 result
.addUpdatedObject(synonym
);
3075 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
3076 DeleteResult result
= new DeleteResult();
3077 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
3078 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
3079 if (taxonBase
instanceof Taxon
){
3080 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
3081 List
<String
> propertyPaths
= new ArrayList
<>();
3082 propertyPaths
.add("taxonNodes");
3083 Taxon taxon
= (Taxon
)load(taxonBaseUuid
, propertyPaths
);
3085 result
= isDeletableForTaxon(references
, taxonConfig
);
3087 if (taxonConfig
.isDeleteNameIfPossible()){
3088 if (taxonBase
.getName() != null){
3089 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), taxonConfig
.getNameDeletionConfig(), taxon
.getUuid());
3090 if (!nameResult
.isOk()){
3091 result
.addExceptions(nameResult
.getExceptions());
3097 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
3098 result
= isDeletableForSynonym(references
, synonymConfig
);
3099 if (synonymConfig
.isDeleteNameIfPossible() && taxonBase
.getName() != null){
3100 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), synonymConfig
.getNameDeletionConfig(), taxonBase
.getUuid());
3101 if (!nameResult
.isOk()){
3102 result
.addExceptions(nameResult
.getExceptions());
3111 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
3113 DeleteResult result
= new DeleteResult();
3114 for (CdmBase ref
: references
){
3115 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3116 String message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
3117 result
.addException(new ReferencedObjectUndeletableException(message
));
3118 result
.addRelatedObject(ref
);
3126 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
3127 String message
= null;
3128 DeleteResult result
= new DeleteResult();
3129 for (CdmBase ref
: references
){
3130 if (!(ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3132 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
3133 message
= "The taxon can't be deleted as long as it has synonyms.";
3135 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
3136 message
= "The taxon can't be deleted as long as it has factual data.";
3139 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
3140 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
3142 if (ref
instanceof TaxonNode
&& config
.getClassificationUuid() != null && !config
.isDeleteInAllClassifications() && !((TaxonNode
)ref
).getClassification().getUuid().equals(config
.getClassificationUuid())){
3143 message
= "The taxon can't be deleted as long as it is used in more than one classification";
3146 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
3147 if (!config
.isDeleteMisappliedNames() &&
3148 (((TaxonRelationship
)ref
).getType().isMisappliedName())){
3149 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3151 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3154 if (ref
instanceof PolytomousKeyNode
){
3155 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3158 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3159 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3162 /* //PolytomousKeyNode
3163 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3164 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3169 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3170 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3174 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3175 message
= "Taxon can't be deleted as it is used in a determination event";
3178 if (message
!= null){
3179 result
.addException(new ReferencedObjectUndeletableException(message
));
3180 result
.addRelatedObject(ref
);
3189 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3190 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3192 //preliminary implementation
3194 Set
<Taxon
> taxa
= new HashSet
<>();
3195 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3196 if (taxonBase
== null){
3197 return new IncludedTaxaDTO();
3198 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3199 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3201 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3202 //TODO partial synonyms ??
3203 //TODO synonyms in general
3204 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3205 taxa
.add(syn
.getAcceptedTaxon());
3207 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3210 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3212 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3213 related
= makeRelatedIncluded(related
, result
, config
);
3220 * @param uncheckedTaxa
3221 * @param existingTaxa
3224 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3226 * @return the set of conceptually related taxa for further use
3228 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3231 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3232 for (Taxon taxon
: uncheckedTaxa
){
3233 taxonNodes
.addAll(taxon
.getTaxonNodes());
3236 Set
<Taxon
> children
= new HashSet
<>();
3237 if (! config
.onlyCongruent
){
3238 for (TaxonNode node
: taxonNodes
){
3239 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3240 for (TaxonNode child
: childNodes
){
3241 children
.add(child
.getTaxon());
3244 children
.remove(null); // just to be on the save side
3247 Iterator
<Taxon
> it
= children
.iterator();
3248 while(it
.hasNext()){
3249 UUID uuid
= it
.next().getUuid();
3250 if (existingTaxa
.contains(uuid
)){
3253 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3258 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3259 uncheckedAndChildren
.addAll(children
);
3261 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3264 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3269 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3270 * @return the set of these computed taxa
3272 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3273 Set
<Taxon
> result
= new HashSet
<>();
3275 for (Taxon taxon
: unchecked
){
3276 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3277 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3279 for (TaxonRelationship fromRel
: fromRelations
){
3280 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3283 TaxonRelationshipType fromRelType
= fromRel
.getType();
3284 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3285 !config
.onlyCongruent
&& (
3286 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3287 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3290 result
.add(fromRel
.getToTaxon());
3294 for (TaxonRelationship toRel
: toRelations
){
3295 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3298 TaxonRelationshipType fromRelType
= toRel
.getType();
3299 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3300 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3301 result
.add(toRel
.getFromTaxon());
3306 Iterator
<Taxon
> it
= result
.iterator();
3307 while(it
.hasNext()){
3308 UUID uuid
= it
.next().getUuid();
3309 if (existingTaxa
.contains(uuid
)){
3312 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3319 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3320 @SuppressWarnings("rawtypes")
3321 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3322 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3327 @Transactional(readOnly
= true)
3328 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3329 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3330 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3331 Integer pageNumber
, List
<String
> propertyPaths
) {
3332 if (subtreeFilter
== null){
3333 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3336 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3337 List
<Object
[]> daoResults
= new ArrayList
<>();
3338 if(numberOfResults
> 0) { // no point checking again
3339 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3340 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3343 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3344 for (Object
[] daoObj
: daoResults
){
3346 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3348 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3351 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3355 @Transactional(readOnly
= true)
3356 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3357 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3358 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3359 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3360 if (subtreeFilter
== null){
3361 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3364 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3365 List
<Object
[]> daoResults
= new ArrayList
<>();
3366 if(numberOfResults
> 0) { // no point checking again
3367 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3368 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3371 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3372 for (Object
[] daoObj
: daoResults
){
3374 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3376 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3379 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3383 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3384 UpdateResult result
= new UpdateResult();
3386 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3387 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3388 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3389 //reload to avoid session conflicts
3390 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3392 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3393 if(description
.isProtectedTitleCache()){
3394 String separator
= "";
3395 if(!StringUtils
.isBlank(description
.getTitleCache())){
3398 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3400 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3401 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3402 description
.addAnnotation(annotation
);
3403 toTaxon
.addDescription(description
);
3404 dao
.saveOrUpdate(toTaxon
);
3405 dao
.saveOrUpdate(fromTaxon
);
3406 result
.addUpdatedObject(toTaxon
);
3407 result
.addUpdatedObject(fromTaxon
);
3415 @Transactional(readOnly
= false)
3416 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3417 UUID acceptedTaxonUuid
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
) {
3418 TaxonBase
<?
> base
= this.load(synonymUUid
);
3419 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3420 base
= this.load(acceptedTaxonUuid
);
3421 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3423 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
, setNameInSource
, newUuidForAcceptedTaxon
);
3427 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3428 Set
<TaxonRelationshipType
> inversTypes
,
3429 Direction direction
, boolean groupMisapplications
,
3430 boolean includeUnpublished
,
3431 Integer pageSize
, Integer pageNumber
) {
3432 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3433 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3435 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3437 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3438 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3439 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3441 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3443 //TODO paging is difficult because misapplication string is an attribute
3445 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3446 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3447 // if(numberOfResults > 0) { // no point checking again
3448 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3451 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3454 List
<Language
> languages
= null;
3456 direction
= Direction
.relatedTo
;
3457 //TODO order hints, property path
3458 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3459 for (TaxonRelationship relation
: relations
){
3460 dto
.addRelation(relation
, direction
, languages
);
3464 direction
= Direction
.relatedFrom
;
3465 //TODO order hints, property path
3466 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3467 for (TaxonRelationship relation
: relations
){
3468 dto
.addRelation(relation
, direction
, languages
);
3471 if (groupMisapplications
){
3473 dto
.createMisapplicationString();