2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.api
.service
;
12 import java
.io
.IOException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collections
;
15 import java
.util
.EnumSet
;
16 import java
.util
.HashMap
;
17 import java
.util
.HashSet
;
18 import java
.util
.Iterator
;
19 import java
.util
.List
;
22 import java
.util
.UUID
;
24 import javax
.persistence
.EntityNotFoundException
;
26 import org
.apache
.commons
.lang3
.StringUtils
;
27 import org
.apache
.log4j
.Logger
;
28 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
29 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
30 import org
.apache
.lucene
.search
.BooleanQuery
;
31 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
32 import org
.apache
.lucene
.search
.Query
;
33 import org
.apache
.lucene
.search
.SortField
;
34 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
35 import org
.apache
.lucene
.search
.join
.ScoreMode
;
36 import org
.apache
.lucene
.util
.BytesRef
;
37 import org
.hibernate
.criterion
.Criterion
;
38 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
39 import org
.springframework
.stereotype
.Service
;
40 import org
.springframework
.transaction
.annotation
.Transactional
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
47 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
48 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
51 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
52 import eu
.etaxonomy
.cdm
.api
.service
.dto
.TaxonRelationshipsDTO
;
53 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
54 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
55 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
56 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
57 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.AbstractPagerImpl
;
58 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
64 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
65 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
66 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
67 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
68 import eu
.etaxonomy
.cdm
.api
.util
.TaxonRelationshipEdge
;
69 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
70 import eu
.etaxonomy
.cdm
.compare
.taxon
.HomotypicGroupTaxonComparator
;
71 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonComparator
;
72 import eu
.etaxonomy
.cdm
.exception
.UnpublishedException
;
73 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
74 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
75 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
76 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
77 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
78 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
79 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
80 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
81 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
82 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
83 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
84 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
85 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
86 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
87 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
88 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementSource
;
89 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
90 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
91 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
92 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
93 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
94 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
95 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
96 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
97 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
98 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
99 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
100 import eu
.etaxonomy
.cdm
.model
.metadata
.SecReferenceHandlingEnum
;
101 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
102 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
103 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
104 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
105 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
106 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
107 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
108 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
109 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
110 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.SecundumSource
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
117 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
118 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
119 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
120 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
121 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
122 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.Restriction
;
123 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
124 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
125 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
126 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
127 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
128 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
129 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
130 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
131 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
132 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
133 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
134 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
138 * @author a.kohlbecker
142 @Transactional(readOnly
= true)
143 public class TaxonServiceImpl
144 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
145 implements ITaxonService
{
147 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
149 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
151 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
153 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
156 private ITaxonNodeDao taxonNodeDao
;
159 private ITaxonNameDao nameDao
;
162 private INameService nameService
;
165 private IOccurrenceService occurrenceService
;
168 private ITaxonNodeService nodeService
;
171 private IDescriptionService descriptionService
;
174 private IReferenceService referenceService
;
177 // private IOrderedTermVocabularyDao orderedVocabularyDao;
180 private IOccurrenceDao occurrenceDao
;
183 private IClassificationDao classificationDao
;
186 private AbstractBeanInitializer beanInitializer
;
189 private ILuceneIndexToolProvider luceneIndexToolProvider
;
191 //************************ CONSTRUCTOR ****************************/
192 public TaxonServiceImpl(){
193 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
196 // ****************************** METHODS ********************************/
199 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
200 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
204 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
205 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
209 @Transactional(readOnly
= false)
210 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
){
211 if (newUuidForAcceptedTaxon
){
212 return swapSynonymAndAcceptedTaxonNewUuid(synonym
, acceptedTaxon
, setNameInSource
);
214 return swapSynonymAndAcceptedTaxon(synonym
, acceptedTaxon
, setNameInSource
);
218 private UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
219 UpdateResult result
= new UpdateResult();
220 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
222 TaxonName synonymName
= synonym
.getName();
223 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
225 acceptedTaxon
.setName(synonymName
);
226 synonym
.setName(taxonName
);
229 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, acceptedTaxon
.getDescriptions());
231 saveOrUpdate(acceptedTaxon
);
232 saveOrUpdate(synonym
);
233 result
.setCdmEntity(acceptedTaxon
);
234 result
.addUpdatedObject(synonym
);
239 private UpdateResult
swapSynonymAndAcceptedTaxonNewUuid(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
240 UpdateResult result
= new UpdateResult();
241 acceptedTaxon
.removeSynonym(synonym
);
242 TaxonName synonymName
= synonym
.getName();
243 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
244 String oldTaxonTitleCache
= acceptedTaxon
.getTitleCache();
246 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(taxonName
.getHomotypicalGroup());
247 synonymName
.removeTaxonBase(synonym
);
249 List
<Synonym
> synonyms
= new ArrayList
<>();
250 for (Synonym syn
: acceptedTaxon
.getSynonyms()){
251 syn
= HibernateProxyHelper
.deproxy(syn
, Synonym
.class);
254 for (Synonym syn
: synonyms
){
255 acceptedTaxon
.removeSynonym(syn
);
257 Taxon newTaxon
= acceptedTaxon
.clone(true, true, false, true);
260 Set
<TaxonDescription
> descriptionsToCopy
= new HashSet
<>(acceptedTaxon
.getDescriptions());
261 for (TaxonDescription description
: descriptionsToCopy
){
262 newTaxon
.addDescription(description
);
265 handleNameUsedInSourceForSwap(setNameInSource
, taxonName
, oldTaxonTitleCache
, newTaxon
.getDescriptions());
267 newTaxon
.setName(synonymName
);
269 newTaxon
.setPublish(synonym
.isPublish());
270 for (Synonym syn
: synonyms
){
271 if (!syn
.getName().equals(newTaxon
.getName())){
272 newTaxon
.addSynonym(syn
, syn
.getType());
276 //move all data to new taxon
277 //Move Taxon RelationShips to new Taxon
278 for(TaxonRelationship taxonRelationship
: newTaxon
.getTaxonRelations()){
279 newTaxon
.removeTaxonRelation(taxonRelationship
);
282 for(TaxonRelationship taxonRelationship
: acceptedTaxon
.getTaxonRelations()){
283 Taxon fromTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getFromTaxon());
284 Taxon toTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getToTaxon());
285 if (fromTaxon
== acceptedTaxon
){
286 newTaxon
.addTaxonRelation(taxonRelationship
.getToTaxon(), taxonRelationship
.getType(),
287 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
289 }else if(toTaxon
== acceptedTaxon
){
290 fromTaxon
.addTaxonRelation(newTaxon
, taxonRelationship
.getType(),
291 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
292 saveOrUpdate(fromTaxon
);
295 logger
.warn("Taxon is not part of its own Taxonrelationship");
298 // Remove old relationships
299 fromTaxon
.removeTaxonRelation(taxonRelationship
);
300 toTaxon
.removeTaxonRelation(taxonRelationship
);
301 taxonRelationship
.setToTaxon(null);
302 taxonRelationship
.setFromTaxon(null);
306 List
<TaxonNode
> nodes
= new ArrayList
<>(acceptedTaxon
.getTaxonNodes());
307 for (TaxonNode node
: nodes
){
308 node
= HibernateProxyHelper
.deproxy(node
);
309 TaxonNode parent
= node
.getParent();
310 acceptedTaxon
.removeTaxonNode(node
);
311 node
.setTaxon(newTaxon
);
313 parent
.addChildNode(node
, null, null);
318 Synonym newSynonym
= synonym
.clone();
319 newSynonym
.setName(taxonName
);
320 newSynonym
.setPublish(acceptedTaxon
.isPublish());
321 if (sameHomotypicGroup
){
322 newTaxon
.addSynonym(newSynonym
, SynonymType
.HOMOTYPIC_SYNONYM_OF());
324 newTaxon
.addSynonym(newSynonym
, SynonymType
.HETEROTYPIC_SYNONYM_OF());
328 TaxonDeletionConfigurator conf
= new TaxonDeletionConfigurator();
329 conf
.setDeleteNameIfPossible(false);
330 SynonymDeletionConfigurator confSyn
= new SynonymDeletionConfigurator();
331 confSyn
.setDeleteNameIfPossible(false);
332 result
.setCdmEntity(newTaxon
);
334 DeleteResult deleteResult
= deleteTaxon(acceptedTaxon
.getUuid(), conf
, null);
335 if (synonym
.isPersited()){
336 synonym
.setSecSource(null);
337 deleteResult
.includeResult(deleteSynonym(synonym
.getUuid(), confSyn
));
339 result
.includeResult(deleteResult
);
344 private void handleNameUsedInSourceForSwap(boolean setNameInSource
, TaxonName taxonName
, String oldTaxonTitleCache
,
345 Set
<TaxonDescription
> descriptions
) {
346 for(TaxonDescription description
: descriptions
){
347 String message
= "Description copied from former accepted taxon: %s (Old title: %s)";
348 message
= String
.format(message
, oldTaxonTitleCache
, description
.getTitleCache());
349 description
.setTitleCache(message
, true);
351 for (DescriptionElementBase element
: description
.getElements()){
352 for (DescriptionElementSource source
: element
.getSources()){
353 if (source
.getNameUsedInSource() == null){
354 source
.setNameUsedInSource(taxonName
);
363 @Transactional(readOnly
= false)
364 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, Reference newSecRef
, String microRef
, SecReferenceHandlingEnum secHandling
, boolean deleteSynonym
) {
365 UpdateResult result
= new UpdateResult();
366 TaxonName acceptedName
= acceptedTaxon
.getName();
367 TaxonName synonymName
= synonym
.getName();
368 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
370 //check synonym is not homotypic
371 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
372 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
373 result
.addException(new HomotypicalGroupChangeException(message
));
377 if (secHandling
!= null && secHandling
.equals(SecReferenceHandlingEnum
.KeepAlways
)){
378 newSecRef
= synonym
.getSec();
380 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, newSecRef
, microRef
);
381 newAcceptedTaxon
.setPublish(synonym
.isPublish());
382 dao
.save(newAcceptedTaxon
);
383 result
.setCdmEntity(newAcceptedTaxon
);
384 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
385 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
387 for (Synonym heteroSynonym
: heteroSynonyms
){
388 if (secHandling
== null || !secHandling
.equals(SecReferenceHandlingEnum
.KeepAlways
)){
389 heteroSynonym
.setSec(newSecRef
);
391 if (synonym
.equals(heteroSynonym
)){
392 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
394 //move synonyms in same homotypic group to new accepted taxon
395 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
398 dao
.saveOrUpdate(acceptedTaxon
);
399 result
.addUpdatedObject(acceptedTaxon
);
404 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
405 config
.setDeleteNameIfPossible(false);
406 this.deleteSynonym(synonym
, config
);
408 } catch (Exception e
) {
409 result
.addException(e
);
417 @Transactional(readOnly
= false)
418 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
419 UUID acceptedTaxonUuid
,
420 UUID newParentNodeUuid
,
422 String microReference
,
423 SecReferenceHandlingEnum secHandling
,
424 boolean deleteSynonym
) {
425 UpdateResult result
= new UpdateResult();
426 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
427 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
428 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
429 Reference newSecRef
= null;
430 switch (secHandling
){
436 case UseNewParentSec
:
437 newSecRef
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
440 Reference parentSec
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
441 Reference synSec
= synonym
.getSec();
442 if (parentSec
!= null && synSec
!= null && parentSec
.equals(synSec
)){
443 newSecRef
= synonym
.getSec();
445 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
449 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
455 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, newSecRef
, microReference
, secHandling
, deleteSynonym
);
456 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
458 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
459 taxonNodeDao
.save(newNode
);
460 result
.addUpdatedObject(newTaxon
);
461 result
.addUpdatedObject(acceptedTaxon
);
462 result
.setCdmEntity(newNode
);
467 @Transactional(readOnly
= false)
468 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
470 TaxonRelationshipType taxonRelationshipType
,
472 String microcitation
){
474 UpdateResult result
= new UpdateResult();
475 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
476 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
477 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
478 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
479 // result.setCdmEntity(relatedTaxon);
480 result
.addUpdatedObject(relatedTaxon
);
481 result
.addUpdatedObject(toTaxon
);
486 @Transactional(readOnly
= false)
487 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
488 // Get name from synonym
489 if (synonym
== null){
493 UpdateResult result
= new UpdateResult();
495 TaxonName synonymName
= synonym
.getName();
497 /* // remove synonym from taxon
498 toTaxon.removeSynonym(synonym);
500 // Create a taxon with synonym name
501 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
502 fromTaxon
.setPublish(synonym
.isPublish());
504 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
506 // Add taxon relation
507 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
508 result
.setCdmEntity(fromTaxon
);
509 // since we are swapping names, we have to detach the name from the synonym completely.
510 // Otherwise the synonym will still be in the list of typified names.
511 // synonym.getName().removeTaxonBase(synonym);
512 result
.includeResult(this.deleteSynonym(synonym
, null));
517 @Transactional(readOnly
= false)
519 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
520 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
522 TaxonName synonymName
= synonym
.getName();
523 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
526 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
527 newHomotypicalGroup
.addTypifiedName(synonymName
);
529 //remove existing basionym relationships
530 synonymName
.removeBasionyms();
532 //add basionym relationship
533 if (setBasionymRelationIfApplicable
){
534 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
535 for (TaxonName basionym
: basionyms
){
536 synonymName
.addBasionym(basionym
);
540 //set synonym relationship correctly
541 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
543 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
544 if (acceptedTaxon
!= null){
546 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
547 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
548 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
549 synonym
.setType(newRelationType
);
551 if (hasNewTargetTaxon
){
552 acceptedTaxon
.removeSynonym(synonym
, false);
555 if (hasNewTargetTaxon
){
556 @SuppressWarnings("null")
557 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
558 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
559 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
560 targetTaxon
.addSynonym(synonym
, relType
);
565 @Transactional(readOnly
= false)
566 public UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
568 clazz
= TaxonBase
.class;
570 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
575 protected void setDao(ITaxonDao dao
) {
580 public <T
extends TaxonBase
> Pager
<T
> findTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
581 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
582 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
);
584 List
<T
> results
= new ArrayList
<>();
585 if(numberOfResults
> 0) { // no point checking again
586 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
,
587 pageSize
, pageNumber
, propertyPaths
);
590 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
594 public <T
extends TaxonBase
> List
<T
> listTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
595 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
597 return findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infragenericEpithet
, authorshipCache
, rank
,
598 pageSize
, pageNumber
, propertyPaths
).getRecords();
602 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
603 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
604 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
606 List
<TaxonRelationship
> results
= new ArrayList
<>();
607 if(numberOfResults
> 0) { // no point checking again
608 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
614 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
615 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
616 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
618 List
<TaxonRelationship
> results
= new ArrayList
<>();
619 if(numberOfResults
> 0) { // no point checking again
620 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
622 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
626 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
627 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
628 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
630 List
<TaxonRelationship
> results
= new ArrayList
<>();
631 if(numberOfResults
> 0) { // no point checking again
632 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
638 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
639 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
640 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
642 List
<TaxonRelationship
> results
= new ArrayList
<>();
643 if(numberOfResults
> 0) { // no point checking again
644 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
646 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
650 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
651 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
653 Long numberOfResults
= dao
.countTaxonRelationships(types
);
654 List
<TaxonRelationship
> results
= new ArrayList
<>();
655 if(numberOfResults
> 0) {
656 results
= dao
.getTaxonRelationships(types
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
662 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
663 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
664 return page(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, INCLUDE_UNPUBLISHED
);
668 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
669 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
, boolean includeUnpublished
) {
672 long resultSize
= dao
.count(clazz
, restrictions
);
673 if(AbstractPagerImpl
.hasResultsInRange(resultSize
, pageIndex
, pageSize
)){
674 records
= dao
.list(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, includeUnpublished
);
676 records
= new ArrayList
<>();
678 Pager
<S
> pager
= new DefaultPagerImpl
<>(pageIndex
, resultSize
, pageSize
, records
);
683 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
684 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
686 Synonym synonym
= null;
689 synonym
= (Synonym
) dao
.load(synonymUuid
);
690 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
691 } catch (ClassCastException e
){
692 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
693 } catch (NullPointerException e
){
694 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
697 Classification classificationFilter
= null;
698 if(classificationUuid
!= null){
700 classificationFilter
= classificationDao
.load(classificationUuid
);
701 } catch (NullPointerException e
){
702 //TODO not sure, why an NPE should be thrown in the above load method
703 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
705 if(classificationFilter
== null){
706 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
710 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
712 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
713 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
721 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
722 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
724 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
725 relatedTaxa
.remove(taxon
);
726 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
731 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
732 * <code>taxon</code> supplied as parameter.
735 * @param includeRelationships
737 * @param maxDepth can be <code>null</code> for infinite depth
740 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
741 boolean includeUnpublished
, Integer maxDepth
) {
747 if(includeRelationships
.isEmpty()){
751 if(maxDepth
!= null) {
754 if(logger
.isDebugEnabled()){
755 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
757 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
758 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
759 for (TaxonRelationship taxRel
: taxonRelationships
) {
762 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
765 // filter by includeRelationships
766 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
767 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
768 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
769 if(logger
.isDebugEnabled()){
770 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
772 taxa
.add(taxRel
.getToTaxon());
773 if(maxDepth
== null || maxDepth
> 0) {
774 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
777 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
778 taxa
.add(taxRel
.getFromTaxon());
779 if(logger
.isDebugEnabled()){
780 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
782 if(maxDepth
== null || maxDepth
> 0) {
783 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
793 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
794 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
796 List
<Synonym
> results
= new ArrayList
<>();
797 if(numberOfResults
> 0) { // no point checking again
798 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
801 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
805 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
806 List
<List
<Synonym
>> result
= new ArrayList
<>();
807 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
808 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
811 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
814 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
815 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
816 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
823 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
824 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
825 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
827 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
831 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
832 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
833 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
834 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
835 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
836 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
838 return heterotypicSynonymyGroups
;
842 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
844 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
845 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
846 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(), config
.isDoIncludeAuthors(),
847 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
848 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
850 return new ArrayList
<>();
855 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
857 @SuppressWarnings("rawtypes")
858 List
<IdentifiableEntity
> results
= new ArrayList
<>();
859 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
860 List
<TaxonBase
> taxa
= null;
863 long numberTaxaResults
= 0L;
865 List
<String
> propertyPath
= new ArrayList
<>();
866 if(configurator
.getTaxonPropertyPath() != null){
867 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
870 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
871 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
873 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
874 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
875 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
876 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
879 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
880 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
881 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
882 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
883 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
884 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
888 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
891 results
.addAll(taxa
);
894 numberOfResults
+= numberTaxaResults
;
896 // Names without taxa
897 if (configurator
.isDoNamesWithoutTaxa()) {
898 int numberNameResults
= 0;
900 List
<TaxonName
> names
=
901 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
902 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
903 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
904 if (names
.size() > 0) {
905 for (TaxonName taxonName
: names
) {
906 if (taxonName
.getTaxonBases().size() == 0) {
907 results
.add(taxonName
);
911 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
912 numberOfResults
+= numberNameResults
;
916 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
919 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
920 return dao
.getUuidAndTitleCache(limit
, pattern
);
924 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
925 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
929 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
930 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
931 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
934 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
936 // logger.setLevel(Level.TRACE);
937 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
939 logger
.trace("listMedia() - START");
941 Set
<Taxon
> taxa
= new HashSet
<>();
942 List
<Media
> taxonMedia
= new ArrayList
<>();
943 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
945 if (limitToGalleries
== null) {
946 limitToGalleries
= false;
949 // --- resolve related taxa
950 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
951 logger
.trace("listMedia() - resolve related taxa");
952 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
955 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
957 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
958 logger
.trace("listMedia() - includeTaxonDescriptions");
959 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
960 // --- TaxonDescriptions
961 for (Taxon t
: taxa
) {
962 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
964 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
965 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
966 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
967 for (Media media
: element
.getMedia()) {
968 if(taxonDescription
.isImageGallery()){
969 taxonMedia
.add(media
);
972 nonImageGalleryImages
.add(media
);
978 //put images from image gallery first (#3242)
979 taxonMedia
.addAll(nonImageGalleryImages
);
983 if(includeOccurrences
!= null && includeOccurrences
) {
984 logger
.trace("listMedia() - includeOccurrences");
985 @SuppressWarnings("rawtypes")
986 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
988 for (Taxon t
: taxa
) {
989 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
991 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
993 // direct media removed from specimen #3597
994 // taxonMedia.addAll(occurrence.getMedia());
996 // SpecimenDescriptions
997 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
998 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
999 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
1000 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
1001 for (DescriptionElementBase element
: elements
) {
1002 for (Media media
: element
.getMedia()) {
1003 taxonMedia
.add(media
);
1009 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
1010 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
1012 //TODO why may collections have media attached? #
1013 if (derivedUnit
.getCollection() != null){
1014 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
1017 //media in hierarchy
1018 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
1022 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
1023 logger
.trace("listMedia() - includeTaxonNameDescriptions");
1024 // --- TaxonNameDescription
1025 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
1026 for (Taxon t
: taxa
) {
1027 nameDescriptions
.addAll(t
.getName().getDescriptions());
1029 for(TaxonNameDescription nameDescription
: nameDescriptions
){
1030 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
1031 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
1032 for (DescriptionElementBase element
: elements
) {
1033 for (Media media
: element
.getMedia()) {
1034 taxonMedia
.add(media
);
1041 logger
.trace("listMedia() - initialize");
1042 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
1044 logger
.trace("listMedia() - END");
1050 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
1051 return this.dao
.loadList(listOfIDs
, null, null);
1055 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
1056 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
1060 public long countSynonyms(boolean onlyAttachedToTaxon
){
1061 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
1065 @Transactional(readOnly
=false)
1066 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
1068 if (config
== null){
1069 config
= new TaxonDeletionConfigurator();
1071 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
1072 DeleteResult result
= new DeleteResult();
1075 result
.addException(new Exception ("The taxon was already deleted."));
1078 taxon
= HibernateProxyHelper
.deproxy(taxon
);
1079 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
1080 config
.setClassificationUuid(classificationUuid
);
1081 result
= isDeletable(taxonUUID
, config
);
1084 // --- DeleteSynonymRelations
1085 if (config
.isDeleteSynonymRelations()){
1086 boolean removeSynonymNameFromHomotypicalGroup
= false;
1087 // use tmp Set to avoid concurrent modification
1088 Set
<Synonym
> synsToDelete
= new HashSet
<>();
1089 synsToDelete
.addAll(taxon
.getSynonyms());
1090 for (Synonym synonym
: synsToDelete
){
1091 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
1093 // --- DeleteSynonymsIfPossible
1094 if (config
.isDeleteSynonymsIfPossible()){
1096 boolean newHomotypicGroupIfNeeded
= true;
1097 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
1098 result
.includeResult(deleteSynonym(synonym
, synConfig
));
1103 // --- DeleteTaxonRelationships
1104 if (! config
.isDeleteTaxonRelationships()){
1105 if (taxon
.getTaxonRelations().size() > 0){
1107 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1108 "Remove taxon from all relations to other taxa prior to deletion."));
1111 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
1112 configRelTaxon
.setDeleteTaxonNodes(false);
1113 configRelTaxon
.setDeleteConceptRelationships(true);
1115 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
1116 if (config
.isDeleteMisappliedNames()
1117 && taxRel
.getType().isMisappliedName()
1118 && taxon
.equals(taxRel
.getToTaxon())){
1119 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
1120 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
1122 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
1123 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1124 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
1125 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1128 taxon
.removeTaxonRelation(taxRel
);
1133 if (config
.isDeleteDescriptions()){
1134 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
1135 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
1136 for (TaxonDescription desc
: descriptions
){
1137 //TODO use description delete configurator ?
1138 //FIXME check if description is ALWAYS deletable
1139 if (desc
.getDescribedSpecimenOrObservation() != null){
1141 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1142 " which also describes specimens or observations"));
1145 removeDescriptions
.add(desc
);
1148 for (TaxonDescription desc
: removeDescriptions
){
1149 taxon
.removeDescription(desc
);
1150 descriptionService
.delete(desc
);
1157 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1158 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1160 if (taxon
.getTaxonNodes().size() != 0){
1161 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1162 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1163 TaxonNode node
= null;
1164 boolean deleteChildren
;
1165 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1166 deleteChildren
= true;
1168 deleteChildren
= false;
1170 boolean success
= true;
1171 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1172 while (iterator
.hasNext()){
1173 node
= iterator
.next();
1174 if (node
.getClassification().equals(classification
)){
1180 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1181 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1182 nodeService
.delete(node
);
1183 result
.addDeletedObject(node
);
1186 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1188 } else if (config
.isDeleteInAllClassifications()){
1189 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1190 nodesList
.addAll(taxon
.getTaxonNodes());
1191 for (ITaxonTreeNode treeNode
: nodesList
){
1192 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1193 if(!deleteChildren
){
1194 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1195 for (Object childNode
: childNodes
){
1196 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1197 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1201 config
.getTaxonNodeConfig().setDeleteElement(false);
1202 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1203 if (!resultNodes
.isOk()){
1204 result
.addExceptions(resultNodes
.getExceptions());
1205 result
.setStatus(resultNodes
.getStatus());
1207 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1212 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1216 TaxonName name
= taxon
.getName();
1217 taxon
.setName(null);
1218 this.saveOrUpdate(taxon
);
1220 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1223 result
.addDeletedObject(taxon
);
1224 }catch(Exception e
){
1225 result
.addException(e
);
1230 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1234 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1235 DeleteResult nameResult
= new DeleteResult();
1236 //remove name if possible (and required)
1238 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1240 if (nameResult
.isError() || nameResult
.isAbort()){
1241 result
.addRelatedObject(name
);
1242 result
.addExceptions(nameResult
.getExceptions());
1244 result
.includeResult(nameResult
);
1253 @Transactional(readOnly
= false)
1254 public DeleteResult
delete(UUID synUUID
){
1255 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1256 return this.deleteSynonym(syn
, null);
1260 @Transactional(readOnly
= false)
1261 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1262 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1266 @Transactional(readOnly
= false)
1267 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1268 DeleteResult result
= new DeleteResult();
1269 if (synonym
== null){
1271 result
.addException(new Exception("The synonym was already deleted."));
1275 if (config
== null){
1276 config
= new SynonymDeletionConfigurator();
1279 result
= isDeletable(synonym
.getUuid(), config
);
1283 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1286 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1288 if (accTaxon
!= null){
1289 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1290 accTaxon
.removeSynonym(synonym
, false);
1291 this.saveOrUpdate(accTaxon
);
1292 result
.addUpdatedObject(accTaxon
);
1294 this.saveOrUpdate(synonym
);
1298 TaxonName name
= synonym
.getName();
1299 synonym
.setName(null);
1301 dao
.delete(synonym
);
1302 result
.addDeletedObject(synonym
);
1304 //remove name if possible (and required)
1305 if (name
!= null && config
.isDeleteNameIfPossible()){
1307 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1308 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1309 result
.addExceptions(nameDeleteResult
.getExceptions());
1310 result
.addRelatedObject(name
);
1312 result
.addDeletedObject(name
);
1320 public Map
<String
, Map
<UUID
,Set
<TaxonName
>>> findIdenticalTaxonNames(List
<UUID
> sourceRefUuids
, List
<String
> propertyPaths
) {
1321 return this.dao
.findIdenticalNames(sourceRefUuids
, propertyPaths
);
1325 public Taxon
findBestMatchingTaxon(String taxonName
) {
1326 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1327 config
.setTaxonNameTitle(taxonName
);
1328 return findBestMatchingTaxon(config
);
1332 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1334 Taxon bestCandidate
= null;
1336 // 1. search for accepted taxa
1337 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1338 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1339 boolean bestCandidateMatchesSecUuid
= false;
1340 boolean bestCandidateIsInClassification
= false;
1341 int countEqualCandidates
= 0;
1342 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1343 if(taxonBaseCandidate
instanceof Taxon
){
1344 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1345 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1346 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1348 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1349 bestCandidate
= newCanditate
;
1350 countEqualCandidates
= 1;
1351 bestCandidateMatchesSecUuid
= true;
1355 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1356 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1358 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1359 bestCandidate
= newCanditate
;
1360 countEqualCandidates
= 1;
1361 bestCandidateIsInClassification
= true;
1364 if (bestCandidate
== null){
1365 bestCandidate
= newCanditate
;
1366 countEqualCandidates
= 1;
1369 }else{ //not Taxon.class
1372 countEqualCandidates
++;
1375 if (bestCandidate
!= null){
1376 if(countEqualCandidates
> 1){
1377 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1378 return bestCandidate
;
1380 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1381 return bestCandidate
;
1385 // 2. search for synonyms
1386 if (config
.isIncludeSynonyms()){
1387 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1388 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1389 for(TaxonBase taxonBase
: synonymList
){
1390 if(taxonBase
instanceof Synonym
){
1391 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1392 bestCandidate
= synonym
.getAcceptedTaxon();
1393 if(bestCandidate
!= null){
1394 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1395 return bestCandidate
;
1397 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1402 } catch (Exception e
){
1404 e
.printStackTrace();
1407 return bestCandidate
;
1410 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1411 UUID configClassificationUuid
= config
.getClassificationUuid();
1412 if (configClassificationUuid
== null){
1415 for (TaxonNode node
: taxon
.getTaxonNodes()){
1416 UUID classUuid
= node
.getClassification().getUuid();
1417 if (configClassificationUuid
.equals(classUuid
)){
1424 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1425 UUID configSecUuid
= config
.getSecUuid();
1426 if (configSecUuid
== null){
1429 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1430 return configSecUuid
.equals(taxonSecUuid
);
1434 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1435 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1436 if(! synonymList
.isEmpty()){
1437 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1438 if(synonymList
.size() == 1){
1439 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1442 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1450 @Transactional(readOnly
= false)
1451 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
1452 SynonymType newSynonymType
, UUID newSecundumUuid
, String newSecundumDetail
,
1453 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1455 UpdateResult result
= new UpdateResult();
1456 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
), Taxon
.class);
1457 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
1458 newSecundumUuid
, newSecundumDetail
, keepSecundumIfUndefined
);
1464 @Transactional(readOnly
= false)
1465 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1467 boolean moveHomotypicGroup
,
1468 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1469 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1471 oldSynonym
.getSec()!= null? oldSynonym
.getSec().getUuid(): null,
1472 oldSynonym
.getSecMicroReference(),
1477 @Transactional(readOnly
= false)
1478 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1480 boolean moveHomotypicGroup
,
1481 SynonymType newSynonymType
,
1482 UUID newSecundumUuid
,
1483 String newSecundumDetail
,
1484 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1486 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1487 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1488 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1489 TaxonName synonymName
= synonym
.getName();
1490 TaxonName fromTaxonName
= oldTaxon
.getName();
1491 //set default relationship type
1492 if (newSynonymType
== null){
1493 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1495 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1497 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1498 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1499 boolean isSingleInGroup
= !(hgSize
> 1);
1501 if (! isSingleInGroup
){
1502 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1503 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1504 if (isHomotypicToAccepted
){
1505 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.";
1506 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1507 message
= String
.format(message
, homotypicRelatives
);
1508 throw new HomotypicalGroupChangeException(message
);
1510 if (! moveHomotypicGroup
){
1511 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.";
1512 throw new HomotypicalGroupChangeException(message
);
1515 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1517 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1519 UpdateResult result
= new UpdateResult();
1520 //move all synonyms to new taxon
1521 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1522 Reference newSecundum
= referenceService
.load(newSecundumUuid
);
1523 for (Synonym synRelation
: homotypicSynonyms
){
1525 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1526 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1527 oldTaxon
.removeSynonym(synRelation
, false);
1528 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1530 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1531 synRelation
.setSec(newSecundum
);
1533 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1534 synRelation
.setSecMicroReference(newSecundumDetail
);
1537 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1538 if (!synRelation
.equals(oldSynonym
)){
1543 result
.addUpdatedObject(oldTaxon
);
1544 result
.addUpdatedObject(newTaxon
);
1545 saveOrUpdate(oldTaxon
);
1546 saveOrUpdate(newTaxon
);
1552 public <T
extends TaxonBase
>List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1554 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1558 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1559 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1560 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1561 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1562 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1564 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1565 null, includeUnpublished
, languages
, highlightFragments
, null);
1567 // --- execute search
1568 TopGroups
<BytesRef
> topDocsResultSet
;
1570 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1571 } catch (ParseException e
) {
1572 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1573 luceneParseException
.setStackTrace(e
.getStackTrace());
1574 throw luceneParseException
;
1577 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1578 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1580 // --- initialize taxa, thighlight matches ....
1581 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1582 @SuppressWarnings("rawtypes")
1583 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1584 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1586 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1587 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1590 @Transactional(readOnly
= true)
1592 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
) {
1593 long numberOfResults
= dao
.countByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
);
1595 long numberOfResults_doubtful
= dao
.countByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
);
1596 List
<S
> results
= new ArrayList
<>();
1597 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1599 results
= dao
.findByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1600 results
.addAll(dao
.findByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1602 Collections
.sort(results
, new TaxonComparator());
1603 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1606 @Transactional(readOnly
= true)
1608 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
) {
1609 long numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
1610 //check whether there are doubtful taxa matching
1611 long numberOfResults_doubtful
= dao
.countByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
);
1612 List
<S
> results
= new ArrayList
<>();
1613 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1614 if (numberOfResults
> 0){
1615 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1617 results
= new ArrayList
<>();
1619 if (numberOfResults_doubtful
> 0){
1620 results
.addAll(dao
.findByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1623 Collections
.sort(results
, new TaxonComparator());
1624 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1628 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1629 Classification classification
, TaxonNode subtree
,
1630 Integer pageSize
, Integer pageNumber
,
1631 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1633 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
1635 // --- execute search
1636 TopGroups
<BytesRef
> topDocsResultSet
;
1638 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1639 } catch (ParseException e
) {
1640 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1641 luceneParseException
.setStackTrace(e
.getStackTrace());
1642 throw luceneParseException
;
1645 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1646 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1648 // --- initialize taxa, thighlight matches ....
1649 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1650 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1651 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1653 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1654 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1659 * @param queryString
1660 * @param classification
1661 * @param includeUnpublished
1663 * @param highlightFragments
1664 * @param sortFields TODO
1665 * @param directorySelectClass
1668 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1669 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1670 boolean highlightFragments
, SortField
[] sortFields
) {
1672 Builder finalQueryBuilder
= new Builder();
1673 Builder textQueryBuilder
= new Builder();
1675 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1676 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1678 if(sortFields
== null){
1679 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1681 luceneSearch
.setSortFields(sortFields
);
1683 // ---- search criteria
1684 luceneSearch
.setCdmTypRestriction(clazz
);
1686 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1687 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1688 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1690 if(className
!= null){
1691 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1694 BooleanQuery textQuery
= textQueryBuilder
.build();
1695 if(textQuery
.clauses().size() > 0) {
1696 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1699 if(classification
!= null){
1700 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1702 if(subtree
!= null){
1703 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1705 if(!includeUnpublished
) {
1706 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1707 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1708 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1711 luceneSearch
.setQuery(finalQueryBuilder
.build());
1713 if(highlightFragments
){
1714 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1716 return luceneSearch
;
1720 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1721 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1722 * drawback of requiring to do the join an indexing time.
1723 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1725 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1727 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1728 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1730 * @param queryString
1731 * @param classification
1733 * @param highlightFragments
1734 * @param sortFields TODO
1737 * @throws IOException
1739 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1740 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1741 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1744 String queryTermField
;
1745 String toField
= "id"; // TaxonBase.uuid
1746 String publishField
;
1747 String publishFieldInvers
;
1749 if(edge
.isBidirectional()){
1750 throw new RuntimeException("Bidirectional joining not supported!");
1753 fromField
= "relatedFrom.id";
1754 queryTermField
= "relatedFrom.titleCache";
1755 publishField
= "relatedFrom.publish";
1756 publishFieldInvers
= "relatedTo.publish";
1757 } else if(edge
.isInvers()) {
1758 fromField
= "relatedTo.id";
1759 queryTermField
= "relatedTo.titleCache";
1760 publishField
= "relatedTo.publish";
1761 publishFieldInvers
= "relatedFrom.publish";
1763 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1766 Builder finalQueryBuilder
= new Builder();
1768 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1769 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1771 Builder joinFromQueryBuilder
= new Builder();
1772 if(!StringUtils
.isEmpty(queryString
)){
1773 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1775 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1776 if(!includeUnpublished
){
1777 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1778 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1781 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1783 if(sortFields
== null){
1784 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1786 luceneSearch
.setSortFields(sortFields
);
1788 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1790 if(classification
!= null){
1791 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1793 if(subtree
!= null){
1794 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1797 luceneSearch
.setQuery(finalQueryBuilder
.build());
1799 if(highlightFragments
){
1800 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1802 return luceneSearch
;
1806 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1807 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1808 Classification classification
, TaxonNode subtree
,
1809 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1810 boolean highlightFragments
, Integer pageSize
,
1811 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1812 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1814 // FIXME: allow taxonomic ordering
1815 // 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";
1816 // this require building a special sort column by a special classBridge
1817 if(highlightFragments
){
1818 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1819 "currently not fully supported by this method and thus " +
1820 "may not work with common names and misapplied names.");
1823 // convert sets to lists
1824 List
<NamedArea
> namedAreaList
= null;
1825 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1826 if(namedAreas
!= null){
1827 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1828 namedAreaList
.addAll(namedAreas
);
1830 if(distributionStatus
!= null){
1831 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1832 distributionStatusList
.addAll(distributionStatus
);
1835 // set default if parameter is null
1836 if(searchModes
== null){
1837 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1840 // set sort order and thus override any sort orders which may have been
1841 // defined by prepare*Search methods
1842 if(orderHints
== null){
1843 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1845 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1847 for(OrderHint oh
: orderHints
){
1848 sortFields
[i
++] = oh
.toSortField();
1850 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1851 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1853 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1855 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1856 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1859 ======== filtering by distribution , HOWTO ========
1861 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1862 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1863 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1864 which will be put into a FilteredQuersy in the end ?
1867 3. how does it work in spatial?
1869 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1870 - http://www.infoq.com/articles/LuceneSpatialSupport
1871 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1872 ------------------------------------------------------------------------
1875 A) use a separate distribution filter per index sub-query/search:
1876 - byTaxonSyonym (query TaxaonBase):
1877 use a join area filter (Distribution -> TaxonBase)
1878 - byCommonName (query DescriptionElementBase): use an area filter on
1879 DescriptionElementBase !!! PROBLEM !!!
1880 This cannot work since the distributions are different entities than the
1881 common names and thus these are different lucene documents.
1882 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1883 use a join area filter (Distribution -> TaxonBase)
1885 B) use a common distribution filter for all index sub-query/searches:
1886 - use a common join area filter (Distribution -> TaxonBase)
1887 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1888 PROBLEM in this case: we are losing the fragment highlighting for the
1889 common names, since the returned documents are always TaxonBases
1892 /* The QueryFactory for creating filter queries on Distributions should
1893 * The query factory used for the common names query cannot be reused
1894 * for this case, since we want to only record the text fields which are
1895 * actually used in the primary query
1897 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1899 Builder multiIndexByAreaFilterBuilder
= new Builder();
1900 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1902 // search for taxa or synonyms
1903 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1904 @SuppressWarnings("rawtypes")
1905 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1906 String className
= null;
1907 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1908 taxonBaseSubclass
= Taxon
.class;
1909 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1910 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1912 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1913 queryString
, classification
, subtree
, className
,
1914 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1915 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1916 /* A) does not work!!!!
1917 if(addDistributionFilter){
1918 // in this case we need a filter which uses a join query
1919 // to get the TaxonBase documents for the DescriptionElementBase documents
1920 // which are matching the areas in question
1921 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1923 distributionStatusList,
1924 distributionFilterQueryFactory
1926 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1929 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1930 // add additional area filter for synonyms
1931 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1932 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1934 //TODO replace by createByDistributionJoinQuery
1935 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1936 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1937 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1942 // search by CommonTaxonName
1943 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1945 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1946 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1947 CommonTaxonName
.class,
1948 "inDescription.taxon.id",
1950 QueryFactory
.addTypeRestriction(
1951 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
1952 , CommonTaxonName
.class
1953 ).build(), "id", null, ScoreMode
.Max
);
1954 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1955 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1956 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1957 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1958 Builder builder
= new BooleanQuery
.Builder();
1959 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1960 if(!includeUnpublished
) {
1961 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1962 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1964 byCommonNameSearch
.setQuery(builder
.build());
1965 byCommonNameSearch
.setSortFields(sortFields
);
1967 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1969 luceneSearches
.add(byCommonNameSearch
);
1971 /* A) does not work!!!!
1973 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1974 queryString, classification, null, languages, highlightFragments)
1976 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1977 if(addDistributionFilter){
1978 // in this case we are able to use DescriptionElementBase documents
1979 // which are matching the areas in question directly
1980 BooleanQuery byDistributionQuery = createByDistributionQuery(
1982 distributionStatusList,
1983 distributionFilterQueryFactory
1985 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1990 // search by misapplied names
1991 //TODO merge with pro parte synonym search once #7487 is fixed
1992 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1994 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1995 // which allows doing query time joins
1996 // finds the misapplied name (Taxon B) which is an misapplication for
1997 // a related Taxon A.
1999 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2000 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
2001 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
2003 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
2004 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
2007 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2008 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2009 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2010 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2012 if(addDistributionFilter
){
2013 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2016 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
2017 * Maybe this is a bug in java itself.
2019 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2022 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2024 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2025 * will execute as expected:
2027 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2028 * String toField = "relation." + misappliedNameForUuid +".to.id";
2030 * Comparing both strings by the String.equals method returns true, so both String are identical.
2032 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2033 * 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)
2034 * The bug is persistent after a reboot of the development computer.
2036 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2037 // String toField = "relation." + misappliedNameForUuid +".to.id";
2038 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2039 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2040 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2042 //TODO replace by createByDistributionJoinQuery
2043 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2044 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2045 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2047 // debug code for bug described above
2048 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
2049 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2050 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2052 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2056 // search by pro parte synonyms
2057 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
2058 //TODO merge with misapplied name search once #7487 is fixed
2059 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2060 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
2062 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2063 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2064 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2065 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2067 if(addDistributionFilter
){
2068 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2069 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
2070 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2071 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2072 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2073 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2075 }//end pro parte synonyms
2077 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
2078 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
2080 if(addDistributionFilter
){
2083 // in this case we need a filter which uses a join query
2084 // to get the TaxonBase documents for the DescriptionElementBase documents
2085 // which are matching the areas in question
2087 // for doTaxa, doByCommonName
2088 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
2090 distributionStatusList
,
2091 distributionFilterQueryFactory
,
2094 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2097 if (addDistributionFilter
){
2098 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
2102 // --- execute search
2103 TopGroups
<BytesRef
> topDocsResultSet
;
2105 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2106 } catch (ParseException e
) {
2107 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2108 luceneParseException
.setStackTrace(e
.getStackTrace());
2109 throw luceneParseException
;
2112 // --- initialize taxa, highlight matches ....
2113 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2116 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2117 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2119 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
2120 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2124 * @param namedAreaList at least one area must be in the list
2125 * @param distributionStatusList optional
2126 * @param toType toType
2127 * Optional parameter. Only used for debugging to print the toType documents
2128 * @param asFilter TODO
2130 * @throws IOException
2132 protected Query
createByDistributionJoinQuery(
2133 List
<NamedArea
> namedAreaList
,
2134 List
<PresenceAbsenceTerm
> distributionStatusList
,
2135 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
2136 ) throws IOException
{
2138 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2139 String toField
= "id"; // id in toType usually this is the TaxonBase index
2141 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
2143 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
2145 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
2147 return taxonAreaJoinQuery
;
2151 * @param namedAreaList
2152 * @param distributionStatusList
2153 * @param queryFactory
2156 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
2157 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
2158 Builder areaQueryBuilder
= new Builder();
2159 // area field from Distribution
2160 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2162 // status field from Distribution
2163 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2164 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2167 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2168 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2173 * This method has been primarily created for testing the area join query but might
2174 * also be useful in other situations
2176 * @param namedAreaList
2177 * @param distributionStatusList
2178 * @param classification
2179 * @param highlightFragments
2181 * @throws IOException
2183 protected LuceneSearch
prepareByDistributionSearch(
2184 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2185 Classification classification
, TaxonNode subtree
) throws IOException
{
2187 Builder finalQueryBuilder
= new Builder();
2189 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2191 // FIXME is this query factory using the wrong type?
2192 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2194 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2195 luceneSearch
.setSortFields(sortFields
);
2198 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2200 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2202 if(classification
!= null){
2203 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
2205 if(subtree
!= null){
2206 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
2208 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2209 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2210 luceneSearch
.setQuery(finalQuery
);
2212 return luceneSearch
;
2216 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2217 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2218 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2219 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2221 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2223 // --- execute search
2224 TopGroups
<BytesRef
> topDocsResultSet
;
2226 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2227 } catch (ParseException e
) {
2228 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2229 luceneParseException
.setStackTrace(e
.getStackTrace());
2230 throw luceneParseException
;
2233 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2234 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2236 // --- initialize taxa, highlight matches ....
2237 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2238 @SuppressWarnings("rawtypes")
2239 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2240 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2242 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2243 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2247 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2248 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2249 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2251 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2252 classification
, subtree
,
2253 null, languages
, highlightFragments
);
2254 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2255 includeUnpublished
, languages
, highlightFragments
, null);
2257 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2259 // --- execute search
2260 TopGroups
<BytesRef
> topDocsResultSet
;
2262 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2263 } catch (ParseException e
) {
2264 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2265 luceneParseException
.setStackTrace(e
.getStackTrace());
2266 throw luceneParseException
;
2269 // --- initialize taxa, highlight matches ....
2270 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2272 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2273 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2274 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2276 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2277 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2279 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2280 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2285 * @param queryString
2286 * @param classification
2289 * @param highlightFragments
2290 * @param directorySelectClass
2293 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2294 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2295 List
<Language
> languages
, boolean highlightFragments
) {
2297 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2298 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2300 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2302 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2303 languages
, descriptionElementQueryFactory
);
2305 luceneSearch
.setSortFields(sortFields
);
2306 luceneSearch
.setCdmTypRestriction(clazz
);
2307 luceneSearch
.setQuery(finalQuery
);
2308 if(highlightFragments
){
2309 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2312 return luceneSearch
;
2316 * @param queryString
2317 * @param classification
2320 * @param descriptionElementQueryFactory
2323 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2324 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2325 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2327 Builder finalQueryBuilder
= new Builder();
2328 Builder textQueryBuilder
= new Builder();
2330 if(!StringUtils
.isEmpty(queryString
)){
2332 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2335 Builder nameQueryBuilder
= new Builder();
2336 if(languages
== null || languages
.size() == 0){
2337 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2339 Builder languageSubQueryBuilder
= new Builder();
2340 for(Language lang
: languages
){
2341 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2343 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2344 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2346 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2349 // text field from TextData
2350 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2352 // --- TermBase fields - by representation ----
2353 // state field from CategoricalData
2354 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2356 // state field from CategoricalData
2357 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2359 // area field from Distribution
2360 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2362 // status field from Distribution
2363 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2365 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2368 // --- classification ----
2371 if(classification
!= null){
2372 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2374 if(subtree
!= null){
2375 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2378 // --- IdentifieableEntity fields - by uuid
2379 if(features
!= null && features
.size() > 0 ){
2380 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2383 // the description must be associated with a taxon
2384 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2386 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2387 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2392 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2394 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2395 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2397 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2398 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2400 UUID nameUuid
= taxon
.getName().getUuid();
2401 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2402 String epithetOfTaxon
= null;
2403 String infragenericEpithetOfTaxon
= null;
2404 String infraspecificEpithetOfTaxon
= null;
2405 if (taxonName
.isSpecies()){
2406 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2407 } else if (taxonName
.isInfraGeneric()){
2408 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2409 } else if (taxonName
.isInfraSpecific()){
2410 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2412 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2413 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2414 List
<String
> taxonNames
= new ArrayList
<>();
2416 for (TaxonNode node
: nodes
){
2417 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2418 // List<String> synonymsEpithet = new ArrayList<>();
2420 if (node
.getClassification().equals(classification
)){
2421 if (!node
.isTopmostNode()){
2422 TaxonNode parent
= node
.getParent();
2423 parent
= CdmBase
.deproxy(parent
);
2424 TaxonName parentName
= parent
.getTaxon().getName();
2425 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2426 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2428 //create inferred synonyms for species, subspecies
2429 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2431 Synonym inferredEpithet
= null;
2432 Synonym inferredGenus
= null;
2433 Synonym potentialCombination
= null;
2435 List
<String
> propertyPaths
= new ArrayList
<>();
2436 propertyPaths
.add("synonym");
2437 propertyPaths
.add("synonym.name");
2438 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2439 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2441 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2442 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2443 null, null,orderHintsSynonyms
,propertyPaths
);
2445 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2446 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2447 if (doWithMisappliedNames
){
2448 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2449 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2450 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2451 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2452 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2453 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2456 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2457 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2459 inferredEpithet
= createInferredEpithets(taxon
,
2460 zooHashMap
, taxonName
, epithetOfTaxon
,
2461 infragenericEpithetOfTaxon
,
2462 infraspecificEpithetOfTaxon
,
2463 taxonNames
, parentName
,
2464 synonymRelationOfParent
);
2466 inferredSynonyms
.add(inferredEpithet
);
2467 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2468 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2471 if (doWithMisappliedNames
){
2473 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2474 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2476 inferredEpithet
= createInferredEpithets(taxon
,
2477 zooHashMap
, taxonName
, epithetOfTaxon
,
2478 infragenericEpithetOfTaxon
,
2479 infraspecificEpithetOfTaxon
,
2480 taxonNames
, parentName
,
2483 inferredSynonyms
.add(inferredEpithet
);
2484 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2485 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2489 if (!taxonNames
.isEmpty()){
2490 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2491 if (!synNotInCDM
.isEmpty()){
2492 inferredSynonymsToBeRemoved
.clear();
2494 for (Synonym syn
:inferredSynonyms
){
2495 IZoologicalName name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2496 if (!synNotInCDM
.contains(name
.getNameCache())){
2497 inferredSynonymsToBeRemoved
.add(syn
);
2501 // Remove identified Synonyms from inferredSynonyms
2502 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2503 inferredSynonyms
.remove(synonym
);
2508 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2510 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2512 inferredGenus
= createInferredGenus(taxon
,
2513 zooHashMap
, taxonName
, epithetOfTaxon
,
2514 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2516 inferredSynonyms
.add(inferredGenus
);
2517 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2518 taxonNames
.add(inferredGenus
.getName().getNameCache());
2521 if (doWithMisappliedNames
){
2523 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2524 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2525 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2527 inferredSynonyms
.add(inferredGenus
);
2528 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2529 taxonNames
.add(inferredGenus
.getName().getNameCache());
2534 if (!taxonNames
.isEmpty()){
2535 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2536 IZoologicalName name
;
2537 if (!synNotInCDM
.isEmpty()){
2538 inferredSynonymsToBeRemoved
.clear();
2540 for (Synonym syn
:inferredSynonyms
){
2541 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2542 if (!synNotInCDM
.contains(name
.getNameCache())){
2543 inferredSynonymsToBeRemoved
.add(syn
);
2547 // Remove identified Synonyms from inferredSynonyms
2548 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2549 inferredSynonyms
.remove(synonym
);
2554 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2556 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2557 //for all synonyms of the parent...
2558 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2560 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2562 synName
= synonymRelationOfParent
.getName();
2564 // Set the sourceReference
2565 sourceReference
= synonymRelationOfParent
.getSec();
2567 // Determine the idInSource
2568 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2570 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2571 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2572 String synParentInfragenericName
= null;
2573 String synParentSpecificEpithet
= null;
2575 if (parentSynZooName
.isInfraGeneric()){
2576 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2578 if (parentSynZooName
.isSpecies()){
2579 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2582 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2583 synonymsGenus.put(synGenusName, idInSource);
2586 //for all synonyms of the taxon
2588 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2590 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2591 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2593 synParentInfragenericName
,
2594 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2596 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2597 inferredSynonyms
.add(potentialCombination
);
2598 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2599 taxonNames
.add(potentialCombination
.getName().getNameCache());
2603 if (doWithMisappliedNames
){
2605 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2607 TaxonName misappliedParentName
;
2609 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2610 misappliedParentName
= misappliedParent
.getName();
2612 HibernateProxyHelper
.deproxy(misappliedParent
);
2614 // Set the sourceReference
2615 sourceReference
= misappliedParent
.getSec();
2617 // Determine the idInSource
2618 String idInSourceParent
= getIdInSource(misappliedParent
);
2620 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2621 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2622 String synParentInfragenericName
= null;
2623 String synParentSpecificEpithet
= null;
2625 if (parentSynZooName
.isInfraGeneric()){
2626 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2628 if (parentSynZooName
.isSpecies()){
2629 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2632 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2633 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2634 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2635 potentialCombination
= createPotentialCombination(
2636 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2638 synParentInfragenericName
,
2639 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2641 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2642 inferredSynonyms
.add(potentialCombination
);
2643 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2644 taxonNames
.add(potentialCombination
.getName().getNameCache());
2649 if (!taxonNames
.isEmpty()){
2650 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2651 IZoologicalName name
;
2652 if (!synNotInCDM
.isEmpty()){
2653 inferredSynonymsToBeRemoved
.clear();
2654 for (Synonym syn
:inferredSynonyms
){
2656 name
= syn
.getName();
2657 }catch (ClassCastException e
){
2658 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2660 if (!synNotInCDM
.contains(name
.getNameCache())){
2661 inferredSynonymsToBeRemoved
.add(syn
);
2664 // Remove identified Synonyms from inferredSynonyms
2665 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2666 inferredSynonyms
.remove(synonym
);
2672 logger
.info("The synonym type is not defined.");
2673 return inferredSynonyms
;
2679 return inferredSynonyms
;
2682 private Synonym
createPotentialCombination(String idInSourceParent
,
2683 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2684 String synParentInfragenericName
, String synParentSpecificEpithet
,
2685 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2686 Synonym potentialCombination
;
2687 Reference sourceReference
;
2688 IZoologicalName inferredSynName
;
2689 HibernateProxyHelper
.deproxy(syn
);
2691 // Set sourceReference
2692 sourceReference
= syn
.getSec();
2693 if (sourceReference
== null){
2694 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2696 if (!parentSynZooName
.getTaxa().isEmpty()){
2697 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2699 sourceReference
= taxon
.getSec();
2702 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2704 String synTaxonInfraSpecificName
= null;
2706 if (parentSynZooName
.isSpecies()){
2707 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2710 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2711 synonymsEpithet.add(epithetName);
2714 //create potential combinations...
2715 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2717 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2718 if (zooSynName
.isSpecies()){
2719 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2720 if (parentSynZooName
.isInfraGeneric()){
2721 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2724 if (zooSynName
.isInfraSpecific()){
2725 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2726 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2728 if (parentSynZooName
.isInfraGeneric()){
2729 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2732 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2734 // Set the sourceReference
2735 potentialCombination
.setSec(sourceReference
);
2738 // Determine the idInSource
2739 String idInSourceSyn
= getIdInSource(syn
);
2741 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2742 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2743 inferredSynName
.addSource(originalSource
);
2744 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2745 potentialCombination
.addSource(originalSource
);
2748 return potentialCombination
;
2751 private Synonym
createInferredGenus(Taxon taxon
,
2752 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2753 String epithetOfTaxon
, String genusOfTaxon
,
2754 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2757 Synonym inferredGenus
;
2759 IZoologicalName inferredSynName
;
2760 synName
=syn
.getName();
2761 HibernateProxyHelper
.deproxy(syn
);
2763 // Determine the idInSource
2764 String idInSourceSyn
= getIdInSource(syn
);
2765 String idInSourceTaxon
= getIdInSource(taxon
);
2766 // Determine the sourceReference
2767 Reference sourceReference
= syn
.getSec();
2769 //logger.warn(sourceReference.getTitleCache());
2771 synName
= syn
.getName();
2772 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2773 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2774 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2775 synonymsEpithet.add(synSpeciesEpithetName);
2778 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2779 //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...
2781 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2782 if (zooParentName
.isInfraGeneric()){
2783 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2786 if (taxonName
.isSpecies()){
2787 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2789 if (taxonName
.isInfraSpecific()){
2790 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2791 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2794 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2796 // Set the sourceReference
2797 inferredGenus
.setSec(sourceReference
);
2799 // Add the original source
2800 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2801 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2802 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2803 inferredGenus
.addSource(originalSource
);
2805 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2806 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2807 inferredSynName
.addSource(originalSource
);
2808 originalSource
= null;
2811 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2812 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2813 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2814 inferredGenus
.addSource(originalSource
);
2816 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2817 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2818 inferredSynName
.addSource(originalSource
);
2819 originalSource
= null;
2822 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2824 return inferredGenus
;
2827 private Synonym
createInferredEpithets(Taxon taxon
,
2828 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2829 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2830 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2831 TaxonName parentName
, TaxonBase
<?
> syn
) {
2833 Synonym inferredEpithet
;
2835 IZoologicalName inferredSynName
;
2836 HibernateProxyHelper
.deproxy(syn
);
2838 // Determine the idInSource
2839 String idInSourceSyn
= getIdInSource(syn
);
2840 String idInSourceTaxon
= getIdInSource(taxon
);
2841 // Determine the sourceReference
2842 Reference sourceReference
= syn
.getSec();
2844 if (sourceReference
== null){
2845 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2846 sourceReference
= taxon
.getSec();
2849 synName
= syn
.getName();
2850 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2851 String synGenusName
= zooSynName
.getGenusOrUninomial();
2852 String synInfraGenericEpithet
= null;
2853 String synSpecificEpithet
= null;
2855 if (zooSynName
.getInfraGenericEpithet() != null){
2856 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2859 if (zooSynName
.isInfraSpecific()){
2860 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2863 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2864 synonymsGenus.put(synGenusName, idInSource);
2867 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2869 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2870 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2871 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2873 inferredSynName
.setGenusOrUninomial(synGenusName
);
2875 if (parentName
.isInfraGeneric()){
2876 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2878 if (taxonName
.isSpecies()){
2879 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2880 }else if (taxonName
.isInfraSpecific()){
2881 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2882 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2885 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2887 // Set the sourceReference
2888 inferredEpithet
.setSec(sourceReference
);
2890 /* Add the original source
2891 if (idInSource != null) {
2892 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2895 Reference citation = getCitation(syn);
2896 if (citation != null) {
2897 originalSource.setCitation(citation);
2898 inferredEpithet.addSource(originalSource);
2901 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2904 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2905 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2907 inferredEpithet
.addSource(originalSource
);
2909 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2910 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2912 inferredSynName
.addSource(originalSource
);
2914 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2916 return inferredEpithet
;
2920 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2921 * Very likely only useful for createInferredSynonyms().
2926 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2927 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2928 if (taxonName
== null) {
2929 taxonName
= zooHashMap
.get(uuid
);
2935 * Returns the idInSource for a given Synonym.
2938 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2939 String idInSource
= null;
2940 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2941 if (sources
.size() == 1) {
2942 IdentifiableSource source
= sources
.iterator().next();
2943 if (source
!= null) {
2944 idInSource
= source
.getIdInSource();
2946 } else if (sources
.size() > 1) {
2949 for (IdentifiableSource source
: sources
) {
2950 idInSource
+= source
.getIdInSource();
2951 if (count
< sources
.size()) {
2956 } else if (sources
.size() == 0){
2957 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2964 * Returns the citation for a given Synonym.
2967 private Reference
getCitation(Synonym syn
) {
2968 Reference citation
= null;
2969 Set
<IdentifiableSource
> sources
= syn
.getSources();
2970 if (sources
.size() == 1) {
2971 IdentifiableSource source
= sources
.iterator().next();
2972 if (source
!= null) {
2973 citation
= source
.getCitation();
2975 } else if (sources
.size() > 1) {
2976 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2983 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2984 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2986 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2987 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2988 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2990 return inferredSynonyms
;
2994 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2996 // TODO quickly implemented, create according dao !!!!
2997 Set
<TaxonNode
> nodes
= new HashSet
<>();
2998 Set
<Classification
> classifications
= new HashSet
<>();
2999 List
<Classification
> list
= new ArrayList
<>();
3001 if (taxonBase
== null) {
3005 taxonBase
= load(taxonBase
.getUuid());
3007 if (taxonBase
instanceof Taxon
) {
3008 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
3010 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
3012 nodes
.addAll(taxon
.getTaxonNodes());
3015 for (TaxonNode node
: nodes
) {
3016 classifications
.add(node
.getClassification());
3018 list
.addAll(classifications
);
3023 @Transactional(readOnly
= false)
3024 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
3026 TaxonRelationshipType oldRelationshipType
,
3027 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3028 UpdateResult result
= new UpdateResult();
3029 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
3030 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3031 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
3033 // result.addUpdatedObject(fromTaxon);
3034 result
.addUpdatedObject(toTaxon
);
3035 result
.addUpdatedObject(result
.getCdmEntity());
3041 @Transactional(readOnly
= false)
3042 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
3043 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3045 UpdateResult result
= new UpdateResult();
3046 // Create new synonym using concept name
3047 TaxonName synonymName
= fromTaxon
.getName();
3049 // Remove concept relation from taxon
3050 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
3052 // Create a new synonym for the taxon
3054 if (synonymType
!= null
3055 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
3056 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
3057 toTaxon
.addHomotypicSynonym(synonym
);
3059 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
3061 //keep the publish flag
3062 synonym
.setPublish(fromTaxon
.isPublish());
3063 this.saveOrUpdate(toTaxon
);
3064 //TODO: configurator and classification
3065 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
3066 config
.setDeleteNameIfPossible(false);
3067 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
3068 result
.setCdmEntity(synonym
);
3069 result
.addUpdatedObject(toTaxon
);
3070 result
.addUpdatedObject(synonym
);
3075 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
3076 DeleteResult result
= new DeleteResult();
3077 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
3078 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
3079 if (taxonBase
instanceof Taxon
){
3080 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
3081 List
<String
> propertyPaths
= new ArrayList
<>();
3082 propertyPaths
.add("taxonNodes");
3083 Taxon taxon
= (Taxon
)load(taxonBaseUuid
, propertyPaths
);
3085 result
= isDeletableForTaxon(references
, taxonConfig
);
3087 if (taxonConfig
.isDeleteNameIfPossible()){
3088 if (taxonBase
.getName() != null){
3089 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), taxonConfig
.getNameDeletionConfig(), taxon
.getUuid());
3090 if (!nameResult
.isOk()){
3091 result
.addExceptions(nameResult
.getExceptions());
3097 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
3098 result
= isDeletableForSynonym(references
, synonymConfig
);
3099 if (synonymConfig
.isDeleteNameIfPossible()){
3100 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), synonymConfig
.getNameDeletionConfig(), taxonBase
.getUuid());
3101 if (!nameResult
.isOk()){
3102 result
.addExceptions(nameResult
.getExceptions());
3110 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
3112 DeleteResult result
= new DeleteResult();
3113 for (CdmBase ref
: references
){
3114 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3115 String message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
3116 result
.addException(new ReferencedObjectUndeletableException(message
));
3117 result
.addRelatedObject(ref
);
3125 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
3126 String message
= null;
3127 DeleteResult result
= new DeleteResult();
3128 for (CdmBase ref
: references
){
3129 if (!(ref
instanceof TaxonName
|| ref
instanceof SecundumSource
)){
3131 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
3132 message
= "The taxon can't be deleted as long as it has synonyms.";
3134 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
3135 message
= "The taxon can't be deleted as long as it has factual data.";
3138 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
3139 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
3141 if (ref
instanceof TaxonNode
&& config
.getClassificationUuid() != null && !config
.isDeleteInAllClassifications() && !((TaxonNode
)ref
).getClassification().getUuid().equals(config
.getClassificationUuid())){
3142 message
= "The taxon can't be deleted as long as it is used in more than one classification";
3145 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
3146 if (!config
.isDeleteMisappliedNames() &&
3147 (((TaxonRelationship
)ref
).getType().isMisappliedName())){
3148 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3150 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3153 if (ref
instanceof PolytomousKeyNode
){
3154 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3157 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3158 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3161 /* //PolytomousKeyNode
3162 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3163 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3168 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3169 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3173 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3174 message
= "Taxon can't be deleted as it is used in a determination event";
3177 if (message
!= null){
3178 result
.addException(new ReferencedObjectUndeletableException(message
));
3179 result
.addRelatedObject(ref
);
3188 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3189 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3191 //preliminary implementation
3193 Set
<Taxon
> taxa
= new HashSet
<>();
3194 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3195 if (taxonBase
== null){
3196 return new IncludedTaxaDTO();
3197 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3198 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3200 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3201 //TODO partial synonyms ??
3202 //TODO synonyms in general
3203 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3204 taxa
.add(syn
.getAcceptedTaxon());
3206 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3209 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3211 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3212 related
= makeRelatedIncluded(related
, result
, config
);
3219 * @param uncheckedTaxa
3220 * @param existingTaxa
3223 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3225 * @return the set of conceptually related taxa for further use
3227 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3230 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3231 for (Taxon taxon
: uncheckedTaxa
){
3232 taxonNodes
.addAll(taxon
.getTaxonNodes());
3235 Set
<Taxon
> children
= new HashSet
<>();
3236 if (! config
.onlyCongruent
){
3237 for (TaxonNode node
: taxonNodes
){
3238 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3239 for (TaxonNode child
: childNodes
){
3240 children
.add(child
.getTaxon());
3243 children
.remove(null); // just to be on the save side
3246 Iterator
<Taxon
> it
= children
.iterator();
3247 while(it
.hasNext()){
3248 UUID uuid
= it
.next().getUuid();
3249 if (existingTaxa
.contains(uuid
)){
3252 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3257 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3258 uncheckedAndChildren
.addAll(children
);
3260 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3263 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3268 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3269 * @return the set of these computed taxa
3271 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3272 Set
<Taxon
> result
= new HashSet
<>();
3274 for (Taxon taxon
: unchecked
){
3275 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3276 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3278 for (TaxonRelationship fromRel
: fromRelations
){
3279 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3282 TaxonRelationshipType fromRelType
= fromRel
.getType();
3283 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3284 !config
.onlyCongruent
&& (
3285 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3286 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3289 result
.add(fromRel
.getToTaxon());
3293 for (TaxonRelationship toRel
: toRelations
){
3294 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3297 TaxonRelationshipType fromRelType
= toRel
.getType();
3298 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3299 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3300 result
.add(toRel
.getFromTaxon());
3305 Iterator
<Taxon
> it
= result
.iterator();
3306 while(it
.hasNext()){
3307 UUID uuid
= it
.next().getUuid();
3308 if (existingTaxa
.contains(uuid
)){
3311 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3318 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3319 @SuppressWarnings("rawtypes")
3320 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3321 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3326 @Transactional(readOnly
= true)
3327 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3328 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3329 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3330 Integer pageNumber
, List
<String
> propertyPaths
) {
3331 if (subtreeFilter
== null){
3332 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3335 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3336 List
<Object
[]> daoResults
= new ArrayList
<>();
3337 if(numberOfResults
> 0) { // no point checking again
3338 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3339 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3342 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3343 for (Object
[] daoObj
: daoResults
){
3345 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3347 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3350 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3354 @Transactional(readOnly
= true)
3355 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3356 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3357 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3358 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3359 if (subtreeFilter
== null){
3360 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3363 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3364 List
<Object
[]> daoResults
= new ArrayList
<>();
3365 if(numberOfResults
> 0) { // no point checking again
3366 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3367 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3370 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3371 for (Object
[] daoObj
: daoResults
){
3373 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3375 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3378 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3382 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3383 UpdateResult result
= new UpdateResult();
3385 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3386 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3387 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3388 //reload to avoid session conflicts
3389 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3391 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3392 if(description
.isProtectedTitleCache()){
3393 String separator
= "";
3394 if(!StringUtils
.isBlank(description
.getTitleCache())){
3397 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3399 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3400 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3401 description
.addAnnotation(annotation
);
3402 toTaxon
.addDescription(description
);
3403 dao
.saveOrUpdate(toTaxon
);
3404 dao
.saveOrUpdate(fromTaxon
);
3405 result
.addUpdatedObject(toTaxon
);
3406 result
.addUpdatedObject(fromTaxon
);
3414 @Transactional(readOnly
= false)
3415 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3416 UUID acceptedTaxonUuid
, boolean setNameInSource
, boolean newUuidForAcceptedTaxon
) {
3417 TaxonBase
<?
> base
= this.load(synonymUUid
);
3418 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3419 base
= this.load(acceptedTaxonUuid
);
3420 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3422 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
, setNameInSource
, newUuidForAcceptedTaxon
);
3426 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3427 Set
<TaxonRelationshipType
> inversTypes
,
3428 Direction direction
, boolean groupMisapplications
,
3429 boolean includeUnpublished
,
3430 Integer pageSize
, Integer pageNumber
) {
3431 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3432 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3434 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3436 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3437 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3438 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3440 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3442 //TODO paging is difficult because misapplication string is an attribute
3444 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3445 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3446 // if(numberOfResults > 0) { // no point checking again
3447 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3450 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3453 List
<Language
> languages
= null;
3455 direction
= Direction
.relatedTo
;
3456 //TODO order hints, property path
3457 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3458 for (TaxonRelationship relation
: relations
){
3459 dto
.addRelation(relation
, direction
, languages
);
3463 direction
= Direction
.relatedFrom
;
3464 //TODO order hints, property path
3465 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3466 for (TaxonRelationship relation
: relations
){
3467 dto
.addRelation(relation
, direction
, languages
);
3470 if (groupMisapplications
){
3472 dto
.createMisapplicationString();