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
.EnumSet
;
15 import java
.util
.HashMap
;
16 import java
.util
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
21 import java
.util
.UUID
;
23 import javax
.persistence
.EntityNotFoundException
;
25 import org
.apache
.commons
.lang
.StringUtils
;
26 import org
.apache
.log4j
.Logger
;
27 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
28 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
29 import org
.apache
.lucene
.search
.BooleanQuery
;
30 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
31 import org
.apache
.lucene
.search
.Query
;
32 import org
.apache
.lucene
.search
.SortField
;
33 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
34 import org
.apache
.lucene
.search
.join
.ScoreMode
;
35 import org
.apache
.lucene
.util
.BytesRef
;
36 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
37 import org
.springframework
.stereotype
.Service
;
38 import org
.springframework
.transaction
.annotation
.Transactional
;
40 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
41 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
47 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
48 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
51 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
52 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
53 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
54 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
55 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
56 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
57 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
58 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
64 import eu
.etaxonomy
.cdm
.api
.service
.util
.TaxonRelationshipEdge
;
65 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
66 import eu
.etaxonomy
.cdm
.exception
.UnpublishedException
;
67 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
68 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
69 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
70 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
71 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
72 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
73 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
74 import eu
.etaxonomy
.cdm
.model
.common
.DefinedTerm
;
75 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
76 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
77 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
78 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
79 import eu
.etaxonomy
.cdm
.model
.common
.OriginalSourceType
;
80 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
81 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
82 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
83 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
84 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
85 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
86 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
87 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
88 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
89 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
90 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
91 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
92 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
93 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
94 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
95 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
96 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
97 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
98 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
99 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
100 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
101 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
102 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
103 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
104 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
105 import eu
.etaxonomy
.cdm
.model
.taxon
.HomotypicGroupTaxonComparator
;
106 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
107 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
108 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
109 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
110 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
114 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
115 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
116 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
117 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
118 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
119 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
120 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
121 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
122 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
123 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
124 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
125 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
129 * @author a.kohlbecker
133 @Transactional(readOnly
= true)
134 public class TaxonServiceImpl
135 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
136 implements ITaxonService
{
138 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
140 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
142 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
144 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
147 private ITaxonNodeDao taxonNodeDao
;
150 private ITaxonNameDao nameDao
;
153 private INameService nameService
;
156 private IOccurrenceService occurrenceService
;
159 private ITaxonNodeService nodeService
;
162 private IDescriptionService descriptionService
;
165 // private IOrderedTermVocabularyDao orderedVocabularyDao;
168 private IOccurrenceDao occurrenceDao
;
171 private IClassificationDao classificationDao
;
174 private AbstractBeanInitializer beanInitializer
;
177 private ILuceneIndexToolProvider luceneIndexToolProvider
;
179 //************************ CONSTRUCTOR ****************************/
180 public TaxonServiceImpl(){
181 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
184 // ****************************** METHODS ********************************/
191 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
192 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
196 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
197 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
201 @Transactional(readOnly
= false)
202 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
){
203 UpdateResult result
= new UpdateResult();
204 TaxonName synonymName
= synonym
.getName();
205 synonymName
.removeTaxonBase(synonym
);
206 TaxonName taxonName
= acceptedTaxon
.getName();
207 taxonName
.removeTaxonBase(acceptedTaxon
);
209 synonym
.setName(taxonName
);
210 synonym
.setTitleCache(null, false);
211 synonym
.getTitleCache();
212 acceptedTaxon
.setName(synonymName
);
213 acceptedTaxon
.setTitleCache(null, false);
214 acceptedTaxon
.getTitleCache();
215 saveOrUpdate(synonym
);
216 saveOrUpdate(acceptedTaxon
);
217 result
.addUpdatedObject(acceptedTaxon
);
218 result
.addUpdatedObject(synonym
);
221 // the accepted taxon needs a new uuid because the concept has changed
222 // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
223 //acceptedTaxon.setUuid(UUID.randomUUID());
228 @Transactional(readOnly
= false)
229 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean deleteSynonym
) {
230 UpdateResult result
= new UpdateResult();
231 TaxonName acceptedName
= acceptedTaxon
.getName();
232 TaxonName synonymName
= synonym
.getName();
233 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
235 //check synonym is not homotypic
236 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
237 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
238 result
.addException(new HomotypicalGroupChangeException(message
));
243 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, acceptedTaxon
.getSec());
244 dao
.save(newAcceptedTaxon
);
245 result
.setCdmEntity(newAcceptedTaxon
);
246 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
247 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
249 for (Synonym heteroSynonym
: heteroSynonyms
){
250 if (synonym
.equals(heteroSynonym
)){
251 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
253 //move synonyms in same homotypic group to new accepted taxon
254 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
257 dao
.saveOrUpdate(acceptedTaxon
);
258 result
.addUpdatedObject(acceptedTaxon
);
263 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
264 config
.setDeleteNameIfPossible(false);
265 this.deleteSynonym(synonym
, config
);
267 } catch (Exception e
) {
268 result
.addException(e
);
276 @Transactional(readOnly
= false)
277 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
278 UUID acceptedTaxonUuid
,
279 UUID newParentNodeUuid
,
280 boolean deleteSynonym
) {
281 UpdateResult result
= new UpdateResult();
282 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
283 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
284 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, deleteSynonym
);
285 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
286 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
287 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
288 taxonNodeDao
.save(newNode
);
289 result
.addUpdatedObject(newTaxon
);
290 result
.addUpdatedObject(acceptedTaxon
);
291 result
.setCdmEntity(newNode
);
299 @Transactional(readOnly
= false)
300 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
302 TaxonRelationshipType taxonRelationshipType
,
304 String microcitation
){
306 UpdateResult result
= new UpdateResult();
307 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
308 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
309 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
310 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
311 // result.setCdmEntity(relatedTaxon);
312 result
.addUpdatedObject(relatedTaxon
);
313 result
.addUpdatedObject(toTaxon
);
318 @Transactional(readOnly
= false)
319 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
320 // Get name from synonym
321 if (synonym
== null){
325 UpdateResult result
= new UpdateResult();
327 TaxonName synonymName
= synonym
.getName();
329 /* // remove synonym from taxon
330 toTaxon.removeSynonym(synonym);
332 // Create a taxon with synonym name
333 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
335 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
337 // Add taxon relation
338 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
339 result
.setCdmEntity(fromTaxon
);
340 // since we are swapping names, we have to detach the name from the synonym completely.
341 // Otherwise the synonym will still be in the list of typified names.
342 // synonym.getName().removeTaxonBase(synonym);
343 result
.includeResult(this.deleteSynonym(synonym
, null));
348 @Transactional(readOnly
= false)
350 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
351 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
353 TaxonName synonymName
= synonym
.getName();
354 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
357 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
358 newHomotypicalGroup
.addTypifiedName(synonymName
);
360 //remove existing basionym relationships
361 synonymName
.removeBasionyms();
363 //add basionym relationship
364 if (setBasionymRelationIfApplicable
){
365 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
366 for (TaxonName basionym
: basionyms
){
367 synonymName
.addBasionym(basionym
);
371 //set synonym relationship correctly
372 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
374 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
375 if (acceptedTaxon
!= null){
377 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
378 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
379 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
380 synonym
.setType(newRelationType
);
382 if (hasNewTargetTaxon
){
383 acceptedTaxon
.removeSynonym(synonym
, false);
386 if (hasNewTargetTaxon
){
387 @SuppressWarnings("null")
388 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
389 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
390 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
391 targetTaxon
.addSynonym(synonym
, relType
);
397 @Transactional(readOnly
= false)
398 public void updateTitleCache(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
400 clazz
= TaxonBase
.class;
402 super.updateTitleCacheImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
407 protected void setDao(ITaxonDao dao
) {
412 public Pager
<TaxonBase
> findTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
413 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
415 List
<TaxonBase
> results
= new ArrayList
<>();
416 if(numberOfResults
> 0) { // no point checking again
417 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
420 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
424 public List
<TaxonBase
> listTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
425 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
427 List
<TaxonBase
> results
= new ArrayList
<>();
428 if(numberOfResults
> 0) { // no point checking again
429 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
436 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
437 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
438 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
440 List
<TaxonRelationship
> results
= new ArrayList
<>();
441 if(numberOfResults
> 0) { // no point checking again
442 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
448 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
449 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
450 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
452 List
<TaxonRelationship
> results
= new ArrayList
<>();
453 if(numberOfResults
> 0) { // no point checking again
454 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
456 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
460 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
461 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
462 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
464 List
<TaxonRelationship
> results
= new ArrayList
<>();
465 if(numberOfResults
> 0) { // no point checking again
466 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
472 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
473 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
474 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
476 List
<TaxonRelationship
> results
= new ArrayList
<>();
477 if(numberOfResults
> 0) { // no point checking again
478 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
480 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
484 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
485 Integer pageSize
, Integer pageStart
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
486 Long numberOfResults
= dao
.countTaxonRelationships(types
);
488 List
<TaxonRelationship
> results
= new ArrayList
<>();
489 if(numberOfResults
> 0) {
490 results
= dao
.getTaxonRelationships(types
, pageSize
, pageStart
, orderHints
, propertyPaths
);
496 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
497 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
499 Synonym synonym
= null;
502 synonym
= (Synonym
) dao
.load(synonymUuid
);
503 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
504 } catch (ClassCastException e
){
505 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
506 } catch (NullPointerException e
){
507 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
510 Classification classificationFilter
= null;
511 if(classificationUuid
!= null){
513 classificationFilter
= classificationDao
.load(classificationUuid
);
514 } catch (NullPointerException e
){
515 //TODO not sure, why an NPE should be thrown in the above load method
516 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
518 if(classificationFilter
== null){
519 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
523 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
525 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
526 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
534 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
535 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
537 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
538 relatedTaxa
.remove(taxon
);
539 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
545 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
546 * <code>taxon</code> supplied as parameter.
549 * @param includeRelationships
551 * @param maxDepth can be <code>null</code> for infinite depth
554 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
555 boolean includeUnpublished
, Integer maxDepth
) {
561 if(includeRelationships
.isEmpty()){
565 if(maxDepth
!= null) {
568 if(logger
.isDebugEnabled()){
569 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
571 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
, null, includeUnpublished
, null, null, null, null, null);
572 for (TaxonRelationship taxRel
: taxonRelationships
) {
575 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
578 // filter by includeRelationships
579 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
580 if ( relationshipEdgeFilter
.getTaxonRelationshipTypes().equals(taxRel
.getType()) ) {
581 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
582 if(logger
.isDebugEnabled()){
583 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
585 taxa
.add(taxRel
.getToTaxon());
586 if(maxDepth
== null || maxDepth
> 0) {
587 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
590 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
591 taxa
.add(taxRel
.getFromTaxon());
592 if(logger
.isDebugEnabled()){
593 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
595 if(maxDepth
== null || maxDepth
> 0) {
596 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
606 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
607 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
609 List
<Synonym
> results
= new ArrayList
<>();
610 if(numberOfResults
> 0) { // no point checking again
611 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
614 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
618 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
619 List
<List
<Synonym
>> result
= new ArrayList
<>();
620 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
621 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
625 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
628 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
629 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
630 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
638 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
639 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
640 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
642 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
646 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
647 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
648 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
649 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
650 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
651 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
653 return heterotypicSynonymyGroups
;
657 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
659 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
660 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
661 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(),
662 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
664 return new ArrayList
<>();
669 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
671 List
<IdentifiableEntity
> results
= new ArrayList
<>();
672 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
673 List
<TaxonBase
> taxa
= null;
676 long numberTaxaResults
= 0L;
679 List
<String
> propertyPath
= new ArrayList
<>();
680 if(configurator
.getTaxonPropertyPath() != null){
681 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
685 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
686 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
688 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
689 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
690 configurator
.getClassification(), configurator
.getMatchMode(),
691 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
694 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
695 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
696 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
697 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(),
698 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
699 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
703 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
706 results
.addAll(taxa
);
709 numberOfResults
+= numberTaxaResults
;
711 // Names without taxa
712 if (configurator
.isDoNamesWithoutTaxa()) {
713 int numberNameResults
= 0;
715 List
<TaxonName
> names
=
716 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
717 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
718 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
719 if (names
.size() > 0) {
720 for (TaxonName taxonName
: names
) {
721 if (taxonName
.getTaxonBases().size() == 0) {
722 results
.add(taxonName
);
726 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
727 numberOfResults
+= numberNameResults
;
731 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
734 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
735 return dao
.getUuidAndTitleCache(limit
, pattern
);
739 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
740 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
744 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
745 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
746 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
749 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
751 // logger.setLevel(Level.TRACE);
752 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
754 logger
.trace("listMedia() - START");
756 Set
<Taxon
> taxa
= new HashSet
<>();
757 List
<Media
> taxonMedia
= new ArrayList
<>();
758 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
760 if (limitToGalleries
== null) {
761 limitToGalleries
= false;
764 // --- resolve related taxa
765 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
766 logger
.trace("listMedia() - resolve related taxa");
767 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
770 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
772 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
773 logger
.trace("listMedia() - includeTaxonDescriptions");
774 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
775 // --- TaxonDescriptions
776 for (Taxon t
: taxa
) {
777 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
779 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
780 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
781 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
782 for (Media media
: element
.getMedia()) {
783 if(taxonDescription
.isImageGallery()){
784 taxonMedia
.add(media
);
787 nonImageGalleryImages
.add(media
);
793 //put images from image gallery first (#3242)
794 taxonMedia
.addAll(nonImageGalleryImages
);
798 if(includeOccurrences
!= null && includeOccurrences
) {
799 logger
.trace("listMedia() - includeOccurrences");
800 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
802 for (Taxon t
: taxa
) {
803 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
805 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
807 // direct media removed from specimen #3597
808 // taxonMedia.addAll(occurrence.getMedia());
810 // SpecimenDescriptions
811 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
812 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
813 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
814 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
815 for (DescriptionElementBase element
: elements
) {
816 for (Media media
: element
.getMedia()) {
817 taxonMedia
.add(media
);
823 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
824 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
826 //TODO why may collections have media attached? #
827 if (derivedUnit
.getCollection() != null){
828 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
832 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
836 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
837 logger
.trace("listMedia() - includeTaxonNameDescriptions");
838 // --- TaxonNameDescription
839 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
840 for (Taxon t
: taxa
) {
841 nameDescriptions
.addAll(t
.getName().getDescriptions());
843 for(TaxonNameDescription nameDescription
: nameDescriptions
){
844 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
845 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
846 for (DescriptionElementBase element
: elements
) {
847 for (Media media
: element
.getMedia()) {
848 taxonMedia
.add(media
);
856 logger
.trace("listMedia() - initialize");
857 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
859 logger
.trace("listMedia() - END");
865 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
866 return this.dao
.loadList(listOfIDs
, null);
870 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
871 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
875 public long countSynonyms(boolean onlyAttachedToTaxon
){
876 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
880 public List
<TaxonName
> findIdenticalTaxonNames(List
<String
> propertyPath
) {
881 return this.dao
.findIdenticalTaxonNames(propertyPath
);
885 @Transactional(readOnly
=false)
886 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
889 config
= new TaxonDeletionConfigurator();
891 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
892 DeleteResult result
= new DeleteResult();
895 result
.addException(new Exception ("The taxon was already deleted."));
898 taxon
= HibernateProxyHelper
.deproxy(taxon
);
899 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
900 result
= isDeletable(taxonUUID
, config
);
903 // --- DeleteSynonymRelations
904 if (config
.isDeleteSynonymRelations()){
905 boolean removeSynonymNameFromHomotypicalGroup
= false;
906 // use tmp Set to avoid concurrent modification
907 Set
<Synonym
> synsToDelete
= new HashSet
<>();
908 synsToDelete
.addAll(taxon
.getSynonyms());
909 for (Synonym synonym
: synsToDelete
){
910 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
912 // --- DeleteSynonymsIfPossible
913 if (config
.isDeleteSynonymsIfPossible()){
915 boolean newHomotypicGroupIfNeeded
= true;
916 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
917 result
.includeResult(deleteSynonym(synonym
, synConfig
));
922 // --- DeleteTaxonRelationships
923 if (! config
.isDeleteTaxonRelationships()){
924 if (taxon
.getTaxonRelations().size() > 0){
926 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
927 "Remove taxon from all relations to other taxa prior to deletion."));
930 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
931 configRelTaxon
.setDeleteTaxonNodes(false);
932 configRelTaxon
.setDeleteConceptRelationships(true);
934 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
935 if (config
.isDeleteMisappliedNamesAndInvalidDesignations()
936 && taxRel
.getType().isMisappliedNameOrInvalidDesignation()
937 && taxon
.equals(taxRel
.getToTaxon())){
938 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
939 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
941 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
942 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
943 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
944 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
947 taxon
.removeTaxonRelation(taxRel
);
952 if (config
.isDeleteDescriptions()){
953 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
954 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
955 for (TaxonDescription desc
: descriptions
){
956 //TODO use description delete configurator ?
957 //FIXME check if description is ALWAYS deletable
958 if (desc
.getDescribedSpecimenOrObservation() != null){
960 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
961 " which also describes specimens or observations"));
964 removeDescriptions
.add(desc
);
969 for (TaxonDescription desc
: removeDescriptions
){
970 taxon
.removeDescription(desc
);
971 descriptionService
.delete(desc
);
979 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
980 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
982 if (taxon
.getTaxonNodes().size() != 0){
983 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
984 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
985 TaxonNode node
= null;
986 boolean deleteChildren
;
987 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
988 deleteChildren
= true;
990 deleteChildren
= false;
992 boolean success
= true;
993 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
994 while (iterator
.hasNext()){
995 node
= iterator
.next();
996 if (node
.getClassification().equals(classification
)){
1002 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1003 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1004 nodeService
.delete(node
);
1005 result
.addDeletedObject(node
);
1008 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1010 } else if (config
.isDeleteInAllClassifications()){
1011 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1012 nodesList
.addAll(taxon
.getTaxonNodes());
1013 for (ITaxonTreeNode treeNode
: nodesList
){
1014 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1015 if(!deleteChildren
){
1016 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1017 for (Object childNode
: childNodes
){
1018 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1019 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1023 config
.getTaxonNodeConfig().setDeleteElement(false);
1024 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1025 if (!resultNodes
.isOk()){
1026 result
.addExceptions(resultNodes
.getExceptions());
1027 result
.setStatus(resultNodes
.getStatus());
1029 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1034 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1038 TaxonName name
= taxon
.getName();
1039 taxon
.setName(null);
1040 this.saveOrUpdate(taxon
);
1042 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1045 result
.addDeletedObject(taxon
);
1046 }catch(Exception e
){
1047 result
.addException(e
);
1052 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1056 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1057 DeleteResult nameResult
= new DeleteResult();
1058 //remove name if possible (and required)
1060 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1062 if (nameResult
.isError() || nameResult
.isAbort()){
1063 result
.addRelatedObject(name
);
1064 result
.addExceptions(nameResult
.getExceptions());
1066 result
.includeResult(nameResult
);
1076 @Transactional(readOnly
= false)
1077 public DeleteResult
delete(UUID synUUID
){
1078 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1079 return this.deleteSynonym(syn
, null);
1083 @Transactional(readOnly
= false)
1084 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1085 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1091 @Transactional(readOnly
= false)
1092 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1093 DeleteResult result
= new DeleteResult();
1094 if (synonym
== null){
1096 result
.addException(new Exception("The synonym was already deleted."));
1100 if (config
== null){
1101 config
= new SynonymDeletionConfigurator();
1104 result
= isDeletable(synonym
.getUuid(), config
);
1108 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1111 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1113 if (accTaxon
!= null){
1114 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1115 accTaxon
.removeSynonym(synonym
, false);
1116 this.saveOrUpdate(accTaxon
);
1117 result
.addUpdatedObject(accTaxon
);
1119 this.saveOrUpdate(synonym
);
1123 TaxonName name
= synonym
.getName();
1124 synonym
.setName(null);
1126 dao
.delete(synonym
);
1127 result
.addDeletedObject(synonym
);
1129 //remove name if possible (and required)
1130 if (name
!= null && config
.isDeleteNameIfPossible()){
1132 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1133 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1134 result
.addExceptions(nameDeleteResult
.getExceptions());
1135 result
.addRelatedObject(name
);
1137 result
.addDeletedObject(name
);
1146 public List
<TaxonName
> findIdenticalTaxonNameIds(List
<String
> propertyPath
) {
1148 return this.dao
.findIdenticalNamesNew(propertyPath
);
1153 public Taxon
findBestMatchingTaxon(String taxonName
) {
1154 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1155 config
.setTaxonNameTitle(taxonName
);
1156 return findBestMatchingTaxon(config
);
1160 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1162 Taxon bestCandidate
= null;
1164 // 1. search for accepted taxa
1165 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1166 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, null, null);
1167 boolean bestCandidateMatchesSecUuid
= false;
1168 boolean bestCandidateIsInClassification
= false;
1169 int countEqualCandidates
= 0;
1170 for(TaxonBase taxonBaseCandidate
: taxonList
){
1171 if(taxonBaseCandidate
instanceof Taxon
){
1172 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1173 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1174 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1176 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1177 bestCandidate
= newCanditate
;
1178 countEqualCandidates
= 1;
1179 bestCandidateMatchesSecUuid
= true;
1183 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1184 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1186 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1187 bestCandidate
= newCanditate
;
1188 countEqualCandidates
= 1;
1189 bestCandidateIsInClassification
= true;
1192 if (bestCandidate
== null){
1193 bestCandidate
= newCanditate
;
1194 countEqualCandidates
= 1;
1198 }else{ //not Taxon.class
1201 countEqualCandidates
++;
1204 if (bestCandidate
!= null){
1205 if(countEqualCandidates
> 1){
1206 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1207 return bestCandidate
;
1209 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1210 return bestCandidate
;
1215 // 2. search for synonyms
1216 if (config
.isIncludeSynonyms()){
1217 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1218 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, null, null);
1219 for(TaxonBase taxonBase
: synonymList
){
1220 if(taxonBase
instanceof Synonym
){
1221 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1222 bestCandidate
= synonym
.getAcceptedTaxon();
1223 if(bestCandidate
!= null){
1224 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1225 return bestCandidate
;
1227 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1232 } catch (Exception e
){
1234 e
.printStackTrace();
1237 return bestCandidate
;
1240 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1241 UUID configClassificationUuid
= config
.getClassificationUuid();
1242 if (configClassificationUuid
== null){
1245 for (TaxonNode node
: taxon
.getTaxonNodes()){
1246 UUID classUuid
= node
.getClassification().getUuid();
1247 if (configClassificationUuid
.equals(classUuid
)){
1254 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1255 UUID configSecUuid
= config
.getSecUuid();
1256 if (configSecUuid
== null){
1259 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1260 return configSecUuid
.equals(taxonSecUuid
);
1264 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1265 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, MatchMode
.EXACT
, null, null, 0, null, null);
1266 if(! synonymList
.isEmpty()){
1267 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1268 if(synonymList
.size() == 1){
1269 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1272 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1280 @Transactional(readOnly
= false)
1281 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1283 boolean moveHomotypicGroup
,
1284 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1285 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1287 oldSynonym
.getSec(),
1288 oldSynonym
.getSecMicroReference(),
1293 @Transactional(readOnly
= false)
1294 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1296 boolean moveHomotypicGroup
,
1297 SynonymType newSynonymType
,
1298 Reference newSecundum
,
1299 String newSecundumDetail
,
1300 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1302 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1303 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1304 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1305 TaxonName synonymName
= synonym
.getName();
1306 TaxonName fromTaxonName
= oldTaxon
.getName();
1307 //set default relationship type
1308 if (newSynonymType
== null){
1309 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1311 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1313 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1314 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1315 boolean isSingleInGroup
= !(hgSize
> 1);
1317 if (! isSingleInGroup
){
1318 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1319 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1320 if (isHomotypicToAccepted
){
1321 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.";
1322 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1323 message
= String
.format(message
, homotypicRelatives
);
1324 throw new HomotypicalGroupChangeException(message
);
1326 if (! moveHomotypicGroup
){
1327 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.";
1328 throw new HomotypicalGroupChangeException(message
);
1331 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1333 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1335 UpdateResult result
= new UpdateResult();
1336 //move all synonyms to new taxon
1337 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1338 for (Synonym synRelation
: homotypicSynonyms
){
1340 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1341 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1342 oldTaxon
.removeSynonym(synRelation
, false);
1343 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1345 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1346 synRelation
.setSec(newSecundum
);
1348 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1349 synRelation
.setSecMicroReference(newSecundumDetail
);
1352 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1353 if (!synRelation
.equals(oldSynonym
)){
1358 result
.addUpdatedObject(oldTaxon
);
1359 result
.addUpdatedObject(newTaxon
);
1360 saveOrUpdate(oldTaxon
);
1361 saveOrUpdate(newTaxon
);
1367 public <T
extends TaxonBase
> List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1368 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1372 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1373 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1374 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
,
1375 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1376 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1378 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, null,
1379 includeUnpublished
, languages
, highlightFragments
, null);
1381 // --- execute search
1382 TopGroups
<BytesRef
> topDocsResultSet
;
1384 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1385 } catch (ParseException e
) {
1386 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1387 luceneParseException
.setStackTrace(e
.getStackTrace());
1388 throw luceneParseException
;
1391 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1392 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1394 // --- initialize taxa, thighlight matches ....
1395 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1396 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1397 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1399 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1400 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1404 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1405 Classification classification
,
1406 Integer pageSize
, Integer pageNumber
,
1407 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1409 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
);
1411 // --- execute search
1412 TopGroups
<BytesRef
> topDocsResultSet
;
1414 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1415 } catch (ParseException e
) {
1416 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1417 luceneParseException
.setStackTrace(e
.getStackTrace());
1418 throw luceneParseException
;
1421 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1422 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1424 // --- initialize taxa, thighlight matches ....
1425 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1426 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1427 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1429 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1430 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1435 * @param queryString
1436 * @param classification
1437 * @param includeUnpublished
1439 * @param highlightFragments
1440 * @param sortFields TODO
1441 * @param directorySelectClass
1444 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1445 Classification classification
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1446 boolean highlightFragments
, SortField
[] sortFields
) {
1448 Builder finalQueryBuilder
= new Builder();
1449 Builder textQueryBuilder
= new Builder();
1451 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1452 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1454 if(sortFields
== null){
1455 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1457 luceneSearch
.setSortFields(sortFields
);
1459 // ---- search criteria
1460 luceneSearch
.setCdmTypRestriction(clazz
);
1462 if(!queryString
.isEmpty() && !queryString
.equals("*") && !queryString
.equals("?") ) {
1463 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1464 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1466 if(className
!= null){
1467 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1470 BooleanQuery textQuery
= textQueryBuilder
.build();
1471 if(textQuery
.clauses().size() > 0) {
1472 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1476 if(classification
!= null){
1477 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1479 if(!includeUnpublished
) {
1480 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1481 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1482 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1485 luceneSearch
.setQuery(finalQueryBuilder
.build());
1487 if(highlightFragments
){
1488 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1490 return luceneSearch
;
1494 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1495 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1496 * drawback of requiring to do the join an indexing time.
1497 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1499 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1501 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1502 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1504 * @param queryString
1505 * @param classification
1507 * @param highlightFragments
1508 * @param sortFields TODO
1511 * @throws IOException
1513 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1514 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
,
1515 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1518 String queryTermField
;
1519 String toField
= "id"; // TaxonBase.uuid
1520 String publishField
;
1521 String publishFieldInvers
;
1523 if(edge
.isBidirectional()){
1524 throw new RuntimeException("Bidirectional joining not supported!");
1527 fromField
= "relatedFrom.id";
1528 queryTermField
= "relatedFrom.titleCache";
1529 publishField
= "relatedFrom.publish";
1530 publishFieldInvers
= "relatedTo.publish";
1531 } else if(edge
.isInvers()) {
1532 fromField
= "relatedTo.id";
1533 queryTermField
= "relatedTo.titleCache";
1534 publishField
= "relatedTo.publish";
1535 publishFieldInvers
= "relatedFrom.publish";
1537 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1540 Builder finalQueryBuilder
= new Builder();
1542 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1543 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1545 Builder joinFromQueryBuilder
= new Builder();
1546 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1547 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getTaxonRelationshipTypes()), Occur
.MUST
);
1548 if(!includeUnpublished
){
1549 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1550 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1553 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1555 if(sortFields
== null){
1556 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1558 luceneSearch
.setSortFields(sortFields
);
1560 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1562 if(classification
!= null){
1563 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1566 luceneSearch
.setQuery(finalQueryBuilder
.build());
1568 if(highlightFragments
){
1569 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1571 return luceneSearch
;
1575 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1576 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
, Classification classification
,
1577 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1578 boolean highlightFragments
, Integer pageSize
,
1579 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1580 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1582 // FIXME: allow taxonomic ordering
1583 // 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";
1584 // this require building a special sort column by a special classBridge
1585 if(highlightFragments
){
1586 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1587 "currently not fully supported by this method and thus " +
1588 "may not work with common names and misapplied names.");
1591 // convert sets to lists
1592 List
<NamedArea
> namedAreaList
= null;
1593 List
<PresenceAbsenceTerm
>distributionStatusList
= null;
1594 if(namedAreas
!= null){
1595 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1596 namedAreaList
.addAll(namedAreas
);
1598 if(distributionStatus
!= null){
1599 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1600 distributionStatusList
.addAll(distributionStatus
);
1603 // set default if parameter is null
1604 if(searchModes
== null){
1605 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1608 // set sort order and thus override any sort orders which may have been
1609 // defined by prepare*Search methods
1610 if(orderHints
== null){
1611 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1613 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1615 for(OrderHint oh
: orderHints
){
1616 sortFields
[i
++] = oh
.toSortField();
1618 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1619 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1622 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1624 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1625 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1628 ======== filtering by distribution , HOWTO ========
1630 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1631 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1632 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1633 which will be put into a FilteredQuersy in the end ?
1636 3. how does it work in spatial?
1638 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1639 - http://www.infoq.com/articles/LuceneSpatialSupport
1640 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1641 ------------------------------------------------------------------------
1644 A) use a separate distribution filter per index sub-query/search:
1645 - byTaxonSyonym (query TaxaonBase):
1646 use a join area filter (Distribution -> TaxonBase)
1647 - byCommonName (query DescriptionElementBase): use an area filter on
1648 DescriptionElementBase !!! PROBLEM !!!
1649 This cannot work since the distributions are different entities than the
1650 common names and thus these are different lucene documents.
1651 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1652 use a join area filter (Distribution -> TaxonBase)
1654 B) use a common distribution filter for all index sub-query/searches:
1655 - use a common join area filter (Distribution -> TaxonBase)
1656 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1657 PROBLEM in this case: we are losing the fragment highlighting for the
1658 common names, since the returned documents are always TaxonBases
1661 /* The QueryFactory for creating filter queries on Distributions should
1662 * The query factory used for the common names query cannot be reused
1663 * for this case, since we want to only record the text fields which are
1664 * actually used in the primary query
1666 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1668 Builder multiIndexByAreaFilterBuilder
= new Builder();
1669 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1671 // search for taxa or synonyms
1672 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1673 @SuppressWarnings("rawtypes")
1674 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1675 String className
= null;
1676 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1677 taxonBaseSubclass
= Taxon
.class;
1678 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1679 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1681 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
, queryString
, classification
, className
,
1682 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1683 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1684 /* A) does not work!!!!
1685 if(addDistributionFilter){
1686 // in this case we need a filter which uses a join query
1687 // to get the TaxonBase documents for the DescriptionElementBase documents
1688 // which are matching the areas in question
1689 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1691 distributionStatusList,
1692 distributionFilterQueryFactory
1694 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1697 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1698 // add additional area filter for synonyms
1699 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1700 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1702 //TODO replace by createByDistributionJoinQuery
1703 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1704 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1705 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1710 // search by CommonTaxonName
1711 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1713 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1714 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1715 CommonTaxonName
.class,
1716 "inDescription.taxon.id",
1718 QueryFactory
.addTypeRestriction(
1719 createByDescriptionElementFullTextQuery(queryString
, classification
, null, languages
, descriptionElementQueryFactory
)
1720 , CommonTaxonName
.class
1721 ).build(), "id", null, ScoreMode
.Max
);
1722 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1723 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1724 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1725 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1726 Builder builder
= new BooleanQuery
.Builder();
1727 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1728 if(!includeUnpublished
) {
1729 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1730 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1732 byCommonNameSearch
.setQuery(builder
.build());
1733 byCommonNameSearch
.setSortFields(sortFields
);
1735 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1737 luceneSearches
.add(byCommonNameSearch
);
1739 /* A) does not work!!!!
1741 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1742 queryString, classification, null, languages, highlightFragments)
1744 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1745 if(addDistributionFilter){
1746 // in this case we are able to use DescriptionElementBase documents
1747 // which are matching the areas in question directly
1748 BooleanQuery byDistributionQuery = createByDistributionQuery(
1750 distributionStatusList,
1751 distributionFilterQueryFactory
1753 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1758 // search by misapplied names
1759 //TODO merge with pro parte synonym search once #7487 is fixed
1760 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1762 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1763 // which allows doing query time joins
1764 // finds the misapplied name (Taxon B) which is an misapplication for
1765 // a related Taxon A.
1767 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1768 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1769 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1771 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1772 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1775 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1776 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1777 queryString
, classification
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1778 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1780 if(addDistributionFilter
){
1781 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1784 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
1785 * Maybe this is a bug in java itself.
1787 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1790 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1792 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1793 * will execute as expected:
1795 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1796 * String toField = "relation." + misappliedNameForUuid +".to.id";
1798 * Comparing both strings by the String.equals method returns true, so both String are identical.
1800 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1801 * 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)
1802 * The bug is persistent after a reboot of the development computer.
1804 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1805 // String toField = "relation." + misappliedNameForUuid +".to.id";
1806 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1807 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1808 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1810 //TODO replace by createByDistributionJoinQuery
1811 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1812 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1813 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1815 // debug code for bug described above
1816 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1817 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1818 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1820 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1825 // search by pro parte synonyms
1826 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1827 //TODO merge with misapplied name search once #7487 is fixed
1828 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1829 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
1831 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1832 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1833 queryString
, classification
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1834 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1836 if(addDistributionFilter
){
1837 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1838 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
1839 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1840 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1841 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1842 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1844 }//end pro parte synonyms
1848 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
1849 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
1852 if(addDistributionFilter
){
1855 // in this case we need a filter which uses a join query
1856 // to get the TaxonBase documents for the DescriptionElementBase documents
1857 // which are matching the areas in question
1859 // for doTaxa, doByCommonName
1860 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
1862 distributionStatusList
,
1863 distributionFilterQueryFactory
,
1866 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1869 if (addDistributionFilter
){
1870 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
1874 // --- execute search
1875 TopGroups
<BytesRef
> topDocsResultSet
;
1877 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
1878 } catch (ParseException e
) {
1879 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1880 luceneParseException
.setStackTrace(e
.getStackTrace());
1881 throw luceneParseException
;
1884 // --- initialize taxa, highlight matches ....
1885 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
1888 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1889 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1891 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1892 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1896 * @param namedAreaList at least one area must be in the list
1897 * @param distributionStatusList optional
1898 * @param toType toType
1899 * Optional parameter. Only used for debugging to print the toType documents
1900 * @param asFilter TODO
1902 * @throws IOException
1904 protected Query
createByDistributionJoinQuery(
1905 List
<NamedArea
> namedAreaList
,
1906 List
<PresenceAbsenceTerm
> distributionStatusList
,
1907 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
1908 ) throws IOException
{
1910 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1911 String toField
= "id"; // id in toType usually this is the TaxonBase index
1913 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
1915 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
1917 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
1919 return taxonAreaJoinQuery
;
1923 * @param namedAreaList
1924 * @param distributionStatusList
1925 * @param queryFactory
1928 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
1929 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
1930 Builder areaQueryBuilder
= new Builder();
1931 // area field from Distribution
1932 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
1934 // status field from Distribution
1935 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
1936 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
1939 BooleanQuery areaQuery
= areaQueryBuilder
.build();
1940 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
1945 * This method has been primarily created for testing the area join query but might
1946 * also be useful in other situations
1948 * @param namedAreaList
1949 * @param distributionStatusList
1950 * @param classification
1951 * @param highlightFragments
1953 * @throws IOException
1955 protected LuceneSearch
prepareByDistributionSearch(
1956 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
1957 Classification classification
) throws IOException
{
1959 Builder finalQueryBuilder
= new Builder();
1961 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1963 // FIXME is this query factory using the wrong type?
1964 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
1966 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1967 luceneSearch
.setSortFields(sortFields
);
1970 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
1972 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
1974 if(classification
!= null){
1975 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1977 BooleanQuery finalQuery
= finalQueryBuilder
.build();
1978 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
1979 luceneSearch
.setQuery(finalQuery
);
1981 return luceneSearch
;
1985 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
1986 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
1987 Classification classification
, List
<Feature
> features
, List
<Language
> languages
,
1988 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1991 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, features
, languages
, highlightFragments
);
1993 // --- execute search
1994 TopGroups
<BytesRef
> topDocsResultSet
;
1996 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1997 } catch (ParseException e
) {
1998 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1999 luceneParseException
.setStackTrace(e
.getStackTrace());
2000 throw luceneParseException
;
2003 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2004 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2006 // --- initialize taxa, highlight matches ....
2007 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2008 @SuppressWarnings("rawtypes")
2009 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2010 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2012 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2013 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2019 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2020 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2021 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2023 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
, classification
,
2024 null, languages
, highlightFragments
);
2025 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, null,
2026 includeUnpublished
, languages
, highlightFragments
, null);
2028 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2030 // --- execute search
2031 TopGroups
<BytesRef
> topDocsResultSet
;
2033 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2034 } catch (ParseException e
) {
2035 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2036 luceneParseException
.setStackTrace(e
.getStackTrace());
2037 throw luceneParseException
;
2040 // --- initialize taxa, highlight matches ....
2041 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2043 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2044 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2045 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2047 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2048 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2050 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2051 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2058 * @param queryString
2059 * @param classification
2062 * @param highlightFragments
2063 * @param directorySelectClass
2066 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2067 String queryString
, Classification classification
, List
<Feature
> features
,
2068 List
<Language
> languages
, boolean highlightFragments
) {
2070 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2071 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2073 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2075 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, features
,
2076 languages
, descriptionElementQueryFactory
);
2078 luceneSearch
.setSortFields(sortFields
);
2079 luceneSearch
.setCdmTypRestriction(clazz
);
2080 luceneSearch
.setQuery(finalQuery
);
2081 if(highlightFragments
){
2082 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2085 return luceneSearch
;
2089 * @param queryString
2090 * @param classification
2093 * @param descriptionElementQueryFactory
2096 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
, Classification classification
,
2097 List
<Feature
> features
, List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2098 Builder finalQueryBuilder
= new Builder();
2099 Builder textQueryBuilder
= new Builder();
2100 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2103 Builder nameQueryBuilder
= new Builder();
2104 if(languages
== null || languages
.size() == 0){
2105 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2107 Builder languageSubQueryBuilder
= new Builder();
2108 for(Language lang
: languages
){
2109 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2111 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2112 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2114 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2117 // text field from TextData
2118 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2120 // --- TermBase fields - by representation ----
2121 // state field from CategoricalData
2122 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2124 // state field from CategoricalData
2125 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2127 // area field from Distribution
2128 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2130 // status field from Distribution
2131 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2133 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2134 // --- classification ----
2136 if(classification
!= null){
2137 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2140 // --- IdentifieableEntity fields - by uuid
2141 if(features
!= null && features
.size() > 0 ){
2142 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2145 // the description must be associated with a taxon
2146 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2148 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2149 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2154 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2157 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2158 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2160 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2161 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2163 UUID nameUuid
= taxon
.getName().getUuid();
2164 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2165 String epithetOfTaxon
= null;
2166 String infragenericEpithetOfTaxon
= null;
2167 String infraspecificEpithetOfTaxon
= null;
2168 if (taxonName
.isSpecies()){
2169 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2170 } else if (taxonName
.isInfraGeneric()){
2171 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2172 } else if (taxonName
.isInfraSpecific()){
2173 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2175 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2176 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2177 List
<String
> taxonNames
= new ArrayList
<>();
2179 for (TaxonNode node
: nodes
){
2180 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2181 // List<String> synonymsEpithet = new ArrayList<>();
2183 if (node
.getClassification().equals(classification
)){
2184 if (!node
.isTopmostNode()){
2185 TaxonNode parent
= node
.getParent();
2186 parent
= CdmBase
.deproxy(parent
);
2187 TaxonName parentName
= parent
.getTaxon().getName();
2188 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2189 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2191 //create inferred synonyms for species, subspecies
2192 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2194 Synonym inferredEpithet
= null;
2195 Synonym inferredGenus
= null;
2196 Synonym potentialCombination
= null;
2198 List
<String
> propertyPaths
= new ArrayList
<>();
2199 propertyPaths
.add("synonym");
2200 propertyPaths
.add("synonym.name");
2201 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2202 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2204 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2205 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2206 null, null,orderHintsSynonyms
,propertyPaths
);
2208 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2209 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2210 if (doWithMisappliedNames
){
2211 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2212 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2213 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2214 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2215 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2216 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2219 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2220 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2222 inferredEpithet
= createInferredEpithets(taxon
,
2223 zooHashMap
, taxonName
, epithetOfTaxon
,
2224 infragenericEpithetOfTaxon
,
2225 infraspecificEpithetOfTaxon
,
2226 taxonNames
, parentName
,
2227 synonymRelationOfParent
);
2229 inferredSynonyms
.add(inferredEpithet
);
2230 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2231 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2234 if (doWithMisappliedNames
){
2236 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2237 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2239 inferredEpithet
= createInferredEpithets(taxon
,
2240 zooHashMap
, taxonName
, epithetOfTaxon
,
2241 infragenericEpithetOfTaxon
,
2242 infraspecificEpithetOfTaxon
,
2243 taxonNames
, parentName
,
2246 inferredSynonyms
.add(inferredEpithet
);
2247 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2248 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2252 if (!taxonNames
.isEmpty()){
2253 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2254 IZoologicalName name
;
2255 if (!synNotInCDM
.isEmpty()){
2256 inferredSynonymsToBeRemoved
.clear();
2258 for (Synonym syn
:inferredSynonyms
){
2259 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2260 if (!synNotInCDM
.contains(name
.getNameCache())){
2261 inferredSynonymsToBeRemoved
.add(syn
);
2265 // Remove identified Synonyms from inferredSynonyms
2266 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2267 inferredSynonyms
.remove(synonym
);
2272 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2274 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2276 inferredGenus
= createInferredGenus(taxon
,
2277 zooHashMap
, taxonName
, epithetOfTaxon
,
2278 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2280 inferredSynonyms
.add(inferredGenus
);
2281 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2282 taxonNames
.add(inferredGenus
.getName().getNameCache());
2285 if (doWithMisappliedNames
){
2287 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2288 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2289 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2291 inferredSynonyms
.add(inferredGenus
);
2292 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2293 taxonNames
.add(inferredGenus
.getName().getNameCache());
2298 if (!taxonNames
.isEmpty()){
2299 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2300 IZoologicalName name
;
2301 if (!synNotInCDM
.isEmpty()){
2302 inferredSynonymsToBeRemoved
.clear();
2304 for (Synonym syn
:inferredSynonyms
){
2305 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2306 if (!synNotInCDM
.contains(name
.getNameCache())){
2307 inferredSynonymsToBeRemoved
.add(syn
);
2311 // Remove identified Synonyms from inferredSynonyms
2312 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2313 inferredSynonyms
.remove(synonym
);
2318 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2320 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2321 //for all synonyms of the parent...
2322 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2324 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2326 synName
= synonymRelationOfParent
.getName();
2328 // Set the sourceReference
2329 sourceReference
= synonymRelationOfParent
.getSec();
2331 // Determine the idInSource
2332 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2334 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2335 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2336 String synParentInfragenericName
= null;
2337 String synParentSpecificEpithet
= null;
2339 if (parentSynZooName
.isInfraGeneric()){
2340 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2342 if (parentSynZooName
.isSpecies()){
2343 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2346 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2347 synonymsGenus.put(synGenusName, idInSource);
2350 //for all synonyms of the taxon
2352 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2354 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2355 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2357 synParentInfragenericName
,
2358 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2360 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2361 inferredSynonyms
.add(potentialCombination
);
2362 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2363 taxonNames
.add(potentialCombination
.getName().getNameCache());
2369 if (doWithMisappliedNames
){
2371 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2373 TaxonName misappliedParentName
;
2375 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2376 misappliedParentName
= misappliedParent
.getName();
2378 HibernateProxyHelper
.deproxy(misappliedParent
);
2380 // Set the sourceReference
2381 sourceReference
= misappliedParent
.getSec();
2383 // Determine the idInSource
2384 String idInSourceParent
= getIdInSource(misappliedParent
);
2386 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2387 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2388 String synParentInfragenericName
= null;
2389 String synParentSpecificEpithet
= null;
2391 if (parentSynZooName
.isInfraGeneric()){
2392 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2394 if (parentSynZooName
.isSpecies()){
2395 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2399 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2400 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2401 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2402 potentialCombination
= createPotentialCombination(
2403 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2405 synParentInfragenericName
,
2406 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2409 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2410 inferredSynonyms
.add(potentialCombination
);
2411 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2412 taxonNames
.add(potentialCombination
.getName().getNameCache());
2417 if (!taxonNames
.isEmpty()){
2418 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2419 IZoologicalName name
;
2420 if (!synNotInCDM
.isEmpty()){
2421 inferredSynonymsToBeRemoved
.clear();
2422 for (Synonym syn
:inferredSynonyms
){
2424 name
= syn
.getName();
2425 }catch (ClassCastException e
){
2426 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2428 if (!synNotInCDM
.contains(name
.getNameCache())){
2429 inferredSynonymsToBeRemoved
.add(syn
);
2432 // Remove identified Synonyms from inferredSynonyms
2433 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2434 inferredSynonyms
.remove(synonym
);
2440 logger
.info("The synonym type is not defined.");
2441 return inferredSynonyms
;
2448 return inferredSynonyms
;
2451 private Synonym
createPotentialCombination(String idInSourceParent
,
2452 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2453 String synParentInfragenericName
, String synParentSpecificEpithet
,
2454 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2455 Synonym potentialCombination
;
2456 Reference sourceReference
;
2457 IZoologicalName inferredSynName
;
2458 HibernateProxyHelper
.deproxy(syn
);
2460 // Set sourceReference
2461 sourceReference
= syn
.getSec();
2462 if (sourceReference
== null){
2463 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2465 if (!parentSynZooName
.getTaxa().isEmpty()){
2466 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2468 sourceReference
= taxon
.getSec();
2471 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2473 String synTaxonInfraSpecificName
= null;
2475 if (parentSynZooName
.isSpecies()){
2476 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2479 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2480 synonymsEpithet.add(epithetName);
2483 //create potential combinations...
2484 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2486 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2487 if (zooSynName
.isSpecies()){
2488 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2489 if (parentSynZooName
.isInfraGeneric()){
2490 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2493 if (zooSynName
.isInfraSpecific()){
2494 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2495 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2497 if (parentSynZooName
.isInfraGeneric()){
2498 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2502 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2504 // Set the sourceReference
2505 potentialCombination
.setSec(sourceReference
);
2508 // Determine the idInSource
2509 String idInSourceSyn
= getIdInSource(syn
);
2511 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2512 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2513 inferredSynName
.addSource(originalSource
);
2514 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2515 potentialCombination
.addSource(originalSource
);
2518 return potentialCombination
;
2521 private Synonym
createInferredGenus(Taxon taxon
,
2522 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2523 String epithetOfTaxon
, String genusOfTaxon
,
2524 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2527 Synonym inferredGenus
;
2529 IZoologicalName inferredSynName
;
2530 synName
=syn
.getName();
2531 HibernateProxyHelper
.deproxy(syn
);
2533 // Determine the idInSource
2534 String idInSourceSyn
= getIdInSource(syn
);
2535 String idInSourceTaxon
= getIdInSource(taxon
);
2536 // Determine the sourceReference
2537 Reference sourceReference
= syn
.getSec();
2539 //logger.warn(sourceReference.getTitleCache());
2541 synName
= syn
.getName();
2542 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2543 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2544 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2545 synonymsEpithet.add(synSpeciesEpithetName);
2548 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2549 //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...
2552 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2553 if (zooParentName
.isInfraGeneric()){
2554 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2557 if (taxonName
.isSpecies()){
2558 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2560 if (taxonName
.isInfraSpecific()){
2561 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2562 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2566 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2568 // Set the sourceReference
2569 inferredGenus
.setSec(sourceReference
);
2571 // Add the original source
2572 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2573 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2574 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2575 inferredGenus
.addSource(originalSource
);
2577 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2578 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2579 inferredSynName
.addSource(originalSource
);
2580 originalSource
= null;
2583 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2584 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2585 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2586 inferredGenus
.addSource(originalSource
);
2588 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2589 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2590 inferredSynName
.addSource(originalSource
);
2591 originalSource
= null;
2594 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2596 return inferredGenus
;
2599 private Synonym
createInferredEpithets(Taxon taxon
,
2600 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2601 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2602 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2603 TaxonName parentName
, TaxonBase
<?
> syn
) {
2605 Synonym inferredEpithet
;
2607 IZoologicalName inferredSynName
;
2608 HibernateProxyHelper
.deproxy(syn
);
2610 // Determine the idInSource
2611 String idInSourceSyn
= getIdInSource(syn
);
2612 String idInSourceTaxon
= getIdInSource(taxon
);
2613 // Determine the sourceReference
2614 Reference sourceReference
= syn
.getSec();
2616 if (sourceReference
== null){
2617 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2618 sourceReference
= taxon
.getSec();
2621 synName
= syn
.getName();
2622 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2623 String synGenusName
= zooSynName
.getGenusOrUninomial();
2624 String synInfraGenericEpithet
= null;
2625 String synSpecificEpithet
= null;
2627 if (zooSynName
.getInfraGenericEpithet() != null){
2628 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2631 if (zooSynName
.isInfraSpecific()){
2632 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2635 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2636 synonymsGenus.put(synGenusName, idInSource);
2639 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2641 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2642 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2643 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2645 inferredSynName
.setGenusOrUninomial(synGenusName
);
2647 if (parentName
.isInfraGeneric()){
2648 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2650 if (taxonName
.isSpecies()){
2651 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2652 }else if (taxonName
.isInfraSpecific()){
2653 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2654 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2657 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2659 // Set the sourceReference
2660 inferredEpithet
.setSec(sourceReference
);
2662 /* Add the original source
2663 if (idInSource != null) {
2664 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2667 Reference citation = getCitation(syn);
2668 if (citation != null) {
2669 originalSource.setCitation(citation);
2670 inferredEpithet.addSource(originalSource);
2673 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2676 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2677 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2679 inferredEpithet
.addSource(originalSource
);
2681 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2682 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2684 inferredSynName
.addSource(originalSource
);
2688 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2690 return inferredEpithet
;
2694 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2695 * Very likely only useful for createInferredSynonyms().
2700 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2701 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2702 if (taxonName
== null) {
2703 taxonName
= zooHashMap
.get(uuid
);
2709 * Returns the idInSource for a given Synonym.
2712 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2713 String idInSource
= null;
2714 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2715 if (sources
.size() == 1) {
2716 IdentifiableSource source
= sources
.iterator().next();
2717 if (source
!= null) {
2718 idInSource
= source
.getIdInSource();
2720 } else if (sources
.size() > 1) {
2723 for (IdentifiableSource source
: sources
) {
2724 idInSource
+= source
.getIdInSource();
2725 if (count
< sources
.size()) {
2730 } else if (sources
.size() == 0){
2731 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2740 * Returns the citation for a given Synonym.
2743 private Reference
getCitation(Synonym syn
) {
2744 Reference citation
= null;
2745 Set
<IdentifiableSource
> sources
= syn
.getSources();
2746 if (sources
.size() == 1) {
2747 IdentifiableSource source
= sources
.iterator().next();
2748 if (source
!= null) {
2749 citation
= source
.getCitation();
2751 } else if (sources
.size() > 1) {
2752 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2759 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2760 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2762 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2763 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2764 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2766 return inferredSynonyms
;
2770 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2772 // TODO quickly implemented, create according dao !!!!
2773 Set
<TaxonNode
> nodes
= new HashSet
<>();
2774 Set
<Classification
> classifications
= new HashSet
<>();
2775 List
<Classification
> list
= new ArrayList
<>();
2777 if (taxonBase
== null) {
2781 taxonBase
= load(taxonBase
.getUuid());
2783 if (taxonBase
instanceof Taxon
) {
2784 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2786 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2788 nodes
.addAll(taxon
.getTaxonNodes());
2791 for (TaxonNode node
: nodes
) {
2792 classifications
.add(node
.getClassification());
2794 list
.addAll(classifications
);
2799 @Transactional(readOnly
= false)
2800 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2802 TaxonRelationshipType oldRelationshipType
,
2803 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2804 UpdateResult result
= new UpdateResult();
2805 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2806 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2807 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2809 result
.addUpdatedObject(fromTaxon
);
2810 result
.addUpdatedObject(toTaxon
);
2811 result
.addUpdatedObject(result
.getCdmEntity());
2817 @Transactional(readOnly
= false)
2818 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2819 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2821 UpdateResult result
= new UpdateResult();
2822 // Create new synonym using concept name
2823 TaxonName synonymName
= fromTaxon
.getName();
2825 // Remove concept relation from taxon
2826 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
2828 // Create a new synonym for the taxon
2830 if (synonymType
!= null
2831 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
2832 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
2833 toTaxon
.addHomotypicSynonym(synonym
);
2835 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
2838 this.saveOrUpdate(toTaxon
);
2839 //TODO: configurator and classification
2840 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
2841 config
.setDeleteNameIfPossible(false);
2842 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
2843 result
.setCdmEntity(synonym
);
2844 result
.addUpdatedObject(toTaxon
);
2845 result
.addUpdatedObject(synonym
);
2850 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
2851 DeleteResult result
= new DeleteResult();
2852 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
2853 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
2854 if (taxonBase
instanceof Taxon
){
2855 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
2856 result
= isDeletableForTaxon(references
, taxonConfig
);
2858 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
2859 result
= isDeletableForSynonym(references
, synonymConfig
);
2864 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
2866 DeleteResult result
= new DeleteResult();
2867 for (CdmBase ref
: references
){
2868 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
2869 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
2870 result
.addException(new ReferencedObjectUndeletableException(message
));
2871 result
.addRelatedObject(ref
);
2879 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
2880 String message
= null;
2881 DeleteResult result
= new DeleteResult();
2882 for (CdmBase ref
: references
){
2883 if (!(ref
instanceof TaxonName
)){
2885 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
2886 message
= "The taxon can't be deleted as long as it has synonyms.";
2888 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
2889 message
= "The taxon can't be deleted as long as it has factual data.";
2892 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
2893 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
2895 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
2896 if (!config
.isDeleteMisappliedNamesAndInvalidDesignations() &&
2897 (((TaxonRelationship
)ref
).getType().isMisappliedNameOrInvalidDesignation())){
2898 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
2900 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
2903 if (ref
instanceof PolytomousKeyNode
){
2904 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
2907 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
2908 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
2912 /* //PolytomousKeyNode
2913 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
2914 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
2919 if (ref
.isInstanceOf(TaxonInteraction
.class)){
2920 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
2924 if (ref
.isInstanceOf(DeterminationEvent
.class)){
2925 message
= "Taxon can't be deleted as it is used in a determination event";
2928 if (message
!= null){
2929 result
.addException(new ReferencedObjectUndeletableException(message
));
2930 result
.addRelatedObject(ref
);
2939 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
2940 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
2942 //preliminary implementation
2944 Set
<Taxon
> taxa
= new HashSet
<>();
2945 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
2946 if (taxonBase
== null){
2947 return new IncludedTaxaDTO();
2948 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
2949 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
2951 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
2952 //TODO partial synonyms ??
2953 //TODO synonyms in general
2954 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
2955 taxa
.add(syn
.getAcceptedTaxon());
2957 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
2960 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
2962 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
2963 related
= makeRelatedIncluded(related
, result
, config
);
2970 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
2972 * @return the set of conceptually related taxa for further use
2975 * @param uncheckedTaxa
2976 * @param existingTaxa
2980 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
2983 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
2984 for (Taxon taxon
: uncheckedTaxa
){
2985 taxonNodes
.addAll(taxon
.getTaxonNodes());
2988 Set
<Taxon
> children
= new HashSet
<>();
2989 if (! config
.onlyCongruent
){
2990 for (TaxonNode node
: taxonNodes
){
2991 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
2992 for (TaxonNode child
: childNodes
){
2993 children
.add(child
.getTaxon());
2996 children
.remove(null); // just to be on the save side
2999 Iterator
<Taxon
> it
= children
.iterator();
3000 while(it
.hasNext()){
3001 UUID uuid
= it
.next().getUuid();
3002 if (existingTaxa
.contains(uuid
)){
3005 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3010 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3011 uncheckedAndChildren
.addAll(children
);
3013 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3016 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3021 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3022 * @return the set of these computed taxa
3024 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3025 Set
<Taxon
> result
= new HashSet
<>();
3027 for (Taxon taxon
: unchecked
){
3028 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3029 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3031 for (TaxonRelationship fromRel
: fromRelations
){
3032 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3035 if (fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3036 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.INCLUDES()) ||
3037 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3039 result
.add(fromRel
.getToTaxon());
3043 for (TaxonRelationship toRel
: toRelations
){
3044 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3047 if (toRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO())){
3048 result
.add(toRel
.getFromTaxon());
3053 Iterator
<Taxon
> it
= result
.iterator();
3054 while(it
.hasNext()){
3055 UUID uuid
= it
.next().getUuid();
3056 if (existingTaxa
.contains(uuid
)){
3059 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3066 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3067 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3068 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3073 @Transactional(readOnly
= true)
3074 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3075 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3076 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3077 Integer pageNumber
, List
<String
> propertyPaths
) {
3078 if (subtreeFilter
== null){
3079 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3082 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3083 List
<Object
[]> daoResults
= new ArrayList
<>();
3084 if(numberOfResults
> 0) { // no point checking again
3085 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3086 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3089 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3090 for (Object
[] daoObj
: daoResults
){
3092 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3094 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3097 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3101 @Transactional(readOnly
= true)
3102 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3103 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3104 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3105 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3106 if (subtreeFilter
== null){
3107 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3110 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3111 List
<Object
[]> daoResults
= new ArrayList
<>();
3112 if(numberOfResults
> 0) { // no point checking again
3113 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3114 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3117 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3118 for (Object
[] daoObj
: daoResults
){
3120 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3122 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3125 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3129 @Transactional(readOnly
= false)
3130 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3131 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3132 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3134 UpdateResult result
= new UpdateResult();
3135 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3136 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3137 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3143 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3144 UpdateResult result
= new UpdateResult();
3146 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3147 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3148 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3149 //reload to avoid session conflicts
3150 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3152 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3153 if(description
.isProtectedTitleCache()){
3154 String separator
= "";
3155 if(!StringUtils
.isBlank(description
.getTitleCache())){
3158 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3160 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3161 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3162 description
.addAnnotation(annotation
);
3163 toTaxon
.addDescription(description
);
3164 dao
.saveOrUpdate(toTaxon
);
3165 dao
.saveOrUpdate(fromTaxon
);
3166 result
.addUpdatedObject(toTaxon
);
3167 result
.addUpdatedObject(fromTaxon
);
3175 @Transactional(readOnly
= false)
3176 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3177 UUID acceptedTaxonUuid
) {
3178 TaxonBase
<?
> base
= this.load(synonymUUid
);
3179 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3180 base
= this.load(acceptedTaxonUuid
);
3181 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3183 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
);