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
.Collections
;
15 import java
.util
.EnumSet
;
16 import java
.util
.HashMap
;
17 import java
.util
.HashSet
;
18 import java
.util
.Iterator
;
19 import java
.util
.List
;
22 import java
.util
.UUID
;
24 import javax
.persistence
.EntityNotFoundException
;
26 import org
.apache
.commons
.lang3
.StringUtils
;
27 import org
.apache
.log4j
.Logger
;
28 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
29 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
30 import org
.apache
.lucene
.search
.BooleanQuery
;
31 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
32 import org
.apache
.lucene
.search
.Query
;
33 import org
.apache
.lucene
.search
.SortField
;
34 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
35 import org
.apache
.lucene
.search
.join
.ScoreMode
;
36 import org
.apache
.lucene
.util
.BytesRef
;
37 import org
.hibernate
.criterion
.Criterion
;
38 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
39 import org
.springframework
.stereotype
.Service
;
40 import org
.springframework
.transaction
.annotation
.Transactional
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
47 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
48 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
51 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
52 import eu
.etaxonomy
.cdm
.api
.service
.dto
.TaxonRelationshipsDTO
;
53 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
54 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
55 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
56 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
57 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.AbstractPagerImpl
;
58 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
64 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
65 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
66 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
67 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
68 import eu
.etaxonomy
.cdm
.api
.util
.TaxonRelationshipEdge
;
69 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
70 import eu
.etaxonomy
.cdm
.compare
.taxon
.HomotypicGroupTaxonComparator
;
71 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonComparator
;
72 import eu
.etaxonomy
.cdm
.exception
.UnpublishedException
;
73 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
74 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
75 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
76 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
77 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
78 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
79 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
80 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
81 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
82 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
83 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
84 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
85 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
86 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
87 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
88 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementSource
;
89 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
90 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
91 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
92 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
93 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
94 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
95 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
96 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
97 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
98 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
99 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
100 import eu
.etaxonomy
.cdm
.model
.metadata
.SecReferenceHandlingEnum
;
101 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
102 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
103 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
104 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
105 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
106 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
107 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
108 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
109 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
110 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
117 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
118 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
119 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
120 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
121 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.Restriction
;
122 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
123 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
124 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
125 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
126 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
127 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
128 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
129 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
130 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
131 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
132 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
133 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
137 * @author a.kohlbecker
141 @Transactional(readOnly
= true)
142 public class TaxonServiceImpl
143 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
144 implements ITaxonService
{
146 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
148 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
150 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
152 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
155 private ITaxonNodeDao taxonNodeDao
;
158 private ITaxonNameDao nameDao
;
161 private INameService nameService
;
164 private IOccurrenceService occurrenceService
;
167 private ITaxonNodeService nodeService
;
170 private IDescriptionService descriptionService
;
173 private IReferenceService referenceService
;
176 // private IOrderedTermVocabularyDao orderedVocabularyDao;
179 private IOccurrenceDao occurrenceDao
;
182 private IClassificationDao classificationDao
;
185 private AbstractBeanInitializer beanInitializer
;
188 private ILuceneIndexToolProvider luceneIndexToolProvider
;
190 //************************ CONSTRUCTOR ****************************/
191 public TaxonServiceImpl(){
192 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
195 // ****************************** METHODS ********************************/
198 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
199 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
203 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
204 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
208 @Transactional(readOnly
= false)
209 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
210 UpdateResult result
= new UpdateResult();
211 acceptedTaxon
.removeSynonym(synonym
);
212 TaxonName synonymName
= synonym
.getName();
213 TaxonName taxonName
= HibernateProxyHelper
.deproxy(acceptedTaxon
.getName());
215 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(taxonName
.getHomotypicalGroup());
217 synonymName
.removeTaxonBase(synonym
);
219 //taxonName.removeTaxonBase(acceptedTaxon);
221 List
<Synonym
> synonyms
= new ArrayList
<>();
222 for (Synonym syn
: acceptedTaxon
.getSynonyms()){
223 syn
= HibernateProxyHelper
.deproxy(syn
, Synonym
.class);
226 for (Synonym syn
: synonyms
){
227 acceptedTaxon
.removeSynonym(syn
);
229 Taxon newTaxon
= acceptedTaxon
.clone(true, true, false, true);
230 // newTaxon.getDescriptions().clear();
232 Set
<TaxonDescription
> descriptionsToCopy
= new HashSet
<>(acceptedTaxon
.getDescriptions());
233 for (TaxonDescription description
: descriptionsToCopy
){
234 newTaxon
.addDescription(description
);
237 newTaxon
.setName(synonymName
);
238 newTaxon
.setSec(synonym
.getSec());
239 newTaxon
.setPublish(synonym
.isPublish());
240 for (Synonym syn
: synonyms
){
241 if (!syn
.getName().equals(newTaxon
.getName())){
242 newTaxon
.addSynonym(syn
, syn
.getType());
246 //move all data to new taxon
247 //Move Taxon RelationShips to new Taxon
248 for(TaxonRelationship taxonRelationship
: newTaxon
.getTaxonRelations()){
249 newTaxon
.removeTaxonRelation(taxonRelationship
);
252 for(TaxonRelationship taxonRelationship
: acceptedTaxon
.getTaxonRelations()){
253 Taxon fromTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getFromTaxon());
254 Taxon toTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getToTaxon());
255 if (fromTaxon
== acceptedTaxon
){
256 newTaxon
.addTaxonRelation(taxonRelationship
.getToTaxon(), taxonRelationship
.getType(),
257 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
259 }else if(toTaxon
== acceptedTaxon
){
260 fromTaxon
.addTaxonRelation(newTaxon
, taxonRelationship
.getType(),
261 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
262 saveOrUpdate(fromTaxon
);
265 logger
.warn("Taxon is not part of its own Taxonrelationship");
267 // Remove old relationships
269 fromTaxon
.removeTaxonRelation(taxonRelationship
);
270 toTaxon
.removeTaxonRelation(taxonRelationship
);
271 taxonRelationship
.setToTaxon(null);
272 taxonRelationship
.setFromTaxon(null);
275 //Move descriptions to new taxon
276 List
<TaxonDescription
> descriptions
= new ArrayList
<>( newTaxon
.getDescriptions()); //to avoid concurrent modification errors (newAcceptedTaxon.addDescription() modifies also oldtaxon.descritpions())
277 for(TaxonDescription description
: descriptions
){
278 String message
= "Description copied from former accepted taxon: %s (Old title: %s)";
279 message
= String
.format(message
, acceptedTaxon
.getTitleCache(), description
.getTitleCache());
280 description
.setTitleCache(message
, true);
282 for (DescriptionElementBase element
: description
.getElements()){
283 for (DescriptionElementSource source
: element
.getSources()){
284 if (source
.getNameUsedInSource() == null){
285 source
.setNameUsedInSource(taxonName
);
290 // //oldTaxon.removeDescription(description, false);
291 // newTaxon.addDescription(description);
293 List
<TaxonNode
> nodes
= new ArrayList
<>(acceptedTaxon
.getTaxonNodes());
294 for (TaxonNode node
: nodes
){
295 node
= HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
296 TaxonNode parent
= node
.getParent();
297 acceptedTaxon
.removeTaxonNode(node
);
298 node
.setTaxon(newTaxon
);
300 parent
.addChildNode(node
, null, null);
304 Synonym newSynonym
= synonym
.clone();
305 newSynonym
.setName(taxonName
);
306 newSynonym
.setSec(acceptedTaxon
.getSec());
307 newSynonym
.setPublish(acceptedTaxon
.isPublish());
308 if (sameHomotypicGroup
){
309 newTaxon
.addSynonym(newSynonym
, SynonymType
.HOMOTYPIC_SYNONYM_OF());
311 newTaxon
.addSynonym(newSynonym
, SynonymType
.HETEROTYPIC_SYNONYM_OF());
314 saveOrUpdate(newSynonym
);
315 saveOrUpdate(newTaxon
);
316 TaxonDeletionConfigurator conf
= new TaxonDeletionConfigurator();
317 conf
.setDeleteNameIfPossible(false);
318 SynonymDeletionConfigurator confSyn
= new SynonymDeletionConfigurator();
319 confSyn
.setDeleteNameIfPossible(false);
320 result
.setCdmEntity(newTaxon
);
322 DeleteResult deleteResult
= deleteTaxon(acceptedTaxon
.getUuid(), conf
, null);
323 if (synonym
.isPersited()){
324 deleteResult
.includeResult(deleteSynonym(synonym
, confSyn
));
326 result
.includeResult(deleteResult
);
331 @Transactional(readOnly
= false)
332 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, Reference newSecRef
, String microRef
, SecReferenceHandlingEnum secHandling
, boolean deleteSynonym
) {
333 UpdateResult result
= new UpdateResult();
334 TaxonName acceptedName
= acceptedTaxon
.getName();
335 TaxonName synonymName
= synonym
.getName();
336 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
338 //check synonym is not homotypic
339 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
340 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
341 result
.addException(new HomotypicalGroupChangeException(message
));
345 if (secHandling
!= null && secHandling
.equals(SecReferenceHandlingEnum
.KeepAlways
)){
346 newSecRef
= synonym
.getSec();
348 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, newSecRef
, microRef
);
349 newAcceptedTaxon
.setPublish(synonym
.isPublish());
350 dao
.save(newAcceptedTaxon
);
351 result
.setCdmEntity(newAcceptedTaxon
);
352 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
353 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
355 for (Synonym heteroSynonym
: heteroSynonyms
){
356 if (secHandling
== null || !secHandling
.equals(SecReferenceHandlingEnum
.KeepAlways
)){
357 heteroSynonym
.setSec(newSecRef
);
359 if (synonym
.equals(heteroSynonym
)){
360 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
362 //move synonyms in same homotypic group to new accepted taxon
363 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
366 dao
.saveOrUpdate(acceptedTaxon
);
367 result
.addUpdatedObject(acceptedTaxon
);
372 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
373 config
.setDeleteNameIfPossible(false);
374 this.deleteSynonym(synonym
, config
);
376 } catch (Exception e
) {
377 result
.addException(e
);
385 @Transactional(readOnly
= false)
386 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
387 UUID acceptedTaxonUuid
,
388 UUID newParentNodeUuid
,
390 String microReference
,
391 SecReferenceHandlingEnum secHandling
,
392 boolean deleteSynonym
) {
393 UpdateResult result
= new UpdateResult();
394 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
395 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
396 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
397 Reference newSecRef
= null;
398 switch (secHandling
){
404 case UseNewParentSec
:
405 newSecRef
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
408 Reference parentSec
= newParentNode
.getTaxon() != null? newParentNode
.getTaxon().getSec(): null;
409 Reference synSec
= synonym
.getSec();
410 if (parentSec
!= null && synSec
!= null && parentSec
.equals(synSec
)){
411 newSecRef
= synonym
.getSec();
413 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
417 newSecRef
= CdmBase
.deproxy(referenceService
.load(newSec
));
424 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, newSecRef
, microReference
, secHandling
, deleteSynonym
);
425 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
427 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
428 taxonNodeDao
.save(newNode
);
429 result
.addUpdatedObject(newTaxon
);
430 result
.addUpdatedObject(acceptedTaxon
);
431 result
.setCdmEntity(newNode
);
437 @Transactional(readOnly
= false)
438 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
440 TaxonRelationshipType taxonRelationshipType
,
442 String microcitation
){
444 UpdateResult result
= new UpdateResult();
445 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
446 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
447 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
448 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
449 // result.setCdmEntity(relatedTaxon);
450 result
.addUpdatedObject(relatedTaxon
);
451 result
.addUpdatedObject(toTaxon
);
456 @Transactional(readOnly
= false)
457 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
458 // Get name from synonym
459 if (synonym
== null){
463 UpdateResult result
= new UpdateResult();
465 TaxonName synonymName
= synonym
.getName();
467 /* // remove synonym from taxon
468 toTaxon.removeSynonym(synonym);
470 // Create a taxon with synonym name
471 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
472 fromTaxon
.setPublish(synonym
.isPublish());
474 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
476 // Add taxon relation
477 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
478 result
.setCdmEntity(fromTaxon
);
479 // since we are swapping names, we have to detach the name from the synonym completely.
480 // Otherwise the synonym will still be in the list of typified names.
481 // synonym.getName().removeTaxonBase(synonym);
482 result
.includeResult(this.deleteSynonym(synonym
, null));
487 @Transactional(readOnly
= false)
489 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
490 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
492 TaxonName synonymName
= synonym
.getName();
493 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
496 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
497 newHomotypicalGroup
.addTypifiedName(synonymName
);
499 //remove existing basionym relationships
500 synonymName
.removeBasionyms();
502 //add basionym relationship
503 if (setBasionymRelationIfApplicable
){
504 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
505 for (TaxonName basionym
: basionyms
){
506 synonymName
.addBasionym(basionym
);
510 //set synonym relationship correctly
511 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
513 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
514 if (acceptedTaxon
!= null){
516 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
517 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
518 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
519 synonym
.setType(newRelationType
);
521 if (hasNewTargetTaxon
){
522 acceptedTaxon
.removeSynonym(synonym
, false);
525 if (hasNewTargetTaxon
){
526 @SuppressWarnings("null")
527 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
528 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
529 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
530 targetTaxon
.addSynonym(synonym
, relType
);
535 @Transactional(readOnly
= false)
536 public UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
538 clazz
= TaxonBase
.class;
540 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
545 protected void setDao(ITaxonDao dao
) {
550 public <T
extends TaxonBase
> Pager
<T
> findTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
551 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
552 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
);
554 List
<T
> results
= new ArrayList
<>();
555 if(numberOfResults
> 0) { // no point checking again
556 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorshipCache
, rank
,
557 pageSize
, pageNumber
, propertyPaths
);
560 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
564 public <T
extends TaxonBase
> List
<T
> listTaxaByName(Class
<T
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
,
565 String infraspecificEpithet
, String authorshipCache
, Rank rank
, Integer pageSize
,Integer pageNumber
, List
<String
> propertyPaths
) {
567 return findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infragenericEpithet
, authorshipCache
, rank
,
568 pageSize
, pageNumber
, propertyPaths
).getRecords();
572 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
573 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
574 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
576 List
<TaxonRelationship
> results
= new ArrayList
<>();
577 if(numberOfResults
> 0) { // no point checking again
578 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
584 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
585 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
586 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
588 List
<TaxonRelationship
> results
= new ArrayList
<>();
589 if(numberOfResults
> 0) { // no point checking again
590 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
592 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
596 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
597 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
598 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
600 List
<TaxonRelationship
> results
= new ArrayList
<>();
601 if(numberOfResults
> 0) { // no point checking again
602 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
608 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
609 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
610 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
612 List
<TaxonRelationship
> results
= new ArrayList
<>();
613 if(numberOfResults
> 0) { // no point checking again
614 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
616 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
620 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
621 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
623 Long numberOfResults
= dao
.countTaxonRelationships(types
);
624 List
<TaxonRelationship
> results
= new ArrayList
<>();
625 if(numberOfResults
> 0) {
626 results
= dao
.getTaxonRelationships(types
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
632 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
633 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
634 return page(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, INCLUDE_UNPUBLISHED
);
638 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
639 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
, boolean includeUnpublished
) {
642 long resultSize
= dao
.count(clazz
, restrictions
);
643 if(AbstractPagerImpl
.hasResultsInRange(resultSize
, pageIndex
, pageSize
)){
644 records
= dao
.list(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, includeUnpublished
);
646 records
= new ArrayList
<>();
648 Pager
<S
> pager
= new DefaultPagerImpl
<>(pageIndex
, resultSize
, pageSize
, records
);
653 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
654 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
656 Synonym synonym
= null;
659 synonym
= (Synonym
) dao
.load(synonymUuid
);
660 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
661 } catch (ClassCastException e
){
662 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
663 } catch (NullPointerException e
){
664 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
667 Classification classificationFilter
= null;
668 if(classificationUuid
!= null){
670 classificationFilter
= classificationDao
.load(classificationUuid
);
671 } catch (NullPointerException e
){
672 //TODO not sure, why an NPE should be thrown in the above load method
673 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
675 if(classificationFilter
== null){
676 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
680 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
682 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
683 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
691 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
692 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
694 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
695 relatedTaxa
.remove(taxon
);
696 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
701 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
702 * <code>taxon</code> supplied as parameter.
705 * @param includeRelationships
707 * @param maxDepth can be <code>null</code> for infinite depth
710 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
711 boolean includeUnpublished
, Integer maxDepth
) {
717 if(includeRelationships
.isEmpty()){
721 if(maxDepth
!= null) {
724 if(logger
.isDebugEnabled()){
725 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
727 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
728 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
729 for (TaxonRelationship taxRel
: taxonRelationships
) {
732 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
735 // filter by includeRelationships
736 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
737 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
738 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
739 if(logger
.isDebugEnabled()){
740 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
742 taxa
.add(taxRel
.getToTaxon());
743 if(maxDepth
== null || maxDepth
> 0) {
744 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
747 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
748 taxa
.add(taxRel
.getFromTaxon());
749 if(logger
.isDebugEnabled()){
750 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
752 if(maxDepth
== null || maxDepth
> 0) {
753 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
763 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
764 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
766 List
<Synonym
> results
= new ArrayList
<>();
767 if(numberOfResults
> 0) { // no point checking again
768 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
771 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
775 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
776 List
<List
<Synonym
>> result
= new ArrayList
<>();
777 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
778 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
781 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
784 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
785 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
786 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
793 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
794 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
795 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
797 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
801 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
802 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
803 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
804 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
805 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
806 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
808 return heterotypicSynonymyGroups
;
812 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
814 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
815 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
816 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(),
817 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
818 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
820 return new ArrayList
<>();
825 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
827 @SuppressWarnings("rawtypes")
828 List
<IdentifiableEntity
> results
= new ArrayList
<>();
829 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
830 List
<TaxonBase
> taxa
= null;
833 long numberTaxaResults
= 0L;
835 List
<String
> propertyPath
= new ArrayList
<>();
836 if(configurator
.getTaxonPropertyPath() != null){
837 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
840 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
841 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
843 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
844 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
845 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
846 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
849 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
850 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
851 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
852 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
853 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
854 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
858 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
861 results
.addAll(taxa
);
864 numberOfResults
+= numberTaxaResults
;
866 // Names without taxa
867 if (configurator
.isDoNamesWithoutTaxa()) {
868 int numberNameResults
= 0;
870 List
<TaxonName
> names
=
871 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
872 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
873 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
874 if (names
.size() > 0) {
875 for (TaxonName taxonName
: names
) {
876 if (taxonName
.getTaxonBases().size() == 0) {
877 results
.add(taxonName
);
881 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
882 numberOfResults
+= numberNameResults
;
886 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
889 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
890 return dao
.getUuidAndTitleCache(limit
, pattern
);
894 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
895 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
899 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
900 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
901 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
904 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
906 // logger.setLevel(Level.TRACE);
907 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
909 logger
.trace("listMedia() - START");
911 Set
<Taxon
> taxa
= new HashSet
<>();
912 List
<Media
> taxonMedia
= new ArrayList
<>();
913 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
915 if (limitToGalleries
== null) {
916 limitToGalleries
= false;
919 // --- resolve related taxa
920 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
921 logger
.trace("listMedia() - resolve related taxa");
922 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
925 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
927 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
928 logger
.trace("listMedia() - includeTaxonDescriptions");
929 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
930 // --- TaxonDescriptions
931 for (Taxon t
: taxa
) {
932 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
934 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
935 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
936 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
937 for (Media media
: element
.getMedia()) {
938 if(taxonDescription
.isImageGallery()){
939 taxonMedia
.add(media
);
942 nonImageGalleryImages
.add(media
);
948 //put images from image gallery first (#3242)
949 taxonMedia
.addAll(nonImageGalleryImages
);
953 if(includeOccurrences
!= null && includeOccurrences
) {
954 logger
.trace("listMedia() - includeOccurrences");
955 @SuppressWarnings("rawtypes")
956 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
958 for (Taxon t
: taxa
) {
959 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
961 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
963 // direct media removed from specimen #3597
964 // taxonMedia.addAll(occurrence.getMedia());
966 // SpecimenDescriptions
967 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
968 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
969 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
970 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
971 for (DescriptionElementBase element
: elements
) {
972 for (Media media
: element
.getMedia()) {
973 taxonMedia
.add(media
);
979 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
980 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
982 //TODO why may collections have media attached? #
983 if (derivedUnit
.getCollection() != null){
984 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
988 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
992 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
993 logger
.trace("listMedia() - includeTaxonNameDescriptions");
994 // --- TaxonNameDescription
995 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
996 for (Taxon t
: taxa
) {
997 nameDescriptions
.addAll(t
.getName().getDescriptions());
999 for(TaxonNameDescription nameDescription
: nameDescriptions
){
1000 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
1001 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
1002 for (DescriptionElementBase element
: elements
) {
1003 for (Media media
: element
.getMedia()) {
1004 taxonMedia
.add(media
);
1011 logger
.trace("listMedia() - initialize");
1012 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
1014 logger
.trace("listMedia() - END");
1020 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
1021 return this.dao
.loadList(listOfIDs
, null, null);
1025 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
1026 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
1030 public long countSynonyms(boolean onlyAttachedToTaxon
){
1031 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
1035 @Transactional(readOnly
=false)
1036 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
1038 if (config
== null){
1039 config
= new TaxonDeletionConfigurator();
1041 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
1042 DeleteResult result
= new DeleteResult();
1045 result
.addException(new Exception ("The taxon was already deleted."));
1048 taxon
= HibernateProxyHelper
.deproxy(taxon
);
1049 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
1050 config
.setClassificationUuid(classificationUuid
);
1051 result
= isDeletable(taxonUUID
, config
);
1054 // --- DeleteSynonymRelations
1055 if (config
.isDeleteSynonymRelations()){
1056 boolean removeSynonymNameFromHomotypicalGroup
= false;
1057 // use tmp Set to avoid concurrent modification
1058 Set
<Synonym
> synsToDelete
= new HashSet
<>();
1059 synsToDelete
.addAll(taxon
.getSynonyms());
1060 for (Synonym synonym
: synsToDelete
){
1061 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
1063 // --- DeleteSynonymsIfPossible
1064 if (config
.isDeleteSynonymsIfPossible()){
1066 boolean newHomotypicGroupIfNeeded
= true;
1067 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
1068 result
.includeResult(deleteSynonym(synonym
, synConfig
));
1073 // --- DeleteTaxonRelationships
1074 if (! config
.isDeleteTaxonRelationships()){
1075 if (taxon
.getTaxonRelations().size() > 0){
1077 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1078 "Remove taxon from all relations to other taxa prior to deletion."));
1081 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
1082 configRelTaxon
.setDeleteTaxonNodes(false);
1083 configRelTaxon
.setDeleteConceptRelationships(true);
1085 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
1086 if (config
.isDeleteMisappliedNames()
1087 && taxRel
.getType().isMisappliedName()
1088 && taxon
.equals(taxRel
.getToTaxon())){
1089 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
1090 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
1092 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
1093 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1094 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
1095 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1098 taxon
.removeTaxonRelation(taxRel
);
1103 if (config
.isDeleteDescriptions()){
1104 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
1105 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
1106 for (TaxonDescription desc
: descriptions
){
1107 //TODO use description delete configurator ?
1108 //FIXME check if description is ALWAYS deletable
1109 if (desc
.getDescribedSpecimenOrObservation() != null){
1111 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1112 " which also describes specimens or observations"));
1115 removeDescriptions
.add(desc
);
1118 for (TaxonDescription desc
: removeDescriptions
){
1119 taxon
.removeDescription(desc
);
1120 descriptionService
.delete(desc
);
1127 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1128 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1130 if (taxon
.getTaxonNodes().size() != 0){
1131 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1132 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1133 TaxonNode node
= null;
1134 boolean deleteChildren
;
1135 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1136 deleteChildren
= true;
1138 deleteChildren
= false;
1140 boolean success
= true;
1141 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1142 while (iterator
.hasNext()){
1143 node
= iterator
.next();
1144 if (node
.getClassification().equals(classification
)){
1150 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1151 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1152 nodeService
.delete(node
);
1153 result
.addDeletedObject(node
);
1156 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1158 } else if (config
.isDeleteInAllClassifications()){
1159 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1160 nodesList
.addAll(taxon
.getTaxonNodes());
1161 for (ITaxonTreeNode treeNode
: nodesList
){
1162 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1163 if(!deleteChildren
){
1164 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1165 for (Object childNode
: childNodes
){
1166 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1167 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1171 config
.getTaxonNodeConfig().setDeleteElement(false);
1172 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1173 if (!resultNodes
.isOk()){
1174 result
.addExceptions(resultNodes
.getExceptions());
1175 result
.setStatus(resultNodes
.getStatus());
1177 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1182 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1186 TaxonName name
= taxon
.getName();
1187 taxon
.setName(null);
1188 this.saveOrUpdate(taxon
);
1190 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1193 result
.addDeletedObject(taxon
);
1194 }catch(Exception e
){
1195 result
.addException(e
);
1200 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1204 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1205 DeleteResult nameResult
= new DeleteResult();
1206 //remove name if possible (and required)
1208 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1210 if (nameResult
.isError() || nameResult
.isAbort()){
1211 result
.addRelatedObject(name
);
1212 result
.addExceptions(nameResult
.getExceptions());
1214 result
.includeResult(nameResult
);
1223 @Transactional(readOnly
= false)
1224 public DeleteResult
delete(UUID synUUID
){
1225 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1226 return this.deleteSynonym(syn
, null);
1230 @Transactional(readOnly
= false)
1231 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1232 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1236 @Transactional(readOnly
= false)
1237 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1238 DeleteResult result
= new DeleteResult();
1239 if (synonym
== null){
1241 result
.addException(new Exception("The synonym was already deleted."));
1245 if (config
== null){
1246 config
= new SynonymDeletionConfigurator();
1249 result
= isDeletable(synonym
.getUuid(), config
);
1253 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1256 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1258 if (accTaxon
!= null){
1259 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1260 accTaxon
.removeSynonym(synonym
, false);
1261 this.saveOrUpdate(accTaxon
);
1262 result
.addUpdatedObject(accTaxon
);
1264 this.saveOrUpdate(synonym
);
1268 TaxonName name
= synonym
.getName();
1269 synonym
.setName(null);
1271 dao
.delete(synonym
);
1272 result
.addDeletedObject(synonym
);
1274 //remove name if possible (and required)
1275 if (name
!= null && config
.isDeleteNameIfPossible()){
1277 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1278 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1279 result
.addExceptions(nameDeleteResult
.getExceptions());
1280 result
.addRelatedObject(name
);
1282 result
.addDeletedObject(name
);
1290 public Map
<String
, Map
<UUID
,Set
<TaxonName
>>> findIdenticalTaxonNames(List
<UUID
> sourceRefUuids
, List
<String
> propertyPaths
) {
1291 return this.dao
.findIdenticalNames(sourceRefUuids
, propertyPaths
);
1295 public Taxon
findBestMatchingTaxon(String taxonName
) {
1296 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1297 config
.setTaxonNameTitle(taxonName
);
1298 return findBestMatchingTaxon(config
);
1302 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1304 Taxon bestCandidate
= null;
1306 // 1. search for accepted taxa
1307 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1308 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1309 boolean bestCandidateMatchesSecUuid
= false;
1310 boolean bestCandidateIsInClassification
= false;
1311 int countEqualCandidates
= 0;
1312 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1313 if(taxonBaseCandidate
instanceof Taxon
){
1314 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1315 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1316 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1318 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1319 bestCandidate
= newCanditate
;
1320 countEqualCandidates
= 1;
1321 bestCandidateMatchesSecUuid
= true;
1325 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1326 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1328 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1329 bestCandidate
= newCanditate
;
1330 countEqualCandidates
= 1;
1331 bestCandidateIsInClassification
= true;
1334 if (bestCandidate
== null){
1335 bestCandidate
= newCanditate
;
1336 countEqualCandidates
= 1;
1339 }else{ //not Taxon.class
1342 countEqualCandidates
++;
1345 if (bestCandidate
!= null){
1346 if(countEqualCandidates
> 1){
1347 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1348 return bestCandidate
;
1350 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1351 return bestCandidate
;
1355 // 2. search for synonyms
1356 if (config
.isIncludeSynonyms()){
1357 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1358 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1359 for(TaxonBase taxonBase
: synonymList
){
1360 if(taxonBase
instanceof Synonym
){
1361 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1362 bestCandidate
= synonym
.getAcceptedTaxon();
1363 if(bestCandidate
!= null){
1364 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1365 return bestCandidate
;
1367 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1372 } catch (Exception e
){
1374 e
.printStackTrace();
1377 return bestCandidate
;
1380 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1381 UUID configClassificationUuid
= config
.getClassificationUuid();
1382 if (configClassificationUuid
== null){
1385 for (TaxonNode node
: taxon
.getTaxonNodes()){
1386 UUID classUuid
= node
.getClassification().getUuid();
1387 if (configClassificationUuid
.equals(classUuid
)){
1394 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1395 UUID configSecUuid
= config
.getSecUuid();
1396 if (configSecUuid
== null){
1399 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1400 return configSecUuid
.equals(taxonSecUuid
);
1404 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1405 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1406 if(! synonymList
.isEmpty()){
1407 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1408 if(synonymList
.size() == 1){
1409 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1412 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1420 @Transactional(readOnly
= false)
1421 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1423 boolean moveHomotypicGroup
,
1424 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1425 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1427 oldSynonym
.getSec(),
1428 oldSynonym
.getSecMicroReference(),
1433 @Transactional(readOnly
= false)
1434 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1436 boolean moveHomotypicGroup
,
1437 SynonymType newSynonymType
,
1438 Reference newSecundum
,
1439 String newSecundumDetail
,
1440 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1442 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1443 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1444 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1445 TaxonName synonymName
= synonym
.getName();
1446 TaxonName fromTaxonName
= oldTaxon
.getName();
1447 //set default relationship type
1448 if (newSynonymType
== null){
1449 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1451 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1453 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1454 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1455 boolean isSingleInGroup
= !(hgSize
> 1);
1457 if (! isSingleInGroup
){
1458 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1459 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1460 if (isHomotypicToAccepted
){
1461 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.";
1462 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1463 message
= String
.format(message
, homotypicRelatives
);
1464 throw new HomotypicalGroupChangeException(message
);
1466 if (! moveHomotypicGroup
){
1467 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.";
1468 throw new HomotypicalGroupChangeException(message
);
1471 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1473 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1475 UpdateResult result
= new UpdateResult();
1476 //move all synonyms to new taxon
1477 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1478 for (Synonym synRelation
: homotypicSynonyms
){
1480 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1481 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1482 oldTaxon
.removeSynonym(synRelation
, false);
1483 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1485 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1486 synRelation
.setSec(newSecundum
);
1488 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1489 synRelation
.setSecMicroReference(newSecundumDetail
);
1492 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1493 if (!synRelation
.equals(oldSynonym
)){
1498 result
.addUpdatedObject(oldTaxon
);
1499 result
.addUpdatedObject(newTaxon
);
1500 saveOrUpdate(oldTaxon
);
1501 saveOrUpdate(newTaxon
);
1507 public <T
extends TaxonBase
>List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1509 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1513 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1514 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1515 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1516 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1517 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1519 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1520 null, includeUnpublished
, languages
, highlightFragments
, null);
1522 // --- execute search
1523 TopGroups
<BytesRef
> topDocsResultSet
;
1525 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1526 } catch (ParseException e
) {
1527 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1528 luceneParseException
.setStackTrace(e
.getStackTrace());
1529 throw luceneParseException
;
1532 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1533 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1535 // --- initialize taxa, thighlight matches ....
1536 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1537 @SuppressWarnings("rawtypes")
1538 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1539 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1541 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1542 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1545 @Transactional(readOnly
= true)
1547 public <S
extends TaxonBase
> Pager
<S
> findByTitleWithRestrictions(Class
<S
> clazz
, String queryString
, MatchMode matchmode
, List
<Restriction
<?
>> restrictions
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
1548 long numberOfResults
= dao
.countByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
);
1550 long numberOfResults_doubtful
= dao
.countByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
);
1551 List
<S
> results
= new ArrayList
<>();
1552 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1554 results
= dao
.findByTitleWithRestrictions(clazz
, queryString
, matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1555 results
.addAll(dao
.findByTitleWithRestrictions(clazz
, "?".concat(queryString
), matchmode
, restrictions
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1557 Collections
.sort(results
, new TaxonComparator());
1558 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1561 @Transactional(readOnly
= true)
1563 public <S
extends TaxonBase
> Pager
<S
> findByTitle(Class
<S
> clazz
, String queryString
,MatchMode matchmode
, List
<Criterion
> criteria
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
1564 long numberOfResults
= dao
.countByTitle(clazz
, queryString
, matchmode
, criteria
);
1565 //check whether there are doubtful taxa matching
1566 long numberOfResults_doubtful
= dao
.countByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
);
1567 List
<S
> results
= new ArrayList
<>();
1568 if(numberOfResults
> 0 || numberOfResults_doubtful
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1569 if (numberOfResults
> 0){
1570 results
= dao
.findByTitle(clazz
, queryString
, matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
1572 results
= new ArrayList
<>();
1574 if (numberOfResults_doubtful
> 0){
1575 results
.addAll(dao
.findByTitle(clazz
, "?".concat(queryString
), matchmode
, criteria
, pageSize
, pageNumber
, orderHints
, propertyPaths
));
1578 Collections
.sort(results
, new TaxonComparator());
1579 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
1583 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1584 Classification classification
, TaxonNode subtree
,
1585 Integer pageSize
, Integer pageNumber
,
1586 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1588 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
1590 // --- execute search
1591 TopGroups
<BytesRef
> topDocsResultSet
;
1593 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1594 } catch (ParseException e
) {
1595 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1596 luceneParseException
.setStackTrace(e
.getStackTrace());
1597 throw luceneParseException
;
1600 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1601 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1603 // --- initialize taxa, thighlight matches ....
1604 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1605 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1606 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1608 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1609 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1614 * @param queryString
1615 * @param classification
1616 * @param includeUnpublished
1618 * @param highlightFragments
1619 * @param sortFields TODO
1620 * @param directorySelectClass
1623 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1624 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1625 boolean highlightFragments
, SortField
[] sortFields
) {
1627 Builder finalQueryBuilder
= new Builder();
1628 Builder textQueryBuilder
= new Builder();
1630 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1631 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1633 if(sortFields
== null){
1634 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1636 luceneSearch
.setSortFields(sortFields
);
1638 // ---- search criteria
1639 luceneSearch
.setCdmTypRestriction(clazz
);
1641 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1642 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1643 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1645 if(className
!= null){
1646 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1649 BooleanQuery textQuery
= textQueryBuilder
.build();
1650 if(textQuery
.clauses().size() > 0) {
1651 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1654 if(classification
!= null){
1655 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1657 if(subtree
!= null){
1658 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1660 if(!includeUnpublished
) {
1661 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1662 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1663 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1666 luceneSearch
.setQuery(finalQueryBuilder
.build());
1668 if(highlightFragments
){
1669 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1671 return luceneSearch
;
1675 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1676 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1677 * drawback of requiring to do the join an indexing time.
1678 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1680 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1682 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1683 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1685 * @param queryString
1686 * @param classification
1688 * @param highlightFragments
1689 * @param sortFields TODO
1692 * @throws IOException
1694 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1695 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1696 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1699 String queryTermField
;
1700 String toField
= "id"; // TaxonBase.uuid
1701 String publishField
;
1702 String publishFieldInvers
;
1704 if(edge
.isBidirectional()){
1705 throw new RuntimeException("Bidirectional joining not supported!");
1708 fromField
= "relatedFrom.id";
1709 queryTermField
= "relatedFrom.titleCache";
1710 publishField
= "relatedFrom.publish";
1711 publishFieldInvers
= "relatedTo.publish";
1712 } else if(edge
.isInvers()) {
1713 fromField
= "relatedTo.id";
1714 queryTermField
= "relatedTo.titleCache";
1715 publishField
= "relatedTo.publish";
1716 publishFieldInvers
= "relatedFrom.publish";
1718 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1721 Builder finalQueryBuilder
= new Builder();
1723 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1724 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1726 Builder joinFromQueryBuilder
= new Builder();
1727 if(!StringUtils
.isEmpty(queryString
)){
1728 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1730 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1731 if(!includeUnpublished
){
1732 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1733 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1736 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1738 if(sortFields
== null){
1739 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1741 luceneSearch
.setSortFields(sortFields
);
1743 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1745 if(classification
!= null){
1746 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1748 if(subtree
!= null){
1749 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1752 luceneSearch
.setQuery(finalQueryBuilder
.build());
1754 if(highlightFragments
){
1755 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1757 return luceneSearch
;
1761 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1762 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1763 Classification classification
, TaxonNode subtree
,
1764 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1765 boolean highlightFragments
, Integer pageSize
,
1766 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1767 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1769 // FIXME: allow taxonomic ordering
1770 // 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";
1771 // this require building a special sort column by a special classBridge
1772 if(highlightFragments
){
1773 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1774 "currently not fully supported by this method and thus " +
1775 "may not work with common names and misapplied names.");
1778 // convert sets to lists
1779 List
<NamedArea
> namedAreaList
= null;
1780 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1781 if(namedAreas
!= null){
1782 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1783 namedAreaList
.addAll(namedAreas
);
1785 if(distributionStatus
!= null){
1786 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1787 distributionStatusList
.addAll(distributionStatus
);
1790 // set default if parameter is null
1791 if(searchModes
== null){
1792 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1795 // set sort order and thus override any sort orders which may have been
1796 // defined by prepare*Search methods
1797 if(orderHints
== null){
1798 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1800 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1802 for(OrderHint oh
: orderHints
){
1803 sortFields
[i
++] = oh
.toSortField();
1805 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1806 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1808 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1810 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1811 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1814 ======== filtering by distribution , HOWTO ========
1816 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1817 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1818 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1819 which will be put into a FilteredQuersy in the end ?
1822 3. how does it work in spatial?
1824 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1825 - http://www.infoq.com/articles/LuceneSpatialSupport
1826 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1827 ------------------------------------------------------------------------
1830 A) use a separate distribution filter per index sub-query/search:
1831 - byTaxonSyonym (query TaxaonBase):
1832 use a join area filter (Distribution -> TaxonBase)
1833 - byCommonName (query DescriptionElementBase): use an area filter on
1834 DescriptionElementBase !!! PROBLEM !!!
1835 This cannot work since the distributions are different entities than the
1836 common names and thus these are different lucene documents.
1837 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1838 use a join area filter (Distribution -> TaxonBase)
1840 B) use a common distribution filter for all index sub-query/searches:
1841 - use a common join area filter (Distribution -> TaxonBase)
1842 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1843 PROBLEM in this case: we are losing the fragment highlighting for the
1844 common names, since the returned documents are always TaxonBases
1847 /* The QueryFactory for creating filter queries on Distributions should
1848 * The query factory used for the common names query cannot be reused
1849 * for this case, since we want to only record the text fields which are
1850 * actually used in the primary query
1852 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1854 Builder multiIndexByAreaFilterBuilder
= new Builder();
1855 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1857 // search for taxa or synonyms
1858 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1859 @SuppressWarnings("rawtypes")
1860 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1861 String className
= null;
1862 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1863 taxonBaseSubclass
= Taxon
.class;
1864 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1865 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1867 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1868 queryString
, classification
, subtree
, className
,
1869 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1870 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1871 /* A) does not work!!!!
1872 if(addDistributionFilter){
1873 // in this case we need a filter which uses a join query
1874 // to get the TaxonBase documents for the DescriptionElementBase documents
1875 // which are matching the areas in question
1876 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1878 distributionStatusList,
1879 distributionFilterQueryFactory
1881 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1884 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1885 // add additional area filter for synonyms
1886 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1887 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1889 //TODO replace by createByDistributionJoinQuery
1890 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1891 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1892 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1897 // search by CommonTaxonName
1898 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1900 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1901 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1902 CommonTaxonName
.class,
1903 "inDescription.taxon.id",
1905 QueryFactory
.addTypeRestriction(
1906 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
1907 , CommonTaxonName
.class
1908 ).build(), "id", null, ScoreMode
.Max
);
1909 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1910 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1911 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1912 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1913 Builder builder
= new BooleanQuery
.Builder();
1914 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1915 if(!includeUnpublished
) {
1916 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1917 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1919 byCommonNameSearch
.setQuery(builder
.build());
1920 byCommonNameSearch
.setSortFields(sortFields
);
1922 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1924 luceneSearches
.add(byCommonNameSearch
);
1926 /* A) does not work!!!!
1928 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1929 queryString, classification, null, languages, highlightFragments)
1931 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1932 if(addDistributionFilter){
1933 // in this case we are able to use DescriptionElementBase documents
1934 // which are matching the areas in question directly
1935 BooleanQuery byDistributionQuery = createByDistributionQuery(
1937 distributionStatusList,
1938 distributionFilterQueryFactory
1940 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1945 // search by misapplied names
1946 //TODO merge with pro parte synonym search once #7487 is fixed
1947 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1949 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1950 // which allows doing query time joins
1951 // finds the misapplied name (Taxon B) which is an misapplication for
1952 // a related Taxon A.
1954 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1955 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1956 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1958 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1959 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1962 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1963 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1964 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1965 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1967 if(addDistributionFilter
){
1968 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1971 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
1972 * Maybe this is a bug in java itself.
1974 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1977 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1979 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1980 * will execute as expected:
1982 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1983 * String toField = "relation." + misappliedNameForUuid +".to.id";
1985 * Comparing both strings by the String.equals method returns true, so both String are identical.
1987 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1988 * 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)
1989 * The bug is persistent after a reboot of the development computer.
1991 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1992 // String toField = "relation." + misappliedNameForUuid +".to.id";
1993 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1994 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1995 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1997 //TODO replace by createByDistributionJoinQuery
1998 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1999 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2000 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2002 // debug code for bug described above
2003 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
2004 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2005 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2007 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2011 // search by pro parte synonyms
2012 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
2013 //TODO merge with misapplied name search once #7487 is fixed
2014 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
2015 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
2017 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
2018 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
2019 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
2020 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2022 if(addDistributionFilter
){
2023 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2024 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
2025 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
2026 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
2027 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
2028 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2030 }//end pro parte synonyms
2032 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
2033 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
2035 if(addDistributionFilter
){
2038 // in this case we need a filter which uses a join query
2039 // to get the TaxonBase documents for the DescriptionElementBase documents
2040 // which are matching the areas in question
2042 // for doTaxa, doByCommonName
2043 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
2045 distributionStatusList
,
2046 distributionFilterQueryFactory
,
2049 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
2052 if (addDistributionFilter
){
2053 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
2057 // --- execute search
2058 TopGroups
<BytesRef
> topDocsResultSet
;
2060 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2061 } catch (ParseException e
) {
2062 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2063 luceneParseException
.setStackTrace(e
.getStackTrace());
2064 throw luceneParseException
;
2067 // --- initialize taxa, highlight matches ....
2068 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2071 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2072 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2074 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
2075 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2079 * @param namedAreaList at least one area must be in the list
2080 * @param distributionStatusList optional
2081 * @param toType toType
2082 * Optional parameter. Only used for debugging to print the toType documents
2083 * @param asFilter TODO
2085 * @throws IOException
2087 protected Query
createByDistributionJoinQuery(
2088 List
<NamedArea
> namedAreaList
,
2089 List
<PresenceAbsenceTerm
> distributionStatusList
,
2090 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
2091 ) throws IOException
{
2093 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2094 String toField
= "id"; // id in toType usually this is the TaxonBase index
2096 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
2098 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
2100 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
2102 return taxonAreaJoinQuery
;
2106 * @param namedAreaList
2107 * @param distributionStatusList
2108 * @param queryFactory
2111 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
2112 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
2113 Builder areaQueryBuilder
= new Builder();
2114 // area field from Distribution
2115 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2117 // status field from Distribution
2118 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2119 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2122 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2123 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2128 * This method has been primarily created for testing the area join query but might
2129 * also be useful in other situations
2131 * @param namedAreaList
2132 * @param distributionStatusList
2133 * @param classification
2134 * @param highlightFragments
2136 * @throws IOException
2138 protected LuceneSearch
prepareByDistributionSearch(
2139 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2140 Classification classification
, TaxonNode subtree
) throws IOException
{
2142 Builder finalQueryBuilder
= new Builder();
2144 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2146 // FIXME is this query factory using the wrong type?
2147 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2149 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2150 luceneSearch
.setSortFields(sortFields
);
2153 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2155 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2157 if(classification
!= null){
2158 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
2160 if(subtree
!= null){
2161 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
2163 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2164 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2165 luceneSearch
.setQuery(finalQuery
);
2167 return luceneSearch
;
2171 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2172 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2173 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2174 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2176 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2178 // --- execute search
2179 TopGroups
<BytesRef
> topDocsResultSet
;
2181 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2182 } catch (ParseException e
) {
2183 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2184 luceneParseException
.setStackTrace(e
.getStackTrace());
2185 throw luceneParseException
;
2188 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2189 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2191 // --- initialize taxa, highlight matches ....
2192 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2193 @SuppressWarnings("rawtypes")
2194 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2195 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2197 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2198 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2202 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2203 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2204 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2206 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2207 classification
, subtree
,
2208 null, languages
, highlightFragments
);
2209 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2210 includeUnpublished
, languages
, highlightFragments
, null);
2212 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2214 // --- execute search
2215 TopGroups
<BytesRef
> topDocsResultSet
;
2217 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2218 } catch (ParseException e
) {
2219 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2220 luceneParseException
.setStackTrace(e
.getStackTrace());
2221 throw luceneParseException
;
2224 // --- initialize taxa, highlight matches ....
2225 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2227 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2228 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2229 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2231 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2232 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2234 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2235 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2240 * @param queryString
2241 * @param classification
2244 * @param highlightFragments
2245 * @param directorySelectClass
2248 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2249 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2250 List
<Language
> languages
, boolean highlightFragments
) {
2252 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2253 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2255 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2257 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2258 languages
, descriptionElementQueryFactory
);
2260 luceneSearch
.setSortFields(sortFields
);
2261 luceneSearch
.setCdmTypRestriction(clazz
);
2262 luceneSearch
.setQuery(finalQuery
);
2263 if(highlightFragments
){
2264 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2267 return luceneSearch
;
2271 * @param queryString
2272 * @param classification
2275 * @param descriptionElementQueryFactory
2278 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2279 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2280 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2282 Builder finalQueryBuilder
= new Builder();
2283 Builder textQueryBuilder
= new Builder();
2285 if(!StringUtils
.isEmpty(queryString
)){
2287 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2290 Builder nameQueryBuilder
= new Builder();
2291 if(languages
== null || languages
.size() == 0){
2292 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2294 Builder languageSubQueryBuilder
= new Builder();
2295 for(Language lang
: languages
){
2296 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2298 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2299 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2301 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2304 // text field from TextData
2305 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2307 // --- TermBase fields - by representation ----
2308 // state field from CategoricalData
2309 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2311 // state field from CategoricalData
2312 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2314 // area field from Distribution
2315 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2317 // status field from Distribution
2318 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2320 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2323 // --- classification ----
2326 if(classification
!= null){
2327 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2329 if(subtree
!= null){
2330 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2333 // --- IdentifieableEntity fields - by uuid
2334 if(features
!= null && features
.size() > 0 ){
2335 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2338 // the description must be associated with a taxon
2339 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2341 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2342 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2347 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2349 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2350 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2352 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2353 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2355 UUID nameUuid
= taxon
.getName().getUuid();
2356 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2357 String epithetOfTaxon
= null;
2358 String infragenericEpithetOfTaxon
= null;
2359 String infraspecificEpithetOfTaxon
= null;
2360 if (taxonName
.isSpecies()){
2361 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2362 } else if (taxonName
.isInfraGeneric()){
2363 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2364 } else if (taxonName
.isInfraSpecific()){
2365 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2367 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2368 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2369 List
<String
> taxonNames
= new ArrayList
<>();
2371 for (TaxonNode node
: nodes
){
2372 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2373 // List<String> synonymsEpithet = new ArrayList<>();
2375 if (node
.getClassification().equals(classification
)){
2376 if (!node
.isTopmostNode()){
2377 TaxonNode parent
= node
.getParent();
2378 parent
= CdmBase
.deproxy(parent
);
2379 TaxonName parentName
= parent
.getTaxon().getName();
2380 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2381 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2383 //create inferred synonyms for species, subspecies
2384 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2386 Synonym inferredEpithet
= null;
2387 Synonym inferredGenus
= null;
2388 Synonym potentialCombination
= null;
2390 List
<String
> propertyPaths
= new ArrayList
<>();
2391 propertyPaths
.add("synonym");
2392 propertyPaths
.add("synonym.name");
2393 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2394 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2396 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2397 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2398 null, null,orderHintsSynonyms
,propertyPaths
);
2400 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2401 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2402 if (doWithMisappliedNames
){
2403 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2404 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2405 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2406 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2407 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2408 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2411 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2412 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2414 inferredEpithet
= createInferredEpithets(taxon
,
2415 zooHashMap
, taxonName
, epithetOfTaxon
,
2416 infragenericEpithetOfTaxon
,
2417 infraspecificEpithetOfTaxon
,
2418 taxonNames
, parentName
,
2419 synonymRelationOfParent
);
2421 inferredSynonyms
.add(inferredEpithet
);
2422 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2423 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2426 if (doWithMisappliedNames
){
2428 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2429 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2431 inferredEpithet
= createInferredEpithets(taxon
,
2432 zooHashMap
, taxonName
, epithetOfTaxon
,
2433 infragenericEpithetOfTaxon
,
2434 infraspecificEpithetOfTaxon
,
2435 taxonNames
, parentName
,
2438 inferredSynonyms
.add(inferredEpithet
);
2439 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2440 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2444 if (!taxonNames
.isEmpty()){
2445 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2446 if (!synNotInCDM
.isEmpty()){
2447 inferredSynonymsToBeRemoved
.clear();
2449 for (Synonym syn
:inferredSynonyms
){
2450 IZoologicalName name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2451 if (!synNotInCDM
.contains(name
.getNameCache())){
2452 inferredSynonymsToBeRemoved
.add(syn
);
2456 // Remove identified Synonyms from inferredSynonyms
2457 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2458 inferredSynonyms
.remove(synonym
);
2463 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2465 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2467 inferredGenus
= createInferredGenus(taxon
,
2468 zooHashMap
, taxonName
, epithetOfTaxon
,
2469 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2471 inferredSynonyms
.add(inferredGenus
);
2472 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2473 taxonNames
.add(inferredGenus
.getName().getNameCache());
2476 if (doWithMisappliedNames
){
2478 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2479 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2480 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2482 inferredSynonyms
.add(inferredGenus
);
2483 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2484 taxonNames
.add(inferredGenus
.getName().getNameCache());
2489 if (!taxonNames
.isEmpty()){
2490 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2491 IZoologicalName name
;
2492 if (!synNotInCDM
.isEmpty()){
2493 inferredSynonymsToBeRemoved
.clear();
2495 for (Synonym syn
:inferredSynonyms
){
2496 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2497 if (!synNotInCDM
.contains(name
.getNameCache())){
2498 inferredSynonymsToBeRemoved
.add(syn
);
2502 // Remove identified Synonyms from inferredSynonyms
2503 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2504 inferredSynonyms
.remove(synonym
);
2509 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2511 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2512 //for all synonyms of the parent...
2513 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2515 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2517 synName
= synonymRelationOfParent
.getName();
2519 // Set the sourceReference
2520 sourceReference
= synonymRelationOfParent
.getSec();
2522 // Determine the idInSource
2523 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2525 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2526 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2527 String synParentInfragenericName
= null;
2528 String synParentSpecificEpithet
= null;
2530 if (parentSynZooName
.isInfraGeneric()){
2531 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2533 if (parentSynZooName
.isSpecies()){
2534 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2537 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2538 synonymsGenus.put(synGenusName, idInSource);
2541 //for all synonyms of the taxon
2543 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2545 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2546 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2548 synParentInfragenericName
,
2549 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2551 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2552 inferredSynonyms
.add(potentialCombination
);
2553 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2554 taxonNames
.add(potentialCombination
.getName().getNameCache());
2558 if (doWithMisappliedNames
){
2560 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2562 TaxonName misappliedParentName
;
2564 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2565 misappliedParentName
= misappliedParent
.getName();
2567 HibernateProxyHelper
.deproxy(misappliedParent
);
2569 // Set the sourceReference
2570 sourceReference
= misappliedParent
.getSec();
2572 // Determine the idInSource
2573 String idInSourceParent
= getIdInSource(misappliedParent
);
2575 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2576 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2577 String synParentInfragenericName
= null;
2578 String synParentSpecificEpithet
= null;
2580 if (parentSynZooName
.isInfraGeneric()){
2581 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2583 if (parentSynZooName
.isSpecies()){
2584 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2587 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2588 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2589 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2590 potentialCombination
= createPotentialCombination(
2591 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2593 synParentInfragenericName
,
2594 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2596 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2597 inferredSynonyms
.add(potentialCombination
);
2598 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2599 taxonNames
.add(potentialCombination
.getName().getNameCache());
2604 if (!taxonNames
.isEmpty()){
2605 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2606 IZoologicalName name
;
2607 if (!synNotInCDM
.isEmpty()){
2608 inferredSynonymsToBeRemoved
.clear();
2609 for (Synonym syn
:inferredSynonyms
){
2611 name
= syn
.getName();
2612 }catch (ClassCastException e
){
2613 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2615 if (!synNotInCDM
.contains(name
.getNameCache())){
2616 inferredSynonymsToBeRemoved
.add(syn
);
2619 // Remove identified Synonyms from inferredSynonyms
2620 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2621 inferredSynonyms
.remove(synonym
);
2627 logger
.info("The synonym type is not defined.");
2628 return inferredSynonyms
;
2634 return inferredSynonyms
;
2637 private Synonym
createPotentialCombination(String idInSourceParent
,
2638 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2639 String synParentInfragenericName
, String synParentSpecificEpithet
,
2640 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2641 Synonym potentialCombination
;
2642 Reference sourceReference
;
2643 IZoologicalName inferredSynName
;
2644 HibernateProxyHelper
.deproxy(syn
);
2646 // Set sourceReference
2647 sourceReference
= syn
.getSec();
2648 if (sourceReference
== null){
2649 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2651 if (!parentSynZooName
.getTaxa().isEmpty()){
2652 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2654 sourceReference
= taxon
.getSec();
2657 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2659 String synTaxonInfraSpecificName
= null;
2661 if (parentSynZooName
.isSpecies()){
2662 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2665 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2666 synonymsEpithet.add(epithetName);
2669 //create potential combinations...
2670 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2672 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2673 if (zooSynName
.isSpecies()){
2674 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2675 if (parentSynZooName
.isInfraGeneric()){
2676 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2679 if (zooSynName
.isInfraSpecific()){
2680 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2681 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2683 if (parentSynZooName
.isInfraGeneric()){
2684 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2687 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2689 // Set the sourceReference
2690 potentialCombination
.setSec(sourceReference
);
2693 // Determine the idInSource
2694 String idInSourceSyn
= getIdInSource(syn
);
2696 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2697 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2698 inferredSynName
.addSource(originalSource
);
2699 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2700 potentialCombination
.addSource(originalSource
);
2703 return potentialCombination
;
2706 private Synonym
createInferredGenus(Taxon taxon
,
2707 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2708 String epithetOfTaxon
, String genusOfTaxon
,
2709 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2712 Synonym inferredGenus
;
2714 IZoologicalName inferredSynName
;
2715 synName
=syn
.getName();
2716 HibernateProxyHelper
.deproxy(syn
);
2718 // Determine the idInSource
2719 String idInSourceSyn
= getIdInSource(syn
);
2720 String idInSourceTaxon
= getIdInSource(taxon
);
2721 // Determine the sourceReference
2722 Reference sourceReference
= syn
.getSec();
2724 //logger.warn(sourceReference.getTitleCache());
2726 synName
= syn
.getName();
2727 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2728 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2729 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2730 synonymsEpithet.add(synSpeciesEpithetName);
2733 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2734 //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...
2736 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2737 if (zooParentName
.isInfraGeneric()){
2738 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2741 if (taxonName
.isSpecies()){
2742 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2744 if (taxonName
.isInfraSpecific()){
2745 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2746 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2749 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2751 // Set the sourceReference
2752 inferredGenus
.setSec(sourceReference
);
2754 // Add the original source
2755 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2756 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2757 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2758 inferredGenus
.addSource(originalSource
);
2760 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2761 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2762 inferredSynName
.addSource(originalSource
);
2763 originalSource
= null;
2766 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2767 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2768 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2769 inferredGenus
.addSource(originalSource
);
2771 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2772 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2773 inferredSynName
.addSource(originalSource
);
2774 originalSource
= null;
2777 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2779 return inferredGenus
;
2782 private Synonym
createInferredEpithets(Taxon taxon
,
2783 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2784 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2785 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2786 TaxonName parentName
, TaxonBase
<?
> syn
) {
2788 Synonym inferredEpithet
;
2790 IZoologicalName inferredSynName
;
2791 HibernateProxyHelper
.deproxy(syn
);
2793 // Determine the idInSource
2794 String idInSourceSyn
= getIdInSource(syn
);
2795 String idInSourceTaxon
= getIdInSource(taxon
);
2796 // Determine the sourceReference
2797 Reference sourceReference
= syn
.getSec();
2799 if (sourceReference
== null){
2800 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2801 sourceReference
= taxon
.getSec();
2804 synName
= syn
.getName();
2805 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2806 String synGenusName
= zooSynName
.getGenusOrUninomial();
2807 String synInfraGenericEpithet
= null;
2808 String synSpecificEpithet
= null;
2810 if (zooSynName
.getInfraGenericEpithet() != null){
2811 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2814 if (zooSynName
.isInfraSpecific()){
2815 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2818 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2819 synonymsGenus.put(synGenusName, idInSource);
2822 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2824 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2825 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2826 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2828 inferredSynName
.setGenusOrUninomial(synGenusName
);
2830 if (parentName
.isInfraGeneric()){
2831 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2833 if (taxonName
.isSpecies()){
2834 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2835 }else if (taxonName
.isInfraSpecific()){
2836 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2837 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2840 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2842 // Set the sourceReference
2843 inferredEpithet
.setSec(sourceReference
);
2845 /* Add the original source
2846 if (idInSource != null) {
2847 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2850 Reference citation = getCitation(syn);
2851 if (citation != null) {
2852 originalSource.setCitation(citation);
2853 inferredEpithet.addSource(originalSource);
2856 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2859 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2860 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2862 inferredEpithet
.addSource(originalSource
);
2864 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2865 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2867 inferredSynName
.addSource(originalSource
);
2869 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2871 return inferredEpithet
;
2875 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2876 * Very likely only useful for createInferredSynonyms().
2881 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2882 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2883 if (taxonName
== null) {
2884 taxonName
= zooHashMap
.get(uuid
);
2890 * Returns the idInSource for a given Synonym.
2893 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2894 String idInSource
= null;
2895 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2896 if (sources
.size() == 1) {
2897 IdentifiableSource source
= sources
.iterator().next();
2898 if (source
!= null) {
2899 idInSource
= source
.getIdInSource();
2901 } else if (sources
.size() > 1) {
2904 for (IdentifiableSource source
: sources
) {
2905 idInSource
+= source
.getIdInSource();
2906 if (count
< sources
.size()) {
2911 } else if (sources
.size() == 0){
2912 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2919 * Returns the citation for a given Synonym.
2922 private Reference
getCitation(Synonym syn
) {
2923 Reference citation
= null;
2924 Set
<IdentifiableSource
> sources
= syn
.getSources();
2925 if (sources
.size() == 1) {
2926 IdentifiableSource source
= sources
.iterator().next();
2927 if (source
!= null) {
2928 citation
= source
.getCitation();
2930 } else if (sources
.size() > 1) {
2931 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2938 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2939 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2941 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2942 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2943 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2945 return inferredSynonyms
;
2949 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2951 // TODO quickly implemented, create according dao !!!!
2952 Set
<TaxonNode
> nodes
= new HashSet
<>();
2953 Set
<Classification
> classifications
= new HashSet
<>();
2954 List
<Classification
> list
= new ArrayList
<>();
2956 if (taxonBase
== null) {
2960 taxonBase
= load(taxonBase
.getUuid());
2962 if (taxonBase
instanceof Taxon
) {
2963 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2965 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2967 nodes
.addAll(taxon
.getTaxonNodes());
2970 for (TaxonNode node
: nodes
) {
2971 classifications
.add(node
.getClassification());
2973 list
.addAll(classifications
);
2978 @Transactional(readOnly
= false)
2979 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2981 TaxonRelationshipType oldRelationshipType
,
2982 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2983 UpdateResult result
= new UpdateResult();
2984 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2985 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2986 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2988 // result.addUpdatedObject(fromTaxon);
2989 result
.addUpdatedObject(toTaxon
);
2990 result
.addUpdatedObject(result
.getCdmEntity());
2996 @Transactional(readOnly
= false)
2997 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2998 SynonymType synonymType
) throws DataChangeNoRollbackException
{
3000 UpdateResult result
= new UpdateResult();
3001 // Create new synonym using concept name
3002 TaxonName synonymName
= fromTaxon
.getName();
3004 // Remove concept relation from taxon
3005 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
3007 // Create a new synonym for the taxon
3009 if (synonymType
!= null
3010 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
3011 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
3012 toTaxon
.addHomotypicSynonym(synonym
);
3014 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
3016 //keep the publish flag
3017 synonym
.setPublish(fromTaxon
.isPublish());
3018 this.saveOrUpdate(toTaxon
);
3019 //TODO: configurator and classification
3020 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
3021 config
.setDeleteNameIfPossible(false);
3022 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
3023 result
.setCdmEntity(synonym
);
3024 result
.addUpdatedObject(toTaxon
);
3025 result
.addUpdatedObject(synonym
);
3030 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
3031 DeleteResult result
= new DeleteResult();
3032 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
3033 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
3034 if (taxonBase
instanceof Taxon
){
3035 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
3036 List
<String
> propertyPaths
= new ArrayList
<>();
3037 propertyPaths
.add("taxonNodes");
3038 Taxon taxon
= (Taxon
)load(taxonBaseUuid
, propertyPaths
);
3040 result
= isDeletableForTaxon(references
, taxonConfig
);
3042 if (taxonConfig
.isDeleteNameIfPossible()){
3043 if (taxonBase
.getName() != null){
3044 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), taxonConfig
.getNameDeletionConfig(), taxon
.getUuid());
3045 if (!nameResult
.isOk()){
3046 result
.addExceptions(nameResult
.getExceptions());
3052 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
3053 result
= isDeletableForSynonym(references
, synonymConfig
);
3054 if (synonymConfig
.isDeleteNameIfPossible()){
3055 DeleteResult nameResult
= nameService
.isDeletable(taxonBase
.getName().getUuid(), synonymConfig
.getNameDeletionConfig(), taxonBase
.getUuid());
3056 if (!nameResult
.isOk()){
3057 result
.addExceptions(nameResult
.getExceptions());
3065 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
3067 DeleteResult result
= new DeleteResult();
3068 for (CdmBase ref
: references
){
3069 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
3070 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
3071 result
.addException(new ReferencedObjectUndeletableException(message
));
3072 result
.addRelatedObject(ref
);
3081 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
3082 String message
= null;
3083 DeleteResult result
= new DeleteResult();
3084 for (CdmBase ref
: references
){
3085 if (!(ref
instanceof TaxonName
)){
3087 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
3088 message
= "The taxon can't be deleted as long as it has synonyms.";
3090 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
3091 message
= "The taxon can't be deleted as long as it has factual data.";
3094 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
3096 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
3098 if (ref
instanceof TaxonNode
&& config
.getClassificationUuid() != null && !config
.isDeleteInAllClassifications() && !((TaxonNode
)ref
).getClassification().getUuid().equals(config
.getClassificationUuid())){
3099 message
= "The taxon can't be deleted as long as it is used in more than one classification";
3102 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
3103 if (!config
.isDeleteMisappliedNames() &&
3104 (((TaxonRelationship
)ref
).getType().isMisappliedName())){
3105 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3107 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3110 if (ref
instanceof PolytomousKeyNode
){
3111 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3114 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3115 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3118 /* //PolytomousKeyNode
3119 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3120 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3125 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3126 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3130 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3131 message
= "Taxon can't be deleted as it is used in a determination event";
3134 if (message
!= null){
3135 result
.addException(new ReferencedObjectUndeletableException(message
));
3136 result
.addRelatedObject(ref
);
3145 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3146 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3148 //preliminary implementation
3150 Set
<Taxon
> taxa
= new HashSet
<>();
3151 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3152 if (taxonBase
== null){
3153 return new IncludedTaxaDTO();
3154 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3155 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3157 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3158 //TODO partial synonyms ??
3159 //TODO synonyms in general
3160 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3161 taxa
.add(syn
.getAcceptedTaxon());
3163 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3166 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3168 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3169 related
= makeRelatedIncluded(related
, result
, config
);
3176 * @param uncheckedTaxa
3177 * @param existingTaxa
3180 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3182 * @return the set of conceptually related taxa for further use
3184 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3187 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3188 for (Taxon taxon
: uncheckedTaxa
){
3189 taxonNodes
.addAll(taxon
.getTaxonNodes());
3192 Set
<Taxon
> children
= new HashSet
<>();
3193 if (! config
.onlyCongruent
){
3194 for (TaxonNode node
: taxonNodes
){
3195 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3196 for (TaxonNode child
: childNodes
){
3197 children
.add(child
.getTaxon());
3200 children
.remove(null); // just to be on the save side
3203 Iterator
<Taxon
> it
= children
.iterator();
3204 while(it
.hasNext()){
3205 UUID uuid
= it
.next().getUuid();
3206 if (existingTaxa
.contains(uuid
)){
3209 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3214 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3215 uncheckedAndChildren
.addAll(children
);
3217 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3220 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3225 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3226 * @return the set of these computed taxa
3228 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3229 Set
<Taxon
> result
= new HashSet
<>();
3231 for (Taxon taxon
: unchecked
){
3232 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3233 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3235 for (TaxonRelationship fromRel
: fromRelations
){
3236 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3239 TaxonRelationshipType fromRelType
= fromRel
.getType();
3240 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3241 !config
.onlyCongruent
&& (
3242 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3243 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3246 result
.add(fromRel
.getToTaxon());
3250 for (TaxonRelationship toRel
: toRelations
){
3251 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3254 TaxonRelationshipType fromRelType
= toRel
.getType();
3255 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3256 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3257 result
.add(toRel
.getFromTaxon());
3262 Iterator
<Taxon
> it
= result
.iterator();
3263 while(it
.hasNext()){
3264 UUID uuid
= it
.next().getUuid();
3265 if (existingTaxa
.contains(uuid
)){
3268 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3275 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3276 @SuppressWarnings("rawtypes")
3277 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3278 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3283 @Transactional(readOnly
= true)
3284 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3285 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3286 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3287 Integer pageNumber
, List
<String
> propertyPaths
) {
3288 if (subtreeFilter
== null){
3289 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3292 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3293 List
<Object
[]> daoResults
= new ArrayList
<>();
3294 if(numberOfResults
> 0) { // no point checking again
3295 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3296 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3299 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3300 for (Object
[] daoObj
: daoResults
){
3302 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3304 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3307 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3311 @Transactional(readOnly
= true)
3312 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3313 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3314 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3315 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3316 if (subtreeFilter
== null){
3317 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3320 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3321 List
<Object
[]> daoResults
= new ArrayList
<>();
3322 if(numberOfResults
> 0) { // no point checking again
3323 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3324 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3327 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3328 for (Object
[] daoObj
: daoResults
){
3330 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3332 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3335 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3339 @Transactional(readOnly
= false)
3340 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3341 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3342 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3344 UpdateResult result
= new UpdateResult();
3345 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3346 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3347 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3353 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3354 UpdateResult result
= new UpdateResult();
3356 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3357 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3358 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3359 //reload to avoid session conflicts
3360 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3362 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3363 if(description
.isProtectedTitleCache()){
3364 String separator
= "";
3365 if(!StringUtils
.isBlank(description
.getTitleCache())){
3368 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3370 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3371 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3372 description
.addAnnotation(annotation
);
3373 toTaxon
.addDescription(description
);
3374 dao
.saveOrUpdate(toTaxon
);
3375 dao
.saveOrUpdate(fromTaxon
);
3376 result
.addUpdatedObject(toTaxon
);
3377 result
.addUpdatedObject(fromTaxon
);
3385 @Transactional(readOnly
= false)
3386 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3387 UUID acceptedTaxonUuid
, boolean setNameInSource
) {
3388 TaxonBase
<?
> base
= this.load(synonymUUid
);
3389 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3390 base
= this.load(acceptedTaxonUuid
);
3391 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3393 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
, setNameInSource
);
3397 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3398 Set
<TaxonRelationshipType
> inversTypes
,
3399 Direction direction
, boolean groupMisapplications
,
3400 boolean includeUnpublished
,
3401 Integer pageSize
, Integer pageNumber
) {
3402 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3403 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3405 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3407 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3408 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3409 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3411 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3413 //TODO paging is difficult because misapplication string is an attribute
3415 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3416 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3417 // if(numberOfResults > 0) { // no point checking again
3418 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3421 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3424 List
<Language
> languages
= null;
3426 direction
= Direction
.relatedTo
;
3427 //TODO order hints, property path
3428 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3429 for (TaxonRelationship relation
: relations
){
3430 dto
.addRelation(relation
, direction
, languages
);
3434 direction
= Direction
.relatedFrom
;
3435 //TODO order hints, property path
3436 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3437 for (TaxonRelationship relation
: relations
){
3438 dto
.addRelation(relation
, direction
, languages
);
3441 if (groupMisapplications
){
3443 dto
.createMisapplicationString();