2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.api
.service
;
12 import java
.io
.IOException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.EnumSet
;
15 import java
.util
.HashMap
;
16 import java
.util
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
21 import java
.util
.UUID
;
23 import javax
.persistence
.EntityNotFoundException
;
25 import org
.apache
.commons
.lang
.StringUtils
;
26 import org
.apache
.log4j
.Logger
;
27 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
28 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
29 import org
.apache
.lucene
.search
.BooleanQuery
;
30 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
31 import org
.apache
.lucene
.search
.Query
;
32 import org
.apache
.lucene
.search
.SortField
;
33 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
34 import org
.apache
.lucene
.search
.join
.ScoreMode
;
35 import org
.apache
.lucene
.util
.BytesRef
;
36 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
37 import org
.springframework
.stereotype
.Service
;
38 import org
.springframework
.transaction
.annotation
.Transactional
;
40 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
41 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
47 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
48 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
51 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
52 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
53 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
54 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
55 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
56 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
57 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
58 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
64 import eu
.etaxonomy
.cdm
.api
.service
.util
.TaxonRelationshipEdge
;
65 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
66 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
67 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
68 import eu
.etaxonomy
.cdm
.hibernate
.search
.DefinedTermBaseClassBridge
;
69 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
70 import eu
.etaxonomy
.cdm
.hibernate
.search
.MultilanguageTextFieldBridge
;
71 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
72 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
73 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
74 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
75 import eu
.etaxonomy
.cdm
.model
.common
.DefinedTerm
;
76 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
77 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
78 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
79 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
80 import eu
.etaxonomy
.cdm
.model
.common
.OriginalSourceType
;
81 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
82 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
83 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
84 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
85 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
86 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
87 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
88 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
89 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
90 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
91 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
92 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
93 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
94 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
95 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
96 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentation
;
97 import eu
.etaxonomy
.cdm
.model
.media
.MediaUtils
;
98 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
99 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
100 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
101 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
102 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
103 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
104 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
105 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
106 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
107 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
108 import eu
.etaxonomy
.cdm
.model
.taxon
.HomotypicGroupTaxonComparator
;
109 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
110 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
117 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.ICdmGenericDao
;
118 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
119 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
120 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
121 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
122 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
123 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
124 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
125 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
126 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
127 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
128 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
129 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
133 * @author a.kohlbecker
137 @Transactional(readOnly
= true)
138 public class TaxonServiceImpl
139 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
140 implements ITaxonService
{
142 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
144 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
146 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
148 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
151 private ITaxonNodeDao taxonNodeDao
;
154 private ITaxonNameDao nameDao
;
157 private INameService nameService
;
160 private IOccurrenceService occurrenceService
;
163 private ITaxonNodeService nodeService
;
166 private ICdmGenericDao genericDao
;
169 private IDescriptionService descriptionService
;
172 // private IOrderedTermVocabularyDao orderedVocabularyDao;
175 private IOccurrenceDao occurrenceDao
;
178 private IClassificationDao classificationDao
;
181 private AbstractBeanInitializer beanInitializer
;
184 private ILuceneIndexToolProvider luceneIndexToolProvider
;
186 //************************ CONSTRUCTOR ****************************/
187 public TaxonServiceImpl(){
188 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
191 // ****************************** METHODS ********************************/
198 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
199 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
203 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
204 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
208 @Transactional(readOnly
= false)
209 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
){
210 UpdateResult result
= new UpdateResult();
211 TaxonName synonymName
= synonym
.getName();
212 synonymName
.removeTaxonBase(synonym
);
213 TaxonName taxonName
= acceptedTaxon
.getName();
214 taxonName
.removeTaxonBase(acceptedTaxon
);
216 synonym
.setName(taxonName
);
217 synonym
.setTitleCache(null, false);
218 synonym
.getTitleCache();
219 acceptedTaxon
.setName(synonymName
);
220 acceptedTaxon
.setTitleCache(null, false);
221 acceptedTaxon
.getTitleCache();
222 saveOrUpdate(synonym
);
223 saveOrUpdate(acceptedTaxon
);
224 result
.addUpdatedObject(acceptedTaxon
);
225 result
.addUpdatedObject(synonym
);
228 // the accepted taxon needs a new uuid because the concept has changed
229 // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
230 //acceptedTaxon.setUuid(UUID.randomUUID());
235 @Transactional(readOnly
= false)
236 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean deleteSynonym
) {
237 UpdateResult result
= new UpdateResult();
238 TaxonName acceptedName
= acceptedTaxon
.getName();
239 TaxonName synonymName
= synonym
.getName();
240 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
242 //check synonym is not homotypic
243 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
244 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
245 result
.addException(new HomotypicalGroupChangeException(message
));
250 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, acceptedTaxon
.getSec());
251 dao
.save(newAcceptedTaxon
);
252 result
.setCdmEntity(newAcceptedTaxon
);
253 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
254 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
256 for (Synonym heteroSynonym
: heteroSynonyms
){
257 if (synonym
.equals(heteroSynonym
)){
258 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
260 //move synonyms in same homotypic group to new accepted taxon
261 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
264 dao
.saveOrUpdate(acceptedTaxon
);
265 result
.addUpdatedObject(acceptedTaxon
);
270 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
271 config
.setDeleteNameIfPossible(false);
272 this.deleteSynonym(synonym
, config
);
274 } catch (Exception e
) {
275 result
.addException(e
);
283 @Transactional(readOnly
= false)
284 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
285 UUID acceptedTaxonUuid
,
286 UUID newParentNodeUuid
,
287 boolean deleteSynonym
) {
288 UpdateResult result
= new UpdateResult();
289 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
290 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
291 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, deleteSynonym
);
292 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
293 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
294 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
295 taxonNodeDao
.save(newNode
);
296 result
.addUpdatedObject(newTaxon
);
297 result
.addUpdatedObject(acceptedTaxon
);
298 result
.setCdmEntity(newNode
);
306 @Transactional(readOnly
= false)
307 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
309 TaxonRelationshipType taxonRelationshipType
,
311 String microcitation
){
313 UpdateResult result
= new UpdateResult();
314 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
315 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
316 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
317 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
318 // result.setCdmEntity(relatedTaxon);
319 result
.addUpdatedObject(relatedTaxon
);
320 result
.addUpdatedObject(toTaxon
);
325 @Transactional(readOnly
= false)
326 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
327 // Get name from synonym
328 if (synonym
== null){
332 UpdateResult result
= new UpdateResult();
334 TaxonName synonymName
= synonym
.getName();
336 /* // remove synonym from taxon
337 toTaxon.removeSynonym(synonym);
339 // Create a taxon with synonym name
340 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
342 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
344 // Add taxon relation
345 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
346 result
.setCdmEntity(fromTaxon
);
347 // since we are swapping names, we have to detach the name from the synonym completely.
348 // Otherwise the synonym will still be in the list of typified names.
349 // synonym.getName().removeTaxonBase(synonym);
350 result
.includeResult(this.deleteSynonym(synonym
, null));
355 @Transactional(readOnly
= false)
357 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
358 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
360 TaxonName synonymName
= synonym
.getName();
361 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
364 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
365 newHomotypicalGroup
.addTypifiedName(synonymName
);
367 //remove existing basionym relationships
368 synonymName
.removeBasionyms();
370 //add basionym relationship
371 if (setBasionymRelationIfApplicable
){
372 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
373 for (TaxonName basionym
: basionyms
){
374 synonymName
.addBasionym(basionym
);
378 //set synonym relationship correctly
379 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
381 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
382 if (acceptedTaxon
!= null){
384 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
385 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
386 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
387 synonym
.setType(newRelationType
);
389 if (hasNewTargetTaxon
){
390 acceptedTaxon
.removeSynonym(synonym
, false);
393 if (hasNewTargetTaxon
){
394 @SuppressWarnings("null")
395 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
396 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
397 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
398 targetTaxon
.addSynonym(synonym
, relType
);
404 @Transactional(readOnly
= false)
405 public void updateTitleCache(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
407 clazz
= TaxonBase
.class;
409 super.updateTitleCacheImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
414 protected void setDao(ITaxonDao dao
) {
419 public Pager
<TaxonBase
> findTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
420 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
422 List
<TaxonBase
> results
= new ArrayList
<>();
423 if(numberOfResults
> 0) { // no point checking again
424 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
427 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
431 public List
<TaxonBase
> listTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
432 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
434 List
<TaxonBase
> results
= new ArrayList
<>();
435 if(numberOfResults
> 0) { // no point checking again
436 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
443 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
444 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
445 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
447 List
<TaxonRelationship
> results
= new ArrayList
<>();
448 if(numberOfResults
> 0) { // no point checking again
449 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
455 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
456 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
457 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
459 List
<TaxonRelationship
> results
= new ArrayList
<>();
460 if(numberOfResults
> 0) { // no point checking again
461 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
463 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
467 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
468 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
469 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
471 List
<TaxonRelationship
> results
= new ArrayList
<>();
472 if(numberOfResults
> 0) { // no point checking again
473 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
479 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
480 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
481 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
483 List
<TaxonRelationship
> results
= new ArrayList
<>();
484 if(numberOfResults
> 0) { // no point checking again
485 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
487 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
491 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
492 Integer pageSize
, Integer pageStart
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
493 Long numberOfResults
= dao
.countTaxonRelationships(types
);
495 List
<TaxonRelationship
> results
= new ArrayList
<>();
496 if(numberOfResults
> 0) {
497 results
= dao
.getTaxonRelationships(types
, pageSize
, pageStart
, orderHints
, propertyPaths
);
503 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
, List
<String
> propertyPaths
){
508 Synonym synonym
= null;
511 synonym
= (Synonym
) dao
.load(synonymUuid
);
512 } catch (ClassCastException e
){
513 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
514 } catch (NullPointerException e
){
515 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
518 Classification classificationFilter
= null;
519 if(classificationUuid
!= null){
521 classificationFilter
= classificationDao
.load(classificationUuid
);
522 } catch (NullPointerException e
){
523 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
525 if(classificationFilter
== null){
530 count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
532 result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
540 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
541 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
543 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
544 relatedTaxa
.remove(taxon
);
545 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
551 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
552 * <code>taxon</code> supplied as parameter.
555 * @param includeRelationships
557 * @param maxDepth can be <code>null</code> for infinite depth
560 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
561 boolean includeUnpublished
, Integer maxDepth
) {
567 if(includeRelationships
.isEmpty()){
571 if(maxDepth
!= null) {
574 if(logger
.isDebugEnabled()){
575 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
577 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
, null, includeUnpublished
, null, null, null, null, null);
578 for (TaxonRelationship taxRel
: taxonRelationships
) {
581 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
584 // filter by includeRelationships
585 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
586 if ( relationshipEdgeFilter
.getTaxonRelationshipTypes().equals(taxRel
.getType()) ) {
587 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
588 if(logger
.isDebugEnabled()){
589 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
591 taxa
.add(taxRel
.getToTaxon());
592 if(maxDepth
== null || maxDepth
> 0) {
593 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
596 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
597 taxa
.add(taxRel
.getFromTaxon());
598 if(logger
.isDebugEnabled()){
599 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
601 if(maxDepth
== null || maxDepth
> 0) {
602 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
612 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
613 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
615 List
<Synonym
> results
= new ArrayList
<>();
616 if(numberOfResults
> 0) { // no point checking again
617 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
620 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
624 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
625 List
<List
<Synonym
>> result
= new ArrayList
<>();
626 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
627 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
631 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
634 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
635 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
636 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
644 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
645 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
646 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
648 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
652 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
653 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
654 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
655 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
656 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
657 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
659 return heterotypicSynonymyGroups
;
663 public List
<UuidAndTitleCache
<IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
665 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
666 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
667 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(),
668 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
670 return new ArrayList
<>();
675 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
677 List
<IdentifiableEntity
> results
= new ArrayList
<>();
678 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
679 List
<TaxonBase
> taxa
= null;
682 long numberTaxaResults
= 0L;
685 List
<String
> propertyPath
= new ArrayList
<>();
686 if(configurator
.getTaxonPropertyPath() != null){
687 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
691 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
692 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
694 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
695 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
696 configurator
.getClassification(), configurator
.getMatchMode(),
697 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
700 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
701 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
702 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
703 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(),
704 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
705 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
709 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
712 results
.addAll(taxa
);
715 numberOfResults
+= numberTaxaResults
;
717 // Names without taxa
718 if (configurator
.isDoNamesWithoutTaxa()) {
719 int numberNameResults
= 0;
721 List
<?
extends TaxonName
> names
=
722 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
723 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
724 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
725 if (names
.size() > 0) {
726 for (TaxonName taxonName
: names
) {
727 if (taxonName
.getTaxonBases().size() == 0) {
728 results
.add(taxonName
);
732 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
733 numberOfResults
+= numberNameResults
;
737 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
740 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
741 return dao
.getUuidAndTitleCache(limit
, pattern
);
745 public List
<MediaRepresentation
> getAllMedia(Taxon taxon
, int size
, int height
, int widthOrDuration
, String
[] mimeTypes
){
746 List
<MediaRepresentation
> medRep
= new ArrayList
<>();
747 taxon
= (Taxon
)dao
.load(taxon
.getUuid());
748 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
749 for (TaxonDescription taxDesc
: descriptions
){
750 Set
<DescriptionElementBase
> elements
= taxDesc
.getElements();
751 for (DescriptionElementBase descElem
: elements
){
752 for(Media media
: descElem
.getMedia()){
754 //find the best matching representation
755 medRep
.add(MediaUtils
.findBestMatchingRepresentation(media
, null, size
, height
, widthOrDuration
, mimeTypes
));
764 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
765 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
769 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
770 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
771 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
774 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
776 // logger.setLevel(Level.TRACE);
777 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
779 logger
.trace("listMedia() - START");
781 Set
<Taxon
> taxa
= new HashSet
<>();
782 List
<Media
> taxonMedia
= new ArrayList
<>();
783 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
785 if (limitToGalleries
== null) {
786 limitToGalleries
= false;
789 // --- resolve related taxa
790 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
791 logger
.trace("listMedia() - resolve related taxa");
792 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
795 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
797 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
798 logger
.trace("listMedia() - includeTaxonDescriptions");
799 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
800 // --- TaxonDescriptions
801 for (Taxon t
: taxa
) {
802 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
804 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
805 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
806 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
807 for (Media media
: element
.getMedia()) {
808 if(taxonDescription
.isImageGallery()){
809 taxonMedia
.add(media
);
812 nonImageGalleryImages
.add(media
);
818 //put images from image gallery first (#3242)
819 taxonMedia
.addAll(nonImageGalleryImages
);
823 if(includeOccurrences
!= null && includeOccurrences
) {
824 logger
.trace("listMedia() - includeOccurrences");
825 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
827 for (Taxon t
: taxa
) {
828 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
830 for (SpecimenOrObservationBase occurrence
: specimensOrObservations
) {
832 // direct media removed from specimen #3597
833 // taxonMedia.addAll(occurrence.getMedia());
835 // SpecimenDescriptions
836 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
837 for (DescriptionBase specimenDescription
: specimenDescriptions
) {
838 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
839 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
840 for (DescriptionElementBase element
: elements
) {
841 for (Media media
: element
.getMedia()) {
842 taxonMedia
.add(media
);
848 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
849 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
851 //TODO why may collections have media attached? #
852 if (derivedUnit
.getCollection() != null){
853 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
857 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
861 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
862 logger
.trace("listMedia() - includeTaxonNameDescriptions");
863 // --- TaxonNameDescription
864 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
865 for (Taxon t
: taxa
) {
866 nameDescriptions
.addAll(t
.getName().getDescriptions());
868 for(TaxonNameDescription nameDescription
: nameDescriptions
){
869 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
870 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
871 for (DescriptionElementBase element
: elements
) {
872 for (Media media
: element
.getMedia()) {
873 taxonMedia
.add(media
);
881 logger
.trace("listMedia() - initialize");
882 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
884 logger
.trace("listMedia() - END");
890 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
891 return this.dao
.loadList(listOfIDs
, null);
895 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
896 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
900 public int countSynonyms(boolean onlyAttachedToTaxon
){
901 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
905 public List
<TaxonName
> findIdenticalTaxonNames(List
<String
> propertyPath
) {
906 return this.dao
.findIdenticalTaxonNames(propertyPath
);
910 @Transactional(readOnly
=false)
911 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
914 config
= new TaxonDeletionConfigurator();
916 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
917 DeleteResult result
= new DeleteResult();
920 result
.addException(new Exception ("The taxon was already deleted."));
923 taxon
= HibernateProxyHelper
.deproxy(taxon
);
924 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
925 result
= isDeletable(taxonUUID
, config
);
928 // --- DeleteSynonymRelations
929 if (config
.isDeleteSynonymRelations()){
930 boolean removeSynonymNameFromHomotypicalGroup
= false;
931 // use tmp Set to avoid concurrent modification
932 Set
<Synonym
> synsToDelete
= new HashSet
<>();
933 synsToDelete
.addAll(taxon
.getSynonyms());
934 for (Synonym synonym
: synsToDelete
){
935 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
937 // --- DeleteSynonymsIfPossible
938 if (config
.isDeleteSynonymsIfPossible()){
940 boolean newHomotypicGroupIfNeeded
= true;
941 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
942 result
.includeResult(deleteSynonym(synonym
, synConfig
));
947 // --- DeleteTaxonRelationships
948 if (! config
.isDeleteTaxonRelationships()){
949 if (taxon
.getTaxonRelations().size() > 0){
951 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
952 "Remove taxon from all relations to other taxa prior to deletion."));
955 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
956 configRelTaxon
.setDeleteTaxonNodes(false);
957 configRelTaxon
.setDeleteConceptRelationships(true);
959 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
960 if (config
.isDeleteMisappliedNamesAndInvalidDesignations()
961 && taxRel
.getType().isMisappliedNameOrInvalidDesignation()
962 && taxon
.equals(taxRel
.getToTaxon())){
963 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
964 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
966 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
967 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
968 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
969 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
972 taxon
.removeTaxonRelation(taxRel
);
977 if (config
.isDeleteDescriptions()){
978 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
979 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
980 for (TaxonDescription desc
: descriptions
){
981 //TODO use description delete configurator ?
982 //FIXME check if description is ALWAYS deletable
983 if (desc
.getDescribedSpecimenOrObservation() != null){
985 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
986 " which also describes specimens or observations"));
989 removeDescriptions
.add(desc
);
994 for (TaxonDescription desc
: removeDescriptions
){
995 taxon
.removeDescription(desc
);
996 descriptionService
.delete(desc
);
1004 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1005 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1007 if (taxon
.getTaxonNodes().size() != 0){
1008 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1009 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1010 TaxonNode node
= null;
1011 boolean deleteChildren
;
1012 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1013 deleteChildren
= true;
1015 deleteChildren
= false;
1017 boolean success
= true;
1018 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1019 while (iterator
.hasNext()){
1020 node
= iterator
.next();
1021 if (node
.getClassification().equals(classification
)){
1027 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1028 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1029 nodeService
.delete(node
);
1030 result
.addDeletedObject(node
);
1033 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1035 } else if (config
.isDeleteInAllClassifications()){
1036 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1037 nodesList
.addAll(taxon
.getTaxonNodes());
1038 for (ITaxonTreeNode treeNode
: nodesList
){
1039 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1040 if(!deleteChildren
){
1041 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1042 for (Object childNode
: childNodes
){
1043 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1044 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1048 config
.getTaxonNodeConfig().setDeleteElement(false);
1049 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1050 if (!resultNodes
.isOk()){
1051 result
.addExceptions(resultNodes
.getExceptions());
1052 result
.setStatus(resultNodes
.getStatus());
1054 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1059 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1063 TaxonName name
= taxon
.getName();
1064 taxon
.setName(null);
1065 this.saveOrUpdate(taxon
);
1067 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1070 result
.addDeletedObject(taxon
);
1071 }catch(Exception e
){
1072 result
.addException(e
);
1077 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1081 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1082 DeleteResult nameResult
= new DeleteResult();
1083 //remove name if possible (and required)
1085 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1087 if (nameResult
.isError() || nameResult
.isAbort()){
1088 result
.addRelatedObject(name
);
1089 result
.addExceptions(nameResult
.getExceptions());
1091 result
.includeResult(nameResult
);
1100 private String
checkForReferences(Taxon taxon
){
1101 Set
<CdmBase
> referencingObjects
= genericDao
.getReferencingObjects(taxon
);
1102 for (CdmBase referencingObject
: referencingObjects
){
1103 //IIdentificationKeys (Media, Polytomous, MultiAccess)
1104 if (HibernateProxyHelper
.isInstanceOf(referencingObject
, IIdentificationKey
.class)){
1105 String message
= "Taxon" + taxon
.getTitleCache() + "can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
1111 /* //PolytomousKeyNode
1112 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1113 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1118 if (referencingObject
.isInstanceOf(TaxonInteraction
.class)){
1119 String message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1124 if (referencingObject
.isInstanceOf(DeterminationEvent
.class)){
1125 String message
= "Taxon can't be deleted as it is used in a determination event";
1131 referencingObjects
= null;
1135 private boolean checkForPolytomousKeys(Taxon taxon
){
1136 boolean result
= false;
1137 List
<CdmBase
> list
= genericDao
.getCdmBasesByFieldAndClass(PolytomousKeyNode
.class, "taxon", taxon
, null);
1138 if (!list
.isEmpty()) {
1145 @Transactional(readOnly
= false)
1146 public DeleteResult
delete(UUID synUUID
){
1147 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1148 return this.deleteSynonym(syn
, null);
1152 @Transactional(readOnly
= false)
1153 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1154 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1160 @Transactional(readOnly
= false)
1161 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1162 DeleteResult result
= new DeleteResult();
1163 if (synonym
== null){
1165 result
.addException(new Exception("The synonym was already deleted."));
1169 if (config
== null){
1170 config
= new SynonymDeletionConfigurator();
1173 result
= isDeletable(synonym
.getUuid(), config
);
1177 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1180 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1182 if (accTaxon
!= null){
1183 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1184 accTaxon
.removeSynonym(synonym
, false);
1185 this.saveOrUpdate(accTaxon
);
1186 result
.addUpdatedObject(accTaxon
);
1188 this.saveOrUpdate(synonym
);
1192 TaxonName name
= synonym
.getName();
1193 synonym
.setName(null);
1195 dao
.delete(synonym
);
1196 result
.addDeletedObject(synonym
);
1198 //remove name if possible (and required)
1199 if (name
!= null && config
.isDeleteNameIfPossible()){
1201 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1202 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1203 result
.addExceptions(nameDeleteResult
.getExceptions());
1204 result
.addRelatedObject(name
);
1206 result
.addDeletedObject(name
);
1215 public List
<TaxonName
> findIdenticalTaxonNameIds(List
<String
> propertyPath
) {
1217 return this.dao
.findIdenticalNamesNew(propertyPath
);
1222 public Taxon
findBestMatchingTaxon(String taxonName
) {
1223 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1224 config
.setTaxonNameTitle(taxonName
);
1225 return findBestMatchingTaxon(config
);
1229 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1231 Taxon bestCandidate
= null;
1233 // 1. search for accepted taxa
1234 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1235 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, null, null);
1236 boolean bestCandidateMatchesSecUuid
= false;
1237 boolean bestCandidateIsInClassification
= false;
1238 int countEqualCandidates
= 0;
1239 for(TaxonBase taxonBaseCandidate
: taxonList
){
1240 if(taxonBaseCandidate
instanceof Taxon
){
1241 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1242 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1243 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1245 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1246 bestCandidate
= newCanditate
;
1247 countEqualCandidates
= 1;
1248 bestCandidateMatchesSecUuid
= true;
1252 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1253 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1255 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1256 bestCandidate
= newCanditate
;
1257 countEqualCandidates
= 1;
1258 bestCandidateIsInClassification
= true;
1261 if (bestCandidate
== null){
1262 bestCandidate
= newCanditate
;
1263 countEqualCandidates
= 1;
1267 }else{ //not Taxon.class
1270 countEqualCandidates
++;
1273 if (bestCandidate
!= null){
1274 if(countEqualCandidates
> 1){
1275 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1276 return bestCandidate
;
1278 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1279 return bestCandidate
;
1284 // 2. search for synonyms
1285 if (config
.isIncludeSynonyms()){
1286 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1287 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, null, 0, null, null);
1288 for(TaxonBase taxonBase
: synonymList
){
1289 if(taxonBase
instanceof Synonym
){
1290 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1291 bestCandidate
= synonym
.getAcceptedTaxon();
1292 if(bestCandidate
!= null){
1293 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1294 return bestCandidate
;
1296 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1301 } catch (Exception e
){
1303 e
.printStackTrace();
1306 return bestCandidate
;
1309 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1310 UUID configClassificationUuid
= config
.getClassificationUuid();
1311 if (configClassificationUuid
== null){
1314 for (TaxonNode node
: taxon
.getTaxonNodes()){
1315 UUID classUuid
= node
.getClassification().getUuid();
1316 if (configClassificationUuid
.equals(classUuid
)){
1323 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1324 UUID configSecUuid
= config
.getSecUuid();
1325 if (configSecUuid
== null){
1328 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1329 return configSecUuid
.equals(taxonSecUuid
);
1333 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1334 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, MatchMode
.EXACT
, null, null, 0, null, null);
1335 if(! synonymList
.isEmpty()){
1336 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1337 if(synonymList
.size() == 1){
1338 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1341 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1349 @Transactional(readOnly
= false)
1350 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1352 boolean moveHomotypicGroup
,
1353 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1354 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1356 oldSynonym
.getSec(),
1357 oldSynonym
.getSecMicroReference(),
1362 @Transactional(readOnly
= false)
1363 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1365 boolean moveHomotypicGroup
,
1366 SynonymType newSynonymType
,
1367 Reference newSecundum
,
1368 String newSecundumDetail
,
1369 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1371 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1372 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1373 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1374 TaxonName synonymName
= synonym
.getName();
1375 TaxonName fromTaxonName
= oldTaxon
.getName();
1376 //set default relationship type
1377 if (newSynonymType
== null){
1378 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1380 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1382 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1383 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1384 boolean isSingleInGroup
= !(hgSize
> 1);
1386 if (! isSingleInGroup
){
1387 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1388 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1389 if (isHomotypicToAccepted
){
1390 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.";
1391 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1392 message
= String
.format(message
, homotypicRelatives
);
1393 throw new HomotypicalGroupChangeException(message
);
1395 if (! moveHomotypicGroup
){
1396 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.";
1397 throw new HomotypicalGroupChangeException(message
);
1400 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1402 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1404 UpdateResult result
= new UpdateResult();
1405 //move all synonyms to new taxon
1406 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1407 for (Synonym synRelation
: homotypicSynonyms
){
1409 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1410 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1411 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1412 oldTaxon
.removeSynonym(synRelation
, false);
1413 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1414 synRelation
.setSec(newSecundum
);
1416 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1417 synRelation
.setSecMicroReference(newSecundumDetail
);
1420 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1421 if (!synRelation
.equals(oldSynonym
)){
1426 result
.addUpdatedObject(oldTaxon
);
1427 result
.addUpdatedObject(newTaxon
);
1428 saveOrUpdate(oldTaxon
);
1429 saveOrUpdate(newTaxon
);
1435 public <T
extends TaxonBase
> List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1436 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1440 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1441 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1442 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
,
1443 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1444 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1446 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, null,
1447 includeUnpublished
, languages
, highlightFragments
, null);
1449 // --- execute search
1450 TopGroups
<BytesRef
> topDocsResultSet
;
1452 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1453 } catch (ParseException e
) {
1454 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1455 luceneParseException
.setStackTrace(e
.getStackTrace());
1456 throw luceneParseException
;
1459 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1460 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1462 // --- initialize taxa, thighlight matches ....
1463 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1464 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1465 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1467 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1468 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1472 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1473 Classification classification
,
1474 Integer pageSize
, Integer pageNumber
,
1475 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1477 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
);
1479 // --- execute search
1480 TopGroups
<BytesRef
> topDocsResultSet
;
1482 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1483 } catch (ParseException e
) {
1484 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1485 luceneParseException
.setStackTrace(e
.getStackTrace());
1486 throw luceneParseException
;
1489 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1490 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1492 // --- initialize taxa, thighlight matches ....
1493 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1494 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1495 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1497 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1498 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1503 * @param queryString
1504 * @param classification
1505 * @param includeUnpublished
1507 * @param highlightFragments
1508 * @param sortFields TODO
1509 * @param directorySelectClass
1512 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1513 Classification classification
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1514 boolean highlightFragments
, SortField
[] sortFields
) {
1516 Builder finalQueryBuilder
= new Builder();
1517 Builder textQueryBuilder
= new Builder();
1519 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1520 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1522 if(sortFields
== null){
1523 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1525 luceneSearch
.setSortFields(sortFields
);
1527 // ---- search criteria
1528 luceneSearch
.setCdmTypRestriction(clazz
);
1530 if(!queryString
.isEmpty() && !queryString
.equals("*") && !queryString
.equals("?") ) {
1531 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1532 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1534 if(className
!= null){
1535 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1538 BooleanQuery textQuery
= textQueryBuilder
.build();
1539 if(textQuery
.clauses().size() > 0) {
1540 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1544 if(classification
!= null){
1545 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1547 if(!includeUnpublished
) {
1548 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1549 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1550 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1553 luceneSearch
.setQuery(finalQueryBuilder
.build());
1555 if(highlightFragments
){
1556 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1558 return luceneSearch
;
1562 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1563 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1564 * drawback of requiring to do the join an indexing time.
1565 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1567 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1569 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1570 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1572 * @param queryString
1573 * @param classification
1575 * @param highlightFragments
1576 * @param sortFields TODO
1579 * @throws IOException
1581 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1582 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
,
1583 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1586 String queryTermField
;
1587 String toField
= "id"; // TaxonBase.uuid
1588 String publishField
;
1589 String publishFieldInvers
;
1591 if(edge
.isBidirectional()){
1592 throw new RuntimeException("Bidirectional joining not supported!");
1595 fromField
= "relatedFrom.id";
1596 queryTermField
= "relatedFrom.titleCache";
1597 publishField
= "relatedFrom.publish";
1598 publishFieldInvers
= "relatedTo.publish";
1599 } else if(edge
.isInvers()) {
1600 fromField
= "relatedTo.id";
1601 queryTermField
= "relatedTo.titleCache";
1602 publishField
= "relatedTo.publish";
1603 publishFieldInvers
= "relatedFrom.publish";
1605 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1608 Builder finalQueryBuilder
= new Builder();
1610 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1611 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1613 Builder joinFromQueryBuilder
= new Builder();
1614 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1615 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getTaxonRelationshipTypes()), Occur
.MUST
);
1616 if(!includeUnpublished
){
1617 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1618 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1621 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1623 if(sortFields
== null){
1624 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1626 luceneSearch
.setSortFields(sortFields
);
1628 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1630 if(classification
!= null){
1631 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1634 luceneSearch
.setQuery(finalQueryBuilder
.build());
1636 if(highlightFragments
){
1637 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1639 return luceneSearch
;
1643 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1644 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
, Classification classification
,
1645 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1646 boolean highlightFragments
, Integer pageSize
,
1647 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1648 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1650 // FIXME: allow taxonomic ordering
1651 // 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";
1652 // this require building a special sort column by a special classBridge
1653 if(highlightFragments
){
1654 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1655 "currently not fully supported by this method and thus " +
1656 "may not work with common names and misapplied names.");
1659 // convert sets to lists
1660 List
<NamedArea
> namedAreaList
= null;
1661 List
<PresenceAbsenceTerm
>distributionStatusList
= null;
1662 if(namedAreas
!= null){
1663 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1664 namedAreaList
.addAll(namedAreas
);
1666 if(distributionStatus
!= null){
1667 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1668 distributionStatusList
.addAll(distributionStatus
);
1671 // set default if parameter is null
1672 if(searchModes
== null){
1673 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1676 // set sort order and thus override any sort orders which may have been
1677 // defined by prepare*Search methods
1678 if(orderHints
== null){
1679 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1681 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1683 for(OrderHint oh
: orderHints
){
1684 sortFields
[i
++] = oh
.toSortField();
1686 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1687 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1690 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1692 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1693 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1696 ======== filtering by distribution , HOWTO ========
1698 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1699 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1700 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1701 which will be put into a FilteredQuersy in the end ?
1704 3. how does it work in spatial?
1706 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1707 - http://www.infoq.com/articles/LuceneSpatialSupport
1708 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1709 ------------------------------------------------------------------------
1712 A) use a separate distribution filter per index sub-query/search:
1713 - byTaxonSyonym (query TaxaonBase):
1714 use a join area filter (Distribution -> TaxonBase)
1715 - byCommonName (query DescriptionElementBase): use an area filter on
1716 DescriptionElementBase !!! PROBLEM !!!
1717 This cannot work since the distributions are different entities than the
1718 common names and thus these are different lucene documents.
1719 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1720 use a join area filter (Distribution -> TaxonBase)
1722 B) use a common distribution filter for all index sub-query/searches:
1723 - use a common join area filter (Distribution -> TaxonBase)
1724 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1725 PROBLEM in this case: we are losing the fragment highlighting for the
1726 common names, since the returned documents are always TaxonBases
1729 /* The QueryFactory for creating filter queries on Distributions should
1730 * The query factory used for the common names query cannot be reused
1731 * for this case, since we want to only record the text fields which are
1732 * actually used in the primary query
1734 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1736 Builder multiIndexByAreaFilterBuilder
= new Builder();
1737 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1739 // search for taxa or synonyms
1740 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1741 @SuppressWarnings("rawtypes")
1742 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1743 String className
= null;
1744 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1745 taxonBaseSubclass
= Taxon
.class;
1746 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1747 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1749 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
, queryString
, classification
, className
,
1750 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1751 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1752 /* A) does not work!!!!
1753 if(addDistributionFilter){
1754 // in this case we need a filter which uses a join query
1755 // to get the TaxonBase documents for the DescriptionElementBase documents
1756 // which are matching the areas in question
1757 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1759 distributionStatusList,
1760 distributionFilterQueryFactory
1762 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1765 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1766 // add additional area filter for synonyms
1767 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1768 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1770 //TODO replace by createByDistributionJoinQuery
1771 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1772 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1773 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1778 // search by CommonTaxonName
1779 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1781 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1782 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1783 CommonTaxonName
.class,
1784 "inDescription.taxon.id",
1786 QueryFactory
.addTypeRestriction(
1787 createByDescriptionElementFullTextQuery(queryString
, classification
, null, languages
, descriptionElementQueryFactory
)
1788 , CommonTaxonName
.class
1789 ).build(), "id", null, ScoreMode
.Max
);
1790 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1791 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1792 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1793 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1794 Builder builder
= new BooleanQuery
.Builder();
1795 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1796 if(!includeUnpublished
) {
1797 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1798 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1800 byCommonNameSearch
.setQuery(builder
.build());
1801 byCommonNameSearch
.setSortFields(sortFields
);
1803 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1805 luceneSearches
.add(byCommonNameSearch
);
1807 /* A) does not work!!!!
1809 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1810 queryString, classification, null, languages, highlightFragments)
1812 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1813 if(addDistributionFilter){
1814 // in this case we are able to use DescriptionElementBase documents
1815 // which are matching the areas in question directly
1816 BooleanQuery byDistributionQuery = createByDistributionQuery(
1818 distributionStatusList,
1819 distributionFilterQueryFactory
1821 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1826 // search by misapplied names
1827 //TODO merge with pro parte synonym search once #7487 is fixed
1828 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1830 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1831 // which allows doing query time joins
1832 // finds the misapplied name (Taxon B) which is an misapplication for
1833 // a related Taxon A.
1835 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1836 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1837 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1839 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1840 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1843 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1844 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1845 queryString
, classification
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1846 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1848 if(addDistributionFilter
){
1849 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1852 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
1853 * Maybe this is a bug in java itself.
1855 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1858 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1860 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1861 * will execute as expected:
1863 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1864 * String toField = "relation." + misappliedNameForUuid +".to.id";
1866 * Comparing both strings by the String.equals method returns true, so both String are identical.
1868 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1869 * 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)
1870 * The bug is persistent after a reboot of the development computer.
1872 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1873 // String toField = "relation." + misappliedNameForUuid +".to.id";
1874 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1875 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1876 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1878 //TODO replace by createByDistributionJoinQuery
1879 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1880 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1881 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1883 // debug code for bug described above
1884 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1885 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1886 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1888 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1893 // search by pro parte synonyms
1894 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1895 //TODO merge with misapplied name search once #7487 is fixed
1896 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1897 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
1899 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1900 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1901 queryString
, classification
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1902 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1904 if(addDistributionFilter
){
1905 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1906 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
1907 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1908 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1909 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1910 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1912 }//end pro parte synonyms
1916 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
1917 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
1920 if(addDistributionFilter
){
1923 // in this case we need a filter which uses a join query
1924 // to get the TaxonBase documents for the DescriptionElementBase documents
1925 // which are matching the areas in question
1927 // for doTaxa, doByCommonName
1928 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
1930 distributionStatusList
,
1931 distributionFilterQueryFactory
,
1934 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1937 if (addDistributionFilter
){
1938 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
1942 // --- execute search
1943 TopGroups
<BytesRef
> topDocsResultSet
;
1945 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
1946 } catch (ParseException e
) {
1947 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1948 luceneParseException
.setStackTrace(e
.getStackTrace());
1949 throw luceneParseException
;
1952 // --- initialize taxa, highlight matches ....
1953 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
1956 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1957 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1959 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1960 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1964 * @param namedAreaList at least one area must be in the list
1965 * @param distributionStatusList optional
1966 * @param toType toType
1967 * Optional parameter. Only used for debugging to print the toType documents
1968 * @param asFilter TODO
1970 * @throws IOException
1972 protected Query
createByDistributionJoinQuery(
1973 List
<NamedArea
> namedAreaList
,
1974 List
<PresenceAbsenceTerm
> distributionStatusList
,
1975 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
1976 ) throws IOException
{
1978 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1979 String toField
= "id"; // id in toType usually this is the TaxonBase index
1981 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
1983 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
1985 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
1987 return taxonAreaJoinQuery
;
1991 * @param namedAreaList
1992 * @param distributionStatusList
1993 * @param queryFactory
1996 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
1997 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
1998 Builder areaQueryBuilder
= new Builder();
1999 // area field from Distribution
2000 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2002 // status field from Distribution
2003 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2004 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2007 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2008 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2013 * This method has been primarily created for testing the area join query but might
2014 * also be useful in other situations
2016 * @param namedAreaList
2017 * @param distributionStatusList
2018 * @param classification
2019 * @param highlightFragments
2021 * @throws IOException
2023 protected LuceneSearch
prepareByDistributionSearch(
2024 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2025 Classification classification
) throws IOException
{
2027 Builder finalQueryBuilder
= new Builder();
2029 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2031 // FIXME is this query factory using the wrong type?
2032 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2034 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2035 luceneSearch
.setSortFields(sortFields
);
2038 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2040 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2042 if(classification
!= null){
2043 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
2045 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2046 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2047 luceneSearch
.setQuery(finalQuery
);
2049 return luceneSearch
;
2053 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2054 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2055 Classification classification
, List
<Feature
> features
, List
<Language
> languages
,
2056 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2059 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, features
, languages
, highlightFragments
);
2061 // --- execute search
2062 TopGroups
<BytesRef
> topDocsResultSet
;
2064 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2065 } catch (ParseException e
) {
2066 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2067 luceneParseException
.setStackTrace(e
.getStackTrace());
2068 throw luceneParseException
;
2071 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2072 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2074 // --- initialize taxa, highlight matches ....
2075 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2076 @SuppressWarnings("rawtypes")
2077 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2078 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2080 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2081 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2087 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2088 Classification classification
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2089 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2091 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
, classification
,
2092 null, languages
, highlightFragments
);
2093 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, null,
2094 includeUnpublished
, languages
, highlightFragments
, null);
2096 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2098 // --- execute search
2099 TopGroups
<BytesRef
> topDocsResultSet
;
2101 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2102 } catch (ParseException e
) {
2103 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2104 luceneParseException
.setStackTrace(e
.getStackTrace());
2105 throw luceneParseException
;
2108 // --- initialize taxa, highlight matches ....
2109 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2111 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2112 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2113 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2115 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2116 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2118 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2119 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2126 * @param queryString
2127 * @param classification
2130 * @param highlightFragments
2131 * @param directorySelectClass
2134 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2135 String queryString
, Classification classification
, List
<Feature
> features
,
2136 List
<Language
> languages
, boolean highlightFragments
) {
2138 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2139 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2141 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2143 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, features
,
2144 languages
, descriptionElementQueryFactory
);
2146 luceneSearch
.setSortFields(sortFields
);
2147 luceneSearch
.setCdmTypRestriction(clazz
);
2148 luceneSearch
.setQuery(finalQuery
);
2149 if(highlightFragments
){
2150 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2153 return luceneSearch
;
2157 * @param queryString
2158 * @param classification
2161 * @param descriptionElementQueryFactory
2164 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
, Classification classification
,
2165 List
<Feature
> features
, List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2166 Builder finalQueryBuilder
= new Builder();
2167 Builder textQueryBuilder
= new Builder();
2168 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2171 Builder nameQueryBuilder
= new Builder();
2172 if(languages
== null || languages
.size() == 0){
2173 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2175 Builder languageSubQueryBuilder
= new Builder();
2176 for(Language lang
: languages
){
2177 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2179 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2180 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2182 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2185 // text field from TextData
2186 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2188 // --- TermBase fields - by representation ----
2189 // state field from CategoricalData
2190 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2192 // state field from CategoricalData
2193 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2195 // area field from Distribution
2196 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2198 // status field from Distribution
2199 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2201 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2202 // --- classification ----
2204 if(classification
!= null){
2205 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2208 // --- IdentifieableEntity fields - by uuid
2209 if(features
!= null && features
.size() > 0 ){
2210 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2213 // the description must be associated with a taxon
2214 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2216 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2217 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2222 * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2223 * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2224 * This method is a convenient means to retrieve a Lucene query string for such the fields.
2226 * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2227 * or {@link MultilanguageTextFieldBridge }
2228 * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2229 * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2230 * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2232 * TODO move to utiliy class !!!!!!!!
2234 private StringBuilder
appendLocalizedFieldQuery(String name
, List
<Language
> languages
, StringBuilder stringBuilder
) {
2236 if(stringBuilder
== null){
2237 stringBuilder
= new StringBuilder();
2239 if(languages
== null || languages
.size() == 0){
2240 stringBuilder
.append(name
+ ".ALL:(%1$s) ");
2242 for(Language lang
: languages
){
2243 stringBuilder
.append(name
+ "." + lang
.getUuid().toString() + ":(%1$s) ");
2246 return stringBuilder
;
2250 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2253 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2254 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2256 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2257 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2259 UUID nameUuid
= taxon
.getName().getUuid();
2260 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2261 String epithetOfTaxon
= null;
2262 String infragenericEpithetOfTaxon
= null;
2263 String infraspecificEpithetOfTaxon
= null;
2264 if (taxonName
.isSpecies()){
2265 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2266 } else if (taxonName
.isInfraGeneric()){
2267 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2268 } else if (taxonName
.isInfraSpecific()){
2269 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2271 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2272 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2273 List
<String
> taxonNames
= new ArrayList
<>();
2275 for (TaxonNode node
: nodes
){
2276 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2277 // List<String> synonymsEpithet = new ArrayList<>();
2279 if (node
.getClassification().equals(classification
)){
2280 if (!node
.isTopmostNode()){
2281 TaxonNode parent
= node
.getParent();
2282 parent
= CdmBase
.deproxy(parent
);
2283 TaxonName parentName
= parent
.getTaxon().getName();
2284 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2285 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2286 Rank rankOfTaxon
= taxonName
.getRank();
2289 //create inferred synonyms for species, subspecies
2290 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2292 Synonym inferredEpithet
= null;
2293 Synonym inferredGenus
= null;
2294 Synonym potentialCombination
= null;
2296 List
<String
> propertyPaths
= new ArrayList
<>();
2297 propertyPaths
.add("synonym");
2298 propertyPaths
.add("synonym.name");
2299 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2300 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2302 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2303 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2304 null, null,orderHintsSynonyms
,propertyPaths
);
2306 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2307 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2308 if (doWithMisappliedNames
){
2309 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2310 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2311 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2312 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2313 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2314 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2317 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2318 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2320 inferredEpithet
= createInferredEpithets(taxon
,
2321 zooHashMap
, taxonName
, epithetOfTaxon
,
2322 infragenericEpithetOfTaxon
,
2323 infraspecificEpithetOfTaxon
,
2324 taxonNames
, parentName
,
2325 synonymRelationOfParent
);
2327 inferredSynonyms
.add(inferredEpithet
);
2328 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2329 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2332 if (doWithMisappliedNames
){
2334 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2335 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2337 inferredEpithet
= createInferredEpithets(taxon
,
2338 zooHashMap
, taxonName
, epithetOfTaxon
,
2339 infragenericEpithetOfTaxon
,
2340 infraspecificEpithetOfTaxon
,
2341 taxonNames
, parentName
,
2344 inferredSynonyms
.add(inferredEpithet
);
2345 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2346 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2350 if (!taxonNames
.isEmpty()){
2351 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2352 IZoologicalName name
;
2353 if (!synNotInCDM
.isEmpty()){
2354 inferredSynonymsToBeRemoved
.clear();
2356 for (Synonym syn
:inferredSynonyms
){
2357 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2358 if (!synNotInCDM
.contains(name
.getNameCache())){
2359 inferredSynonymsToBeRemoved
.add(syn
);
2363 // Remove identified Synonyms from inferredSynonyms
2364 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2365 inferredSynonyms
.remove(synonym
);
2370 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2372 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2374 inferredGenus
= createInferredGenus(taxon
,
2375 zooHashMap
, taxonName
, epithetOfTaxon
,
2376 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2378 inferredSynonyms
.add(inferredGenus
);
2379 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2380 taxonNames
.add(inferredGenus
.getName().getNameCache());
2383 if (doWithMisappliedNames
){
2385 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2386 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2387 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2389 inferredSynonyms
.add(inferredGenus
);
2390 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2391 taxonNames
.add(inferredGenus
.getName().getNameCache());
2396 if (!taxonNames
.isEmpty()){
2397 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2398 IZoologicalName name
;
2399 if (!synNotInCDM
.isEmpty()){
2400 inferredSynonymsToBeRemoved
.clear();
2402 for (Synonym syn
:inferredSynonyms
){
2403 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2404 if (!synNotInCDM
.contains(name
.getNameCache())){
2405 inferredSynonymsToBeRemoved
.add(syn
);
2409 // Remove identified Synonyms from inferredSynonyms
2410 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2411 inferredSynonyms
.remove(synonym
);
2416 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2418 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2419 IZoologicalName inferredSynName
;
2420 //for all synonyms of the parent...
2421 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2423 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2425 synName
= synonymRelationOfParent
.getName();
2427 // Set the sourceReference
2428 sourceReference
= synonymRelationOfParent
.getSec();
2430 // Determine the idInSource
2431 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2433 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2434 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2435 String synParentInfragenericName
= null;
2436 String synParentSpecificEpithet
= null;
2438 if (parentSynZooName
.isInfraGeneric()){
2439 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2441 if (parentSynZooName
.isSpecies()){
2442 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2445 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2446 synonymsGenus.put(synGenusName, idInSource);
2449 //for all synonyms of the taxon
2451 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2453 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2454 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2456 synParentInfragenericName
,
2457 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2459 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2460 inferredSynonyms
.add(potentialCombination
);
2461 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2462 taxonNames
.add(potentialCombination
.getName().getNameCache());
2468 if (doWithMisappliedNames
){
2470 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2472 TaxonName misappliedParentName
;
2474 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2475 misappliedParentName
= misappliedParent
.getName();
2477 HibernateProxyHelper
.deproxy(misappliedParent
);
2479 // Set the sourceReference
2480 sourceReference
= misappliedParent
.getSec();
2482 // Determine the idInSource
2483 String idInSourceParent
= getIdInSource(misappliedParent
);
2485 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2486 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2487 String synParentInfragenericName
= null;
2488 String synParentSpecificEpithet
= null;
2490 if (parentSynZooName
.isInfraGeneric()){
2491 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2493 if (parentSynZooName
.isSpecies()){
2494 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2498 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2499 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2500 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2501 potentialCombination
= createPotentialCombination(
2502 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2504 synParentInfragenericName
,
2505 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2508 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2509 inferredSynonyms
.add(potentialCombination
);
2510 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2511 taxonNames
.add(potentialCombination
.getName().getNameCache());
2516 if (!taxonNames
.isEmpty()){
2517 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2518 IZoologicalName name
;
2519 if (!synNotInCDM
.isEmpty()){
2520 inferredSynonymsToBeRemoved
.clear();
2521 for (Synonym syn
:inferredSynonyms
){
2523 name
= syn
.getName();
2524 }catch (ClassCastException e
){
2525 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2527 if (!synNotInCDM
.contains(name
.getNameCache())){
2528 inferredSynonymsToBeRemoved
.add(syn
);
2531 // Remove identified Synonyms from inferredSynonyms
2532 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2533 inferredSynonyms
.remove(synonym
);
2539 logger
.info("The synonym type is not defined.");
2540 return inferredSynonyms
;
2547 return inferredSynonyms
;
2550 private Synonym
createPotentialCombination(String idInSourceParent
,
2551 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2552 String synParentInfragenericName
, String synParentSpecificEpithet
,
2553 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2554 Synonym potentialCombination
;
2555 Reference sourceReference
;
2556 IZoologicalName inferredSynName
;
2557 HibernateProxyHelper
.deproxy(syn
);
2559 // Set sourceReference
2560 sourceReference
= syn
.getSec();
2561 if (sourceReference
== null){
2562 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2564 if (!parentSynZooName
.getTaxa().isEmpty()){
2565 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2567 sourceReference
= taxon
.getSec();
2570 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2572 String synTaxonInfraSpecificName
= null;
2574 if (parentSynZooName
.isSpecies()){
2575 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2578 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2579 synonymsEpithet.add(epithetName);
2582 //create potential combinations...
2583 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2585 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2586 if (zooSynName
.isSpecies()){
2587 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2588 if (parentSynZooName
.isInfraGeneric()){
2589 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2592 if (zooSynName
.isInfraSpecific()){
2593 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2594 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2596 if (parentSynZooName
.isInfraGeneric()){
2597 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2601 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2603 // Set the sourceReference
2604 potentialCombination
.setSec(sourceReference
);
2607 // Determine the idInSource
2608 String idInSourceSyn
= getIdInSource(syn
);
2610 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2611 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2612 inferredSynName
.addSource(originalSource
);
2613 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2614 potentialCombination
.addSource(originalSource
);
2617 return potentialCombination
;
2620 private Synonym
createInferredGenus(Taxon taxon
,
2621 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2622 String epithetOfTaxon
, String genusOfTaxon
,
2623 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2626 Synonym inferredGenus
;
2628 IZoologicalName inferredSynName
;
2629 synName
=syn
.getName();
2630 HibernateProxyHelper
.deproxy(syn
);
2632 // Determine the idInSource
2633 String idInSourceSyn
= getIdInSource(syn
);
2634 String idInSourceTaxon
= getIdInSource(taxon
);
2635 // Determine the sourceReference
2636 Reference sourceReference
= syn
.getSec();
2638 //logger.warn(sourceReference.getTitleCache());
2640 synName
= syn
.getName();
2641 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2642 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2643 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2644 synonymsEpithet.add(synSpeciesEpithetName);
2647 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2648 //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...
2651 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2652 if (zooParentName
.isInfraGeneric()){
2653 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2656 if (taxonName
.isSpecies()){
2657 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2659 if (taxonName
.isInfraSpecific()){
2660 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2661 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2665 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2667 // Set the sourceReference
2668 inferredGenus
.setSec(sourceReference
);
2670 // Add the original source
2671 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2672 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2673 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2674 inferredGenus
.addSource(originalSource
);
2676 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2677 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2678 inferredSynName
.addSource(originalSource
);
2679 originalSource
= null;
2682 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2683 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2684 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2685 inferredGenus
.addSource(originalSource
);
2687 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2688 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2689 inferredSynName
.addSource(originalSource
);
2690 originalSource
= null;
2693 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2695 return inferredGenus
;
2698 private Synonym
createInferredEpithets(Taxon taxon
,
2699 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2700 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2701 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2702 TaxonName parentName
, TaxonBase
<?
> syn
) {
2704 Synonym inferredEpithet
;
2706 IZoologicalName inferredSynName
;
2707 HibernateProxyHelper
.deproxy(syn
);
2709 // Determine the idInSource
2710 String idInSourceSyn
= getIdInSource(syn
);
2711 String idInSourceTaxon
= getIdInSource(taxon
);
2712 // Determine the sourceReference
2713 Reference sourceReference
= syn
.getSec();
2715 if (sourceReference
== null){
2716 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2717 sourceReference
= taxon
.getSec();
2720 synName
= syn
.getName();
2721 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2722 String synGenusName
= zooSynName
.getGenusOrUninomial();
2723 String synInfraGenericEpithet
= null;
2724 String synSpecificEpithet
= null;
2726 if (zooSynName
.getInfraGenericEpithet() != null){
2727 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2730 if (zooSynName
.isInfraSpecific()){
2731 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2734 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2735 synonymsGenus.put(synGenusName, idInSource);
2738 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2740 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2741 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2742 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2744 inferredSynName
.setGenusOrUninomial(synGenusName
);
2746 if (parentName
.isInfraGeneric()){
2747 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2749 if (taxonName
.isSpecies()){
2750 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2751 }else if (taxonName
.isInfraSpecific()){
2752 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2753 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2756 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2758 // Set the sourceReference
2759 inferredEpithet
.setSec(sourceReference
);
2761 /* Add the original source
2762 if (idInSource != null) {
2763 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2766 Reference citation = getCitation(syn);
2767 if (citation != null) {
2768 originalSource.setCitation(citation);
2769 inferredEpithet.addSource(originalSource);
2772 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2775 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2776 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2778 inferredEpithet
.addSource(originalSource
);
2780 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2781 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2783 inferredSynName
.addSource(originalSource
);
2787 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2789 return inferredEpithet
;
2793 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2794 * Very likely only useful for createInferredSynonyms().
2799 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2800 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2801 if (taxonName
== null) {
2802 taxonName
= zooHashMap
.get(uuid
);
2808 * Returns the idInSource for a given Synonym.
2811 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2812 String idInSource
= null;
2813 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2814 if (sources
.size() == 1) {
2815 IdentifiableSource source
= sources
.iterator().next();
2816 if (source
!= null) {
2817 idInSource
= source
.getIdInSource();
2819 } else if (sources
.size() > 1) {
2822 for (IdentifiableSource source
: sources
) {
2823 idInSource
+= source
.getIdInSource();
2824 if (count
< sources
.size()) {
2829 } else if (sources
.size() == 0){
2830 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2839 * Returns the citation for a given Synonym.
2842 private Reference
getCitation(Synonym syn
) {
2843 Reference citation
= null;
2844 Set
<IdentifiableSource
> sources
= syn
.getSources();
2845 if (sources
.size() == 1) {
2846 IdentifiableSource source
= sources
.iterator().next();
2847 if (source
!= null) {
2848 citation
= source
.getCitation();
2850 } else if (sources
.size() > 1) {
2851 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2858 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2859 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2861 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2862 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2863 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2865 return inferredSynonyms
;
2869 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2871 // TODO quickly implemented, create according dao !!!!
2872 Set
<TaxonNode
> nodes
= new HashSet
<>();
2873 Set
<Classification
> classifications
= new HashSet
<>();
2874 List
<Classification
> list
= new ArrayList
<>();
2876 if (taxonBase
== null) {
2880 taxonBase
= load(taxonBase
.getUuid());
2882 if (taxonBase
instanceof Taxon
) {
2883 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2885 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2887 nodes
.addAll(taxon
.getTaxonNodes());
2890 for (TaxonNode node
: nodes
) {
2891 classifications
.add(node
.getClassification());
2893 list
.addAll(classifications
);
2898 @Transactional(readOnly
= false)
2899 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2901 TaxonRelationshipType oldRelationshipType
,
2902 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2903 UpdateResult result
= new UpdateResult();
2904 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2905 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2906 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2908 result
.addUpdatedObject(fromTaxon
);
2909 result
.addUpdatedObject(toTaxon
);
2910 result
.addUpdatedObject(result
.getCdmEntity());
2916 @Transactional(readOnly
= false)
2917 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2918 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2920 UpdateResult result
= new UpdateResult();
2921 // Create new synonym using concept name
2922 TaxonName synonymName
= fromTaxon
.getName();
2924 // Remove concept relation from taxon
2925 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
2927 // Create a new synonym for the taxon
2929 if (synonymType
!= null
2930 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
2931 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
2932 toTaxon
.addHomotypicSynonym(synonym
);
2934 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
2937 this.saveOrUpdate(toTaxon
);
2938 //TODO: configurator and classification
2939 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
2940 config
.setDeleteNameIfPossible(false);
2941 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
2942 result
.setCdmEntity(synonym
);
2943 result
.addUpdatedObject(toTaxon
);
2944 result
.addUpdatedObject(synonym
);
2949 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
2950 DeleteResult result
= new DeleteResult();
2951 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
2952 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
2953 if (taxonBase
instanceof Taxon
){
2954 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
2955 result
= isDeletableForTaxon(references
, taxonConfig
);
2957 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
2958 result
= isDeletableForSynonym(references
, synonymConfig
);
2963 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
2965 DeleteResult result
= new DeleteResult();
2966 for (CdmBase ref
: references
){
2967 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
2968 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
2969 result
.addException(new ReferencedObjectUndeletableException(message
));
2970 result
.addRelatedObject(ref
);
2978 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
2979 String message
= null;
2980 DeleteResult result
= new DeleteResult();
2981 for (CdmBase ref
: references
){
2982 if (!(ref
instanceof TaxonName
)){
2984 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
2985 message
= "The taxon can't be deleted as long as it has synonyms.";
2987 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
2988 message
= "The taxon can't be deleted as long as it has factual data.";
2991 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
2992 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
2994 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
2995 if (!config
.isDeleteMisappliedNamesAndInvalidDesignations() &&
2996 (((TaxonRelationship
)ref
).getType().isMisappliedNameOrInvalidDesignation())){
2997 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
2999 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3002 if (ref
instanceof PolytomousKeyNode
){
3003 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3006 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3007 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3011 /* //PolytomousKeyNode
3012 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3013 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3018 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3019 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3023 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3024 message
= "Taxon can't be deleted as it is used in a determination event";
3027 if (message
!= null){
3028 result
.addException(new ReferencedObjectUndeletableException(message
));
3029 result
.addRelatedObject(ref
);
3038 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3039 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3041 //preliminary implementation
3043 Set
<Taxon
> taxa
= new HashSet
<>();
3044 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3045 if (taxonBase
== null){
3046 return new IncludedTaxaDTO();
3047 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3048 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3050 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3051 //TODO partial synonyms ??
3052 //TODO synonyms in general
3053 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3054 taxa
.add(syn
.getAcceptedTaxon());
3056 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3059 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3061 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3062 related
= makeRelatedIncluded(related
, result
, config
);
3069 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3071 * @return the set of conceptually related taxa for further use
3074 * @param uncheckedTaxa
3075 * @param existingTaxa
3079 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3082 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3083 for (Taxon taxon
: uncheckedTaxa
){
3084 taxonNodes
.addAll(taxon
.getTaxonNodes());
3087 Set
<Taxon
> children
= new HashSet
<>();
3088 if (! config
.onlyCongruent
){
3089 for (TaxonNode node
: taxonNodes
){
3090 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3091 for (TaxonNode child
: childNodes
){
3092 children
.add(child
.getTaxon());
3095 children
.remove(null); // just to be on the save side
3098 Iterator
<Taxon
> it
= children
.iterator();
3099 while(it
.hasNext()){
3100 UUID uuid
= it
.next().getUuid();
3101 if (existingTaxa
.contains(uuid
)){
3104 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3109 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3110 uncheckedAndChildren
.addAll(children
);
3112 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3115 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3120 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3121 * @return the set of these computed taxa
3123 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3124 Set
<Taxon
> result
= new HashSet
<>();
3126 for (Taxon taxon
: unchecked
){
3127 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3128 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3130 for (TaxonRelationship fromRel
: fromRelations
){
3131 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3134 if (fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3135 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.INCLUDES()) ||
3136 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3138 result
.add(fromRel
.getToTaxon());
3142 for (TaxonRelationship toRel
: toRelations
){
3143 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3146 if (toRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO())){
3147 result
.add(toRel
.getFromTaxon());
3152 Iterator
<Taxon
> it
= result
.iterator();
3153 while(it
.hasNext()){
3154 UUID uuid
= it
.next().getUuid();
3155 if (existingTaxa
.contains(uuid
)){
3158 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3165 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3166 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3167 config
.getTaxonNameTitle(), null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3172 @Transactional(readOnly
= true)
3173 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3174 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3175 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3176 Integer pageNumber
, List
<String
> propertyPaths
) {
3177 if (subtreeFilter
== null){
3178 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3181 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3182 List
<Object
[]> daoResults
= new ArrayList
<>();
3183 if(numberOfResults
> 0) { // no point checking again
3184 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3185 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3188 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3189 for (Object
[] daoObj
: daoResults
){
3191 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3193 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3196 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3200 @Transactional(readOnly
= true)
3201 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3202 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3203 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3204 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3205 if (subtreeFilter
== null){
3206 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3209 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3210 List
<Object
[]> daoResults
= new ArrayList
<>();
3211 if(numberOfResults
> 0) { // no point checking again
3212 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3213 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3216 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3217 for (Object
[] daoObj
: daoResults
){
3219 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3221 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3224 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3228 @Transactional(readOnly
= false)
3229 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3230 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3231 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3233 UpdateResult result
= new UpdateResult();
3234 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3235 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3236 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3242 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3243 UpdateResult result
= new UpdateResult();
3245 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3246 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3247 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3248 //reload to avoid session conflicts
3249 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3251 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3252 if(description
.isProtectedTitleCache()){
3253 String separator
= "";
3254 if(!StringUtils
.isBlank(description
.getTitleCache())){
3257 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3259 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3260 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3261 description
.addAnnotation(annotation
);
3262 toTaxon
.addDescription(description
);
3263 dao
.saveOrUpdate(toTaxon
);
3264 dao
.saveOrUpdate(fromTaxon
);
3265 result
.addUpdatedObject(toTaxon
);
3266 result
.addUpdatedObject(fromTaxon
);
3274 @Transactional(readOnly
= false)
3275 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3276 UUID acceptedTaxonUuid
) {
3277 TaxonBase
<?
> base
= this.load(synonymUUid
);
3278 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3279 base
= this.load(acceptedTaxonUuid
);
3280 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3282 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
);