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
.lang3
.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
.IdentifiableEntity
;
76 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
77 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
78 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
79 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
80 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
81 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
82 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
83 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
84 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
85 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
86 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
87 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
88 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
89 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
90 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
91 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
92 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
93 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
94 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
95 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
96 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
97 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
98 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
99 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
100 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
101 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
102 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
103 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
104 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
105 import eu
.etaxonomy
.cdm
.model
.taxon
.HomotypicGroupTaxonComparator
;
106 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
107 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
108 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
109 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
110 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
114 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
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 UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
401 clazz
= TaxonBase
.class;
403 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
408 protected void setDao(ITaxonDao dao
) {
414 public Pager
<TaxonBase
> findTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
415 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
417 List
<TaxonBase
> results
= new ArrayList
<>();
418 if(numberOfResults
> 0) { // no point checking again
419 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
422 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
426 public List
<TaxonBase
> listTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
427 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
429 List
<TaxonBase
> results
= new ArrayList
<>();
430 if(numberOfResults
> 0) { // no point checking again
431 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
438 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
439 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
440 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
442 List
<TaxonRelationship
> results
= new ArrayList
<>();
443 if(numberOfResults
> 0) { // no point checking again
444 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
450 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
451 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
452 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
454 List
<TaxonRelationship
> results
= new ArrayList
<>();
455 if(numberOfResults
> 0) { // no point checking again
456 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
458 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
462 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
463 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
464 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
466 List
<TaxonRelationship
> results
= new ArrayList
<>();
467 if(numberOfResults
> 0) { // no point checking again
468 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
474 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
475 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
476 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
478 List
<TaxonRelationship
> results
= new ArrayList
<>();
479 if(numberOfResults
> 0) { // no point checking again
480 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
482 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
486 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
487 Integer pageSize
, Integer pageStart
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
488 Long numberOfResults
= dao
.countTaxonRelationships(types
);
490 List
<TaxonRelationship
> results
= new ArrayList
<>();
491 if(numberOfResults
> 0) {
492 results
= dao
.getTaxonRelationships(types
, pageSize
, pageStart
, orderHints
, propertyPaths
);
498 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
499 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
501 Synonym synonym
= null;
504 synonym
= (Synonym
) dao
.load(synonymUuid
);
505 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
506 } catch (ClassCastException e
){
507 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
508 } catch (NullPointerException e
){
509 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
512 Classification classificationFilter
= null;
513 if(classificationUuid
!= null){
515 classificationFilter
= classificationDao
.load(classificationUuid
);
516 } catch (NullPointerException e
){
517 //TODO not sure, why an NPE should be thrown in the above load method
518 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
520 if(classificationFilter
== null){
521 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
525 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
527 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
528 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
536 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
537 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
539 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
540 relatedTaxa
.remove(taxon
);
541 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
547 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
548 * <code>taxon</code> supplied as parameter.
551 * @param includeRelationships
553 * @param maxDepth can be <code>null</code> for infinite depth
556 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
557 boolean includeUnpublished
, Integer maxDepth
) {
563 if(includeRelationships
.isEmpty()){
567 if(maxDepth
!= null) {
570 if(logger
.isDebugEnabled()){
571 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
573 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
574 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
575 for (TaxonRelationship taxRel
: taxonRelationships
) {
578 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
581 // filter by includeRelationships
582 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
583 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
584 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
585 if(logger
.isDebugEnabled()){
586 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
588 taxa
.add(taxRel
.getToTaxon());
589 if(maxDepth
== null || maxDepth
> 0) {
590 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
593 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
594 taxa
.add(taxRel
.getFromTaxon());
595 if(logger
.isDebugEnabled()){
596 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
598 if(maxDepth
== null || maxDepth
> 0) {
599 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
609 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
610 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
612 List
<Synonym
> results
= new ArrayList
<>();
613 if(numberOfResults
> 0) { // no point checking again
614 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
617 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
621 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
622 List
<List
<Synonym
>> result
= new ArrayList
<>();
623 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
624 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
628 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
631 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
632 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
633 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
641 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
642 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
643 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
645 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
649 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
650 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
651 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
652 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
653 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
654 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
656 return heterotypicSynonymyGroups
;
660 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
662 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
663 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
664 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(),
665 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
666 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
668 return new ArrayList
<>();
673 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
675 List
<IdentifiableEntity
> results
= new ArrayList
<>();
676 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
677 List
<TaxonBase
> taxa
= null;
680 long numberTaxaResults
= 0L;
682 List
<String
> propertyPath
= new ArrayList
<>();
683 if(configurator
.getTaxonPropertyPath() != null){
684 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
.getSubtree(), 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(), configurator
.getSubtree(),
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, 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, 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, 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
, TaxonNode subtree
, 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
, subtree
,
1381 null, 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
, TaxonNode subtree
,
1408 Integer pageSize
, Integer pageNumber
,
1409 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1411 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
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
, TaxonNode subtree
, 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(!StringUtils
.isEmpty(queryString
) && !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
);
1477 if(classification
!= null){
1478 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1480 if(subtree
!= null){
1481 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1483 if(!includeUnpublished
) {
1484 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1485 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1486 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1489 luceneSearch
.setQuery(finalQueryBuilder
.build());
1491 if(highlightFragments
){
1492 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1494 return luceneSearch
;
1498 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1499 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1500 * drawback of requiring to do the join an indexing time.
1501 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1503 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1505 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1506 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1508 * @param queryString
1509 * @param classification
1511 * @param highlightFragments
1512 * @param sortFields TODO
1515 * @throws IOException
1517 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1518 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1519 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1522 String queryTermField
;
1523 String toField
= "id"; // TaxonBase.uuid
1524 String publishField
;
1525 String publishFieldInvers
;
1527 if(edge
.isBidirectional()){
1528 throw new RuntimeException("Bidirectional joining not supported!");
1531 fromField
= "relatedFrom.id";
1532 queryTermField
= "relatedFrom.titleCache";
1533 publishField
= "relatedFrom.publish";
1534 publishFieldInvers
= "relatedTo.publish";
1535 } else if(edge
.isInvers()) {
1536 fromField
= "relatedTo.id";
1537 queryTermField
= "relatedTo.titleCache";
1538 publishField
= "relatedTo.publish";
1539 publishFieldInvers
= "relatedFrom.publish";
1541 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1544 Builder finalQueryBuilder
= new Builder();
1546 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1547 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1549 Builder joinFromQueryBuilder
= new Builder();
1550 if(!StringUtils
.isEmpty(queryString
)){
1551 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1553 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1554 if(!includeUnpublished
){
1555 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1556 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1559 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1561 if(sortFields
== null){
1562 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1564 luceneSearch
.setSortFields(sortFields
);
1566 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1568 if(classification
!= null){
1569 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1571 if(subtree
!= null){
1572 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1575 luceneSearch
.setQuery(finalQueryBuilder
.build());
1577 if(highlightFragments
){
1578 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1580 return luceneSearch
;
1584 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1585 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1586 Classification classification
, TaxonNode subtree
,
1587 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1588 boolean highlightFragments
, Integer pageSize
,
1589 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1590 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1592 // FIXME: allow taxonomic ordering
1593 // 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";
1594 // this require building a special sort column by a special classBridge
1595 if(highlightFragments
){
1596 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1597 "currently not fully supported by this method and thus " +
1598 "may not work with common names and misapplied names.");
1601 // convert sets to lists
1602 List
<NamedArea
> namedAreaList
= null;
1603 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1604 if(namedAreas
!= null){
1605 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1606 namedAreaList
.addAll(namedAreas
);
1608 if(distributionStatus
!= null){
1609 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1610 distributionStatusList
.addAll(distributionStatus
);
1613 // set default if parameter is null
1614 if(searchModes
== null){
1615 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1618 // set sort order and thus override any sort orders which may have been
1619 // defined by prepare*Search methods
1620 if(orderHints
== null){
1621 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1623 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1625 for(OrderHint oh
: orderHints
){
1626 sortFields
[i
++] = oh
.toSortField();
1628 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1629 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1632 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1634 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1635 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1638 ======== filtering by distribution , HOWTO ========
1640 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1641 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1642 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1643 which will be put into a FilteredQuersy in the end ?
1646 3. how does it work in spatial?
1648 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1649 - http://www.infoq.com/articles/LuceneSpatialSupport
1650 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1651 ------------------------------------------------------------------------
1654 A) use a separate distribution filter per index sub-query/search:
1655 - byTaxonSyonym (query TaxaonBase):
1656 use a join area filter (Distribution -> TaxonBase)
1657 - byCommonName (query DescriptionElementBase): use an area filter on
1658 DescriptionElementBase !!! PROBLEM !!!
1659 This cannot work since the distributions are different entities than the
1660 common names and thus these are different lucene documents.
1661 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1662 use a join area filter (Distribution -> TaxonBase)
1664 B) use a common distribution filter for all index sub-query/searches:
1665 - use a common join area filter (Distribution -> TaxonBase)
1666 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1667 PROBLEM in this case: we are losing the fragment highlighting for the
1668 common names, since the returned documents are always TaxonBases
1671 /* The QueryFactory for creating filter queries on Distributions should
1672 * The query factory used for the common names query cannot be reused
1673 * for this case, since we want to only record the text fields which are
1674 * actually used in the primary query
1676 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1678 Builder multiIndexByAreaFilterBuilder
= new Builder();
1679 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1681 // search for taxa or synonyms
1682 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1683 @SuppressWarnings("rawtypes")
1684 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1685 String className
= null;
1686 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1687 taxonBaseSubclass
= Taxon
.class;
1688 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1689 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1691 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1692 queryString
, classification
, subtree
, className
,
1693 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1694 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1695 /* A) does not work!!!!
1696 if(addDistributionFilter){
1697 // in this case we need a filter which uses a join query
1698 // to get the TaxonBase documents for the DescriptionElementBase documents
1699 // which are matching the areas in question
1700 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1702 distributionStatusList,
1703 distributionFilterQueryFactory
1705 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1708 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1709 // add additional area filter for synonyms
1710 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1711 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1713 //TODO replace by createByDistributionJoinQuery
1714 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1715 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1716 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1721 // search by CommonTaxonName
1722 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1724 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1725 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1726 CommonTaxonName
.class,
1727 "inDescription.taxon.id",
1729 QueryFactory
.addTypeRestriction(
1730 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
1731 , CommonTaxonName
.class
1732 ).build(), "id", null, ScoreMode
.Max
);
1733 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1734 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1735 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1736 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1737 Builder builder
= new BooleanQuery
.Builder();
1738 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1739 if(!includeUnpublished
) {
1740 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1741 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1743 byCommonNameSearch
.setQuery(builder
.build());
1744 byCommonNameSearch
.setSortFields(sortFields
);
1746 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1748 luceneSearches
.add(byCommonNameSearch
);
1750 /* A) does not work!!!!
1752 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1753 queryString, classification, null, languages, highlightFragments)
1755 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1756 if(addDistributionFilter){
1757 // in this case we are able to use DescriptionElementBase documents
1758 // which are matching the areas in question directly
1759 BooleanQuery byDistributionQuery = createByDistributionQuery(
1761 distributionStatusList,
1762 distributionFilterQueryFactory
1764 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1769 // search by misapplied names
1770 //TODO merge with pro parte synonym search once #7487 is fixed
1771 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1773 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1774 // which allows doing query time joins
1775 // finds the misapplied name (Taxon B) which is an misapplication for
1776 // a related Taxon A.
1778 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1779 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1780 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1782 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1783 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1786 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1787 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1788 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1789 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1791 if(addDistributionFilter
){
1792 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1795 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
1796 * Maybe this is a bug in java itself.
1798 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1801 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1803 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1804 * will execute as expected:
1806 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1807 * String toField = "relation." + misappliedNameForUuid +".to.id";
1809 * Comparing both strings by the String.equals method returns true, so both String are identical.
1811 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1812 * 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)
1813 * The bug is persistent after a reboot of the development computer.
1815 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1816 // String toField = "relation." + misappliedNameForUuid +".to.id";
1817 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1818 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1819 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1821 //TODO replace by createByDistributionJoinQuery
1822 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1823 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1824 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1826 // debug code for bug described above
1827 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1828 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1829 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1831 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1836 // search by pro parte synonyms
1837 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1838 //TODO merge with misapplied name search once #7487 is fixed
1839 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1840 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
1842 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1843 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1844 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1845 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1847 if(addDistributionFilter
){
1848 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1849 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
1850 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1851 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1852 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1853 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1855 }//end pro parte synonyms
1859 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
1860 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
1863 if(addDistributionFilter
){
1866 // in this case we need a filter which uses a join query
1867 // to get the TaxonBase documents for the DescriptionElementBase documents
1868 // which are matching the areas in question
1870 // for doTaxa, doByCommonName
1871 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
1873 distributionStatusList
,
1874 distributionFilterQueryFactory
,
1877 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1880 if (addDistributionFilter
){
1881 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
1885 // --- execute search
1886 TopGroups
<BytesRef
> topDocsResultSet
;
1888 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
1889 } catch (ParseException e
) {
1890 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1891 luceneParseException
.setStackTrace(e
.getStackTrace());
1892 throw luceneParseException
;
1895 // --- initialize taxa, highlight matches ....
1896 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
1899 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1900 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1902 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1903 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1907 * @param namedAreaList at least one area must be in the list
1908 * @param distributionStatusList optional
1909 * @param toType toType
1910 * Optional parameter. Only used for debugging to print the toType documents
1911 * @param asFilter TODO
1913 * @throws IOException
1915 protected Query
createByDistributionJoinQuery(
1916 List
<NamedArea
> namedAreaList
,
1917 List
<PresenceAbsenceTerm
> distributionStatusList
,
1918 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
1919 ) throws IOException
{
1921 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1922 String toField
= "id"; // id in toType usually this is the TaxonBase index
1924 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
1926 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
1928 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
1930 return taxonAreaJoinQuery
;
1934 * @param namedAreaList
1935 * @param distributionStatusList
1936 * @param queryFactory
1939 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
1940 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
1941 Builder areaQueryBuilder
= new Builder();
1942 // area field from Distribution
1943 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
1945 // status field from Distribution
1946 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
1947 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
1950 BooleanQuery areaQuery
= areaQueryBuilder
.build();
1951 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
1956 * This method has been primarily created for testing the area join query but might
1957 * also be useful in other situations
1959 * @param namedAreaList
1960 * @param distributionStatusList
1961 * @param classification
1962 * @param highlightFragments
1964 * @throws IOException
1966 protected LuceneSearch
prepareByDistributionSearch(
1967 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
1968 Classification classification
, TaxonNode subtree
) throws IOException
{
1970 Builder finalQueryBuilder
= new Builder();
1972 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1974 // FIXME is this query factory using the wrong type?
1975 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
1977 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1978 luceneSearch
.setSortFields(sortFields
);
1981 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
1983 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
1985 if(classification
!= null){
1986 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1988 if(subtree
!= null){
1989 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1991 BooleanQuery finalQuery
= finalQueryBuilder
.build();
1992 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
1993 luceneSearch
.setQuery(finalQuery
);
1995 return luceneSearch
;
1999 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2000 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2001 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2002 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2005 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2007 // --- execute search
2008 TopGroups
<BytesRef
> topDocsResultSet
;
2010 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2011 } catch (ParseException e
) {
2012 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2013 luceneParseException
.setStackTrace(e
.getStackTrace());
2014 throw luceneParseException
;
2017 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2018 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2020 // --- initialize taxa, highlight matches ....
2021 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2022 @SuppressWarnings("rawtypes")
2023 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2024 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2026 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2027 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2033 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2034 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2035 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2037 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2038 classification
, subtree
,
2039 null, languages
, highlightFragments
);
2040 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2041 includeUnpublished
, languages
, highlightFragments
, null);
2043 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2045 // --- execute search
2046 TopGroups
<BytesRef
> topDocsResultSet
;
2048 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2049 } catch (ParseException e
) {
2050 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2051 luceneParseException
.setStackTrace(e
.getStackTrace());
2052 throw luceneParseException
;
2055 // --- initialize taxa, highlight matches ....
2056 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2058 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2059 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2060 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2062 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2063 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2065 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2066 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2073 * @param queryString
2074 * @param classification
2077 * @param highlightFragments
2078 * @param directorySelectClass
2081 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2082 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2083 List
<Language
> languages
, boolean highlightFragments
) {
2085 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2086 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2088 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2090 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2091 languages
, descriptionElementQueryFactory
);
2093 luceneSearch
.setSortFields(sortFields
);
2094 luceneSearch
.setCdmTypRestriction(clazz
);
2095 luceneSearch
.setQuery(finalQuery
);
2096 if(highlightFragments
){
2097 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2100 return luceneSearch
;
2104 * @param queryString
2105 * @param classification
2108 * @param descriptionElementQueryFactory
2111 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2112 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2113 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2115 Builder finalQueryBuilder
= new Builder();
2116 Builder textQueryBuilder
= new Builder();
2118 if(!StringUtils
.isEmpty(queryString
)){
2120 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2123 Builder nameQueryBuilder
= new Builder();
2124 if(languages
== null || languages
.size() == 0){
2125 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2127 Builder languageSubQueryBuilder
= new Builder();
2128 for(Language lang
: languages
){
2129 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2131 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2132 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2134 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2137 // text field from TextData
2138 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2140 // --- TermBase fields - by representation ----
2141 // state field from CategoricalData
2142 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2144 // state field from CategoricalData
2145 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2147 // area field from Distribution
2148 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2150 // status field from Distribution
2151 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2153 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2156 // --- classification ----
2159 if(classification
!= null){
2160 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2162 if(subtree
!= null){
2163 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2166 // --- IdentifieableEntity fields - by uuid
2167 if(features
!= null && features
.size() > 0 ){
2168 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2171 // the description must be associated with a taxon
2172 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2174 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2175 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2180 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2183 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2184 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2186 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2187 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2189 UUID nameUuid
= taxon
.getName().getUuid();
2190 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2191 String epithetOfTaxon
= null;
2192 String infragenericEpithetOfTaxon
= null;
2193 String infraspecificEpithetOfTaxon
= null;
2194 if (taxonName
.isSpecies()){
2195 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2196 } else if (taxonName
.isInfraGeneric()){
2197 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2198 } else if (taxonName
.isInfraSpecific()){
2199 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2201 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2202 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2203 List
<String
> taxonNames
= new ArrayList
<>();
2205 for (TaxonNode node
: nodes
){
2206 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2207 // List<String> synonymsEpithet = new ArrayList<>();
2209 if (node
.getClassification().equals(classification
)){
2210 if (!node
.isTopmostNode()){
2211 TaxonNode parent
= node
.getParent();
2212 parent
= CdmBase
.deproxy(parent
);
2213 TaxonName parentName
= parent
.getTaxon().getName();
2214 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2215 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2217 //create inferred synonyms for species, subspecies
2218 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2220 Synonym inferredEpithet
= null;
2221 Synonym inferredGenus
= null;
2222 Synonym potentialCombination
= null;
2224 List
<String
> propertyPaths
= new ArrayList
<>();
2225 propertyPaths
.add("synonym");
2226 propertyPaths
.add("synonym.name");
2227 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2228 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2230 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2231 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2232 null, null,orderHintsSynonyms
,propertyPaths
);
2234 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2235 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2236 if (doWithMisappliedNames
){
2237 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2238 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2239 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2240 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2241 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2242 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2245 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2246 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2248 inferredEpithet
= createInferredEpithets(taxon
,
2249 zooHashMap
, taxonName
, epithetOfTaxon
,
2250 infragenericEpithetOfTaxon
,
2251 infraspecificEpithetOfTaxon
,
2252 taxonNames
, parentName
,
2253 synonymRelationOfParent
);
2255 inferredSynonyms
.add(inferredEpithet
);
2256 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2257 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2260 if (doWithMisappliedNames
){
2262 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2263 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2265 inferredEpithet
= createInferredEpithets(taxon
,
2266 zooHashMap
, taxonName
, epithetOfTaxon
,
2267 infragenericEpithetOfTaxon
,
2268 infraspecificEpithetOfTaxon
,
2269 taxonNames
, parentName
,
2272 inferredSynonyms
.add(inferredEpithet
);
2273 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2274 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2278 if (!taxonNames
.isEmpty()){
2279 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2280 IZoologicalName name
;
2281 if (!synNotInCDM
.isEmpty()){
2282 inferredSynonymsToBeRemoved
.clear();
2284 for (Synonym syn
:inferredSynonyms
){
2285 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2286 if (!synNotInCDM
.contains(name
.getNameCache())){
2287 inferredSynonymsToBeRemoved
.add(syn
);
2291 // Remove identified Synonyms from inferredSynonyms
2292 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2293 inferredSynonyms
.remove(synonym
);
2298 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2300 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2302 inferredGenus
= createInferredGenus(taxon
,
2303 zooHashMap
, taxonName
, epithetOfTaxon
,
2304 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2306 inferredSynonyms
.add(inferredGenus
);
2307 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2308 taxonNames
.add(inferredGenus
.getName().getNameCache());
2311 if (doWithMisappliedNames
){
2313 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2314 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2315 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2317 inferredSynonyms
.add(inferredGenus
);
2318 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2319 taxonNames
.add(inferredGenus
.getName().getNameCache());
2324 if (!taxonNames
.isEmpty()){
2325 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2326 IZoologicalName name
;
2327 if (!synNotInCDM
.isEmpty()){
2328 inferredSynonymsToBeRemoved
.clear();
2330 for (Synonym syn
:inferredSynonyms
){
2331 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2332 if (!synNotInCDM
.contains(name
.getNameCache())){
2333 inferredSynonymsToBeRemoved
.add(syn
);
2337 // Remove identified Synonyms from inferredSynonyms
2338 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2339 inferredSynonyms
.remove(synonym
);
2344 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2346 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2347 //for all synonyms of the parent...
2348 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2350 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2352 synName
= synonymRelationOfParent
.getName();
2354 // Set the sourceReference
2355 sourceReference
= synonymRelationOfParent
.getSec();
2357 // Determine the idInSource
2358 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2360 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2361 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2362 String synParentInfragenericName
= null;
2363 String synParentSpecificEpithet
= null;
2365 if (parentSynZooName
.isInfraGeneric()){
2366 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2368 if (parentSynZooName
.isSpecies()){
2369 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2372 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2373 synonymsGenus.put(synGenusName, idInSource);
2376 //for all synonyms of the taxon
2378 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2380 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2381 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2383 synParentInfragenericName
,
2384 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2386 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2387 inferredSynonyms
.add(potentialCombination
);
2388 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2389 taxonNames
.add(potentialCombination
.getName().getNameCache());
2395 if (doWithMisappliedNames
){
2397 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2399 TaxonName misappliedParentName
;
2401 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2402 misappliedParentName
= misappliedParent
.getName();
2404 HibernateProxyHelper
.deproxy(misappliedParent
);
2406 // Set the sourceReference
2407 sourceReference
= misappliedParent
.getSec();
2409 // Determine the idInSource
2410 String idInSourceParent
= getIdInSource(misappliedParent
);
2412 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2413 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2414 String synParentInfragenericName
= null;
2415 String synParentSpecificEpithet
= null;
2417 if (parentSynZooName
.isInfraGeneric()){
2418 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2420 if (parentSynZooName
.isSpecies()){
2421 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2425 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2426 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2427 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2428 potentialCombination
= createPotentialCombination(
2429 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2431 synParentInfragenericName
,
2432 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2435 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2436 inferredSynonyms
.add(potentialCombination
);
2437 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2438 taxonNames
.add(potentialCombination
.getName().getNameCache());
2443 if (!taxonNames
.isEmpty()){
2444 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2445 IZoologicalName name
;
2446 if (!synNotInCDM
.isEmpty()){
2447 inferredSynonymsToBeRemoved
.clear();
2448 for (Synonym syn
:inferredSynonyms
){
2450 name
= syn
.getName();
2451 }catch (ClassCastException e
){
2452 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2454 if (!synNotInCDM
.contains(name
.getNameCache())){
2455 inferredSynonymsToBeRemoved
.add(syn
);
2458 // Remove identified Synonyms from inferredSynonyms
2459 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2460 inferredSynonyms
.remove(synonym
);
2466 logger
.info("The synonym type is not defined.");
2467 return inferredSynonyms
;
2474 return inferredSynonyms
;
2477 private Synonym
createPotentialCombination(String idInSourceParent
,
2478 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2479 String synParentInfragenericName
, String synParentSpecificEpithet
,
2480 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2481 Synonym potentialCombination
;
2482 Reference sourceReference
;
2483 IZoologicalName inferredSynName
;
2484 HibernateProxyHelper
.deproxy(syn
);
2486 // Set sourceReference
2487 sourceReference
= syn
.getSec();
2488 if (sourceReference
== null){
2489 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2491 if (!parentSynZooName
.getTaxa().isEmpty()){
2492 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2494 sourceReference
= taxon
.getSec();
2497 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2499 String synTaxonInfraSpecificName
= null;
2501 if (parentSynZooName
.isSpecies()){
2502 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2505 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2506 synonymsEpithet.add(epithetName);
2509 //create potential combinations...
2510 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2512 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2513 if (zooSynName
.isSpecies()){
2514 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2515 if (parentSynZooName
.isInfraGeneric()){
2516 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2519 if (zooSynName
.isInfraSpecific()){
2520 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2521 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2523 if (parentSynZooName
.isInfraGeneric()){
2524 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2528 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2530 // Set the sourceReference
2531 potentialCombination
.setSec(sourceReference
);
2534 // Determine the idInSource
2535 String idInSourceSyn
= getIdInSource(syn
);
2537 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2538 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2539 inferredSynName
.addSource(originalSource
);
2540 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2541 potentialCombination
.addSource(originalSource
);
2544 return potentialCombination
;
2547 private Synonym
createInferredGenus(Taxon taxon
,
2548 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2549 String epithetOfTaxon
, String genusOfTaxon
,
2550 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2553 Synonym inferredGenus
;
2555 IZoologicalName inferredSynName
;
2556 synName
=syn
.getName();
2557 HibernateProxyHelper
.deproxy(syn
);
2559 // Determine the idInSource
2560 String idInSourceSyn
= getIdInSource(syn
);
2561 String idInSourceTaxon
= getIdInSource(taxon
);
2562 // Determine the sourceReference
2563 Reference sourceReference
= syn
.getSec();
2565 //logger.warn(sourceReference.getTitleCache());
2567 synName
= syn
.getName();
2568 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2569 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2570 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2571 synonymsEpithet.add(synSpeciesEpithetName);
2574 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2575 //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...
2578 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2579 if (zooParentName
.isInfraGeneric()){
2580 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2583 if (taxonName
.isSpecies()){
2584 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2586 if (taxonName
.isInfraSpecific()){
2587 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2588 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2592 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2594 // Set the sourceReference
2595 inferredGenus
.setSec(sourceReference
);
2597 // Add the original source
2598 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2599 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2600 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2601 inferredGenus
.addSource(originalSource
);
2603 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2604 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2605 inferredSynName
.addSource(originalSource
);
2606 originalSource
= null;
2609 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2610 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2611 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2612 inferredGenus
.addSource(originalSource
);
2614 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2615 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2616 inferredSynName
.addSource(originalSource
);
2617 originalSource
= null;
2620 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2622 return inferredGenus
;
2625 private Synonym
createInferredEpithets(Taxon taxon
,
2626 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2627 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2628 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2629 TaxonName parentName
, TaxonBase
<?
> syn
) {
2631 Synonym inferredEpithet
;
2633 IZoologicalName inferredSynName
;
2634 HibernateProxyHelper
.deproxy(syn
);
2636 // Determine the idInSource
2637 String idInSourceSyn
= getIdInSource(syn
);
2638 String idInSourceTaxon
= getIdInSource(taxon
);
2639 // Determine the sourceReference
2640 Reference sourceReference
= syn
.getSec();
2642 if (sourceReference
== null){
2643 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2644 sourceReference
= taxon
.getSec();
2647 synName
= syn
.getName();
2648 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2649 String synGenusName
= zooSynName
.getGenusOrUninomial();
2650 String synInfraGenericEpithet
= null;
2651 String synSpecificEpithet
= null;
2653 if (zooSynName
.getInfraGenericEpithet() != null){
2654 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2657 if (zooSynName
.isInfraSpecific()){
2658 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2661 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2662 synonymsGenus.put(synGenusName, idInSource);
2665 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2667 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2668 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2669 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2671 inferredSynName
.setGenusOrUninomial(synGenusName
);
2673 if (parentName
.isInfraGeneric()){
2674 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2676 if (taxonName
.isSpecies()){
2677 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2678 }else if (taxonName
.isInfraSpecific()){
2679 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2680 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2683 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2685 // Set the sourceReference
2686 inferredEpithet
.setSec(sourceReference
);
2688 /* Add the original source
2689 if (idInSource != null) {
2690 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2693 Reference citation = getCitation(syn);
2694 if (citation != null) {
2695 originalSource.setCitation(citation);
2696 inferredEpithet.addSource(originalSource);
2699 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2702 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2703 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2705 inferredEpithet
.addSource(originalSource
);
2707 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2708 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2710 inferredSynName
.addSource(originalSource
);
2714 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2716 return inferredEpithet
;
2720 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2721 * Very likely only useful for createInferredSynonyms().
2726 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2727 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2728 if (taxonName
== null) {
2729 taxonName
= zooHashMap
.get(uuid
);
2735 * Returns the idInSource for a given Synonym.
2738 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2739 String idInSource
= null;
2740 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2741 if (sources
.size() == 1) {
2742 IdentifiableSource source
= sources
.iterator().next();
2743 if (source
!= null) {
2744 idInSource
= source
.getIdInSource();
2746 } else if (sources
.size() > 1) {
2749 for (IdentifiableSource source
: sources
) {
2750 idInSource
+= source
.getIdInSource();
2751 if (count
< sources
.size()) {
2756 } else if (sources
.size() == 0){
2757 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2766 * Returns the citation for a given Synonym.
2769 private Reference
getCitation(Synonym syn
) {
2770 Reference citation
= null;
2771 Set
<IdentifiableSource
> sources
= syn
.getSources();
2772 if (sources
.size() == 1) {
2773 IdentifiableSource source
= sources
.iterator().next();
2774 if (source
!= null) {
2775 citation
= source
.getCitation();
2777 } else if (sources
.size() > 1) {
2778 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2785 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2786 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2788 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2789 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2790 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2792 return inferredSynonyms
;
2796 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2798 // TODO quickly implemented, create according dao !!!!
2799 Set
<TaxonNode
> nodes
= new HashSet
<>();
2800 Set
<Classification
> classifications
= new HashSet
<>();
2801 List
<Classification
> list
= new ArrayList
<>();
2803 if (taxonBase
== null) {
2807 taxonBase
= load(taxonBase
.getUuid());
2809 if (taxonBase
instanceof Taxon
) {
2810 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2812 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2814 nodes
.addAll(taxon
.getTaxonNodes());
2817 for (TaxonNode node
: nodes
) {
2818 classifications
.add(node
.getClassification());
2820 list
.addAll(classifications
);
2825 @Transactional(readOnly
= false)
2826 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2828 TaxonRelationshipType oldRelationshipType
,
2829 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2830 UpdateResult result
= new UpdateResult();
2831 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2832 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2833 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2835 result
.addUpdatedObject(fromTaxon
);
2836 result
.addUpdatedObject(toTaxon
);
2837 result
.addUpdatedObject(result
.getCdmEntity());
2843 @Transactional(readOnly
= false)
2844 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2845 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2847 UpdateResult result
= new UpdateResult();
2848 // Create new synonym using concept name
2849 TaxonName synonymName
= fromTaxon
.getName();
2851 // Remove concept relation from taxon
2852 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
2854 // Create a new synonym for the taxon
2856 if (synonymType
!= null
2857 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
2858 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
2859 toTaxon
.addHomotypicSynonym(synonym
);
2861 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
2864 this.saveOrUpdate(toTaxon
);
2865 //TODO: configurator and classification
2866 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
2867 config
.setDeleteNameIfPossible(false);
2868 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
2869 result
.setCdmEntity(synonym
);
2870 result
.addUpdatedObject(toTaxon
);
2871 result
.addUpdatedObject(synonym
);
2876 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
2877 DeleteResult result
= new DeleteResult();
2878 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
2879 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
2880 if (taxonBase
instanceof Taxon
){
2881 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
2882 result
= isDeletableForTaxon(references
, taxonConfig
);
2884 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
2885 result
= isDeletableForSynonym(references
, synonymConfig
);
2890 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
2892 DeleteResult result
= new DeleteResult();
2893 for (CdmBase ref
: references
){
2894 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
2895 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
2896 result
.addException(new ReferencedObjectUndeletableException(message
));
2897 result
.addRelatedObject(ref
);
2905 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
2906 String message
= null;
2907 DeleteResult result
= new DeleteResult();
2908 for (CdmBase ref
: references
){
2909 if (!(ref
instanceof TaxonName
)){
2911 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
2912 message
= "The taxon can't be deleted as long as it has synonyms.";
2914 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
2915 message
= "The taxon can't be deleted as long as it has factual data.";
2918 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
2919 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
2921 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
2922 if (!config
.isDeleteMisappliedNamesAndInvalidDesignations() &&
2923 (((TaxonRelationship
)ref
).getType().isMisappliedNameOrInvalidDesignation())){
2924 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
2926 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
2929 if (ref
instanceof PolytomousKeyNode
){
2930 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
2933 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
2934 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
2938 /* //PolytomousKeyNode
2939 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
2940 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
2945 if (ref
.isInstanceOf(TaxonInteraction
.class)){
2946 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
2950 if (ref
.isInstanceOf(DeterminationEvent
.class)){
2951 message
= "Taxon can't be deleted as it is used in a determination event";
2954 if (message
!= null){
2955 result
.addException(new ReferencedObjectUndeletableException(message
));
2956 result
.addRelatedObject(ref
);
2965 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
2966 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
2968 //preliminary implementation
2970 Set
<Taxon
> taxa
= new HashSet
<>();
2971 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
2972 if (taxonBase
== null){
2973 return new IncludedTaxaDTO();
2974 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
2975 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
2977 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
2978 //TODO partial synonyms ??
2979 //TODO synonyms in general
2980 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
2981 taxa
.add(syn
.getAcceptedTaxon());
2983 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
2986 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
2988 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
2989 related
= makeRelatedIncluded(related
, result
, config
);
2996 * @param uncheckedTaxa
2997 * @param existingTaxa
3000 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3002 * @return the set of conceptually related taxa for further use
3004 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3007 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3008 for (Taxon taxon
: uncheckedTaxa
){
3009 taxonNodes
.addAll(taxon
.getTaxonNodes());
3012 Set
<Taxon
> children
= new HashSet
<>();
3013 if (! config
.onlyCongruent
){
3014 for (TaxonNode node
: taxonNodes
){
3015 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3016 for (TaxonNode child
: childNodes
){
3017 children
.add(child
.getTaxon());
3020 children
.remove(null); // just to be on the save side
3023 Iterator
<Taxon
> it
= children
.iterator();
3024 while(it
.hasNext()){
3025 UUID uuid
= it
.next().getUuid();
3026 if (existingTaxa
.contains(uuid
)){
3029 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3034 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3035 uncheckedAndChildren
.addAll(children
);
3037 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3040 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3045 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3046 * @return the set of these computed taxa
3048 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3049 Set
<Taxon
> result
= new HashSet
<>();
3051 for (Taxon taxon
: unchecked
){
3052 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3053 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3055 for (TaxonRelationship fromRel
: fromRelations
){
3056 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3059 TaxonRelationshipType fromRelType
= fromRel
.getType();
3060 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3061 !config
.onlyCongruent
&& (
3062 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3063 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3066 result
.add(fromRel
.getToTaxon());
3070 for (TaxonRelationship toRel
: toRelations
){
3071 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3074 TaxonRelationshipType fromRelType
= toRel
.getType();
3075 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3076 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3077 result
.add(toRel
.getFromTaxon());
3082 Iterator
<Taxon
> it
= result
.iterator();
3083 while(it
.hasNext()){
3084 UUID uuid
= it
.next().getUuid();
3085 if (existingTaxa
.contains(uuid
)){
3088 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3095 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3096 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3097 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3102 @Transactional(readOnly
= true)
3103 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3104 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3105 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3106 Integer pageNumber
, List
<String
> propertyPaths
) {
3107 if (subtreeFilter
== null){
3108 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3111 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3112 List
<Object
[]> daoResults
= new ArrayList
<>();
3113 if(numberOfResults
> 0) { // no point checking again
3114 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3115 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3118 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3119 for (Object
[] daoObj
: daoResults
){
3121 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3123 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3126 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3130 @Transactional(readOnly
= true)
3131 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3132 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3133 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3134 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3135 if (subtreeFilter
== null){
3136 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3139 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3140 List
<Object
[]> daoResults
= new ArrayList
<>();
3141 if(numberOfResults
> 0) { // no point checking again
3142 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3143 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3146 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3147 for (Object
[] daoObj
: daoResults
){
3149 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3151 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3154 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3158 @Transactional(readOnly
= false)
3159 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3160 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3161 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3163 UpdateResult result
= new UpdateResult();
3164 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3165 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3166 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3172 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3173 UpdateResult result
= new UpdateResult();
3175 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3176 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3177 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3178 //reload to avoid session conflicts
3179 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3181 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3182 if(description
.isProtectedTitleCache()){
3183 String separator
= "";
3184 if(!StringUtils
.isBlank(description
.getTitleCache())){
3187 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3189 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3190 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3191 description
.addAnnotation(annotation
);
3192 toTaxon
.addDescription(description
);
3193 dao
.saveOrUpdate(toTaxon
);
3194 dao
.saveOrUpdate(fromTaxon
);
3195 result
.addUpdatedObject(toTaxon
);
3196 result
.addUpdatedObject(fromTaxon
);
3204 @Transactional(readOnly
= false)
3205 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3206 UUID acceptedTaxonUuid
) {
3207 TaxonBase
<?
> base
= this.load(synonymUUid
);
3208 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3209 base
= this.load(acceptedTaxonUuid
);
3210 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3212 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
);
3219 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3220 Set
<TaxonRelationshipType
> inversTypes
,
3221 Direction direction
, boolean groupMisapplications
,
3222 boolean includeUnpublished
,
3223 Integer pageSize
, Integer pageNumber
) {
3224 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3225 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3227 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3229 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3230 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3231 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3233 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3235 //TODO paging is difficult because misapplication string is an attribute
3237 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3238 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3239 // if(numberOfResults > 0) { // no point checking again
3240 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3243 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3246 List
<Language
> languages
= null;
3248 direction
= Direction
.relatedTo
;
3249 //TODO order hints, property path
3250 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3251 for (TaxonRelationship relation
: relations
){
3252 dto
.addRelation(relation
, direction
, languages
);
3256 direction
= Direction
.relatedFrom
;
3257 //TODO order hints, property path
3258 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3259 for (TaxonRelationship relation
: relations
){
3260 dto
.addRelation(relation
, direction
, languages
);
3263 if (groupMisapplications
){
3265 dto
.createMisapplicationString();