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
;
23 import java
.util
.stream
.Collectors
;
25 import javax
.persistence
.EntityNotFoundException
;
27 import org
.apache
.commons
.lang3
.StringUtils
;
28 import org
.apache
.log4j
.Logger
;
29 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
30 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
31 import org
.apache
.lucene
.search
.BooleanQuery
;
32 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
33 import org
.apache
.lucene
.search
.Query
;
34 import org
.apache
.lucene
.search
.SortField
;
35 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
36 import org
.apache
.lucene
.search
.join
.ScoreMode
;
37 import org
.apache
.lucene
.util
.BytesRef
;
38 import org
.hibernate
.criterion
.Criterion
;
39 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
40 import org
.springframework
.stereotype
.Service
;
41 import org
.springframework
.transaction
.annotation
.Transactional
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
47 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
48 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
49 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
51 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
52 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
53 import eu
.etaxonomy
.cdm
.api
.service
.dto
.TaxonRelationshipsDTO
;
54 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
55 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
56 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
57 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
58 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.AbstractPagerImpl
;
59 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
64 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
65 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
66 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
67 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
68 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
69 import eu
.etaxonomy
.cdm
.api
.util
.TaxonRelationshipEdge
;
70 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
71 import eu
.etaxonomy
.cdm
.compare
.taxon
.HomotypicGroupTaxonComparator
;
72 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonComparator
;
73 import eu
.etaxonomy
.cdm
.exception
.UnpublishedException
;
74 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
75 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
76 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
77 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
78 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
79 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
80 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
81 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
82 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
83 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
84 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
85 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
86 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
87 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
88 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
89 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementSource
;
90 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
91 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
92 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
93 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
94 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
95 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
96 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
97 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
98 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
99 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
100 import eu
.etaxonomy
.cdm
.model
.media
.ExternalLink
;
101 import eu
.etaxonomy
.cdm
.model
.media
.ExternalLinkType
;
102 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
103 import eu
.etaxonomy
.cdm
.model
.metadata
.SecReferenceHandlingEnum
;
104 import eu
.etaxonomy
.cdm
.model
.metadata
.SecReferenceHandlingSwapEnum
;
105 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
106 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
107 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
108 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
109 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
110 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
111 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
112 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
113 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
114 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
117 import eu
.etaxonomy
.cdm
.model
.taxon
.SecundumSource
;
118 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
119 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
120 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
121 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
122 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
123 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
124 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
125 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
126 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.Restriction
;
127 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
128 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
129 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
130 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
131 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
132 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
133 import eu
.etaxonomy
.cdm
.persistence
.dto
.MergeResult
;
134 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
135 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
136 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
137 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
138 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
139 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
143 * @author a.kohlbecker
147 @Transactional(readOnly
= true)
148 public class TaxonServiceImpl
149 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
150 implements ITaxonService
{
152 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
154 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
156 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
158 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
161 private ITaxonNodeDao taxonNodeDao
;
164 private ITaxonNameDao nameDao
;
167 private INameService nameService
;
170 private IOccurrenceService occurrenceService
;
173 private ITaxonNodeService nodeService
;
176 private IDescriptionService descriptionService
;
179 private IReferenceService referenceService
;
182 // private IOrderedTermVocabularyDao orderedVocabularyDao;
185 private IOccurrenceDao occurrenceDao
;
188 private IClassificationDao classificationDao
;
191 private AbstractBeanInitializer beanInitializer
;
194 private ILuceneIndexToolProvider luceneIndexToolProvider
;
196 //************************ CONSTRUCTOR ****************************/
197 public TaxonServiceImpl(){
198 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
201 // ****************************** METHODS ********************************/
204 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
205 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
209 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
210 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
214 @Transactional(readOnly
= false)
215 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
, SecReferenceHandlingSwapEnum secHandling
, Reference newSecAcc
, Reference newSecSyn
){
216 if (newUuidForAcceptedTaxon
){
217 return swapSynonymAndAcceptedTaxonNewUuid(synonym
, acceptedTaxon
, setNameInSource
, secHandling
, newSecAcc
, newSecSyn
);
219 return swapSynonymAndAcceptedTaxon(synonym
, acceptedTaxon
, setNameInSource
, secHandling
, newSecAcc
, newSecSyn
);
223 private UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
, SecReferenceHandlingSwapEnum secHandling
, Reference newSecAcc
, Reference newSecSyn
){
224 UpdateResult result
= new UpdateResult();
225 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
227 TaxonName synonymName
= synonym
.getName();
228 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
229 Reference secAccepted
= acceptedTaxon
.getSec();
230 String microRefSecAccepted
= acceptedTaxon
.getSecMicroReference();
231 Reference secSynonym
= synonym
.getSec();
232 String microRefSecSynonym
= synonym
.getSecMicroReference();
234 // if (secHandling.equals(SecReferenceHandlingSwapEnum.AlwaysDelete) || (secAccepted != null && secSynonym != null && !secAccepted.getUuid().equals(secSynonym.getUuid()) && (secHandling.equals(SecReferenceHandlingSwapEnum.AlwaysSelect) || secHandling.equals(SecReferenceHandlingSwapEnum.KeepOrSelect)))){
235 // secAccepted = null;
236 // microRefSecAccepted = null;
237 // secSynonym = null;
238 // microRefSecSynonym = null;
242 Set
<ExternalLink
> accLinks
= new HashSet
<>();
243 if (acceptedTaxon
.getSecSource() != null){
244 for (ExternalLink link
: acceptedTaxon
.getSecSource().getLinks()){
245 accLinks
.add(ExternalLink
.NewInstance(ExternalLinkType
.Unknown
, link
.getUri()));
248 acceptedTaxon
.setName(synonymName
);
249 acceptedTaxon
.setSec(newSecAcc
);
250 // acceptedTaxon.setSecMicroReference(synonym.getSecMicroReference());
252 // if (synonym.getSecSource()!= null && synonym.getSecSource().getLinks() != null){
253 // acceptedTaxon.getSecSource().getLinks().clear();
254 // for (ExternalLink link: synonym.getSecSource().getLinks()){
255 // acceptedTaxon.getSecSource().addLink(ExternalLink.NewInstance(ExternalLinkType.Unknown, link.getUri()));
259 synonym
.setName(taxonName
);
260 synonym
.setSec(newSecSyn
);
261 // synonym.setSecMicroReference(microRefSecAccepted);
262 // if (synonym.getSecSource() != null){
263 // synonym.getSecSource().getLinks().clear();
264 // for (ExternalLink link: accLinks){
265 // synonym.getSecSource().addLink(link);
270 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, acceptedTaxon
.getDescriptions());
272 acceptedTaxon
.resetTitleCache();
273 synonym
.resetTitleCache();
275 MergeResult mergeTaxon
= merge(acceptedTaxon
, true);
276 MergeResult mergeSynonym
= merge(synonym
, true);
277 result
.setCdmEntity((CdmBase
) mergeTaxon
.getMergedEntity());
278 result
.addUpdatedObject((CdmBase
) mergeSynonym
.getMergedEntity());
283 private UpdateResult
swapSynonymAndAcceptedTaxonNewUuid(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
, SecReferenceHandlingSwapEnum secHandling
, Reference newSecAcc
, Reference newSecSyn
){
284 UpdateResult result
= new UpdateResult();
285 acceptedTaxon
.removeSynonym(synonym
);
286 TaxonName synonymName
= synonym
.getName();
287 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
288 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
290 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(taxonName
.getHomotypicalGroup());
291 synonymName
.removeTaxonBase(synonym
);
293 List
<Synonym
> synonyms
= new ArrayList
<>();
294 for (Synonym syn
: acceptedTaxon
.getSynonyms()){
295 syn
= HibernateProxyHelper
.deproxy(syn
, Synonym
.class);
298 for (Synonym syn
: synonyms
){
299 acceptedTaxon
.removeSynonym(syn
);
301 Taxon newTaxon
= acceptedTaxon
.clone(true, true, false, true);
302 newTaxon
.setSec(newSecAcc
);
305 Set
<TaxonDescription
> descriptionsToCopy
= new HashSet
<>(acceptedTaxon
.getDescriptions());
306 for (TaxonDescription description
: descriptionsToCopy
){
307 newTaxon
.addDescription(description
);
310 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, newTaxon
.getDescriptions());
312 newTaxon
.setName(synonymName
);
314 newTaxon
.setPublish(synonym
.isPublish());
315 for (Synonym syn
: synonyms
){
316 if (!syn
.getName().equals(newTaxon
.getName())){
317 newTaxon
.addSynonym(syn
, syn
.getType());
321 //move all data to new taxon
322 //Move Taxon RelationShips to new Taxon
323 for(TaxonRelationship taxonRelationship
: newTaxon
.getTaxonRelations()){
324 newTaxon
.removeTaxonRelation(taxonRelationship
);
327 for(TaxonRelationship taxonRelationship
: acceptedTaxon
.getTaxonRelations()){
328 Taxon fromTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getFromTaxon());
329 Taxon toTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getToTaxon());
330 if (fromTaxon
== acceptedTaxon
){
331 newTaxon
.addTaxonRelation(taxonRelationship
.getToTaxon(), taxonRelationship
.getType(),
332 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
334 }else if(toTaxon
== acceptedTaxon
){
335 fromTaxon
.addTaxonRelation(newTaxon
, taxonRelationship
.getType(),
336 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
337 saveOrUpdate(fromTaxon
);
340 logger
.warn("Taxon is not part of its own Taxonrelationship");
343 // Remove old relationships
344 fromTaxon
.removeTaxonRelation(taxonRelationship
);
345 toTaxon
.removeTaxonRelation(taxonRelationship
);
346 taxonRelationship
.setToTaxon(null);
347 taxonRelationship
.setFromTaxon(null);
351 List
<TaxonNode
> nodes
= new ArrayList
<>(acceptedTaxon
.getTaxonNodes());
352 for (TaxonNode node
: nodes
){
353 node
= HibernateProxyHelper
.deproxy(node
);
354 TaxonNode parent
= node
.getParent();
355 acceptedTaxon
.removeTaxonNode(node
);
356 node
.setTaxon(newTaxon
);
358 parent
.addChildNode(node
, null, null);
363 Synonym newSynonym
= synonym
.clone();
364 newSynonym
.setName(taxonName
);
365 newSynonym
.setPublish(acceptedTaxon
.isPublish());
366 newSynonym
.setSec(newSecSyn
);
367 if (sameHomotypicGroup
){
368 newTaxon
.addSynonym(newSynonym
, SynonymType
.HOMOTYPIC_SYNONYM_OF());
370 newTaxon
.addSynonym(newSynonym
, SynonymType
.HETEROTYPIC_SYNONYM_OF());
374 TaxonDeletionConfigurator conf
= new TaxonDeletionConfigurator();
375 conf
.setDeleteNameIfPossible(false);
376 SynonymDeletionConfigurator confSyn
= new SynonymDeletionConfigurator();
377 confSyn
.setDeleteNameIfPossible(false);
378 result
.setCdmEntity(newTaxon
);
380 DeleteResult deleteResult
= deleteTaxon(acceptedTaxon
.getUuid(), conf
, null);
381 if (synonym
.isPersited()){
382 synonym
.setSecSource(null);
383 deleteResult
.includeResult(deleteSynonym(synonym
.getUuid(), confSyn
));
385 result
.includeResult(deleteResult
);
390 private void handleNameUsedInSourceForSwap(boolean setNameInSource
, TaxonName taxonName
, String oldTaxonTitleCache
,
391 Set
<TaxonDescription
> descriptions
) {
392 for(TaxonDescription description
: descriptions
){
393 String message
= "Description copied from former accepted taxon: %s (Old title: %s)";
394 message
= String
.format(message
, oldTaxonTitleCache
, description
.getTitleCache());
395 description
.setTitleCache(message
, true);
397 for (DescriptionElementBase element
: description
.getElements()){
398 for (DescriptionElementSource source
: element
.getSources()){
399 if (source
.getNameUsedInSource() == null){
400 source
.setNameUsedInSource(taxonName
);
409 @Transactional(readOnly
= false)
410 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, Reference newSecRef
, String microRef
, SecReferenceHandlingEnum secHandling
, boolean deleteSynonym
) {
411 UpdateResult result
= new UpdateResult();
412 TaxonName acceptedName
= acceptedTaxon
.getName();
413 TaxonName synonymName
= synonym
.getName();
414 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
416 //check synonym is not homotypic
417 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
418 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
419 result
.addException(new HomotypicalGroupChangeException(message
));
424 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, newSecRef
, microRef
);
425 newAcceptedTaxon
.setPublish(synonym
.isPublish());
426 dao
.save(newAcceptedTaxon
);
427 result
.setCdmEntity(newAcceptedTaxon
);
428 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
429 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
431 for (Synonym heteroSynonym
: heteroSynonyms
){
432 if (secHandling
== null){
433 heteroSynonym
.setSec(newSecRef
);
435 if (synonym
.equals(heteroSynonym
)){
436 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
438 //move synonyms in same homotypic group to new accepted taxon
439 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
442 dao
.saveOrUpdate(acceptedTaxon
);
443 result
.addUpdatedObject(acceptedTaxon
);
448 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
449 config
.setDeleteNameIfPossible(false);
450 this.deleteSynonym(synonym
, config
);
452 } catch (Exception e
) {
453 result
.addException(e
);
461 @Transactional(readOnly
= false)
462 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
463 UUID acceptedTaxonUuid
,
464 UUID newParentNodeUuid
,
466 String microReference
,
467 SecReferenceHandlingEnum secHandling
,
468 boolean deleteSynonym
) {
469 UpdateResult result
= new UpdateResult();
470 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
471 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
472 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
473 Reference newSecRef
= null;
474 switch (secHandling
){
478 case UseNewParentSec
:
479 newSecRef
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
482 Reference parentSec
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
483 Reference synSec
= synonym
.getSec();
484 if (synSec
!= null ){
485 newSecRef
= CdmBase
.deproxy(synSec
);
487 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
491 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
497 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, newSecRef
, microReference
, secHandling
, deleteSynonym
);
498 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
500 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
501 taxonNodeDao
.save(newNode
);
502 result
.addUpdatedObject(newTaxon
);
503 result
.addUpdatedObject(acceptedTaxon
);
504 result
.setCdmEntity(newNode
);
509 @Transactional(readOnly
= false)
510 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
512 TaxonRelationshipType taxonRelationshipType
,
514 String microcitation
){
516 UpdateResult result
= new UpdateResult();
517 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
518 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
519 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
520 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
521 // result.setCdmEntity(relatedTaxon);
522 result
.addUpdatedObject(relatedTaxon
);
523 result
.addUpdatedObject(toTaxon
);
528 @Transactional(readOnly
= false)
529 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
530 // Get name from synonym
531 if (synonym
== null){
535 UpdateResult result
= new UpdateResult();
537 TaxonName synonymName
= synonym
.getName();
539 /* // remove synonym from taxon
540 toTaxon.removeSynonym(synonym);
542 // Create a taxon with synonym name
543 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
544 fromTaxon
.setPublish(synonym
.isPublish());
546 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
548 // Add taxon relation
549 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
550 result
.setCdmEntity(fromTaxon
);
551 // since we are swapping names, we have to detach the name from the synonym completely.
552 // Otherwise the synonym will still be in the list of typified names.
553 // synonym.getName().removeTaxonBase(synonym);
554 result
.includeResult(this.deleteSynonym(synonym
, null));
559 @Transactional(readOnly
= false)
561 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
562 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
564 TaxonName synonymName
= synonym
.getName();
565 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
568 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
569 newHomotypicalGroup
.addTypifiedName(synonymName
);
571 //remove existing basionym relationships
572 synonymName
.removeBasionyms();
574 //add basionym relationship
575 if (setBasionymRelationIfApplicable
){
576 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
577 for (TaxonName basionym
: basionyms
){
578 synonymName
.addBasionym(basionym
);
582 //set synonym relationship correctly
583 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
585 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
586 if (acceptedTaxon
!= null){
588 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
589 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
590 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
591 synonym
.setType(newRelationType
);
593 if (hasNewTargetTaxon
){
594 acceptedTaxon
.removeSynonym(synonym
, false);
597 if (hasNewTargetTaxon
){
598 @SuppressWarnings("null")
599 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
600 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
601 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
602 targetTaxon
.addSynonym(synonym
, relType
);
607 @Transactional(readOnly
= false)
608 public UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
610 clazz
= TaxonBase
.class;
612 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
617 protected void setDao(ITaxonDao dao
) {
622 public <T
extends TaxonBase
> Pager
<T
> findTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
623 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
624 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
);
626 List
<T
> results
= new ArrayList
<>();
627 if(numberOfResults
> 0) { // no point checking again
628 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
,
629 pageSize
, pageNumber
, propertyPaths
);
632 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
636 public <T
extends TaxonBase
> List
<T
> listTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
637 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
639 return findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infragenericEpithet
, authorshipCache
, rank
,
640 pageSize
, pageNumber
, propertyPaths
).getRecords();
644 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
645 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
646 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
648 List
<TaxonRelationship
> results
= new ArrayList
<>();
649 if(numberOfResults
> 0) { // no point checking again
650 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
656 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
657 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
658 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
660 List
<TaxonRelationship
> results
= new ArrayList
<>();
661 if(numberOfResults
> 0) { // no point checking again
662 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
664 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
668 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
669 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
670 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
672 List
<TaxonRelationship
> results
= new ArrayList
<>();
673 if(numberOfResults
> 0) { // no point checking again
674 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
680 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
681 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
682 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
684 List
<TaxonRelationship
> results
= new ArrayList
<>();
685 if(numberOfResults
> 0) { // no point checking again
686 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
688 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
692 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
693 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
695 Long numberOfResults
= dao
.countTaxonRelationships(types
);
696 List
<TaxonRelationship
> results
= new ArrayList
<>();
697 if(numberOfResults
> 0) {
698 results
= dao
.getTaxonRelationships(types
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
704 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
705 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
706 return page(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, INCLUDE_UNPUBLISHED
);
710 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
711 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
, boolean includeUnpublished
) {
714 long resultSize
= dao
.count(clazz
, restrictions
);
715 if(AbstractPagerImpl
.hasResultsInRange(resultSize
, pageIndex
, pageSize
)){
716 records
= dao
.list(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, includeUnpublished
);
718 records
= new ArrayList
<>();
720 Pager
<S
> pager
= new DefaultPagerImpl
<>(pageIndex
, resultSize
, pageSize
, records
);
725 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
726 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
728 Synonym synonym
= null;
731 synonym
= (Synonym
) dao
.load(synonymUuid
);
732 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
733 } catch (ClassCastException e
){
734 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
735 } catch (NullPointerException e
){
736 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
739 Classification classificationFilter
= null;
740 if(classificationUuid
!= null){
742 classificationFilter
= classificationDao
.load(classificationUuid
);
743 } catch (NullPointerException e
){
744 //TODO not sure, why an NPE should be thrown in the above load method
745 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
747 if(classificationFilter
== null){
748 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
752 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
754 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
755 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
763 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
764 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
766 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
767 relatedTaxa
.remove(taxon
);
768 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
773 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
774 * <code>taxon</code> supplied as parameter.
777 * @param includeRelationships
779 * @param maxDepth can be <code>null</code> for infinite depth
782 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
783 boolean includeUnpublished
, Integer maxDepth
) {
789 if(includeRelationships
.isEmpty()){
793 if(maxDepth
!= null) {
796 if(logger
.isDebugEnabled()){
797 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
799 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
800 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
801 for (TaxonRelationship taxRel
: taxonRelationships
) {
804 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
807 // filter by includeRelationships
808 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
809 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
810 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
811 if(logger
.isDebugEnabled()){
812 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
814 taxa
.add(taxRel
.getToTaxon());
815 if(maxDepth
== null || maxDepth
> 0) {
816 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
819 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
820 taxa
.add(taxRel
.getFromTaxon());
821 if(logger
.isDebugEnabled()){
822 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
824 if(maxDepth
== null || maxDepth
> 0) {
825 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
835 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
836 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
838 List
<Synonym
> results
= new ArrayList
<>();
839 if(numberOfResults
> 0) { // no point checking again
840 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
843 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
847 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
848 List
<List
<Synonym
>> result
= new ArrayList
<>();
849 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
850 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
853 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
856 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
857 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
858 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
865 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
866 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
867 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
869 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
873 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
874 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
875 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
876 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
877 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
878 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
880 return heterotypicSynonymyGroups
;
884 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
886 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
887 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
888 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(), config
.isDoIncludeAuthors(),
889 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
890 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
892 return new ArrayList
<>();
897 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
899 @SuppressWarnings("rawtypes")
900 List
<IdentifiableEntity
> results
= new ArrayList
<>();
901 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
902 List
<TaxonBase
> taxa
= null;
905 long numberTaxaResults
= 0L;
907 List
<String
> propertyPath
= new ArrayList
<>();
908 if(configurator
.getTaxonPropertyPath() != null){
909 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
912 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
913 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
915 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
916 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
917 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
918 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
921 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
922 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
923 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
924 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
925 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
926 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
930 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
933 results
.addAll(taxa
);
936 numberOfResults
+= numberTaxaResults
;
938 // Names without taxa
939 if (configurator
.isDoNamesWithoutTaxa()) {
940 int numberNameResults
= 0;
942 List
<TaxonName
> names
=
943 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
944 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
945 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
946 if (names
.size() > 0) {
947 for (TaxonName taxonName
: names
) {
948 if (taxonName
.getTaxonBases().size() == 0) {
949 results
.add(taxonName
);
953 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
954 numberOfResults
+= numberNameResults
;
958 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
961 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
962 return dao
.getUuidAndTitleCache(limit
, pattern
);
966 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
967 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
971 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
972 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
973 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
976 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
978 // logger.setLevel(Level.TRACE);
979 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
981 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - START");}
983 Set
<Taxon
> taxa
= new HashSet
<>();
984 List
<Media
> taxonMedia
= new ArrayList
<>();
985 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
987 if (limitToGalleries
== null) {
988 limitToGalleries
= false;
991 // --- resolve related taxa
992 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
993 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - resolve related taxa");}
994 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
997 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
999 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
1000 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - includeTaxonDescriptions");}
1001 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
1002 // --- TaxonDescriptions
1003 for (Taxon t
: taxa
) {
1004 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
1006 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
1007 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
1008 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
1009 for (Media media
: element
.getMedia()) {
1010 if(taxonDescription
.isImageGallery()){
1011 taxonMedia
.add(media
);
1014 nonImageGalleryImages
.add(media
);
1020 //put images from image gallery first (#3242)
1021 taxonMedia
.addAll(nonImageGalleryImages
);
1025 if(includeOccurrences
!= null && includeOccurrences
) {
1026 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - includeOccurrences");}
1027 @SuppressWarnings("rawtypes")
1028 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
1030 for (Taxon t
: taxa
) {
1031 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
1033 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
1035 // direct media removed from specimen #3597
1036 // taxonMedia.addAll(occurrence.getMedia());
1038 // SpecimenDescriptions
1039 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
1040 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
1041 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
1042 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
1043 for (DescriptionElementBase element
: elements
) {
1044 for (Media media
: element
.getMedia()) {
1045 taxonMedia
.add(media
);
1051 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
1052 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
1054 //TODO why may collections have media attached? #
1055 if (derivedUnit
.getCollection() != null){
1056 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
1059 //media in hierarchy
1060 taxonMedia
.addAll(occurrenceService
.getMediaInHierarchy(occurrence
, null, null, propertyPath
).getRecords());
1064 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
1065 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - includeTaxonNameDescriptions");}
1066 // --- TaxonNameDescription
1067 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
1068 for (Taxon t
: taxa
) {
1069 nameDescriptions
.addAll(t
.getName().getDescriptions());
1071 for(TaxonNameDescription nameDescription
: nameDescriptions
){
1072 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
1073 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
1074 for (DescriptionElementBase element
: elements
) {
1075 for (Media media
: element
.getMedia()) {
1076 taxonMedia
.add(media
);
1083 taxonMedia
= deduplicateMedia(taxonMedia
);
1085 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - initialize");}
1086 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
1088 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - END");}
1093 private List
<Media
> deduplicateMedia(List
<Media
> taxonMedia
) {
1094 return taxonMedia
.stream().distinct().collect(Collectors
.toList());
1098 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
1099 return this.dao
.loadList(listOfIDs
, null, null);
1103 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
1104 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
1108 public long countSynonyms(boolean onlyAttachedToTaxon
){
1109 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
1113 @Transactional(readOnly
=false)
1114 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
1116 if (config
== null){
1117 config
= new TaxonDeletionConfigurator();
1119 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
1120 DeleteResult result
= new DeleteResult();
1123 result
.addException(new Exception ("The taxon was already deleted."));
1126 taxon
= HibernateProxyHelper
.deproxy(taxon
);
1127 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
1128 config
.setClassificationUuid(classificationUuid
);
1129 result
= isDeletable(taxonUUID
, config
);
1132 // --- DeleteSynonymRelations
1133 if (config
.isDeleteSynonymRelations()){
1134 boolean removeSynonymNameFromHomotypicalGroup
= false;
1135 // use tmp Set to avoid concurrent modification
1136 Set
<Synonym
> synsToDelete
= new HashSet
<>();
1137 synsToDelete
.addAll(taxon
.getSynonyms());
1138 for (Synonym synonym
: synsToDelete
){
1139 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
1141 // --- DeleteSynonymsIfPossible
1142 if (config
.isDeleteSynonymsIfPossible()){
1144 boolean newHomotypicGroupIfNeeded
= true;
1145 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
1146 result
.includeResult(deleteSynonym(synonym
, synConfig
));
1151 // --- DeleteTaxonRelationships
1152 if (! config
.isDeleteTaxonRelationships()){
1153 if (taxon
.getTaxonRelations().size() > 0){
1155 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1156 "Remove taxon from all relations to other taxa prior to deletion."));
1159 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
1160 configRelTaxon
.setDeleteTaxonNodes(false);
1161 configRelTaxon
.setDeleteConceptRelationships(true);
1163 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
1164 if (config
.isDeleteMisappliedNames()
1165 && taxRel
.getType().isMisappliedName()
1166 && taxon
.equals(taxRel
.getToTaxon())){
1167 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
1168 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
1170 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
1171 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1172 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
1173 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1176 taxon
.removeTaxonRelation(taxRel
);
1181 if (config
.isDeleteDescriptions()){
1182 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
1183 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
1184 for (TaxonDescription desc
: descriptions
){
1185 //TODO use description delete configurator ?
1186 //FIXME check if description is ALWAYS deletable
1187 if (desc
.getDescribedSpecimenOrObservation() != null){
1189 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1190 " which also describes specimens or observations"));
1193 removeDescriptions
.add(desc
);
1196 for (TaxonDescription desc
: removeDescriptions
){
1197 taxon
.removeDescription(desc
);
1198 descriptionService
.delete(desc
);
1205 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1206 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1208 if (taxon
.getTaxonNodes().size() != 0){
1209 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1210 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1211 TaxonNode node
= null;
1212 boolean deleteChildren
;
1213 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1214 deleteChildren
= true;
1216 deleteChildren
= false;
1218 boolean success
= true;
1219 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1220 while (iterator
.hasNext()){
1221 node
= iterator
.next();
1222 if (node
.getClassification().equals(classification
)){
1228 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1229 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1230 nodeService
.delete(node
);
1231 result
.addDeletedObject(node
);
1234 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1236 } else if (config
.isDeleteInAllClassifications()){
1237 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1238 nodesList
.addAll(taxon
.getTaxonNodes());
1239 for (ITaxonTreeNode treeNode
: nodesList
){
1240 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1241 if(!deleteChildren
){
1242 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1243 for (Object childNode
: childNodes
){
1244 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1245 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1249 config
.getTaxonNodeConfig().setDeleteElement(false);
1250 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1251 if (!resultNodes
.isOk()){
1252 result
.addExceptions(resultNodes
.getExceptions());
1253 result
.setStatus(resultNodes
.getStatus());
1255 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1260 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1264 TaxonName name
= taxon
.getName();
1265 taxon
.setName(null);
1266 this.saveOrUpdate(taxon
);
1268 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1271 result
.addDeletedObject(taxon
);
1272 }catch(Exception e
){
1273 result
.addException(e
);
1278 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1282 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1283 DeleteResult nameResult
= new DeleteResult();
1284 //remove name if possible (and required)
1286 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1288 if (nameResult
.isError() || nameResult
.isAbort()){
1289 result
.addRelatedObject(name
);
1290 result
.addExceptions(nameResult
.getExceptions());
1292 result
.includeResult(nameResult
);
1301 @Transactional(readOnly
= false)
1302 public DeleteResult
delete(UUID synUUID
){
1303 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1304 return this.deleteSynonym(syn
, null);
1308 @Transactional(readOnly
= false)
1309 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1310 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1314 @Transactional(readOnly
= false)
1315 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1316 DeleteResult result
= new DeleteResult();
1317 if (synonym
== null){
1319 result
.addException(new Exception("The synonym was already deleted."));
1323 if (config
== null){
1324 config
= new SynonymDeletionConfigurator();
1327 result
= isDeletable(synonym
.getUuid(), config
);
1331 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1334 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1336 if (accTaxon
!= null){
1337 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1338 accTaxon
.removeSynonym(synonym
, false);
1339 this.saveOrUpdate(accTaxon
);
1340 result
.addUpdatedObject(accTaxon
);
1342 this.saveOrUpdate(synonym
);
1346 TaxonName name
= synonym
.getName();
1347 synonym
.setName(null);
1349 dao
.delete(synonym
);
1350 result
.addDeletedObject(synonym
);
1352 //remove name if possible (and required)
1353 if (name
!= null && config
.isDeleteNameIfPossible()){
1355 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1356 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1357 result
.addExceptions(nameDeleteResult
.getExceptions());
1358 result
.addRelatedObject(name
);
1359 result
.addUpdatedObject(name
);
1361 result
.addDeletedObject(name
);
1369 public Map
<String
, Map
<UUID
,Set
<TaxonName
>>> findIdenticalTaxonNames(List
<UUID
> sourceRefUuids
, List
<String
> propertyPaths
) {
1370 return this.dao
.findIdenticalNames(sourceRefUuids
, propertyPaths
);
1374 public Taxon
findBestMatchingTaxon(String taxonName
) {
1375 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1376 config
.setTaxonNameTitle(taxonName
);
1377 return findBestMatchingTaxon(config
);
1381 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1383 Taxon bestCandidate
= null;
1385 // 1. search for accepted taxa
1386 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1387 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1388 boolean bestCandidateMatchesSecUuid
= false;
1389 boolean bestCandidateIsInClassification
= false;
1390 int countEqualCandidates
= 0;
1391 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1392 if(taxonBaseCandidate
instanceof Taxon
){
1393 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1394 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1395 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1397 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1398 bestCandidate
= newCanditate
;
1399 countEqualCandidates
= 1;
1400 bestCandidateMatchesSecUuid
= true;
1404 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1405 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1407 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1408 bestCandidate
= newCanditate
;
1409 countEqualCandidates
= 1;
1410 bestCandidateIsInClassification
= true;
1413 if (bestCandidate
== null){
1414 bestCandidate
= newCanditate
;
1415 countEqualCandidates
= 1;
1418 }else{ //not Taxon.class
1421 countEqualCandidates
++;
1424 if (bestCandidate
!= null){
1425 if(countEqualCandidates
> 1){
1426 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1427 return bestCandidate
;
1429 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1430 return bestCandidate
;
1434 // 2. search for synonyms
1435 if (config
.isIncludeSynonyms()){
1436 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1437 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1438 for(TaxonBase taxonBase
: synonymList
){
1439 if(taxonBase
instanceof Synonym
){
1440 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1441 bestCandidate
= synonym
.getAcceptedTaxon();
1442 if(bestCandidate
!= null){
1443 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1444 return bestCandidate
;
1446 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1451 } catch (Exception e
){
1453 e
.printStackTrace();
1456 return bestCandidate
;
1459 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1460 UUID configClassificationUuid
= config
.getClassificationUuid();
1461 if (configClassificationUuid
== null){
1464 for (TaxonNode node
: taxon
.getTaxonNodes()){
1465 UUID classUuid
= node
.getClassification().getUuid();
1466 if (configClassificationUuid
.equals(classUuid
)){
1473 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1474 UUID configSecUuid
= config
.getSecUuid();
1475 if (configSecUuid
== null){
1478 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1479 return configSecUuid
.equals(taxonSecUuid
);
1483 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1484 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1485 if(! synonymList
.isEmpty()){
1486 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1487 if(synonymList
.size() == 1){
1488 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1491 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1499 @Transactional(readOnly
= false)
1500 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
1501 SynonymType newSynonymType
, UUID newSecundumUuid
, String newSecundumDetail
,
1502 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1504 UpdateResult result
= new UpdateResult();
1505 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
), Taxon
.class);
1506 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
1507 newSecundumUuid
, newSecundumDetail
, keepSecundumIfUndefined
);
1513 @Transactional(readOnly
= false)
1514 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1516 boolean moveHomotypicGroup
,
1517 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1518 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1520 oldSynonym
.getSec()!= null? oldSynonym
.getSec().getUuid(): null,
1521 oldSynonym
.getSecMicroReference(),
1526 @Transactional(readOnly
= false)
1527 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1529 boolean moveHomotypicGroup
,
1530 SynonymType newSynonymType
,
1531 UUID newSecundumUuid
,
1532 String newSecundumDetail
,
1533 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1535 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1536 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1537 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1538 TaxonName synonymName
= synonym
.getName();
1539 TaxonName fromTaxonName
= oldTaxon
.getName();
1540 //set default relationship type
1541 if (newSynonymType
== null){
1542 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1544 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1546 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1547 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1548 boolean isSingleInGroup
= !(hgSize
> 1);
1550 if (! isSingleInGroup
){
1551 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1552 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1553 if (isHomotypicToAccepted
){
1554 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.";
1555 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1556 message
= String
.format(message
, homotypicRelatives
);
1557 throw new HomotypicalGroupChangeException(message
);
1559 if (! moveHomotypicGroup
){
1560 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.";
1561 throw new HomotypicalGroupChangeException(message
);
1564 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1566 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1568 UpdateResult result
= new UpdateResult();
1569 //move all synonyms to new taxon
1570 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1571 Reference newSecundum
= referenceService
.load(newSecundumUuid
);
1572 for (Synonym synRelation
: homotypicSynonyms
){
1574 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1575 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1576 oldTaxon
.removeSynonym(synRelation
, false);
1577 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1579 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1580 synRelation
.setSec(newSecundum
);
1582 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1583 synRelation
.setSecMicroReference(newSecundumDetail
);
1586 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1587 if (!synRelation
.equals(oldSynonym
)){
1592 result
.addUpdatedObject(oldTaxon
);
1593 result
.addUpdatedObject(newTaxon
);
1594 result
.addUpdatedObject(homotypicGroup
);
1595 result
.addUpdatedObject(synonym
);
1596 saveOrUpdate(oldTaxon
);
1597 saveOrUpdate(newTaxon
);
1603 public <T
extends TaxonBase
>List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1605 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1609 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1610 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1611 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1612 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1613 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1615 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1616 null, includeUnpublished
, languages
, highlightFragments
, null);
1618 // --- execute search
1619 TopGroups
<BytesRef
> topDocsResultSet
;
1621 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1622 } catch (ParseException e
) {
1623 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1624 luceneParseException
.setStackTrace(e
.getStackTrace());
1625 throw luceneParseException
;
1628 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1629 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
1631 // --- initialize taxa, thighlight matches ....
1632 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1633 @SuppressWarnings("rawtypes")
1634 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1635 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1637 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1638 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1641 @Transactional(readOnly
= true)
1643 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
) {
1644 long numberOfResults
= dao
.countByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
);
1646 long numberOfResults_doubtful
= dao
.countByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
);
1647 List
<S
> results
= new ArrayList
<>();
1648 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1650 results
= dao
.findByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1651 results
.addAll(dao
.findByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1653 Collections
.sort(results
, new TaxonComparator());
1654 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1657 @Transactional(readOnly
= true)
1659 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
) {
1660 long numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
1661 //check whether there are doubtful taxa matching
1662 long numberOfResults_doubtful
= dao
.countByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
);
1663 List
<S
> results
= new ArrayList
<>();
1664 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1665 if (numberOfResults
> 0){
1666 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1668 results
= new ArrayList
<>();
1670 if (numberOfResults_doubtful
> 0){
1671 results
.addAll(dao
.findByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1674 Collections
.sort(results
, new TaxonComparator());
1675 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1679 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1680 Classification classification
, TaxonNode subtree
,
1681 Integer pageSize
, Integer pageNumber
,
1682 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1684 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
1686 // --- execute search
1687 TopGroups
<BytesRef
> topDocsResultSet
;
1689 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1690 } catch (ParseException e
) {
1691 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1692 luceneParseException
.setStackTrace(e
.getStackTrace());
1693 throw luceneParseException
;
1696 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1697 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
1699 // --- initialize taxa, thighlight matches ....
1700 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1701 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1702 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1704 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1705 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1710 * @param queryString
1711 * @param classification
1712 * @param includeUnpublished
1714 * @param highlightFragments
1715 * @param sortFields TODO
1716 * @param directorySelectClass
1719 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1720 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1721 boolean highlightFragments
, SortField
[] sortFields
) {
1723 Builder finalQueryBuilder
= new Builder();
1724 Builder textQueryBuilder
= new Builder();
1726 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1727 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1729 if(sortFields
== null){
1730 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1732 luceneSearch
.setSortFields(sortFields
);
1734 // ---- search criteria
1735 luceneSearch
.setCdmTypRestriction(clazz
);
1737 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1738 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1739 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1741 if(className
!= null){
1742 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1745 BooleanQuery textQuery
= textQueryBuilder
.build();
1746 if(textQuery
.clauses().size() > 0) {
1747 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1750 if(classification
!= null){
1751 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1753 if(subtree
!= null){
1754 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1756 if(!includeUnpublished
) {
1757 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1758 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1759 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1762 luceneSearch
.setQuery(finalQueryBuilder
.build());
1764 if(highlightFragments
){
1765 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1767 return luceneSearch
;
1771 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1772 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1773 * drawback of requiring to do the join an indexing time.
1774 * see https://dev.e-taxonomy.eu/redmine/projects/edit/wiki/LuceneNotes#JoinsinLucene for more information on this.
1776 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1778 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1779 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1781 * @param queryString
1782 * @param classification
1784 * @param highlightFragments
1785 * @param sortFields TODO
1788 * @throws IOException
1790 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1791 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1792 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1795 String queryTermField
;
1796 String toField
= "id"; // TaxonBase.uuid
1797 String publishField
;
1798 String publishFieldInvers
;
1800 if(edge
.isBidirectional()){
1801 throw new RuntimeException("Bidirectional joining not supported!");
1804 fromField
= "relatedFrom.id";
1805 queryTermField
= "relatedFrom.titleCache";
1806 publishField
= "relatedFrom.publish";
1807 publishFieldInvers
= "relatedTo.publish";
1808 } else if(edge
.isInvers()) {
1809 fromField
= "relatedTo.id";
1810 queryTermField
= "relatedTo.titleCache";
1811 publishField
= "relatedTo.publish";
1812 publishFieldInvers
= "relatedFrom.publish";
1814 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1817 Builder finalQueryBuilder
= new Builder();
1819 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1820 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1822 Builder joinFromQueryBuilder
= new Builder();
1823 if(!StringUtils
.isEmpty(queryString
)){
1824 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1826 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1827 if(!includeUnpublished
){
1828 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1829 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1832 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1834 if(sortFields
== null){
1835 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1837 luceneSearch
.setSortFields(sortFields
);
1839 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1841 if(classification
!= null){
1842 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1844 if(subtree
!= null){
1845 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1848 luceneSearch
.setQuery(finalQueryBuilder
.build());
1850 if(highlightFragments
){
1851 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1853 return luceneSearch
;
1857 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1858 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1859 Classification classification
, TaxonNode subtree
,
1860 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1861 boolean highlightFragments
, Integer pageSize
,
1862 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1863 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1865 // FIXME: allow taxonomic ordering
1866 // hql equivalent: order by t.name.genusOrUninomial, case when t.name.specificEpithet
1867 // like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache";
1868 // this requires building a special sort column by a special classBridge
1869 if(highlightFragments
){
1870 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1871 "currently not fully supported by this method and thus " +
1872 "may not work with common names and misapplied names.");
1875 // convert sets to lists
1876 List
<NamedArea
> namedAreaList
= null;
1877 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1878 if(namedAreas
!= null){
1879 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1880 namedAreaList
.addAll(namedAreas
);
1882 if(distributionStatus
!= null){
1883 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1884 distributionStatusList
.addAll(distributionStatus
);
1887 // set default if parameter is null
1888 if(searchModes
== null){
1889 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1892 // set sort order and thus override any sort orders which may have been
1893 // defined by prepare*Search methods
1894 if(orderHints
== null){
1895 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1897 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1899 for(OrderHint oh
: orderHints
){
1900 sortFields
[i
++] = oh
.toSortField();
1902 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1903 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1905 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1907 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1908 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1911 ======== filtering by distribution , HOWTO ========
1913 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1914 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1915 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1916 which will be put into a FilteredQuersy in the end ?
1919 3. how does it work in spatial?
1921 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1922 - http://www.infoq.com/articles/LuceneSpatialSupport
1923 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1924 ------------------------------------------------------------------------
1927 A) use a separate distribution filter per index sub-query/search:
1928 - byTaxonSyonym (query TaxaonBase):
1929 use a join area filter (Distribution -> TaxonBase)
1930 - byCommonName (query DescriptionElementBase): use an area filter on
1931 DescriptionElementBase !!! PROBLEM !!!
1932 This cannot work since the distributions are different entities than the
1933 common names and thus these are different lucene documents.
1934 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1935 use a join area filter (Distribution -> TaxonBase)
1937 B) use a common distribution filter for all index sub-query/searches:
1938 - use a common join area filter (Distribution -> TaxonBase)
1939 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1940 PROBLEM in this case: we are losing the fragment highlighting for the
1941 common names, since the returned documents are always TaxonBases
1944 /* The QueryFactory for creating filter queries on Distributions should
1945 * The query factory used for the common names query cannot be reused
1946 * for this case, since we want to only record the text fields which are
1947 * actually used in the primary query
1949 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1951 Builder multiIndexByAreaFilterBuilder
= new Builder();
1952 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1954 // search for taxa or synonyms
1955 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1956 @SuppressWarnings("rawtypes")
1957 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1958 String className
= null;
1959 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1960 taxonBaseSubclass
= Taxon
.class;
1961 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1962 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1964 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1965 queryString
, classification
, subtree
, className
,
1966 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1967 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
1968 /* A) does not work!!!!
1969 if(addDistributionFilter){
1970 // in this case we need a filter which uses a join query
1971 // to get the TaxonBase documents for the DescriptionElementBase documents
1972 // which are matching the areas in question
1973 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1975 distributionStatusList,
1976 distributionFilterQueryFactory
1978 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1981 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1982 // add additional area filter for synonyms
1983 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1984 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1986 //TODO replace by createByDistributionJoinQuery
1987 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1988 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1989 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1994 // search by CommonTaxonName
1995 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1997 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1998 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1999 CommonTaxonName
.class,
2000 "inDescription.taxon.id",
2002 QueryFactory
.addTypeRestriction(
2003 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
2004 , CommonTaxonName
.class
2005 ).build(), "id", null, ScoreMode
.Max
);
2006 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
2007 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
2008 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2009 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
2010 Builder builder
= new BooleanQuery
.Builder();
2011 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
2012 if(!includeUnpublished
) {
2013 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
2014 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
2016 byCommonNameSearch
.setQuery(builder
.build());
2017 byCommonNameSearch
.setSortFields(sortFields
);
2019 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
2021 luceneSearches
.add(byCommonNameSearch
);
2023 /* A) does not work!!!!
2025 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
2026 queryString, classification, null, languages, highlightFragments)
2028 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2029 if(addDistributionFilter){
2030 // in this case we are able to use DescriptionElementBase documents
2031 // which are matching the areas in question directly
2032 BooleanQuery byDistributionQuery = createByDistributionQuery(
2034 distributionStatusList,
2035 distributionFilterQueryFactory
2037 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
2042 // search by misapplied names
2043 //TODO merge with pro parte synonym search once #7487 is fixed
2044 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
2046 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
2047 // which allows doing query time joins
2048 // finds the misapplied name (Taxon B) which is an misapplication for
2049 // a related Taxon A.
2051 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2052 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
2053 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
2055 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
2056 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
2059 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2060 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2061 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2062 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
2064 if(addDistributionFilter
){
2065 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2068 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
2069 * Maybe this is a bug in java itself.
2071 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2074 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2076 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2077 * will execute as expected:
2079 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2080 * String toField = "relation." + misappliedNameForUuid +".to.id";
2082 * Comparing both strings by the String.equals method returns true, so both String are identical.
2084 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2085 * 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)
2086 * The bug is persistent after a reboot of the development computer.
2088 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2089 // String toField = "relation." + misappliedNameForUuid +".to.id";
2090 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2091 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2092 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2094 //TODO replace by createByDistributionJoinQuery
2095 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2096 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2097 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2099 // debug code for bug described above
2100 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
2101 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2102 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2104 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2108 // search by pro parte synonyms
2109 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
2110 //TODO merge with misapplied name search once #7487 is fixed
2111 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2112 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
2114 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2115 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2116 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2117 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
2119 if(addDistributionFilter
){
2120 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2121 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
2122 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2123 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2124 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2125 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2127 }//end pro parte synonyms
2129 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
2130 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
2132 if(addDistributionFilter
){
2135 // in this case we need a filter which uses a join query
2136 // to get the TaxonBase documents for the DescriptionElementBase documents
2137 // which are matching the areas in question
2139 // for doTaxa, doByCommonName
2140 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
2142 distributionStatusList
,
2143 distributionFilterQueryFactory
,
2146 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2149 if (addDistributionFilter
){
2150 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
2154 // --- execute search
2155 TopGroups
<BytesRef
> topDocsResultSet
;
2157 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2158 } catch (ParseException e
) {
2159 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2160 luceneParseException
.setStackTrace(e
.getStackTrace());
2161 throw luceneParseException
;
2164 // --- initialize taxa, highlight matches ....
2165 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2168 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2169 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2171 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
2172 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2176 * @param namedAreaList at least one area must be in the list
2177 * @param distributionStatusList optional
2178 * @param toType toType
2179 * Optional parameter. Only used for debugging to print the toType documents
2180 * @param asFilter TODO
2182 * @throws IOException
2184 protected Query
createByDistributionJoinQuery(
2185 List
<NamedArea
> namedAreaList
,
2186 List
<PresenceAbsenceTerm
> distributionStatusList
,
2187 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
2188 ) throws IOException
{
2190 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2191 String toField
= "id"; // id in toType usually this is the TaxonBase index
2193 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
2195 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
2197 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
2199 return taxonAreaJoinQuery
;
2203 * @param namedAreaList
2204 * @param distributionStatusList
2205 * @param queryFactory
2208 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
2209 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
2210 Builder areaQueryBuilder
= new Builder();
2211 // area field from Distribution
2212 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2214 // status field from Distribution
2215 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2216 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2219 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2220 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2225 * This method has been primarily created for testing the area join query but might
2226 * also be useful in other situations
2228 * @param namedAreaList
2229 * @param distributionStatusList
2230 * @param classification
2231 * @param highlightFragments
2233 * @throws IOException
2235 protected LuceneSearch
prepareByDistributionSearch(
2236 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2237 Classification classification
, TaxonNode subtree
) throws IOException
{
2239 Builder finalQueryBuilder
= new Builder();
2241 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2243 // FIXME is this query factory using the wrong type?
2244 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2246 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2247 luceneSearch
.setSortFields(sortFields
);
2250 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2252 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2254 if(classification
!= null){
2255 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
2257 if(subtree
!= null){
2258 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
2260 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2261 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2262 luceneSearch
.setQuery(finalQuery
);
2264 return luceneSearch
;
2268 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2269 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2270 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2271 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2273 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2275 // --- execute search
2276 TopGroups
<BytesRef
> topDocsResultSet
;
2278 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2279 } catch (ParseException e
) {
2280 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2281 luceneParseException
.setStackTrace(e
.getStackTrace());
2282 throw luceneParseException
;
2285 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2286 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2288 // --- initialize taxa, highlight matches ....
2289 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2290 @SuppressWarnings("rawtypes")
2291 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2292 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2294 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2295 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2299 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2300 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2301 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2303 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2304 classification
, subtree
,
2305 null, languages
, highlightFragments
);
2306 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2307 includeUnpublished
, languages
, highlightFragments
, null);
2309 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2311 // --- execute search
2312 TopGroups
<BytesRef
> topDocsResultSet
;
2314 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2315 } catch (ParseException e
) {
2316 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2317 luceneParseException
.setStackTrace(e
.getStackTrace());
2318 throw luceneParseException
;
2321 // --- initialize taxa, highlight matches ....
2322 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2324 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2325 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
2326 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2328 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2329 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2331 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2332 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2337 * @param queryString
2338 * @param classification
2341 * @param highlightFragments
2342 * @param directorySelectClass
2345 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2346 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2347 List
<Language
> languages
, boolean highlightFragments
) {
2349 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2350 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2352 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2354 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2355 languages
, descriptionElementQueryFactory
);
2357 luceneSearch
.setSortFields(sortFields
);
2358 luceneSearch
.setCdmTypRestriction(clazz
);
2359 luceneSearch
.setQuery(finalQuery
);
2360 if(highlightFragments
){
2361 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2364 return luceneSearch
;
2368 * @param queryString
2369 * @param classification
2372 * @param descriptionElementQueryFactory
2375 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2376 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2377 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2379 Builder finalQueryBuilder
= new Builder();
2380 Builder textQueryBuilder
= new Builder();
2382 if(!StringUtils
.isEmpty(queryString
)){
2384 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2387 Builder nameQueryBuilder
= new Builder();
2388 if(languages
== null || languages
.size() == 0){
2389 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2391 Builder languageSubQueryBuilder
= new Builder();
2392 for(Language lang
: languages
){
2393 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2395 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2396 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2398 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2401 // text field from TextData
2402 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2404 // --- TermBase fields - by representation ----
2405 // state field from CategoricalData
2406 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2408 // state field from CategoricalData
2409 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2411 // area field from Distribution
2412 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2414 // status field from Distribution
2415 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2417 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2420 // --- classification ----
2423 if(classification
!= null){
2424 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2426 if(subtree
!= null){
2427 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2430 // --- IdentifieableEntity fields - by uuid
2431 if(features
!= null && features
.size() > 0 ){
2432 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2435 // the description must be associated with a taxon
2436 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2438 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2439 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2444 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2446 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2447 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2449 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2450 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2452 UUID nameUuid
= taxon
.getName().getUuid();
2453 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2454 String epithetOfTaxon
= null;
2455 String infragenericEpithetOfTaxon
= null;
2456 String infraspecificEpithetOfTaxon
= null;
2457 if (taxonName
.isSpecies()){
2458 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2459 } else if (taxonName
.isInfraGeneric()){
2460 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2461 } else if (taxonName
.isInfraSpecific()){
2462 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2464 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2465 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2466 List
<String
> taxonNames
= new ArrayList
<>();
2468 for (TaxonNode node
: nodes
){
2469 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2470 // List<String> synonymsEpithet = new ArrayList<>();
2472 if (node
.getClassification().equals(classification
)){
2473 if (!node
.isTopmostNode()){
2474 TaxonNode parent
= node
.getParent();
2475 parent
= CdmBase
.deproxy(parent
);
2476 TaxonName parentName
= parent
.getTaxon().getName();
2477 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2478 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2480 //create inferred synonyms for species, subspecies
2481 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2483 Synonym inferredEpithet
= null;
2484 Synonym inferredGenus
= null;
2485 Synonym potentialCombination
= null;
2487 List
<String
> propertyPaths
= new ArrayList
<>();
2488 propertyPaths
.add("synonym");
2489 propertyPaths
.add("synonym.name");
2490 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2491 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2493 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2494 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2495 null, null,orderHintsSynonyms
,propertyPaths
);
2497 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2498 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2499 if (doWithMisappliedNames
){
2500 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2501 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2502 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2503 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2504 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2505 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2508 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2509 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2511 inferredEpithet
= createInferredEpithets(taxon
,
2512 zooHashMap
, taxonName
, epithetOfTaxon
,
2513 infragenericEpithetOfTaxon
,
2514 infraspecificEpithetOfTaxon
,
2515 taxonNames
, parentName
,
2516 synonymRelationOfParent
);
2518 inferredSynonyms
.add(inferredEpithet
);
2519 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2520 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2523 if (doWithMisappliedNames
){
2525 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2526 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2528 inferredEpithet
= createInferredEpithets(taxon
,
2529 zooHashMap
, taxonName
, epithetOfTaxon
,
2530 infragenericEpithetOfTaxon
,
2531 infraspecificEpithetOfTaxon
,
2532 taxonNames
, parentName
,
2535 inferredSynonyms
.add(inferredEpithet
);
2536 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2537 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2541 if (!taxonNames
.isEmpty()){
2542 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2543 if (!synNotInCDM
.isEmpty()){
2544 inferredSynonymsToBeRemoved
.clear();
2546 for (Synonym syn
:inferredSynonyms
){
2547 IZoologicalName name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2548 if (!synNotInCDM
.contains(name
.getNameCache())){
2549 inferredSynonymsToBeRemoved
.add(syn
);
2553 // Remove identified Synonyms from inferredSynonyms
2554 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2555 inferredSynonyms
.remove(synonym
);
2560 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2562 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2564 inferredGenus
= createInferredGenus(taxon
,
2565 zooHashMap
, taxonName
, epithetOfTaxon
,
2566 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2568 inferredSynonyms
.add(inferredGenus
);
2569 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2570 taxonNames
.add(inferredGenus
.getName().getNameCache());
2573 if (doWithMisappliedNames
){
2575 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2576 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2577 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2579 inferredSynonyms
.add(inferredGenus
);
2580 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2581 taxonNames
.add(inferredGenus
.getName().getNameCache());
2586 if (!taxonNames
.isEmpty()){
2587 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2588 IZoologicalName name
;
2589 if (!synNotInCDM
.isEmpty()){
2590 inferredSynonymsToBeRemoved
.clear();
2592 for (Synonym syn
:inferredSynonyms
){
2593 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2594 if (!synNotInCDM
.contains(name
.getNameCache())){
2595 inferredSynonymsToBeRemoved
.add(syn
);
2599 // Remove identified Synonyms from inferredSynonyms
2600 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2601 inferredSynonyms
.remove(synonym
);
2606 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2608 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2609 //for all synonyms of the parent...
2610 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2612 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2614 synName
= synonymRelationOfParent
.getName();
2616 // Set the sourceReference
2617 sourceReference
= synonymRelationOfParent
.getSec();
2619 // Determine the idInSource
2620 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2622 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2623 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2624 String synParentInfragenericName
= null;
2625 String synParentSpecificEpithet
= null;
2627 if (parentSynZooName
.isInfraGeneric()){
2628 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2630 if (parentSynZooName
.isSpecies()){
2631 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2634 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2635 synonymsGenus.put(synGenusName, idInSource);
2638 //for all synonyms of the taxon
2640 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2642 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2643 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2645 synParentInfragenericName
,
2646 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2648 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2649 inferredSynonyms
.add(potentialCombination
);
2650 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2651 taxonNames
.add(potentialCombination
.getName().getNameCache());
2655 if (doWithMisappliedNames
){
2657 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2659 TaxonName misappliedParentName
;
2661 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2662 misappliedParentName
= misappliedParent
.getName();
2664 HibernateProxyHelper
.deproxy(misappliedParent
);
2666 // Set the sourceReference
2667 sourceReference
= misappliedParent
.getSec();
2669 // Determine the idInSource
2670 String idInSourceParent
= getIdInSource(misappliedParent
);
2672 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2673 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2674 String synParentInfragenericName
= null;
2675 String synParentSpecificEpithet
= null;
2677 if (parentSynZooName
.isInfraGeneric()){
2678 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2680 if (parentSynZooName
.isSpecies()){
2681 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2684 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2685 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2686 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2687 potentialCombination
= createPotentialCombination(
2688 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2690 synParentInfragenericName
,
2691 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2693 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2694 inferredSynonyms
.add(potentialCombination
);
2695 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2696 taxonNames
.add(potentialCombination
.getName().getNameCache());
2701 if (!taxonNames
.isEmpty()){
2702 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2703 IZoologicalName name
;
2704 if (!synNotInCDM
.isEmpty()){
2705 inferredSynonymsToBeRemoved
.clear();
2706 for (Synonym syn
:inferredSynonyms
){
2708 name
= syn
.getName();
2709 }catch (ClassCastException e
){
2710 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2712 if (!synNotInCDM
.contains(name
.getNameCache())){
2713 inferredSynonymsToBeRemoved
.add(syn
);
2716 // Remove identified Synonyms from inferredSynonyms
2717 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2718 inferredSynonyms
.remove(synonym
);
2724 logger
.info("The synonym type is not defined.");
2725 return inferredSynonyms
;
2731 return inferredSynonyms
;
2734 private Synonym
createPotentialCombination(String idInSourceParent
,
2735 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2736 String synParentInfragenericName
, String synParentSpecificEpithet
,
2737 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2738 Synonym potentialCombination
;
2739 Reference sourceReference
;
2740 IZoologicalName inferredSynName
;
2741 HibernateProxyHelper
.deproxy(syn
);
2743 // Set sourceReference
2744 sourceReference
= syn
.getSec();
2745 if (sourceReference
== null){
2746 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2748 if (!parentSynZooName
.getTaxa().isEmpty()){
2749 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2751 sourceReference
= taxon
.getSec();
2754 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2756 String synTaxonInfraSpecificName
= null;
2758 if (parentSynZooName
.isSpecies()){
2759 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2762 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2763 synonymsEpithet.add(epithetName);
2766 //create potential combinations...
2767 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2769 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2770 if (zooSynName
.isSpecies()){
2771 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2772 if (parentSynZooName
.isInfraGeneric()){
2773 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2776 if (zooSynName
.isInfraSpecific()){
2777 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2778 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2780 if (parentSynZooName
.isInfraGeneric()){
2781 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2784 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2786 // Set the sourceReference
2787 potentialCombination
.setSec(sourceReference
);
2790 // Determine the idInSource
2791 String idInSourceSyn
= getIdInSource(syn
);
2793 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2794 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2795 inferredSynName
.addSource(originalSource
);
2796 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2797 potentialCombination
.addSource(originalSource
);
2800 return potentialCombination
;
2803 private Synonym
createInferredGenus(Taxon taxon
,
2804 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2805 String epithetOfTaxon
, String genusOfTaxon
,
2806 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2809 Synonym inferredGenus
;
2811 IZoologicalName inferredSynName
;
2812 synName
=syn
.getName();
2813 HibernateProxyHelper
.deproxy(syn
);
2815 // Determine the idInSource
2816 String idInSourceSyn
= getIdInSource(syn
);
2817 String idInSourceTaxon
= getIdInSource(taxon
);
2818 // Determine the sourceReference
2819 Reference sourceReference
= syn
.getSec();
2821 //logger.warn(sourceReference.getTitleCache());
2823 synName
= syn
.getName();
2824 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2825 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2826 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2827 synonymsEpithet.add(synSpeciesEpithetName);
2830 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2831 //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...
2833 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2834 if (zooParentName
.isInfraGeneric()){
2835 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2838 if (taxonName
.isSpecies()){
2839 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2841 if (taxonName
.isInfraSpecific()){
2842 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2843 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2846 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2848 // Set the sourceReference
2849 inferredGenus
.setSec(sourceReference
);
2851 // Add the original source
2852 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2853 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2854 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2855 inferredGenus
.addSource(originalSource
);
2857 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2858 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2859 inferredSynName
.addSource(originalSource
);
2860 originalSource
= null;
2863 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2864 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2865 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2866 inferredGenus
.addSource(originalSource
);
2868 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2869 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2870 inferredSynName
.addSource(originalSource
);
2871 originalSource
= null;
2874 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2876 return inferredGenus
;
2879 private Synonym
createInferredEpithets(Taxon taxon
,
2880 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2881 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2882 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2883 TaxonName parentName
, TaxonBase
<?
> syn
) {
2885 Synonym inferredEpithet
;
2887 IZoologicalName inferredSynName
;
2888 HibernateProxyHelper
.deproxy(syn
);
2890 // Determine the idInSource
2891 String idInSourceSyn
= getIdInSource(syn
);
2892 String idInSourceTaxon
= getIdInSource(taxon
);
2893 // Determine the sourceReference
2894 Reference sourceReference
= syn
.getSec();
2896 if (sourceReference
== null){
2897 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2898 sourceReference
= taxon
.getSec();
2901 synName
= syn
.getName();
2902 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2903 String synGenusName
= zooSynName
.getGenusOrUninomial();
2904 String synInfraGenericEpithet
= null;
2905 String synSpecificEpithet
= null;
2907 if (zooSynName
.getInfraGenericEpithet() != null){
2908 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2911 if (zooSynName
.isInfraSpecific()){
2912 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2915 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2916 synonymsGenus.put(synGenusName, idInSource);
2919 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2921 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2922 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2923 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2925 inferredSynName
.setGenusOrUninomial(synGenusName
);
2927 if (parentName
.isInfraGeneric()){
2928 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2930 if (taxonName
.isSpecies()){
2931 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2932 }else if (taxonName
.isInfraSpecific()){
2933 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2934 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2937 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2939 // Set the sourceReference
2940 inferredEpithet
.setSec(sourceReference
);
2942 /* Add the original source
2943 if (idInSource != null) {
2944 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2947 Reference citation = getCitation(syn);
2948 if (citation != null) {
2949 originalSource.setCitation(citation);
2950 inferredEpithet.addSource(originalSource);
2953 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2956 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2957 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2959 inferredEpithet
.addSource(originalSource
);
2961 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2962 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2964 inferredSynName
.addSource(originalSource
);
2966 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2968 return inferredEpithet
;
2972 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2973 * Very likely only useful for createInferredSynonyms().
2978 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2979 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2980 if (taxonName
== null) {
2981 taxonName
= zooHashMap
.get(uuid
);
2987 * Returns the idInSource for a given Synonym.
2990 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2991 String idInSource
= null;
2992 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2993 if (sources
.size() == 1) {
2994 IdentifiableSource source
= sources
.iterator().next();
2995 if (source
!= null) {
2996 idInSource
= source
.getIdInSource();
2998 } else if (sources
.size() > 1) {
3001 for (IdentifiableSource source
: sources
) {
3002 idInSource
+= source
.getIdInSource();
3003 if (count
< sources
.size()) {
3008 } else if (sources
.size() == 0){
3009 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
3016 * Returns the citation for a given Synonym.
3019 private Reference
getCitation(Synonym syn
) {
3020 Reference citation
= null;
3021 Set
<IdentifiableSource
> sources
= syn
.getSources();
3022 if (sources
.size() == 1) {
3023 IdentifiableSource source
= sources
.iterator().next();
3024 if (source
!= null) {
3025 citation
= source
.getCitation();
3027 } else if (sources
.size() > 1) {
3028 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
3035 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
3036 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
3038 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
3039 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
3040 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
3042 return inferredSynonyms
;
3046 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
3048 // TODO quickly implemented, create according dao !!!!
3049 Set
<TaxonNode
> nodes
= new HashSet
<>();
3050 Set
<Classification
> classifications
= new HashSet
<>();
3051 List
<Classification
> list
= new ArrayList
<>();
3053 if (taxonBase
== null) {
3057 taxonBase
= load(taxonBase
.getUuid());
3059 if (taxonBase
instanceof Taxon
) {
3060 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
3062 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
3064 nodes
.addAll(taxon
.getTaxonNodes());
3067 for (TaxonNode node
: nodes
) {
3068 classifications
.add(node
.getClassification());
3070 list
.addAll(classifications
);
3075 @Transactional(readOnly
= false)
3076 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
3078 TaxonRelationshipType oldRelationshipType
,
3079 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3080 UpdateResult result
= new UpdateResult();
3081 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
3082 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3083 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
3085 result
.addUpdatedObject(toTaxon
);
3086 result
.addUpdatedObject(result
.getCdmEntity());
3092 @Transactional(readOnly
= false)
3093 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
3094 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3096 UpdateResult result
= new UpdateResult();
3097 // Create new synonym using concept name
3098 TaxonName synonymName
= fromTaxon
.getName();
3100 // Remove concept relation from taxon
3101 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
3103 // Create a new synonym for the taxon
3105 if (synonymType
!= null
3106 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
3107 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
3108 toTaxon
.addHomotypicSynonym(synonym
);
3110 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
3112 //keep the publish flag
3113 synonym
.setPublish(fromTaxon
.isPublish());
3114 this.saveOrUpdate(toTaxon
);
3115 //TODO: configurator and classification
3116 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
3117 config
.setDeleteNameIfPossible(false);
3118 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
3119 result
.setCdmEntity(synonym
);
3120 result
.addUpdatedObject(toTaxon
);
3121 result
.addUpdatedObject(synonym
);
3126 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
3127 DeleteResult result
= new DeleteResult();
3128 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
3129 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
3130 if (taxonBase
instanceof Taxon
){
3131 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
3132 List
<String
> propertyPaths
= new ArrayList
<>();
3133 propertyPaths
.add("taxonNodes");
3134 Taxon taxon
= (Taxon
)load(taxonBaseUuid
, propertyPaths
);
3136 result
= isDeletableForTaxon(references
, taxonConfig
);
3138 if (taxonConfig
.isDeleteNameIfPossible()){
3139 if (taxonBase
.getName() != null){
3140 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), taxonConfig
.getNameDeletionConfig(), taxon
.getUuid());
3141 if (!nameResult
.isOk()){
3142 result
.addExceptions(nameResult
.getExceptions());
3148 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
3149 result
= isDeletableForSynonym(references
, synonymConfig
);
3150 if (synonymConfig
.isDeleteNameIfPossible() && taxonBase
.getName() != null){
3151 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), synonymConfig
.getNameDeletionConfig(), taxonBase
.getUuid());
3152 if (!nameResult
.isOk()){
3153 result
.addExceptions(nameResult
.getExceptions());
3162 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
3164 DeleteResult result
= new DeleteResult();
3165 for (CdmBase ref
: references
){
3166 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3167 String message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
3168 result
.addException(new ReferencedObjectUndeletableException(message
));
3169 result
.addRelatedObject(ref
);
3177 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
3178 String message
= null;
3179 DeleteResult result
= new DeleteResult();
3180 for (CdmBase ref
: references
){
3181 if (!(ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3183 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
3184 message
= "The taxon can't be deleted as long as it has synonyms.";
3186 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
3187 message
= "The taxon can't be deleted as long as it has factual data.";
3190 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
3191 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
3193 if (ref
instanceof TaxonNode
&& config
.getClassificationUuid() != null && !config
.isDeleteInAllClassifications() && !((TaxonNode
)ref
).getClassification().getUuid().equals(config
.getClassificationUuid())){
3194 message
= "The taxon can't be deleted as long as it is used in more than one classification";
3197 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
3198 if (!config
.isDeleteMisappliedNames() &&
3199 (((TaxonRelationship
)ref
).getType().isMisappliedName())){
3200 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3202 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3205 if (ref
instanceof PolytomousKeyNode
){
3206 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3209 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3210 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3213 /* //PolytomousKeyNode
3214 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3215 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3220 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3221 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3225 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3226 message
= "Taxon can't be deleted as it is used in a determination event";
3229 if (message
!= null){
3230 result
.addException(new ReferencedObjectUndeletableException(message
));
3231 result
.addRelatedObject(ref
);
3240 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3241 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3243 //preliminary implementation
3245 Set
<Taxon
> taxa
= new HashSet
<>();
3246 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3247 if (taxonBase
== null){
3248 return new IncludedTaxaDTO();
3249 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3250 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3252 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3253 //TODO partial synonyms ??
3254 //TODO synonyms in general
3255 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3256 taxa
.add(syn
.getAcceptedTaxon());
3258 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3261 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3263 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3264 related
= makeRelatedIncluded(related
, result
, config
);
3271 * @param uncheckedTaxa
3272 * @param existingTaxa
3275 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3277 * @return the set of conceptually related taxa for further use
3279 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3282 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3283 for (Taxon taxon
: uncheckedTaxa
){
3284 taxonNodes
.addAll(taxon
.getTaxonNodes());
3287 Set
<Taxon
> children
= new HashSet
<>();
3288 if (! config
.onlyCongruent
){
3289 for (TaxonNode node
: taxonNodes
){
3290 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3291 for (TaxonNode child
: childNodes
){
3292 children
.add(child
.getTaxon());
3295 children
.remove(null); // just to be on the save side
3298 Iterator
<Taxon
> it
= children
.iterator();
3299 while(it
.hasNext()){
3300 UUID uuid
= it
.next().getUuid();
3301 if (existingTaxa
.contains(uuid
)){
3304 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3309 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3310 uncheckedAndChildren
.addAll(children
);
3312 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3315 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3320 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3321 * @return the set of these computed taxa
3323 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3324 Set
<Taxon
> result
= new HashSet
<>();
3326 for (Taxon taxon
: unchecked
){
3327 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3328 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3330 for (TaxonRelationship fromRel
: fromRelations
){
3331 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3334 TaxonRelationshipType fromRelType
= fromRel
.getType();
3335 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3336 !config
.onlyCongruent
&& (
3337 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3338 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3341 result
.add(fromRel
.getToTaxon());
3345 for (TaxonRelationship toRel
: toRelations
){
3346 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3349 TaxonRelationshipType fromRelType
= toRel
.getType();
3350 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3351 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3352 result
.add(toRel
.getFromTaxon());
3357 Iterator
<Taxon
> it
= result
.iterator();
3358 while(it
.hasNext()){
3359 UUID uuid
= it
.next().getUuid();
3360 if (existingTaxa
.contains(uuid
)){
3363 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3370 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3371 @SuppressWarnings("rawtypes")
3372 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3373 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3378 @Transactional(readOnly
= true)
3379 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3380 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3381 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3382 Integer pageNumber
, List
<String
> propertyPaths
) {
3383 if (subtreeFilter
== null){
3384 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3387 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3388 List
<Object
[]> daoResults
= new ArrayList
<>();
3389 if(numberOfResults
> 0) { // no point checking again
3390 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3391 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3394 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3395 for (Object
[] daoObj
: daoResults
){
3397 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3399 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3402 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3406 @Transactional(readOnly
= true)
3407 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3408 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3409 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3410 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3411 if (subtreeFilter
== null){
3412 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3415 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3416 List
<Object
[]> daoResults
= new ArrayList
<>();
3417 if(numberOfResults
> 0) { // no point checking again
3418 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3419 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3422 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3423 for (Object
[] daoObj
: daoResults
){
3425 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3427 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3430 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3434 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3435 UpdateResult result
= new UpdateResult();
3437 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3438 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3439 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3440 //reload to avoid session conflicts
3441 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3443 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3444 if(description
.isProtectedTitleCache()){
3445 String separator
= "";
3446 if(!StringUtils
.isBlank(description
.getTitleCache())){
3449 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3451 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3452 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3453 description
.addAnnotation(annotation
);
3454 toTaxon
.addDescription(description
);
3455 dao
.saveOrUpdate(toTaxon
);
3456 dao
.saveOrUpdate(fromTaxon
);
3457 result
.addUpdatedObject(toTaxon
);
3458 result
.addUpdatedObject(fromTaxon
);
3466 @Transactional(readOnly
= false)
3467 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3468 UUID acceptedTaxonUuid
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
, SecReferenceHandlingSwapEnum secHandling
, UUID newSecAcc
, UUID newSecSyn
) {
3469 TaxonBase
<?
> base
= this.load(synonymUUid
);
3470 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3471 base
= this.load(acceptedTaxonUuid
);
3472 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3474 Reference refAcc
= referenceService
.load(newSecAcc
);
3475 Reference refSyn
= referenceService
.load(newSecSyn
);
3477 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
, setNameInSource
, newUuidForAcceptedTaxon
, secHandling
, refAcc
, refSyn
);
3481 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3482 Set
<TaxonRelationshipType
> inversTypes
,
3483 Direction direction
, boolean groupMisapplications
,
3484 boolean includeUnpublished
,
3485 Integer pageSize
, Integer pageNumber
) {
3486 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3487 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3489 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3491 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3492 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3493 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3495 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3497 //TODO paging is difficult because misapplication string is an attribute
3499 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3500 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3501 // if(numberOfResults > 0) { // no point checking again
3502 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3505 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3508 List
<Language
> languages
= null;
3510 direction
= Direction
.relatedTo
;
3511 //TODO order hints, property path
3512 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3513 for (TaxonRelationship relation
: relations
){
3514 dto
.addRelation(relation
, direction
, languages
);
3518 direction
= Direction
.relatedFrom
;
3519 //TODO order hints, property path
3520 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3521 for (TaxonRelationship relation
: relations
){
3522 dto
.addRelation(relation
, direction
, languages
);
3525 if (groupMisapplications
){
3527 dto
.createMisapplicationString();