2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.api
.service
;
12 import java
.io
.IOException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.EnumSet
;
15 import java
.util
.HashMap
;
16 import java
.util
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
21 import java
.util
.UUID
;
23 import javax
.persistence
.EntityNotFoundException
;
25 import org
.apache
.commons
.lang3
.StringUtils
;
26 import org
.apache
.log4j
.Logger
;
27 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
28 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
29 import org
.apache
.lucene
.search
.BooleanQuery
;
30 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
31 import org
.apache
.lucene
.search
.Query
;
32 import org
.apache
.lucene
.search
.SortField
;
33 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
34 import org
.apache
.lucene
.search
.join
.ScoreMode
;
35 import org
.apache
.lucene
.util
.BytesRef
;
36 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
37 import org
.springframework
.stereotype
.Service
;
38 import org
.springframework
.transaction
.annotation
.Transactional
;
40 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
41 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
47 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
48 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.TaxonRelationshipsDTO
;
51 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
52 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
53 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
54 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
55 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
56 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
57 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
58 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
64 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
65 import eu
.etaxonomy
.cdm
.api
.service
.util
.TaxonRelationshipEdge
;
66 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
67 import eu
.etaxonomy
.cdm
.exception
.UnpublishedException
;
68 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
69 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
70 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
71 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
72 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
73 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
74 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
75 import eu
.etaxonomy
.cdm
.model
.common
.DefinedTerm
;
76 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
77 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
78 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
79 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
80 import eu
.etaxonomy
.cdm
.model
.common
.OriginalSourceType
;
81 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
82 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
83 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
84 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
85 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
86 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
87 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
88 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
89 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
90 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
91 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
92 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
93 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
94 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
95 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
96 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
97 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
98 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
99 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
100 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
101 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
102 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
103 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
104 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
105 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
106 import eu
.etaxonomy
.cdm
.model
.taxon
.HomotypicGroupTaxonComparator
;
107 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
108 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
109 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
110 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
115 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
116 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
117 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
118 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
119 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
120 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
121 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
122 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
123 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
124 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
125 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
126 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
130 * @author a.kohlbecker
134 @Transactional(readOnly
= true)
135 public class TaxonServiceImpl
136 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
137 implements ITaxonService
{
139 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
141 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
143 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
145 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
148 private ITaxonNodeDao taxonNodeDao
;
151 private ITaxonNameDao nameDao
;
154 private INameService nameService
;
157 private IOccurrenceService occurrenceService
;
160 private ITaxonNodeService nodeService
;
163 private IDescriptionService descriptionService
;
166 // private IOrderedTermVocabularyDao orderedVocabularyDao;
169 private IOccurrenceDao occurrenceDao
;
172 private IClassificationDao classificationDao
;
175 private AbstractBeanInitializer beanInitializer
;
178 private ILuceneIndexToolProvider luceneIndexToolProvider
;
180 //************************ CONSTRUCTOR ****************************/
181 public TaxonServiceImpl(){
182 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
185 // ****************************** METHODS ********************************/
192 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
193 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
197 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
198 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
202 @Transactional(readOnly
= false)
203 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
){
204 UpdateResult result
= new UpdateResult();
205 TaxonName synonymName
= synonym
.getName();
206 synonymName
.removeTaxonBase(synonym
);
207 TaxonName taxonName
= acceptedTaxon
.getName();
208 taxonName
.removeTaxonBase(acceptedTaxon
);
210 synonym
.setName(taxonName
);
211 synonym
.setTitleCache(null, false);
212 synonym
.getTitleCache();
213 acceptedTaxon
.setName(synonymName
);
214 acceptedTaxon
.setTitleCache(null, false);
215 acceptedTaxon
.getTitleCache();
216 saveOrUpdate(synonym
);
217 saveOrUpdate(acceptedTaxon
);
218 result
.addUpdatedObject(acceptedTaxon
);
219 result
.addUpdatedObject(synonym
);
222 // the accepted taxon needs a new uuid because the concept has changed
223 // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
224 //acceptedTaxon.setUuid(UUID.randomUUID());
229 @Transactional(readOnly
= false)
230 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean deleteSynonym
) {
231 UpdateResult result
= new UpdateResult();
232 TaxonName acceptedName
= acceptedTaxon
.getName();
233 TaxonName synonymName
= synonym
.getName();
234 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
236 //check synonym is not homotypic
237 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
238 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
239 result
.addException(new HomotypicalGroupChangeException(message
));
244 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, acceptedTaxon
.getSec());
245 dao
.save(newAcceptedTaxon
);
246 result
.setCdmEntity(newAcceptedTaxon
);
247 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
248 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
250 for (Synonym heteroSynonym
: heteroSynonyms
){
251 if (synonym
.equals(heteroSynonym
)){
252 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
254 //move synonyms in same homotypic group to new accepted taxon
255 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
258 dao
.saveOrUpdate(acceptedTaxon
);
259 result
.addUpdatedObject(acceptedTaxon
);
264 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
265 config
.setDeleteNameIfPossible(false);
266 this.deleteSynonym(synonym
, config
);
268 } catch (Exception e
) {
269 result
.addException(e
);
277 @Transactional(readOnly
= false)
278 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
279 UUID acceptedTaxonUuid
,
280 UUID newParentNodeUuid
,
281 boolean deleteSynonym
) {
282 UpdateResult result
= new UpdateResult();
283 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
284 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
285 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, deleteSynonym
);
286 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
287 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
288 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
289 taxonNodeDao
.save(newNode
);
290 result
.addUpdatedObject(newTaxon
);
291 result
.addUpdatedObject(acceptedTaxon
);
292 result
.setCdmEntity(newNode
);
300 @Transactional(readOnly
= false)
301 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
303 TaxonRelationshipType taxonRelationshipType
,
305 String microcitation
){
307 UpdateResult result
= new UpdateResult();
308 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
309 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
310 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
311 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
312 // result.setCdmEntity(relatedTaxon);
313 result
.addUpdatedObject(relatedTaxon
);
314 result
.addUpdatedObject(toTaxon
);
319 @Transactional(readOnly
= false)
320 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
321 // Get name from synonym
322 if (synonym
== null){
326 UpdateResult result
= new UpdateResult();
328 TaxonName synonymName
= synonym
.getName();
330 /* // remove synonym from taxon
331 toTaxon.removeSynonym(synonym);
333 // Create a taxon with synonym name
334 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
336 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
338 // Add taxon relation
339 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
340 result
.setCdmEntity(fromTaxon
);
341 // since we are swapping names, we have to detach the name from the synonym completely.
342 // Otherwise the synonym will still be in the list of typified names.
343 // synonym.getName().removeTaxonBase(synonym);
344 result
.includeResult(this.deleteSynonym(synonym
, null));
349 @Transactional(readOnly
= false)
351 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
352 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
354 TaxonName synonymName
= synonym
.getName();
355 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
358 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
359 newHomotypicalGroup
.addTypifiedName(synonymName
);
361 //remove existing basionym relationships
362 synonymName
.removeBasionyms();
364 //add basionym relationship
365 if (setBasionymRelationIfApplicable
){
366 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
367 for (TaxonName basionym
: basionyms
){
368 synonymName
.addBasionym(basionym
);
372 //set synonym relationship correctly
373 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
375 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
376 if (acceptedTaxon
!= null){
378 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
379 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
380 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
381 synonym
.setType(newRelationType
);
383 if (hasNewTargetTaxon
){
384 acceptedTaxon
.removeSynonym(synonym
, false);
387 if (hasNewTargetTaxon
){
388 @SuppressWarnings("null")
389 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
390 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
391 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
392 targetTaxon
.addSynonym(synonym
, relType
);
398 @Transactional(readOnly
= false)
399 public void updateTitleCache(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
401 clazz
= TaxonBase
.class;
403 super.updateTitleCacheImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
408 protected void setDao(ITaxonDao dao
) {
413 public Pager
<TaxonBase
> findTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
414 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
416 List
<TaxonBase
> results
= new ArrayList
<>();
417 if(numberOfResults
> 0) { // no point checking again
418 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
421 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
425 public List
<TaxonBase
> listTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
426 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
428 List
<TaxonBase
> results
= new ArrayList
<>();
429 if(numberOfResults
> 0) { // no point checking again
430 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
437 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
438 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
439 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
441 List
<TaxonRelationship
> results
= new ArrayList
<>();
442 if(numberOfResults
> 0) { // no point checking again
443 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
449 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
450 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
451 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
453 List
<TaxonRelationship
> results
= new ArrayList
<>();
454 if(numberOfResults
> 0) { // no point checking again
455 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
457 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
461 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
462 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
463 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
465 List
<TaxonRelationship
> results
= new ArrayList
<>();
466 if(numberOfResults
> 0) { // no point checking again
467 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
473 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
474 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
475 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
477 List
<TaxonRelationship
> results
= new ArrayList
<>();
478 if(numberOfResults
> 0) { // no point checking again
479 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
481 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
485 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
486 Integer pageSize
, Integer pageStart
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
487 Long numberOfResults
= dao
.countTaxonRelationships(types
);
489 List
<TaxonRelationship
> results
= new ArrayList
<>();
490 if(numberOfResults
> 0) {
491 results
= dao
.getTaxonRelationships(types
, pageSize
, pageStart
, orderHints
, propertyPaths
);
497 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
498 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
500 Synonym synonym
= null;
503 synonym
= (Synonym
) dao
.load(synonymUuid
);
504 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
505 } catch (ClassCastException e
){
506 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
507 } catch (NullPointerException e
){
508 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
511 Classification classificationFilter
= null;
512 if(classificationUuid
!= null){
514 classificationFilter
= classificationDao
.load(classificationUuid
);
515 } catch (NullPointerException e
){
516 //TODO not sure, why an NPE should be thrown in the above load method
517 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
519 if(classificationFilter
== null){
520 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
524 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
526 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
527 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
535 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
536 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
538 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
539 relatedTaxa
.remove(taxon
);
540 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
546 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
547 * <code>taxon</code> supplied as parameter.
550 * @param includeRelationships
552 * @param maxDepth can be <code>null</code> for infinite depth
555 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
556 boolean includeUnpublished
, Integer maxDepth
) {
562 if(includeRelationships
.isEmpty()){
566 if(maxDepth
!= null) {
569 if(logger
.isDebugEnabled()){
570 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
572 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
573 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
574 for (TaxonRelationship taxRel
: taxonRelationships
) {
577 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
580 // filter by includeRelationships
581 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
582 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
583 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
584 if(logger
.isDebugEnabled()){
585 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
587 taxa
.add(taxRel
.getToTaxon());
588 if(maxDepth
== null || maxDepth
> 0) {
589 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
592 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
593 taxa
.add(taxRel
.getFromTaxon());
594 if(logger
.isDebugEnabled()){
595 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
597 if(maxDepth
== null || maxDepth
> 0) {
598 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
608 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
609 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
611 List
<Synonym
> results
= new ArrayList
<>();
612 if(numberOfResults
> 0) { // no point checking again
613 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
616 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
620 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
621 List
<List
<Synonym
>> result
= new ArrayList
<>();
622 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
623 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
627 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
630 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
631 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
632 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
640 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
641 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
642 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
644 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
648 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
649 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
650 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
651 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
652 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
653 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
655 return heterotypicSynonymyGroups
;
659 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
661 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
662 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
663 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(),
664 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
665 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
667 return new ArrayList
<>();
672 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
674 List
<IdentifiableEntity
> results
= new ArrayList
<>();
675 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
676 List
<TaxonBase
> taxa
= null;
679 long numberTaxaResults
= 0L;
681 List
<String
> propertyPath
= new ArrayList
<>();
682 if(configurator
.getTaxonPropertyPath() != null){
683 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
686 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
687 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
689 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
690 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
691 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
692 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
695 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
696 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
697 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
698 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
699 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
700 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
704 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
707 results
.addAll(taxa
);
710 numberOfResults
+= numberTaxaResults
;
712 // Names without taxa
713 if (configurator
.isDoNamesWithoutTaxa()) {
714 int numberNameResults
= 0;
716 List
<TaxonName
> names
=
717 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
718 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
719 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
720 if (names
.size() > 0) {
721 for (TaxonName taxonName
: names
) {
722 if (taxonName
.getTaxonBases().size() == 0) {
723 results
.add(taxonName
);
727 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
728 numberOfResults
+= numberNameResults
;
732 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
735 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
736 return dao
.getUuidAndTitleCache(limit
, pattern
);
740 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
741 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
745 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
746 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
747 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
750 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
752 // logger.setLevel(Level.TRACE);
753 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
755 logger
.trace("listMedia() - START");
757 Set
<Taxon
> taxa
= new HashSet
<>();
758 List
<Media
> taxonMedia
= new ArrayList
<>();
759 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
761 if (limitToGalleries
== null) {
762 limitToGalleries
= false;
765 // --- resolve related taxa
766 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
767 logger
.trace("listMedia() - resolve related taxa");
768 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
771 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
773 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
774 logger
.trace("listMedia() - includeTaxonDescriptions");
775 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
776 // --- TaxonDescriptions
777 for (Taxon t
: taxa
) {
778 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
780 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
781 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
782 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
783 for (Media media
: element
.getMedia()) {
784 if(taxonDescription
.isImageGallery()){
785 taxonMedia
.add(media
);
788 nonImageGalleryImages
.add(media
);
794 //put images from image gallery first (#3242)
795 taxonMedia
.addAll(nonImageGalleryImages
);
799 if(includeOccurrences
!= null && includeOccurrences
) {
800 logger
.trace("listMedia() - includeOccurrences");
801 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
803 for (Taxon t
: taxa
) {
804 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
806 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
808 // direct media removed from specimen #3597
809 // taxonMedia.addAll(occurrence.getMedia());
811 // SpecimenDescriptions
812 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
813 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
814 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
815 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
816 for (DescriptionElementBase element
: elements
) {
817 for (Media media
: element
.getMedia()) {
818 taxonMedia
.add(media
);
824 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
825 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
827 //TODO why may collections have media attached? #
828 if (derivedUnit
.getCollection() != null){
829 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
833 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
837 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
838 logger
.trace("listMedia() - includeTaxonNameDescriptions");
839 // --- TaxonNameDescription
840 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
841 for (Taxon t
: taxa
) {
842 nameDescriptions
.addAll(t
.getName().getDescriptions());
844 for(TaxonNameDescription nameDescription
: nameDescriptions
){
845 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
846 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
847 for (DescriptionElementBase element
: elements
) {
848 for (Media media
: element
.getMedia()) {
849 taxonMedia
.add(media
);
857 logger
.trace("listMedia() - initialize");
858 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
860 logger
.trace("listMedia() - END");
866 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
867 return this.dao
.loadList(listOfIDs
, null);
871 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
872 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
876 public long countSynonyms(boolean onlyAttachedToTaxon
){
877 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
881 public List
<TaxonName
> findIdenticalTaxonNames(List
<String
> propertyPath
) {
882 return this.dao
.findIdenticalTaxonNames(propertyPath
);
886 @Transactional(readOnly
=false)
887 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
890 config
= new TaxonDeletionConfigurator();
892 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
893 DeleteResult result
= new DeleteResult();
896 result
.addException(new Exception ("The taxon was already deleted."));
899 taxon
= HibernateProxyHelper
.deproxy(taxon
);
900 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
901 result
= isDeletable(taxonUUID
, config
);
904 // --- DeleteSynonymRelations
905 if (config
.isDeleteSynonymRelations()){
906 boolean removeSynonymNameFromHomotypicalGroup
= false;
907 // use tmp Set to avoid concurrent modification
908 Set
<Synonym
> synsToDelete
= new HashSet
<>();
909 synsToDelete
.addAll(taxon
.getSynonyms());
910 for (Synonym synonym
: synsToDelete
){
911 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
913 // --- DeleteSynonymsIfPossible
914 if (config
.isDeleteSynonymsIfPossible()){
916 boolean newHomotypicGroupIfNeeded
= true;
917 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
918 result
.includeResult(deleteSynonym(synonym
, synConfig
));
923 // --- DeleteTaxonRelationships
924 if (! config
.isDeleteTaxonRelationships()){
925 if (taxon
.getTaxonRelations().size() > 0){
927 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
928 "Remove taxon from all relations to other taxa prior to deletion."));
931 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
932 configRelTaxon
.setDeleteTaxonNodes(false);
933 configRelTaxon
.setDeleteConceptRelationships(true);
935 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
936 if (config
.isDeleteMisappliedNamesAndInvalidDesignations()
937 && taxRel
.getType().isMisappliedNameOrInvalidDesignation()
938 && taxon
.equals(taxRel
.getToTaxon())){
939 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
940 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
942 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
943 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
944 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
945 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
948 taxon
.removeTaxonRelation(taxRel
);
953 if (config
.isDeleteDescriptions()){
954 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
955 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
956 for (TaxonDescription desc
: descriptions
){
957 //TODO use description delete configurator ?
958 //FIXME check if description is ALWAYS deletable
959 if (desc
.getDescribedSpecimenOrObservation() != null){
961 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
962 " which also describes specimens or observations"));
965 removeDescriptions
.add(desc
);
970 for (TaxonDescription desc
: removeDescriptions
){
971 taxon
.removeDescription(desc
);
972 descriptionService
.delete(desc
);
980 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
981 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
983 if (taxon
.getTaxonNodes().size() != 0){
984 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
985 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
986 TaxonNode node
= null;
987 boolean deleteChildren
;
988 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
989 deleteChildren
= true;
991 deleteChildren
= false;
993 boolean success
= true;
994 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
995 while (iterator
.hasNext()){
996 node
= iterator
.next();
997 if (node
.getClassification().equals(classification
)){
1003 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1004 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1005 nodeService
.delete(node
);
1006 result
.addDeletedObject(node
);
1009 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1011 } else if (config
.isDeleteInAllClassifications()){
1012 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1013 nodesList
.addAll(taxon
.getTaxonNodes());
1014 for (ITaxonTreeNode treeNode
: nodesList
){
1015 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1016 if(!deleteChildren
){
1017 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1018 for (Object childNode
: childNodes
){
1019 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1020 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1024 config
.getTaxonNodeConfig().setDeleteElement(false);
1025 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1026 if (!resultNodes
.isOk()){
1027 result
.addExceptions(resultNodes
.getExceptions());
1028 result
.setStatus(resultNodes
.getStatus());
1030 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1035 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1039 TaxonName name
= taxon
.getName();
1040 taxon
.setName(null);
1041 this.saveOrUpdate(taxon
);
1043 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1046 result
.addDeletedObject(taxon
);
1047 }catch(Exception e
){
1048 result
.addException(e
);
1053 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1057 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1058 DeleteResult nameResult
= new DeleteResult();
1059 //remove name if possible (and required)
1061 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1063 if (nameResult
.isError() || nameResult
.isAbort()){
1064 result
.addRelatedObject(name
);
1065 result
.addExceptions(nameResult
.getExceptions());
1067 result
.includeResult(nameResult
);
1077 @Transactional(readOnly
= false)
1078 public DeleteResult
delete(UUID synUUID
){
1079 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1080 return this.deleteSynonym(syn
, null);
1084 @Transactional(readOnly
= false)
1085 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1086 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1092 @Transactional(readOnly
= false)
1093 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1094 DeleteResult result
= new DeleteResult();
1095 if (synonym
== null){
1097 result
.addException(new Exception("The synonym was already deleted."));
1101 if (config
== null){
1102 config
= new SynonymDeletionConfigurator();
1105 result
= isDeletable(synonym
.getUuid(), config
);
1109 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1112 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1114 if (accTaxon
!= null){
1115 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1116 accTaxon
.removeSynonym(synonym
, false);
1117 this.saveOrUpdate(accTaxon
);
1118 result
.addUpdatedObject(accTaxon
);
1120 this.saveOrUpdate(synonym
);
1124 TaxonName name
= synonym
.getName();
1125 synonym
.setName(null);
1127 dao
.delete(synonym
);
1128 result
.addDeletedObject(synonym
);
1130 //remove name if possible (and required)
1131 if (name
!= null && config
.isDeleteNameIfPossible()){
1133 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1134 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1135 result
.addExceptions(nameDeleteResult
.getExceptions());
1136 result
.addRelatedObject(name
);
1138 result
.addDeletedObject(name
);
1147 public List
<TaxonName
> findIdenticalTaxonNameIds(List
<String
> propertyPath
) {
1149 return this.dao
.findIdenticalNamesNew(propertyPath
);
1154 public Taxon
findBestMatchingTaxon(String taxonName
) {
1155 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1156 config
.setTaxonNameTitle(taxonName
);
1157 return findBestMatchingTaxon(config
);
1161 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1163 Taxon bestCandidate
= null;
1165 // 1. search for accepted taxa
1166 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1167 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1168 boolean bestCandidateMatchesSecUuid
= false;
1169 boolean bestCandidateIsInClassification
= false;
1170 int countEqualCandidates
= 0;
1171 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1172 if(taxonBaseCandidate
instanceof Taxon
){
1173 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1174 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1175 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1177 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1178 bestCandidate
= newCanditate
;
1179 countEqualCandidates
= 1;
1180 bestCandidateMatchesSecUuid
= true;
1184 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1185 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1187 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1188 bestCandidate
= newCanditate
;
1189 countEqualCandidates
= 1;
1190 bestCandidateIsInClassification
= true;
1193 if (bestCandidate
== null){
1194 bestCandidate
= newCanditate
;
1195 countEqualCandidates
= 1;
1199 }else{ //not Taxon.class
1202 countEqualCandidates
++;
1205 if (bestCandidate
!= null){
1206 if(countEqualCandidates
> 1){
1207 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1208 return bestCandidate
;
1210 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1211 return bestCandidate
;
1216 // 2. search for synonyms
1217 if (config
.isIncludeSynonyms()){
1218 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1219 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1220 for(TaxonBase taxonBase
: synonymList
){
1221 if(taxonBase
instanceof Synonym
){
1222 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1223 bestCandidate
= synonym
.getAcceptedTaxon();
1224 if(bestCandidate
!= null){
1225 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1226 return bestCandidate
;
1228 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1233 } catch (Exception e
){
1235 e
.printStackTrace();
1238 return bestCandidate
;
1241 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1242 UUID configClassificationUuid
= config
.getClassificationUuid();
1243 if (configClassificationUuid
== null){
1246 for (TaxonNode node
: taxon
.getTaxonNodes()){
1247 UUID classUuid
= node
.getClassification().getUuid();
1248 if (configClassificationUuid
.equals(classUuid
)){
1255 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1256 UUID configSecUuid
= config
.getSecUuid();
1257 if (configSecUuid
== null){
1260 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1261 return configSecUuid
.equals(taxonSecUuid
);
1265 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1266 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1267 if(! synonymList
.isEmpty()){
1268 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1269 if(synonymList
.size() == 1){
1270 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1273 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1281 @Transactional(readOnly
= false)
1282 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1284 boolean moveHomotypicGroup
,
1285 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1286 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1288 oldSynonym
.getSec(),
1289 oldSynonym
.getSecMicroReference(),
1294 @Transactional(readOnly
= false)
1295 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1297 boolean moveHomotypicGroup
,
1298 SynonymType newSynonymType
,
1299 Reference newSecundum
,
1300 String newSecundumDetail
,
1301 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1303 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1304 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1305 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1306 TaxonName synonymName
= synonym
.getName();
1307 TaxonName fromTaxonName
= oldTaxon
.getName();
1308 //set default relationship type
1309 if (newSynonymType
== null){
1310 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1312 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1314 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1315 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1316 boolean isSingleInGroup
= !(hgSize
> 1);
1318 if (! isSingleInGroup
){
1319 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1320 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1321 if (isHomotypicToAccepted
){
1322 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.";
1323 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1324 message
= String
.format(message
, homotypicRelatives
);
1325 throw new HomotypicalGroupChangeException(message
);
1327 if (! moveHomotypicGroup
){
1328 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.";
1329 throw new HomotypicalGroupChangeException(message
);
1332 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1334 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1336 UpdateResult result
= new UpdateResult();
1337 //move all synonyms to new taxon
1338 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1339 for (Synonym synRelation
: homotypicSynonyms
){
1341 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1342 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1343 oldTaxon
.removeSynonym(synRelation
, false);
1344 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1346 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1347 synRelation
.setSec(newSecundum
);
1349 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1350 synRelation
.setSecMicroReference(newSecundumDetail
);
1353 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1354 if (!synRelation
.equals(oldSynonym
)){
1359 result
.addUpdatedObject(oldTaxon
);
1360 result
.addUpdatedObject(newTaxon
);
1361 saveOrUpdate(oldTaxon
);
1362 saveOrUpdate(newTaxon
);
1368 public <T
extends TaxonBase
> List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1369 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1373 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1374 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1375 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1376 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1377 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1379 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1380 null, includeUnpublished
, languages
, highlightFragments
, null);
1382 // --- execute search
1383 TopGroups
<BytesRef
> topDocsResultSet
;
1385 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1386 } catch (ParseException e
) {
1387 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1388 luceneParseException
.setStackTrace(e
.getStackTrace());
1389 throw luceneParseException
;
1392 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1393 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1395 // --- initialize taxa, thighlight matches ....
1396 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1397 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1398 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1400 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1401 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1405 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1406 Classification classification
,
1407 Integer pageSize
, Integer pageNumber
,
1408 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1410 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
);
1412 // --- execute search
1413 TopGroups
<BytesRef
> topDocsResultSet
;
1415 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1416 } catch (ParseException e
) {
1417 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1418 luceneParseException
.setStackTrace(e
.getStackTrace());
1419 throw luceneParseException
;
1422 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1423 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1425 // --- initialize taxa, thighlight matches ....
1426 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1427 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1428 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1430 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1431 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1436 * @param queryString
1437 * @param classification
1438 * @param includeUnpublished
1440 * @param highlightFragments
1441 * @param sortFields TODO
1442 * @param directorySelectClass
1445 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1446 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1447 boolean highlightFragments
, SortField
[] sortFields
) {
1449 Builder finalQueryBuilder
= new Builder();
1450 Builder textQueryBuilder
= new Builder();
1452 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1453 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1455 if(sortFields
== null){
1456 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1458 luceneSearch
.setSortFields(sortFields
);
1460 // ---- search criteria
1461 luceneSearch
.setCdmTypRestriction(clazz
);
1463 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1464 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1465 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1467 if(className
!= null){
1468 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1471 BooleanQuery textQuery
= textQueryBuilder
.build();
1472 if(textQuery
.clauses().size() > 0) {
1473 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1476 if(classification
!= null){
1477 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1479 if(subtree
!= null){
1480 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
1482 if(!includeUnpublished
) {
1483 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1484 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1485 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1488 luceneSearch
.setQuery(finalQueryBuilder
.build());
1490 if(highlightFragments
){
1491 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1493 return luceneSearch
;
1497 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1498 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1499 * drawback of requiring to do the join an indexing time.
1500 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1502 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1504 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1505 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1507 * @param queryString
1508 * @param classification
1510 * @param highlightFragments
1511 * @param sortFields TODO
1514 * @throws IOException
1516 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1517 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1518 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1521 String queryTermField
;
1522 String toField
= "id"; // TaxonBase.uuid
1523 String publishField
;
1524 String publishFieldInvers
;
1526 if(edge
.isBidirectional()){
1527 throw new RuntimeException("Bidirectional joining not supported!");
1530 fromField
= "relatedFrom.id";
1531 queryTermField
= "relatedFrom.titleCache";
1532 publishField
= "relatedFrom.publish";
1533 publishFieldInvers
= "relatedTo.publish";
1534 } else if(edge
.isInvers()) {
1535 fromField
= "relatedTo.id";
1536 queryTermField
= "relatedTo.titleCache";
1537 publishField
= "relatedTo.publish";
1538 publishFieldInvers
= "relatedFrom.publish";
1540 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1543 Builder finalQueryBuilder
= new Builder();
1545 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1546 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1548 Builder joinFromQueryBuilder
= new Builder();
1549 if(!StringUtils
.isEmpty(queryString
)){
1550 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1552 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1553 if(!includeUnpublished
){
1554 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1555 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1558 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1560 if(sortFields
== null){
1561 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1563 luceneSearch
.setSortFields(sortFields
);
1565 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1567 if(classification
!= null){
1568 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1570 if(subtree
!= null){
1571 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
1574 luceneSearch
.setQuery(finalQueryBuilder
.build());
1576 if(highlightFragments
){
1577 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1579 return luceneSearch
;
1583 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1584 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1585 Classification classification
, TaxonNode subtree
,
1586 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1587 boolean highlightFragments
, Integer pageSize
,
1588 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1589 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1591 // FIXME: allow taxonomic ordering
1592 // 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";
1593 // this require building a special sort column by a special classBridge
1594 if(highlightFragments
){
1595 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1596 "currently not fully supported by this method and thus " +
1597 "may not work with common names and misapplied names.");
1600 // convert sets to lists
1601 List
<NamedArea
> namedAreaList
= null;
1602 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1603 if(namedAreas
!= null){
1604 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1605 namedAreaList
.addAll(namedAreas
);
1607 if(distributionStatus
!= null){
1608 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1609 distributionStatusList
.addAll(distributionStatus
);
1612 // set default if parameter is null
1613 if(searchModes
== null){
1614 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1617 // set sort order and thus override any sort orders which may have been
1618 // defined by prepare*Search methods
1619 if(orderHints
== null){
1620 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1622 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1624 for(OrderHint oh
: orderHints
){
1625 sortFields
[i
++] = oh
.toSortField();
1627 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1628 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1631 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1633 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1634 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1637 ======== filtering by distribution , HOWTO ========
1639 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1640 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1641 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1642 which will be put into a FilteredQuersy in the end ?
1645 3. how does it work in spatial?
1647 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1648 - http://www.infoq.com/articles/LuceneSpatialSupport
1649 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1650 ------------------------------------------------------------------------
1653 A) use a separate distribution filter per index sub-query/search:
1654 - byTaxonSyonym (query TaxaonBase):
1655 use a join area filter (Distribution -> TaxonBase)
1656 - byCommonName (query DescriptionElementBase): use an area filter on
1657 DescriptionElementBase !!! PROBLEM !!!
1658 This cannot work since the distributions are different entities than the
1659 common names and thus these are different lucene documents.
1660 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1661 use a join area filter (Distribution -> TaxonBase)
1663 B) use a common distribution filter for all index sub-query/searches:
1664 - use a common join area filter (Distribution -> TaxonBase)
1665 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1666 PROBLEM in this case: we are losing the fragment highlighting for the
1667 common names, since the returned documents are always TaxonBases
1670 /* The QueryFactory for creating filter queries on Distributions should
1671 * The query factory used for the common names query cannot be reused
1672 * for this case, since we want to only record the text fields which are
1673 * actually used in the primary query
1675 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1677 Builder multiIndexByAreaFilterBuilder
= new Builder();
1678 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1680 // search for taxa or synonyms
1681 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1682 @SuppressWarnings("rawtypes")
1683 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1684 String className
= null;
1685 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1686 taxonBaseSubclass
= Taxon
.class;
1687 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1688 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1690 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1691 queryString
, classification
, subtree
, className
,
1692 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1693 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1694 /* A) does not work!!!!
1695 if(addDistributionFilter){
1696 // in this case we need a filter which uses a join query
1697 // to get the TaxonBase documents for the DescriptionElementBase documents
1698 // which are matching the areas in question
1699 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1701 distributionStatusList,
1702 distributionFilterQueryFactory
1704 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1707 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1708 // add additional area filter for synonyms
1709 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1710 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1712 //TODO replace by createByDistributionJoinQuery
1713 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1714 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1715 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1720 // search by CommonTaxonName
1721 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1723 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1724 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1725 CommonTaxonName
.class,
1726 "inDescription.taxon.id",
1728 QueryFactory
.addTypeRestriction(
1729 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
1730 , CommonTaxonName
.class
1731 ).build(), "id", null, ScoreMode
.Max
);
1732 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1733 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1734 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1735 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1736 Builder builder
= new BooleanQuery
.Builder();
1737 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1738 if(!includeUnpublished
) {
1739 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1740 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1742 byCommonNameSearch
.setQuery(builder
.build());
1743 byCommonNameSearch
.setSortFields(sortFields
);
1745 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1747 luceneSearches
.add(byCommonNameSearch
);
1749 /* A) does not work!!!!
1751 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1752 queryString, classification, null, languages, highlightFragments)
1754 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1755 if(addDistributionFilter){
1756 // in this case we are able to use DescriptionElementBase documents
1757 // which are matching the areas in question directly
1758 BooleanQuery byDistributionQuery = createByDistributionQuery(
1760 distributionStatusList,
1761 distributionFilterQueryFactory
1763 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1768 // search by misapplied names
1769 //TODO merge with pro parte synonym search once #7487 is fixed
1770 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1772 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1773 // which allows doing query time joins
1774 // finds the misapplied name (Taxon B) which is an misapplication for
1775 // a related Taxon A.
1777 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1778 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1779 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1781 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1782 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1785 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1786 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1787 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1788 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1790 if(addDistributionFilter
){
1791 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1794 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
1795 * Maybe this is a bug in java itself.
1797 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1800 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1802 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1803 * will execute as expected:
1805 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1806 * String toField = "relation." + misappliedNameForUuid +".to.id";
1808 * Comparing both strings by the String.equals method returns true, so both String are identical.
1810 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1811 * 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)
1812 * The bug is persistent after a reboot of the development computer.
1814 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1815 // String toField = "relation." + misappliedNameForUuid +".to.id";
1816 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1817 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1818 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1820 //TODO replace by createByDistributionJoinQuery
1821 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1822 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1823 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1825 // debug code for bug described above
1826 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1827 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1828 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1830 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1835 // search by pro parte synonyms
1836 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1837 //TODO merge with misapplied name search once #7487 is fixed
1838 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1839 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
1841 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1842 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1843 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1844 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1846 if(addDistributionFilter
){
1847 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1848 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
1849 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1850 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1851 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1852 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1854 }//end pro parte synonyms
1858 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
1859 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
1862 if(addDistributionFilter
){
1865 // in this case we need a filter which uses a join query
1866 // to get the TaxonBase documents for the DescriptionElementBase documents
1867 // which are matching the areas in question
1869 // for doTaxa, doByCommonName
1870 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
1872 distributionStatusList
,
1873 distributionFilterQueryFactory
,
1876 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1879 if (addDistributionFilter
){
1880 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
1884 // --- execute search
1885 TopGroups
<BytesRef
> topDocsResultSet
;
1887 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
1888 } catch (ParseException e
) {
1889 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1890 luceneParseException
.setStackTrace(e
.getStackTrace());
1891 throw luceneParseException
;
1894 // --- initialize taxa, highlight matches ....
1895 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
1898 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1899 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1901 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1902 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1906 * @param namedAreaList at least one area must be in the list
1907 * @param distributionStatusList optional
1908 * @param toType toType
1909 * Optional parameter. Only used for debugging to print the toType documents
1910 * @param asFilter TODO
1912 * @throws IOException
1914 protected Query
createByDistributionJoinQuery(
1915 List
<NamedArea
> namedAreaList
,
1916 List
<PresenceAbsenceTerm
> distributionStatusList
,
1917 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
1918 ) throws IOException
{
1920 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1921 String toField
= "id"; // id in toType usually this is the TaxonBase index
1923 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
1925 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
1927 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
1929 return taxonAreaJoinQuery
;
1933 * @param namedAreaList
1934 * @param distributionStatusList
1935 * @param queryFactory
1938 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
1939 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
1940 Builder areaQueryBuilder
= new Builder();
1941 // area field from Distribution
1942 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
1944 // status field from Distribution
1945 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
1946 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
1949 BooleanQuery areaQuery
= areaQueryBuilder
.build();
1950 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
1955 * This method has been primarily created for testing the area join query but might
1956 * also be useful in other situations
1958 * @param namedAreaList
1959 * @param distributionStatusList
1960 * @param classification
1961 * @param highlightFragments
1963 * @throws IOException
1965 protected LuceneSearch
prepareByDistributionSearch(
1966 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
1967 Classification classification
) throws IOException
{
1969 Builder finalQueryBuilder
= new Builder();
1971 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1973 // FIXME is this query factory using the wrong type?
1974 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
1976 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1977 luceneSearch
.setSortFields(sortFields
);
1980 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
1982 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
1984 if(classification
!= null){
1985 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery("taxonNodes.classification.id", classification
), Occur
.MUST
);
1987 BooleanQuery finalQuery
= finalQueryBuilder
.build();
1988 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
1989 luceneSearch
.setQuery(finalQuery
);
1991 return luceneSearch
;
1995 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
1996 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
1997 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
1998 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2001 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2003 // --- execute search
2004 TopGroups
<BytesRef
> topDocsResultSet
;
2006 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2007 } catch (ParseException e
) {
2008 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2009 luceneParseException
.setStackTrace(e
.getStackTrace());
2010 throw luceneParseException
;
2013 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2014 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2016 // --- initialize taxa, highlight matches ....
2017 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2018 @SuppressWarnings("rawtypes")
2019 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2020 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2022 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2023 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2029 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2030 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2031 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2033 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2034 classification
, subtree
,
2035 null, languages
, highlightFragments
);
2036 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2037 includeUnpublished
, languages
, highlightFragments
, null);
2039 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2041 // --- execute search
2042 TopGroups
<BytesRef
> topDocsResultSet
;
2044 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2045 } catch (ParseException e
) {
2046 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2047 luceneParseException
.setStackTrace(e
.getStackTrace());
2048 throw luceneParseException
;
2051 // --- initialize taxa, highlight matches ....
2052 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2054 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2055 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2056 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2058 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2059 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2061 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2062 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2069 * @param queryString
2070 * @param classification
2073 * @param highlightFragments
2074 * @param directorySelectClass
2077 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2078 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2079 List
<Language
> languages
, boolean highlightFragments
) {
2081 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2082 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2084 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2086 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2087 languages
, descriptionElementQueryFactory
);
2089 luceneSearch
.setSortFields(sortFields
);
2090 luceneSearch
.setCdmTypRestriction(clazz
);
2091 luceneSearch
.setQuery(finalQuery
);
2092 if(highlightFragments
){
2093 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2096 return luceneSearch
;
2100 * @param queryString
2101 * @param classification
2104 * @param descriptionElementQueryFactory
2107 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2108 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2109 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2111 Builder finalQueryBuilder
= new Builder();
2112 Builder textQueryBuilder
= new Builder();
2114 if(!StringUtils
.isEmpty(queryString
)){
2116 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2119 Builder nameQueryBuilder
= new Builder();
2120 if(languages
== null || languages
.size() == 0){
2121 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2123 Builder languageSubQueryBuilder
= new Builder();
2124 for(Language lang
: languages
){
2125 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2127 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2128 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2130 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2133 // text field from TextData
2134 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2136 // --- TermBase fields - by representation ----
2137 // state field from CategoricalData
2138 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2140 // state field from CategoricalData
2141 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2143 // area field from Distribution
2144 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2146 // status field from Distribution
2147 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2149 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2152 // --- classification ----
2155 if(classification
!= null){
2156 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2158 if(subtree
!= null){
2159 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2162 // --- IdentifieableEntity fields - by uuid
2163 if(features
!= null && features
.size() > 0 ){
2164 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2167 // the description must be associated with a taxon
2168 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2170 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2171 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2176 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2179 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2180 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2182 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2183 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2185 UUID nameUuid
= taxon
.getName().getUuid();
2186 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2187 String epithetOfTaxon
= null;
2188 String infragenericEpithetOfTaxon
= null;
2189 String infraspecificEpithetOfTaxon
= null;
2190 if (taxonName
.isSpecies()){
2191 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2192 } else if (taxonName
.isInfraGeneric()){
2193 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2194 } else if (taxonName
.isInfraSpecific()){
2195 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2197 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2198 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2199 List
<String
> taxonNames
= new ArrayList
<>();
2201 for (TaxonNode node
: nodes
){
2202 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2203 // List<String> synonymsEpithet = new ArrayList<>();
2205 if (node
.getClassification().equals(classification
)){
2206 if (!node
.isTopmostNode()){
2207 TaxonNode parent
= node
.getParent();
2208 parent
= CdmBase
.deproxy(parent
);
2209 TaxonName parentName
= parent
.getTaxon().getName();
2210 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2211 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2213 //create inferred synonyms for species, subspecies
2214 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2216 Synonym inferredEpithet
= null;
2217 Synonym inferredGenus
= null;
2218 Synonym potentialCombination
= null;
2220 List
<String
> propertyPaths
= new ArrayList
<>();
2221 propertyPaths
.add("synonym");
2222 propertyPaths
.add("synonym.name");
2223 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2224 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2226 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2227 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2228 null, null,orderHintsSynonyms
,propertyPaths
);
2230 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2231 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2232 if (doWithMisappliedNames
){
2233 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2234 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2235 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2236 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2237 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2238 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2241 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2242 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2244 inferredEpithet
= createInferredEpithets(taxon
,
2245 zooHashMap
, taxonName
, epithetOfTaxon
,
2246 infragenericEpithetOfTaxon
,
2247 infraspecificEpithetOfTaxon
,
2248 taxonNames
, parentName
,
2249 synonymRelationOfParent
);
2251 inferredSynonyms
.add(inferredEpithet
);
2252 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2253 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2256 if (doWithMisappliedNames
){
2258 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2259 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2261 inferredEpithet
= createInferredEpithets(taxon
,
2262 zooHashMap
, taxonName
, epithetOfTaxon
,
2263 infragenericEpithetOfTaxon
,
2264 infraspecificEpithetOfTaxon
,
2265 taxonNames
, parentName
,
2268 inferredSynonyms
.add(inferredEpithet
);
2269 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2270 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2274 if (!taxonNames
.isEmpty()){
2275 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2276 IZoologicalName name
;
2277 if (!synNotInCDM
.isEmpty()){
2278 inferredSynonymsToBeRemoved
.clear();
2280 for (Synonym syn
:inferredSynonyms
){
2281 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2282 if (!synNotInCDM
.contains(name
.getNameCache())){
2283 inferredSynonymsToBeRemoved
.add(syn
);
2287 // Remove identified Synonyms from inferredSynonyms
2288 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2289 inferredSynonyms
.remove(synonym
);
2294 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2296 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2298 inferredGenus
= createInferredGenus(taxon
,
2299 zooHashMap
, taxonName
, epithetOfTaxon
,
2300 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2302 inferredSynonyms
.add(inferredGenus
);
2303 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2304 taxonNames
.add(inferredGenus
.getName().getNameCache());
2307 if (doWithMisappliedNames
){
2309 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2310 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2311 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2313 inferredSynonyms
.add(inferredGenus
);
2314 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2315 taxonNames
.add(inferredGenus
.getName().getNameCache());
2320 if (!taxonNames
.isEmpty()){
2321 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2322 IZoologicalName name
;
2323 if (!synNotInCDM
.isEmpty()){
2324 inferredSynonymsToBeRemoved
.clear();
2326 for (Synonym syn
:inferredSynonyms
){
2327 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2328 if (!synNotInCDM
.contains(name
.getNameCache())){
2329 inferredSynonymsToBeRemoved
.add(syn
);
2333 // Remove identified Synonyms from inferredSynonyms
2334 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2335 inferredSynonyms
.remove(synonym
);
2340 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2342 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2343 //for all synonyms of the parent...
2344 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2346 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2348 synName
= synonymRelationOfParent
.getName();
2350 // Set the sourceReference
2351 sourceReference
= synonymRelationOfParent
.getSec();
2353 // Determine the idInSource
2354 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2356 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2357 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2358 String synParentInfragenericName
= null;
2359 String synParentSpecificEpithet
= null;
2361 if (parentSynZooName
.isInfraGeneric()){
2362 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2364 if (parentSynZooName
.isSpecies()){
2365 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2368 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2369 synonymsGenus.put(synGenusName, idInSource);
2372 //for all synonyms of the taxon
2374 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2376 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2377 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2379 synParentInfragenericName
,
2380 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2382 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2383 inferredSynonyms
.add(potentialCombination
);
2384 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2385 taxonNames
.add(potentialCombination
.getName().getNameCache());
2391 if (doWithMisappliedNames
){
2393 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2395 TaxonName misappliedParentName
;
2397 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2398 misappliedParentName
= misappliedParent
.getName();
2400 HibernateProxyHelper
.deproxy(misappliedParent
);
2402 // Set the sourceReference
2403 sourceReference
= misappliedParent
.getSec();
2405 // Determine the idInSource
2406 String idInSourceParent
= getIdInSource(misappliedParent
);
2408 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2409 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2410 String synParentInfragenericName
= null;
2411 String synParentSpecificEpithet
= null;
2413 if (parentSynZooName
.isInfraGeneric()){
2414 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2416 if (parentSynZooName
.isSpecies()){
2417 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2421 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2422 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2423 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2424 potentialCombination
= createPotentialCombination(
2425 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2427 synParentInfragenericName
,
2428 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2431 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2432 inferredSynonyms
.add(potentialCombination
);
2433 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2434 taxonNames
.add(potentialCombination
.getName().getNameCache());
2439 if (!taxonNames
.isEmpty()){
2440 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2441 IZoologicalName name
;
2442 if (!synNotInCDM
.isEmpty()){
2443 inferredSynonymsToBeRemoved
.clear();
2444 for (Synonym syn
:inferredSynonyms
){
2446 name
= syn
.getName();
2447 }catch (ClassCastException e
){
2448 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2450 if (!synNotInCDM
.contains(name
.getNameCache())){
2451 inferredSynonymsToBeRemoved
.add(syn
);
2454 // Remove identified Synonyms from inferredSynonyms
2455 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2456 inferredSynonyms
.remove(synonym
);
2462 logger
.info("The synonym type is not defined.");
2463 return inferredSynonyms
;
2470 return inferredSynonyms
;
2473 private Synonym
createPotentialCombination(String idInSourceParent
,
2474 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2475 String synParentInfragenericName
, String synParentSpecificEpithet
,
2476 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2477 Synonym potentialCombination
;
2478 Reference sourceReference
;
2479 IZoologicalName inferredSynName
;
2480 HibernateProxyHelper
.deproxy(syn
);
2482 // Set sourceReference
2483 sourceReference
= syn
.getSec();
2484 if (sourceReference
== null){
2485 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2487 if (!parentSynZooName
.getTaxa().isEmpty()){
2488 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2490 sourceReference
= taxon
.getSec();
2493 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2495 String synTaxonInfraSpecificName
= null;
2497 if (parentSynZooName
.isSpecies()){
2498 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2501 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2502 synonymsEpithet.add(epithetName);
2505 //create potential combinations...
2506 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2508 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2509 if (zooSynName
.isSpecies()){
2510 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2511 if (parentSynZooName
.isInfraGeneric()){
2512 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2515 if (zooSynName
.isInfraSpecific()){
2516 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2517 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2519 if (parentSynZooName
.isInfraGeneric()){
2520 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2524 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2526 // Set the sourceReference
2527 potentialCombination
.setSec(sourceReference
);
2530 // Determine the idInSource
2531 String idInSourceSyn
= getIdInSource(syn
);
2533 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2534 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2535 inferredSynName
.addSource(originalSource
);
2536 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2537 potentialCombination
.addSource(originalSource
);
2540 return potentialCombination
;
2543 private Synonym
createInferredGenus(Taxon taxon
,
2544 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2545 String epithetOfTaxon
, String genusOfTaxon
,
2546 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2549 Synonym inferredGenus
;
2551 IZoologicalName inferredSynName
;
2552 synName
=syn
.getName();
2553 HibernateProxyHelper
.deproxy(syn
);
2555 // Determine the idInSource
2556 String idInSourceSyn
= getIdInSource(syn
);
2557 String idInSourceTaxon
= getIdInSource(taxon
);
2558 // Determine the sourceReference
2559 Reference sourceReference
= syn
.getSec();
2561 //logger.warn(sourceReference.getTitleCache());
2563 synName
= syn
.getName();
2564 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2565 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2566 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2567 synonymsEpithet.add(synSpeciesEpithetName);
2570 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2571 //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...
2574 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2575 if (zooParentName
.isInfraGeneric()){
2576 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2579 if (taxonName
.isSpecies()){
2580 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2582 if (taxonName
.isInfraSpecific()){
2583 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2584 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2588 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2590 // Set the sourceReference
2591 inferredGenus
.setSec(sourceReference
);
2593 // Add the original source
2594 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2595 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2596 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2597 inferredGenus
.addSource(originalSource
);
2599 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2600 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2601 inferredSynName
.addSource(originalSource
);
2602 originalSource
= null;
2605 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2606 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2607 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2608 inferredGenus
.addSource(originalSource
);
2610 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2611 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2612 inferredSynName
.addSource(originalSource
);
2613 originalSource
= null;
2616 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2618 return inferredGenus
;
2621 private Synonym
createInferredEpithets(Taxon taxon
,
2622 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2623 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2624 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2625 TaxonName parentName
, TaxonBase
<?
> syn
) {
2627 Synonym inferredEpithet
;
2629 IZoologicalName inferredSynName
;
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 if (sourceReference
== null){
2639 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2640 sourceReference
= taxon
.getSec();
2643 synName
= syn
.getName();
2644 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2645 String synGenusName
= zooSynName
.getGenusOrUninomial();
2646 String synInfraGenericEpithet
= null;
2647 String synSpecificEpithet
= null;
2649 if (zooSynName
.getInfraGenericEpithet() != null){
2650 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2653 if (zooSynName
.isInfraSpecific()){
2654 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2657 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2658 synonymsGenus.put(synGenusName, idInSource);
2661 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2663 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2664 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2665 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2667 inferredSynName
.setGenusOrUninomial(synGenusName
);
2669 if (parentName
.isInfraGeneric()){
2670 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2672 if (taxonName
.isSpecies()){
2673 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2674 }else if (taxonName
.isInfraSpecific()){
2675 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2676 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2679 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2681 // Set the sourceReference
2682 inferredEpithet
.setSec(sourceReference
);
2684 /* Add the original source
2685 if (idInSource != null) {
2686 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2689 Reference citation = getCitation(syn);
2690 if (citation != null) {
2691 originalSource.setCitation(citation);
2692 inferredEpithet.addSource(originalSource);
2695 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2698 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2699 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2701 inferredEpithet
.addSource(originalSource
);
2703 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2704 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2706 inferredSynName
.addSource(originalSource
);
2710 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2712 return inferredEpithet
;
2716 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2717 * Very likely only useful for createInferredSynonyms().
2722 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2723 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2724 if (taxonName
== null) {
2725 taxonName
= zooHashMap
.get(uuid
);
2731 * Returns the idInSource for a given Synonym.
2734 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2735 String idInSource
= null;
2736 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2737 if (sources
.size() == 1) {
2738 IdentifiableSource source
= sources
.iterator().next();
2739 if (source
!= null) {
2740 idInSource
= source
.getIdInSource();
2742 } else if (sources
.size() > 1) {
2745 for (IdentifiableSource source
: sources
) {
2746 idInSource
+= source
.getIdInSource();
2747 if (count
< sources
.size()) {
2752 } else if (sources
.size() == 0){
2753 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2762 * Returns the citation for a given Synonym.
2765 private Reference
getCitation(Synonym syn
) {
2766 Reference citation
= null;
2767 Set
<IdentifiableSource
> sources
= syn
.getSources();
2768 if (sources
.size() == 1) {
2769 IdentifiableSource source
= sources
.iterator().next();
2770 if (source
!= null) {
2771 citation
= source
.getCitation();
2773 } else if (sources
.size() > 1) {
2774 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2781 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2782 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2784 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2785 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2786 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2788 return inferredSynonyms
;
2792 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2794 // TODO quickly implemented, create according dao !!!!
2795 Set
<TaxonNode
> nodes
= new HashSet
<>();
2796 Set
<Classification
> classifications
= new HashSet
<>();
2797 List
<Classification
> list
= new ArrayList
<>();
2799 if (taxonBase
== null) {
2803 taxonBase
= load(taxonBase
.getUuid());
2805 if (taxonBase
instanceof Taxon
) {
2806 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2808 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2810 nodes
.addAll(taxon
.getTaxonNodes());
2813 for (TaxonNode node
: nodes
) {
2814 classifications
.add(node
.getClassification());
2816 list
.addAll(classifications
);
2821 @Transactional(readOnly
= false)
2822 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2824 TaxonRelationshipType oldRelationshipType
,
2825 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2826 UpdateResult result
= new UpdateResult();
2827 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2828 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2829 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2831 result
.addUpdatedObject(fromTaxon
);
2832 result
.addUpdatedObject(toTaxon
);
2833 result
.addUpdatedObject(result
.getCdmEntity());
2839 @Transactional(readOnly
= false)
2840 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2841 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2843 UpdateResult result
= new UpdateResult();
2844 // Create new synonym using concept name
2845 TaxonName synonymName
= fromTaxon
.getName();
2847 // Remove concept relation from taxon
2848 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
2850 // Create a new synonym for the taxon
2852 if (synonymType
!= null
2853 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
2854 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
2855 toTaxon
.addHomotypicSynonym(synonym
);
2857 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
2860 this.saveOrUpdate(toTaxon
);
2861 //TODO: configurator and classification
2862 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
2863 config
.setDeleteNameIfPossible(false);
2864 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
2865 result
.setCdmEntity(synonym
);
2866 result
.addUpdatedObject(toTaxon
);
2867 result
.addUpdatedObject(synonym
);
2872 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
2873 DeleteResult result
= new DeleteResult();
2874 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
2875 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
2876 if (taxonBase
instanceof Taxon
){
2877 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
2878 result
= isDeletableForTaxon(references
, taxonConfig
);
2880 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
2881 result
= isDeletableForSynonym(references
, synonymConfig
);
2886 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
2888 DeleteResult result
= new DeleteResult();
2889 for (CdmBase ref
: references
){
2890 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
2891 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
2892 result
.addException(new ReferencedObjectUndeletableException(message
));
2893 result
.addRelatedObject(ref
);
2901 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
2902 String message
= null;
2903 DeleteResult result
= new DeleteResult();
2904 for (CdmBase ref
: references
){
2905 if (!(ref
instanceof TaxonName
)){
2907 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
2908 message
= "The taxon can't be deleted as long as it has synonyms.";
2910 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
2911 message
= "The taxon can't be deleted as long as it has factual data.";
2914 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
2915 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
2917 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
2918 if (!config
.isDeleteMisappliedNamesAndInvalidDesignations() &&
2919 (((TaxonRelationship
)ref
).getType().isMisappliedNameOrInvalidDesignation())){
2920 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
2922 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
2925 if (ref
instanceof PolytomousKeyNode
){
2926 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
2929 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
2930 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
2934 /* //PolytomousKeyNode
2935 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
2936 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
2941 if (ref
.isInstanceOf(TaxonInteraction
.class)){
2942 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
2946 if (ref
.isInstanceOf(DeterminationEvent
.class)){
2947 message
= "Taxon can't be deleted as it is used in a determination event";
2950 if (message
!= null){
2951 result
.addException(new ReferencedObjectUndeletableException(message
));
2952 result
.addRelatedObject(ref
);
2961 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
2962 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
2964 //preliminary implementation
2966 Set
<Taxon
> taxa
= new HashSet
<>();
2967 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
2968 if (taxonBase
== null){
2969 return new IncludedTaxaDTO();
2970 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
2971 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
2973 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
2974 //TODO partial synonyms ??
2975 //TODO synonyms in general
2976 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
2977 taxa
.add(syn
.getAcceptedTaxon());
2979 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
2982 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
2984 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
2985 related
= makeRelatedIncluded(related
, result
, config
);
2992 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
2994 * @return the set of conceptually related taxa for further use
2997 * @param uncheckedTaxa
2998 * @param existingTaxa
3002 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3005 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3006 for (Taxon taxon
: uncheckedTaxa
){
3007 taxonNodes
.addAll(taxon
.getTaxonNodes());
3010 Set
<Taxon
> children
= new HashSet
<>();
3011 if (! config
.onlyCongruent
){
3012 for (TaxonNode node
: taxonNodes
){
3013 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3014 for (TaxonNode child
: childNodes
){
3015 children
.add(child
.getTaxon());
3018 children
.remove(null); // just to be on the save side
3021 Iterator
<Taxon
> it
= children
.iterator();
3022 while(it
.hasNext()){
3023 UUID uuid
= it
.next().getUuid();
3024 if (existingTaxa
.contains(uuid
)){
3027 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3032 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3033 uncheckedAndChildren
.addAll(children
);
3035 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3038 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3043 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3044 * @return the set of these computed taxa
3046 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3047 Set
<Taxon
> result
= new HashSet
<>();
3049 for (Taxon taxon
: unchecked
){
3050 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3051 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3053 for (TaxonRelationship fromRel
: fromRelations
){
3054 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3057 if (fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3058 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.INCLUDES()) ||
3059 !config
.onlyCongruent
&& fromRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3061 result
.add(fromRel
.getToTaxon());
3065 for (TaxonRelationship toRel
: toRelations
){
3066 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3069 if (toRel
.getType().equals(TaxonRelationshipType
.CONGRUENT_TO())){
3070 result
.add(toRel
.getFromTaxon());
3075 Iterator
<Taxon
> it
= result
.iterator();
3076 while(it
.hasNext()){
3077 UUID uuid
= it
.next().getUuid();
3078 if (existingTaxa
.contains(uuid
)){
3081 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3088 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3089 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3090 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3095 @Transactional(readOnly
= true)
3096 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3097 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3098 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3099 Integer pageNumber
, List
<String
> propertyPaths
) {
3100 if (subtreeFilter
== null){
3101 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3104 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3105 List
<Object
[]> daoResults
= new ArrayList
<>();
3106 if(numberOfResults
> 0) { // no point checking again
3107 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3108 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3111 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3112 for (Object
[] daoObj
: daoResults
){
3114 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3116 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3119 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3123 @Transactional(readOnly
= true)
3124 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3125 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3126 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3127 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3128 if (subtreeFilter
== null){
3129 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3132 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3133 List
<Object
[]> daoResults
= new ArrayList
<>();
3134 if(numberOfResults
> 0) { // no point checking again
3135 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3136 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3139 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3140 for (Object
[] daoObj
: daoResults
){
3142 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3144 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3147 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3151 @Transactional(readOnly
= false)
3152 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3153 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3154 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3156 UpdateResult result
= new UpdateResult();
3157 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3158 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3159 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3165 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3166 UpdateResult result
= new UpdateResult();
3168 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3169 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3170 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3171 //reload to avoid session conflicts
3172 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3174 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3175 if(description
.isProtectedTitleCache()){
3176 String separator
= "";
3177 if(!StringUtils
.isBlank(description
.getTitleCache())){
3180 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3182 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3183 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3184 description
.addAnnotation(annotation
);
3185 toTaxon
.addDescription(description
);
3186 dao
.saveOrUpdate(toTaxon
);
3187 dao
.saveOrUpdate(fromTaxon
);
3188 result
.addUpdatedObject(toTaxon
);
3189 result
.addUpdatedObject(fromTaxon
);
3197 @Transactional(readOnly
= false)
3198 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3199 UUID acceptedTaxonUuid
) {
3200 TaxonBase
<?
> base
= this.load(synonymUUid
);
3201 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3202 base
= this.load(acceptedTaxonUuid
);
3203 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3205 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
);
3212 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3213 Set
<TaxonRelationshipType
> inversTypes
,
3214 Direction direction
, boolean groupMisapplications
,
3215 boolean includeUnpublished
,
3216 Integer pageSize
, Integer pageNumber
) {
3217 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3218 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3220 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3222 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3223 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3224 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3226 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3228 //TODO paging is difficult because misapplication string is an attribute
3230 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3231 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3232 // if(numberOfResults > 0) { // no point checking again
3233 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3236 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3239 List
<Language
> languages
= null;
3241 direction
= Direction
.relatedTo
;
3242 //TODO order hints, property path
3243 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3244 for (TaxonRelationship relation
: relations
){
3245 dto
.addRelation(relation
, direction
, languages
);
3249 direction
= Direction
.relatedFrom
;
3250 //TODO order hints, property path
3251 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3252 for (TaxonRelationship relation
: relations
){
3253 dto
.addRelation(relation
, direction
, languages
);
3256 if (groupMisapplications
){
3258 dto
.createMisapplicationString();