2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.api
.service
;
12 import java
.io
.IOException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.EnumSet
;
15 import java
.util
.HashMap
;
16 import java
.util
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
21 import java
.util
.UUID
;
23 import javax
.persistence
.EntityNotFoundException
;
25 import org
.apache
.commons
.lang
.StringUtils
;
26 import org
.apache
.log4j
.Logger
;
27 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
28 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
29 import org
.apache
.lucene
.search
.BooleanQuery
;
30 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
31 import org
.apache
.lucene
.search
.Query
;
32 import org
.apache
.lucene
.search
.SortField
;
33 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
34 import org
.apache
.lucene
.search
.join
.ScoreMode
;
35 import org
.apache
.lucene
.util
.BytesRef
;
36 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
37 import org
.springframework
.stereotype
.Service
;
38 import org
.springframework
.transaction
.annotation
.Transactional
;
40 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
41 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
47 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
48 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.TaxonRelationshipsDTO
;
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
.exception
.UnpublishedException
;
68 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
69 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
70 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
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
.name
.HomotypicalGroup
;
97 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
98 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
99 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
100 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
101 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
102 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
103 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
104 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
105 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
106 import eu
.etaxonomy
.cdm
.model
.taxon
.HomotypicGroupTaxonComparator
;
107 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
108 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
109 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
110 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
115 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
116 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
117 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
118 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
119 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
120 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
121 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
122 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
123 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
124 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
125 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
126 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
130 * @author a.kohlbecker
134 @Transactional(readOnly
= true)
135 public class TaxonServiceImpl
136 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
137 implements ITaxonService
{
139 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
141 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
143 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
145 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
148 private ITaxonNodeDao taxonNodeDao
;
151 private ITaxonNameDao nameDao
;
154 private INameService nameService
;
157 private IOccurrenceService occurrenceService
;
160 private ITaxonNodeService nodeService
;
163 private IDescriptionService descriptionService
;
166 // private IOrderedTermVocabularyDao orderedVocabularyDao;
169 private IOccurrenceDao occurrenceDao
;
172 private IClassificationDao classificationDao
;
175 private AbstractBeanInitializer beanInitializer
;
178 private ILuceneIndexToolProvider luceneIndexToolProvider
;
180 //************************ CONSTRUCTOR ****************************/
181 public TaxonServiceImpl(){
182 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
185 // ****************************** METHODS ********************************/
192 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
193 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
197 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
198 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
202 @Transactional(readOnly
= false)
203 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
){
204 UpdateResult result
= new UpdateResult();
205 TaxonName synonymName
= synonym
.getName();
206 synonymName
.removeTaxonBase(synonym
);
207 TaxonName taxonName
= acceptedTaxon
.getName();
208 taxonName
.removeTaxonBase(acceptedTaxon
);
210 synonym
.setName(taxonName
);
211 synonym
.setTitleCache(null, false);
212 synonym
.getTitleCache();
213 acceptedTaxon
.setName(synonymName
);
214 acceptedTaxon
.setTitleCache(null, false);
215 acceptedTaxon
.getTitleCache();
216 saveOrUpdate(synonym
);
217 saveOrUpdate(acceptedTaxon
);
218 result
.addUpdatedObject(acceptedTaxon
);
219 result
.addUpdatedObject(synonym
);
222 // the accepted taxon needs a new uuid because the concept has changed
223 // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
224 //acceptedTaxon.setUuid(UUID.randomUUID());
229 @Transactional(readOnly
= false)
230 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean deleteSynonym
) {
231 UpdateResult result
= new UpdateResult();
232 TaxonName acceptedName
= acceptedTaxon
.getName();
233 TaxonName synonymName
= synonym
.getName();
234 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
236 //check synonym is not homotypic
237 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
238 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
239 result
.addException(new HomotypicalGroupChangeException(message
));
244 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, acceptedTaxon
.getSec());
245 dao
.save(newAcceptedTaxon
);
246 result
.setCdmEntity(newAcceptedTaxon
);
247 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
248 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
250 for (Synonym heteroSynonym
: heteroSynonyms
){
251 if (synonym
.equals(heteroSynonym
)){
252 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
254 //move synonyms in same homotypic group to new accepted taxon
255 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
258 dao
.saveOrUpdate(acceptedTaxon
);
259 result
.addUpdatedObject(acceptedTaxon
);
264 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
265 config
.setDeleteNameIfPossible(false);
266 this.deleteSynonym(synonym
, config
);
268 } catch (Exception e
) {
269 result
.addException(e
);
277 @Transactional(readOnly
= false)
278 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
279 UUID acceptedTaxonUuid
,
280 UUID newParentNodeUuid
,
281 boolean deleteSynonym
) {
282 UpdateResult result
= new UpdateResult();
283 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
284 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
285 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, deleteSynonym
);
286 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
287 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
288 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
289 taxonNodeDao
.save(newNode
);
290 result
.addUpdatedObject(newTaxon
);
291 result
.addUpdatedObject(acceptedTaxon
);
292 result
.setCdmEntity(newNode
);
300 @Transactional(readOnly
= false)
301 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
303 TaxonRelationshipType taxonRelationshipType
,
305 String microcitation
){
307 UpdateResult result
= new UpdateResult();
308 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
309 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
310 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
311 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
312 // result.setCdmEntity(relatedTaxon);
313 result
.addUpdatedObject(relatedTaxon
);
314 result
.addUpdatedObject(toTaxon
);
319 @Transactional(readOnly
= false)
320 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
321 // Get name from synonym
322 if (synonym
== null){
326 UpdateResult result
= new UpdateResult();
328 TaxonName synonymName
= synonym
.getName();
330 /* // remove synonym from taxon
331 toTaxon.removeSynonym(synonym);
333 // Create a taxon with synonym name
334 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
336 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
338 // Add taxon relation
339 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
340 result
.setCdmEntity(fromTaxon
);
341 // since we are swapping names, we have to detach the name from the synonym completely.
342 // Otherwise the synonym will still be in the list of typified names.
343 // synonym.getName().removeTaxonBase(synonym);
344 result
.includeResult(this.deleteSynonym(synonym
, null));
349 @Transactional(readOnly
= false)
351 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
352 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
354 TaxonName synonymName
= synonym
.getName();
355 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
358 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
359 newHomotypicalGroup
.addTypifiedName(synonymName
);
361 //remove existing basionym relationships
362 synonymName
.removeBasionyms();
364 //add basionym relationship
365 if (setBasionymRelationIfApplicable
){
366 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
367 for (TaxonName basionym
: basionyms
){
368 synonymName
.addBasionym(basionym
);
372 //set synonym relationship correctly
373 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
375 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
376 if (acceptedTaxon
!= null){
378 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
379 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
380 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
381 synonym
.setType(newRelationType
);
383 if (hasNewTargetTaxon
){
384 acceptedTaxon
.removeSynonym(synonym
, false);
387 if (hasNewTargetTaxon
){
388 @SuppressWarnings("null")
389 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
390 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
391 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
392 targetTaxon
.addSynonym(synonym
, relType
);
398 @Transactional(readOnly
= false)
399 public void updateTitleCache(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
401 clazz
= TaxonBase
.class;
403 super.updateTitleCacheImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
408 protected void setDao(ITaxonDao dao
) {
413 public Pager
<TaxonBase
> findTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
414 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
416 List
<TaxonBase
> results
= new ArrayList
<>();
417 if(numberOfResults
> 0) { // no point checking again
418 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
421 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
425 public List
<TaxonBase
> listTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
426 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
428 List
<TaxonBase
> results
= new ArrayList
<>();
429 if(numberOfResults
> 0) { // no point checking again
430 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
437 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
438 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
439 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
441 List
<TaxonRelationship
> results
= new ArrayList
<>();
442 if(numberOfResults
> 0) { // no point checking again
443 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
449 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
450 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
451 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
453 List
<TaxonRelationship
> results
= new ArrayList
<>();
454 if(numberOfResults
> 0) { // no point checking again
455 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
457 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
461 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
462 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
463 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
465 List
<TaxonRelationship
> results
= new ArrayList
<>();
466 if(numberOfResults
> 0) { // no point checking again
467 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
473 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
474 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
475 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
477 List
<TaxonRelationship
> results
= new ArrayList
<>();
478 if(numberOfResults
> 0) { // no point checking again
479 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
481 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
485 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
486 Integer pageSize
, Integer pageStart
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
487 Long numberOfResults
= dao
.countTaxonRelationships(types
);
489 List
<TaxonRelationship
> results
= new ArrayList
<>();
490 if(numberOfResults
> 0) {
491 results
= dao
.getTaxonRelationships(types
, pageSize
, pageStart
, orderHints
, propertyPaths
);
497 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
498 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
500 Synonym synonym
= null;
503 synonym
= (Synonym
) dao
.load(synonymUuid
);
504 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
505 } catch (ClassCastException e
){
506 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
507 } catch (NullPointerException e
){
508 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
511 Classification classificationFilter
= null;
512 if(classificationUuid
!= null){
514 classificationFilter
= classificationDao
.load(classificationUuid
);
515 } catch (NullPointerException e
){
516 //TODO not sure, why an NPE should be thrown in the above load method
517 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
519 if(classificationFilter
== null){
520 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
524 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
526 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
527 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
535 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
536 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
538 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
539 relatedTaxa
.remove(taxon
);
540 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
546 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
547 * <code>taxon</code> supplied as parameter.
550 * @param includeRelationships
552 * @param maxDepth can be <code>null</code> for infinite depth
555 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
556 boolean includeUnpublished
, Integer maxDepth
) {
562 if(includeRelationships
.isEmpty()){
566 if(maxDepth
!= null) {
569 if(logger
.isDebugEnabled()){
570 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
572 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
573 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
574 for (TaxonRelationship taxRel
: taxonRelationships
) {
577 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
580 // filter by includeRelationships
581 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
582 if ( relationshipEdgeFilter
.getTaxonRelationshipTypes().equals(taxRel
.getType()) ) {
583 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
584 if(logger
.isDebugEnabled()){
585 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
587 taxa
.add(taxRel
.getToTaxon());
588 if(maxDepth
== null || maxDepth
> 0) {
589 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
592 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
593 taxa
.add(taxRel
.getFromTaxon());
594 if(logger
.isDebugEnabled()){
595 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
597 if(maxDepth
== null || maxDepth
> 0) {
598 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
608 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
609 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
611 List
<Synonym
> results
= new ArrayList
<>();
612 if(numberOfResults
> 0) { // no point checking again
613 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
616 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
620 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
621 List
<List
<Synonym
>> result
= new ArrayList
<>();
622 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
623 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
627 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
630 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
631 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
632 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
640 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
641 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
642 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
644 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
648 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
649 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
650 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
651 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
652 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
653 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
655 return heterotypicSynonymyGroups
;
659 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
661 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
662 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
663 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(),
664 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
666 return new ArrayList
<>();
671 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
673 List
<IdentifiableEntity
> results
= new ArrayList
<>();
674 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
675 List
<TaxonBase
> taxa
= null;
678 long numberTaxaResults
= 0L;
681 List
<String
> propertyPath
= new ArrayList
<>();
682 if(configurator
.getTaxonPropertyPath() != null){
683 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
687 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
688 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
690 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
691 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
692 configurator
.getClassification(), configurator
.getMatchMode(),
693 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
696 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
697 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
698 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
699 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(),
700 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
701 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
705 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
708 results
.addAll(taxa
);
711 numberOfResults
+= numberTaxaResults
;
713 // Names without taxa
714 if (configurator
.isDoNamesWithoutTaxa()) {
715 int numberNameResults
= 0;
717 List
<TaxonName
> names
=
718 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
719 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
720 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
721 if (names
.size() > 0) {
722 for (TaxonName taxonName
: names
) {
723 if (taxonName
.getTaxonBases().size() == 0) {
724 results
.add(taxonName
);
728 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
729 numberOfResults
+= numberNameResults
;
733 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
736 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
737 return dao
.getUuidAndTitleCache(limit
, pattern
);
741 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
742 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
746 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
747 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
748 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
751 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
753 // logger.setLevel(Level.TRACE);
754 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
756 logger
.trace("listMedia() - START");
758 Set
<Taxon
> taxa
= new HashSet
<>();
759 List
<Media
> taxonMedia
= new ArrayList
<>();
760 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
762 if (limitToGalleries
== null) {
763 limitToGalleries
= false;
766 // --- resolve related taxa
767 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
768 logger
.trace("listMedia() - resolve related taxa");
769 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
772 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
774 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
775 logger
.trace("listMedia() - includeTaxonDescriptions");
776 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
777 // --- TaxonDescriptions
778 for (Taxon t
: taxa
) {
779 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
781 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
782 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
783 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
784 for (Media media
: element
.getMedia()) {
785 if(taxonDescription
.isImageGallery()){
786 taxonMedia
.add(media
);
789 nonImageGalleryImages
.add(media
);
795 //put images from image gallery first (#3242)
796 taxonMedia
.addAll(nonImageGalleryImages
);
800 if(includeOccurrences
!= null && includeOccurrences
) {
801 logger
.trace("listMedia() - includeOccurrences");
802 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
804 for (Taxon t
: taxa
) {
805 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
807 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
809 // direct media removed from specimen #3597
810 // taxonMedia.addAll(occurrence.getMedia());
812 // SpecimenDescriptions
813 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
814 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
815 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
816 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
817 for (DescriptionElementBase element
: elements
) {
818 for (Media media
: element
.getMedia()) {
819 taxonMedia
.add(media
);
825 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
826 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
828 //TODO why may collections have media attached? #
829 if (derivedUnit
.getCollection() != null){
830 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
834 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
838 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
839 logger
.trace("listMedia() - includeTaxonNameDescriptions");
840 // --- TaxonNameDescription
841 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
842 for (Taxon t
: taxa
) {
843 nameDescriptions
.addAll(t
.getName().getDescriptions());
845 for(TaxonNameDescription nameDescription
: nameDescriptions
){
846 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
847 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
848 for (DescriptionElementBase element
: elements
) {
849 for (Media media
: element
.getMedia()) {
850 taxonMedia
.add(media
);
858 logger
.trace("listMedia() - initialize");
859 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
861 logger
.trace("listMedia() - END");
867 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
868 return this.dao
.loadList(listOfIDs
, null);
872 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
873 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
877 public long countSynonyms(boolean onlyAttachedToTaxon
){
878 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
882 public List
<TaxonName
> findIdenticalTaxonNames(List
<String
> propertyPath
) {
883 return this.dao
.findIdenticalTaxonNames(propertyPath
);
887 @Transactional(readOnly
=false)
888 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
891 config
= new TaxonDeletionConfigurator();
893 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
894 DeleteResult result
= new DeleteResult();
897 result
.addException(new Exception ("The taxon was already deleted."));
900 taxon
= HibernateProxyHelper
.deproxy(taxon
);
901 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
902 result
= isDeletable(taxonUUID
, config
);
905 // --- DeleteSynonymRelations
906 if (config
.isDeleteSynonymRelations()){
907 boolean removeSynonymNameFromHomotypicalGroup
= false;
908 // use tmp Set to avoid concurrent modification
909 Set
<Synonym
> synsToDelete
= new HashSet
<>();
910 synsToDelete
.addAll(taxon
.getSynonyms());
911 for (Synonym synonym
: synsToDelete
){
912 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
914 // --- DeleteSynonymsIfPossible
915 if (config
.isDeleteSynonymsIfPossible()){
917 boolean newHomotypicGroupIfNeeded
= true;
918 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
919 result
.includeResult(deleteSynonym(synonym
, synConfig
));
924 // --- DeleteTaxonRelationships
925 if (! config
.isDeleteTaxonRelationships()){
926 if (taxon
.getTaxonRelations().size() > 0){
928 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
929 "Remove taxon from all relations to other taxa prior to deletion."));
932 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
933 configRelTaxon
.setDeleteTaxonNodes(false);
934 configRelTaxon
.setDeleteConceptRelationships(true);
936 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
937 if (config
.isDeleteMisappliedNamesAndInvalidDesignations()
938 && taxRel
.getType().isMisappliedNameOrInvalidDesignation()
939 && taxon
.equals(taxRel
.getToTaxon())){
940 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
941 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
943 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
944 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
945 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
946 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
949 taxon
.removeTaxonRelation(taxRel
);
954 if (config
.isDeleteDescriptions()){
955 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
956 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
957 for (TaxonDescription desc
: descriptions
){
958 //TODO use description delete configurator ?
959 //FIXME check if description is ALWAYS deletable
960 if (desc
.getDescribedSpecimenOrObservation() != null){
962 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
963 " which also describes specimens or observations"));
966 removeDescriptions
.add(desc
);
971 for (TaxonDescription desc
: removeDescriptions
){
972 taxon
.removeDescription(desc
);
973 descriptionService
.delete(desc
);
981 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
982 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
984 if (taxon
.getTaxonNodes().size() != 0){
985 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
986 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
987 TaxonNode node
= null;
988 boolean deleteChildren
;
989 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
990 deleteChildren
= true;
992 deleteChildren
= false;
994 boolean success
= true;
995 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
996 while (iterator
.hasNext()){
997 node
= iterator
.next();
998 if (node
.getClassification().equals(classification
)){
1004 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1005 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1006 nodeService
.delete(node
);
1007 result
.addDeletedObject(node
);
1010 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1012 } else if (config
.isDeleteInAllClassifications()){
1013 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1014 nodesList
.addAll(taxon
.getTaxonNodes());
1015 for (ITaxonTreeNode treeNode
: nodesList
){
1016 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1017 if(!deleteChildren
){
1018 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1019 for (Object childNode
: childNodes
){
1020 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1021 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1025 config
.getTaxonNodeConfig().setDeleteElement(false);
1026 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1027 if (!resultNodes
.isOk()){
1028 result
.addExceptions(resultNodes
.getExceptions());
1029 result
.setStatus(resultNodes
.getStatus());
1031 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1036 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1040 TaxonName name
= taxon
.getName();
1041 taxon
.setName(null);
1042 this.saveOrUpdate(taxon
);
1044 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1047 result
.addDeletedObject(taxon
);
1048 }catch(Exception e
){
1049 result
.addException(e
);
1054 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1058 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1059 DeleteResult nameResult
= new DeleteResult();
1060 //remove name if possible (and required)
1062 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1064 if (nameResult
.isError() || nameResult
.isAbort()){
1065 result
.addRelatedObject(name
);
1066 result
.addExceptions(nameResult
.getExceptions());
1068 result
.includeResult(nameResult
);
1078 @Transactional(readOnly
= false)
1079 public DeleteResult
delete(UUID synUUID
){
1080 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1081 return this.deleteSynonym(syn
, null);
1085 @Transactional(readOnly
= false)
1086 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1087 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1093 @Transactional(readOnly
= false)
1094 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1095 DeleteResult result
= new DeleteResult();
1096 if (synonym
== null){
1098 result
.addException(new Exception("The synonym was already deleted."));
1102 if (config
== null){
1103 config
= new SynonymDeletionConfigurator();
1106 result
= isDeletable(synonym
.getUuid(), config
);
1110 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1113 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1115 if (accTaxon
!= null){
1116 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1117 accTaxon
.removeSynonym(synonym
, false);
1118 this.saveOrUpdate(accTaxon
);
1119 result
.addUpdatedObject(accTaxon
);
1121 this.saveOrUpdate(synonym
);
1125 TaxonName name
= synonym
.getName();
1126 synonym
.setName(null);
1128 dao
.delete(synonym
);
1129 result
.addDeletedObject(synonym
);
1131 //remove name if possible (and required)
1132 if (name
!= null && config
.isDeleteNameIfPossible()){
1134 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1135 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1136 result
.addExceptions(nameDeleteResult
.getExceptions());
1137 result
.addRelatedObject(name
);
1139 result
.addDeletedObject(name
);
1148 public List
<TaxonName
> findIdenticalTaxonNameIds(List
<String
> propertyPath
) {
1150 return this.dao
.findIdenticalNamesNew(propertyPath
);
1155 public Taxon
findBestMatchingTaxon(String taxonName
) {
1156 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1157 config
.setTaxonNameTitle(taxonName
);
1158 return findBestMatchingTaxon(config
);
1162 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1164 Taxon bestCandidate
= null;
1166 // 1. search for accepted taxa
1167 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1168 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, null, null);
1169 boolean bestCandidateMatchesSecUuid
= false;
1170 boolean bestCandidateIsInClassification
= false;
1171 int countEqualCandidates
= 0;
1172 for(TaxonBase taxonBaseCandidate
: taxonList
){
1173 if(taxonBaseCandidate
instanceof Taxon
){
1174 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1175 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1176 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1178 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1179 bestCandidate
= newCanditate
;
1180 countEqualCandidates
= 1;
1181 bestCandidateMatchesSecUuid
= true;
1185 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1186 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1188 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1189 bestCandidate
= newCanditate
;
1190 countEqualCandidates
= 1;
1191 bestCandidateIsInClassification
= true;
1194 if (bestCandidate
== null){
1195 bestCandidate
= newCanditate
;
1196 countEqualCandidates
= 1;
1200 }else{ //not Taxon.class
1203 countEqualCandidates
++;
1206 if (bestCandidate
!= null){
1207 if(countEqualCandidates
> 1){
1208 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1209 return bestCandidate
;
1211 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1212 return bestCandidate
;
1217 // 2. search for synonyms
1218 if (config
.isIncludeSynonyms()){
1219 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1220 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, null, null);
1221 for(TaxonBase taxonBase
: synonymList
){
1222 if(taxonBase
instanceof Synonym
){
1223 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1224 bestCandidate
= synonym
.getAcceptedTaxon();
1225 if(bestCandidate
!= null){
1226 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1227 return bestCandidate
;
1229 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1234 } catch (Exception e
){
1236 e
.printStackTrace();
1239 return bestCandidate
;
1242 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1243 UUID configClassificationUuid
= config
.getClassificationUuid();
1244 if (configClassificationUuid
== null){
1247 for (TaxonNode node
: taxon
.getTaxonNodes()){
1248 UUID classUuid
= node
.getClassification().getUuid();
1249 if (configClassificationUuid
.equals(classUuid
)){
1256 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1257 UUID configSecUuid
= config
.getSecUuid();
1258 if (configSecUuid
== null){
1261 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1262 return configSecUuid
.equals(taxonSecUuid
);
1266 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1267 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, MatchMode
.EXACT
, null, null, 0, null, null);
1268 if(! synonymList
.isEmpty()){
1269 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1270 if(synonymList
.size() == 1){
1271 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1274 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1282 @Transactional(readOnly
= false)
1283 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1285 boolean moveHomotypicGroup
,
1286 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1287 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1289 oldSynonym
.getSec(),
1290 oldSynonym
.getSecMicroReference(),
1295 @Transactional(readOnly
= false)
1296 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1298 boolean moveHomotypicGroup
,
1299 SynonymType newSynonymType
,
1300 Reference newSecundum
,
1301 String newSecundumDetail
,
1302 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1304 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1305 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1306 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1307 TaxonName synonymName
= synonym
.getName();
1308 TaxonName fromTaxonName
= oldTaxon
.getName();
1309 //set default relationship type
1310 if (newSynonymType
== null){
1311 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1313 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1315 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1316 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1317 boolean isSingleInGroup
= !(hgSize
> 1);
1319 if (! isSingleInGroup
){
1320 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1321 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1322 if (isHomotypicToAccepted
){
1323 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.";
1324 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1325 message
= String
.format(message
, homotypicRelatives
);
1326 throw new HomotypicalGroupChangeException(message
);
1328 if (! moveHomotypicGroup
){
1329 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.";
1330 throw new HomotypicalGroupChangeException(message
);
1333 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1335 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1337 UpdateResult result
= new UpdateResult();
1338 //move all synonyms to new taxon
1339 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1340 for (Synonym synRelation
: homotypicSynonyms
){
1342 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1343 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1344 oldTaxon
.removeSynonym(synRelation
, false);
1345 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1347 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1348 synRelation
.setSec(newSecundum
);
1350 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1351 synRelation
.setSecMicroReference(newSecundumDetail
);
1354 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1355 if (!synRelation
.equals(oldSynonym
)){
1360 result
.addUpdatedObject(oldTaxon
);
1361 result
.addUpdatedObject(newTaxon
);
1362 saveOrUpdate(oldTaxon
);
1363 saveOrUpdate(newTaxon
);
1369 public <T
extends TaxonBase
> List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1370 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1374 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1375 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1376 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
,
1377 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1378 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1380 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, null,
1381 includeUnpublished
, languages
, highlightFragments
, null);
1383 // --- execute search
1384 TopGroups
<BytesRef
> topDocsResultSet
;
1386 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1387 } catch (ParseException e
) {
1388 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1389 luceneParseException
.setStackTrace(e
.getStackTrace());
1390 throw luceneParseException
;
1393 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1394 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1396 // --- initialize taxa, thighlight matches ....
1397 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1398 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1399 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1401 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1402 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1406 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1407 Classification classification
,
1408 Integer pageSize
, Integer pageNumber
,
1409 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1411 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
);
1413 // --- execute search
1414 TopGroups
<BytesRef
> topDocsResultSet
;
1416 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1417 } catch (ParseException e
) {
1418 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1419 luceneParseException
.setStackTrace(e
.getStackTrace());
1420 throw luceneParseException
;
1423 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1424 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1426 // --- initialize taxa, thighlight matches ....
1427 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1428 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1429 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1431 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1432 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1437 * @param queryString
1438 * @param classification
1439 * @param includeUnpublished
1441 * @param highlightFragments
1442 * @param sortFields TODO
1443 * @param directorySelectClass
1446 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1447 Classification classification
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1448 boolean highlightFragments
, SortField
[] sortFields
) {
1450 Builder finalQueryBuilder
= new Builder();
1451 Builder textQueryBuilder
= new Builder();
1453 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1454 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1456 if(sortFields
== null){
1457 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1459 luceneSearch
.setSortFields(sortFields
);
1461 // ---- search criteria
1462 luceneSearch
.setCdmTypRestriction(clazz
);
1464 if(!queryString
.isEmpty() && !queryString
.equals("*") && !queryString
.equals("?") ) {
1465 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1466 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1468 if(className
!= null){
1469 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1472 BooleanQuery textQuery
= textQueryBuilder
.build();
1473 if(textQuery
.clauses().size() > 0) {
1474 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1478 if(classification
!= null){
1479 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1481 if(!includeUnpublished
) {
1482 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1483 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1484 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1487 luceneSearch
.setQuery(finalQueryBuilder
.build());
1489 if(highlightFragments
){
1490 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1492 return luceneSearch
;
1496 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1497 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1498 * drawback of requiring to do the join an indexing time.
1499 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1501 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1503 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1504 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1506 * @param queryString
1507 * @param classification
1509 * @param highlightFragments
1510 * @param sortFields TODO
1513 * @throws IOException
1515 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1516 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
,
1517 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1520 String queryTermField
;
1521 String toField
= "id"; // TaxonBase.uuid
1522 String publishField
;
1523 String publishFieldInvers
;
1525 if(edge
.isBidirectional()){
1526 throw new RuntimeException("Bidirectional joining not supported!");
1529 fromField
= "relatedFrom.id";
1530 queryTermField
= "relatedFrom.titleCache";
1531 publishField
= "relatedFrom.publish";
1532 publishFieldInvers
= "relatedTo.publish";
1533 } else if(edge
.isInvers()) {
1534 fromField
= "relatedTo.id";
1535 queryTermField
= "relatedTo.titleCache";
1536 publishField
= "relatedTo.publish";
1537 publishFieldInvers
= "relatedFrom.publish";
1539 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1542 Builder finalQueryBuilder
= new Builder();
1544 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1545 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1547 Builder joinFromQueryBuilder
= new Builder();
1548 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1549 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getTaxonRelationshipTypes()), Occur
.MUST
);
1550 if(!includeUnpublished
){
1551 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1552 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1555 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1557 if(sortFields
== null){
1558 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1560 luceneSearch
.setSortFields(sortFields
);
1562 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1564 if(classification
!= null){
1565 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1568 luceneSearch
.setQuery(finalQueryBuilder
.build());
1570 if(highlightFragments
){
1571 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1573 return luceneSearch
;
1577 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1578 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
, Classification classification
,
1579 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1580 boolean highlightFragments
, Integer pageSize
,
1581 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1582 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1584 // FIXME: allow taxonomic ordering
1585 // 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";
1586 // this require building a special sort column by a special classBridge
1587 if(highlightFragments
){
1588 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1589 "currently not fully supported by this method and thus " +
1590 "may not work with common names and misapplied names.");
1593 // convert sets to lists
1594 List
<NamedArea
> namedAreaList
= null;
1595 List
<PresenceAbsenceTerm
>distributionStatusList
= null;
1596 if(namedAreas
!= null){
1597 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1598 namedAreaList
.addAll(namedAreas
);
1600 if(distributionStatus
!= null){
1601 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1602 distributionStatusList
.addAll(distributionStatus
);
1605 // set default if parameter is null
1606 if(searchModes
== null){
1607 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1610 // set sort order and thus override any sort orders which may have been
1611 // defined by prepare*Search methods
1612 if(orderHints
== null){
1613 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1615 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1617 for(OrderHint oh
: orderHints
){
1618 sortFields
[i
++] = oh
.toSortField();
1620 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1621 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1624 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1626 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1627 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1630 ======== filtering by distribution , HOWTO ========
1632 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1633 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1634 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1635 which will be put into a FilteredQuersy in the end ?
1638 3. how does it work in spatial?
1640 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1641 - http://www.infoq.com/articles/LuceneSpatialSupport
1642 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1643 ------------------------------------------------------------------------
1646 A) use a separate distribution filter per index sub-query/search:
1647 - byTaxonSyonym (query TaxaonBase):
1648 use a join area filter (Distribution -> TaxonBase)
1649 - byCommonName (query DescriptionElementBase): use an area filter on
1650 DescriptionElementBase !!! PROBLEM !!!
1651 This cannot work since the distributions are different entities than the
1652 common names and thus these are different lucene documents.
1653 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1654 use a join area filter (Distribution -> TaxonBase)
1656 B) use a common distribution filter for all index sub-query/searches:
1657 - use a common join area filter (Distribution -> TaxonBase)
1658 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1659 PROBLEM in this case: we are losing the fragment highlighting for the
1660 common names, since the returned documents are always TaxonBases
1663 /* The QueryFactory for creating filter queries on Distributions should
1664 * The query factory used for the common names query cannot be reused
1665 * for this case, since we want to only record the text fields which are
1666 * actually used in the primary query
1668 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1670 Builder multiIndexByAreaFilterBuilder
= new Builder();
1671 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1673 // search for taxa or synonyms
1674 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1675 @SuppressWarnings("rawtypes")
1676 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1677 String className
= null;
1678 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1679 taxonBaseSubclass
= Taxon
.class;
1680 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1681 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1683 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
, queryString
, classification
, className
,
1684 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1685 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1686 /* A) does not work!!!!
1687 if(addDistributionFilter){
1688 // in this case we need a filter which uses a join query
1689 // to get the TaxonBase documents for the DescriptionElementBase documents
1690 // which are matching the areas in question
1691 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1693 distributionStatusList,
1694 distributionFilterQueryFactory
1696 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1699 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1700 // add additional area filter for synonyms
1701 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1702 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1704 //TODO replace by createByDistributionJoinQuery
1705 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1706 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1707 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1712 // search by CommonTaxonName
1713 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1715 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1716 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1717 CommonTaxonName
.class,
1718 "inDescription.taxon.id",
1720 QueryFactory
.addTypeRestriction(
1721 createByDescriptionElementFullTextQuery(queryString
, classification
, null, languages
, descriptionElementQueryFactory
)
1722 , CommonTaxonName
.class
1723 ).build(), "id", null, ScoreMode
.Max
);
1724 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1725 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1726 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1727 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1728 Builder builder
= new BooleanQuery
.Builder();
1729 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1730 if(!includeUnpublished
) {
1731 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1732 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1734 byCommonNameSearch
.setQuery(builder
.build());
1735 byCommonNameSearch
.setSortFields(sortFields
);
1737 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1739 luceneSearches
.add(byCommonNameSearch
);
1741 /* A) does not work!!!!
1743 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1744 queryString, classification, null, languages, highlightFragments)
1746 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1747 if(addDistributionFilter){
1748 // in this case we are able to use DescriptionElementBase documents
1749 // which are matching the areas in question directly
1750 BooleanQuery byDistributionQuery = createByDistributionQuery(
1752 distributionStatusList,
1753 distributionFilterQueryFactory
1755 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1760 // search by misapplied names
1761 //TODO merge with pro parte synonym search once #7487 is fixed
1762 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1764 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1765 // which allows doing query time joins
1766 // finds the misapplied name (Taxon B) which is an misapplication for
1767 // a related Taxon A.
1769 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1770 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1771 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1773 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1774 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1777 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1778 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1779 queryString
, classification
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1780 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1782 if(addDistributionFilter
){
1783 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1786 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
1787 * Maybe this is a bug in java itself.
1789 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1792 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1794 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1795 * will execute as expected:
1797 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1798 * String toField = "relation." + misappliedNameForUuid +".to.id";
1800 * Comparing both strings by the String.equals method returns true, so both String are identical.
1802 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1803 * 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)
1804 * The bug is persistent after a reboot of the development computer.
1806 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1807 // String toField = "relation." + misappliedNameForUuid +".to.id";
1808 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1809 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1810 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1812 //TODO replace by createByDistributionJoinQuery
1813 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1814 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1815 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1817 // debug code for bug described above
1818 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1819 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1820 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1822 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1827 // search by pro parte synonyms
1828 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1829 //TODO merge with misapplied name search once #7487 is fixed
1830 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1831 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
1833 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1834 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1835 queryString
, classification
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1836 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1838 if(addDistributionFilter
){
1839 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1840 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
1841 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1842 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1843 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1844 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1846 }//end pro parte synonyms
1850 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
1851 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
1854 if(addDistributionFilter
){
1857 // in this case we need a filter which uses a join query
1858 // to get the TaxonBase documents for the DescriptionElementBase documents
1859 // which are matching the areas in question
1861 // for doTaxa, doByCommonName
1862 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
1864 distributionStatusList
,
1865 distributionFilterQueryFactory
,
1868 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1871 if (addDistributionFilter
){
1872 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
1876 // --- execute search
1877 TopGroups
<BytesRef
> topDocsResultSet
;
1879 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
1880 } catch (ParseException e
) {
1881 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1882 luceneParseException
.setStackTrace(e
.getStackTrace());
1883 throw luceneParseException
;
1886 // --- initialize taxa, highlight matches ....
1887 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
1890 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1891 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1893 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1894 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1898 * @param namedAreaList at least one area must be in the list
1899 * @param distributionStatusList optional
1900 * @param toType toType
1901 * Optional parameter. Only used for debugging to print the toType documents
1902 * @param asFilter TODO
1904 * @throws IOException
1906 protected Query
createByDistributionJoinQuery(
1907 List
<NamedArea
> namedAreaList
,
1908 List
<PresenceAbsenceTerm
> distributionStatusList
,
1909 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
1910 ) throws IOException
{
1912 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1913 String toField
= "id"; // id in toType usually this is the TaxonBase index
1915 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
1917 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
1919 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
1921 return taxonAreaJoinQuery
;
1925 * @param namedAreaList
1926 * @param distributionStatusList
1927 * @param queryFactory
1930 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
1931 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
1932 Builder areaQueryBuilder
= new Builder();
1933 // area field from Distribution
1934 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
1936 // status field from Distribution
1937 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
1938 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
1941 BooleanQuery areaQuery
= areaQueryBuilder
.build();
1942 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
1947 * This method has been primarily created for testing the area join query but might
1948 * also be useful in other situations
1950 * @param namedAreaList
1951 * @param distributionStatusList
1952 * @param classification
1953 * @param highlightFragments
1955 * @throws IOException
1957 protected LuceneSearch
prepareByDistributionSearch(
1958 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
1959 Classification classification
) throws IOException
{
1961 Builder finalQueryBuilder
= new Builder();
1963 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1965 // FIXME is this query factory using the wrong type?
1966 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
1968 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1969 luceneSearch
.setSortFields(sortFields
);
1972 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
1974 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
1976 if(classification
!= null){
1977 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1979 BooleanQuery finalQuery
= finalQueryBuilder
.build();
1980 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
1981 luceneSearch
.setQuery(finalQuery
);
1983 return luceneSearch
;
1987 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
1988 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
1989 Classification classification
, List
<Feature
> features
, List
<Language
> languages
,
1990 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1993 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, features
, languages
, highlightFragments
);
1995 // --- execute search
1996 TopGroups
<BytesRef
> topDocsResultSet
;
1998 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1999 } catch (ParseException e
) {
2000 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2001 luceneParseException
.setStackTrace(e
.getStackTrace());
2002 throw luceneParseException
;
2005 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2006 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2008 // --- initialize taxa, highlight matches ....
2009 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2010 @SuppressWarnings("rawtypes")
2011 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2012 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2014 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2015 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2021 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2022 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2023 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2025 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
, classification
,
2026 null, languages
, highlightFragments
);
2027 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, null,
2028 includeUnpublished
, 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
, Long
.valueOf(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 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2159 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2160 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2162 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2163 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2165 UUID nameUuid
= taxon
.getName().getUuid();
2166 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2167 String epithetOfTaxon
= null;
2168 String infragenericEpithetOfTaxon
= null;
2169 String infraspecificEpithetOfTaxon
= null;
2170 if (taxonName
.isSpecies()){
2171 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2172 } else if (taxonName
.isInfraGeneric()){
2173 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2174 } else if (taxonName
.isInfraSpecific()){
2175 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2177 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2178 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2179 List
<String
> taxonNames
= new ArrayList
<>();
2181 for (TaxonNode node
: nodes
){
2182 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2183 // List<String> synonymsEpithet = new ArrayList<>();
2185 if (node
.getClassification().equals(classification
)){
2186 if (!node
.isTopmostNode()){
2187 TaxonNode parent
= node
.getParent();
2188 parent
= CdmBase
.deproxy(parent
);
2189 TaxonName parentName
= parent
.getTaxon().getName();
2190 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2191 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2193 //create inferred synonyms for species, subspecies
2194 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2196 Synonym inferredEpithet
= null;
2197 Synonym inferredGenus
= null;
2198 Synonym potentialCombination
= null;
2200 List
<String
> propertyPaths
= new ArrayList
<>();
2201 propertyPaths
.add("synonym");
2202 propertyPaths
.add("synonym.name");
2203 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2204 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2206 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2207 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2208 null, null,orderHintsSynonyms
,propertyPaths
);
2210 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2211 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2212 if (doWithMisappliedNames
){
2213 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2214 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2215 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2216 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2217 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2218 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2221 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2222 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2224 inferredEpithet
= createInferredEpithets(taxon
,
2225 zooHashMap
, taxonName
, epithetOfTaxon
,
2226 infragenericEpithetOfTaxon
,
2227 infraspecificEpithetOfTaxon
,
2228 taxonNames
, parentName
,
2229 synonymRelationOfParent
);
2231 inferredSynonyms
.add(inferredEpithet
);
2232 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2233 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2236 if (doWithMisappliedNames
){
2238 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2239 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2241 inferredEpithet
= createInferredEpithets(taxon
,
2242 zooHashMap
, taxonName
, epithetOfTaxon
,
2243 infragenericEpithetOfTaxon
,
2244 infraspecificEpithetOfTaxon
,
2245 taxonNames
, parentName
,
2248 inferredSynonyms
.add(inferredEpithet
);
2249 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2250 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2254 if (!taxonNames
.isEmpty()){
2255 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2256 IZoologicalName name
;
2257 if (!synNotInCDM
.isEmpty()){
2258 inferredSynonymsToBeRemoved
.clear();
2260 for (Synonym syn
:inferredSynonyms
){
2261 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2262 if (!synNotInCDM
.contains(name
.getNameCache())){
2263 inferredSynonymsToBeRemoved
.add(syn
);
2267 // Remove identified Synonyms from inferredSynonyms
2268 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2269 inferredSynonyms
.remove(synonym
);
2274 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2276 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2278 inferredGenus
= createInferredGenus(taxon
,
2279 zooHashMap
, taxonName
, epithetOfTaxon
,
2280 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2282 inferredSynonyms
.add(inferredGenus
);
2283 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2284 taxonNames
.add(inferredGenus
.getName().getNameCache());
2287 if (doWithMisappliedNames
){
2289 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2290 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2291 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2293 inferredSynonyms
.add(inferredGenus
);
2294 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2295 taxonNames
.add(inferredGenus
.getName().getNameCache());
2300 if (!taxonNames
.isEmpty()){
2301 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2302 IZoologicalName name
;
2303 if (!synNotInCDM
.isEmpty()){
2304 inferredSynonymsToBeRemoved
.clear();
2306 for (Synonym syn
:inferredSynonyms
){
2307 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2308 if (!synNotInCDM
.contains(name
.getNameCache())){
2309 inferredSynonymsToBeRemoved
.add(syn
);
2313 // Remove identified Synonyms from inferredSynonyms
2314 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2315 inferredSynonyms
.remove(synonym
);
2320 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2322 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2323 //for all synonyms of the parent...
2324 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2326 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2328 synName
= synonymRelationOfParent
.getName();
2330 // Set the sourceReference
2331 sourceReference
= synonymRelationOfParent
.getSec();
2333 // Determine the idInSource
2334 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2336 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2337 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2338 String synParentInfragenericName
= null;
2339 String synParentSpecificEpithet
= null;
2341 if (parentSynZooName
.isInfraGeneric()){
2342 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2344 if (parentSynZooName
.isSpecies()){
2345 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2348 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2349 synonymsGenus.put(synGenusName, idInSource);
2352 //for all synonyms of the taxon
2354 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2356 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2357 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2359 synParentInfragenericName
,
2360 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2362 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2363 inferredSynonyms
.add(potentialCombination
);
2364 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2365 taxonNames
.add(potentialCombination
.getName().getNameCache());
2371 if (doWithMisappliedNames
){
2373 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2375 TaxonName misappliedParentName
;
2377 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2378 misappliedParentName
= misappliedParent
.getName();
2380 HibernateProxyHelper
.deproxy(misappliedParent
);
2382 // Set the sourceReference
2383 sourceReference
= misappliedParent
.getSec();
2385 // Determine the idInSource
2386 String idInSourceParent
= getIdInSource(misappliedParent
);
2388 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2389 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2390 String synParentInfragenericName
= null;
2391 String synParentSpecificEpithet
= null;
2393 if (parentSynZooName
.isInfraGeneric()){
2394 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2396 if (parentSynZooName
.isSpecies()){
2397 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2401 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2402 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2403 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2404 potentialCombination
= createPotentialCombination(
2405 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2407 synParentInfragenericName
,
2408 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2411 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2412 inferredSynonyms
.add(potentialCombination
);
2413 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2414 taxonNames
.add(potentialCombination
.getName().getNameCache());
2419 if (!taxonNames
.isEmpty()){
2420 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2421 IZoologicalName name
;
2422 if (!synNotInCDM
.isEmpty()){
2423 inferredSynonymsToBeRemoved
.clear();
2424 for (Synonym syn
:inferredSynonyms
){
2426 name
= syn
.getName();
2427 }catch (ClassCastException e
){
2428 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2430 if (!synNotInCDM
.contains(name
.getNameCache())){
2431 inferredSynonymsToBeRemoved
.add(syn
);
2434 // Remove identified Synonyms from inferredSynonyms
2435 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2436 inferredSynonyms
.remove(synonym
);
2442 logger
.info("The synonym type is not defined.");
2443 return inferredSynonyms
;
2450 return inferredSynonyms
;
2453 private Synonym
createPotentialCombination(String idInSourceParent
,
2454 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2455 String synParentInfragenericName
, String synParentSpecificEpithet
,
2456 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2457 Synonym potentialCombination
;
2458 Reference sourceReference
;
2459 IZoologicalName inferredSynName
;
2460 HibernateProxyHelper
.deproxy(syn
);
2462 // Set sourceReference
2463 sourceReference
= syn
.getSec();
2464 if (sourceReference
== null){
2465 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2467 if (!parentSynZooName
.getTaxa().isEmpty()){
2468 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2470 sourceReference
= taxon
.getSec();
2473 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2475 String synTaxonInfraSpecificName
= null;
2477 if (parentSynZooName
.isSpecies()){
2478 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2481 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2482 synonymsEpithet.add(epithetName);
2485 //create potential combinations...
2486 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2488 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2489 if (zooSynName
.isSpecies()){
2490 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2491 if (parentSynZooName
.isInfraGeneric()){
2492 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2495 if (zooSynName
.isInfraSpecific()){
2496 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2497 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2499 if (parentSynZooName
.isInfraGeneric()){
2500 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2504 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2506 // Set the sourceReference
2507 potentialCombination
.setSec(sourceReference
);
2510 // Determine the idInSource
2511 String idInSourceSyn
= getIdInSource(syn
);
2513 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2514 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2515 inferredSynName
.addSource(originalSource
);
2516 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2517 potentialCombination
.addSource(originalSource
);
2520 return potentialCombination
;
2523 private Synonym
createInferredGenus(Taxon taxon
,
2524 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2525 String epithetOfTaxon
, String genusOfTaxon
,
2526 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2529 Synonym inferredGenus
;
2531 IZoologicalName inferredSynName
;
2532 synName
=syn
.getName();
2533 HibernateProxyHelper
.deproxy(syn
);
2535 // Determine the idInSource
2536 String idInSourceSyn
= getIdInSource(syn
);
2537 String idInSourceTaxon
= getIdInSource(taxon
);
2538 // Determine the sourceReference
2539 Reference sourceReference
= syn
.getSec();
2541 //logger.warn(sourceReference.getTitleCache());
2543 synName
= syn
.getName();
2544 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2545 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2546 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2547 synonymsEpithet.add(synSpeciesEpithetName);
2550 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2551 //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...
2554 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2555 if (zooParentName
.isInfraGeneric()){
2556 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2559 if (taxonName
.isSpecies()){
2560 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2562 if (taxonName
.isInfraSpecific()){
2563 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2564 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2568 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2570 // Set the sourceReference
2571 inferredGenus
.setSec(sourceReference
);
2573 // Add the original source
2574 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2575 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2576 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2577 inferredGenus
.addSource(originalSource
);
2579 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2580 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2581 inferredSynName
.addSource(originalSource
);
2582 originalSource
= null;
2585 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2586 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2587 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2588 inferredGenus
.addSource(originalSource
);
2590 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2591 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2592 inferredSynName
.addSource(originalSource
);
2593 originalSource
= null;
2596 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2598 return inferredGenus
;
2601 private Synonym
createInferredEpithets(Taxon taxon
,
2602 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2603 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2604 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2605 TaxonName parentName
, TaxonBase
<?
> syn
) {
2607 Synonym inferredEpithet
;
2609 IZoologicalName inferredSynName
;
2610 HibernateProxyHelper
.deproxy(syn
);
2612 // Determine the idInSource
2613 String idInSourceSyn
= getIdInSource(syn
);
2614 String idInSourceTaxon
= getIdInSource(taxon
);
2615 // Determine the sourceReference
2616 Reference sourceReference
= syn
.getSec();
2618 if (sourceReference
== null){
2619 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2620 sourceReference
= taxon
.getSec();
2623 synName
= syn
.getName();
2624 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2625 String synGenusName
= zooSynName
.getGenusOrUninomial();
2626 String synInfraGenericEpithet
= null;
2627 String synSpecificEpithet
= null;
2629 if (zooSynName
.getInfraGenericEpithet() != null){
2630 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2633 if (zooSynName
.isInfraSpecific()){
2634 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2637 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2638 synonymsGenus.put(synGenusName, idInSource);
2641 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2643 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2644 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2645 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2647 inferredSynName
.setGenusOrUninomial(synGenusName
);
2649 if (parentName
.isInfraGeneric()){
2650 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2652 if (taxonName
.isSpecies()){
2653 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2654 }else if (taxonName
.isInfraSpecific()){
2655 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2656 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2659 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2661 // Set the sourceReference
2662 inferredEpithet
.setSec(sourceReference
);
2664 /* Add the original source
2665 if (idInSource != null) {
2666 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2669 Reference citation = getCitation(syn);
2670 if (citation != null) {
2671 originalSource.setCitation(citation);
2672 inferredEpithet.addSource(originalSource);
2675 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2678 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2679 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2681 inferredEpithet
.addSource(originalSource
);
2683 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2684 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2686 inferredSynName
.addSource(originalSource
);
2690 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2692 return inferredEpithet
;
2696 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2697 * Very likely only useful for createInferredSynonyms().
2702 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2703 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2704 if (taxonName
== null) {
2705 taxonName
= zooHashMap
.get(uuid
);
2711 * Returns the idInSource for a given Synonym.
2714 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2715 String idInSource
= null;
2716 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2717 if (sources
.size() == 1) {
2718 IdentifiableSource source
= sources
.iterator().next();
2719 if (source
!= null) {
2720 idInSource
= source
.getIdInSource();
2722 } else if (sources
.size() > 1) {
2725 for (IdentifiableSource source
: sources
) {
2726 idInSource
+= source
.getIdInSource();
2727 if (count
< sources
.size()) {
2732 } else if (sources
.size() == 0){
2733 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2742 * Returns the citation for a given Synonym.
2745 private Reference
getCitation(Synonym syn
) {
2746 Reference citation
= null;
2747 Set
<IdentifiableSource
> sources
= syn
.getSources();
2748 if (sources
.size() == 1) {
2749 IdentifiableSource source
= sources
.iterator().next();
2750 if (source
!= null) {
2751 citation
= source
.getCitation();
2753 } else if (sources
.size() > 1) {
2754 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2761 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2762 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2764 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2765 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2766 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2768 return inferredSynonyms
;
2772 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2774 // TODO quickly implemented, create according dao !!!!
2775 Set
<TaxonNode
> nodes
= new HashSet
<>();
2776 Set
<Classification
> classifications
= new HashSet
<>();
2777 List
<Classification
> list
= new ArrayList
<>();
2779 if (taxonBase
== null) {
2783 taxonBase
= load(taxonBase
.getUuid());
2785 if (taxonBase
instanceof Taxon
) {
2786 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2788 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2790 nodes
.addAll(taxon
.getTaxonNodes());
2793 for (TaxonNode node
: nodes
) {
2794 classifications
.add(node
.getClassification());
2796 list
.addAll(classifications
);
2801 @Transactional(readOnly
= false)
2802 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2804 TaxonRelationshipType oldRelationshipType
,
2805 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2806 UpdateResult result
= new UpdateResult();
2807 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2808 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2809 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2811 result
.addUpdatedObject(fromTaxon
);
2812 result
.addUpdatedObject(toTaxon
);
2813 result
.addUpdatedObject(result
.getCdmEntity());
2819 @Transactional(readOnly
= false)
2820 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2821 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2823 UpdateResult result
= new UpdateResult();
2824 // Create new synonym using concept name
2825 TaxonName synonymName
= fromTaxon
.getName();
2827 // Remove concept relation from taxon
2828 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
2830 // Create a new synonym for the taxon
2832 if (synonymType
!= null
2833 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
2834 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
2835 toTaxon
.addHomotypicSynonym(synonym
);
2837 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
2840 this.saveOrUpdate(toTaxon
);
2841 //TODO: configurator and classification
2842 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
2843 config
.setDeleteNameIfPossible(false);
2844 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
2845 result
.setCdmEntity(synonym
);
2846 result
.addUpdatedObject(toTaxon
);
2847 result
.addUpdatedObject(synonym
);
2852 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
2853 DeleteResult result
= new DeleteResult();
2854 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
2855 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
2856 if (taxonBase
instanceof Taxon
){
2857 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
2858 result
= isDeletableForTaxon(references
, taxonConfig
);
2860 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
2861 result
= isDeletableForSynonym(references
, synonymConfig
);
2866 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
2868 DeleteResult result
= new DeleteResult();
2869 for (CdmBase ref
: references
){
2870 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
2871 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
2872 result
.addException(new ReferencedObjectUndeletableException(message
));
2873 result
.addRelatedObject(ref
);
2881 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
2882 String message
= null;
2883 DeleteResult result
= new DeleteResult();
2884 for (CdmBase ref
: references
){
2885 if (!(ref
instanceof TaxonName
)){
2887 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
2888 message
= "The taxon can't be deleted as long as it has synonyms.";
2890 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
2891 message
= "The taxon can't be deleted as long as it has factual data.";
2894 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
2895 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
2897 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
2898 if (!config
.isDeleteMisappliedNamesAndInvalidDesignations() &&
2899 (((TaxonRelationship
)ref
).getType().isMisappliedNameOrInvalidDesignation())){
2900 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
2902 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
2905 if (ref
instanceof PolytomousKeyNode
){
2906 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
2909 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
2910 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
2914 /* //PolytomousKeyNode
2915 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
2916 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
2921 if (ref
.isInstanceOf(TaxonInteraction
.class)){
2922 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
2926 if (ref
.isInstanceOf(DeterminationEvent
.class)){
2927 message
= "Taxon can't be deleted as it is used in a determination event";
2930 if (message
!= null){
2931 result
.addException(new ReferencedObjectUndeletableException(message
));
2932 result
.addRelatedObject(ref
);
2941 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
2942 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
2944 //preliminary implementation
2946 Set
<Taxon
> taxa
= new HashSet
<>();
2947 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
2948 if (taxonBase
== null){
2949 return new IncludedTaxaDTO();
2950 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
2951 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
2953 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
2954 //TODO partial synonyms ??
2955 //TODO synonyms in general
2956 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
2957 taxa
.add(syn
.getAcceptedTaxon());
2959 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
2962 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
2964 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
2965 related
= makeRelatedIncluded(related
, result
, config
);
2972 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
2974 * @return the set of conceptually related taxa for further use
2977 * @param uncheckedTaxa
2978 * @param existingTaxa
2982 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
2985 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
2986 for (Taxon taxon
: uncheckedTaxa
){
2987 taxonNodes
.addAll(taxon
.getTaxonNodes());
2990 Set
<Taxon
> children
= new HashSet
<>();
2991 if (! config
.onlyCongruent
){
2992 for (TaxonNode node
: taxonNodes
){
2993 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
2994 for (TaxonNode child
: childNodes
){
2995 children
.add(child
.getTaxon());
2998 children
.remove(null); // just to be on the save side
3001 Iterator
<Taxon
> it
= children
.iterator();
3002 while(it
.hasNext()){
3003 UUID uuid
= it
.next().getUuid();
3004 if (existingTaxa
.contains(uuid
)){
3007 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3012 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3013 uncheckedAndChildren
.addAll(children
);
3015 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3018 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3023 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3024 * @return the set of these computed taxa
3026 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3027 Set
<Taxon
> result
= new HashSet
<>();
3029 for (Taxon taxon
: unchecked
){
3030 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3031 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3033 for (TaxonRelationship fromRel
: fromRelations
){
3034 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3037 if (fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3038 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.INCLUDES()) ||
3039 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3041 result
.add(fromRel
.getToTaxon());
3045 for (TaxonRelationship toRel
: toRelations
){
3046 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3049 if (toRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO())){
3050 result
.add(toRel
.getFromTaxon());
3055 Iterator
<Taxon
> it
= result
.iterator();
3056 while(it
.hasNext()){
3057 UUID uuid
= it
.next().getUuid();
3058 if (existingTaxa
.contains(uuid
)){
3061 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3068 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3069 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3070 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3075 @Transactional(readOnly
= true)
3076 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3077 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3078 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3079 Integer pageNumber
, List
<String
> propertyPaths
) {
3080 if (subtreeFilter
== null){
3081 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3084 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3085 List
<Object
[]> daoResults
= new ArrayList
<>();
3086 if(numberOfResults
> 0) { // no point checking again
3087 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3088 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3091 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3092 for (Object
[] daoObj
: daoResults
){
3094 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3096 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3099 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3103 @Transactional(readOnly
= true)
3104 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3105 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3106 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3107 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3108 if (subtreeFilter
== null){
3109 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3112 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3113 List
<Object
[]> daoResults
= new ArrayList
<>();
3114 if(numberOfResults
> 0) { // no point checking again
3115 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3116 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3119 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3120 for (Object
[] daoObj
: daoResults
){
3122 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3124 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3127 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3131 @Transactional(readOnly
= false)
3132 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3133 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3134 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3136 UpdateResult result
= new UpdateResult();
3137 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3138 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3139 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3145 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3146 UpdateResult result
= new UpdateResult();
3148 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3149 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3150 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3151 //reload to avoid session conflicts
3152 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3154 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3155 if(description
.isProtectedTitleCache()){
3156 String separator
= "";
3157 if(!StringUtils
.isBlank(description
.getTitleCache())){
3160 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3162 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3163 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3164 description
.addAnnotation(annotation
);
3165 toTaxon
.addDescription(description
);
3166 dao
.saveOrUpdate(toTaxon
);
3167 dao
.saveOrUpdate(fromTaxon
);
3168 result
.addUpdatedObject(toTaxon
);
3169 result
.addUpdatedObject(fromTaxon
);
3177 @Transactional(readOnly
= false)
3178 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3179 UUID acceptedTaxonUuid
) {
3180 TaxonBase
<?
> base
= this.load(synonymUUid
);
3181 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3182 base
= this.load(acceptedTaxonUuid
);
3183 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3185 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
);
3192 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3193 Set
<TaxonRelationshipType
> inversTypes
,
3194 Direction direction
, boolean groupMisapplications
,
3195 boolean includeUnpublished
,
3196 Integer pageSize
, Integer pageNumber
) {
3197 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3198 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3200 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3202 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3203 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3204 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3206 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3208 //TODO paging is difficult because misapplication string is an attribute
3210 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3211 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3212 // if(numberOfResults > 0) { // no point checking again
3213 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3216 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3219 List
<Language
> languages
= null;
3221 direction
= Direction
.relatedTo
;
3222 //TODO order hints, property path
3223 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3224 for (TaxonRelationship relation
: relations
){
3225 dto
.addRelation(relation
, direction
, languages
);
3229 direction
= Direction
.relatedFrom
;
3230 //TODO order hints, property path
3231 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3232 for (TaxonRelationship relation
: relations
){
3233 dto
.addRelation(relation
, direction
, languages
);
3236 if (groupMisapplications
){
3238 dto
.createMisapplicationString();