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
.sandbox
.queries
.DuplicateFilter
;
29 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
30 import org
.apache
.lucene
.search
.BooleanQuery
;
31 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
32 import org
.apache
.lucene
.search
.Query
;
33 import org
.apache
.lucene
.search
.SortField
;
34 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
35 import org
.apache
.lucene
.search
.join
.ScoreMode
;
36 import org
.apache
.lucene
.util
.BytesRef
;
37 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
38 import org
.springframework
.stereotype
.Service
;
39 import org
.springframework
.transaction
.annotation
.Transactional
;
41 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
47 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
48 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
51 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
52 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
53 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
54 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
55 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
56 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
57 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
58 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
64 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
65 import eu
.etaxonomy
.cdm
.api
.service
.util
.TaxonRelationshipEdge
;
66 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
67 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
68 import eu
.etaxonomy
.cdm
.hibernate
.search
.DefinedTermBaseClassBridge
;
69 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
70 import eu
.etaxonomy
.cdm
.hibernate
.search
.MultilanguageTextFieldBridge
;
71 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
72 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
73 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
74 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
75 import eu
.etaxonomy
.cdm
.model
.common
.DefinedTerm
;
76 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
77 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
78 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
79 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
80 import eu
.etaxonomy
.cdm
.model
.common
.OriginalSourceType
;
81 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
82 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
83 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
84 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
85 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
86 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
87 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
88 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
89 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
90 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
91 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
92 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
93 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
94 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
95 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
96 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentation
;
97 import eu
.etaxonomy
.cdm
.model
.media
.MediaUtils
;
98 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
99 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
100 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
101 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
102 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
103 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
104 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
105 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
106 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
107 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
108 import eu
.etaxonomy
.cdm
.model
.taxon
.HomotypicGroupTaxonComparator
;
109 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
110 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
117 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.ICdmGenericDao
;
118 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.IOrderedTermVocabularyDao
;
119 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
120 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
121 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
122 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
123 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
124 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
125 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
126 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
127 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
128 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
129 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
130 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
134 * @author a.kohlbecker
138 @Transactional(readOnly
= true)
139 public class TaxonServiceImpl
extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
> implements ITaxonService
{
140 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
142 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
144 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
146 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
149 private ITaxonNodeDao taxonNodeDao
;
152 private ITaxonNameDao nameDao
;
155 private INameService nameService
;
158 private IOccurrenceService occurrenceService
;
161 private ITaxonNodeService nodeService
;
164 private ICdmGenericDao genericDao
;
167 private IDescriptionService descriptionService
;
170 private IOrderedTermVocabularyDao orderedVocabularyDao
;
173 private IOccurrenceDao occurrenceDao
;
176 private IClassificationDao classificationDao
;
179 private AbstractBeanInitializer beanInitializer
;
182 private ILuceneIndexToolProvider luceneIndexToolProvider
;
187 public TaxonServiceImpl(){
188 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
192 * FIXME Candidate for harmonization
193 * rename searchByName ?
196 public List
<TaxonBase
> searchTaxaByName(String name
, Reference sec
) {
197 return dao
.getTaxaByName(name
, 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
);
296 @Transactional(readOnly
= false)
297 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
299 TaxonRelationshipType taxonRelationshipType
,
301 String microcitation
){
303 UpdateResult result
= new UpdateResult();
304 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
305 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
306 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
307 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
308 // result.setCdmEntity(relatedTaxon);
309 result
.addUpdatedObject(relatedTaxon
);
310 result
.addUpdatedObject(toTaxon
);
315 @Transactional(readOnly
= false)
316 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
317 UpdateResult result
= new UpdateResult();
318 // Get name from synonym
319 if (synonym
== null){
322 TaxonName synonymName
= synonym
.getName();
324 /* // remove synonym from taxon
325 toTaxon.removeSynonym(synonym);
327 // Create a taxon with synonym name
328 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
329 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
331 // Add taxon relation
332 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
333 result
.setCdmEntity(fromTaxon
);
334 // since we are swapping names, we have to detach the name from the synonym completely.
335 // Otherwise the synonym will still be in the list of typified names.
336 // synonym.getName().removeTaxonBase(synonym);
337 result
.includeResult(this.deleteSynonym(synonym
, null));
342 @Transactional(readOnly
= false)
344 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
345 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
347 TaxonName synonymName
= synonym
.getName();
348 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
351 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
352 newHomotypicalGroup
.addTypifiedName(synonymName
);
354 //remove existing basionym relationships
355 synonymName
.removeBasionyms();
357 //add basionym relationship
358 if (setBasionymRelationIfApplicable
){
359 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
360 for (TaxonName basionym
: basionyms
){
361 synonymName
.addBasionym(basionym
);
365 //set synonym relationship correctly
366 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
368 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
369 if (acceptedTaxon
!= null){
371 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
372 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
373 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
374 synonym
.setType(newRelationType
);
376 if (hasNewTargetTaxon
){
377 acceptedTaxon
.removeSynonym(synonym
, false);
380 if (hasNewTargetTaxon
){
381 @SuppressWarnings("null")
382 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
383 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
384 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
385 targetTaxon
.addSynonym(synonym
, relType
);
391 @Transactional(readOnly
= false)
392 public void updateTitleCache(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
394 clazz
= TaxonBase
.class;
396 super.updateTitleCacheImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
401 protected void setDao(ITaxonDao dao
) {
406 public Pager
<TaxonBase
> findTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
408 clazz
= TaxonBase
.class;
410 Integer numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
412 List
<TaxonBase
> results
= new ArrayList
<>();
413 if(numberOfResults
> 0) { // no point checking again
414 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
417 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
421 public List
<TaxonBase
> listTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
423 clazz
= TaxonBase
.class;
425 Integer 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
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
437 Integer numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, TaxonRelationship
.Direction
.relatedTo
);
439 List
<TaxonRelationship
> results
= new ArrayList
<>();
440 if(numberOfResults
> 0) { // no point checking again
441 results
= dao
.getTaxonRelationships(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
447 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
448 Integer numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, TaxonRelationship
.Direction
.relatedTo
);
450 List
<TaxonRelationship
> results
= new ArrayList
<>();
451 if(numberOfResults
> 0) { // no point checking again
452 results
= dao
.getTaxonRelationships(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
454 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
458 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
459 Integer numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, TaxonRelationship
.Direction
.relatedFrom
);
461 List
<TaxonRelationship
> results
= new ArrayList
<>();
462 if(numberOfResults
> 0) { // no point checking again
463 results
= dao
.getTaxonRelationships(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
469 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
470 Integer numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, TaxonRelationship
.Direction
.relatedFrom
);
472 List
<TaxonRelationship
> results
= new ArrayList
<>();
473 if(numberOfResults
> 0) { // no point checking again
474 results
= dao
.getTaxonRelationships(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
476 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
480 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
481 Integer pageSize
, Integer pageStart
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
482 Long numberOfResults
= dao
.countTaxonRelationships(types
);
484 List
<TaxonRelationship
> results
= new ArrayList
<>();
485 if(numberOfResults
> 0) {
486 results
= dao
.getTaxonRelationships(types
, pageSize
, pageStart
, orderHints
, propertyPaths
);
492 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
, List
<String
> propertyPaths
){
497 Synonym synonym
= null;
500 synonym
= (Synonym
) dao
.load(synonymUuid
);
501 } catch (ClassCastException e
){
502 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
503 } catch (NullPointerException e
){
504 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
507 Classification classificationFilter
= null;
508 if(classificationUuid
!= null){
510 classificationFilter
= classificationDao
.load(classificationUuid
);
511 } catch (NullPointerException e
){
512 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
514 if(classificationFilter
== null){
519 count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
521 result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
529 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
530 Integer limit
, Integer start
, List
<String
> propertyPaths
) {
532 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), maxDepth
);
533 relatedTaxa
.remove(taxon
);
534 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
540 * recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
541 * <code>taxon</code> supplied as parameter.
544 * @param includeRelationships
546 * @param maxDepth can be <code>null</code> for infinite depth
549 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
, Integer maxDepth
) {
555 if(includeRelationships
.isEmpty()){
559 if(maxDepth
!= null) {
562 if(logger
.isDebugEnabled()){
563 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
565 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
, null, null, null, null, null, null);
566 for (TaxonRelationship taxRel
: taxonRelationships
) {
569 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
572 // filter by includeRelationships
573 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
574 if ( relationshipEdgeFilter
.getTaxonRelationshipTypes().equals(taxRel
.getType()) ) {
575 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
576 if(logger
.isDebugEnabled()){
577 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
579 taxa
.add(taxRel
.getToTaxon());
580 if(maxDepth
== null || maxDepth
> 0) {
581 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, maxDepth
));
584 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
585 taxa
.add(taxRel
.getFromTaxon());
586 if(logger
.isDebugEnabled()){
587 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
589 if(maxDepth
== null || maxDepth
> 0) {
590 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, maxDepth
));
600 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
601 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
603 List
<Synonym
> results
= new ArrayList
<>();
604 if(numberOfResults
> 0) { // no point checking again
605 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
608 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
612 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
613 List
<List
<Synonym
>> result
= new ArrayList
<>();
614 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
615 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
619 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
622 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
623 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
624 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
632 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
633 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
634 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
636 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
640 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
641 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
642 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
643 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
644 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
645 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
647 return heterotypicSynonymyGroups
;
651 public List
<UuidAndTitleCache
<IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator
){
653 List
<UuidAndTitleCache
<IdentifiableEntity
>> results
= new ArrayList
<>();
656 if (configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoNamesWithoutTaxa() || configurator
.isDoTaxaByCommonNames()){
657 results
= dao
.getTaxaByNameForEditor(configurator
.isDoTaxa(), configurator
.isDoSynonyms(), configurator
.isDoNamesWithoutTaxa(), configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.getOrder());
664 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
666 List
<IdentifiableEntity
> results
= new ArrayList
<>();
667 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
668 List
<TaxonBase
> taxa
= null;
671 long numberTaxaResults
= 0L;
674 List
<String
> propertyPath
= new ArrayList
<>();
675 if(configurator
.getTaxonPropertyPath() != null){
676 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
680 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
681 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
683 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
684 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
685 configurator
.getClassification(), configurator
.getMatchMode(),
686 configurator
.getNamedAreas());
689 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
690 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
691 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
692 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(),
693 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.getOrder(),
694 configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
698 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
701 results
.addAll(taxa
);
704 numberOfResults
+= numberTaxaResults
;
706 // Names without taxa
707 if (configurator
.isDoNamesWithoutTaxa()) {
708 int numberNameResults
= 0;
710 List
<?
extends TaxonName
> names
=
711 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
712 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
713 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
714 if (names
.size() > 0) {
715 for (TaxonName taxonName
: names
) {
716 if (taxonName
.getTaxonBases().size() == 0) {
717 results
.add(taxonName
);
721 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
722 numberOfResults
+= numberNameResults
;
728 return new DefaultPagerImpl
<>
729 (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
732 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
733 return dao
.getUuidAndTitleCache(limit
, pattern
);
737 public List
<MediaRepresentation
> getAllMedia(Taxon taxon
, int size
, int height
, int widthOrDuration
, String
[] mimeTypes
){
738 List
<MediaRepresentation
> medRep
= new ArrayList
<>();
739 taxon
= (Taxon
)dao
.load(taxon
.getUuid());
740 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
741 for (TaxonDescription taxDesc
: descriptions
){
742 Set
<DescriptionElementBase
> elements
= taxDesc
.getElements();
743 for (DescriptionElementBase descElem
: elements
){
744 for(Media media
: descElem
.getMedia()){
746 //find the best matching representation
747 medRep
.add(MediaUtils
.findBestMatchingRepresentation(media
, null, size
, height
, widthOrDuration
, mimeTypes
));
756 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
757 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
761 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
762 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
763 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
765 // logger.setLevel(Level.TRACE);
766 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
768 logger
.trace("listMedia() - START");
770 Set
<Taxon
> taxa
= new HashSet
<>();
771 List
<Media
> taxonMedia
= new ArrayList
<>();
772 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
774 if (limitToGalleries
== null) {
775 limitToGalleries
= false;
778 // --- resolve related taxa
779 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
780 logger
.trace("listMedia() - resolve related taxa");
781 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, null, null, null);
784 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
786 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
787 logger
.trace("listMedia() - includeTaxonDescriptions");
788 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
789 // --- TaxonDescriptions
790 for (Taxon t
: taxa
) {
791 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
793 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
794 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
795 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
796 for (Media media
: element
.getMedia()) {
797 if(taxonDescription
.isImageGallery()){
798 taxonMedia
.add(media
);
801 nonImageGalleryImages
.add(media
);
807 //put images from image gallery first (#3242)
808 taxonMedia
.addAll(nonImageGalleryImages
);
812 if(includeOccurrences
!= null && includeOccurrences
) {
813 logger
.trace("listMedia() - includeOccurrences");
814 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
816 for (Taxon t
: taxa
) {
817 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
819 for (SpecimenOrObservationBase occurrence
: specimensOrObservations
) {
821 // direct media removed from specimen #3597
822 // taxonMedia.addAll(occurrence.getMedia());
824 // SpecimenDescriptions
825 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
826 for (DescriptionBase specimenDescription
: specimenDescriptions
) {
827 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
828 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
829 for (DescriptionElementBase element
: elements
) {
830 for (Media media
: element
.getMedia()) {
831 taxonMedia
.add(media
);
837 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
838 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
840 //TODO why may collections have media attached? #
841 if (derivedUnit
.getCollection() != null){
842 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
846 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
850 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
851 logger
.trace("listMedia() - includeTaxonNameDescriptions");
852 // --- TaxonNameDescription
853 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
854 for (Taxon t
: taxa
) {
855 nameDescriptions
.addAll(t
.getName().getDescriptions());
857 for(TaxonNameDescription nameDescription
: nameDescriptions
){
858 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
859 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
860 for (DescriptionElementBase element
: elements
) {
861 for (Media media
: element
.getMedia()) {
862 taxonMedia
.add(media
);
870 logger
.trace("listMedia() - initialize");
871 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
873 logger
.trace("listMedia() - END");
879 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
880 return this.dao
.loadList(listOfIDs
, null);
884 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
885 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
889 public int countSynonyms(boolean onlyAttachedToTaxon
){
890 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
894 public List
<TaxonName
> findIdenticalTaxonNames(List
<String
> propertyPath
) {
895 return this.dao
.findIdenticalTaxonNames(propertyPath
);
899 @Transactional(readOnly
=false)
900 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
903 config
= new TaxonDeletionConfigurator();
905 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
906 DeleteResult result
= new DeleteResult();
909 result
.addException(new Exception ("The taxon was already deleted."));
912 taxon
= HibernateProxyHelper
.deproxy(taxon
);
913 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
914 result
= isDeletable(taxonUUID
, config
);
917 // --- DeleteSynonymRelations
918 if (config
.isDeleteSynonymRelations()){
919 boolean removeSynonymNameFromHomotypicalGroup
= false;
920 // use tmp Set to avoid concurrent modification
921 Set
<Synonym
> synsToDelete
= new HashSet
<>();
922 synsToDelete
.addAll(taxon
.getSynonyms());
923 for (Synonym synonym
: synsToDelete
){
924 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
926 // --- DeleteSynonymsIfPossible
927 if (config
.isDeleteSynonymsIfPossible()){
929 boolean newHomotypicGroupIfNeeded
= true;
930 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
931 deleteSynonym(synonym
, synConfig
);
936 // --- DeleteTaxonRelationships
937 if (! config
.isDeleteTaxonRelationships()){
938 if (taxon
.getTaxonRelations().size() > 0){
940 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
941 "Remove taxon from all relations to other taxa prior to deletion."));
945 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
946 configRelTaxon
.setDeleteTaxonNodes(false);
947 configRelTaxon
.setDeleteConceptRelationships(true);
949 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
950 if (config
.isDeleteMisappliedNamesAndInvalidDesignations()
951 && taxRel
.getType().isMisappliedNameOrInvalidDesignation()
952 && taxon
.equals(taxRel
.getToTaxon())){
953 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
954 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
956 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
957 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
958 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
959 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
962 taxon
.removeTaxonRelation(taxRel
);
968 if (config
.isDeleteDescriptions()){
969 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
970 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
971 for (TaxonDescription desc
: descriptions
){
972 //TODO use description delete configurator ?
973 //FIXME check if description is ALWAYS deletable
974 if (desc
.getDescribedSpecimenOrObservation() != null){
976 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
977 " which also describes specimens or observations"));
980 removeDescriptions
.add(desc
);
985 for (TaxonDescription desc
: removeDescriptions
){
986 taxon
.removeDescription(desc
);
987 descriptionService
.delete(desc
);
995 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
996 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
998 if (taxon
.getTaxonNodes().size() != 0){
999 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1000 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1001 TaxonNode node
= null;
1002 boolean deleteChildren
;
1003 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1004 deleteChildren
= true;
1006 deleteChildren
= false;
1008 boolean success
= true;
1009 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1010 while (iterator
.hasNext()){
1011 node
= iterator
.next();
1012 if (node
.getClassification().equals(classification
)){
1018 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1019 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1020 nodeService
.delete(node
);
1023 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1025 } else if (config
.isDeleteInAllClassifications()){
1026 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1027 nodesList
.addAll(taxon
.getTaxonNodes());
1028 for (ITaxonTreeNode treeNode
: nodesList
){
1029 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1030 if(!deleteChildren
){
1031 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1032 for (Object childNode
: childNodes
){
1033 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1034 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1038 config
.getTaxonNodeConfig().setDeleteElement(false);
1039 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1040 if (!resultNodes
.isOk()){
1041 result
.addExceptions(resultNodes
.getExceptions());
1042 result
.setStatus(resultNodes
.getStatus());
1044 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1049 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1053 TaxonName name
= taxon
.getName();
1054 taxon
.setName(null);
1055 this.saveOrUpdate(taxon
);
1057 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1059 //taxon.setName(null);
1060 UUID uuid
= dao
.delete(taxon
);
1062 }catch(Exception e
){
1063 result
.addException(e
);
1069 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1073 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1074 // name = HibernateProxyHelper.deproxy(name);
1076 DeleteResult nameResult
= new DeleteResult();
1077 //remove name if possible (and required)
1079 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1081 if (nameResult
.isError() || nameResult
.isAbort()){
1082 result
.addRelatedObject(name
);
1083 result
.addExceptions(nameResult
.getExceptions());
1099 private String
checkForReferences(Taxon taxon
){
1100 Set
<CdmBase
> referencingObjects
= genericDao
.getReferencingObjects(taxon
);
1101 for (CdmBase referencingObject
: referencingObjects
){
1102 //IIdentificationKeys (Media, Polytomous, MultiAccess)
1103 if (HibernateProxyHelper
.isInstanceOf(referencingObject
, IIdentificationKey
.class)){
1104 String message
= "Taxon" + taxon
.getTitleCache() + "can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
1110 /* //PolytomousKeyNode
1111 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1112 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1117 if (referencingObject
.isInstanceOf(TaxonInteraction
.class)){
1118 String message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1123 if (referencingObject
.isInstanceOf(DeterminationEvent
.class)){
1124 String message
= "Taxon can't be deleted as it is used in a determination event";
1130 referencingObjects
= null;
1134 private boolean checkForPolytomousKeys(Taxon taxon
){
1135 boolean result
= false;
1136 List
<CdmBase
> list
= genericDao
.getCdmBasesByFieldAndClass(PolytomousKeyNode
.class, "taxon", taxon
, null);
1137 if (!list
.isEmpty()) {
1144 @Transactional(readOnly
= false)
1145 public DeleteResult
delete(UUID synUUID
){
1146 DeleteResult result
= new DeleteResult();
1147 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1149 return this.deleteSynonym(syn
, null);
1153 @Transactional(readOnly
= false)
1154 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1155 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1161 @Transactional(readOnly
= false)
1162 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1163 DeleteResult result
= new DeleteResult();
1164 if (synonym
== null){
1166 result
.addException(new Exception("The synonym was already deleted."));
1170 if (config
== null){
1171 config
= new SynonymDeletionConfigurator();
1174 result
= isDeletable(synonym
.getUuid(), config
);
1179 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1182 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1184 if (accTaxon
!= null){
1185 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1186 accTaxon
.removeSynonym(synonym
, false);
1187 this.saveOrUpdate(accTaxon
);
1188 result
.addUpdatedObject(accTaxon
);
1190 this.saveOrUpdate(synonym
);
1194 TaxonName name
= synonym
.getName();
1195 synonym
.setName(null);
1197 dao
.delete(synonym
);
1200 //remove name if possible (and required)
1201 if (name
!= null && config
.isDeleteNameIfPossible()){
1203 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1204 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1205 result
.addExceptions(nameDeleteResult
.getExceptions());
1206 result
.addRelatedObject(name
);
1215 public List
<TaxonName
> findIdenticalTaxonNameIds(List
<String
> propertyPath
) {
1217 return this.dao
.findIdenticalNamesNew(propertyPath
);
1221 // public String getPhylumName(TaxonName name){
1222 // return this.dao.getPhylumName(name);
1226 public Taxon
findBestMatchingTaxon(String taxonName
) {
1227 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1228 config
.setTaxonNameTitle(taxonName
);
1229 return findBestMatchingTaxon(config
);
1233 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1235 Taxon bestCandidate
= null;
1237 // 1. search for accepted taxa
1238 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, null, null);
1239 boolean bestCandidateMatchesSecUuid
= false;
1240 boolean bestCandidateIsInClassification
= false;
1241 int countEqualCandidates
= 0;
1242 for(TaxonBase taxonBaseCandidate
: taxonList
){
1243 if(taxonBaseCandidate
instanceof Taxon
){
1244 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1245 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1246 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1248 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1249 bestCandidate
= newCanditate
;
1250 countEqualCandidates
= 1;
1251 bestCandidateMatchesSecUuid
= true;
1255 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1256 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1258 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1259 bestCandidate
= newCanditate
;
1260 countEqualCandidates
= 1;
1261 bestCandidateIsInClassification
= true;
1264 if (bestCandidate
== null){
1265 bestCandidate
= newCanditate
;
1266 countEqualCandidates
= 1;
1270 }else{ //not Taxon.class
1273 countEqualCandidates
++;
1276 if (bestCandidate
!= null){
1277 if(countEqualCandidates
> 1){
1278 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1279 return bestCandidate
;
1281 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1282 return bestCandidate
;
1287 // 2. search for synonyms
1288 if (config
.isIncludeSynonyms()){
1289 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, null, null);
1290 for(TaxonBase taxonBase
: synonymList
){
1291 if(taxonBase
instanceof Synonym
){
1292 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1293 bestCandidate
= synonym
.getAcceptedTaxon();
1294 if(bestCandidate
!= null){
1295 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1296 return bestCandidate
;
1298 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1303 } catch (Exception e
){
1305 e
.printStackTrace();
1308 return bestCandidate
;
1311 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1312 UUID configClassificationUuid
= config
.getClassificationUuid();
1313 if (configClassificationUuid
== null){
1316 for (TaxonNode node
: taxon
.getTaxonNodes()){
1317 UUID classUuid
= node
.getClassification().getUuid();
1318 if (configClassificationUuid
.equals(classUuid
)){
1325 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1326 UUID configSecUuid
= config
.getSecUuid();
1327 if (configSecUuid
== null){
1330 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1331 return configSecUuid
.equals(taxonSecUuid
);
1335 public Synonym
findBestMatchingSynonym(String taxonName
) {
1336 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, taxonName
, null, MatchMode
.EXACT
, null, null, 0, null, null);
1337 if(! synonymList
.isEmpty()){
1338 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1339 if(synonymList
.size() == 1){
1340 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1343 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1351 @Transactional(readOnly
= false)
1352 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1354 boolean moveHomotypicGroup
,
1355 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1356 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1358 oldSynonym
.getSec(),
1359 oldSynonym
.getSecMicroReference(),
1364 @Transactional(readOnly
= false)
1365 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1367 boolean moveHomotypicGroup
,
1368 SynonymType newSynonymType
,
1369 Reference newSecundum
,
1370 String newSecundumDetail
,
1371 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1373 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1374 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1375 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1376 TaxonName synonymName
= synonym
.getName();
1377 TaxonName fromTaxonName
= oldTaxon
.getName();
1378 //set default relationship type
1379 if (newSynonymType
== null){
1380 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1382 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1384 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1385 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1386 boolean isSingleInGroup
= !(hgSize
> 1);
1388 if (! isSingleInGroup
){
1389 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1390 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1391 if (isHomotypicToAccepted
){
1392 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.";
1393 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1394 message
= String
.format(message
, homotypicRelatives
);
1395 throw new HomotypicalGroupChangeException(message
);
1397 if (! moveHomotypicGroup
){
1398 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.";
1399 throw new HomotypicalGroupChangeException(message
);
1402 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1404 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1406 UpdateResult result
= new UpdateResult();
1407 //move all synonyms to new taxon
1408 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1409 for (Synonym synRelation
: homotypicSynonyms
){
1411 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1412 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1413 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1414 oldTaxon
.removeSynonym(synRelation
, false);
1415 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1416 synRelation
.setSec(newSecundum
);
1418 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1419 synRelation
.setSecMicroReference(newSecundumDetail
);
1422 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1423 if (!synRelation
.equals(oldSynonym
)){
1428 result
.addUpdatedObject(oldTaxon
);
1429 result
.addUpdatedObject(newTaxon
);
1430 saveOrUpdate(oldTaxon
);
1431 saveOrUpdate(newTaxon
);
1437 public <T
extends TaxonBase
> List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1438 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1442 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1443 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1444 Classification classification
, List
<Language
> languages
,
1445 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1448 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, languages
, highlightFragments
, null);
1450 // --- execute search
1451 TopGroups
<BytesRef
> topDocsResultSet
;
1453 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1454 } catch (ParseException e
) {
1455 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1456 luceneParseException
.setStackTrace(e
.getStackTrace());
1457 throw luceneParseException
;
1460 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1461 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1463 // --- initialize taxa, thighlight matches ....
1464 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1465 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1466 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1468 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
1469 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1473 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1474 Classification classification
,
1475 Integer pageSize
, Integer pageNumber
,
1476 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1478 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
);
1480 // --- execute search
1481 TopGroups
<BytesRef
> topDocsResultSet
;
1483 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1484 } catch (ParseException e
) {
1485 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1486 luceneParseException
.setStackTrace(e
.getStackTrace());
1487 throw luceneParseException
;
1490 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1491 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1493 // --- initialize taxa, thighlight matches ....
1494 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1495 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1496 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1498 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
1499 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1504 * @param queryString
1505 * @param classification
1507 * @param highlightFragments
1508 * @param sortFields TODO
1509 * @param directorySelectClass
1512 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
, Classification classification
, List
<Language
> languages
,
1513 boolean highlightFragments
, SortField
[] sortFields
) {
1514 Builder finalQueryBuilder
= new Builder();
1515 Builder textQueryBuilder
= new Builder();
1517 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1518 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1520 if(sortFields
== null){
1521 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1523 luceneSearch
.setSortFields(sortFields
);
1525 // ---- search criteria
1526 luceneSearch
.setCdmTypRestriction(clazz
);
1528 if(!queryString
.isEmpty() && !queryString
.equals("*") && !queryString
.equals("?") ) {
1529 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1530 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1533 BooleanQuery textQuery
= textQueryBuilder
.build();
1534 if(textQuery
.clauses().size() > 0) {
1535 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1539 if(classification
!= null){
1540 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1542 luceneSearch
.setQuery(finalQueryBuilder
.build());
1544 if(highlightFragments
){
1545 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1547 return luceneSearch
;
1551 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1552 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1553 * drawback of requiring to do the join an indexing time.
1554 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1556 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1558 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1559 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1561 * @param queryString
1562 * @param classification
1564 * @param highlightFragments
1565 * @param sortFields TODO
1568 * @throws IOException
1570 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
, Classification classification
, List
<Language
> languages
,
1571 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1574 String queryTermField
;
1575 String toField
= "id"; // TaxonBase.uuid
1577 if(edge
.isBidirectional()){
1578 throw new RuntimeException("Bidirectional joining not supported!");
1581 fromField
= "relatedFrom.id";
1582 queryTermField
= "relatedFrom.titleCache";
1583 } else if(edge
.isInvers()) {
1584 fromField
= "relatedTo.id";
1585 queryTermField
= "relatedTo.titleCache";
1587 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1590 Builder finalQueryBuilder
= new Builder();
1592 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1593 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1595 Builder joinFromQueryBuilder
= new Builder();
1596 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1597 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getTaxonRelationshipTypes()), Occur
.MUST
);
1599 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1601 if(sortFields
== null){
1602 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1604 luceneSearch
.setSortFields(sortFields
);
1606 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1608 if(classification
!= null){
1609 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1611 luceneSearch
.setQuery(finalQueryBuilder
.build());
1613 if(highlightFragments
){
1614 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1616 return luceneSearch
;
1620 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1621 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
, Classification classification
,
1622 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1623 boolean highlightFragments
, Integer pageSize
,
1624 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1625 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1627 // FIXME: allow taxonomic ordering
1628 // 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";
1629 // this require building a special sort column by a special classBridge
1630 if(highlightFragments
){
1631 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1632 "currently not fully supported by this method and thus " +
1633 "may not work with common names and misapplied names.");
1636 // convert sets to lists
1637 List
<NamedArea
> namedAreaList
= null;
1638 List
<PresenceAbsenceTerm
>distributionStatusList
= null;
1639 if(namedAreas
!= null){
1640 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1641 namedAreaList
.addAll(namedAreas
);
1643 if(distributionStatus
!= null){
1644 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1645 distributionStatusList
.addAll(distributionStatus
);
1648 // set default if parameter is null
1649 if(searchModes
== null){
1650 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1653 // set sort order and thus override any sort orders which may have been
1654 // defined by prepare*Search methods
1655 if(orderHints
== null){
1656 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1658 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1660 for(OrderHint oh
: orderHints
){
1661 sortFields
[i
++] = oh
.toSortField();
1663 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1664 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1667 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1669 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1670 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1673 ======== filtering by distribution , HOWTO ========
1675 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1676 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1677 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1678 which will be put into a FilteredQuersy in the end ?
1681 3. how does it work in spatial?
1683 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1684 - http://www.infoq.com/articles/LuceneSpatialSupport
1685 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1686 ------------------------------------------------------------------------
1689 A) use a separate distribution filter per index sub-query/search:
1690 - byTaxonSyonym (query TaxaonBase):
1691 use a join area filter (Distribution -> TaxonBase)
1692 - byCommonName (query DescriptionElementBase): use an area filter on
1693 DescriptionElementBase !!! PROBLEM !!!
1694 This cannot work since the distributions are different entities than the
1695 common names and thus these are different lucene documents.
1696 - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1697 use a join area filter (Distribution -> TaxonBase)
1699 B) use a common distribution filter for all index sub-query/searches:
1700 - use a common join area filter (Distribution -> TaxonBase)
1701 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1702 PROBLEM in this case: we are losing the fragment highlighting for the
1703 common names, since the returned documents are always TaxonBases
1706 /* The QueryFactory for creating filter queries on Distributions should
1707 * The query factory used for the common names query cannot be reused
1708 * for this case, since we want to only record the text fields which are
1709 * actually used in the primary query
1711 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1713 Builder multiIndexByAreaFilterBuilder
= new Builder();
1715 // search for taxa or synonyms
1716 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1717 Class taxonBaseSubclass
= TaxonBase
.class;
1718 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1719 taxonBaseSubclass
= Taxon
.class;
1720 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1721 taxonBaseSubclass
= Synonym
.class;
1723 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
, queryString
, classification
, languages
, highlightFragments
, sortFields
));
1724 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1725 /* A) does not work!!!!
1726 if(addDistributionFilter){
1727 // in this case we need a filter which uses a join query
1728 // to get the TaxonBase documents for the DescriptionElementBase documents
1729 // which are matching the areas in question
1730 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1732 distributionStatusList,
1733 distributionFilterQueryFactory
1735 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1738 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1739 // add additional area filter for synonyms
1740 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1741 String toField
= "accTaxon.id"; // id in TaxonBase index (is multivalued)
1743 //TODO replace by createByDistributionJoinQuery
1744 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1745 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1746 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1751 // search by CommonTaxonName
1752 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1754 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1755 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1756 CommonTaxonName
.class,
1757 "inDescription.taxon.id",
1759 QueryFactory
.addTypeRestriction(
1760 createByDescriptionElementFullTextQuery(queryString
, classification
, null, languages
, descriptionElementQueryFactory
)
1761 , CommonTaxonName
.class
1762 ).build(), "id", null, ScoreMode
.Max
);
1763 logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());
1764 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1765 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1766 byCommonNameSearch
.setQuery(byCommonNameJoinQuery
);
1767 byCommonNameSearch
.setSortFields(sortFields
);
1769 DuplicateFilter df
= new DuplicateFilter("inDescription.taxon.id");
1770 Set
<String
> results
=new HashSet
<>();
1771 // ScoreDoc[] hits = searcher.search(tq,df, 1000).scoreDocs;
1773 // byCommonNameSearch.setFilter(df);
1774 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1776 luceneSearches
.add(byCommonNameSearch
);
1778 /* A) does not work!!!!
1780 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1781 queryString, classification, null, languages, highlightFragments)
1783 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1784 if(addDistributionFilter){
1785 // in this case we are able to use DescriptionElementBase documents
1786 // which are matching the areas in question directly
1787 BooleanQuery byDistributionQuery = createByDistributionQuery(
1789 distributionStatusList,
1790 distributionFilterQueryFactory
1792 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1796 // search by misapplied names
1797 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)) {
1799 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1800 // which allows doing query time joins
1801 // finds the misapplied name (Taxon B) which is an misapplication for
1802 // a related Taxon A.
1804 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1805 new TaxonRelationshipEdge(TaxonRelationshipType
.allMisappliedNameTypes(), Direction
.relatedTo
),
1806 queryString
, classification
, languages
, highlightFragments
, sortFields
));
1807 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1809 if(addDistributionFilter
){
1810 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1813 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
1814 * Maybe this is a bug in java itself java.
1816 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1819 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1821 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1822 * will execute as expected:
1824 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1825 * String toField = "relation." + misappliedNameForUuid +".to.id";
1827 * Comparing both strings by the String.equals method returns true, so both String are identical.
1829 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1830 * 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)
1831 * The bug is persistent after a reboot of the development computer.
1833 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1834 // String toField = "relation." + misappliedNameForUuid +".to.id";
1835 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1836 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1837 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1839 //TODO replace by createByDistributionJoinQuery
1840 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1841 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1843 // debug code for bug described above
1844 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1845 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1846 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1848 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1852 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
1853 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
1856 if(addDistributionFilter
){
1859 // in this case we need a filter which uses a join query
1860 // to get the TaxonBase documents for the DescriptionElementBase documents
1861 // which are matching the areas in question
1863 // for toTaxa, doByCommonName
1864 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
1866 distributionStatusList
,
1867 distributionFilterQueryFactory
,
1870 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1873 if (addDistributionFilter
){
1874 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
1878 // --- execute search
1879 TopGroups
<BytesRef
> topDocsResultSet
;
1881 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
1882 } catch (ParseException e
) {
1883 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1884 luceneParseException
.setStackTrace(e
.getStackTrace());
1885 throw luceneParseException
;
1888 // --- initialize taxa, highlight matches ....
1889 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
1892 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1893 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1895 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
1896 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1900 * @param namedAreaList at least one area must be in the list
1901 * @param distributionStatusList optional
1902 * @param toType toType
1903 * Optional parameter. Only used for debugging to print the toType documents
1904 * @param asFilter TODO
1906 * @throws IOException
1908 protected Query
createByDistributionJoinQuery(
1909 List
<NamedArea
> namedAreaList
,
1910 List
<PresenceAbsenceTerm
> distributionStatusList
,
1911 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
1912 ) throws IOException
{
1914 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1915 String toField
= "id"; // id in toType usually this is the TaxonBase index
1917 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
1919 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
1921 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
1923 return taxonAreaJoinQuery
;
1927 * @param namedAreaList
1928 * @param distributionStatusList
1929 * @param queryFactory
1932 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
1933 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
1934 Builder areaQueryBuilder
= new Builder();
1935 // area field from Distribution
1936 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
1938 // status field from Distribution
1939 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
1940 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
1943 BooleanQuery areaQuery
= areaQueryBuilder
.build();
1944 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
1949 * This method has been primarily created for testing the area join query but might
1950 * also be useful in other situations
1952 * @param namedAreaList
1953 * @param distributionStatusList
1954 * @param classification
1955 * @param highlightFragments
1957 * @throws IOException
1959 protected LuceneSearch
prepareByDistributionSearch(
1960 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
1961 Classification classification
) throws IOException
{
1963 Builder finalQueryBuilder
= new Builder();
1965 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1967 // FIXME is this query factory using the wrong type?
1968 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
1970 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1971 luceneSearch
.setSortFields(sortFields
);
1974 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
1976 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
1978 if(classification
!= null){
1979 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1981 BooleanQuery finalQuery
= finalQueryBuilder
.build();
1982 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
1983 luceneSearch
.setQuery(finalQuery
);
1985 return luceneSearch
;
1989 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
1990 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
1991 Classification classification
, List
<Feature
> features
, List
<Language
> languages
,
1992 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1995 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, features
, languages
, highlightFragments
);
1997 // --- execute search
1998 TopGroups
<BytesRef
> topDocsResultSet
;
2000 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2001 } catch (ParseException e
) {
2002 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2003 luceneParseException
.setStackTrace(e
.getStackTrace());
2004 throw luceneParseException
;
2007 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2008 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2010 // --- initialize taxa, highlight matches ....
2011 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2012 @SuppressWarnings("rawtypes")
2013 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2014 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2016 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2017 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2023 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2024 Classification classification
, List
<Language
> languages
, boolean highlightFragments
,
2025 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2027 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
, classification
, null, languages
, highlightFragments
);
2028 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, languages
, highlightFragments
, null);
2030 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2032 // --- execute search
2033 TopGroups
<BytesRef
> topDocsResultSet
;
2035 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2036 } catch (ParseException e
) {
2037 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2038 luceneParseException
.setStackTrace(e
.getStackTrace());
2039 throw luceneParseException
;
2042 // --- initialize taxa, highlight matches ....
2043 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2045 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2046 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2047 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2049 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2050 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2052 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2053 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2060 * @param queryString
2061 * @param classification
2064 * @param highlightFragments
2065 * @param directorySelectClass
2068 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2069 String queryString
, Classification classification
, List
<Feature
> features
,
2070 List
<Language
> languages
, boolean highlightFragments
) {
2072 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2073 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2075 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2077 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, features
,
2078 languages
, descriptionElementQueryFactory
);
2080 luceneSearch
.setSortFields(sortFields
);
2081 luceneSearch
.setCdmTypRestriction(clazz
);
2082 luceneSearch
.setQuery(finalQuery
);
2083 if(highlightFragments
){
2084 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2087 return luceneSearch
;
2091 * @param queryString
2092 * @param classification
2095 * @param descriptionElementQueryFactory
2098 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
, Classification classification
,
2099 List
<Feature
> features
, List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2100 Builder finalQueryBuilder
= new Builder();
2101 Builder textQueryBuilder
= new Builder();
2102 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2105 Builder nameQueryBuilder
= new Builder();
2106 if(languages
== null || languages
.size() == 0){
2107 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2109 Builder languageSubQueryBuilder
= new Builder();
2110 for(Language lang
: languages
){
2111 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2113 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2114 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2116 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2119 // text field from TextData
2120 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2122 // --- TermBase fields - by representation ----
2123 // state field from CategoricalData
2124 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2126 // state field from CategoricalData
2127 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2129 // area field from Distribution
2130 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2132 // status field from Distribution
2133 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2135 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2136 // --- classification ----
2138 if(classification
!= null){
2139 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2142 // --- IdentifieableEntity fields - by uuid
2143 if(features
!= null && features
.size() > 0 ){
2144 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2147 // the description must be associated with a taxon
2148 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2150 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2151 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2156 * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2157 * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2158 * This method is a convenient means to retrieve a Lucene query string for such the fields.
2160 * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2161 * or {@link MultilanguageTextFieldBridge }
2162 * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2163 * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2164 * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2166 * TODO move to utiliy class !!!!!!!!
2168 private StringBuilder
appendLocalizedFieldQuery(String name
, List
<Language
> languages
, StringBuilder stringBuilder
) {
2170 if(stringBuilder
== null){
2171 stringBuilder
= new StringBuilder();
2173 if(languages
== null || languages
.size() == 0){
2174 stringBuilder
.append(name
+ ".ALL:(%1$s) ");
2176 for(Language lang
: languages
){
2177 stringBuilder
.append(name
+ "." + lang
.getUuid().toString() + ":(%1$s) ");
2180 return stringBuilder
;
2184 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2185 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2186 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2188 HashMap
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2191 UUID nameUuid
= taxon
.getName().getUuid();
2192 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2193 String epithetOfTaxon
= null;
2194 String infragenericEpithetOfTaxon
= null;
2195 String infraspecificEpithetOfTaxon
= null;
2196 if (taxonName
.isSpecies()){
2197 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2198 } else if (taxonName
.isInfraGeneric()){
2199 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2200 } else if (taxonName
.isInfraSpecific()){
2201 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2203 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2204 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2205 List
<String
> taxonNames
= new ArrayList
<>();
2207 for (TaxonNode node
: nodes
){
2208 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2209 // List<String> synonymsEpithet = new ArrayList<>();
2211 if (node
.getClassification().equals(classification
)){
2212 if (!node
.isTopmostNode()){
2213 TaxonNode parent
= node
.getParent();
2214 parent
= CdmBase
.deproxy(parent
);
2215 TaxonName parentName
= parent
.getTaxon().getName();
2216 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2217 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2218 Rank rankOfTaxon
= taxonName
.getRank();
2221 //create inferred synonyms for species, subspecies
2222 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2224 Synonym inferredEpithet
= null;
2225 Synonym inferredGenus
= null;
2226 Synonym potentialCombination
= null;
2228 List
<String
> propertyPaths
= new ArrayList
<>();
2229 propertyPaths
.add("synonym");
2230 propertyPaths
.add("synonym.name");
2231 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2232 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2234 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2235 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2236 null, null,orderHintsSynonyms
,propertyPaths
);
2238 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2239 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2240 if (doWithMisappliedNames
){
2241 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2242 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2243 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2244 null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2245 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2246 null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2249 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2250 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2252 inferredEpithet
= createInferredEpithets(taxon
,
2253 zooHashMap
, taxonName
, epithetOfTaxon
,
2254 infragenericEpithetOfTaxon
,
2255 infraspecificEpithetOfTaxon
,
2256 taxonNames
, parentName
,
2257 synonymRelationOfParent
);
2259 inferredSynonyms
.add(inferredEpithet
);
2260 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2261 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2264 if (doWithMisappliedNames
){
2266 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2267 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2269 inferredEpithet
= createInferredEpithets(taxon
,
2270 zooHashMap
, taxonName
, epithetOfTaxon
,
2271 infragenericEpithetOfTaxon
,
2272 infraspecificEpithetOfTaxon
,
2273 taxonNames
, parentName
,
2276 inferredSynonyms
.add(inferredEpithet
);
2277 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2278 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2282 if (!taxonNames
.isEmpty()){
2283 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2284 IZoologicalName name
;
2285 if (!synNotInCDM
.isEmpty()){
2286 inferredSynonymsToBeRemoved
.clear();
2288 for (Synonym syn
:inferredSynonyms
){
2289 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2290 if (!synNotInCDM
.contains(name
.getNameCache())){
2291 inferredSynonymsToBeRemoved
.add(syn
);
2295 // Remove identified Synonyms from inferredSynonyms
2296 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2297 inferredSynonyms
.remove(synonym
);
2302 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2304 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2306 inferredGenus
= createInferredGenus(taxon
,
2307 zooHashMap
, taxonName
, epithetOfTaxon
,
2308 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2310 inferredSynonyms
.add(inferredGenus
);
2311 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2312 taxonNames
.add(inferredGenus
.getName().getNameCache());
2315 if (doWithMisappliedNames
){
2317 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2318 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2319 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2321 inferredSynonyms
.add(inferredGenus
);
2322 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2323 taxonNames
.add(inferredGenus
.getName().getNameCache());
2328 if (!taxonNames
.isEmpty()){
2329 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2330 IZoologicalName name
;
2331 if (!synNotInCDM
.isEmpty()){
2332 inferredSynonymsToBeRemoved
.clear();
2334 for (Synonym syn
:inferredSynonyms
){
2335 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2336 if (!synNotInCDM
.contains(name
.getNameCache())){
2337 inferredSynonymsToBeRemoved
.add(syn
);
2341 // Remove identified Synonyms from inferredSynonyms
2342 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2343 inferredSynonyms
.remove(synonym
);
2348 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2350 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2351 IZoologicalName inferredSynName
;
2352 //for all synonyms of the parent...
2353 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2355 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2357 synName
= synonymRelationOfParent
.getName();
2359 // Set the sourceReference
2360 sourceReference
= synonymRelationOfParent
.getSec();
2362 // Determine the idInSource
2363 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2365 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2366 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2367 String synParentInfragenericName
= null;
2368 String synParentSpecificEpithet
= null;
2370 if (parentSynZooName
.isInfraGeneric()){
2371 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2373 if (parentSynZooName
.isSpecies()){
2374 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2377 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2378 synonymsGenus.put(synGenusName, idInSource);
2381 //for all synonyms of the taxon
2383 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2385 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2386 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2388 synParentInfragenericName
,
2389 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2391 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2392 inferredSynonyms
.add(potentialCombination
);
2393 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2394 taxonNames
.add(potentialCombination
.getName().getNameCache());
2400 if (doWithMisappliedNames
){
2402 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2404 TaxonName misappliedParentName
;
2406 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2407 misappliedParentName
= misappliedParent
.getName();
2409 HibernateProxyHelper
.deproxy(misappliedParent
);
2411 // Set the sourceReference
2412 sourceReference
= misappliedParent
.getSec();
2414 // Determine the idInSource
2415 String idInSourceParent
= getIdInSource(misappliedParent
);
2417 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2418 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2419 String synParentInfragenericName
= null;
2420 String synParentSpecificEpithet
= null;
2422 if (parentSynZooName
.isInfraGeneric()){
2423 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2425 if (parentSynZooName
.isSpecies()){
2426 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2430 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2431 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2432 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2433 potentialCombination
= createPotentialCombination(
2434 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2436 synParentInfragenericName
,
2437 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2440 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2441 inferredSynonyms
.add(potentialCombination
);
2442 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2443 taxonNames
.add(potentialCombination
.getName().getNameCache());
2448 if (!taxonNames
.isEmpty()){
2449 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2450 IZoologicalName name
;
2451 if (!synNotInCDM
.isEmpty()){
2452 inferredSynonymsToBeRemoved
.clear();
2453 for (Synonym syn
:inferredSynonyms
){
2455 name
= syn
.getName();
2456 }catch (ClassCastException e
){
2457 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2459 if (!synNotInCDM
.contains(name
.getNameCache())){
2460 inferredSynonymsToBeRemoved
.add(syn
);
2463 // Remove identified Synonyms from inferredSynonyms
2464 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2465 inferredSynonyms
.remove(synonym
);
2471 logger
.info("The synonym type is not defined.");
2472 return inferredSynonyms
;
2479 return inferredSynonyms
;
2482 private Synonym
createPotentialCombination(String idInSourceParent
,
2483 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2484 String synParentInfragenericName
, String synParentSpecificEpithet
,
2485 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2486 Synonym potentialCombination
;
2487 Reference sourceReference
;
2488 IZoologicalName inferredSynName
;
2489 HibernateProxyHelper
.deproxy(syn
);
2491 // Set sourceReference
2492 sourceReference
= syn
.getSec();
2493 if (sourceReference
== null){
2494 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2496 if (!parentSynZooName
.getTaxa().isEmpty()){
2497 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2499 sourceReference
= taxon
.getSec();
2502 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2504 String synTaxonInfraSpecificName
= null;
2506 if (parentSynZooName
.isSpecies()){
2507 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2510 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2511 synonymsEpithet.add(epithetName);
2514 //create potential combinations...
2515 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2517 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2518 if (zooSynName
.isSpecies()){
2519 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2520 if (parentSynZooName
.isInfraGeneric()){
2521 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2524 if (zooSynName
.isInfraSpecific()){
2525 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2526 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2528 if (parentSynZooName
.isInfraGeneric()){
2529 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2533 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2535 // Set the sourceReference
2536 potentialCombination
.setSec(sourceReference
);
2539 // Determine the idInSource
2540 String idInSourceSyn
= getIdInSource(syn
);
2542 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2543 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2544 inferredSynName
.addSource(originalSource
);
2545 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2546 potentialCombination
.addSource(originalSource
);
2549 return potentialCombination
;
2552 private Synonym
createInferredGenus(Taxon taxon
,
2553 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2554 String epithetOfTaxon
, String genusOfTaxon
,
2555 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2558 Synonym inferredGenus
;
2560 IZoologicalName inferredSynName
;
2561 synName
=syn
.getName();
2562 HibernateProxyHelper
.deproxy(syn
);
2564 // Determine the idInSource
2565 String idInSourceSyn
= getIdInSource(syn
);
2566 String idInSourceTaxon
= getIdInSource(taxon
);
2567 // Determine the sourceReference
2568 Reference sourceReference
= syn
.getSec();
2570 //logger.warn(sourceReference.getTitleCache());
2572 synName
= syn
.getName();
2573 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2574 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2575 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2576 synonymsEpithet.add(synSpeciesEpithetName);
2579 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2580 //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...
2583 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2584 if (zooParentName
.isInfraGeneric()){
2585 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2588 if (taxonName
.isSpecies()){
2589 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2591 if (taxonName
.isInfraSpecific()){
2592 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2593 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2597 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2599 // Set the sourceReference
2600 inferredGenus
.setSec(sourceReference
);
2602 // Add the original source
2603 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2604 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2605 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2606 inferredGenus
.addSource(originalSource
);
2608 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2609 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2610 inferredSynName
.addSource(originalSource
);
2611 originalSource
= null;
2614 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2615 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2616 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2617 inferredGenus
.addSource(originalSource
);
2619 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2620 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2621 inferredSynName
.addSource(originalSource
);
2622 originalSource
= null;
2625 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2627 return inferredGenus
;
2630 private Synonym
createInferredEpithets(Taxon taxon
,
2631 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2632 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2633 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2634 TaxonName parentName
, TaxonBase
<?
> syn
) {
2636 Synonym inferredEpithet
;
2638 IZoologicalName inferredSynName
;
2639 HibernateProxyHelper
.deproxy(syn
);
2641 // Determine the idInSource
2642 String idInSourceSyn
= getIdInSource(syn
);
2643 String idInSourceTaxon
= getIdInSource(taxon
);
2644 // Determine the sourceReference
2645 Reference sourceReference
= syn
.getSec();
2647 if (sourceReference
== null){
2648 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2649 sourceReference
= taxon
.getSec();
2652 synName
= syn
.getName();
2653 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2654 String synGenusName
= zooSynName
.getGenusOrUninomial();
2655 String synInfraGenericEpithet
= null;
2656 String synSpecificEpithet
= null;
2658 if (zooSynName
.getInfraGenericEpithet() != null){
2659 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2662 if (zooSynName
.isInfraSpecific()){
2663 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2666 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2667 synonymsGenus.put(synGenusName, idInSource);
2670 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2672 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2673 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2674 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2676 inferredSynName
.setGenusOrUninomial(synGenusName
);
2678 if (parentName
.isInfraGeneric()){
2679 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2681 if (taxonName
.isSpecies()){
2682 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2683 }else if (taxonName
.isInfraSpecific()){
2684 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2685 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2688 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2690 // Set the sourceReference
2691 inferredEpithet
.setSec(sourceReference
);
2693 /* Add the original source
2694 if (idInSource != null) {
2695 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2698 Reference citation = getCitation(syn);
2699 if (citation != null) {
2700 originalSource.setCitation(citation);
2701 inferredEpithet.addSource(originalSource);
2704 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2707 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2708 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2710 inferredEpithet
.addSource(originalSource
);
2712 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2713 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2715 inferredSynName
.addSource(originalSource
);
2719 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2721 return inferredEpithet
;
2725 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2726 * Very likely only useful for createInferredSynonyms().
2731 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2732 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2733 if (taxonName
== null) {
2734 taxonName
= zooHashMap
.get(uuid
);
2740 * Returns the idInSource for a given Synonym.
2743 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2744 String idInSource
= null;
2745 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2746 if (sources
.size() == 1) {
2747 IdentifiableSource source
= sources
.iterator().next();
2748 if (source
!= null) {
2749 idInSource
= source
.getIdInSource();
2751 } else if (sources
.size() > 1) {
2754 for (IdentifiableSource source
: sources
) {
2755 idInSource
+= source
.getIdInSource();
2756 if (count
< sources
.size()) {
2761 } else if (sources
.size() == 0){
2762 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2771 * Returns the citation for a given Synonym.
2774 private Reference
getCitation(Synonym syn
) {
2775 Reference citation
= null;
2776 Set
<IdentifiableSource
> sources
= syn
.getSources();
2777 if (sources
.size() == 1) {
2778 IdentifiableSource source
= sources
.iterator().next();
2779 if (source
!= null) {
2780 citation
= source
.getCitation();
2782 } else if (sources
.size() > 1) {
2783 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2790 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2791 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2793 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2794 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2795 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2797 return inferredSynonyms
;
2801 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2803 // TODO quickly implemented, create according dao !!!!
2804 Set
<TaxonNode
> nodes
= new HashSet
<>();
2805 Set
<Classification
> classifications
= new HashSet
<>();
2806 List
<Classification
> list
= new ArrayList
<>();
2808 if (taxonBase
== null) {
2812 taxonBase
= load(taxonBase
.getUuid());
2814 if (taxonBase
instanceof Taxon
) {
2815 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2817 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2819 nodes
.addAll(taxon
.getTaxonNodes());
2822 for (TaxonNode node
: nodes
) {
2823 classifications
.add(node
.getClassification());
2825 list
.addAll(classifications
);
2830 @Transactional(readOnly
= false)
2831 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2833 TaxonRelationshipType oldRelationshipType
,
2834 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2835 UpdateResult result
= new UpdateResult();
2836 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2837 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2838 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2840 result
.addUpdatedObject(fromTaxon
);
2841 result
.addUpdatedObject(toTaxon
);
2842 result
.addUpdatedObject(result
.getCdmEntity());
2848 @Transactional(readOnly
= false)
2849 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2850 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2852 UpdateResult result
= new UpdateResult();
2853 // Create new synonym using concept name
2854 TaxonName synonymName
= fromTaxon
.getName();
2856 // Remove concept relation from taxon
2857 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
2859 // Create a new synonym for the taxon
2861 if (synonymType
!= null
2862 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
2863 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
2864 toTaxon
.addHomotypicSynonym(synonym
);
2866 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
2869 this.saveOrUpdate(toTaxon
);
2870 //TODO: configurator and classification
2871 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
2872 config
.setDeleteNameIfPossible(false);
2873 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
2874 result
.setCdmEntity(synonym
);
2875 result
.addUpdatedObject(toTaxon
);
2876 result
.addUpdatedObject(synonym
);
2881 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
2882 DeleteResult result
= new DeleteResult();
2883 TaxonBase taxonBase
= load(taxonBaseUuid
);
2884 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
2885 if (taxonBase
instanceof Taxon
){
2886 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
2887 result
= isDeletableForTaxon(references
, taxonConfig
);
2889 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
2890 result
= isDeletableForSynonym(references
, synonymConfig
);
2895 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
2897 DeleteResult result
= new DeleteResult();
2898 for (CdmBase ref
: references
){
2899 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
2900 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
2901 result
.addException(new ReferencedObjectUndeletableException(message
));
2902 result
.addRelatedObject(ref
);
2910 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
2911 String message
= null;
2912 DeleteResult result
= new DeleteResult();
2913 for (CdmBase ref
: references
){
2914 if (!(ref
instanceof TaxonName
)){
2916 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
2917 message
= "The taxon can't be deleted as long as it has synonyms.";
2919 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
2920 message
= "The taxon can't be deleted as long as it has factual data.";
2923 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
2924 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
2926 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
2927 if (!config
.isDeleteMisappliedNamesAndInvalidDesignations() &&
2928 (((TaxonRelationship
)ref
).getType().isMisappliedNameOrInvalidDesignation())){
2929 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
2931 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
2934 if (ref
instanceof PolytomousKeyNode
){
2935 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
2938 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
2939 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
2943 /* //PolytomousKeyNode
2944 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
2945 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
2950 if (ref
.isInstanceOf(TaxonInteraction
.class)){
2951 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
2955 if (ref
.isInstanceOf(DeterminationEvent
.class)){
2956 message
= "Taxon can't be deleted as it is used in a determination event";
2959 if (message
!= null){
2960 result
.addException(new ReferencedObjectUndeletableException(message
));
2961 result
.addRelatedObject(ref
);
2970 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
2971 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
2973 //preliminary implementation
2975 Set
<Taxon
> taxa
= new HashSet
<>();
2976 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
2977 if (taxonBase
== null){
2978 return new IncludedTaxaDTO();
2979 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
2980 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
2982 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
2983 //TODO partial synonyms ??
2984 //TODO synonyms in general
2985 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
2986 taxa
.add(syn
.getAcceptedTaxon());
2988 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
2991 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
2993 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
2994 related
= makeRelatedIncluded(related
, result
, config
);
3001 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3003 * @return the set of conceptually related taxa for further use
3006 * @param uncheckedTaxa
3007 * @param existingTaxa
3011 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3014 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3015 for (Taxon taxon
: uncheckedTaxa
){
3016 taxonNodes
.addAll(taxon
.getTaxonNodes());
3019 Set
<Taxon
> children
= new HashSet
<>();
3020 if (! config
.onlyCongruent
){
3021 for (TaxonNode node
: taxonNodes
){
3022 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, null);
3023 for (TaxonNode child
: childNodes
){
3024 children
.add(child
.getTaxon());
3027 children
.remove(null); // just to be on the save side
3030 Iterator
<Taxon
> it
= children
.iterator();
3031 while(it
.hasNext()){
3032 UUID uuid
= it
.next().getUuid();
3033 if (existingTaxa
.contains(uuid
)){
3036 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3041 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3042 uncheckedAndChildren
.addAll(children
);
3044 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3047 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3052 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3053 * @return the set of these computed taxa
3055 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3056 Set
<Taxon
> result
= new HashSet
<>();
3058 for (Taxon taxon
: unchecked
){
3059 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3060 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3062 for (TaxonRelationship fromRel
: fromRelations
){
3063 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3066 if (fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3067 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.INCLUDES()) ||
3068 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3070 result
.add(fromRel
.getToTaxon());
3074 for (TaxonRelationship toRel
: toRelations
){
3075 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3078 if (toRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO())){
3079 result
.add(toRel
.getFromTaxon());
3084 Iterator
<Taxon
> it
= result
.iterator();
3085 while(it
.hasNext()){
3086 UUID uuid
= it
.next().getUuid();
3087 if (existingTaxa
.contains(uuid
)){
3090 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3097 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3098 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3099 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, 0, config
.getPropertyPath());
3104 @Transactional(readOnly
= true)
3105 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3106 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3107 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3108 Integer pageNumber
, List
<String
> propertyPaths
) {
3109 if (subtreeFilter
== null){
3110 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3113 Integer numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3114 List
<Object
[]> daoResults
= new ArrayList
<>();
3115 if(numberOfResults
> 0) { // no point checking again
3116 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3117 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3120 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3121 for (Object
[] daoObj
: daoResults
){
3123 result
.add(new IdentifiedEntityDTO
<S
>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3125 result
.add(new IdentifiedEntityDTO
<S
>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3128 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3132 @Transactional(readOnly
= true)
3133 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3134 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3135 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3136 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3137 if (subtreeFilter
== null){
3138 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3141 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3142 List
<Object
[]> daoResults
= new ArrayList
<>();
3143 if(numberOfResults
> 0) { // no point checking again
3144 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3145 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3148 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3149 for (Object
[] daoObj
: daoResults
){
3151 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3153 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3156 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3160 @Transactional(readOnly
= false)
3161 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3162 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3163 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3165 UpdateResult result
= new UpdateResult();
3166 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3167 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3168 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3174 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3175 UpdateResult result
= new UpdateResult();
3177 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3178 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3179 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3180 //reload to avoid session conflicts
3181 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3183 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3184 if(description
.isProtectedTitleCache()){
3185 String separator
= "";
3186 if(!StringUtils
.isBlank(description
.getTitleCache())){
3189 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3191 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3192 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3193 description
.addAnnotation(annotation
);
3194 toTaxon
.addDescription(description
);
3195 dao
.saveOrUpdate(toTaxon
);
3196 dao
.saveOrUpdate(fromTaxon
);
3197 result
.addUpdatedObject(toTaxon
);
3198 result
.addUpdatedObject(fromTaxon
);
3206 @Transactional(readOnly
= false)
3207 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3208 UUID acceptedTaxonUuid
) {
3209 TaxonBase
<?
> base
= this.load(synonymUUid
);
3210 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3211 base
= this.load(acceptedTaxonUuid
);
3212 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3214 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
);