2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.api
.service
;
12 import java
.io
.IOException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collections
;
15 import java
.util
.EnumSet
;
16 import java
.util
.HashMap
;
17 import java
.util
.HashSet
;
18 import java
.util
.Iterator
;
19 import java
.util
.List
;
22 import java
.util
.UUID
;
24 import javax
.persistence
.EntityNotFoundException
;
26 import org
.apache
.commons
.lang3
.StringUtils
;
27 import org
.apache
.log4j
.Logger
;
28 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
29 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
30 import org
.apache
.lucene
.search
.BooleanQuery
;
31 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
32 import org
.apache
.lucene
.search
.Query
;
33 import org
.apache
.lucene
.search
.SortField
;
34 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
35 import org
.apache
.lucene
.search
.join
.ScoreMode
;
36 import org
.apache
.lucene
.util
.BytesRef
;
37 import org
.hibernate
.criterion
.Criterion
;
38 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
39 import org
.springframework
.stereotype
.Service
;
40 import org
.springframework
.transaction
.annotation
.Transactional
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
47 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
48 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
51 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
52 import eu
.etaxonomy
.cdm
.api
.service
.dto
.TaxonRelationshipsDTO
;
53 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
54 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
55 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
56 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
57 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.AbstractPagerImpl
;
58 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
64 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
65 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
66 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
67 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
68 import eu
.etaxonomy
.cdm
.api
.util
.TaxonRelationshipEdge
;
69 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
70 import eu
.etaxonomy
.cdm
.compare
.taxon
.HomotypicGroupTaxonComparator
;
71 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonComparator
;
72 import eu
.etaxonomy
.cdm
.exception
.UnpublishedException
;
73 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
74 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
75 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
76 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
77 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
78 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
79 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
80 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
81 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
82 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
83 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
84 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
85 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
86 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
87 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
88 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementSource
;
89 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
90 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
91 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
92 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
93 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
94 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
95 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
96 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
97 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
98 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
99 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
100 import eu
.etaxonomy
.cdm
.model
.metadata
.SecReferenceHandlingEnum
;
101 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
102 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
103 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
104 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
105 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
106 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
107 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
108 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
109 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
110 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.SecundumSource
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
117 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
118 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
119 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
120 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
121 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
122 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.Restriction
;
123 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
124 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
125 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
126 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
127 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
128 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
129 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
130 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
131 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
132 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
133 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
134 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
138 * @author a.kohlbecker
142 @Transactional(readOnly
= true)
143 public class TaxonServiceImpl
144 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
145 implements ITaxonService
{
147 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
149 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
151 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
153 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
156 private ITaxonNodeDao taxonNodeDao
;
159 private ITaxonNameDao nameDao
;
162 private INameService nameService
;
165 private IOccurrenceService occurrenceService
;
168 private ITaxonNodeService nodeService
;
171 private IDescriptionService descriptionService
;
174 private IReferenceService referenceService
;
177 // private IOrderedTermVocabularyDao orderedVocabularyDao;
180 private IOccurrenceDao occurrenceDao
;
183 private IClassificationDao classificationDao
;
186 private AbstractBeanInitializer beanInitializer
;
189 private ILuceneIndexToolProvider luceneIndexToolProvider
;
191 //************************ CONSTRUCTOR ****************************/
192 public TaxonServiceImpl(){
193 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
196 // ****************************** METHODS ********************************/
199 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
200 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
204 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
205 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
209 @Transactional(readOnly
= false)
210 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
){
211 if (newUuidForAcceptedTaxon
){
212 return swapSynonymAndAcceptedTaxonNewUuid(synonym
, acceptedTaxon
, setNameInSource
);
214 return swapSynonymAndAcceptedTaxon(synonym
, acceptedTaxon
, setNameInSource
);
218 private UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
219 UpdateResult result
= new UpdateResult();
220 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
222 TaxonName synonymName
= synonym
.getName();
223 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
225 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(taxonName
.getHomotypicalGroup());
227 acceptedTaxon
.setName(synonymName
);
228 synonym
.setName(taxonName
);
231 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, acceptedTaxon
.getDescriptions());
233 saveOrUpdate(acceptedTaxon
);
234 saveOrUpdate(synonym
);
235 result
.setCdmEntity(acceptedTaxon
);
236 result
.addUpdatedObject(synonym
);
241 private UpdateResult
swapSynonymAndAcceptedTaxonNewUuid(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
242 UpdateResult result
= new UpdateResult();
243 acceptedTaxon
.removeSynonym(synonym
);
244 TaxonName synonymName
= synonym
.getName();
245 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
246 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
248 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(taxonName
.getHomotypicalGroup());
249 synonymName
.removeTaxonBase(synonym
);
251 List
<Synonym
> synonyms
= new ArrayList
<>();
252 for (Synonym syn
: acceptedTaxon
.getSynonyms()){
253 syn
= HibernateProxyHelper
.deproxy(syn
, Synonym
.class);
256 for (Synonym syn
: synonyms
){
257 acceptedTaxon
.removeSynonym(syn
);
259 Taxon newTaxon
= acceptedTaxon
.clone(true, true, false, true);
262 Set
<TaxonDescription
> descriptionsToCopy
= new HashSet
<>(acceptedTaxon
.getDescriptions());
263 for (TaxonDescription description
: descriptionsToCopy
){
264 newTaxon
.addDescription(description
);
267 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, newTaxon
.getDescriptions());
269 newTaxon
.setName(synonymName
);
271 newTaxon
.setPublish(synonym
.isPublish());
272 for (Synonym syn
: synonyms
){
273 if (!syn
.getName().equals(newTaxon
.getName())){
274 newTaxon
.addSynonym(syn
, syn
.getType());
278 //move all data to new taxon
279 //Move Taxon RelationShips to new Taxon
280 for(TaxonRelationship taxonRelationship
: newTaxon
.getTaxonRelations()){
281 newTaxon
.removeTaxonRelation(taxonRelationship
);
284 for(TaxonRelationship taxonRelationship
: acceptedTaxon
.getTaxonRelations()){
285 Taxon fromTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getFromTaxon());
286 Taxon toTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getToTaxon());
287 if (fromTaxon
== acceptedTaxon
){
288 newTaxon
.addTaxonRelation(taxonRelationship
.getToTaxon(), taxonRelationship
.getType(),
289 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
291 }else if(toTaxon
== acceptedTaxon
){
292 fromTaxon
.addTaxonRelation(newTaxon
, taxonRelationship
.getType(),
293 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
294 saveOrUpdate(fromTaxon
);
297 logger
.warn("Taxon is not part of its own Taxonrelationship");
300 // Remove old relationships
301 fromTaxon
.removeTaxonRelation(taxonRelationship
);
302 toTaxon
.removeTaxonRelation(taxonRelationship
);
303 taxonRelationship
.setToTaxon(null);
304 taxonRelationship
.setFromTaxon(null);
308 List
<TaxonNode
> nodes
= new ArrayList
<>(acceptedTaxon
.getTaxonNodes());
309 for (TaxonNode node
: nodes
){
310 node
= HibernateProxyHelper
.deproxy(node
);
311 TaxonNode parent
= node
.getParent();
312 acceptedTaxon
.removeTaxonNode(node
);
313 node
.setTaxon(newTaxon
);
315 parent
.addChildNode(node
, null, null);
320 Synonym newSynonym
= synonym
.clone();
321 newSynonym
.setName(taxonName
);
322 newSynonym
.setPublish(acceptedTaxon
.isPublish());
323 if (sameHomotypicGroup
){
324 newTaxon
.addSynonym(newSynonym
, SynonymType
.HOMOTYPIC_SYNONYM_OF());
326 newTaxon
.addSynonym(newSynonym
, SynonymType
.HETEROTYPIC_SYNONYM_OF());
330 TaxonDeletionConfigurator conf
= new TaxonDeletionConfigurator();
331 conf
.setDeleteNameIfPossible(false);
332 SynonymDeletionConfigurator confSyn
= new SynonymDeletionConfigurator();
333 confSyn
.setDeleteNameIfPossible(false);
334 result
.setCdmEntity(newTaxon
);
336 DeleteResult deleteResult
= deleteTaxon(acceptedTaxon
.getUuid(), conf
, null);
337 if (synonym
.isPersited()){
338 synonym
.setSecSource(null);
339 deleteResult
.includeResult(deleteSynonym(synonym
.getUuid(), confSyn
));
341 result
.includeResult(deleteResult
);
346 private void handleNameUsedInSourceForSwap(boolean setNameInSource
, TaxonName taxonName
, String oldTaxonTitleCache
,
347 Set
<TaxonDescription
> descriptions
) {
348 for(TaxonDescription description
: descriptions
){
349 String message
= "Description copied from former accepted taxon: %s (Old title: %s)";
350 message
= String
.format(message
, oldTaxonTitleCache
, description
.getTitleCache());
351 description
.setTitleCache(message
, true);
353 for (DescriptionElementBase element
: description
.getElements()){
354 for (DescriptionElementSource source
: element
.getSources()){
355 if (source
.getNameUsedInSource() == null){
356 source
.setNameUsedInSource(taxonName
);
365 @Transactional(readOnly
= false)
366 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, Reference newSecRef
, String microRef
, SecReferenceHandlingEnum secHandling
, boolean deleteSynonym
) {
367 UpdateResult result
= new UpdateResult();
368 TaxonName acceptedName
= acceptedTaxon
.getName();
369 TaxonName synonymName
= synonym
.getName();
370 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
372 //check synonym is not homotypic
373 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
374 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
375 result
.addException(new HomotypicalGroupChangeException(message
));
379 if (secHandling
!= null && secHandling
.equals(SecReferenceHandlingEnum
.KeepAlways
)){
380 newSecRef
= synonym
.getSec();
382 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, newSecRef
, microRef
);
383 newAcceptedTaxon
.setPublish(synonym
.isPublish());
384 dao
.save(newAcceptedTaxon
);
385 result
.setCdmEntity(newAcceptedTaxon
);
386 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
387 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
389 for (Synonym heteroSynonym
: heteroSynonyms
){
390 if (secHandling
== null || !secHandling
.equals(SecReferenceHandlingEnum
.KeepAlways
)){
391 heteroSynonym
.setSec(newSecRef
);
393 if (synonym
.equals(heteroSynonym
)){
394 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
396 //move synonyms in same homotypic group to new accepted taxon
397 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
400 dao
.saveOrUpdate(acceptedTaxon
);
401 result
.addUpdatedObject(acceptedTaxon
);
406 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
407 config
.setDeleteNameIfPossible(false);
408 this.deleteSynonym(synonym
, config
);
410 } catch (Exception e
) {
411 result
.addException(e
);
419 @Transactional(readOnly
= false)
420 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
421 UUID acceptedTaxonUuid
,
422 UUID newParentNodeUuid
,
424 String microReference
,
425 SecReferenceHandlingEnum secHandling
,
426 boolean deleteSynonym
) {
427 UpdateResult result
= new UpdateResult();
428 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
429 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
430 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
431 Reference newSecRef
= null;
432 switch (secHandling
){
438 case UseNewParentSec
:
439 newSecRef
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
442 Reference parentSec
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
443 Reference synSec
= synonym
.getSec();
444 if (parentSec
!= null && synSec
!= null && parentSec
.equals(synSec
)){
445 newSecRef
= synonym
.getSec();
447 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
451 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
457 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, newSecRef
, microReference
, secHandling
, deleteSynonym
);
458 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
460 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
461 taxonNodeDao
.save(newNode
);
462 result
.addUpdatedObject(newTaxon
);
463 result
.addUpdatedObject(acceptedTaxon
);
464 result
.setCdmEntity(newNode
);
469 @Transactional(readOnly
= false)
470 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
472 TaxonRelationshipType taxonRelationshipType
,
474 String microcitation
){
476 UpdateResult result
= new UpdateResult();
477 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
478 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
479 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
480 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
481 // result.setCdmEntity(relatedTaxon);
482 result
.addUpdatedObject(relatedTaxon
);
483 result
.addUpdatedObject(toTaxon
);
488 @Transactional(readOnly
= false)
489 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
490 // Get name from synonym
491 if (synonym
== null){
495 UpdateResult result
= new UpdateResult();
497 TaxonName synonymName
= synonym
.getName();
499 /* // remove synonym from taxon
500 toTaxon.removeSynonym(synonym);
502 // Create a taxon with synonym name
503 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
504 fromTaxon
.setPublish(synonym
.isPublish());
506 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
508 // Add taxon relation
509 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
510 result
.setCdmEntity(fromTaxon
);
511 // since we are swapping names, we have to detach the name from the synonym completely.
512 // Otherwise the synonym will still be in the list of typified names.
513 // synonym.getName().removeTaxonBase(synonym);
514 result
.includeResult(this.deleteSynonym(synonym
, null));
519 @Transactional(readOnly
= false)
521 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
522 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
524 TaxonName synonymName
= synonym
.getName();
525 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
528 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
529 newHomotypicalGroup
.addTypifiedName(synonymName
);
531 //remove existing basionym relationships
532 synonymName
.removeBasionyms();
534 //add basionym relationship
535 if (setBasionymRelationIfApplicable
){
536 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
537 for (TaxonName basionym
: basionyms
){
538 synonymName
.addBasionym(basionym
);
542 //set synonym relationship correctly
543 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
545 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
546 if (acceptedTaxon
!= null){
548 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
549 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
550 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
551 synonym
.setType(newRelationType
);
553 if (hasNewTargetTaxon
){
554 acceptedTaxon
.removeSynonym(synonym
, false);
557 if (hasNewTargetTaxon
){
558 @SuppressWarnings("null")
559 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
560 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
561 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
562 targetTaxon
.addSynonym(synonym
, relType
);
567 @Transactional(readOnly
= false)
568 public UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
570 clazz
= TaxonBase
.class;
572 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
577 protected void setDao(ITaxonDao dao
) {
582 public <T
extends TaxonBase
> Pager
<T
> findTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
583 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
584 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
);
586 List
<T
> results
= new ArrayList
<>();
587 if(numberOfResults
> 0) { // no point checking again
588 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
,
589 pageSize
, pageNumber
, propertyPaths
);
592 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
596 public <T
extends TaxonBase
> List
<T
> listTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
597 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
599 return findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infragenericEpithet
, authorshipCache
, rank
,
600 pageSize
, pageNumber
, propertyPaths
).getRecords();
604 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
605 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
606 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
608 List
<TaxonRelationship
> results
= new ArrayList
<>();
609 if(numberOfResults
> 0) { // no point checking again
610 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
616 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
617 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
618 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
620 List
<TaxonRelationship
> results
= new ArrayList
<>();
621 if(numberOfResults
> 0) { // no point checking again
622 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
624 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
628 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
629 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
630 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
632 List
<TaxonRelationship
> results
= new ArrayList
<>();
633 if(numberOfResults
> 0) { // no point checking again
634 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
640 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
641 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
642 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
644 List
<TaxonRelationship
> results
= new ArrayList
<>();
645 if(numberOfResults
> 0) { // no point checking again
646 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
648 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
652 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
653 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
655 Long numberOfResults
= dao
.countTaxonRelationships(types
);
656 List
<TaxonRelationship
> results
= new ArrayList
<>();
657 if(numberOfResults
> 0) {
658 results
= dao
.getTaxonRelationships(types
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
664 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
665 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
666 return page(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, INCLUDE_UNPUBLISHED
);
670 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
671 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
, boolean includeUnpublished
) {
674 long resultSize
= dao
.count(clazz
, restrictions
);
675 if(AbstractPagerImpl
.hasResultsInRange(resultSize
, pageIndex
, pageSize
)){
676 records
= dao
.list(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, includeUnpublished
);
678 records
= new ArrayList
<>();
680 Pager
<S
> pager
= new DefaultPagerImpl
<>(pageIndex
, resultSize
, pageSize
, records
);
685 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
686 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
688 Synonym synonym
= null;
691 synonym
= (Synonym
) dao
.load(synonymUuid
);
692 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
693 } catch (ClassCastException e
){
694 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
695 } catch (NullPointerException e
){
696 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
699 Classification classificationFilter
= null;
700 if(classificationUuid
!= null){
702 classificationFilter
= classificationDao
.load(classificationUuid
);
703 } catch (NullPointerException e
){
704 //TODO not sure, why an NPE should be thrown in the above load method
705 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
707 if(classificationFilter
== null){
708 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
712 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
714 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
715 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
723 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
724 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
726 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
727 relatedTaxa
.remove(taxon
);
728 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
733 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
734 * <code>taxon</code> supplied as parameter.
737 * @param includeRelationships
739 * @param maxDepth can be <code>null</code> for infinite depth
742 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
743 boolean includeUnpublished
, Integer maxDepth
) {
749 if(includeRelationships
.isEmpty()){
753 if(maxDepth
!= null) {
756 if(logger
.isDebugEnabled()){
757 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
759 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
760 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
761 for (TaxonRelationship taxRel
: taxonRelationships
) {
764 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
767 // filter by includeRelationships
768 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
769 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
770 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
771 if(logger
.isDebugEnabled()){
772 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
774 taxa
.add(taxRel
.getToTaxon());
775 if(maxDepth
== null || maxDepth
> 0) {
776 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
779 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
780 taxa
.add(taxRel
.getFromTaxon());
781 if(logger
.isDebugEnabled()){
782 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
784 if(maxDepth
== null || maxDepth
> 0) {
785 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
795 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
796 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
798 List
<Synonym
> results
= new ArrayList
<>();
799 if(numberOfResults
> 0) { // no point checking again
800 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
803 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
807 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
808 List
<List
<Synonym
>> result
= new ArrayList
<>();
809 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
810 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
813 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
816 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
817 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
818 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
825 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
826 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
827 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
829 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
833 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
834 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
835 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
836 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
837 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
838 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
840 return heterotypicSynonymyGroups
;
844 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
846 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
847 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
848 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(), config
.isDoIncludeAuthors(),
849 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
850 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
852 return new ArrayList
<>();
857 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
859 @SuppressWarnings("rawtypes")
860 List
<IdentifiableEntity
> results
= new ArrayList
<>();
861 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
862 List
<TaxonBase
> taxa
= null;
865 long numberTaxaResults
= 0L;
867 List
<String
> propertyPath
= new ArrayList
<>();
868 if(configurator
.getTaxonPropertyPath() != null){
869 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
872 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
873 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
875 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
876 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
877 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
878 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
881 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
882 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
883 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
884 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
885 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
886 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
890 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
893 results
.addAll(taxa
);
896 numberOfResults
+= numberTaxaResults
;
898 // Names without taxa
899 if (configurator
.isDoNamesWithoutTaxa()) {
900 int numberNameResults
= 0;
902 List
<TaxonName
> names
=
903 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
904 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
905 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
906 if (names
.size() > 0) {
907 for (TaxonName taxonName
: names
) {
908 if (taxonName
.getTaxonBases().size() == 0) {
909 results
.add(taxonName
);
913 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
914 numberOfResults
+= numberNameResults
;
918 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
921 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
922 return dao
.getUuidAndTitleCache(limit
, pattern
);
926 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
927 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
931 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
932 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
933 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
936 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
938 // logger.setLevel(Level.TRACE);
939 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
941 logger
.trace("listMedia() - START");
943 Set
<Taxon
> taxa
= new HashSet
<>();
944 List
<Media
> taxonMedia
= new ArrayList
<>();
945 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
947 if (limitToGalleries
== null) {
948 limitToGalleries
= false;
951 // --- resolve related taxa
952 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
953 logger
.trace("listMedia() - resolve related taxa");
954 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
957 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
959 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
960 logger
.trace("listMedia() - includeTaxonDescriptions");
961 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
962 // --- TaxonDescriptions
963 for (Taxon t
: taxa
) {
964 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
966 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
967 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
968 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
969 for (Media media
: element
.getMedia()) {
970 if(taxonDescription
.isImageGallery()){
971 taxonMedia
.add(media
);
974 nonImageGalleryImages
.add(media
);
980 //put images from image gallery first (#3242)
981 taxonMedia
.addAll(nonImageGalleryImages
);
985 if(includeOccurrences
!= null && includeOccurrences
) {
986 logger
.trace("listMedia() - includeOccurrences");
987 @SuppressWarnings("rawtypes")
988 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
990 for (Taxon t
: taxa
) {
991 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
993 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
995 // direct media removed from specimen #3597
996 // taxonMedia.addAll(occurrence.getMedia());
998 // SpecimenDescriptions
999 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
1000 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
1001 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
1002 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
1003 for (DescriptionElementBase element
: elements
) {
1004 for (Media media
: element
.getMedia()) {
1005 taxonMedia
.add(media
);
1011 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
1012 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
1014 //TODO why may collections have media attached? #
1015 if (derivedUnit
.getCollection() != null){
1016 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
1019 //media in hierarchy
1020 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
1024 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
1025 logger
.trace("listMedia() - includeTaxonNameDescriptions");
1026 // --- TaxonNameDescription
1027 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
1028 for (Taxon t
: taxa
) {
1029 nameDescriptions
.addAll(t
.getName().getDescriptions());
1031 for(TaxonNameDescription nameDescription
: nameDescriptions
){
1032 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
1033 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
1034 for (DescriptionElementBase element
: elements
) {
1035 for (Media media
: element
.getMedia()) {
1036 taxonMedia
.add(media
);
1043 logger
.trace("listMedia() - initialize");
1044 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
1046 logger
.trace("listMedia() - END");
1052 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
1053 return this.dao
.loadList(listOfIDs
, null, null);
1057 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
1058 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
1062 public long countSynonyms(boolean onlyAttachedToTaxon
){
1063 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
1067 @Transactional(readOnly
=false)
1068 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
1070 if (config
== null){
1071 config
= new TaxonDeletionConfigurator();
1073 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
1074 DeleteResult result
= new DeleteResult();
1077 result
.addException(new Exception ("The taxon was already deleted."));
1080 taxon
= HibernateProxyHelper
.deproxy(taxon
);
1081 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
1082 config
.setClassificationUuid(classificationUuid
);
1083 result
= isDeletable(taxonUUID
, config
);
1086 // --- DeleteSynonymRelations
1087 if (config
.isDeleteSynonymRelations()){
1088 boolean removeSynonymNameFromHomotypicalGroup
= false;
1089 // use tmp Set to avoid concurrent modification
1090 Set
<Synonym
> synsToDelete
= new HashSet
<>();
1091 synsToDelete
.addAll(taxon
.getSynonyms());
1092 for (Synonym synonym
: synsToDelete
){
1093 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
1095 // --- DeleteSynonymsIfPossible
1096 if (config
.isDeleteSynonymsIfPossible()){
1098 boolean newHomotypicGroupIfNeeded
= true;
1099 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
1100 result
.includeResult(deleteSynonym(synonym
, synConfig
));
1105 // --- DeleteTaxonRelationships
1106 if (! config
.isDeleteTaxonRelationships()){
1107 if (taxon
.getTaxonRelations().size() > 0){
1109 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1110 "Remove taxon from all relations to other taxa prior to deletion."));
1113 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
1114 configRelTaxon
.setDeleteTaxonNodes(false);
1115 configRelTaxon
.setDeleteConceptRelationships(true);
1117 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
1118 if (config
.isDeleteMisappliedNames()
1119 && taxRel
.getType().isMisappliedName()
1120 && taxon
.equals(taxRel
.getToTaxon())){
1121 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
1122 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
1124 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
1125 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1126 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
1127 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1130 taxon
.removeTaxonRelation(taxRel
);
1135 if (config
.isDeleteDescriptions()){
1136 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
1137 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
1138 for (TaxonDescription desc
: descriptions
){
1139 //TODO use description delete configurator ?
1140 //FIXME check if description is ALWAYS deletable
1141 if (desc
.getDescribedSpecimenOrObservation() != null){
1143 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1144 " which also describes specimens or observations"));
1147 removeDescriptions
.add(desc
);
1150 for (TaxonDescription desc
: removeDescriptions
){
1151 taxon
.removeDescription(desc
);
1152 descriptionService
.delete(desc
);
1159 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1160 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1162 if (taxon
.getTaxonNodes().size() != 0){
1163 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1164 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1165 TaxonNode node
= null;
1166 boolean deleteChildren
;
1167 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1168 deleteChildren
= true;
1170 deleteChildren
= false;
1172 boolean success
= true;
1173 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1174 while (iterator
.hasNext()){
1175 node
= iterator
.next();
1176 if (node
.getClassification().equals(classification
)){
1182 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1183 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1184 nodeService
.delete(node
);
1185 result
.addDeletedObject(node
);
1188 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1190 } else if (config
.isDeleteInAllClassifications()){
1191 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1192 nodesList
.addAll(taxon
.getTaxonNodes());
1193 for (ITaxonTreeNode treeNode
: nodesList
){
1194 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1195 if(!deleteChildren
){
1196 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1197 for (Object childNode
: childNodes
){
1198 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1199 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1203 config
.getTaxonNodeConfig().setDeleteElement(false);
1204 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1205 if (!resultNodes
.isOk()){
1206 result
.addExceptions(resultNodes
.getExceptions());
1207 result
.setStatus(resultNodes
.getStatus());
1209 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1214 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1218 TaxonName name
= taxon
.getName();
1219 taxon
.setName(null);
1220 this.saveOrUpdate(taxon
);
1222 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1225 result
.addDeletedObject(taxon
);
1226 }catch(Exception e
){
1227 result
.addException(e
);
1232 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1236 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1237 DeleteResult nameResult
= new DeleteResult();
1238 //remove name if possible (and required)
1240 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1242 if (nameResult
.isError() || nameResult
.isAbort()){
1243 result
.addRelatedObject(name
);
1244 result
.addExceptions(nameResult
.getExceptions());
1246 result
.includeResult(nameResult
);
1255 @Transactional(readOnly
= false)
1256 public DeleteResult
delete(UUID synUUID
){
1257 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1258 return this.deleteSynonym(syn
, null);
1262 @Transactional(readOnly
= false)
1263 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1264 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1268 @Transactional(readOnly
= false)
1269 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1270 DeleteResult result
= new DeleteResult();
1271 if (synonym
== null){
1273 result
.addException(new Exception("The synonym was already deleted."));
1277 if (config
== null){
1278 config
= new SynonymDeletionConfigurator();
1281 result
= isDeletable(synonym
.getUuid(), config
);
1285 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1288 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1290 if (accTaxon
!= null){
1291 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1292 accTaxon
.removeSynonym(synonym
, false);
1293 this.saveOrUpdate(accTaxon
);
1294 result
.addUpdatedObject(accTaxon
);
1296 this.saveOrUpdate(synonym
);
1300 TaxonName name
= synonym
.getName();
1301 synonym
.setName(null);
1303 dao
.delete(synonym
);
1304 result
.addDeletedObject(synonym
);
1306 //remove name if possible (and required)
1307 if (name
!= null && config
.isDeleteNameIfPossible()){
1309 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1310 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1311 result
.addExceptions(nameDeleteResult
.getExceptions());
1312 result
.addRelatedObject(name
);
1314 result
.addDeletedObject(name
);
1322 public Map
<String
, Map
<UUID
,Set
<TaxonName
>>> findIdenticalTaxonNames(List
<UUID
> sourceRefUuids
, List
<String
> propertyPaths
) {
1323 return this.dao
.findIdenticalNames(sourceRefUuids
, propertyPaths
);
1327 public Taxon
findBestMatchingTaxon(String taxonName
) {
1328 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1329 config
.setTaxonNameTitle(taxonName
);
1330 return findBestMatchingTaxon(config
);
1334 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1336 Taxon bestCandidate
= null;
1338 // 1. search for accepted taxa
1339 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1340 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1341 boolean bestCandidateMatchesSecUuid
= false;
1342 boolean bestCandidateIsInClassification
= false;
1343 int countEqualCandidates
= 0;
1344 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1345 if(taxonBaseCandidate
instanceof Taxon
){
1346 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1347 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1348 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1350 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1351 bestCandidate
= newCanditate
;
1352 countEqualCandidates
= 1;
1353 bestCandidateMatchesSecUuid
= true;
1357 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1358 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1360 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1361 bestCandidate
= newCanditate
;
1362 countEqualCandidates
= 1;
1363 bestCandidateIsInClassification
= true;
1366 if (bestCandidate
== null){
1367 bestCandidate
= newCanditate
;
1368 countEqualCandidates
= 1;
1371 }else{ //not Taxon.class
1374 countEqualCandidates
++;
1377 if (bestCandidate
!= null){
1378 if(countEqualCandidates
> 1){
1379 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1380 return bestCandidate
;
1382 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1383 return bestCandidate
;
1387 // 2. search for synonyms
1388 if (config
.isIncludeSynonyms()){
1389 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1390 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1391 for(TaxonBase taxonBase
: synonymList
){
1392 if(taxonBase
instanceof Synonym
){
1393 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1394 bestCandidate
= synonym
.getAcceptedTaxon();
1395 if(bestCandidate
!= null){
1396 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1397 return bestCandidate
;
1399 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1404 } catch (Exception e
){
1406 e
.printStackTrace();
1409 return bestCandidate
;
1412 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1413 UUID configClassificationUuid
= config
.getClassificationUuid();
1414 if (configClassificationUuid
== null){
1417 for (TaxonNode node
: taxon
.getTaxonNodes()){
1418 UUID classUuid
= node
.getClassification().getUuid();
1419 if (configClassificationUuid
.equals(classUuid
)){
1426 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1427 UUID configSecUuid
= config
.getSecUuid();
1428 if (configSecUuid
== null){
1431 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1432 return configSecUuid
.equals(taxonSecUuid
);
1436 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1437 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1438 if(! synonymList
.isEmpty()){
1439 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1440 if(synonymList
.size() == 1){
1441 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1444 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1452 @Transactional(readOnly
= false)
1453 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1455 boolean moveHomotypicGroup
,
1456 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1457 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1459 oldSynonym
.getSec()!= null?oldSynonym
.getSec().getUuid():null,
1460 oldSynonym
.getSecMicroReference(),
1465 @Transactional(readOnly
= false)
1466 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1468 boolean moveHomotypicGroup
,
1469 SynonymType newSynonymType
,
1470 UUID newSecundumUuid
,
1471 String newSecundumDetail
,
1472 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1474 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1475 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1476 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1477 TaxonName synonymName
= synonym
.getName();
1478 TaxonName fromTaxonName
= oldTaxon
.getName();
1479 //set default relationship type
1480 if (newSynonymType
== null){
1481 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1483 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1485 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1486 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1487 boolean isSingleInGroup
= !(hgSize
> 1);
1489 if (! isSingleInGroup
){
1490 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1491 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1492 if (isHomotypicToAccepted
){
1493 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.";
1494 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1495 message
= String
.format(message
, homotypicRelatives
);
1496 throw new HomotypicalGroupChangeException(message
);
1498 if (! moveHomotypicGroup
){
1499 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.";
1500 throw new HomotypicalGroupChangeException(message
);
1503 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1505 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1507 UpdateResult result
= new UpdateResult();
1508 //move all synonyms to new taxon
1509 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1510 Reference newSecundum
= referenceService
.load(newSecundumUuid
);
1511 for (Synonym synRelation
: homotypicSynonyms
){
1513 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1514 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1515 oldTaxon
.removeSynonym(synRelation
, false);
1516 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1518 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1519 synRelation
.setSec(newSecundum
);
1521 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1522 synRelation
.setSecMicroReference(newSecundumDetail
);
1525 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1526 if (!synRelation
.equals(oldSynonym
)){
1531 result
.addUpdatedObject(oldTaxon
);
1532 result
.addUpdatedObject(newTaxon
);
1533 saveOrUpdate(oldTaxon
);
1534 saveOrUpdate(newTaxon
);
1540 public <T
extends TaxonBase
>List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1542 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1546 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1547 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1548 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1549 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1550 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1552 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1553 null, includeUnpublished
, languages
, highlightFragments
, null);
1555 // --- execute search
1556 TopGroups
<BytesRef
> topDocsResultSet
;
1558 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1559 } catch (ParseException e
) {
1560 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1561 luceneParseException
.setStackTrace(e
.getStackTrace());
1562 throw luceneParseException
;
1565 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1566 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1568 // --- initialize taxa, thighlight matches ....
1569 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1570 @SuppressWarnings("rawtypes")
1571 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1572 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1574 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1575 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1578 @Transactional(readOnly
= true)
1580 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
) {
1581 long numberOfResults
= dao
.countByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
);
1583 long numberOfResults_doubtful
= dao
.countByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
);
1584 List
<S
> results
= new ArrayList
<>();
1585 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1587 results
= dao
.findByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1588 results
.addAll(dao
.findByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1590 Collections
.sort(results
, new TaxonComparator());
1591 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1594 @Transactional(readOnly
= true)
1596 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
) {
1597 long numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
1598 //check whether there are doubtful taxa matching
1599 long numberOfResults_doubtful
= dao
.countByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
);
1600 List
<S
> results
= new ArrayList
<>();
1601 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1602 if (numberOfResults
> 0){
1603 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1605 results
= new ArrayList
<>();
1607 if (numberOfResults_doubtful
> 0){
1608 results
.addAll(dao
.findByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1611 Collections
.sort(results
, new TaxonComparator());
1612 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1616 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1617 Classification classification
, TaxonNode subtree
,
1618 Integer pageSize
, Integer pageNumber
,
1619 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1621 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
1623 // --- execute search
1624 TopGroups
<BytesRef
> topDocsResultSet
;
1626 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1627 } catch (ParseException e
) {
1628 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1629 luceneParseException
.setStackTrace(e
.getStackTrace());
1630 throw luceneParseException
;
1633 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1634 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1636 // --- initialize taxa, thighlight matches ....
1637 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1638 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1639 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1641 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1642 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1647 * @param queryString
1648 * @param classification
1649 * @param includeUnpublished
1651 * @param highlightFragments
1652 * @param sortFields TODO
1653 * @param directorySelectClass
1656 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1657 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1658 boolean highlightFragments
, SortField
[] sortFields
) {
1660 Builder finalQueryBuilder
= new Builder();
1661 Builder textQueryBuilder
= new Builder();
1663 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1664 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1666 if(sortFields
== null){
1667 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1669 luceneSearch
.setSortFields(sortFields
);
1671 // ---- search criteria
1672 luceneSearch
.setCdmTypRestriction(clazz
);
1674 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1675 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1676 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1678 if(className
!= null){
1679 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1682 BooleanQuery textQuery
= textQueryBuilder
.build();
1683 if(textQuery
.clauses().size() > 0) {
1684 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1687 if(classification
!= null){
1688 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1690 if(subtree
!= null){
1691 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1693 if(!includeUnpublished
) {
1694 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1695 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1696 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1699 luceneSearch
.setQuery(finalQueryBuilder
.build());
1701 if(highlightFragments
){
1702 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1704 return luceneSearch
;
1708 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1709 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1710 * drawback of requiring to do the join an indexing time.
1711 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1713 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1715 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1716 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1718 * @param queryString
1719 * @param classification
1721 * @param highlightFragments
1722 * @param sortFields TODO
1725 * @throws IOException
1727 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1728 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1729 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1732 String queryTermField
;
1733 String toField
= "id"; // TaxonBase.uuid
1734 String publishField
;
1735 String publishFieldInvers
;
1737 if(edge
.isBidirectional()){
1738 throw new RuntimeException("Bidirectional joining not supported!");
1741 fromField
= "relatedFrom.id";
1742 queryTermField
= "relatedFrom.titleCache";
1743 publishField
= "relatedFrom.publish";
1744 publishFieldInvers
= "relatedTo.publish";
1745 } else if(edge
.isInvers()) {
1746 fromField
= "relatedTo.id";
1747 queryTermField
= "relatedTo.titleCache";
1748 publishField
= "relatedTo.publish";
1749 publishFieldInvers
= "relatedFrom.publish";
1751 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1754 Builder finalQueryBuilder
= new Builder();
1756 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1757 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1759 Builder joinFromQueryBuilder
= new Builder();
1760 if(!StringUtils
.isEmpty(queryString
)){
1761 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1763 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1764 if(!includeUnpublished
){
1765 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1766 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1769 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1771 if(sortFields
== null){
1772 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1774 luceneSearch
.setSortFields(sortFields
);
1776 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1778 if(classification
!= null){
1779 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1781 if(subtree
!= null){
1782 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1785 luceneSearch
.setQuery(finalQueryBuilder
.build());
1787 if(highlightFragments
){
1788 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1790 return luceneSearch
;
1794 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1795 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1796 Classification classification
, TaxonNode subtree
,
1797 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1798 boolean highlightFragments
, Integer pageSize
,
1799 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1800 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1802 // FIXME: allow taxonomic ordering
1803 // hql equivalent: order by t.name.genusOrUninomial, case when t.name.specificEpithet like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache";
1804 // this require building a special sort column by a special classBridge
1805 if(highlightFragments
){
1806 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1807 "currently not fully supported by this method and thus " +
1808 "may not work with common names and misapplied names.");
1811 // convert sets to lists
1812 List
<NamedArea
> namedAreaList
= null;
1813 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1814 if(namedAreas
!= null){
1815 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1816 namedAreaList
.addAll(namedAreas
);
1818 if(distributionStatus
!= null){
1819 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1820 distributionStatusList
.addAll(distributionStatus
);
1823 // set default if parameter is null
1824 if(searchModes
== null){
1825 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1828 // set sort order and thus override any sort orders which may have been
1829 // defined by prepare*Search methods
1830 if(orderHints
== null){
1831 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1833 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1835 for(OrderHint oh
: orderHints
){
1836 sortFields
[i
++] = oh
.toSortField();
1838 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1839 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1841 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1843 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1844 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1847 ======== filtering by distribution , HOWTO ========
1849 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1850 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1851 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1852 which will be put into a FilteredQuersy in the end ?
1855 3. how does it work in spatial?
1857 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1858 - http://www.infoq.com/articles/LuceneSpatialSupport
1859 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1860 ------------------------------------------------------------------------
1863 A) use a separate distribution filter per index sub-query/search:
1864 - byTaxonSyonym (query TaxaonBase):
1865 use a join area filter (Distribution -> TaxonBase)
1866 - byCommonName (query DescriptionElementBase): use an area filter on
1867 DescriptionElementBase !!! PROBLEM !!!
1868 This cannot work since the distributions are different entities than the
1869 common names and thus these are different lucene documents.
1870 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1871 use a join area filter (Distribution -> TaxonBase)
1873 B) use a common distribution filter for all index sub-query/searches:
1874 - use a common join area filter (Distribution -> TaxonBase)
1875 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1876 PROBLEM in this case: we are losing the fragment highlighting for the
1877 common names, since the returned documents are always TaxonBases
1880 /* The QueryFactory for creating filter queries on Distributions should
1881 * The query factory used for the common names query cannot be reused
1882 * for this case, since we want to only record the text fields which are
1883 * actually used in the primary query
1885 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1887 Builder multiIndexByAreaFilterBuilder
= new Builder();
1888 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1890 // search for taxa or synonyms
1891 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1892 @SuppressWarnings("rawtypes")
1893 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1894 String className
= null;
1895 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1896 taxonBaseSubclass
= Taxon
.class;
1897 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1898 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1900 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1901 queryString
, classification
, subtree
, className
,
1902 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1903 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1904 /* A) does not work!!!!
1905 if(addDistributionFilter){
1906 // in this case we need a filter which uses a join query
1907 // to get the TaxonBase documents for the DescriptionElementBase documents
1908 // which are matching the areas in question
1909 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1911 distributionStatusList,
1912 distributionFilterQueryFactory
1914 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1917 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1918 // add additional area filter for synonyms
1919 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1920 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1922 //TODO replace by createByDistributionJoinQuery
1923 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1924 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1925 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1930 // search by CommonTaxonName
1931 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1933 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1934 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1935 CommonTaxonName
.class,
1936 "inDescription.taxon.id",
1938 QueryFactory
.addTypeRestriction(
1939 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
1940 , CommonTaxonName
.class
1941 ).build(), "id", null, ScoreMode
.Max
);
1942 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1943 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1944 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1945 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1946 Builder builder
= new BooleanQuery
.Builder();
1947 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1948 if(!includeUnpublished
) {
1949 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1950 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1952 byCommonNameSearch
.setQuery(builder
.build());
1953 byCommonNameSearch
.setSortFields(sortFields
);
1955 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1957 luceneSearches
.add(byCommonNameSearch
);
1959 /* A) does not work!!!!
1961 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1962 queryString, classification, null, languages, highlightFragments)
1964 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1965 if(addDistributionFilter){
1966 // in this case we are able to use DescriptionElementBase documents
1967 // which are matching the areas in question directly
1968 BooleanQuery byDistributionQuery = createByDistributionQuery(
1970 distributionStatusList,
1971 distributionFilterQueryFactory
1973 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1978 // search by misapplied names
1979 //TODO merge with pro parte synonym search once #7487 is fixed
1980 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1982 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1983 // which allows doing query time joins
1984 // finds the misapplied name (Taxon B) which is an misapplication for
1985 // a related Taxon A.
1987 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1988 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1989 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1991 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1992 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1995 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1996 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1997 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1998 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2000 if(addDistributionFilter
){
2001 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2004 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
2005 * Maybe this is a bug in java itself.
2007 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2010 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2012 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2013 * will execute as expected:
2015 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2016 * String toField = "relation." + misappliedNameForUuid +".to.id";
2018 * Comparing both strings by the String.equals method returns true, so both String are identical.
2020 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2021 * 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)
2022 * The bug is persistent after a reboot of the development computer.
2024 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2025 // String toField = "relation." + misappliedNameForUuid +".to.id";
2026 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2027 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2028 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2030 //TODO replace by createByDistributionJoinQuery
2031 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2032 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2033 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2035 // debug code for bug described above
2036 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
2037 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2038 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2040 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2044 // search by pro parte synonyms
2045 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
2046 //TODO merge with misapplied name search once #7487 is fixed
2047 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2048 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
2050 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2051 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2052 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2053 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2055 if(addDistributionFilter
){
2056 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2057 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
2058 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2059 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2060 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2061 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2063 }//end pro parte synonyms
2065 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
2066 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
2068 if(addDistributionFilter
){
2071 // in this case we need a filter which uses a join query
2072 // to get the TaxonBase documents for the DescriptionElementBase documents
2073 // which are matching the areas in question
2075 // for doTaxa, doByCommonName
2076 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
2078 distributionStatusList
,
2079 distributionFilterQueryFactory
,
2082 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2085 if (addDistributionFilter
){
2086 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
2090 // --- execute search
2091 TopGroups
<BytesRef
> topDocsResultSet
;
2093 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2094 } catch (ParseException e
) {
2095 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2096 luceneParseException
.setStackTrace(e
.getStackTrace());
2097 throw luceneParseException
;
2100 // --- initialize taxa, highlight matches ....
2101 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2104 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2105 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2107 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
2108 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2112 * @param namedAreaList at least one area must be in the list
2113 * @param distributionStatusList optional
2114 * @param toType toType
2115 * Optional parameter. Only used for debugging to print the toType documents
2116 * @param asFilter TODO
2118 * @throws IOException
2120 protected Query
createByDistributionJoinQuery(
2121 List
<NamedArea
> namedAreaList
,
2122 List
<PresenceAbsenceTerm
> distributionStatusList
,
2123 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
2124 ) throws IOException
{
2126 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2127 String toField
= "id"; // id in toType usually this is the TaxonBase index
2129 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
2131 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
2133 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
2135 return taxonAreaJoinQuery
;
2139 * @param namedAreaList
2140 * @param distributionStatusList
2141 * @param queryFactory
2144 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
2145 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
2146 Builder areaQueryBuilder
= new Builder();
2147 // area field from Distribution
2148 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2150 // status field from Distribution
2151 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2152 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2155 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2156 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2161 * This method has been primarily created for testing the area join query but might
2162 * also be useful in other situations
2164 * @param namedAreaList
2165 * @param distributionStatusList
2166 * @param classification
2167 * @param highlightFragments
2169 * @throws IOException
2171 protected LuceneSearch
prepareByDistributionSearch(
2172 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2173 Classification classification
, TaxonNode subtree
) throws IOException
{
2175 Builder finalQueryBuilder
= new Builder();
2177 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2179 // FIXME is this query factory using the wrong type?
2180 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2182 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2183 luceneSearch
.setSortFields(sortFields
);
2186 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2188 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2190 if(classification
!= null){
2191 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
2193 if(subtree
!= null){
2194 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
2196 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2197 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2198 luceneSearch
.setQuery(finalQuery
);
2200 return luceneSearch
;
2204 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2205 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2206 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2207 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2209 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2211 // --- execute search
2212 TopGroups
<BytesRef
> topDocsResultSet
;
2214 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2215 } catch (ParseException e
) {
2216 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2217 luceneParseException
.setStackTrace(e
.getStackTrace());
2218 throw luceneParseException
;
2221 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2222 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2224 // --- initialize taxa, highlight matches ....
2225 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2226 @SuppressWarnings("rawtypes")
2227 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2228 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2230 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2231 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2235 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2236 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2237 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2239 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2240 classification
, subtree
,
2241 null, languages
, highlightFragments
);
2242 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2243 includeUnpublished
, languages
, highlightFragments
, null);
2245 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2247 // --- execute search
2248 TopGroups
<BytesRef
> topDocsResultSet
;
2250 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2251 } catch (ParseException e
) {
2252 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2253 luceneParseException
.setStackTrace(e
.getStackTrace());
2254 throw luceneParseException
;
2257 // --- initialize taxa, highlight matches ....
2258 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2260 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2261 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2262 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2264 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2265 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2267 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2268 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2273 * @param queryString
2274 * @param classification
2277 * @param highlightFragments
2278 * @param directorySelectClass
2281 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2282 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2283 List
<Language
> languages
, boolean highlightFragments
) {
2285 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2286 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2288 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2290 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2291 languages
, descriptionElementQueryFactory
);
2293 luceneSearch
.setSortFields(sortFields
);
2294 luceneSearch
.setCdmTypRestriction(clazz
);
2295 luceneSearch
.setQuery(finalQuery
);
2296 if(highlightFragments
){
2297 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2300 return luceneSearch
;
2304 * @param queryString
2305 * @param classification
2308 * @param descriptionElementQueryFactory
2311 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2312 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2313 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2315 Builder finalQueryBuilder
= new Builder();
2316 Builder textQueryBuilder
= new Builder();
2318 if(!StringUtils
.isEmpty(queryString
)){
2320 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2323 Builder nameQueryBuilder
= new Builder();
2324 if(languages
== null || languages
.size() == 0){
2325 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2327 Builder languageSubQueryBuilder
= new Builder();
2328 for(Language lang
: languages
){
2329 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2331 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2332 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2334 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2337 // text field from TextData
2338 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2340 // --- TermBase fields - by representation ----
2341 // state field from CategoricalData
2342 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2344 // state field from CategoricalData
2345 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2347 // area field from Distribution
2348 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2350 // status field from Distribution
2351 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2353 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2356 // --- classification ----
2359 if(classification
!= null){
2360 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2362 if(subtree
!= null){
2363 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2366 // --- IdentifieableEntity fields - by uuid
2367 if(features
!= null && features
.size() > 0 ){
2368 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2371 // the description must be associated with a taxon
2372 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2374 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2375 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2380 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2382 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2383 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2385 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2386 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2388 UUID nameUuid
= taxon
.getName().getUuid();
2389 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2390 String epithetOfTaxon
= null;
2391 String infragenericEpithetOfTaxon
= null;
2392 String infraspecificEpithetOfTaxon
= null;
2393 if (taxonName
.isSpecies()){
2394 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2395 } else if (taxonName
.isInfraGeneric()){
2396 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2397 } else if (taxonName
.isInfraSpecific()){
2398 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2400 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2401 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2402 List
<String
> taxonNames
= new ArrayList
<>();
2404 for (TaxonNode node
: nodes
){
2405 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2406 // List<String> synonymsEpithet = new ArrayList<>();
2408 if (node
.getClassification().equals(classification
)){
2409 if (!node
.isTopmostNode()){
2410 TaxonNode parent
= node
.getParent();
2411 parent
= CdmBase
.deproxy(parent
);
2412 TaxonName parentName
= parent
.getTaxon().getName();
2413 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2414 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2416 //create inferred synonyms for species, subspecies
2417 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2419 Synonym inferredEpithet
= null;
2420 Synonym inferredGenus
= null;
2421 Synonym potentialCombination
= null;
2423 List
<String
> propertyPaths
= new ArrayList
<>();
2424 propertyPaths
.add("synonym");
2425 propertyPaths
.add("synonym.name");
2426 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2427 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2429 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2430 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2431 null, null,orderHintsSynonyms
,propertyPaths
);
2433 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2434 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2435 if (doWithMisappliedNames
){
2436 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2437 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2438 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2439 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2440 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2441 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2444 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2445 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2447 inferredEpithet
= createInferredEpithets(taxon
,
2448 zooHashMap
, taxonName
, epithetOfTaxon
,
2449 infragenericEpithetOfTaxon
,
2450 infraspecificEpithetOfTaxon
,
2451 taxonNames
, parentName
,
2452 synonymRelationOfParent
);
2454 inferredSynonyms
.add(inferredEpithet
);
2455 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2456 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2459 if (doWithMisappliedNames
){
2461 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2462 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2464 inferredEpithet
= createInferredEpithets(taxon
,
2465 zooHashMap
, taxonName
, epithetOfTaxon
,
2466 infragenericEpithetOfTaxon
,
2467 infraspecificEpithetOfTaxon
,
2468 taxonNames
, parentName
,
2471 inferredSynonyms
.add(inferredEpithet
);
2472 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2473 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2477 if (!taxonNames
.isEmpty()){
2478 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2479 if (!synNotInCDM
.isEmpty()){
2480 inferredSynonymsToBeRemoved
.clear();
2482 for (Synonym syn
:inferredSynonyms
){
2483 IZoologicalName name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2484 if (!synNotInCDM
.contains(name
.getNameCache())){
2485 inferredSynonymsToBeRemoved
.add(syn
);
2489 // Remove identified Synonyms from inferredSynonyms
2490 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2491 inferredSynonyms
.remove(synonym
);
2496 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2498 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2500 inferredGenus
= createInferredGenus(taxon
,
2501 zooHashMap
, taxonName
, epithetOfTaxon
,
2502 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2504 inferredSynonyms
.add(inferredGenus
);
2505 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2506 taxonNames
.add(inferredGenus
.getName().getNameCache());
2509 if (doWithMisappliedNames
){
2511 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2512 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2513 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2515 inferredSynonyms
.add(inferredGenus
);
2516 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2517 taxonNames
.add(inferredGenus
.getName().getNameCache());
2522 if (!taxonNames
.isEmpty()){
2523 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2524 IZoologicalName name
;
2525 if (!synNotInCDM
.isEmpty()){
2526 inferredSynonymsToBeRemoved
.clear();
2528 for (Synonym syn
:inferredSynonyms
){
2529 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2530 if (!synNotInCDM
.contains(name
.getNameCache())){
2531 inferredSynonymsToBeRemoved
.add(syn
);
2535 // Remove identified Synonyms from inferredSynonyms
2536 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2537 inferredSynonyms
.remove(synonym
);
2542 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2544 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2545 //for all synonyms of the parent...
2546 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2548 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2550 synName
= synonymRelationOfParent
.getName();
2552 // Set the sourceReference
2553 sourceReference
= synonymRelationOfParent
.getSec();
2555 // Determine the idInSource
2556 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2558 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2559 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2560 String synParentInfragenericName
= null;
2561 String synParentSpecificEpithet
= null;
2563 if (parentSynZooName
.isInfraGeneric()){
2564 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2566 if (parentSynZooName
.isSpecies()){
2567 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2570 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2571 synonymsGenus.put(synGenusName, idInSource);
2574 //for all synonyms of the taxon
2576 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2578 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2579 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2581 synParentInfragenericName
,
2582 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2584 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2585 inferredSynonyms
.add(potentialCombination
);
2586 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2587 taxonNames
.add(potentialCombination
.getName().getNameCache());
2591 if (doWithMisappliedNames
){
2593 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2595 TaxonName misappliedParentName
;
2597 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2598 misappliedParentName
= misappliedParent
.getName();
2600 HibernateProxyHelper
.deproxy(misappliedParent
);
2602 // Set the sourceReference
2603 sourceReference
= misappliedParent
.getSec();
2605 // Determine the idInSource
2606 String idInSourceParent
= getIdInSource(misappliedParent
);
2608 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2609 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2610 String synParentInfragenericName
= null;
2611 String synParentSpecificEpithet
= null;
2613 if (parentSynZooName
.isInfraGeneric()){
2614 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2616 if (parentSynZooName
.isSpecies()){
2617 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2620 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2621 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2622 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2623 potentialCombination
= createPotentialCombination(
2624 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2626 synParentInfragenericName
,
2627 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2629 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2630 inferredSynonyms
.add(potentialCombination
);
2631 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2632 taxonNames
.add(potentialCombination
.getName().getNameCache());
2637 if (!taxonNames
.isEmpty()){
2638 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2639 IZoologicalName name
;
2640 if (!synNotInCDM
.isEmpty()){
2641 inferredSynonymsToBeRemoved
.clear();
2642 for (Synonym syn
:inferredSynonyms
){
2644 name
= syn
.getName();
2645 }catch (ClassCastException e
){
2646 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2648 if (!synNotInCDM
.contains(name
.getNameCache())){
2649 inferredSynonymsToBeRemoved
.add(syn
);
2652 // Remove identified Synonyms from inferredSynonyms
2653 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2654 inferredSynonyms
.remove(synonym
);
2660 logger
.info("The synonym type is not defined.");
2661 return inferredSynonyms
;
2667 return inferredSynonyms
;
2670 private Synonym
createPotentialCombination(String idInSourceParent
,
2671 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2672 String synParentInfragenericName
, String synParentSpecificEpithet
,
2673 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2674 Synonym potentialCombination
;
2675 Reference sourceReference
;
2676 IZoologicalName inferredSynName
;
2677 HibernateProxyHelper
.deproxy(syn
);
2679 // Set sourceReference
2680 sourceReference
= syn
.getSec();
2681 if (sourceReference
== null){
2682 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2684 if (!parentSynZooName
.getTaxa().isEmpty()){
2685 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2687 sourceReference
= taxon
.getSec();
2690 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2692 String synTaxonInfraSpecificName
= null;
2694 if (parentSynZooName
.isSpecies()){
2695 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2698 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2699 synonymsEpithet.add(epithetName);
2702 //create potential combinations...
2703 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2705 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2706 if (zooSynName
.isSpecies()){
2707 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2708 if (parentSynZooName
.isInfraGeneric()){
2709 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2712 if (zooSynName
.isInfraSpecific()){
2713 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2714 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2716 if (parentSynZooName
.isInfraGeneric()){
2717 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2720 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2722 // Set the sourceReference
2723 potentialCombination
.setSec(sourceReference
);
2726 // Determine the idInSource
2727 String idInSourceSyn
= getIdInSource(syn
);
2729 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2730 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2731 inferredSynName
.addSource(originalSource
);
2732 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2733 potentialCombination
.addSource(originalSource
);
2736 return potentialCombination
;
2739 private Synonym
createInferredGenus(Taxon taxon
,
2740 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2741 String epithetOfTaxon
, String genusOfTaxon
,
2742 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2745 Synonym inferredGenus
;
2747 IZoologicalName inferredSynName
;
2748 synName
=syn
.getName();
2749 HibernateProxyHelper
.deproxy(syn
);
2751 // Determine the idInSource
2752 String idInSourceSyn
= getIdInSource(syn
);
2753 String idInSourceTaxon
= getIdInSource(taxon
);
2754 // Determine the sourceReference
2755 Reference sourceReference
= syn
.getSec();
2757 //logger.warn(sourceReference.getTitleCache());
2759 synName
= syn
.getName();
2760 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2761 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2762 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2763 synonymsEpithet.add(synSpeciesEpithetName);
2766 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2767 //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...
2769 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2770 if (zooParentName
.isInfraGeneric()){
2771 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2774 if (taxonName
.isSpecies()){
2775 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2777 if (taxonName
.isInfraSpecific()){
2778 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2779 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2782 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2784 // Set the sourceReference
2785 inferredGenus
.setSec(sourceReference
);
2787 // Add the original source
2788 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2789 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2790 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2791 inferredGenus
.addSource(originalSource
);
2793 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2794 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2795 inferredSynName
.addSource(originalSource
);
2796 originalSource
= null;
2799 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2800 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2801 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2802 inferredGenus
.addSource(originalSource
);
2804 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2805 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2806 inferredSynName
.addSource(originalSource
);
2807 originalSource
= null;
2810 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2812 return inferredGenus
;
2815 private Synonym
createInferredEpithets(Taxon taxon
,
2816 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2817 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2818 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2819 TaxonName parentName
, TaxonBase
<?
> syn
) {
2821 Synonym inferredEpithet
;
2823 IZoologicalName inferredSynName
;
2824 HibernateProxyHelper
.deproxy(syn
);
2826 // Determine the idInSource
2827 String idInSourceSyn
= getIdInSource(syn
);
2828 String idInSourceTaxon
= getIdInSource(taxon
);
2829 // Determine the sourceReference
2830 Reference sourceReference
= syn
.getSec();
2832 if (sourceReference
== null){
2833 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2834 sourceReference
= taxon
.getSec();
2837 synName
= syn
.getName();
2838 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2839 String synGenusName
= zooSynName
.getGenusOrUninomial();
2840 String synInfraGenericEpithet
= null;
2841 String synSpecificEpithet
= null;
2843 if (zooSynName
.getInfraGenericEpithet() != null){
2844 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2847 if (zooSynName
.isInfraSpecific()){
2848 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2851 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2852 synonymsGenus.put(synGenusName, idInSource);
2855 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2857 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2858 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2859 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2861 inferredSynName
.setGenusOrUninomial(synGenusName
);
2863 if (parentName
.isInfraGeneric()){
2864 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2866 if (taxonName
.isSpecies()){
2867 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2868 }else if (taxonName
.isInfraSpecific()){
2869 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2870 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2873 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2875 // Set the sourceReference
2876 inferredEpithet
.setSec(sourceReference
);
2878 /* Add the original source
2879 if (idInSource != null) {
2880 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2883 Reference citation = getCitation(syn);
2884 if (citation != null) {
2885 originalSource.setCitation(citation);
2886 inferredEpithet.addSource(originalSource);
2889 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2892 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2893 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2895 inferredEpithet
.addSource(originalSource
);
2897 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2898 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2900 inferredSynName
.addSource(originalSource
);
2902 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2904 return inferredEpithet
;
2908 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2909 * Very likely only useful for createInferredSynonyms().
2914 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2915 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2916 if (taxonName
== null) {
2917 taxonName
= zooHashMap
.get(uuid
);
2923 * Returns the idInSource for a given Synonym.
2926 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2927 String idInSource
= null;
2928 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2929 if (sources
.size() == 1) {
2930 IdentifiableSource source
= sources
.iterator().next();
2931 if (source
!= null) {
2932 idInSource
= source
.getIdInSource();
2934 } else if (sources
.size() > 1) {
2937 for (IdentifiableSource source
: sources
) {
2938 idInSource
+= source
.getIdInSource();
2939 if (count
< sources
.size()) {
2944 } else if (sources
.size() == 0){
2945 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2952 * Returns the citation for a given Synonym.
2955 private Reference
getCitation(Synonym syn
) {
2956 Reference citation
= null;
2957 Set
<IdentifiableSource
> sources
= syn
.getSources();
2958 if (sources
.size() == 1) {
2959 IdentifiableSource source
= sources
.iterator().next();
2960 if (source
!= null) {
2961 citation
= source
.getCitation();
2963 } else if (sources
.size() > 1) {
2964 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2971 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2972 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2974 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2975 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2976 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2978 return inferredSynonyms
;
2982 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2984 // TODO quickly implemented, create according dao !!!!
2985 Set
<TaxonNode
> nodes
= new HashSet
<>();
2986 Set
<Classification
> classifications
= new HashSet
<>();
2987 List
<Classification
> list
= new ArrayList
<>();
2989 if (taxonBase
== null) {
2993 taxonBase
= load(taxonBase
.getUuid());
2995 if (taxonBase
instanceof Taxon
) {
2996 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2998 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
3000 nodes
.addAll(taxon
.getTaxonNodes());
3003 for (TaxonNode node
: nodes
) {
3004 classifications
.add(node
.getClassification());
3006 list
.addAll(classifications
);
3011 @Transactional(readOnly
= false)
3012 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
3014 TaxonRelationshipType oldRelationshipType
,
3015 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3016 UpdateResult result
= new UpdateResult();
3017 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
3018 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3019 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
3021 // result.addUpdatedObject(fromTaxon);
3022 result
.addUpdatedObject(toTaxon
);
3023 result
.addUpdatedObject(result
.getCdmEntity());
3029 @Transactional(readOnly
= false)
3030 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
3031 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3033 UpdateResult result
= new UpdateResult();
3034 // Create new synonym using concept name
3035 TaxonName synonymName
= fromTaxon
.getName();
3037 // Remove concept relation from taxon
3038 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
3040 // Create a new synonym for the taxon
3042 if (synonymType
!= null
3043 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
3044 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
3045 toTaxon
.addHomotypicSynonym(synonym
);
3047 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
3049 //keep the publish flag
3050 synonym
.setPublish(fromTaxon
.isPublish());
3051 this.saveOrUpdate(toTaxon
);
3052 //TODO: configurator and classification
3053 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
3054 config
.setDeleteNameIfPossible(false);
3055 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
3056 result
.setCdmEntity(synonym
);
3057 result
.addUpdatedObject(toTaxon
);
3058 result
.addUpdatedObject(synonym
);
3063 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
3064 DeleteResult result
= new DeleteResult();
3065 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
3066 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
3067 if (taxonBase
instanceof Taxon
){
3068 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
3069 List
<String
> propertyPaths
= new ArrayList
<>();
3070 propertyPaths
.add("taxonNodes");
3071 Taxon taxon
= (Taxon
)load(taxonBaseUuid
, propertyPaths
);
3073 result
= isDeletableForTaxon(references
, taxonConfig
);
3075 if (taxonConfig
.isDeleteNameIfPossible()){
3076 if (taxonBase
.getName() != null){
3077 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), taxonConfig
.getNameDeletionConfig(), taxon
.getUuid());
3078 if (!nameResult
.isOk()){
3079 result
.addExceptions(nameResult
.getExceptions());
3085 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
3086 result
= isDeletableForSynonym(references
, synonymConfig
);
3087 if (synonymConfig
.isDeleteNameIfPossible()){
3088 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), synonymConfig
.getNameDeletionConfig(), taxonBase
.getUuid());
3089 if (!nameResult
.isOk()){
3090 result
.addExceptions(nameResult
.getExceptions());
3098 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
3100 DeleteResult result
= new DeleteResult();
3101 for (CdmBase ref
: references
){
3102 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3103 String message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
3104 result
.addException(new ReferencedObjectUndeletableException(message
));
3105 result
.addRelatedObject(ref
);
3113 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
3114 String message
= null;
3115 DeleteResult result
= new DeleteResult();
3116 for (CdmBase ref
: references
){
3117 if (!(ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3119 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
3120 message
= "The taxon can't be deleted as long as it has synonyms.";
3122 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
3123 message
= "The taxon can't be deleted as long as it has factual data.";
3126 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
3127 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
3129 if (ref
instanceof TaxonNode
&& config
.getClassificationUuid() != null && !config
.isDeleteInAllClassifications() && !((TaxonNode
)ref
).getClassification().getUuid().equals(config
.getClassificationUuid())){
3130 message
= "The taxon can't be deleted as long as it is used in more than one classification";
3133 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
3134 if (!config
.isDeleteMisappliedNames() &&
3135 (((TaxonRelationship
)ref
).getType().isMisappliedName())){
3136 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3138 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3141 if (ref
instanceof PolytomousKeyNode
){
3142 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3145 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3146 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3149 /* //PolytomousKeyNode
3150 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3151 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3156 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3157 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3161 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3162 message
= "Taxon can't be deleted as it is used in a determination event";
3165 if (message
!= null){
3166 result
.addException(new ReferencedObjectUndeletableException(message
));
3167 result
.addRelatedObject(ref
);
3176 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3177 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3179 //preliminary implementation
3181 Set
<Taxon
> taxa
= new HashSet
<>();
3182 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3183 if (taxonBase
== null){
3184 return new IncludedTaxaDTO();
3185 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3186 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3188 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3189 //TODO partial synonyms ??
3190 //TODO synonyms in general
3191 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3192 taxa
.add(syn
.getAcceptedTaxon());
3194 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3197 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3199 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3200 related
= makeRelatedIncluded(related
, result
, config
);
3207 * @param uncheckedTaxa
3208 * @param existingTaxa
3211 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3213 * @return the set of conceptually related taxa for further use
3215 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3218 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3219 for (Taxon taxon
: uncheckedTaxa
){
3220 taxonNodes
.addAll(taxon
.getTaxonNodes());
3223 Set
<Taxon
> children
= new HashSet
<>();
3224 if (! config
.onlyCongruent
){
3225 for (TaxonNode node
: taxonNodes
){
3226 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3227 for (TaxonNode child
: childNodes
){
3228 children
.add(child
.getTaxon());
3231 children
.remove(null); // just to be on the save side
3234 Iterator
<Taxon
> it
= children
.iterator();
3235 while(it
.hasNext()){
3236 UUID uuid
= it
.next().getUuid();
3237 if (existingTaxa
.contains(uuid
)){
3240 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3245 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3246 uncheckedAndChildren
.addAll(children
);
3248 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3251 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3256 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3257 * @return the set of these computed taxa
3259 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3260 Set
<Taxon
> result
= new HashSet
<>();
3262 for (Taxon taxon
: unchecked
){
3263 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3264 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3266 for (TaxonRelationship fromRel
: fromRelations
){
3267 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3270 TaxonRelationshipType fromRelType
= fromRel
.getType();
3271 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3272 !config
.onlyCongruent
&& (
3273 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3274 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3277 result
.add(fromRel
.getToTaxon());
3281 for (TaxonRelationship toRel
: toRelations
){
3282 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3285 TaxonRelationshipType fromRelType
= toRel
.getType();
3286 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3287 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3288 result
.add(toRel
.getFromTaxon());
3293 Iterator
<Taxon
> it
= result
.iterator();
3294 while(it
.hasNext()){
3295 UUID uuid
= it
.next().getUuid();
3296 if (existingTaxa
.contains(uuid
)){
3299 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3306 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3307 @SuppressWarnings("rawtypes")
3308 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3309 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3314 @Transactional(readOnly
= true)
3315 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3316 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3317 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3318 Integer pageNumber
, List
<String
> propertyPaths
) {
3319 if (subtreeFilter
== null){
3320 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3323 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3324 List
<Object
[]> daoResults
= new ArrayList
<>();
3325 if(numberOfResults
> 0) { // no point checking again
3326 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3327 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3330 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3331 for (Object
[] daoObj
: daoResults
){
3333 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3335 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3338 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3342 @Transactional(readOnly
= true)
3343 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3344 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3345 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3346 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3347 if (subtreeFilter
== null){
3348 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3351 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3352 List
<Object
[]> daoResults
= new ArrayList
<>();
3353 if(numberOfResults
> 0) { // no point checking again
3354 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3355 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3358 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3359 for (Object
[] daoObj
: daoResults
){
3361 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3363 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3366 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3370 @Transactional(readOnly
= false)
3371 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3372 SynonymType newSynonymType
, UUID newSecundumUuid
, String newSecundumDetail
,
3373 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3375 UpdateResult result
= new UpdateResult();
3376 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3377 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3378 newSecundumUuid
, newSecundumDetail
, keepSecundumIfUndefined
);
3384 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3385 UpdateResult result
= new UpdateResult();
3387 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3388 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3389 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3390 //reload to avoid session conflicts
3391 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3393 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3394 if(description
.isProtectedTitleCache()){
3395 String separator
= "";
3396 if(!StringUtils
.isBlank(description
.getTitleCache())){
3399 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3401 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3402 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3403 description
.addAnnotation(annotation
);
3404 toTaxon
.addDescription(description
);
3405 dao
.saveOrUpdate(toTaxon
);
3406 dao
.saveOrUpdate(fromTaxon
);
3407 result
.addUpdatedObject(toTaxon
);
3408 result
.addUpdatedObject(fromTaxon
);
3416 @Transactional(readOnly
= false)
3417 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3418 UUID acceptedTaxonUuid
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
) {
3419 TaxonBase
<?
> base
= this.load(synonymUUid
);
3420 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3421 base
= this.load(acceptedTaxonUuid
);
3422 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3424 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
, setNameInSource
, newUuidForAcceptedTaxon
);
3428 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3429 Set
<TaxonRelationshipType
> inversTypes
,
3430 Direction direction
, boolean groupMisapplications
,
3431 boolean includeUnpublished
,
3432 Integer pageSize
, Integer pageNumber
) {
3433 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3434 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3436 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3438 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3439 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3440 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3442 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3444 //TODO paging is difficult because misapplication string is an attribute
3446 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3447 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3448 // if(numberOfResults > 0) { // no point checking again
3449 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3452 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3455 List
<Language
> languages
= null;
3457 direction
= Direction
.relatedTo
;
3458 //TODO order hints, property path
3459 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3460 for (TaxonRelationship relation
: relations
){
3461 dto
.addRelation(relation
, direction
, languages
);
3465 direction
= Direction
.relatedFrom
;
3466 //TODO order hints, property path
3467 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3468 for (TaxonRelationship relation
: relations
){
3469 dto
.addRelation(relation
, direction
, languages
);
3472 if (groupMisapplications
){
3474 dto
.createMisapplicationString();