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.
9 package eu
.etaxonomy
.cdm
.api
.service
;
11 import java
.io
.IOException
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Collections
;
14 import java
.util
.EnumSet
;
15 import java
.util
.HashMap
;
16 import java
.util
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
21 import java
.util
.UUID
;
22 import java
.util
.stream
.Collectors
;
24 import javax
.persistence
.EntityNotFoundException
;
26 import org
.apache
.commons
.lang3
.StringUtils
;
27 import org
.apache
.logging
.log4j
.LogManager
;
28 import org
.apache
.logging
.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
.IdentifierType
;
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
;
142 * @author a.kohlbecker
146 @Transactional(readOnly
= true)
147 public class TaxonServiceImpl
148 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
149 implements ITaxonService
{
151 private static final Logger logger
= LogManager
.getLogger();
153 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
155 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
157 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
160 private ITaxonNodeDao taxonNodeDao
;
163 private ITaxonNameDao nameDao
;
166 private INameService nameService
;
169 private IOccurrenceService occurrenceService
;
172 private ITaxonNodeService nodeService
;
175 private IDescriptionService descriptionService
;
178 private IReferenceService referenceService
;
181 // private IOrderedTermVocabularyDao orderedVocabularyDao;
184 private IOccurrenceDao occurrenceDao
;
187 private IClassificationDao classificationDao
;
190 private AbstractBeanInitializer beanInitializer
;
193 private ILuceneIndexToolProvider luceneIndexToolProvider
;
195 //************************ CONSTRUCTOR ****************************/
196 public TaxonServiceImpl(){
197 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
200 // ****************************** METHODS ********************************/
203 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
204 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
208 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
209 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
213 @Transactional(readOnly
= false)
214 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
, SecReferenceHandlingSwapEnum secHandling
, Reference newSecAcc
, Reference newSecSyn
){
215 if (newUuidForAcceptedTaxon
){
216 return swapSynonymAndAcceptedTaxonNewUuid(synonym
, acceptedTaxon
, setNameInSource
, secHandling
, newSecAcc
, newSecSyn
);
218 return swapSynonymAndAcceptedTaxon(synonym
, acceptedTaxon
, setNameInSource
, secHandling
, newSecAcc
, newSecSyn
);
222 private UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
, SecReferenceHandlingSwapEnum secHandling
, Reference newSecAcc
, Reference newSecSyn
){
223 UpdateResult result
= new UpdateResult();
224 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
226 TaxonName synonymName
= synonym
.getName();
227 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
228 Reference secAccepted
= acceptedTaxon
.getSec();
229 String microRefSecAccepted
= acceptedTaxon
.getSecMicroReference();
230 Reference secSynonym
= synonym
.getSec();
231 String microRefSecSynonym
= synonym
.getSecMicroReference();
233 // if (secHandling.equals(SecReferenceHandlingSwapEnum.AlwaysDelete) || (secAccepted != null && secSynonym != null && !secAccepted.getUuid().equals(secSynonym.getUuid()) && (secHandling.equals(SecReferenceHandlingSwapEnum.AlwaysSelect) || secHandling.equals(SecReferenceHandlingSwapEnum.KeepOrSelect)))){
234 // secAccepted = null;
235 // microRefSecAccepted = null;
236 // secSynonym = null;
237 // microRefSecSynonym = null;
241 Set
<ExternalLink
> accLinks
= new HashSet
<>();
242 if (acceptedTaxon
.getSecSource() != null){
243 for (ExternalLink link
: acceptedTaxon
.getSecSource().getLinks()){
244 accLinks
.add(ExternalLink
.NewInstance(ExternalLinkType
.Unknown
, link
.getUri()));
247 acceptedTaxon
.setName(synonymName
);
248 acceptedTaxon
.setSec(newSecAcc
);
249 // acceptedTaxon.setSecMicroReference(synonym.getSecMicroReference());
251 // if (synonym.getSecSource()!= null && synonym.getSecSource().getLinks() != null){
252 // acceptedTaxon.getSecSource().getLinks().clear();
253 // for (ExternalLink link: synonym.getSecSource().getLinks()){
254 // acceptedTaxon.getSecSource().addLink(ExternalLink.NewInstance(ExternalLinkType.Unknown, link.getUri()));
258 synonym
.setName(taxonName
);
259 synonym
.setSec(newSecSyn
);
260 // synonym.setSecMicroReference(microRefSecAccepted);
261 // if (synonym.getSecSource() != null){
262 // synonym.getSecSource().getLinks().clear();
263 // for (ExternalLink link: accLinks){
264 // synonym.getSecSource().addLink(link);
269 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, acceptedTaxon
.getDescriptions());
271 acceptedTaxon
.resetTitleCache();
272 synonym
.resetTitleCache();
274 MergeResult mergeTaxon
= merge(acceptedTaxon
, true);
275 MergeResult mergeSynonym
= merge(synonym
, true);
276 result
.setCdmEntity((CdmBase
) mergeTaxon
.getMergedEntity());
277 result
.addUpdatedObject((CdmBase
) mergeSynonym
.getMergedEntity());
282 private UpdateResult
swapSynonymAndAcceptedTaxonNewUuid(Synonym oldSynonym
, Taxon oldAcceptedTaxon
, boolean setNameInSource
, SecReferenceHandlingSwapEnum secHandling
, Reference newSecAcc
, Reference newSecSyn
){
283 UpdateResult result
= new UpdateResult();
284 oldAcceptedTaxon
.removeSynonym(oldSynonym
);
285 TaxonName synonymName
= oldSynonym
.getName();
286 TaxonName taxonName
= HibernateProxyHelper
.deproxy(oldAcceptedTaxon
.getName());
287 String oldTaxonTitleCache
= oldAcceptedTaxon
.getTitleCache();
289 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(taxonName
.getHomotypicalGroup());
290 synonymName
.removeTaxonBase(oldSynonym
);
292 List
<Synonym
> synonyms
= new ArrayList
<>();
293 for (Synonym syn
: oldAcceptedTaxon
.getSynonyms()){
294 syn
= HibernateProxyHelper
.deproxy(syn
, Synonym
.class);
297 for (Synonym syn
: synonyms
){
298 oldAcceptedTaxon
.removeSynonym(syn
);
300 Taxon newTaxon
= oldAcceptedTaxon
.clone(true, true, false, true);
301 newTaxon
.setSec(newSecAcc
);
304 Set
<TaxonDescription
> descriptionsToCopy
= new HashSet
<>(oldAcceptedTaxon
.getDescriptions());
305 for (TaxonDescription description
: descriptionsToCopy
){
306 newTaxon
.addDescription(description
);
309 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, newTaxon
.getDescriptions());
311 newTaxon
.setName(synonymName
);
313 newTaxon
.setPublish(oldSynonym
.isPublish());
314 for (Synonym syn
: synonyms
){
315 if (!syn
.getName().equals(newTaxon
.getName())){
316 newTaxon
.addSynonym(syn
, syn
.getType());
320 //move all data to new taxon
321 //Move Taxon RelationShips to new Taxon
322 for(TaxonRelationship taxonRelationship
: newTaxon
.getTaxonRelations()){
323 newTaxon
.removeTaxonRelation(taxonRelationship
);
326 for(TaxonRelationship taxonRelationship
: oldAcceptedTaxon
.getTaxonRelations()){
327 Taxon fromTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getFromTaxon());
328 Taxon toTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getToTaxon());
329 if (fromTaxon
== oldAcceptedTaxon
){
330 newTaxon
.addTaxonRelation(taxonRelationship
.getToTaxon(), taxonRelationship
.getType(),
331 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
333 }else if(toTaxon
== oldAcceptedTaxon
){
334 fromTaxon
.addTaxonRelation(newTaxon
, taxonRelationship
.getType(),
335 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
336 saveOrUpdate(fromTaxon
);
339 logger
.warn("Taxon is not part of its own Taxonrelationship");
342 // Remove old relationships
343 fromTaxon
.removeTaxonRelation(taxonRelationship
);
344 toTaxon
.removeTaxonRelation(taxonRelationship
);
345 taxonRelationship
.setToTaxon(null);
346 taxonRelationship
.setFromTaxon(null);
350 List
<TaxonNode
> nodes
= new ArrayList
<>(oldAcceptedTaxon
.getTaxonNodes());
351 for (TaxonNode node
: nodes
){
352 node
= HibernateProxyHelper
.deproxy(node
);
353 TaxonNode parent
= node
.getParent();
354 oldAcceptedTaxon
.removeTaxonNode(node
);
355 node
.setTaxon(newTaxon
);
357 parent
.addChildNode(node
, null, null);
362 Synonym newSynonym
= oldSynonym
.clone();
363 newSynonym
.setName(taxonName
);
364 newSynonym
.setPublish(oldAcceptedTaxon
.isPublish());
365 newSynonym
.setSec(newSecSyn
);
366 if (sameHomotypicGroup
){
367 newTaxon
.addSynonym(newSynonym
, SynonymType
.HOMOTYPIC_SYNONYM_OF
);
369 newTaxon
.addSynonym(newSynonym
, SynonymType
.HETEROTYPIC_SYNONYM_OF
);
373 TaxonDeletionConfigurator conf
= new TaxonDeletionConfigurator();
374 conf
.setDeleteNameIfPossible(false);
375 SynonymDeletionConfigurator confSyn
= new SynonymDeletionConfigurator();
376 confSyn
.setDeleteNameIfPossible(false);
377 result
.setCdmEntity(newTaxon
);
379 DeleteResult deleteResult
= deleteTaxon(oldAcceptedTaxon
.getUuid(), conf
, null);
380 if (oldSynonym
.isPersisted()){
381 oldSynonym
.setSecSource(null);
382 deleteResult
.includeResult(deleteSynonym(oldSynonym
.getUuid(), confSyn
));
384 result
.includeResult(deleteResult
);
389 private void handleNameUsedInSourceForSwap(boolean setNameInSource
, TaxonName taxonName
, String oldTaxonTitleCache
,
390 Set
<TaxonDescription
> descriptions
) {
391 for(TaxonDescription description
: descriptions
){
392 String message
= "Description copied from former accepted taxon: %s (Old title: %s)";
393 message
= String
.format(message
, oldTaxonTitleCache
, description
.getTitleCache());
394 description
.setTitleCache(message
, true);
396 for (DescriptionElementBase element
: description
.getElements()){
397 for (DescriptionElementSource source
: element
.getSources()){
398 if (source
.getNameUsedInSource() == null){
399 source
.setNameUsedInSource(taxonName
);
408 @Transactional(readOnly
= false)
409 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, Reference newSecRef
, String microRef
, SecReferenceHandlingEnum secHandling
, boolean deleteSynonym
) {
410 UpdateResult result
= new UpdateResult();
411 TaxonName acceptedName
= acceptedTaxon
.getName();
412 TaxonName synonymName
= synonym
.getName();
413 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
415 //check synonym is not homotypic
416 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
417 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
418 result
.addException(new HomotypicalGroupChangeException(message
));
423 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, newSecRef
, microRef
);
424 newAcceptedTaxon
.setPublish(synonym
.isPublish());
425 dao
.save(newAcceptedTaxon
);
426 result
.setCdmEntity(newAcceptedTaxon
);
427 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF
;
428 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
430 for (Synonym heteroSynonym
: heteroSynonyms
){
431 if (secHandling
== null){
432 heteroSynonym
.setSec(newSecRef
);
434 if (synonym
.equals(heteroSynonym
)){
435 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
437 //move synonyms in same homotypic group to new accepted taxon
438 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
441 dao
.saveOrUpdate(acceptedTaxon
);
442 result
.addUpdatedObject(acceptedTaxon
);
447 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
448 config
.setDeleteNameIfPossible(false);
449 this.deleteSynonym(synonym
, config
);
451 } catch (Exception e
) {
452 result
.addException(e
);
460 @Transactional(readOnly
= false)
461 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
462 UUID acceptedTaxonUuid
,
463 UUID newParentNodeUuid
,
465 String microReference
,
466 SecReferenceHandlingEnum secHandling
,
467 boolean deleteSynonym
) {
468 UpdateResult result
= new UpdateResult();
469 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
470 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
471 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
472 Reference newSecRef
= null;
473 switch (secHandling
){
476 case UseNewParentSec
:
477 newSecRef
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
480 Reference synSec
= synonym
.getSec();
481 if (synSec
!= null ){
482 newSecRef
= CdmBase
.deproxy(synSec
);
484 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
488 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
494 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, newSecRef
, microReference
, secHandling
, deleteSynonym
);
495 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
497 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
498 taxonNodeDao
.save(newNode
);
499 result
.addUpdatedObject(newTaxon
);
500 result
.addUpdatedObject(acceptedTaxon
);
501 result
.setCdmEntity(newNode
);
506 @Transactional(readOnly
= false)
507 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
509 TaxonRelationshipType taxonRelationshipType
,
511 String microcitation
){
513 UpdateResult result
= new UpdateResult();
514 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
515 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
516 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
517 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
518 // result.setCdmEntity(relatedTaxon);
519 result
.addUpdatedObject(relatedTaxon
);
520 result
.addUpdatedObject(toTaxon
);
525 @Transactional(readOnly
= false)
526 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
527 // Get name from synonym
528 if (synonym
== null){
532 UpdateResult result
= new UpdateResult();
534 TaxonName synonymName
= synonym
.getName();
536 /* // remove synonym from taxon
537 toTaxon.removeSynonym(synonym);
539 // Create a taxon with synonym name
540 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
541 fromTaxon
.setPublish(synonym
.isPublish());
543 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
545 // Add taxon relation
546 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
547 result
.setCdmEntity(fromTaxon
);
548 // since we are swapping names, we have to detach the name from the synonym completely.
549 // Otherwise the synonym will still be in the list of typified names.
550 // synonym.getName().removeTaxonBase(synonym);
551 result
.includeResult(this.deleteSynonym(synonym
, null));
556 @Transactional(readOnly
= false)
558 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
559 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
561 TaxonName synonymName
= synonym
.getName();
562 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
565 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
566 newHomotypicalGroup
.addTypifiedName(synonymName
);
568 //remove existing basionym relationships
569 synonymName
.removeBasionyms();
571 //add basionym relationship
572 if (setBasionymRelationIfApplicable
){
573 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
574 for (TaxonName basionym
: basionyms
){
575 synonymName
.addBasionym(basionym
);
579 //set synonym relationship correctly
580 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
582 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
583 if (acceptedTaxon
!= null){
585 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
586 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
587 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF
: SynonymType
.HETEROTYPIC_SYNONYM_OF
;
588 synonym
.setType(newRelationType
);
590 if (hasNewTargetTaxon
){
591 acceptedTaxon
.removeSynonym(synonym
, false);
594 if (hasNewTargetTaxon
){
595 @SuppressWarnings("null")
596 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
597 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
598 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF
: SynonymType
.HETEROTYPIC_SYNONYM_OF
;
599 targetTaxon
.addSynonym(synonym
, relType
);
604 @Transactional(readOnly
= false)
605 public UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
607 clazz
= TaxonBase
.class;
609 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
614 protected void setDao(ITaxonDao dao
) {
619 public <T
extends TaxonBase
> Pager
<T
> findTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
620 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
621 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
);
623 List
<T
> results
= new ArrayList
<>();
624 if(numberOfResults
> 0) { // no point checking again
625 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
,
626 pageSize
, pageNumber
, propertyPaths
);
629 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
633 public <T
extends TaxonBase
> List
<T
> listTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
634 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
636 return findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infragenericEpithet
, authorshipCache
, rank
,
637 pageSize
, pageNumber
, propertyPaths
).getRecords();
641 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
642 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
643 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
645 List
<TaxonRelationship
> results
= new ArrayList
<>();
646 if(numberOfResults
> 0) { // no point checking again
647 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
653 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
654 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
655 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
657 List
<TaxonRelationship
> results
= new ArrayList
<>();
658 if(numberOfResults
> 0) { // no point checking again
659 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
661 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
665 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
666 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
667 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
669 List
<TaxonRelationship
> results
= new ArrayList
<>();
670 if(numberOfResults
> 0) { // no point checking again
671 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
677 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
678 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
679 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
681 List
<TaxonRelationship
> results
= new ArrayList
<>();
682 if(numberOfResults
> 0) { // no point checking again
683 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
685 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
689 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
690 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
692 Long numberOfResults
= dao
.countTaxonRelationships(types
);
693 List
<TaxonRelationship
> results
= new ArrayList
<>();
694 if(numberOfResults
> 0) {
695 results
= dao
.getTaxonRelationships(types
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
701 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
702 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
703 return page(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, INCLUDE_UNPUBLISHED
);
707 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
708 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
, boolean includeUnpublished
) {
711 long resultSize
= dao
.count(clazz
, restrictions
);
712 if(AbstractPagerImpl
.hasResultsInRange(resultSize
, pageIndex
, pageSize
)){
713 records
= dao
.list(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, includeUnpublished
);
715 records
= new ArrayList
<>();
717 Pager
<S
> pager
= new DefaultPagerImpl
<>(pageIndex
, resultSize
, pageSize
, records
);
722 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
723 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
725 Synonym synonym
= null;
728 synonym
= (Synonym
) dao
.load(synonymUuid
);
729 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
730 } catch (ClassCastException e
){
731 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
732 } catch (NullPointerException e
){
733 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
736 Classification classificationFilter
= null;
737 if(classificationUuid
!= null){
739 classificationFilter
= classificationDao
.load(classificationUuid
);
740 } catch (NullPointerException e
){
741 //TODO not sure, why an NPE should be thrown in the above load method
742 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
744 if(classificationFilter
== null){
745 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
749 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
751 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
752 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
760 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
761 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
763 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
764 relatedTaxa
.remove(taxon
);
765 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
770 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
771 * <code>taxon</code> supplied as parameter.
774 * @param includeRelationships
776 * @param maxDepth can be <code>null</code> for infinite depth
779 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
780 boolean includeUnpublished
, Integer maxDepth
) {
786 if(includeRelationships
.isEmpty()){
790 if(maxDepth
!= null) {
793 if(logger
.isDebugEnabled()){
794 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
796 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
797 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
798 for (TaxonRelationship taxRel
: taxonRelationships
) {
801 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
804 // filter by includeRelationships
805 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
806 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
807 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
808 if(logger
.isDebugEnabled()){
809 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
811 taxa
.add(taxRel
.getToTaxon());
812 if(maxDepth
== null || maxDepth
> 0) {
813 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
816 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
817 taxa
.add(taxRel
.getFromTaxon());
818 if(logger
.isDebugEnabled()){
819 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
821 if(maxDepth
== null || maxDepth
> 0) {
822 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
832 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
833 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
835 List
<Synonym
> results
= new ArrayList
<>();
836 if(numberOfResults
> 0) { // no point checking again
837 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
840 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
844 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
845 List
<List
<Synonym
>> result
= new ArrayList
<>();
846 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
847 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
850 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
853 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
854 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
855 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
862 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
863 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
864 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
866 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
870 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
871 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
872 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
873 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
874 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
875 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
877 return heterotypicSynonymyGroups
;
881 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
883 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
884 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
885 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(), config
.isDoIncludeAuthors(),
886 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
887 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
889 return new ArrayList
<>();
894 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
896 @SuppressWarnings("rawtypes")
897 List
<IdentifiableEntity
> results
= new ArrayList
<>();
898 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
899 List
<TaxonBase
> taxa
= null;
902 long numberTaxaResults
= 0L;
904 List
<String
> propertyPath
= new ArrayList
<>();
905 if(configurator
.getTaxonPropertyPath() != null){
906 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
909 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
910 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
912 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
913 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
914 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
915 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
918 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
919 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
920 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
921 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
922 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
923 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
927 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
930 results
.addAll(taxa
);
933 numberOfResults
+= numberTaxaResults
;
935 // Names without taxa
936 if (configurator
.isDoNamesWithoutTaxa()) {
937 int numberNameResults
= 0;
939 List
<TaxonName
> names
=
940 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
941 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
942 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
943 if (names
.size() > 0) {
944 for (TaxonName taxonName
: names
) {
945 if (taxonName
.getTaxonBases().size() == 0) {
946 results
.add(taxonName
);
950 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
951 numberOfResults
+= numberNameResults
;
955 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
958 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
959 return dao
.getUuidAndTitleCache(limit
, pattern
);
963 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
964 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
968 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
969 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
970 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
971 return listMedia(taxon
, includeRelationships
, limitToGalleries
, includeTaxonDescriptions
, includeOccurrences
, false,
972 includeTaxonNameDescriptions
, propertyPath
);
976 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
977 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
978 Boolean includeOriginalOccurences
, Boolean includeTaxonNameDescriptions
,
979 List
<String
> propertyPath
) {
982 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
984 // LogUtils.setLevel(logger, Level.TRACE);
985 // LogUtils.setLevel("org.hibernate.SQL", Level.TRACE);
987 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - START");}
989 Set
<Taxon
> taxa
= new HashSet
<>();
990 List
<Media
> taxonMedia
= new ArrayList
<>();
991 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
993 if (limitToGalleries
== null) {
994 limitToGalleries
= false;
997 // --- resolve related taxa
998 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
999 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - resolve related taxa");}
1000 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
1003 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
1005 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
1006 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - includeTaxonDescriptions");}
1007 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
1008 // --- TaxonDescriptions
1009 for (Taxon t
: taxa
) {
1010 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
1012 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
1013 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
1014 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
1015 for (Media media
: element
.getMedia()) {
1016 if(taxonDescription
.isImageGallery()){
1017 taxonMedia
.add(media
);
1020 nonImageGalleryImages
.add(media
);
1026 //put images from image gallery first (#3242)
1027 taxonMedia
.addAll(nonImageGalleryImages
);
1031 if(includeOccurrences
!= null && includeOccurrences
) {
1032 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - includeOccurrences");}
1033 @SuppressWarnings("rawtypes")
1034 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
1036 for (Taxon t
: taxa
) {
1037 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(
1038 null, t
, includeUnpublished
, null, null, null, null));
1040 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
1042 // direct media removed from specimen #3597
1043 // taxonMedia.addAll(occurrence.getMedia());
1045 // SpecimenDescriptions
1046 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
1047 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
1048 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
1049 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
1050 for (DescriptionElementBase element
: elements
) {
1051 for (Media media
: element
.getMedia()) {
1052 taxonMedia
.add(media
);
1058 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
1059 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
1061 //TODO why may collections have media attached? #
1062 if (derivedUnit
.getCollection() != null){
1063 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
1066 //media in hierarchy
1068 taxonMedia
.addAll(occurrenceService
.getMediaInHierarchy(occurrence
, includeOriginalOccurences
, includeOccurrences
, null, null, propertyPath
).getRecords());
1073 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
1074 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - includeTaxonNameDescriptions");}
1075 // --- TaxonNameDescription
1076 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
1077 for (Taxon t
: taxa
) {
1078 nameDescriptions
.addAll(t
.getName().getDescriptions());
1080 for(TaxonNameDescription nameDescription
: nameDescriptions
){
1081 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
1082 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
1083 for (DescriptionElementBase element
: elements
) {
1084 for (Media media
: element
.getMedia()) {
1085 taxonMedia
.add(media
);
1092 taxonMedia
= deduplicateMedia(taxonMedia
);
1094 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - initialize");}
1095 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
1097 if (logger
.isTraceEnabled()){logger
.trace("listMedia() - END");}
1102 private List
<Media
> deduplicateMedia(List
<Media
> taxonMedia
) {
1103 return taxonMedia
.stream().distinct().collect(Collectors
.toList());
1107 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
1108 return this.dao
.loadList(listOfIDs
, null, null);
1112 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
1113 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
1117 public long countSynonyms(boolean onlyAttachedToTaxon
){
1118 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
1122 @Transactional(readOnly
=false)
1123 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
1125 if (config
== null){
1126 config
= new TaxonDeletionConfigurator();
1128 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
1129 DeleteResult result
= new DeleteResult();
1132 result
.addException(new Exception ("The taxon was already deleted."));
1135 taxon
= HibernateProxyHelper
.deproxy(taxon
);
1136 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
1137 config
.setClassificationUuid(classificationUuid
);
1138 result
= isDeletable(taxonUUID
, config
);
1141 // --- DeleteSynonymRelations
1142 if (config
.isDeleteSynonymRelations()){
1143 boolean removeSynonymNameFromHomotypicalGroup
= false;
1144 // use tmp Set to avoid concurrent modification
1145 Set
<Synonym
> synsToDelete
= new HashSet
<>();
1146 synsToDelete
.addAll(taxon
.getSynonyms());
1147 for (Synonym synonym
: synsToDelete
){
1148 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
1150 // --- DeleteSynonymsIfPossible
1151 if (config
.isDeleteSynonymsIfPossible()){
1153 boolean newHomotypicGroupIfNeeded
= true;
1154 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
1155 result
.includeResult(deleteSynonym(synonym
, synConfig
));
1160 // --- DeleteTaxonRelationships
1161 if (! config
.isDeleteTaxonRelationships()){
1162 if (taxon
.getTaxonRelations().size() > 0){
1164 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1165 "Remove taxon from all relations to other taxa prior to deletion."));
1168 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
1169 configRelTaxon
.setDeleteTaxonNodes(false);
1170 configRelTaxon
.setDeleteConceptRelationships(true);
1172 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
1173 if (config
.isDeleteMisappliedNames()
1174 && taxRel
.getType().isMisappliedName()
1175 && taxon
.equals(taxRel
.getToTaxon())){
1176 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
1177 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
1179 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
1180 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1181 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
1182 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1185 taxon
.removeTaxonRelation(taxRel
);
1190 if (config
.isDeleteDescriptions()){
1191 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
1192 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
1193 for (TaxonDescription desc
: descriptions
){
1194 //TODO use description delete configurator ?
1195 //FIXME check if description is ALWAYS deletable
1196 if (desc
.getDescribedSpecimenOrObservation() != null){
1198 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1199 " which also describes specimens or observations"));
1202 removeDescriptions
.add(desc
);
1205 for (TaxonDescription desc
: removeDescriptions
){
1206 taxon
.removeDescription(desc
);
1207 descriptionService
.delete(desc
);
1214 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1215 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1217 if (taxon
.getTaxonNodes().size() != 0){
1218 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1219 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1220 TaxonNode node
= null;
1221 boolean deleteChildren
;
1222 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1223 deleteChildren
= true;
1225 deleteChildren
= false;
1227 boolean success
= true;
1228 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1229 while (iterator
.hasNext()){
1230 node
= iterator
.next();
1231 if (node
.getClassification().equals(classification
)){
1237 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1238 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1239 nodeService
.delete(node
);
1240 result
.addDeletedObject(node
);
1243 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1245 } else if (config
.isDeleteInAllClassifications()){
1246 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1247 nodesList
.addAll(taxon
.getTaxonNodes());
1248 for (ITaxonTreeNode treeNode
: nodesList
){
1249 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1250 if(!deleteChildren
){
1251 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1252 for (Object childNode
: childNodes
){
1253 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1254 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1258 config
.getTaxonNodeConfig().setDeleteElement(false);
1259 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1260 if (!resultNodes
.isOk()){
1261 result
.addExceptions(resultNodes
.getExceptions());
1262 result
.setStatus(resultNodes
.getStatus());
1264 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1269 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1273 TaxonName name
= taxon
.getName();
1274 taxon
.setName(null);
1275 this.saveOrUpdate(taxon
);
1277 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1280 result
.addDeletedObject(taxon
);
1281 }catch(Exception e
){
1282 result
.addException(e
);
1287 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1291 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1292 DeleteResult nameResult
= new DeleteResult();
1293 //remove name if possible (and required)
1295 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1297 if (nameResult
.isError() || nameResult
.isAbort()){
1298 result
.addRelatedObject(name
);
1299 //result.addExceptions(nameResult.getExceptions()); this was already added in deleteTaxon!
1301 result
.includeResult(nameResult
);
1310 @Transactional(readOnly
= false)
1311 public DeleteResult
delete(UUID synUUID
){
1312 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1313 return this.deleteSynonym(syn
, null);
1317 @Transactional(readOnly
= false)
1318 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1319 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1323 @Transactional(readOnly
= false)
1324 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1325 DeleteResult result
= new DeleteResult();
1326 if (synonym
== null){
1328 result
.addException(new Exception("The synonym was already deleted."));
1332 if (config
== null){
1333 config
= new SynonymDeletionConfigurator();
1336 result
= isDeletable(synonym
.getUuid(), config
);
1340 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1343 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1345 if (accTaxon
!= null){
1346 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1347 accTaxon
.removeSynonym(synonym
, false);
1348 this.saveOrUpdate(accTaxon
);
1349 result
.addUpdatedObject(accTaxon
);
1351 this.saveOrUpdate(synonym
);
1355 TaxonName name
= synonym
.getName();
1356 synonym
.setName(null);
1358 dao
.delete(synonym
);
1359 result
.addDeletedObject(synonym
);
1361 //remove name if possible (and required)
1362 if (name
!= null && config
.isDeleteNameIfPossible()){
1364 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1365 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1366 result
.addExceptions(nameDeleteResult
.getExceptions());
1367 result
.addRelatedObject(name
);
1368 result
.addUpdatedObject(name
);
1370 result
.addDeletedObject(name
);
1378 public Map
<String
, Map
<UUID
,Set
<TaxonName
>>> findIdenticalTaxonNames(List
<UUID
> sourceRefUuids
, List
<String
> propertyPaths
) {
1379 return this.dao
.findIdenticalNames(sourceRefUuids
, propertyPaths
);
1383 public Taxon
findBestMatchingTaxon(String taxonName
) {
1384 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1385 config
.setTaxonNameTitle(taxonName
);
1386 return findBestMatchingTaxon(config
);
1390 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1392 Taxon bestCandidate
= null;
1394 // 1. search for accepted taxa
1395 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1396 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1397 boolean bestCandidateMatchesSecUuid
= false;
1398 boolean bestCandidateIsInClassification
= false;
1399 int countEqualCandidates
= 0;
1400 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1401 if(taxonBaseCandidate
instanceof Taxon
){
1402 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1403 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1404 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1406 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1407 bestCandidate
= newCanditate
;
1408 countEqualCandidates
= 1;
1409 bestCandidateMatchesSecUuid
= true;
1413 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1414 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1416 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1417 bestCandidate
= newCanditate
;
1418 countEqualCandidates
= 1;
1419 bestCandidateIsInClassification
= true;
1422 if (bestCandidate
== null){
1423 bestCandidate
= newCanditate
;
1424 countEqualCandidates
= 1;
1427 }else{ //not Taxon.class
1430 countEqualCandidates
++;
1433 if (bestCandidate
!= null){
1434 if(countEqualCandidates
> 1){
1435 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1436 return bestCandidate
;
1438 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1439 return bestCandidate
;
1443 // 2. search for synonyms
1444 if (config
.isIncludeSynonyms()){
1445 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1446 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1447 for(TaxonBase taxonBase
: synonymList
){
1448 if(taxonBase
instanceof Synonym
){
1449 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1450 bestCandidate
= synonym
.getAcceptedTaxon();
1451 if(bestCandidate
!= null){
1452 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1453 return bestCandidate
;
1455 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1460 } catch (Exception e
){
1462 e
.printStackTrace();
1465 return bestCandidate
;
1468 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1469 UUID configClassificationUuid
= config
.getClassificationUuid();
1470 if (configClassificationUuid
== null){
1473 for (TaxonNode node
: taxon
.getTaxonNodes()){
1474 UUID classUuid
= node
.getClassification().getUuid();
1475 if (configClassificationUuid
.equals(classUuid
)){
1482 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1483 UUID configSecUuid
= config
.getSecUuid();
1484 if (configSecUuid
== null){
1487 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1488 return configSecUuid
.equals(taxonSecUuid
);
1492 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1493 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1494 if(! synonymList
.isEmpty()){
1495 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1496 if(synonymList
.size() == 1){
1497 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1500 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1508 @Transactional(readOnly
= false)
1509 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
1510 SynonymType newSynonymType
, UUID newSecundumUuid
, String newSecundumDetail
,
1511 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1513 UpdateResult result
= new UpdateResult();
1514 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
), Taxon
.class);
1515 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
1516 newSecundumUuid
, newSecundumDetail
, keepSecundumIfUndefined
);
1522 @Transactional(readOnly
= false)
1523 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1525 boolean moveHomotypicGroup
,
1526 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1527 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1529 oldSynonym
.getSec()!= null? oldSynonym
.getSec().getUuid(): null,
1530 oldSynonym
.getSecMicroReference(),
1535 @Transactional(readOnly
= false)
1536 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1538 boolean moveHomotypicGroup
,
1539 SynonymType newSynonymType
,
1540 UUID newSecundumUuid
,
1541 String newSecundumDetail
,
1542 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1544 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1545 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1546 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1547 TaxonName synonymName
= synonym
.getName();
1548 TaxonName fromTaxonName
= oldTaxon
.getName();
1549 //set default relationship type
1550 if (newSynonymType
== null){
1551 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF
;
1553 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF
);
1555 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1556 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1557 boolean isSingleInGroup
= !(hgSize
> 1);
1559 if (! isSingleInGroup
){
1560 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1561 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1562 if (isHomotypicToAccepted
){
1563 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.";
1564 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1565 message
= String
.format(message
, homotypicRelatives
);
1566 throw new HomotypicalGroupChangeException(message
);
1568 if (! moveHomotypicGroup
){
1569 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.";
1570 throw new HomotypicalGroupChangeException(message
);
1573 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1575 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1577 UpdateResult result
= new UpdateResult();
1578 //move all synonyms to new taxon
1579 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1580 Reference newSecundum
= referenceService
.load(newSecundumUuid
);
1581 for (Synonym synRelation
: homotypicSynonyms
){
1583 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1584 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1585 oldTaxon
.removeSynonym(synRelation
, false);
1586 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1588 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1589 synRelation
.setSec(newSecundum
);
1591 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1592 synRelation
.setSecMicroReference(newSecundumDetail
);
1595 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1596 if (!synRelation
.equals(oldSynonym
)){
1601 result
.addUpdatedObject(oldTaxon
);
1602 result
.addUpdatedObject(newTaxon
);
1603 result
.addUpdatedObject(homotypicGroup
);
1604 result
.addUpdatedObject(synonym
);
1605 saveOrUpdate(oldTaxon
);
1606 saveOrUpdate(newTaxon
);
1612 public <T
extends TaxonBase
>List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1614 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1618 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1619 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1620 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1621 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1622 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1624 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1625 null, includeUnpublished
, languages
, highlightFragments
, null);
1627 // --- execute search
1628 TopGroups
<BytesRef
> topDocsResultSet
;
1630 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1631 } catch (ParseException e
) {
1632 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1633 luceneParseException
.setStackTrace(e
.getStackTrace());
1634 throw luceneParseException
;
1637 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1638 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
1640 // --- initialize taxa, thighlight matches ....
1641 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1642 @SuppressWarnings("rawtypes")
1643 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1644 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1646 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1647 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1650 @Transactional(readOnly
= true)
1652 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
) {
1653 long numberOfResults
= dao
.countByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
);
1655 long numberOfResults_doubtful
= dao
.countByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
);
1656 List
<S
> results
= new ArrayList
<>();
1657 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1659 results
= dao
.findByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1660 results
.addAll(dao
.findByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1662 Collections
.sort(results
, new TaxonComparator());
1663 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1666 @Transactional(readOnly
= true)
1668 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
) {
1669 long numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
1670 //check whether there are doubtful taxa matching
1671 long numberOfResults_doubtful
= dao
.countByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
);
1672 List
<S
> results
= new ArrayList
<>();
1673 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1674 if (numberOfResults
> 0){
1675 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1677 results
= new ArrayList
<>();
1679 if (numberOfResults_doubtful
> 0){
1680 results
.addAll(dao
.findByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1683 Collections
.sort(results
, new TaxonComparator());
1684 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1688 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1689 Classification classification
, TaxonNode subtree
,
1690 Integer pageSize
, Integer pageNumber
,
1691 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1693 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
1695 // --- execute search
1696 TopGroups
<BytesRef
> topDocsResultSet
;
1698 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1699 } catch (ParseException e
) {
1700 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1701 luceneParseException
.setStackTrace(e
.getStackTrace());
1702 throw luceneParseException
;
1705 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1706 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
1708 // --- initialize taxa, thighlight matches ....
1709 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1710 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1711 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1713 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1714 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1719 * @param queryString
1720 * @param classification
1721 * @param includeUnpublished
1723 * @param highlightFragments
1724 * @param sortFields TODO
1725 * @param directorySelectClass
1728 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1729 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1730 boolean highlightFragments
, SortField
[] sortFields
) {
1732 Builder finalQueryBuilder
= new Builder();
1733 Builder textQueryBuilder
= new Builder();
1735 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1736 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1738 if(sortFields
== null){
1739 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1741 luceneSearch
.setSortFields(sortFields
);
1743 // ---- search criteria
1744 luceneSearch
.setCdmTypRestriction(clazz
);
1746 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1747 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1748 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1750 if(className
!= null){
1751 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1754 BooleanQuery textQuery
= textQueryBuilder
.build();
1755 if(textQuery
.clauses().size() > 0) {
1756 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1759 if(classification
!= null){
1760 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1762 if(subtree
!= null){
1763 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1765 if(!includeUnpublished
) {
1766 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1767 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1768 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1771 luceneSearch
.setQuery(finalQueryBuilder
.build());
1773 if(highlightFragments
){
1774 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1776 return luceneSearch
;
1780 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1781 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1782 * drawback of requiring to do the join an indexing time.
1783 * see https://dev.e-taxonomy.eu/redmine/projects/edit/wiki/LuceneNotes#JoinsinLucene for more information on this.
1785 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1787 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1788 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1790 * @param queryString
1791 * @param classification
1793 * @param highlightFragments
1794 * @param sortFields TODO
1797 * @throws IOException
1799 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1800 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1801 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1804 String queryTermField
;
1805 String toField
= "id"; // TaxonBase.uuid
1806 String publishField
;
1807 String publishFieldInvers
;
1809 if(edge
.isBidirectional()){
1810 throw new RuntimeException("Bidirectional joining not supported!");
1813 fromField
= "relatedFrom.id";
1814 queryTermField
= "relatedFrom.titleCache";
1815 publishField
= "relatedFrom.publish";
1816 publishFieldInvers
= "relatedTo.publish";
1817 } else if(edge
.isInvers()) {
1818 fromField
= "relatedTo.id";
1819 queryTermField
= "relatedTo.titleCache";
1820 publishField
= "relatedTo.publish";
1821 publishFieldInvers
= "relatedFrom.publish";
1823 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1826 Builder finalQueryBuilder
= new Builder();
1828 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1829 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1831 Builder joinFromQueryBuilder
= new Builder();
1832 if(StringUtils
.isNotEmpty(queryString
)){
1833 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1835 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1836 if(!includeUnpublished
){
1837 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1838 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1841 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1843 if(sortFields
== null){
1844 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1846 luceneSearch
.setSortFields(sortFields
);
1848 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1850 if(classification
!= null){
1851 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1853 if(subtree
!= null){
1854 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1857 luceneSearch
.setQuery(finalQueryBuilder
.build());
1859 if(highlightFragments
){
1860 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1862 return luceneSearch
;
1866 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1867 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1868 Classification classification
, TaxonNode subtree
,
1869 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1870 boolean highlightFragments
, Integer pageSize
,
1871 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1872 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1874 // FIXME: allow taxonomic ordering
1875 // hql equivalent: order by t.name.genusOrUninomial, case when t.name.specificEpithet
1876 // like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache";
1877 // this requires building a special sort column by a special classBridge
1878 if(highlightFragments
){
1879 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1880 "currently not fully supported by this method and thus " +
1881 "may not work with common names and misapplied names.");
1884 // convert sets to lists
1885 List
<NamedArea
> namedAreaList
= null;
1886 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1887 if(namedAreas
!= null){
1888 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1889 namedAreaList
.addAll(namedAreas
);
1891 if(distributionStatus
!= null){
1892 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1893 distributionStatusList
.addAll(distributionStatus
);
1896 // set default if parameter is null
1897 if(searchModes
== null){
1898 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1901 // set sort order and thus override any sort orders which may have been
1902 // defined by prepare*Search methods
1903 if(orderHints
== null){
1904 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1906 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1908 for(OrderHint orderHint
: orderHints
){
1909 sortFields
[i
++] = orderHint
.toSortField();
1911 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1912 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1914 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1916 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1917 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1920 ======== filtering by distribution , HOWTO ========
1922 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1923 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1924 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1925 which will be put into a FilteredQuersy in the end ?
1928 3. how does it work in spatial?
1930 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1931 - http://www.infoq.com/articles/LuceneSpatialSupport
1932 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1933 ------------------------------------------------------------------------
1936 A) use a separate distribution filter per index sub-query/search:
1937 - byTaxonSyonym (query TaxaonBase):
1938 use a join area filter (Distribution -> TaxonBase)
1939 - byCommonName (query DescriptionElementBase): use an area filter on
1940 DescriptionElementBase !!! PROBLEM !!!
1941 This cannot work since the distributions are different entities than the
1942 common names and thus these are different lucene documents.
1943 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1944 use a join area filter (Distribution -> TaxonBase)
1946 B) use a common distribution filter for all index sub-query/searches:
1947 - use a common join area filter (Distribution -> TaxonBase)
1948 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1949 PROBLEM in this case: we are losing the fragment highlighting for the
1950 common names, since the returned documents are always TaxonBases
1953 /* The QueryFactory for creating filter queries on Distributions should
1954 * The query factory used for the common names query cannot be reused
1955 * for this case, since we want to only record the text fields which are
1956 * actually used in the primary query
1958 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1960 Builder multiIndexByAreaFilterBuilder
= new Builder();
1961 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1963 // search for taxa or synonyms
1964 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1965 @SuppressWarnings("rawtypes")
1966 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1967 String className
= null;
1968 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1969 taxonBaseSubclass
= Taxon
.class;
1970 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1971 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1973 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1974 queryString
, classification
, subtree
, className
,
1975 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1976 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
1977 /* A) does not work!!!!
1978 if(addDistributionFilter){
1979 // in this case we need a filter which uses a join query
1980 // to get the TaxonBase documents for the DescriptionElementBase documents
1981 // which are matching the areas in question
1982 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1984 distributionStatusList,
1985 distributionFilterQueryFactory
1987 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1990 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1991 // add additional area filter for synonyms
1992 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1993 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1995 //TODO replace by createByDistributionJoinQuery
1996 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1997 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1998 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2003 // search by CommonTaxonName
2004 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
2006 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2007 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
2008 CommonTaxonName
.class,
2009 "inDescription.taxon.id",
2011 QueryFactory
.addTypeRestriction(
2012 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
2013 , CommonTaxonName
.class
2014 ).build(), "id", null, ScoreMode
.Max
);
2015 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
2016 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
2017 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2018 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
2019 Builder builder
= new BooleanQuery
.Builder();
2020 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
2021 if(!includeUnpublished
) {
2022 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
2023 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
2025 byCommonNameSearch
.setQuery(builder
.build());
2026 byCommonNameSearch
.setSortFields(sortFields
);
2028 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
2030 luceneSearches
.add(byCommonNameSearch
);
2032 /* A) does not work!!!!
2034 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
2035 queryString, classification, null, languages, highlightFragments)
2037 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2038 if(addDistributionFilter){
2039 // in this case we are able to use DescriptionElementBase documents
2040 // which are matching the areas in question directly
2041 BooleanQuery byDistributionQuery = createByDistributionQuery(
2043 distributionStatusList,
2044 distributionFilterQueryFactory
2046 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
2051 // search by misapplied names
2052 //TODO merge with pro parte synonym search once #7487 is fixed
2053 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
2055 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
2056 // which allows doing query time joins
2057 // finds the misapplied name (Taxon B) which is an misapplication for
2058 // a related Taxon A.
2060 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2061 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
2062 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
2064 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
2065 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
2068 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2069 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2070 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2071 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
2073 if(addDistributionFilter
){
2074 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2076 //TODO replace by createByDistributionJoinQuery
2077 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2080 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
2081 * Maybe this is a bug in java itself.
2083 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2086 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2088 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2089 * will execute as expected:
2091 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2092 * String toField = "relation." + misappliedNameForUuid +".to.id";
2094 * Comparing both strings by the String.equals method returns true, so both String are identical.
2096 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2097 * 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)
2098 * The bug is persistent after a reboot of the development computer.
2100 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2101 String toField
= "relation." + TaxonRelationshipType
.uuidMisappliedNameFor
+".to.id";
2102 // String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2103 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2104 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2107 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2108 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2109 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2111 String toFieldProParte
= "relation." + TaxonRelationshipType
.uuidProParteMisappliedNameFor
+".to.id";
2112 Query taxonAreaJoinQueryProParte
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2113 fromField
, true, byDistributionQuery
, toFieldProParte
, null, ScoreMode
.None
);
2114 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQueryProParte
, Occur
.SHOULD
);
2116 String toFieldPartial
= "relation." + TaxonRelationshipType
.uuidPartialMisappliedNameFor
+".to.id";
2117 Query taxonAreaJoinQueryPartial
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2118 fromField
, true, byDistributionQuery
, toFieldPartial
, null, ScoreMode
.None
);
2119 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQueryPartial
, Occur
.SHOULD
);
2123 // search by pro parte synonyms
2124 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
2125 //TODO merge with misapplied name search once #7487 is fixed
2126 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2127 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
2129 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2130 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2131 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2132 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
2134 if(addDistributionFilter
){
2135 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2138 String toField
= "relation."+TaxonRelationshipType
.uuidProParteSynonymFor
+".to.id";
2139 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2140 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2141 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2142 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2145 toField
= "relation."+TaxonRelationshipType
.uuidPartialSynonymFor
+".to.id";
2146 // BooleanQuery byDistributionQuery2 = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2147 Query taxonAreaJoinQuery2
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2148 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2149 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery2
, Occur
.SHOULD
);
2151 }//end pro parte synonyms
2153 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
2154 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
2156 if(addDistributionFilter
){
2159 // in this case we need a filter which uses a join query
2160 // to get the TaxonBase documents for the DescriptionElementBase documents
2161 // which are matching the areas in question
2163 // for doTaxa, doByCommonName
2164 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
2166 distributionStatusList
,
2167 distributionFilterQueryFactory
,
2170 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2173 if (addDistributionFilter
){
2174 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
2178 // --- execute search
2179 TopGroups
<BytesRef
> topDocsResultSet
;
2181 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2182 } catch (ParseException e
) {
2183 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2184 luceneParseException
.setStackTrace(e
.getStackTrace());
2185 throw luceneParseException
;
2188 // --- initialize taxa, highlight matches ....
2189 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2192 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2193 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2195 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
2196 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2200 * @param namedAreaList at least one area must be in the list
2201 * @param distributionStatusList optional
2202 * @param toType toType
2203 * Optional parameter. Only used for debugging to print the toType documents
2204 * @param asFilter TODO
2206 * @throws IOException
2208 protected Query
createByDistributionJoinQuery(
2209 List
<NamedArea
> namedAreaList
,
2210 List
<PresenceAbsenceTerm
> distributionStatusList
,
2211 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
2212 ) throws IOException
{
2214 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2215 String toField
= "id"; // id in toType usually this is the TaxonBase index
2217 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
2219 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
2221 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
2223 return taxonAreaJoinQuery
;
2227 * @param namedAreaList
2228 * @param distributionStatusList
2229 * @param queryFactory
2232 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
2233 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
2234 Builder areaQueryBuilder
= new Builder();
2235 // area field from Distribution
2236 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2238 // status field from Distribution
2239 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2240 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2243 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2244 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2249 * This method has been primarily created for testing the area join query but might
2250 * also be useful in other situations
2252 * @param namedAreaList
2253 * @param distributionStatusList
2254 * @param classification
2255 * @param highlightFragments
2257 * @throws IOException
2259 protected LuceneSearch
prepareByDistributionSearch(
2260 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2261 Classification classification
, TaxonNode subtree
) throws IOException
{
2263 Builder finalQueryBuilder
= new Builder();
2265 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2267 // FIXME is this query factory using the wrong type?
2268 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2270 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2271 luceneSearch
.setSortFields(sortFields
);
2274 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2276 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2278 if(classification
!= null){
2279 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
2281 if(subtree
!= null){
2282 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
2284 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2285 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2286 luceneSearch
.setQuery(finalQuery
);
2288 return luceneSearch
;
2292 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2293 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2294 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2295 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2297 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2299 // --- execute search
2300 TopGroups
<BytesRef
> topDocsResultSet
;
2302 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2303 } catch (ParseException e
) {
2304 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2305 luceneParseException
.setStackTrace(e
.getStackTrace());
2306 throw luceneParseException
;
2309 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2310 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2312 // --- initialize taxa, highlight matches ....
2313 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2314 @SuppressWarnings("rawtypes")
2315 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2316 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2318 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2319 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2323 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2324 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2325 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2327 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2328 classification
, subtree
,
2329 null, languages
, highlightFragments
);
2330 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2331 includeUnpublished
, languages
, highlightFragments
, null);
2333 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2335 // --- execute search
2336 TopGroups
<BytesRef
> topDocsResultSet
;
2338 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2339 } catch (ParseException e
) {
2340 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2341 luceneParseException
.setStackTrace(e
.getStackTrace());
2342 throw luceneParseException
;
2345 // --- initialize taxa, highlight matches ....
2346 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2348 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2349 idFieldMap
.put(CdmBaseType
.TAXON_BASE
, "id");
2350 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2352 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2353 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2355 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2356 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2359 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2360 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2361 List
<Language
> languages
, boolean highlightFragments
) {
2363 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2364 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2366 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2368 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2369 languages
, descriptionElementQueryFactory
);
2371 luceneSearch
.setSortFields(sortFields
);
2372 luceneSearch
.setCdmTypRestriction(clazz
);
2373 luceneSearch
.setQuery(finalQuery
);
2374 if(highlightFragments
){
2375 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2378 return luceneSearch
;
2381 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2382 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2383 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2385 Builder finalQueryBuilder
= new Builder();
2386 Builder textQueryBuilder
= new Builder();
2388 if(!StringUtils
.isEmpty(queryString
)){
2390 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2393 Builder nameQueryBuilder
= new Builder();
2394 if(languages
== null || languages
.size() == 0){
2395 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2397 Builder languageSubQueryBuilder
= new Builder();
2398 for(Language lang
: languages
){
2399 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2401 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2402 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2404 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2407 // text field from TextData
2408 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2410 // --- TermBase fields - by representation ----
2411 // state field from CategoricalData
2412 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2414 // state field from CategoricalData
2415 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2417 // area field from Distribution
2418 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2420 // status field from Distribution
2421 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2423 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2426 // --- classification ----
2429 if(classification
!= null){
2430 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2432 if(subtree
!= null){
2433 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2436 // --- IdentifieableEntity fields - by uuid
2437 if(features
!= null && features
.size() > 0 ){
2438 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2441 // the description must be associated with a taxon
2442 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2444 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2445 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2450 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2452 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2453 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2455 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2456 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2458 UUID nameUuid
= taxon
.getName().getUuid();
2459 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2460 String epithetOfTaxon
= null;
2461 String infragenericEpithetOfTaxon
= null;
2462 String infraspecificEpithetOfTaxon
= null;
2463 if (taxonName
.isSpecies()){
2464 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2465 } else if (taxonName
.isInfraGeneric()){
2466 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2467 } else if (taxonName
.isInfraSpecific()){
2468 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2470 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2471 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2472 List
<String
> taxonNames
= new ArrayList
<>();
2474 for (TaxonNode node
: nodes
){
2475 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2476 // List<String> synonymsEpithet = new ArrayList<>();
2478 if (node
.getClassification().equals(classification
)){
2479 if (!node
.isTopmostNode()){
2480 TaxonNode parent
= node
.getParent();
2481 parent
= CdmBase
.deproxy(parent
);
2482 TaxonName parentName
= parent
.getTaxon().getName();
2483 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2484 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2486 //create inferred synonyms for species, subspecies
2487 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2489 Synonym inferredEpithet
= null;
2490 Synonym inferredGenus
= null;
2491 Synonym potentialCombination
= null;
2493 List
<String
> propertyPaths
= new ArrayList
<>();
2494 propertyPaths
.add("synonym");
2495 propertyPaths
.add("synonym.name");
2496 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2497 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2499 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF
, null, null,orderHintsSynonyms
,propertyPaths
);
2500 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF
,
2501 null, null,orderHintsSynonyms
,propertyPaths
);
2503 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2504 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2505 if (doWithMisappliedNames
){
2506 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2507 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2508 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2509 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2510 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2511 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2514 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF
)){
2515 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2517 inferredEpithet
= createInferredEpithets(taxon
,
2518 zooHashMap
, taxonName
, epithetOfTaxon
,
2519 infragenericEpithetOfTaxon
,
2520 infraspecificEpithetOfTaxon
,
2521 taxonNames
, parentName
,
2522 synonymRelationOfParent
);
2524 inferredSynonyms
.add(inferredEpithet
);
2525 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2526 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2529 if (doWithMisappliedNames
){
2531 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2532 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2534 inferredEpithet
= createInferredEpithets(taxon
,
2535 zooHashMap
, taxonName
, epithetOfTaxon
,
2536 infragenericEpithetOfTaxon
,
2537 infraspecificEpithetOfTaxon
,
2538 taxonNames
, parentName
,
2541 inferredSynonyms
.add(inferredEpithet
);
2542 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2543 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2547 if (!taxonNames
.isEmpty()){
2548 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2549 if (!synNotInCDM
.isEmpty()){
2550 inferredSynonymsToBeRemoved
.clear();
2552 for (Synonym syn
:inferredSynonyms
){
2553 IZoologicalName name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2554 if (!synNotInCDM
.contains(name
.getNameCache())){
2555 inferredSynonymsToBeRemoved
.add(syn
);
2559 // Remove identified Synonyms from inferredSynonyms
2560 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2561 inferredSynonyms
.remove(synonym
);
2566 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF
)){
2568 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2570 inferredGenus
= createInferredGenus(taxon
,
2571 zooHashMap
, taxonName
, epithetOfTaxon
,
2572 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2574 inferredSynonyms
.add(inferredGenus
);
2575 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2576 taxonNames
.add(inferredGenus
.getName().getNameCache());
2579 if (doWithMisappliedNames
){
2581 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2582 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2583 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2585 inferredSynonyms
.add(inferredGenus
);
2586 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2587 taxonNames
.add(inferredGenus
.getName().getNameCache());
2592 if (!taxonNames
.isEmpty()){
2593 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2594 IZoologicalName name
;
2595 if (!synNotInCDM
.isEmpty()){
2596 inferredSynonymsToBeRemoved
.clear();
2598 for (Synonym syn
:inferredSynonyms
){
2599 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2600 if (!synNotInCDM
.contains(name
.getNameCache())){
2601 inferredSynonymsToBeRemoved
.add(syn
);
2605 // Remove identified Synonyms from inferredSynonyms
2606 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2607 inferredSynonyms
.remove(synonym
);
2612 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF
)){
2614 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2615 //for all synonyms of the parent...
2616 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2618 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2620 synName
= synonymRelationOfParent
.getName();
2622 // Set the sourceReference
2623 sourceReference
= synonymRelationOfParent
.getSec();
2625 // Determine the idInSource
2626 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2628 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2629 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2630 String synParentInfragenericName
= null;
2631 String synParentSpecificEpithet
= null;
2633 if (parentSynZooName
.isInfraGeneric()){
2634 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2636 if (parentSynZooName
.isSpecies()){
2637 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2640 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2641 synonymsGenus.put(synGenusName, idInSource);
2644 //for all synonyms of the taxon
2646 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2648 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2649 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2651 synParentInfragenericName
,
2652 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2654 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF
);
2655 inferredSynonyms
.add(potentialCombination
);
2656 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2657 taxonNames
.add(potentialCombination
.getName().getNameCache());
2661 if (doWithMisappliedNames
){
2663 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2665 TaxonName misappliedParentName
;
2667 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2668 misappliedParentName
= misappliedParent
.getName();
2670 HibernateProxyHelper
.deproxy(misappliedParent
);
2672 // Set the sourceReference
2673 sourceReference
= misappliedParent
.getSec();
2675 // Determine the idInSource
2676 String idInSourceParent
= getIdInSource(misappliedParent
);
2678 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2679 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2680 String synParentInfragenericName
= null;
2681 String synParentSpecificEpithet
= null;
2683 if (parentSynZooName
.isInfraGeneric()){
2684 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2686 if (parentSynZooName
.isSpecies()){
2687 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2690 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2691 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2692 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2693 potentialCombination
= createPotentialCombination(
2694 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2696 synParentInfragenericName
,
2697 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2699 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF
);
2700 inferredSynonyms
.add(potentialCombination
);
2701 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2702 taxonNames
.add(potentialCombination
.getName().getNameCache());
2707 if (!taxonNames
.isEmpty()){
2708 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2709 IZoologicalName name
;
2710 if (!synNotInCDM
.isEmpty()){
2711 inferredSynonymsToBeRemoved
.clear();
2712 for (Synonym syn
:inferredSynonyms
){
2714 name
= syn
.getName();
2715 }catch (ClassCastException e
){
2716 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2718 if (!synNotInCDM
.contains(name
.getNameCache())){
2719 inferredSynonymsToBeRemoved
.add(syn
);
2722 // Remove identified Synonyms from inferredSynonyms
2723 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2724 inferredSynonyms
.remove(synonym
);
2730 logger
.info("The synonym type is not defined.");
2731 return inferredSynonyms
;
2737 return inferredSynonyms
;
2740 private Synonym
createPotentialCombination(String idInSourceParent
,
2741 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2742 String synParentInfragenericName
, String synParentSpecificEpithet
,
2743 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2744 Synonym potentialCombination
;
2745 Reference sourceReference
;
2746 IZoologicalName inferredSynName
;
2747 HibernateProxyHelper
.deproxy(syn
);
2749 // Set sourceReference
2750 sourceReference
= syn
.getSec();
2751 if (sourceReference
== null){
2752 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2754 if (!parentSynZooName
.getTaxa().isEmpty()){
2755 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2757 sourceReference
= taxon
.getSec();
2760 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2762 String synTaxonInfraSpecificName
= null;
2764 if (parentSynZooName
.isSpecies()){
2765 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2768 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2769 synonymsEpithet.add(epithetName);
2772 //create potential combinations...
2773 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2775 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2776 if (zooSynName
.isSpecies()){
2777 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2778 if (parentSynZooName
.isInfraGeneric()){
2779 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2782 if (zooSynName
.isInfraSpecific()){
2783 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2784 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2786 if (parentSynZooName
.isInfraGeneric()){
2787 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2790 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2792 // Set the sourceReference
2793 potentialCombination
.setSec(sourceReference
);
2796 // Determine the idInSource
2797 String idInSourceSyn
= getIdInSource(syn
);
2799 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2800 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2801 inferredSynName
.addSource(originalSource
);
2802 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2803 potentialCombination
.addSource(originalSource
);
2806 return potentialCombination
;
2809 private Synonym
createInferredGenus(Taxon taxon
,
2810 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2811 String epithetOfTaxon
, String genusOfTaxon
,
2812 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2815 Synonym inferredGenus
;
2817 IZoologicalName inferredSynName
;
2818 synName
=syn
.getName();
2819 HibernateProxyHelper
.deproxy(syn
);
2821 // Determine the idInSource
2822 String idInSourceSyn
= getIdInSource(syn
);
2823 String idInSourceTaxon
= getIdInSource(taxon
);
2824 // Determine the sourceReference
2825 Reference sourceReference
= syn
.getSec();
2827 //logger.warn(sourceReference.getTitleCache());
2829 synName
= syn
.getName();
2830 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2831 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2832 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2833 synonymsEpithet.add(synSpeciesEpithetName);
2836 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2837 //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...
2839 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2840 if (zooParentName
.isInfraGeneric()){
2841 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2844 if (taxonName
.isSpecies()){
2845 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2847 if (taxonName
.isInfraSpecific()){
2848 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2849 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2852 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2854 // Set the sourceReference
2855 inferredGenus
.setSec(sourceReference
);
2857 // Add the original source
2858 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2859 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2860 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2861 inferredGenus
.addSource(originalSource
);
2863 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2864 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2865 inferredSynName
.addSource(originalSource
);
2866 originalSource
= null;
2869 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2870 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2871 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2872 inferredGenus
.addSource(originalSource
);
2874 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2875 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2876 inferredSynName
.addSource(originalSource
);
2877 originalSource
= null;
2880 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF
);
2882 return inferredGenus
;
2885 private Synonym
createInferredEpithets(Taxon taxon
,
2886 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2887 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2888 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2889 TaxonName parentName
, TaxonBase
<?
> syn
) {
2891 Synonym inferredEpithet
;
2893 IZoologicalName inferredSynName
;
2894 HibernateProxyHelper
.deproxy(syn
);
2896 // Determine the idInSource
2897 String idInSourceSyn
= getIdInSource(syn
);
2898 String idInSourceTaxon
= getIdInSource(taxon
);
2899 // Determine the sourceReference
2900 Reference sourceReference
= syn
.getSec();
2902 if (sourceReference
== null){
2903 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2904 sourceReference
= taxon
.getSec();
2907 synName
= syn
.getName();
2908 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2909 String synGenusName
= zooSynName
.getGenusOrUninomial();
2910 String synInfraGenericEpithet
= null;
2911 String synSpecificEpithet
= null;
2913 if (zooSynName
.getInfraGenericEpithet() != null){
2914 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2917 if (zooSynName
.isInfraSpecific()){
2918 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2921 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2922 synonymsGenus.put(synGenusName, idInSource);
2925 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2927 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2928 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2929 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2931 inferredSynName
.setGenusOrUninomial(synGenusName
);
2933 if (parentName
.isInfraGeneric()){
2934 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2936 if (taxonName
.isSpecies()){
2937 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2938 }else if (taxonName
.isInfraSpecific()){
2939 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2940 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2943 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2945 // Set the sourceReference
2946 inferredEpithet
.setSec(sourceReference
);
2948 /* Add the original source
2949 if (idInSource != null) {
2950 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2953 Reference citation = getCitation(syn);
2954 if (citation != null) {
2955 originalSource.setCitation(citation);
2956 inferredEpithet.addSource(originalSource);
2959 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2962 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2963 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2965 inferredEpithet
.addSource(originalSource
);
2967 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2968 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2970 inferredSynName
.addSource(originalSource
);
2972 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF
);
2974 return inferredEpithet
;
2978 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2979 * Very likely only useful for createInferredSynonyms().
2984 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2985 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2986 if (taxonName
== null) {
2987 taxonName
= zooHashMap
.get(uuid
);
2993 * Returns the idInSource for a given Synonym.
2996 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2997 String idInSource
= null;
2998 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2999 if (sources
.size() == 1) {
3000 IdentifiableSource source
= sources
.iterator().next();
3001 if (source
!= null) {
3002 idInSource
= source
.getIdInSource();
3004 } else if (sources
.size() > 1) {
3007 for (IdentifiableSource source
: sources
) {
3008 idInSource
+= source
.getIdInSource();
3009 if (count
< sources
.size()) {
3014 } else if (sources
.size() == 0){
3015 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
3022 * Returns the citation for a given Synonym.
3025 private Reference
getCitation(Synonym syn
) {
3026 Reference citation
= null;
3027 Set
<IdentifiableSource
> sources
= syn
.getSources();
3028 if (sources
.size() == 1) {
3029 IdentifiableSource source
= sources
.iterator().next();
3030 if (source
!= null) {
3031 citation
= source
.getCitation();
3033 } else if (sources
.size() > 1) {
3034 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
3041 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
3042 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
3044 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF
, doWithMisappliedNames
));
3045 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF
, doWithMisappliedNames
));
3046 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF
, doWithMisappliedNames
));
3048 return inferredSynonyms
;
3052 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
3054 // TODO quickly implemented, create according dao !!!!
3055 Set
<TaxonNode
> nodes
= new HashSet
<>();
3056 Set
<Classification
> classifications
= new HashSet
<>();
3057 List
<Classification
> list
= new ArrayList
<>();
3059 if (taxonBase
== null) {
3063 taxonBase
= load(taxonBase
.getUuid());
3065 if (taxonBase
instanceof Taxon
) {
3066 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
3068 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
3070 nodes
.addAll(taxon
.getTaxonNodes());
3073 for (TaxonNode node
: nodes
) {
3074 classifications
.add(node
.getClassification());
3076 list
.addAll(classifications
);
3081 @Transactional(readOnly
= false)
3082 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
3084 TaxonRelationshipType oldRelationshipType
,
3085 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3086 UpdateResult result
= new UpdateResult();
3087 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
3088 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3089 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
3091 result
.addUpdatedObject(toTaxon
);
3092 result
.addUpdatedObject(result
.getCdmEntity());
3098 @Transactional(readOnly
= false)
3099 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
3100 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3102 UpdateResult result
= new UpdateResult();
3103 // Create new synonym using concept name
3104 TaxonName synonymName
= fromTaxon
.getName();
3106 // Remove concept relation from taxon
3107 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
3109 // Create a new synonym for the taxon
3111 if (synonymType
!= null
3112 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF
)){
3113 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
3114 toTaxon
.addHomotypicSynonym(synonym
);
3116 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
3118 //keep the publish flag
3119 synonym
.setPublish(fromTaxon
.isPublish());
3120 this.saveOrUpdate(toTaxon
);
3121 //TODO: configurator and classification
3122 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
3123 config
.setDeleteNameIfPossible(false);
3124 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
3125 result
.setCdmEntity(synonym
);
3126 result
.addUpdatedObject(toTaxon
);
3127 result
.addUpdatedObject(synonym
);
3132 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
3133 DeleteResult result
= new DeleteResult();
3134 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
3135 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
3136 if (taxonBase
instanceof Taxon
){
3137 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
3138 List
<String
> propertyPaths
= new ArrayList
<>();
3139 propertyPaths
.add("taxonNodes");
3140 Taxon taxon
= (Taxon
)load(taxonBaseUuid
, propertyPaths
);
3142 result
= isDeletableForTaxon(references
, taxonConfig
);
3144 if (taxonConfig
.isDeleteNameIfPossible()){
3145 if (taxonBase
.getName() != null){
3146 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), taxonConfig
.getNameDeletionConfig(), taxon
.getUuid());
3147 if (!nameResult
.isOk()){
3148 result
.addExceptions(nameResult
.getExceptions());
3154 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
3155 result
= isDeletableForSynonym(references
, synonymConfig
);
3156 if (synonymConfig
.isDeleteNameIfPossible() && taxonBase
.getName() != null){
3157 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), synonymConfig
.getNameDeletionConfig(), taxonBase
.getUuid());
3158 if (!nameResult
.isOk()){
3159 result
.addExceptions(nameResult
.getExceptions());
3168 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
3170 DeleteResult result
= new DeleteResult();
3171 for (CdmBase ref
: references
){
3172 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3173 String message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
3174 result
.addException(new ReferencedObjectUndeletableException(message
));
3175 result
.addRelatedObject(ref
);
3183 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
3184 String message
= null;
3185 DeleteResult result
= new DeleteResult();
3186 for (CdmBase ref
: references
){
3187 if (!(ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3189 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
3190 message
= "The taxon can't be deleted as long as it has synonyms.";
3192 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
3193 message
= "The taxon can't be deleted as long as it has factual data.";
3196 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
3197 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
3199 if (ref
instanceof TaxonNode
&& config
.getClassificationUuid() != null && !config
.isDeleteInAllClassifications() && !((TaxonNode
)ref
).getClassification().getUuid().equals(config
.getClassificationUuid())){
3200 message
= "The taxon can't be deleted as long as it is used in more than one classification";
3203 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
3204 if (!config
.isDeleteMisappliedNames() &&
3205 (((TaxonRelationship
)ref
).getType().isMisappliedName())){
3206 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3208 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3211 if (ref
instanceof PolytomousKeyNode
){
3212 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3215 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3216 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3219 /* //PolytomousKeyNode
3220 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3221 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3226 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3227 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3231 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3232 message
= "Taxon can't be deleted as it is used in a determination event";
3235 if (message
!= null){
3236 result
.addException(new ReferencedObjectUndeletableException(message
));
3237 result
.addRelatedObject(ref
);
3246 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3247 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3249 //preliminary implementation
3251 Set
<Taxon
> taxa
= new HashSet
<>();
3252 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3253 if (taxonBase
== null){
3254 return new IncludedTaxaDTO();
3255 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3256 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3258 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3259 //TODO partial synonyms ??
3260 //TODO synonyms in general
3261 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3262 taxa
.add(syn
.getAcceptedTaxon());
3264 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3267 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3269 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3270 related
= makeRelatedIncluded(related
, result
, config
);
3277 * @param uncheckedTaxa
3278 * @param existingTaxa
3281 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3283 * @return the set of conceptually related taxa for further use
3285 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3288 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3289 for (Taxon taxon
: uncheckedTaxa
){
3290 taxonNodes
.addAll(taxon
.getTaxonNodes());
3293 Set
<Taxon
> children
= new HashSet
<>();
3294 if (! config
.onlyCongruent
){
3295 for (TaxonNode node
: taxonNodes
){
3296 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3297 for (TaxonNode child
: childNodes
){
3298 children
.add(child
.getTaxon());
3301 children
.remove(null); // just to be on the save side
3304 Iterator
<Taxon
> it
= children
.iterator();
3305 while(it
.hasNext()){
3306 UUID uuid
= it
.next().getUuid();
3307 if (existingTaxa
.contains(uuid
)){
3310 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3315 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3316 uncheckedAndChildren
.addAll(children
);
3318 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3321 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3326 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3327 * @return the set of these computed taxa
3329 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3330 Set
<Taxon
> result
= new HashSet
<>();
3332 for (Taxon taxon
: unchecked
){
3333 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3334 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3336 for (TaxonRelationship fromRel
: fromRelations
){
3337 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3340 TaxonRelationshipType fromRelType
= fromRel
.getType();
3341 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3342 !config
.onlyCongruent
&& (
3343 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3344 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3347 result
.add(fromRel
.getToTaxon());
3351 for (TaxonRelationship toRel
: toRelations
){
3352 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3355 TaxonRelationshipType fromRelType
= toRel
.getType();
3356 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3357 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3358 result
.add(toRel
.getFromTaxon());
3363 Iterator
<Taxon
> it
= result
.iterator();
3364 while(it
.hasNext()){
3365 UUID uuid
= it
.next().getUuid();
3366 if (existingTaxa
.contains(uuid
)){
3369 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3376 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3377 @SuppressWarnings("rawtypes")
3378 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3379 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, null, null, config
.getPropertyPath());
3384 @Transactional(readOnly
= true)
3385 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3386 Class
<S
> clazz
, String identifier
, IdentifierType identifierType
, TaxonNode subtreeFilter
,
3387 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3388 Integer pageNumber
, List
<String
> propertyPaths
) {
3389 if (subtreeFilter
== null){
3390 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3393 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3394 List
<Object
[]> daoResults
= new ArrayList
<>();
3395 if(numberOfResults
> 0) { // no point checking again
3396 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3397 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3400 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3401 for (Object
[] daoObj
: daoResults
){
3403 result
.add(new IdentifiedEntityDTO
<>((IdentifierType
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3405 result
.add(new IdentifiedEntityDTO
<>((IdentifierType
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3408 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3412 @Transactional(readOnly
= true)
3413 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3414 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3415 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3416 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3417 if (subtreeFilter
== null){
3418 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3421 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3422 List
<Object
[]> daoResults
= new ArrayList
<>();
3423 if(numberOfResults
> 0) { // no point checking again
3424 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3425 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3428 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3429 for (Object
[] daoObj
: daoResults
){
3431 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3433 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3436 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3440 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3441 UpdateResult result
= new UpdateResult();
3443 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3444 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3445 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3446 //reload to avoid session conflicts
3447 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3449 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3450 if(description
.isProtectedTitleCache()){
3451 String separator
= "";
3452 if(!StringUtils
.isBlank(description
.getTitleCache())){
3455 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3457 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3458 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3459 description
.addAnnotation(annotation
);
3460 toTaxon
.addDescription(description
);
3461 dao
.saveOrUpdate(toTaxon
);
3462 dao
.saveOrUpdate(fromTaxon
);
3463 result
.addUpdatedObject(toTaxon
);
3464 result
.addUpdatedObject(fromTaxon
);
3472 @Transactional(readOnly
= false)
3473 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3474 UUID acceptedTaxonUuid
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
, SecReferenceHandlingSwapEnum secHandling
, UUID newSecAcc
, UUID newSecSyn
) {
3475 TaxonBase
<?
> base
= this.load(synonymUUid
);
3476 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3477 base
= this.load(acceptedTaxonUuid
);
3478 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3480 Reference refAcc
= referenceService
.load(newSecAcc
);
3481 Reference refSyn
= referenceService
.load(newSecSyn
);
3483 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
, setNameInSource
, newUuidForAcceptedTaxon
, secHandling
, refAcc
, refSyn
);
3487 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3488 Set
<TaxonRelationshipType
> inversTypes
,
3489 Direction direction
, boolean groupMisapplications
,
3490 boolean includeUnpublished
,
3491 Integer pageSize
, Integer pageNumber
) {
3492 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3493 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3495 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3497 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3498 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3499 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3501 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3503 //TODO paging is difficult because misapplication string is an attribute
3505 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3506 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3507 // if(numberOfResults > 0) { // no point checking again
3508 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3511 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3514 List
<Language
> languages
= null;
3516 direction
= Direction
.relatedTo
;
3517 //TODO order hints, property path
3518 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3519 for (TaxonRelationship relation
: relations
){
3520 dto
.addRelation(relation
, direction
, languages
);
3524 direction
= Direction
.relatedFrom
;
3525 //TODO order hints, property path
3526 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3527 for (TaxonRelationship relation
: relations
){
3528 dto
.addRelation(relation
, direction
, languages
);
3531 if (groupMisapplications
){
3533 dto
.createMisapplicationString();