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
.service
.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
.Synonym
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
117 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
118 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
119 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
120 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
121 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.Restriction
;
122 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
123 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
124 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
125 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
126 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
127 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
128 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
129 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
130 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
131 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
132 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
133 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
137 * @author a.kohlbecker
141 @Transactional(readOnly
= true)
142 public class TaxonServiceImpl
143 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
144 implements ITaxonService
{
146 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
148 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
150 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
152 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
155 private ITaxonNodeDao taxonNodeDao
;
158 private ITaxonNameDao nameDao
;
161 private INameService nameService
;
164 private IOccurrenceService occurrenceService
;
167 private ITaxonNodeService nodeService
;
170 private IDescriptionService descriptionService
;
173 private IReferenceService referenceService
;
176 // private IOrderedTermVocabularyDao orderedVocabularyDao;
179 private IOccurrenceDao occurrenceDao
;
182 private IClassificationDao classificationDao
;
185 private AbstractBeanInitializer beanInitializer
;
188 private ILuceneIndexToolProvider luceneIndexToolProvider
;
190 //************************ CONSTRUCTOR ****************************/
191 public TaxonServiceImpl(){
192 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
195 // ****************************** METHODS ********************************/
198 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
199 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
203 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
204 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
208 @Transactional(readOnly
= false)
209 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
210 UpdateResult result
= new UpdateResult();
211 acceptedTaxon
.removeSynonym(synonym
);
212 TaxonName synonymName
= synonym
.getName();
213 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
215 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(taxonName
.getHomotypicalGroup());
217 synonymName
.removeTaxonBase(synonym
);
219 //taxonName.removeTaxonBase(acceptedTaxon);
221 List
<Synonym
> synonyms
= new ArrayList
<>();
222 for (Synonym syn
: acceptedTaxon
.getSynonyms()){
223 syn
= HibernateProxyHelper
.deproxy(syn
, Synonym
.class);
226 for (Synonym syn
: synonyms
){
227 acceptedTaxon
.removeSynonym(syn
);
229 Taxon newTaxon
= acceptedTaxon
.clone();
230 newTaxon
.getDescriptions().clear();
231 Set
<TaxonDescription
> descriptionsToCopy
= new HashSet
<>();
232 for (TaxonDescription desc
: acceptedTaxon
.getDescriptions()){
233 descriptionsToCopy
.add(desc
);
235 for (TaxonDescription description
: descriptionsToCopy
){
236 newTaxon
.addDescription(description
);
238 newTaxon
.setName(synonymName
);
239 newTaxon
.setSec(synonym
.getSec());
240 newTaxon
.setPublish(synonym
.isPublish());
241 for (Synonym syn
: synonyms
){
242 if (!syn
.getName().equals(newTaxon
.getName())){
243 newTaxon
.addSynonym(syn
, syn
.getType());
247 //move all data to new taxon
248 //Move Taxon RelationShips to new Taxon
249 for(TaxonRelationship taxonRelationship
: newTaxon
.getTaxonRelations()){
250 newTaxon
.removeTaxonRelation(taxonRelationship
);
253 for(TaxonRelationship taxonRelationship
: acceptedTaxon
.getTaxonRelations()){
254 Taxon fromTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getFromTaxon());
255 Taxon toTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getToTaxon());
256 if (fromTaxon
== acceptedTaxon
){
257 newTaxon
.addTaxonRelation(taxonRelationship
.getToTaxon(), taxonRelationship
.getType(),
258 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
260 }else if(toTaxon
== acceptedTaxon
){
261 fromTaxon
.addTaxonRelation(newTaxon
, taxonRelationship
.getType(),
262 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
263 saveOrUpdate(fromTaxon
);
266 logger
.warn("Taxon is not part of its own Taxonrelationship");
268 // Remove old relationships
270 fromTaxon
.removeTaxonRelation(taxonRelationship
);
271 toTaxon
.removeTaxonRelation(taxonRelationship
);
272 taxonRelationship
.setToTaxon(null);
273 taxonRelationship
.setFromTaxon(null);
276 //Move descriptions to new taxon
277 List
<TaxonDescription
> descriptions
= new ArrayList
<TaxonDescription
>( newTaxon
.getDescriptions()); //to avoid concurrent modification errors (newAcceptedTaxon.addDescription() modifies also oldtaxon.descritpions())
278 for(TaxonDescription description
: descriptions
){
279 String message
= "Description copied from former accepted taxon: %s (Old title: %s)";
280 message
= String
.format(message
, acceptedTaxon
.getTitleCache(), description
.getTitleCache());
281 description
.setTitleCache(message
, true);
283 for (DescriptionElementBase element
: description
.getElements()){
284 for (DescriptionElementSource source
: element
.getSources()){
285 if (source
.getNameUsedInSource() == null){
286 source
.setNameUsedInSource(taxonName
);
291 // //oldTaxon.removeDescription(description, false);
292 // newTaxon.addDescription(description);
294 List
<TaxonNode
> nodes
= new ArrayList
<>(acceptedTaxon
.getTaxonNodes());
295 for (TaxonNode node
: nodes
){
296 node
= HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
297 TaxonNode parent
= node
.getParent();
298 acceptedTaxon
.removeTaxonNode(node
);
299 node
.setTaxon(newTaxon
);
301 parent
.addChildNode(node
, null, null);
305 Synonym newSynonym
= synonym
.clone();
306 newSynonym
.setName(taxonName
);
307 newSynonym
.setSec(acceptedTaxon
.getSec());
308 newSynonym
.setPublish(acceptedTaxon
.isPublish());
309 if (sameHomotypicGroup
){
310 newTaxon
.addSynonym(newSynonym
, SynonymType
.HOMOTYPIC_SYNONYM_OF());
312 newTaxon
.addSynonym(newSynonym
, SynonymType
.HETEROTYPIC_SYNONYM_OF());
315 saveOrUpdate(newSynonym
);
316 saveOrUpdate(newTaxon
);
317 TaxonDeletionConfigurator conf
= new TaxonDeletionConfigurator();
318 conf
.setDeleteNameIfPossible(false);
319 SynonymDeletionConfigurator confSyn
= new SynonymDeletionConfigurator();
320 confSyn
.setDeleteNameIfPossible(false);
321 result
.setCdmEntity(newTaxon
);
323 DeleteResult deleteResult
= deleteTaxon(acceptedTaxon
.getUuid(), conf
, null);
324 if (synonym
.isPersited()){
325 deleteResult
.includeResult(deleteSynonym(synonym
, confSyn
));
327 result
.includeResult(deleteResult
);
332 @Transactional(readOnly
= false)
333 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, Reference newSecRef
, String microRef
, boolean deleteSynonym
) {
334 UpdateResult result
= new UpdateResult();
335 TaxonName acceptedName
= acceptedTaxon
.getName();
336 TaxonName synonymName
= synonym
.getName();
337 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
339 //check synonym is not homotypic
340 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
341 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
342 result
.addException(new HomotypicalGroupChangeException(message
));
346 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, newSecRef
, microRef
);
347 newAcceptedTaxon
.setPublish(synonym
.isPublish());
348 dao
.save(newAcceptedTaxon
);
349 result
.setCdmEntity(newAcceptedTaxon
);
350 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
351 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
353 for (Synonym heteroSynonym
: heteroSynonyms
){
354 if (synonym
.equals(heteroSynonym
)){
355 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
357 //move synonyms in same homotypic group to new accepted taxon
358 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
361 dao
.saveOrUpdate(acceptedTaxon
);
362 result
.addUpdatedObject(acceptedTaxon
);
367 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
368 config
.setDeleteNameIfPossible(false);
369 this.deleteSynonym(synonym
, config
);
371 } catch (Exception e
) {
372 result
.addException(e
);
380 @Transactional(readOnly
= false)
381 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
382 UUID acceptedTaxonUuid
,
383 UUID newParentNodeUuid
,
385 String microReference
,
386 SecReferenceHandlingEnum secHandling
,
387 boolean deleteSynonym
) {
388 UpdateResult result
= new UpdateResult();
389 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
390 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
391 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
392 Reference newSecRef
= null;
393 switch (secHandling
){
398 newSecRef
= synonym
.getSec();
400 case UseNewParentSec
:
401 newSecRef
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
404 Reference parentSec
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
405 Reference synSec
= synonym
.getSec();
406 if (parentSec
!= null && synSec
!= null && parentSec
.equals(synSec
)){
407 newSecRef
= synonym
.getSec();
409 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
412 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
419 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, newSecRef
, microReference
, deleteSynonym
);
420 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
422 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
423 taxonNodeDao
.save(newNode
);
424 result
.addUpdatedObject(newTaxon
);
425 result
.addUpdatedObject(acceptedTaxon
);
426 result
.setCdmEntity(newNode
);
432 @Transactional(readOnly
= false)
433 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
435 TaxonRelationshipType taxonRelationshipType
,
437 String microcitation
){
439 UpdateResult result
= new UpdateResult();
440 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
441 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
442 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
443 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
444 // result.setCdmEntity(relatedTaxon);
445 result
.addUpdatedObject(relatedTaxon
);
446 result
.addUpdatedObject(toTaxon
);
451 @Transactional(readOnly
= false)
452 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
453 // Get name from synonym
454 if (synonym
== null){
458 UpdateResult result
= new UpdateResult();
460 TaxonName synonymName
= synonym
.getName();
462 /* // remove synonym from taxon
463 toTaxon.removeSynonym(synonym);
465 // Create a taxon with synonym name
466 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
467 fromTaxon
.setPublish(synonym
.isPublish());
469 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
471 // Add taxon relation
472 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
473 result
.setCdmEntity(fromTaxon
);
474 // since we are swapping names, we have to detach the name from the synonym completely.
475 // Otherwise the synonym will still be in the list of typified names.
476 // synonym.getName().removeTaxonBase(synonym);
477 result
.includeResult(this.deleteSynonym(synonym
, null));
482 @Transactional(readOnly
= false)
484 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
485 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
487 TaxonName synonymName
= synonym
.getName();
488 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
491 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
492 newHomotypicalGroup
.addTypifiedName(synonymName
);
494 //remove existing basionym relationships
495 synonymName
.removeBasionyms();
497 //add basionym relationship
498 if (setBasionymRelationIfApplicable
){
499 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
500 for (TaxonName basionym
: basionyms
){
501 synonymName
.addBasionym(basionym
);
505 //set synonym relationship correctly
506 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
508 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
509 if (acceptedTaxon
!= null){
511 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
512 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
513 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
514 synonym
.setType(newRelationType
);
516 if (hasNewTargetTaxon
){
517 acceptedTaxon
.removeSynonym(synonym
, false);
520 if (hasNewTargetTaxon
){
521 @SuppressWarnings("null")
522 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
523 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
524 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
525 targetTaxon
.addSynonym(synonym
, relType
);
530 @Transactional(readOnly
= false)
531 public UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
533 clazz
= TaxonBase
.class;
535 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
540 protected void setDao(ITaxonDao dao
) {
545 public <T
extends TaxonBase
> Pager
<T
> findTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
546 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
547 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
);
549 List
<T
> results
= new ArrayList
<>();
550 if(numberOfResults
> 0) { // no point checking again
551 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
,
552 pageSize
, pageNumber
, propertyPaths
);
555 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
559 public <T
extends TaxonBase
> List
<T
> listTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
560 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
562 return findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infragenericEpithet
, authorshipCache
, rank
,
563 pageSize
, pageNumber
, propertyPaths
).getRecords();
567 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
568 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
569 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
571 List
<TaxonRelationship
> results
= new ArrayList
<>();
572 if(numberOfResults
> 0) { // no point checking again
573 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
579 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
580 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
581 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
583 List
<TaxonRelationship
> results
= new ArrayList
<>();
584 if(numberOfResults
> 0) { // no point checking again
585 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
587 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
591 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
592 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
593 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
595 List
<TaxonRelationship
> results
= new ArrayList
<>();
596 if(numberOfResults
> 0) { // no point checking again
597 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
603 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
604 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
605 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
607 List
<TaxonRelationship
> results
= new ArrayList
<>();
608 if(numberOfResults
> 0) { // no point checking again
609 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
611 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
615 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
616 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
618 Long numberOfResults
= dao
.countTaxonRelationships(types
);
619 List
<TaxonRelationship
> results
= new ArrayList
<>();
620 if(numberOfResults
> 0) {
621 results
= dao
.getTaxonRelationships(types
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
627 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
628 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
629 return page(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, INCLUDE_UNPUBLISHED
);
633 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
634 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
, boolean includeUnpublished
) {
637 long resultSize
= dao
.count(clazz
, restrictions
);
638 if(AbstractPagerImpl
.hasResultsInRange(resultSize
, pageIndex
, pageSize
)){
639 records
= dao
.list(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, includeUnpublished
);
641 records
= new ArrayList
<>();
643 Pager
<S
> pager
= new DefaultPagerImpl
<>(pageIndex
, resultSize
, pageSize
, records
);
648 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
649 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
651 Synonym synonym
= null;
654 synonym
= (Synonym
) dao
.load(synonymUuid
);
655 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
656 } catch (ClassCastException e
){
657 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
658 } catch (NullPointerException e
){
659 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
662 Classification classificationFilter
= null;
663 if(classificationUuid
!= null){
665 classificationFilter
= classificationDao
.load(classificationUuid
);
666 } catch (NullPointerException e
){
667 //TODO not sure, why an NPE should be thrown in the above load method
668 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
670 if(classificationFilter
== null){
671 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
675 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
677 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
678 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
686 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
687 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
689 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
690 relatedTaxa
.remove(taxon
);
691 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
696 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
697 * <code>taxon</code> supplied as parameter.
700 * @param includeRelationships
702 * @param maxDepth can be <code>null</code> for infinite depth
705 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
706 boolean includeUnpublished
, Integer maxDepth
) {
712 if(includeRelationships
.isEmpty()){
716 if(maxDepth
!= null) {
719 if(logger
.isDebugEnabled()){
720 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
722 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
723 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
724 for (TaxonRelationship taxRel
: taxonRelationships
) {
727 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
730 // filter by includeRelationships
731 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
732 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
733 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
734 if(logger
.isDebugEnabled()){
735 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
737 taxa
.add(taxRel
.getToTaxon());
738 if(maxDepth
== null || maxDepth
> 0) {
739 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
742 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
743 taxa
.add(taxRel
.getFromTaxon());
744 if(logger
.isDebugEnabled()){
745 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
747 if(maxDepth
== null || maxDepth
> 0) {
748 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
758 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
759 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
761 List
<Synonym
> results
= new ArrayList
<>();
762 if(numberOfResults
> 0) { // no point checking again
763 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
766 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
770 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
771 List
<List
<Synonym
>> result
= new ArrayList
<>();
772 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
773 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
776 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
779 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
780 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
781 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
788 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
789 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
790 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
792 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
796 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
797 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
798 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
799 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
800 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
801 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
803 return heterotypicSynonymyGroups
;
807 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
809 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
810 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
811 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(),
812 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
813 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
815 return new ArrayList
<>();
820 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
822 @SuppressWarnings("rawtypes")
823 List
<IdentifiableEntity
> results
= new ArrayList
<>();
824 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
825 List
<TaxonBase
> taxa
= null;
828 long numberTaxaResults
= 0L;
830 List
<String
> propertyPath
= new ArrayList
<>();
831 if(configurator
.getTaxonPropertyPath() != null){
832 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
835 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
836 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
838 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
839 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
840 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
841 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
844 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
845 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
846 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
847 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
848 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
849 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
853 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
856 results
.addAll(taxa
);
859 numberOfResults
+= numberTaxaResults
;
861 // Names without taxa
862 if (configurator
.isDoNamesWithoutTaxa()) {
863 int numberNameResults
= 0;
865 List
<TaxonName
> names
=
866 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
867 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
868 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
869 if (names
.size() > 0) {
870 for (TaxonName taxonName
: names
) {
871 if (taxonName
.getTaxonBases().size() == 0) {
872 results
.add(taxonName
);
876 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
877 numberOfResults
+= numberNameResults
;
881 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
884 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
885 return dao
.getUuidAndTitleCache(limit
, pattern
);
889 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
890 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
894 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
895 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
896 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
899 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
901 // logger.setLevel(Level.TRACE);
902 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
904 logger
.trace("listMedia() - START");
906 Set
<Taxon
> taxa
= new HashSet
<>();
907 List
<Media
> taxonMedia
= new ArrayList
<>();
908 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
910 if (limitToGalleries
== null) {
911 limitToGalleries
= false;
914 // --- resolve related taxa
915 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
916 logger
.trace("listMedia() - resolve related taxa");
917 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
920 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
922 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
923 logger
.trace("listMedia() - includeTaxonDescriptions");
924 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
925 // --- TaxonDescriptions
926 for (Taxon t
: taxa
) {
927 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
929 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
930 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
931 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
932 for (Media media
: element
.getMedia()) {
933 if(taxonDescription
.isImageGallery()){
934 taxonMedia
.add(media
);
937 nonImageGalleryImages
.add(media
);
943 //put images from image gallery first (#3242)
944 taxonMedia
.addAll(nonImageGalleryImages
);
948 if(includeOccurrences
!= null && includeOccurrences
) {
949 logger
.trace("listMedia() - includeOccurrences");
950 @SuppressWarnings("rawtypes")
951 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
953 for (Taxon t
: taxa
) {
954 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
956 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
958 // direct media removed from specimen #3597
959 // taxonMedia.addAll(occurrence.getMedia());
961 // SpecimenDescriptions
962 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
963 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
964 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
965 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
966 for (DescriptionElementBase element
: elements
) {
967 for (Media media
: element
.getMedia()) {
968 taxonMedia
.add(media
);
974 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
975 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
977 //TODO why may collections have media attached? #
978 if (derivedUnit
.getCollection() != null){
979 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
983 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
987 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
988 logger
.trace("listMedia() - includeTaxonNameDescriptions");
989 // --- TaxonNameDescription
990 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
991 for (Taxon t
: taxa
) {
992 nameDescriptions
.addAll(t
.getName().getDescriptions());
994 for(TaxonNameDescription nameDescription
: nameDescriptions
){
995 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
996 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
997 for (DescriptionElementBase element
: elements
) {
998 for (Media media
: element
.getMedia()) {
999 taxonMedia
.add(media
);
1006 logger
.trace("listMedia() - initialize");
1007 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
1009 logger
.trace("listMedia() - END");
1015 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
1016 return this.dao
.loadList(listOfIDs
, null, null);
1020 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
1021 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
1025 public long countSynonyms(boolean onlyAttachedToTaxon
){
1026 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
1030 @Transactional(readOnly
=false)
1031 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
1033 if (config
== null){
1034 config
= new TaxonDeletionConfigurator();
1036 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
1037 DeleteResult result
= new DeleteResult();
1040 result
.addException(new Exception ("The taxon was already deleted."));
1043 taxon
= HibernateProxyHelper
.deproxy(taxon
);
1044 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
1045 config
.setClassificationUuid(classificationUuid
);
1046 result
= isDeletable(taxonUUID
, config
);
1049 // --- DeleteSynonymRelations
1050 if (config
.isDeleteSynonymRelations()){
1051 boolean removeSynonymNameFromHomotypicalGroup
= false;
1052 // use tmp Set to avoid concurrent modification
1053 Set
<Synonym
> synsToDelete
= new HashSet
<>();
1054 synsToDelete
.addAll(taxon
.getSynonyms());
1055 for (Synonym synonym
: synsToDelete
){
1056 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
1058 // --- DeleteSynonymsIfPossible
1059 if (config
.isDeleteSynonymsIfPossible()){
1061 boolean newHomotypicGroupIfNeeded
= true;
1062 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
1063 result
.includeResult(deleteSynonym(synonym
, synConfig
));
1068 // --- DeleteTaxonRelationships
1069 if (! config
.isDeleteTaxonRelationships()){
1070 if (taxon
.getTaxonRelations().size() > 0){
1072 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1073 "Remove taxon from all relations to other taxa prior to deletion."));
1076 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
1077 configRelTaxon
.setDeleteTaxonNodes(false);
1078 configRelTaxon
.setDeleteConceptRelationships(true);
1080 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
1081 if (config
.isDeleteMisappliedNames()
1082 && taxRel
.getType().isMisappliedName()
1083 && taxon
.equals(taxRel
.getToTaxon())){
1084 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
1085 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
1087 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
1088 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1089 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
1090 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1093 taxon
.removeTaxonRelation(taxRel
);
1098 if (config
.isDeleteDescriptions()){
1099 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
1100 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
1101 for (TaxonDescription desc
: descriptions
){
1102 //TODO use description delete configurator ?
1103 //FIXME check if description is ALWAYS deletable
1104 if (desc
.getDescribedSpecimenOrObservation() != null){
1106 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1107 " which also describes specimens or observations"));
1110 removeDescriptions
.add(desc
);
1113 for (TaxonDescription desc
: removeDescriptions
){
1114 taxon
.removeDescription(desc
);
1115 descriptionService
.delete(desc
);
1122 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1123 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1125 if (taxon
.getTaxonNodes().size() != 0){
1126 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1127 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1128 TaxonNode node
= null;
1129 boolean deleteChildren
;
1130 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1131 deleteChildren
= true;
1133 deleteChildren
= false;
1135 boolean success
= true;
1136 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1137 while (iterator
.hasNext()){
1138 node
= iterator
.next();
1139 if (node
.getClassification().equals(classification
)){
1145 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1146 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1147 nodeService
.delete(node
);
1148 result
.addDeletedObject(node
);
1151 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1153 } else if (config
.isDeleteInAllClassifications()){
1154 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1155 nodesList
.addAll(taxon
.getTaxonNodes());
1156 for (ITaxonTreeNode treeNode
: nodesList
){
1157 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1158 if(!deleteChildren
){
1159 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1160 for (Object childNode
: childNodes
){
1161 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1162 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1166 config
.getTaxonNodeConfig().setDeleteElement(false);
1167 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1168 if (!resultNodes
.isOk()){
1169 result
.addExceptions(resultNodes
.getExceptions());
1170 result
.setStatus(resultNodes
.getStatus());
1172 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1177 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1181 TaxonName name
= taxon
.getName();
1182 taxon
.setName(null);
1183 this.saveOrUpdate(taxon
);
1185 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1188 result
.addDeletedObject(taxon
);
1189 }catch(Exception e
){
1190 result
.addException(e
);
1195 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1199 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1200 DeleteResult nameResult
= new DeleteResult();
1201 //remove name if possible (and required)
1203 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1205 if (nameResult
.isError() || nameResult
.isAbort()){
1206 result
.addRelatedObject(name
);
1207 result
.addExceptions(nameResult
.getExceptions());
1209 result
.includeResult(nameResult
);
1218 @Transactional(readOnly
= false)
1219 public DeleteResult
delete(UUID synUUID
){
1220 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1221 return this.deleteSynonym(syn
, null);
1225 @Transactional(readOnly
= false)
1226 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1227 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1231 @Transactional(readOnly
= false)
1232 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1233 DeleteResult result
= new DeleteResult();
1234 if (synonym
== null){
1236 result
.addException(new Exception("The synonym was already deleted."));
1240 if (config
== null){
1241 config
= new SynonymDeletionConfigurator();
1244 result
= isDeletable(synonym
.getUuid(), config
);
1248 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1251 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1253 if (accTaxon
!= null){
1254 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1255 accTaxon
.removeSynonym(synonym
, false);
1256 this.saveOrUpdate(accTaxon
);
1257 result
.addUpdatedObject(accTaxon
);
1259 this.saveOrUpdate(synonym
);
1263 TaxonName name
= synonym
.getName();
1264 synonym
.setName(null);
1266 dao
.delete(synonym
);
1267 result
.addDeletedObject(synonym
);
1269 //remove name if possible (and required)
1270 if (name
!= null && config
.isDeleteNameIfPossible()){
1272 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1273 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1274 result
.addExceptions(nameDeleteResult
.getExceptions());
1275 result
.addRelatedObject(name
);
1277 result
.addDeletedObject(name
);
1285 public Map
<String
, Map
<UUID
,Set
<TaxonName
>>> findIdenticalTaxonNames(List
<UUID
> sourceRefUuids
, List
<String
> propertyPaths
) {
1286 return this.dao
.findIdenticalNames(sourceRefUuids
, propertyPaths
);
1290 public Taxon
findBestMatchingTaxon(String taxonName
) {
1291 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1292 config
.setTaxonNameTitle(taxonName
);
1293 return findBestMatchingTaxon(config
);
1297 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1299 Taxon bestCandidate
= null;
1301 // 1. search for accepted taxa
1302 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1303 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1304 boolean bestCandidateMatchesSecUuid
= false;
1305 boolean bestCandidateIsInClassification
= false;
1306 int countEqualCandidates
= 0;
1307 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1308 if(taxonBaseCandidate
instanceof Taxon
){
1309 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1310 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1311 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1313 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1314 bestCandidate
= newCanditate
;
1315 countEqualCandidates
= 1;
1316 bestCandidateMatchesSecUuid
= true;
1320 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1321 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1323 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1324 bestCandidate
= newCanditate
;
1325 countEqualCandidates
= 1;
1326 bestCandidateIsInClassification
= true;
1329 if (bestCandidate
== null){
1330 bestCandidate
= newCanditate
;
1331 countEqualCandidates
= 1;
1334 }else{ //not Taxon.class
1337 countEqualCandidates
++;
1340 if (bestCandidate
!= null){
1341 if(countEqualCandidates
> 1){
1342 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1343 return bestCandidate
;
1345 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1346 return bestCandidate
;
1350 // 2. search for synonyms
1351 if (config
.isIncludeSynonyms()){
1352 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1353 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1354 for(TaxonBase taxonBase
: synonymList
){
1355 if(taxonBase
instanceof Synonym
){
1356 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1357 bestCandidate
= synonym
.getAcceptedTaxon();
1358 if(bestCandidate
!= null){
1359 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1360 return bestCandidate
;
1362 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1367 } catch (Exception e
){
1369 e
.printStackTrace();
1372 return bestCandidate
;
1375 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1376 UUID configClassificationUuid
= config
.getClassificationUuid();
1377 if (configClassificationUuid
== null){
1380 for (TaxonNode node
: taxon
.getTaxonNodes()){
1381 UUID classUuid
= node
.getClassification().getUuid();
1382 if (configClassificationUuid
.equals(classUuid
)){
1389 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1390 UUID configSecUuid
= config
.getSecUuid();
1391 if (configSecUuid
== null){
1394 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1395 return configSecUuid
.equals(taxonSecUuid
);
1399 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1400 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1401 if(! synonymList
.isEmpty()){
1402 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1403 if(synonymList
.size() == 1){
1404 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1407 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1415 @Transactional(readOnly
= false)
1416 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1418 boolean moveHomotypicGroup
,
1419 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1420 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1422 oldSynonym
.getSec(),
1423 oldSynonym
.getSecMicroReference(),
1428 @Transactional(readOnly
= false)
1429 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1431 boolean moveHomotypicGroup
,
1432 SynonymType newSynonymType
,
1433 Reference newSecundum
,
1434 String newSecundumDetail
,
1435 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1437 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1438 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1439 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1440 TaxonName synonymName
= synonym
.getName();
1441 TaxonName fromTaxonName
= oldTaxon
.getName();
1442 //set default relationship type
1443 if (newSynonymType
== null){
1444 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1446 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1448 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1449 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1450 boolean isSingleInGroup
= !(hgSize
> 1);
1452 if (! isSingleInGroup
){
1453 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1454 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1455 if (isHomotypicToAccepted
){
1456 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.";
1457 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1458 message
= String
.format(message
, homotypicRelatives
);
1459 throw new HomotypicalGroupChangeException(message
);
1461 if (! moveHomotypicGroup
){
1462 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.";
1463 throw new HomotypicalGroupChangeException(message
);
1466 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1468 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1470 UpdateResult result
= new UpdateResult();
1471 //move all synonyms to new taxon
1472 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1473 for (Synonym synRelation
: homotypicSynonyms
){
1475 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1476 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1477 oldTaxon
.removeSynonym(synRelation
, false);
1478 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1480 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1481 synRelation
.setSec(newSecundum
);
1483 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1484 synRelation
.setSecMicroReference(newSecundumDetail
);
1487 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1488 if (!synRelation
.equals(oldSynonym
)){
1493 result
.addUpdatedObject(oldTaxon
);
1494 result
.addUpdatedObject(newTaxon
);
1495 saveOrUpdate(oldTaxon
);
1496 saveOrUpdate(newTaxon
);
1502 public <T
extends TaxonBase
>List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1504 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1508 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1509 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1510 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1511 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1512 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1514 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1515 null, includeUnpublished
, languages
, highlightFragments
, null);
1517 // --- execute search
1518 TopGroups
<BytesRef
> topDocsResultSet
;
1520 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1521 } catch (ParseException e
) {
1522 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1523 luceneParseException
.setStackTrace(e
.getStackTrace());
1524 throw luceneParseException
;
1527 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1528 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1530 // --- initialize taxa, thighlight matches ....
1531 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1532 @SuppressWarnings("rawtypes")
1533 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1534 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1536 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1537 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1540 @Transactional(readOnly
= true)
1542 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
) {
1543 long numberOfResults
= dao
.countByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
);
1545 long numberOfResults_doubtful
= dao
.countByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
);
1546 List
<S
> results
= new ArrayList
<>();
1547 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1549 results
= dao
.findByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1550 results
.addAll(dao
.findByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1552 Collections
.sort(results
, new TaxonComparator());
1553 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1556 @Transactional(readOnly
= true)
1558 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
) {
1559 long numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
1560 //check whether there are doubtful taxa matching
1561 long numberOfResults_doubtful
= dao
.countByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
);
1562 List
<S
> results
= new ArrayList
<>();
1563 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1564 if (numberOfResults
> 0){
1565 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1567 results
= new ArrayList
<>();
1569 if (numberOfResults_doubtful
> 0){
1570 results
.addAll(dao
.findByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1573 Collections
.sort(results
, new TaxonComparator());
1574 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1578 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1579 Classification classification
, TaxonNode subtree
,
1580 Integer pageSize
, Integer pageNumber
,
1581 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1583 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
1585 // --- execute search
1586 TopGroups
<BytesRef
> topDocsResultSet
;
1588 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1589 } catch (ParseException e
) {
1590 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1591 luceneParseException
.setStackTrace(e
.getStackTrace());
1592 throw luceneParseException
;
1595 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1596 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1598 // --- initialize taxa, thighlight matches ....
1599 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1600 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1601 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1603 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1604 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1609 * @param queryString
1610 * @param classification
1611 * @param includeUnpublished
1613 * @param highlightFragments
1614 * @param sortFields TODO
1615 * @param directorySelectClass
1618 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1619 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1620 boolean highlightFragments
, SortField
[] sortFields
) {
1622 Builder finalQueryBuilder
= new Builder();
1623 Builder textQueryBuilder
= new Builder();
1625 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1626 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1628 if(sortFields
== null){
1629 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1631 luceneSearch
.setSortFields(sortFields
);
1633 // ---- search criteria
1634 luceneSearch
.setCdmTypRestriction(clazz
);
1636 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1637 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1638 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1640 if(className
!= null){
1641 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1644 BooleanQuery textQuery
= textQueryBuilder
.build();
1645 if(textQuery
.clauses().size() > 0) {
1646 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1649 if(classification
!= null){
1650 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1652 if(subtree
!= null){
1653 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1655 if(!includeUnpublished
) {
1656 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1657 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1658 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1661 luceneSearch
.setQuery(finalQueryBuilder
.build());
1663 if(highlightFragments
){
1664 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1666 return luceneSearch
;
1670 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1671 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1672 * drawback of requiring to do the join an indexing time.
1673 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1675 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1677 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1678 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1680 * @param queryString
1681 * @param classification
1683 * @param highlightFragments
1684 * @param sortFields TODO
1687 * @throws IOException
1689 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1690 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1691 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1694 String queryTermField
;
1695 String toField
= "id"; // TaxonBase.uuid
1696 String publishField
;
1697 String publishFieldInvers
;
1699 if(edge
.isBidirectional()){
1700 throw new RuntimeException("Bidirectional joining not supported!");
1703 fromField
= "relatedFrom.id";
1704 queryTermField
= "relatedFrom.titleCache";
1705 publishField
= "relatedFrom.publish";
1706 publishFieldInvers
= "relatedTo.publish";
1707 } else if(edge
.isInvers()) {
1708 fromField
= "relatedTo.id";
1709 queryTermField
= "relatedTo.titleCache";
1710 publishField
= "relatedTo.publish";
1711 publishFieldInvers
= "relatedFrom.publish";
1713 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1716 Builder finalQueryBuilder
= new Builder();
1718 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1719 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1721 Builder joinFromQueryBuilder
= new Builder();
1722 if(!StringUtils
.isEmpty(queryString
)){
1723 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1725 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1726 if(!includeUnpublished
){
1727 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1728 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1731 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1733 if(sortFields
== null){
1734 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1736 luceneSearch
.setSortFields(sortFields
);
1738 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1740 if(classification
!= null){
1741 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1743 if(subtree
!= null){
1744 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1747 luceneSearch
.setQuery(finalQueryBuilder
.build());
1749 if(highlightFragments
){
1750 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1752 return luceneSearch
;
1756 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1757 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1758 Classification classification
, TaxonNode subtree
,
1759 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1760 boolean highlightFragments
, Integer pageSize
,
1761 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1762 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1764 // FIXME: allow taxonomic ordering
1765 // 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";
1766 // this require building a special sort column by a special classBridge
1767 if(highlightFragments
){
1768 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1769 "currently not fully supported by this method and thus " +
1770 "may not work with common names and misapplied names.");
1773 // convert sets to lists
1774 List
<NamedArea
> namedAreaList
= null;
1775 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1776 if(namedAreas
!= null){
1777 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1778 namedAreaList
.addAll(namedAreas
);
1780 if(distributionStatus
!= null){
1781 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1782 distributionStatusList
.addAll(distributionStatus
);
1785 // set default if parameter is null
1786 if(searchModes
== null){
1787 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1790 // set sort order and thus override any sort orders which may have been
1791 // defined by prepare*Search methods
1792 if(orderHints
== null){
1793 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1795 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1797 for(OrderHint oh
: orderHints
){
1798 sortFields
[i
++] = oh
.toSortField();
1800 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1801 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1803 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1805 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1806 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1809 ======== filtering by distribution , HOWTO ========
1811 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1812 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1813 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1814 which will be put into a FilteredQuersy in the end ?
1817 3. how does it work in spatial?
1819 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1820 - http://www.infoq.com/articles/LuceneSpatialSupport
1821 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1822 ------------------------------------------------------------------------
1825 A) use a separate distribution filter per index sub-query/search:
1826 - byTaxonSyonym (query TaxaonBase):
1827 use a join area filter (Distribution -> TaxonBase)
1828 - byCommonName (query DescriptionElementBase): use an area filter on
1829 DescriptionElementBase !!! PROBLEM !!!
1830 This cannot work since the distributions are different entities than the
1831 common names and thus these are different lucene documents.
1832 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1833 use a join area filter (Distribution -> TaxonBase)
1835 B) use a common distribution filter for all index sub-query/searches:
1836 - use a common join area filter (Distribution -> TaxonBase)
1837 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1838 PROBLEM in this case: we are losing the fragment highlighting for the
1839 common names, since the returned documents are always TaxonBases
1842 /* The QueryFactory for creating filter queries on Distributions should
1843 * The query factory used for the common names query cannot be reused
1844 * for this case, since we want to only record the text fields which are
1845 * actually used in the primary query
1847 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1849 Builder multiIndexByAreaFilterBuilder
= new Builder();
1850 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1852 // search for taxa or synonyms
1853 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1854 @SuppressWarnings("rawtypes")
1855 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1856 String className
= null;
1857 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1858 taxonBaseSubclass
= Taxon
.class;
1859 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1860 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1862 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1863 queryString
, classification
, subtree
, className
,
1864 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1865 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1866 /* A) does not work!!!!
1867 if(addDistributionFilter){
1868 // in this case we need a filter which uses a join query
1869 // to get the TaxonBase documents for the DescriptionElementBase documents
1870 // which are matching the areas in question
1871 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1873 distributionStatusList,
1874 distributionFilterQueryFactory
1876 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1879 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1880 // add additional area filter for synonyms
1881 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1882 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1884 //TODO replace by createByDistributionJoinQuery
1885 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1886 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1887 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1892 // search by CommonTaxonName
1893 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1895 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1896 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1897 CommonTaxonName
.class,
1898 "inDescription.taxon.id",
1900 QueryFactory
.addTypeRestriction(
1901 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
1902 , CommonTaxonName
.class
1903 ).build(), "id", null, ScoreMode
.Max
);
1904 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1905 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1906 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1907 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1908 Builder builder
= new BooleanQuery
.Builder();
1909 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1910 if(!includeUnpublished
) {
1911 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1912 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1914 byCommonNameSearch
.setQuery(builder
.build());
1915 byCommonNameSearch
.setSortFields(sortFields
);
1917 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1919 luceneSearches
.add(byCommonNameSearch
);
1921 /* A) does not work!!!!
1923 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1924 queryString, classification, null, languages, highlightFragments)
1926 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1927 if(addDistributionFilter){
1928 // in this case we are able to use DescriptionElementBase documents
1929 // which are matching the areas in question directly
1930 BooleanQuery byDistributionQuery = createByDistributionQuery(
1932 distributionStatusList,
1933 distributionFilterQueryFactory
1935 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1940 // search by misapplied names
1941 //TODO merge with pro parte synonym search once #7487 is fixed
1942 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1944 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1945 // which allows doing query time joins
1946 // finds the misapplied name (Taxon B) which is an misapplication for
1947 // a related Taxon A.
1949 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1950 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1951 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1953 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1954 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1957 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1958 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1959 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1960 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1962 if(addDistributionFilter
){
1963 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1966 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
1967 * Maybe this is a bug in java itself.
1969 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1972 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1974 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1975 * will execute as expected:
1977 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1978 * String toField = "relation." + misappliedNameForUuid +".to.id";
1980 * Comparing both strings by the String.equals method returns true, so both String are identical.
1982 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1983 * 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)
1984 * The bug is persistent after a reboot of the development computer.
1986 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1987 // String toField = "relation." + misappliedNameForUuid +".to.id";
1988 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1989 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1990 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1992 //TODO replace by createByDistributionJoinQuery
1993 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1994 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1995 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1997 // debug code for bug described above
1998 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1999 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2000 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2002 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2006 // search by pro parte synonyms
2007 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
2008 //TODO merge with misapplied name search once #7487 is fixed
2009 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2010 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
2012 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2013 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2014 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2015 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2017 if(addDistributionFilter
){
2018 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2019 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
2020 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2021 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2022 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2023 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2025 }//end pro parte synonyms
2027 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
2028 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
2030 if(addDistributionFilter
){
2033 // in this case we need a filter which uses a join query
2034 // to get the TaxonBase documents for the DescriptionElementBase documents
2035 // which are matching the areas in question
2037 // for doTaxa, doByCommonName
2038 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
2040 distributionStatusList
,
2041 distributionFilterQueryFactory
,
2044 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2047 if (addDistributionFilter
){
2048 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
2052 // --- execute search
2053 TopGroups
<BytesRef
> topDocsResultSet
;
2055 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2056 } catch (ParseException e
) {
2057 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2058 luceneParseException
.setStackTrace(e
.getStackTrace());
2059 throw luceneParseException
;
2062 // --- initialize taxa, highlight matches ....
2063 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2066 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2067 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2069 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
2070 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2074 * @param namedAreaList at least one area must be in the list
2075 * @param distributionStatusList optional
2076 * @param toType toType
2077 * Optional parameter. Only used for debugging to print the toType documents
2078 * @param asFilter TODO
2080 * @throws IOException
2082 protected Query
createByDistributionJoinQuery(
2083 List
<NamedArea
> namedAreaList
,
2084 List
<PresenceAbsenceTerm
> distributionStatusList
,
2085 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
2086 ) throws IOException
{
2088 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2089 String toField
= "id"; // id in toType usually this is the TaxonBase index
2091 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
2093 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
2095 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
2097 return taxonAreaJoinQuery
;
2101 * @param namedAreaList
2102 * @param distributionStatusList
2103 * @param queryFactory
2106 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
2107 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
2108 Builder areaQueryBuilder
= new Builder();
2109 // area field from Distribution
2110 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2112 // status field from Distribution
2113 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2114 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2117 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2118 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2123 * This method has been primarily created for testing the area join query but might
2124 * also be useful in other situations
2126 * @param namedAreaList
2127 * @param distributionStatusList
2128 * @param classification
2129 * @param highlightFragments
2131 * @throws IOException
2133 protected LuceneSearch
prepareByDistributionSearch(
2134 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2135 Classification classification
, TaxonNode subtree
) throws IOException
{
2137 Builder finalQueryBuilder
= new Builder();
2139 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2141 // FIXME is this query factory using the wrong type?
2142 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2144 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2145 luceneSearch
.setSortFields(sortFields
);
2148 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2150 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2152 if(classification
!= null){
2153 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
2155 if(subtree
!= null){
2156 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
2158 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2159 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2160 luceneSearch
.setQuery(finalQuery
);
2162 return luceneSearch
;
2166 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2167 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2168 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2169 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2171 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2173 // --- execute search
2174 TopGroups
<BytesRef
> topDocsResultSet
;
2176 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2177 } catch (ParseException e
) {
2178 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2179 luceneParseException
.setStackTrace(e
.getStackTrace());
2180 throw luceneParseException
;
2183 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2184 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2186 // --- initialize taxa, highlight matches ....
2187 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2188 @SuppressWarnings("rawtypes")
2189 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2190 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2192 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2193 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2197 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2198 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2199 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2201 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2202 classification
, subtree
,
2203 null, languages
, highlightFragments
);
2204 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2205 includeUnpublished
, languages
, highlightFragments
, null);
2207 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2209 // --- execute search
2210 TopGroups
<BytesRef
> topDocsResultSet
;
2212 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2213 } catch (ParseException e
) {
2214 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2215 luceneParseException
.setStackTrace(e
.getStackTrace());
2216 throw luceneParseException
;
2219 // --- initialize taxa, highlight matches ....
2220 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2222 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2223 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2224 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2226 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2227 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2229 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2230 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2235 * @param queryString
2236 * @param classification
2239 * @param highlightFragments
2240 * @param directorySelectClass
2243 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2244 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2245 List
<Language
> languages
, boolean highlightFragments
) {
2247 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2248 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2250 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2252 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2253 languages
, descriptionElementQueryFactory
);
2255 luceneSearch
.setSortFields(sortFields
);
2256 luceneSearch
.setCdmTypRestriction(clazz
);
2257 luceneSearch
.setQuery(finalQuery
);
2258 if(highlightFragments
){
2259 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2262 return luceneSearch
;
2266 * @param queryString
2267 * @param classification
2270 * @param descriptionElementQueryFactory
2273 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2274 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2275 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2277 Builder finalQueryBuilder
= new Builder();
2278 Builder textQueryBuilder
= new Builder();
2280 if(!StringUtils
.isEmpty(queryString
)){
2282 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2285 Builder nameQueryBuilder
= new Builder();
2286 if(languages
== null || languages
.size() == 0){
2287 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2289 Builder languageSubQueryBuilder
= new Builder();
2290 for(Language lang
: languages
){
2291 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2293 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2294 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2296 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2299 // text field from TextData
2300 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2302 // --- TermBase fields - by representation ----
2303 // state field from CategoricalData
2304 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2306 // state field from CategoricalData
2307 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2309 // area field from Distribution
2310 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2312 // status field from Distribution
2313 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2315 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2318 // --- classification ----
2321 if(classification
!= null){
2322 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2324 if(subtree
!= null){
2325 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2328 // --- IdentifieableEntity fields - by uuid
2329 if(features
!= null && features
.size() > 0 ){
2330 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2333 // the description must be associated with a taxon
2334 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2336 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2337 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2342 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2344 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2345 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2347 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2348 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2350 UUID nameUuid
= taxon
.getName().getUuid();
2351 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2352 String epithetOfTaxon
= null;
2353 String infragenericEpithetOfTaxon
= null;
2354 String infraspecificEpithetOfTaxon
= null;
2355 if (taxonName
.isSpecies()){
2356 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2357 } else if (taxonName
.isInfraGeneric()){
2358 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2359 } else if (taxonName
.isInfraSpecific()){
2360 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2362 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2363 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2364 List
<String
> taxonNames
= new ArrayList
<>();
2366 for (TaxonNode node
: nodes
){
2367 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2368 // List<String> synonymsEpithet = new ArrayList<>();
2370 if (node
.getClassification().equals(classification
)){
2371 if (!node
.isTopmostNode()){
2372 TaxonNode parent
= node
.getParent();
2373 parent
= CdmBase
.deproxy(parent
);
2374 TaxonName parentName
= parent
.getTaxon().getName();
2375 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2376 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2378 //create inferred synonyms for species, subspecies
2379 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2381 Synonym inferredEpithet
= null;
2382 Synonym inferredGenus
= null;
2383 Synonym potentialCombination
= null;
2385 List
<String
> propertyPaths
= new ArrayList
<>();
2386 propertyPaths
.add("synonym");
2387 propertyPaths
.add("synonym.name");
2388 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2389 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2391 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2392 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2393 null, null,orderHintsSynonyms
,propertyPaths
);
2395 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2396 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2397 if (doWithMisappliedNames
){
2398 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2399 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2400 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2401 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2402 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2403 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2406 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2407 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2409 inferredEpithet
= createInferredEpithets(taxon
,
2410 zooHashMap
, taxonName
, epithetOfTaxon
,
2411 infragenericEpithetOfTaxon
,
2412 infraspecificEpithetOfTaxon
,
2413 taxonNames
, parentName
,
2414 synonymRelationOfParent
);
2416 inferredSynonyms
.add(inferredEpithet
);
2417 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2418 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2421 if (doWithMisappliedNames
){
2423 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2424 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2426 inferredEpithet
= createInferredEpithets(taxon
,
2427 zooHashMap
, taxonName
, epithetOfTaxon
,
2428 infragenericEpithetOfTaxon
,
2429 infraspecificEpithetOfTaxon
,
2430 taxonNames
, parentName
,
2433 inferredSynonyms
.add(inferredEpithet
);
2434 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2435 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2439 if (!taxonNames
.isEmpty()){
2440 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2441 if (!synNotInCDM
.isEmpty()){
2442 inferredSynonymsToBeRemoved
.clear();
2444 for (Synonym syn
:inferredSynonyms
){
2445 IZoologicalName name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2446 if (!synNotInCDM
.contains(name
.getNameCache())){
2447 inferredSynonymsToBeRemoved
.add(syn
);
2451 // Remove identified Synonyms from inferredSynonyms
2452 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2453 inferredSynonyms
.remove(synonym
);
2458 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2460 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2462 inferredGenus
= createInferredGenus(taxon
,
2463 zooHashMap
, taxonName
, epithetOfTaxon
,
2464 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2466 inferredSynonyms
.add(inferredGenus
);
2467 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2468 taxonNames
.add(inferredGenus
.getName().getNameCache());
2471 if (doWithMisappliedNames
){
2473 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2474 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2475 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2477 inferredSynonyms
.add(inferredGenus
);
2478 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2479 taxonNames
.add(inferredGenus
.getName().getNameCache());
2484 if (!taxonNames
.isEmpty()){
2485 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2486 IZoologicalName name
;
2487 if (!synNotInCDM
.isEmpty()){
2488 inferredSynonymsToBeRemoved
.clear();
2490 for (Synonym syn
:inferredSynonyms
){
2491 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2492 if (!synNotInCDM
.contains(name
.getNameCache())){
2493 inferredSynonymsToBeRemoved
.add(syn
);
2497 // Remove identified Synonyms from inferredSynonyms
2498 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2499 inferredSynonyms
.remove(synonym
);
2504 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2506 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2507 //for all synonyms of the parent...
2508 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2510 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2512 synName
= synonymRelationOfParent
.getName();
2514 // Set the sourceReference
2515 sourceReference
= synonymRelationOfParent
.getSec();
2517 // Determine the idInSource
2518 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2520 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2521 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2522 String synParentInfragenericName
= null;
2523 String synParentSpecificEpithet
= null;
2525 if (parentSynZooName
.isInfraGeneric()){
2526 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2528 if (parentSynZooName
.isSpecies()){
2529 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2532 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2533 synonymsGenus.put(synGenusName, idInSource);
2536 //for all synonyms of the taxon
2538 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2540 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2541 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2543 synParentInfragenericName
,
2544 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2546 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2547 inferredSynonyms
.add(potentialCombination
);
2548 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2549 taxonNames
.add(potentialCombination
.getName().getNameCache());
2553 if (doWithMisappliedNames
){
2555 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2557 TaxonName misappliedParentName
;
2559 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2560 misappliedParentName
= misappliedParent
.getName();
2562 HibernateProxyHelper
.deproxy(misappliedParent
);
2564 // Set the sourceReference
2565 sourceReference
= misappliedParent
.getSec();
2567 // Determine the idInSource
2568 String idInSourceParent
= getIdInSource(misappliedParent
);
2570 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.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 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2583 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2584 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2585 potentialCombination
= createPotentialCombination(
2586 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2588 synParentInfragenericName
,
2589 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2591 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2592 inferredSynonyms
.add(potentialCombination
);
2593 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2594 taxonNames
.add(potentialCombination
.getName().getNameCache());
2599 if (!taxonNames
.isEmpty()){
2600 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2601 IZoologicalName name
;
2602 if (!synNotInCDM
.isEmpty()){
2603 inferredSynonymsToBeRemoved
.clear();
2604 for (Synonym syn
:inferredSynonyms
){
2606 name
= syn
.getName();
2607 }catch (ClassCastException e
){
2608 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2610 if (!synNotInCDM
.contains(name
.getNameCache())){
2611 inferredSynonymsToBeRemoved
.add(syn
);
2614 // Remove identified Synonyms from inferredSynonyms
2615 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2616 inferredSynonyms
.remove(synonym
);
2622 logger
.info("The synonym type is not defined.");
2623 return inferredSynonyms
;
2629 return inferredSynonyms
;
2632 private Synonym
createPotentialCombination(String idInSourceParent
,
2633 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2634 String synParentInfragenericName
, String synParentSpecificEpithet
,
2635 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2636 Synonym potentialCombination
;
2637 Reference sourceReference
;
2638 IZoologicalName inferredSynName
;
2639 HibernateProxyHelper
.deproxy(syn
);
2641 // Set sourceReference
2642 sourceReference
= syn
.getSec();
2643 if (sourceReference
== null){
2644 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2646 if (!parentSynZooName
.getTaxa().isEmpty()){
2647 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2649 sourceReference
= taxon
.getSec();
2652 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2654 String synTaxonInfraSpecificName
= null;
2656 if (parentSynZooName
.isSpecies()){
2657 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2660 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2661 synonymsEpithet.add(epithetName);
2664 //create potential combinations...
2665 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2667 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2668 if (zooSynName
.isSpecies()){
2669 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2670 if (parentSynZooName
.isInfraGeneric()){
2671 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2674 if (zooSynName
.isInfraSpecific()){
2675 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2676 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2678 if (parentSynZooName
.isInfraGeneric()){
2679 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2682 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2684 // Set the sourceReference
2685 potentialCombination
.setSec(sourceReference
);
2688 // Determine the idInSource
2689 String idInSourceSyn
= getIdInSource(syn
);
2691 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2692 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2693 inferredSynName
.addSource(originalSource
);
2694 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2695 potentialCombination
.addSource(originalSource
);
2698 return potentialCombination
;
2701 private Synonym
createInferredGenus(Taxon taxon
,
2702 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2703 String epithetOfTaxon
, String genusOfTaxon
,
2704 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2707 Synonym inferredGenus
;
2709 IZoologicalName inferredSynName
;
2710 synName
=syn
.getName();
2711 HibernateProxyHelper
.deproxy(syn
);
2713 // Determine the idInSource
2714 String idInSourceSyn
= getIdInSource(syn
);
2715 String idInSourceTaxon
= getIdInSource(taxon
);
2716 // Determine the sourceReference
2717 Reference sourceReference
= syn
.getSec();
2719 //logger.warn(sourceReference.getTitleCache());
2721 synName
= syn
.getName();
2722 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2723 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2724 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2725 synonymsEpithet.add(synSpeciesEpithetName);
2728 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2729 //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...
2731 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2732 if (zooParentName
.isInfraGeneric()){
2733 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2736 if (taxonName
.isSpecies()){
2737 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2739 if (taxonName
.isInfraSpecific()){
2740 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2741 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2744 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2746 // Set the sourceReference
2747 inferredGenus
.setSec(sourceReference
);
2749 // Add the original source
2750 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2751 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2752 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2753 inferredGenus
.addSource(originalSource
);
2755 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2756 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2757 inferredSynName
.addSource(originalSource
);
2758 originalSource
= null;
2761 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2762 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2763 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2764 inferredGenus
.addSource(originalSource
);
2766 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2767 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2768 inferredSynName
.addSource(originalSource
);
2769 originalSource
= null;
2772 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2774 return inferredGenus
;
2777 private Synonym
createInferredEpithets(Taxon taxon
,
2778 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2779 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2780 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2781 TaxonName parentName
, TaxonBase
<?
> syn
) {
2783 Synonym inferredEpithet
;
2785 IZoologicalName inferredSynName
;
2786 HibernateProxyHelper
.deproxy(syn
);
2788 // Determine the idInSource
2789 String idInSourceSyn
= getIdInSource(syn
);
2790 String idInSourceTaxon
= getIdInSource(taxon
);
2791 // Determine the sourceReference
2792 Reference sourceReference
= syn
.getSec();
2794 if (sourceReference
== null){
2795 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2796 sourceReference
= taxon
.getSec();
2799 synName
= syn
.getName();
2800 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2801 String synGenusName
= zooSynName
.getGenusOrUninomial();
2802 String synInfraGenericEpithet
= null;
2803 String synSpecificEpithet
= null;
2805 if (zooSynName
.getInfraGenericEpithet() != null){
2806 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2809 if (zooSynName
.isInfraSpecific()){
2810 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2813 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2814 synonymsGenus.put(synGenusName, idInSource);
2817 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2819 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2820 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2821 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2823 inferredSynName
.setGenusOrUninomial(synGenusName
);
2825 if (parentName
.isInfraGeneric()){
2826 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2828 if (taxonName
.isSpecies()){
2829 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2830 }else if (taxonName
.isInfraSpecific()){
2831 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2832 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2835 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2837 // Set the sourceReference
2838 inferredEpithet
.setSec(sourceReference
);
2840 /* Add the original source
2841 if (idInSource != null) {
2842 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2845 Reference citation = getCitation(syn);
2846 if (citation != null) {
2847 originalSource.setCitation(citation);
2848 inferredEpithet.addSource(originalSource);
2851 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2854 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2855 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2857 inferredEpithet
.addSource(originalSource
);
2859 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2860 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2862 inferredSynName
.addSource(originalSource
);
2864 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2866 return inferredEpithet
;
2870 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2871 * Very likely only useful for createInferredSynonyms().
2876 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2877 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2878 if (taxonName
== null) {
2879 taxonName
= zooHashMap
.get(uuid
);
2885 * Returns the idInSource for a given Synonym.
2888 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2889 String idInSource
= null;
2890 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2891 if (sources
.size() == 1) {
2892 IdentifiableSource source
= sources
.iterator().next();
2893 if (source
!= null) {
2894 idInSource
= source
.getIdInSource();
2896 } else if (sources
.size() > 1) {
2899 for (IdentifiableSource source
: sources
) {
2900 idInSource
+= source
.getIdInSource();
2901 if (count
< sources
.size()) {
2906 } else if (sources
.size() == 0){
2907 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2914 * Returns the citation for a given Synonym.
2917 private Reference
getCitation(Synonym syn
) {
2918 Reference citation
= null;
2919 Set
<IdentifiableSource
> sources
= syn
.getSources();
2920 if (sources
.size() == 1) {
2921 IdentifiableSource source
= sources
.iterator().next();
2922 if (source
!= null) {
2923 citation
= source
.getCitation();
2925 } else if (sources
.size() > 1) {
2926 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2933 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2934 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2936 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2937 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2938 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2940 return inferredSynonyms
;
2944 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2946 // TODO quickly implemented, create according dao !!!!
2947 Set
<TaxonNode
> nodes
= new HashSet
<>();
2948 Set
<Classification
> classifications
= new HashSet
<>();
2949 List
<Classification
> list
= new ArrayList
<>();
2951 if (taxonBase
== null) {
2955 taxonBase
= load(taxonBase
.getUuid());
2957 if (taxonBase
instanceof Taxon
) {
2958 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2960 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2962 nodes
.addAll(taxon
.getTaxonNodes());
2965 for (TaxonNode node
: nodes
) {
2966 classifications
.add(node
.getClassification());
2968 list
.addAll(classifications
);
2973 @Transactional(readOnly
= false)
2974 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2976 TaxonRelationshipType oldRelationshipType
,
2977 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2978 UpdateResult result
= new UpdateResult();
2979 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2980 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2981 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2983 result
.addUpdatedObject(fromTaxon
);
2984 result
.addUpdatedObject(toTaxon
);
2985 result
.addUpdatedObject(result
.getCdmEntity());
2991 @Transactional(readOnly
= false)
2992 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2993 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2995 UpdateResult result
= new UpdateResult();
2996 // Create new synonym using concept name
2997 TaxonName synonymName
= fromTaxon
.getName();
2999 // Remove concept relation from taxon
3000 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
3002 // Create a new synonym for the taxon
3004 if (synonymType
!= null
3005 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
3006 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
3007 toTaxon
.addHomotypicSynonym(synonym
);
3009 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
3012 this.saveOrUpdate(toTaxon
);
3013 //TODO: configurator and classification
3014 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
3015 config
.setDeleteNameIfPossible(false);
3016 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
3017 result
.setCdmEntity(synonym
);
3018 result
.addUpdatedObject(toTaxon
);
3019 result
.addUpdatedObject(synonym
);
3024 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
3025 DeleteResult result
= new DeleteResult();
3026 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
3027 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
3028 if (taxonBase
instanceof Taxon
){
3029 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
3030 List
<String
> propertyPaths
= new ArrayList
<>();
3031 propertyPaths
.add("taxonNodes");
3032 Taxon taxon
= (Taxon
)load(taxonBaseUuid
, propertyPaths
);
3034 result
= isDeletableForTaxon(references
, taxonConfig
);
3036 if (taxonConfig
.isDeleteNameIfPossible()){
3037 if (taxonBase
.getName() != null){
3038 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), taxonConfig
.getNameDeletionConfig(), taxon
.getUuid());
3039 if (!nameResult
.isOk()){
3040 result
.addExceptions(nameResult
.getExceptions());
3046 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
3047 result
= isDeletableForSynonym(references
, synonymConfig
);
3048 if (synonymConfig
.isDeleteNameIfPossible()){
3049 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), synonymConfig
.getNameDeletionConfig(), taxonBase
.getUuid());
3050 if (!nameResult
.isOk()){
3051 result
.addExceptions(nameResult
.getExceptions());
3059 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
3061 DeleteResult result
= new DeleteResult();
3062 for (CdmBase ref
: references
){
3063 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
3064 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
3065 result
.addException(new ReferencedObjectUndeletableException(message
));
3066 result
.addRelatedObject(ref
);
3075 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
3076 String message
= null;
3077 DeleteResult result
= new DeleteResult();
3078 for (CdmBase ref
: references
){
3079 if (!(ref
instanceof TaxonName
)){
3081 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
3082 message
= "The taxon can't be deleted as long as it has synonyms.";
3084 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
3085 message
= "The taxon can't be deleted as long as it has factual data.";
3088 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
3090 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
3092 if (ref
instanceof TaxonNode
&& config
.getClassificationUuid() != null && !config
.isDeleteInAllClassifications() && !((TaxonNode
)ref
).getClassification().getUuid().equals(config
.getClassificationUuid())){
3093 message
= "The taxon can't be deleted as long as it is used in more than one classification";
3096 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
3097 if (!config
.isDeleteMisappliedNames() &&
3098 (((TaxonRelationship
)ref
).getType().isMisappliedName())){
3099 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3101 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3104 if (ref
instanceof PolytomousKeyNode
){
3105 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3108 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3109 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3112 /* //PolytomousKeyNode
3113 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3114 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3119 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3120 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3124 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3125 message
= "Taxon can't be deleted as it is used in a determination event";
3128 if (message
!= null){
3129 result
.addException(new ReferencedObjectUndeletableException(message
));
3130 result
.addRelatedObject(ref
);
3139 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3140 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3142 //preliminary implementation
3144 Set
<Taxon
> taxa
= new HashSet
<>();
3145 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3146 if (taxonBase
== null){
3147 return new IncludedTaxaDTO();
3148 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3149 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3151 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3152 //TODO partial synonyms ??
3153 //TODO synonyms in general
3154 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3155 taxa
.add(syn
.getAcceptedTaxon());
3157 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3160 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3162 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3163 related
= makeRelatedIncluded(related
, result
, config
);
3170 * @param uncheckedTaxa
3171 * @param existingTaxa
3174 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3176 * @return the set of conceptually related taxa for further use
3178 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3181 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3182 for (Taxon taxon
: uncheckedTaxa
){
3183 taxonNodes
.addAll(taxon
.getTaxonNodes());
3186 Set
<Taxon
> children
= new HashSet
<>();
3187 if (! config
.onlyCongruent
){
3188 for (TaxonNode node
: taxonNodes
){
3189 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3190 for (TaxonNode child
: childNodes
){
3191 children
.add(child
.getTaxon());
3194 children
.remove(null); // just to be on the save side
3197 Iterator
<Taxon
> it
= children
.iterator();
3198 while(it
.hasNext()){
3199 UUID uuid
= it
.next().getUuid();
3200 if (existingTaxa
.contains(uuid
)){
3203 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3208 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3209 uncheckedAndChildren
.addAll(children
);
3211 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3214 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3219 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3220 * @return the set of these computed taxa
3222 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3223 Set
<Taxon
> result
= new HashSet
<>();
3225 for (Taxon taxon
: unchecked
){
3226 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3227 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3229 for (TaxonRelationship fromRel
: fromRelations
){
3230 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3233 TaxonRelationshipType fromRelType
= fromRel
.getType();
3234 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3235 !config
.onlyCongruent
&& (
3236 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3237 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3240 result
.add(fromRel
.getToTaxon());
3244 for (TaxonRelationship toRel
: toRelations
){
3245 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3248 TaxonRelationshipType fromRelType
= toRel
.getType();
3249 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3250 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3251 result
.add(toRel
.getFromTaxon());
3256 Iterator
<Taxon
> it
= result
.iterator();
3257 while(it
.hasNext()){
3258 UUID uuid
= it
.next().getUuid();
3259 if (existingTaxa
.contains(uuid
)){
3262 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3269 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3270 @SuppressWarnings("rawtypes")
3271 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3272 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3277 @Transactional(readOnly
= true)
3278 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3279 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3280 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3281 Integer pageNumber
, List
<String
> propertyPaths
) {
3282 if (subtreeFilter
== null){
3283 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3286 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3287 List
<Object
[]> daoResults
= new ArrayList
<>();
3288 if(numberOfResults
> 0) { // no point checking again
3289 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3290 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3293 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3294 for (Object
[] daoObj
: daoResults
){
3296 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3298 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3301 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3305 @Transactional(readOnly
= true)
3306 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3307 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3308 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3309 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3310 if (subtreeFilter
== null){
3311 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3314 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3315 List
<Object
[]> daoResults
= new ArrayList
<>();
3316 if(numberOfResults
> 0) { // no point checking again
3317 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3318 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3321 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3322 for (Object
[] daoObj
: daoResults
){
3324 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3326 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3329 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3333 @Transactional(readOnly
= false)
3334 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3335 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3336 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3338 UpdateResult result
= new UpdateResult();
3339 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3340 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3341 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3347 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3348 UpdateResult result
= new UpdateResult();
3350 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3351 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3352 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3353 //reload to avoid session conflicts
3354 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3356 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3357 if(description
.isProtectedTitleCache()){
3358 String separator
= "";
3359 if(!StringUtils
.isBlank(description
.getTitleCache())){
3362 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3364 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3365 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3366 description
.addAnnotation(annotation
);
3367 toTaxon
.addDescription(description
);
3368 dao
.saveOrUpdate(toTaxon
);
3369 dao
.saveOrUpdate(fromTaxon
);
3370 result
.addUpdatedObject(toTaxon
);
3371 result
.addUpdatedObject(fromTaxon
);
3379 @Transactional(readOnly
= false)
3380 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3381 UUID acceptedTaxonUuid
, boolean setNameInSource
) {
3382 TaxonBase
<?
> base
= this.load(synonymUUid
);
3383 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3384 base
= this.load(acceptedTaxonUuid
);
3385 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3387 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
, setNameInSource
);
3391 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3392 Set
<TaxonRelationshipType
> inversTypes
,
3393 Direction direction
, boolean groupMisapplications
,
3394 boolean includeUnpublished
,
3395 Integer pageSize
, Integer pageNumber
) {
3396 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3397 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3399 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3401 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3402 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3403 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3405 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3407 //TODO paging is difficult because misapplication string is an attribute
3409 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3410 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3411 // if(numberOfResults > 0) { // no point checking again
3412 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3415 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3418 List
<Language
> languages
= null;
3420 direction
= Direction
.relatedTo
;
3421 //TODO order hints, property path
3422 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3423 for (TaxonRelationship relation
: relations
){
3424 dto
.addRelation(relation
, direction
, languages
);
3428 direction
= Direction
.relatedFrom
;
3429 //TODO order hints, property path
3430 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3431 for (TaxonRelationship relation
: relations
){
3432 dto
.addRelation(relation
, direction
, languages
);
3435 if (groupMisapplications
){
3437 dto
.createMisapplicationString();