2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.api
.service
;
12 import java
.io
.IOException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.EnumSet
;
15 import java
.util
.HashMap
;
16 import java
.util
.HashSet
;
17 import java
.util
.Iterator
;
18 import java
.util
.List
;
21 import java
.util
.UUID
;
23 import javax
.persistence
.EntityNotFoundException
;
25 import org
.apache
.commons
.lang3
.StringUtils
;
26 import org
.apache
.log4j
.Logger
;
27 import org
.apache
.lucene
.queryparser
.classic
.ParseException
;
28 import org
.apache
.lucene
.search
.BooleanClause
.Occur
;
29 import org
.apache
.lucene
.search
.BooleanQuery
;
30 import org
.apache
.lucene
.search
.BooleanQuery
.Builder
;
31 import org
.apache
.lucene
.search
.Query
;
32 import org
.apache
.lucene
.search
.SortField
;
33 import org
.apache
.lucene
.search
.grouping
.TopGroups
;
34 import org
.apache
.lucene
.search
.join
.ScoreMode
;
35 import org
.apache
.lucene
.util
.BytesRef
;
36 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
37 import org
.springframework
.stereotype
.Service
;
38 import org
.springframework
.transaction
.annotation
.Transactional
;
40 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
41 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
42 import eu
.etaxonomy
.cdm
.api
.service
.config
.IncludedTaxonConfiguration
;
43 import eu
.etaxonomy
.cdm
.api
.service
.config
.MatchingTaxonConfigurator
;
44 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
45 import eu
.etaxonomy
.cdm
.api
.service
.config
.SynonymDeletionConfigurator
;
46 import eu
.etaxonomy
.cdm
.api
.service
.config
.TaxonDeletionConfigurator
;
47 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IdentifiedEntityDTO
;
48 import eu
.etaxonomy
.cdm
.api
.service
.dto
.IncludedTaxaDTO
;
49 import eu
.etaxonomy
.cdm
.api
.service
.dto
.MarkedEntityDTO
;
50 import eu
.etaxonomy
.cdm
.api
.service
.dto
.TaxonRelationshipsDTO
;
51 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
52 import eu
.etaxonomy
.cdm
.api
.service
.exception
.HomotypicalGroupChangeException
;
53 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
54 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
55 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.AbstractPagerImpl
;
56 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
57 import eu
.etaxonomy
.cdm
.api
.service
.search
.ILuceneIndexToolProvider
;
58 import eu
.etaxonomy
.cdm
.api
.service
.search
.ISearchResultBuilder
;
59 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearch
;
60 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneMultiSearchException
;
61 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneParseException
;
62 import eu
.etaxonomy
.cdm
.api
.service
.search
.LuceneSearch
;
63 import eu
.etaxonomy
.cdm
.api
.service
.search
.QueryFactory
;
64 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResult
;
65 import eu
.etaxonomy
.cdm
.api
.service
.search
.SearchResultBuilder
;
66 import eu
.etaxonomy
.cdm
.api
.service
.util
.TaxonRelationshipEdge
;
67 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
68 import eu
.etaxonomy
.cdm
.exception
.UnpublishedException
;
69 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
70 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
71 import eu
.etaxonomy
.cdm
.hibernate
.search
.GroupByTaxonClassBridge
;
72 import eu
.etaxonomy
.cdm
.model
.CdmBaseType
;
73 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
74 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
75 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
76 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
77 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableSource
;
78 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
79 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
80 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
.Direction
;
81 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
82 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
83 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
84 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementSource
;
85 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
86 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
87 import eu
.etaxonomy
.cdm
.model
.description
.IIdentificationKey
;
88 import eu
.etaxonomy
.cdm
.model
.description
.PolytomousKeyNode
;
89 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
90 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
91 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
92 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
93 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
94 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
95 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
96 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
97 import eu
.etaxonomy
.cdm
.model
.name
.IZoologicalName
;
98 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
99 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
100 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameFactory
;
101 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
102 import eu
.etaxonomy
.cdm
.model
.occurrence
.DeterminationEvent
;
103 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
104 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
105 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
106 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
107 import eu
.etaxonomy
.cdm
.model
.taxon
.HomotypicGroupTaxonComparator
;
108 import eu
.etaxonomy
.cdm
.model
.taxon
.ITaxonTreeNode
;
109 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
110 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymType
;
111 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
112 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
113 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
114 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationshipType
;
116 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
117 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.Restriction
;
118 import eu
.etaxonomy
.cdm
.persistence
.dao
.initializer
.AbstractBeanInitializer
;
119 import eu
.etaxonomy
.cdm
.persistence
.dao
.name
.ITaxonNameDao
;
120 import eu
.etaxonomy
.cdm
.persistence
.dao
.occurrence
.IOccurrenceDao
;
121 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.IClassificationDao
;
122 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonDao
;
123 import eu
.etaxonomy
.cdm
.persistence
.dao
.taxon
.ITaxonNodeDao
;
124 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
125 import eu
.etaxonomy
.cdm
.persistence
.query
.MatchMode
;
126 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
127 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
.SortOrder
;
128 import eu
.etaxonomy
.cdm
.persistence
.query
.TaxonTitleType
;
129 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
133 * @author a.kohlbecker
137 @Transactional(readOnly
= true)
138 public class TaxonServiceImpl
139 extends IdentifiableServiceBase
<TaxonBase
,ITaxonDao
>
140 implements ITaxonService
{
142 private static final Logger logger
= Logger
.getLogger(TaxonServiceImpl
.class);
144 public static final String POTENTIAL_COMBINATION_NAMESPACE
= "Potential combination";
146 public static final String INFERRED_EPITHET_NAMESPACE
= "Inferred epithet";
148 public static final String INFERRED_GENUS_NAMESPACE
= "Inferred genus";
151 private ITaxonNodeDao taxonNodeDao
;
154 private ITaxonNameDao nameDao
;
157 private INameService nameService
;
160 private IOccurrenceService occurrenceService
;
163 private ITaxonNodeService nodeService
;
166 private IDescriptionService descriptionService
;
169 // private IOrderedTermVocabularyDao orderedVocabularyDao;
172 private IOccurrenceDao occurrenceDao
;
175 private IClassificationDao classificationDao
;
178 private AbstractBeanInitializer beanInitializer
;
181 private ILuceneIndexToolProvider luceneIndexToolProvider
;
183 //************************ CONSTRUCTOR ****************************/
184 public TaxonServiceImpl(){
185 if (logger
.isDebugEnabled()) { logger
.debug("Load TaxonService Bean"); }
188 // ****************************** METHODS ********************************/
195 public TaxonBase
load(UUID uuid
, boolean includeUnpublished
, List
<String
> propertyPaths
) {
196 return dao
.load(uuid
, includeUnpublished
, propertyPaths
);
200 public List
<TaxonBase
> searchByName(String name
, boolean includeUnpublished
, Reference sec
) {
201 return dao
.getTaxaByName(name
, includeUnpublished
, sec
);
205 @Transactional(readOnly
= false)
206 public UpdateResult
swapSynonymAndAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean setNameInSource
){
207 UpdateResult result
= new UpdateResult();
208 TaxonName synonymName
= synonym
.getName();
209 synonymName
.removeTaxonBase(synonym
);
210 boolean sameHomotypicGroup
= synonymName
.getHomotypicalGroup().equals(acceptedTaxon
.getName().getHomotypicalGroup());
211 TaxonName taxonName
= acceptedTaxon
.getName();
212 taxonName
.removeTaxonBase(acceptedTaxon
);
213 Taxon newTaxon
= Taxon
.NewInstance(synonymName
, synonym
.getSec());
214 List
<Synonym
> synonyms
= new ArrayList
<>();
215 for (Synonym syn
: acceptedTaxon
.getSynonyms()){
218 for (Synonym syn
: synonyms
){
219 newTaxon
.addSynonym(syn
, syn
.getType());
222 //move all data to new taxon
223 //Move Taxon RelationShips to new Taxon
224 for(TaxonRelationship taxonRelationship
: acceptedTaxon
.getTaxonRelations()){
225 Taxon fromTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getFromTaxon());
226 Taxon toTaxon
= HibernateProxyHelper
.deproxy(taxonRelationship
.getToTaxon());
227 if (fromTaxon
== acceptedTaxon
){
228 newTaxon
.addTaxonRelation(taxonRelationship
.getToTaxon(), taxonRelationship
.getType(),
229 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
231 }else if(toTaxon
== acceptedTaxon
){
232 fromTaxon
.addTaxonRelation(newTaxon
, taxonRelationship
.getType(),
233 taxonRelationship
.getCitation(), taxonRelationship
.getCitationMicroReference());
234 saveOrUpdate(fromTaxon
);
237 logger
.warn("Taxon is not part of its own Taxonrelationship");
239 // Remove old relationships
241 fromTaxon
.removeTaxonRelation(taxonRelationship
);
242 toTaxon
.removeTaxonRelation(taxonRelationship
);
243 taxonRelationship
.setToTaxon(null);
244 taxonRelationship
.setFromTaxon(null);
248 //Move descriptions to new taxon
249 List
<TaxonDescription
> descriptions
= new ArrayList
<TaxonDescription
>( acceptedTaxon
.getDescriptions()); //to avoid concurrent modification errors (newAcceptedTaxon.addDescription() modifies also oldtaxon.descritpions())
250 for(TaxonDescription description
: descriptions
){
251 String message
= "Description copied from former accepted taxon: %s (Old title: %s)";
252 message
= String
.format(message
, acceptedTaxon
.getTitleCache(), description
.getTitleCache());
253 description
.setTitleCache(message
, true);
255 for (DescriptionElementBase element
: description
.getElements()){
256 for (DescriptionElementSource source
: element
.getSources()){
257 if (source
.getNameUsedInSource() == null){
258 source
.setNameUsedInSource(taxonName
);
263 //oldTaxon.removeDescription(description, false);
264 newTaxon
.addDescription(description
);
266 List
<TaxonNode
> nodes
= new ArrayList(acceptedTaxon
.getTaxonNodes());
267 for (TaxonNode node
: nodes
){
268 node
.setTaxon(newTaxon
);
269 acceptedTaxon
.removeTaxonNode(node
);
271 Synonym newSynonym
= Synonym
.NewInstance(taxonName
, acceptedTaxon
.getSec());
272 if (sameHomotypicGroup
){
273 newTaxon
.addSynonym(newSynonym
, SynonymType
.HOMOTYPIC_SYNONYM_OF());
275 newTaxon
.addSynonym(newSynonym
, SynonymType
.HETEROTYPIC_SYNONYM_OF());
277 // synonym.setName(taxonName);
278 // synonym.setTitleCache(null, false);
279 // synonym.getTitleCache();
280 // acceptedTaxon.setName(synonymName);
281 // acceptedTaxon.setTitleCache(null, false);
282 // acceptedTaxon.getTitleCache();
283 saveOrUpdate(newSynonym
);
284 saveOrUpdate(newTaxon
);
285 TaxonDeletionConfigurator conf
= new TaxonDeletionConfigurator();
286 conf
.setDeleteNameIfPossible(false);
287 SynonymDeletionConfigurator confSyn
= new SynonymDeletionConfigurator();
288 confSyn
.setDeleteNameIfPossible(false);
289 result
.setCdmEntity(newTaxon
);
291 DeleteResult deleteResult
= deleteTaxon(acceptedTaxon
.getUuid(), conf
, null);
292 deleteResult
.includeResult(deleteSynonym(synonym
, confSyn
));
293 result
.includeResult(deleteResult
);
296 // the accepted taxon needs a new uuid because the concept has changed
297 // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
298 //acceptedTaxon.setUuid(UUID.randomUUID());
303 @Transactional(readOnly
= false)
304 public UpdateResult
changeSynonymToAcceptedTaxon(Synonym synonym
, Taxon acceptedTaxon
, boolean deleteSynonym
) {
305 UpdateResult result
= new UpdateResult();
306 TaxonName acceptedName
= acceptedTaxon
.getName();
307 TaxonName synonymName
= synonym
.getName();
308 HomotypicalGroup synonymHomotypicGroup
= synonymName
.getHomotypicalGroup();
310 //check synonym is not homotypic
311 if (acceptedName
.getHomotypicalGroup().equals(synonymHomotypicGroup
)){
312 String message
= "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
313 result
.addException(new HomotypicalGroupChangeException(message
));
318 Taxon newAcceptedTaxon
= Taxon
.NewInstance(synonymName
, acceptedTaxon
.getSec());
319 dao
.save(newAcceptedTaxon
);
320 result
.setCdmEntity(newAcceptedTaxon
);
321 SynonymType relTypeForGroup
= SynonymType
.HOMOTYPIC_SYNONYM_OF();
322 List
<Synonym
> heteroSynonyms
= acceptedTaxon
.getSynonymsInGroup(synonymHomotypicGroup
);
324 for (Synonym heteroSynonym
: heteroSynonyms
){
325 if (synonym
.equals(heteroSynonym
)){
326 acceptedTaxon
.removeSynonym(heteroSynonym
, false);
328 //move synonyms in same homotypic group to new accepted taxon
329 newAcceptedTaxon
.addSynonym(heteroSynonym
, relTypeForGroup
);
332 dao
.saveOrUpdate(acceptedTaxon
);
333 result
.addUpdatedObject(acceptedTaxon
);
338 SynonymDeletionConfigurator config
= new SynonymDeletionConfigurator();
339 config
.setDeleteNameIfPossible(false);
340 this.deleteSynonym(synonym
, config
);
342 } catch (Exception e
) {
343 result
.addException(e
);
351 @Transactional(readOnly
= false)
352 public UpdateResult
changeSynonymToAcceptedTaxon(UUID synonymUuid
,
353 UUID acceptedTaxonUuid
,
354 UUID newParentNodeUuid
,
355 boolean deleteSynonym
) {
356 UpdateResult result
= new UpdateResult();
357 Synonym synonym
= CdmBase
.deproxy(dao
.load(synonymUuid
), Synonym
.class);
358 Taxon acceptedTaxon
= CdmBase
.deproxy(dao
.load(acceptedTaxonUuid
), Taxon
.class);
359 result
= changeSynonymToAcceptedTaxon(synonym
, acceptedTaxon
, deleteSynonym
);
360 Taxon newTaxon
= (Taxon
)result
.getCdmEntity();
361 TaxonNode newParentNode
= taxonNodeDao
.load(newParentNodeUuid
);
362 TaxonNode newNode
= newParentNode
.addChildTaxon(newTaxon
, null, null);
363 taxonNodeDao
.save(newNode
);
364 result
.addUpdatedObject(newTaxon
);
365 result
.addUpdatedObject(acceptedTaxon
);
366 result
.setCdmEntity(newNode
);
374 @Transactional(readOnly
= false)
375 public UpdateResult
changeSynonymToRelatedTaxon(UUID synonymUuid
,
377 TaxonRelationshipType taxonRelationshipType
,
379 String microcitation
){
381 UpdateResult result
= new UpdateResult();
382 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
383 Synonym synonym
= (Synonym
) dao
.load(synonymUuid
);
384 result
= changeSynonymToRelatedTaxon(synonym
, toTaxon
, taxonRelationshipType
, citation
, microcitation
);
385 Taxon relatedTaxon
= (Taxon
)result
.getCdmEntity();
386 // result.setCdmEntity(relatedTaxon);
387 result
.addUpdatedObject(relatedTaxon
);
388 result
.addUpdatedObject(toTaxon
);
393 @Transactional(readOnly
= false)
394 public UpdateResult
changeSynonymToRelatedTaxon(Synonym synonym
, Taxon toTaxon
, TaxonRelationshipType taxonRelationshipType
, Reference citation
, String microcitation
){
395 // Get name from synonym
396 if (synonym
== null){
400 UpdateResult result
= new UpdateResult();
402 TaxonName synonymName
= synonym
.getName();
404 /* // remove synonym from taxon
405 toTaxon.removeSynonym(synonym);
407 // Create a taxon with synonym name
408 Taxon fromTaxon
= Taxon
.NewInstance(synonymName
, null);
410 fromTaxon
.setAppendedPhrase(synonym
.getAppendedPhrase());
412 // Add taxon relation
413 fromTaxon
.addTaxonRelation(toTaxon
, taxonRelationshipType
, citation
, microcitation
);
414 result
.setCdmEntity(fromTaxon
);
415 // since we are swapping names, we have to detach the name from the synonym completely.
416 // Otherwise the synonym will still be in the list of typified names.
417 // synonym.getName().removeTaxonBase(synonym);
418 result
.includeResult(this.deleteSynonym(synonym
, null));
423 @Transactional(readOnly
= false)
425 public void changeHomotypicalGroupOfSynonym(Synonym synonym
, HomotypicalGroup newHomotypicalGroup
,
426 Taxon targetTaxon
, boolean setBasionymRelationIfApplicable
){
428 TaxonName synonymName
= synonym
.getName();
429 HomotypicalGroup oldHomotypicalGroup
= synonymName
.getHomotypicalGroup();
432 oldHomotypicalGroup
.removeTypifiedName(synonymName
, false);
433 newHomotypicalGroup
.addTypifiedName(synonymName
);
435 //remove existing basionym relationships
436 synonymName
.removeBasionyms();
438 //add basionym relationship
439 if (setBasionymRelationIfApplicable
){
440 Set
<TaxonName
> basionyms
= newHomotypicalGroup
.getBasionyms();
441 for (TaxonName basionym
: basionyms
){
442 synonymName
.addBasionym(basionym
);
446 //set synonym relationship correctly
447 Taxon acceptedTaxon
= synonym
.getAcceptedTaxon();
449 boolean hasNewTargetTaxon
= targetTaxon
!= null && !targetTaxon
.equals(acceptedTaxon
);
450 if (acceptedTaxon
!= null){
452 HomotypicalGroup acceptedGroup
= acceptedTaxon
.getHomotypicGroup();
453 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
454 SynonymType newRelationType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
455 synonym
.setType(newRelationType
);
457 if (hasNewTargetTaxon
){
458 acceptedTaxon
.removeSynonym(synonym
, false);
461 if (hasNewTargetTaxon
){
462 @SuppressWarnings("null")
463 HomotypicalGroup acceptedGroup
= targetTaxon
.getHomotypicGroup();
464 boolean isHomotypicToTaxon
= acceptedGroup
.equals(newHomotypicalGroup
);
465 SynonymType relType
= isHomotypicToTaxon? SynonymType
.HOMOTYPIC_SYNONYM_OF() : SynonymType
.HETEROTYPIC_SYNONYM_OF();
466 targetTaxon
.addSynonym(synonym
, relType
);
472 @Transactional(readOnly
= false)
473 public UpdateResult
updateCaches(Class
<?
extends TaxonBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<TaxonBase
> cacheStrategy
, IProgressMonitor monitor
) {
475 clazz
= TaxonBase
.class;
477 return super.updateCachesImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
482 protected void setDao(ITaxonDao dao
) {
488 public Pager
<TaxonBase
> findTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
489 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
491 List
<TaxonBase
> results
= new ArrayList
<>();
492 if(numberOfResults
> 0) { // no point checking again
493 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
496 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
500 public List
<TaxonBase
> listTaxaByName(Class
<?
extends TaxonBase
> clazz
, String uninomial
, String infragenericEpithet
, String specificEpithet
, String infraspecificEpithet
, String authorship
, Rank rank
, Integer pageSize
,Integer pageNumber
) {
501 long numberOfResults
= dao
.countTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, rank
);
503 List
<TaxonBase
> results
= new ArrayList
<>();
504 if(numberOfResults
> 0) { // no point checking again
505 results
= dao
.findTaxaByName(clazz
, uninomial
, infragenericEpithet
, specificEpithet
, infraspecificEpithet
, authorship
, rank
, pageSize
, pageNumber
);
512 public List
<TaxonRelationship
> listToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
513 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
514 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
516 List
<TaxonRelationship
> results
= new ArrayList
<>();
517 if(numberOfResults
> 0) { // no point checking again
518 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
524 public Pager
<TaxonRelationship
> pageToTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
525 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
526 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedTo
);
528 List
<TaxonRelationship
> results
= new ArrayList
<>();
529 if(numberOfResults
> 0) { // no point checking again
530 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedTo
);
532 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
536 public List
<TaxonRelationship
> listFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
537 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
538 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
540 List
<TaxonRelationship
> results
= new ArrayList
<>();
541 if(numberOfResults
> 0) { // no point checking again
542 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
548 public Pager
<TaxonRelationship
> pageFromTaxonRelationships(Taxon taxon
, TaxonRelationshipType type
,
549 boolean includeUnpublished
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
550 long numberOfResults
= dao
.countTaxonRelationships(taxon
, type
, includeUnpublished
, TaxonRelationship
.Direction
.relatedFrom
);
552 List
<TaxonRelationship
> results
= new ArrayList
<>();
553 if(numberOfResults
> 0) { // no point checking again
554 results
= dao
.getTaxonRelationships(taxon
, type
, includeUnpublished
, pageSize
, pageNumber
, orderHints
, propertyPaths
, TaxonRelationship
.Direction
.relatedFrom
);
556 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
560 public List
<TaxonRelationship
> listTaxonRelationships(Set
<TaxonRelationshipType
> types
,
561 Integer pageSize
, Integer pageStart
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
562 Long numberOfResults
= dao
.countTaxonRelationships(types
);
564 List
<TaxonRelationship
> results
= new ArrayList
<>();
565 if(numberOfResults
> 0) {
566 results
= dao
.getTaxonRelationships(types
, pageSize
, pageStart
, orderHints
, propertyPaths
);
572 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
573 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
574 return page(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, INCLUDE_UNPUBLISHED
);
578 public <S
extends TaxonBase
> Pager
<S
> page(Class
<S
> clazz
, List
<Restriction
<?
>> restrictions
, Integer pageSize
,
579 Integer pageIndex
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
, boolean includeUnpublished
) {
582 long resultSize
= dao
.count(clazz
, restrictions
);
583 if(AbstractPagerImpl
.hasResultsInRange(resultSize
, pageIndex
, pageSize
)){
584 records
= dao
.list(clazz
, restrictions
, pageSize
, pageIndex
, orderHints
, propertyPaths
, includeUnpublished
);
586 records
= new ArrayList
<>();
588 Pager
<S
> pager
= new DefaultPagerImpl
<>(pageIndex
, resultSize
, pageSize
, records
);
593 public Taxon
findAcceptedTaxonFor(UUID synonymUuid
, UUID classificationUuid
,
594 boolean includeUnpublished
, List
<String
> propertyPaths
) throws UnpublishedException
{
596 Synonym synonym
= null;
599 synonym
= (Synonym
) dao
.load(synonymUuid
);
600 checkPublished(synonym
, includeUnpublished
, "Synoym is unpublished.");
601 } catch (ClassCastException e
){
602 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid
+ " is not a Synonmy");
603 } catch (NullPointerException e
){
604 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid
);
607 Classification classificationFilter
= null;
608 if(classificationUuid
!= null){
610 classificationFilter
= classificationDao
.load(classificationUuid
);
611 } catch (NullPointerException e
){
612 //TODO not sure, why an NPE should be thrown in the above load method
613 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
615 if(classificationFilter
== null){
616 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid
);
620 long count
= dao
.countAcceptedTaxonFor(synonym
, classificationFilter
) ;
622 Taxon result
= dao
.acceptedTaxonFor(synonym
, classificationFilter
, propertyPaths
);
623 checkPublished(result
, includeUnpublished
, "Accepted taxon unpublished");
631 public Set
<Taxon
> listRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Integer maxDepth
,
632 boolean includeUnpublished
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
634 Set
<Taxon
> relatedTaxa
= collectRelatedTaxa(taxon
, includeRelationships
, new HashSet
<>(), includeUnpublished
, maxDepth
);
635 relatedTaxa
.remove(taxon
);
636 beanInitializer
.initializeAll(relatedTaxa
, propertyPaths
);
642 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
643 * <code>taxon</code> supplied as parameter.
646 * @param includeRelationships
648 * @param maxDepth can be <code>null</code> for infinite depth
651 private Set
<Taxon
> collectRelatedTaxa(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, Set
<Taxon
> taxa
,
652 boolean includeUnpublished
, Integer maxDepth
) {
658 if(includeRelationships
.isEmpty()){
662 if(maxDepth
!= null) {
665 if(logger
.isDebugEnabled()){
666 logger
.debug("collecting related taxa for " + taxon
+ " with maxDepth=" + maxDepth
);
668 List
<TaxonRelationship
> taxonRelationships
= dao
.getTaxonRelationships(taxon
,
669 (Set
<TaxonRelationshipType
>)null, includeUnpublished
, null, null, null, null, null);
670 for (TaxonRelationship taxRel
: taxonRelationships
) {
673 if (taxRel
.getToTaxon() == null || taxRel
.getFromTaxon() == null || taxRel
.getType() == null) {
676 // filter by includeRelationships
677 for (TaxonRelationshipEdge relationshipEdgeFilter
: includeRelationships
) {
678 if ( relationshipEdgeFilter
.getRelationshipTypes().equals(taxRel
.getType()) ) {
679 if (relationshipEdgeFilter
.getDirections().contains(Direction
.relatedTo
) && !taxa
.contains(taxRel
.getToTaxon())) {
680 if(logger
.isDebugEnabled()){
681 logger
.debug(maxDepth
+ ": " + taxon
.getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxRel
.getToTaxon().getTitleCache());
683 taxa
.add(taxRel
.getToTaxon());
684 if(maxDepth
== null || maxDepth
> 0) {
685 taxa
.addAll(collectRelatedTaxa(taxRel
.getToTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
688 if(relationshipEdgeFilter
.getDirections().contains(Direction
.relatedFrom
) && !taxa
.contains(taxRel
.getFromTaxon())) {
689 taxa
.add(taxRel
.getFromTaxon());
690 if(logger
.isDebugEnabled()){
691 logger
.debug(maxDepth
+ ": " +taxRel
.getFromTaxon().getTitleCache() + " --[" + taxRel
.getType().getLabel() + "]--> " + taxon
.getTitleCache() );
693 if(maxDepth
== null || maxDepth
> 0) {
694 taxa
.addAll(collectRelatedTaxa(taxRel
.getFromTaxon(), includeRelationships
, taxa
, includeUnpublished
, maxDepth
));
704 public Pager
<Synonym
> getSynonyms(Taxon taxon
, SynonymType type
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
705 Long numberOfResults
= dao
.countSynonyms(taxon
, type
);
707 List
<Synonym
> results
= new ArrayList
<>();
708 if(numberOfResults
> 0) { // no point checking again
709 results
= dao
.getSynonyms(taxon
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
712 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
716 public List
<List
<Synonym
>> getSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
717 List
<List
<Synonym
>> result
= new ArrayList
<>();
718 taxon
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
719 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
723 result
.add(taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
));
726 List
<HomotypicalGroup
> homotypicalGroups
= taxon
.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
727 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
728 result
.add(taxon
.getSynonymsInGroup(homotypicalGroup
, comparator
));
736 public List
<Synonym
> getHomotypicSynonymsByHomotypicGroup(Taxon taxon
, List
<String
> propertyPaths
){
737 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
738 HomotypicGroupTaxonComparator comparator
= new HomotypicGroupTaxonComparator(taxon
);
740 return t
.getHomotypicSynonymsByHomotypicGroup(comparator
);
744 public List
<List
<Synonym
>> getHeterotypicSynonymyGroups(Taxon taxon
, List
<String
> propertyPaths
){
745 Taxon t
= (Taxon
)dao
.load(taxon
.getUuid(), propertyPaths
);
746 List
<HomotypicalGroup
> homotypicalGroups
= t
.getHeterotypicSynonymyGroups();
747 List
<List
<Synonym
>> heterotypicSynonymyGroups
= new ArrayList
<>(homotypicalGroups
.size());
748 for(HomotypicalGroup homotypicalGroup
: homotypicalGroups
){
749 heterotypicSynonymyGroups
.add(t
.getSynonymsInGroup(homotypicalGroup
));
751 return heterotypicSynonymyGroups
;
755 public List
<UuidAndTitleCache
<?
extends IdentifiableEntity
>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config
){
757 if (config
.isDoSynonyms() || config
.isDoTaxa() || config
.isDoNamesWithoutTaxa() || config
.isDoTaxaByCommonNames()){
758 return dao
.getTaxaByNameForEditor(config
.isDoTaxa(), config
.isDoSynonyms(), config
.isDoNamesWithoutTaxa(),
759 config
.isDoMisappliedNames(), config
.isDoTaxaByCommonNames(), config
.isIncludeUnpublished(),
760 config
.getTitleSearchStringSqlized(), config
.getClassification(), config
.getSubtree(),
761 config
.getMatchMode(), config
.getNamedAreas(), config
.getOrder());
763 return new ArrayList
<>();
768 public Pager
<IdentifiableEntity
> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator
) {
770 List
<IdentifiableEntity
> results
= new ArrayList
<>();
771 long numberOfResults
= 0; // overall number of results (as opposed to number of results per page)
772 List
<TaxonBase
> taxa
= null;
775 long numberTaxaResults
= 0L;
777 List
<String
> propertyPath
= new ArrayList
<>();
778 if(configurator
.getTaxonPropertyPath() != null){
779 propertyPath
.addAll(configurator
.getTaxonPropertyPath());
782 if (configurator
.isDoMisappliedNames() || configurator
.isDoSynonyms() || configurator
.isDoTaxa() || configurator
.isDoTaxaByCommonNames()){
783 if(configurator
.getPageSize() != null){ // no point counting if we need all anyway
785 dao
.countTaxaByName(configurator
.isDoTaxa(),configurator
.isDoSynonyms(), configurator
.isDoMisappliedNames(),
786 configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(),
787 configurator
.getClassification(), configurator
.getSubtree(), configurator
.getMatchMode(),
788 configurator
.getNamedAreas(), configurator
.isIncludeUnpublished());
791 if(configurator
.getPageSize() == null || numberTaxaResults
> configurator
.getPageSize() * configurator
.getPageNumber()){ // no point checking again if less results
792 taxa
= dao
.getTaxaByName(configurator
.isDoTaxa(), configurator
.isDoSynonyms(),
793 configurator
.isDoMisappliedNames(), configurator
.isDoTaxaByCommonNames(), configurator
.isDoIncludeAuthors(),
794 configurator
.getTitleSearchStringSqlized(), configurator
.getClassification(), configurator
.getSubtree(),
795 configurator
.getMatchMode(), configurator
.getNamedAreas(), configurator
.isIncludeUnpublished(),
796 configurator
.getOrder(), configurator
.getPageSize(), configurator
.getPageNumber(), propertyPath
);
800 if (logger
.isDebugEnabled()) { logger
.debug(numberTaxaResults
+ " matching taxa counted"); }
803 results
.addAll(taxa
);
806 numberOfResults
+= numberTaxaResults
;
808 // Names without taxa
809 if (configurator
.isDoNamesWithoutTaxa()) {
810 int numberNameResults
= 0;
812 List
<TaxonName
> names
=
813 nameDao
.findByName(configurator
.isDoIncludeAuthors(), configurator
.getTitleSearchStringSqlized(), configurator
.getMatchMode(),
814 configurator
.getPageSize(), configurator
.getPageNumber(), null, configurator
.getTaxonNamePropertyPath());
815 if (logger
.isDebugEnabled()) { logger
.debug(names
.size() + " matching name(s) found"); }
816 if (names
.size() > 0) {
817 for (TaxonName taxonName
: names
) {
818 if (taxonName
.getTaxonBases().size() == 0) {
819 results
.add(taxonName
);
823 if (logger
.isDebugEnabled()) { logger
.debug(numberNameResults
+ " matching name(s) without taxa found"); }
824 numberOfResults
+= numberNameResults
;
828 return new DefaultPagerImpl
<> (configurator
.getPageNumber(), numberOfResults
, configurator
.getPageSize(), results
);
831 public List
<UuidAndTitleCache
<TaxonBase
>> getTaxonUuidAndTitleCache(Integer limit
, String pattern
){
832 return dao
.getUuidAndTitleCache(limit
, pattern
);
836 public List
<Media
> listTaxonDescriptionMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
, boolean limitToGalleries
, List
<String
> propertyPath
){
837 return listMedia(taxon
, includeRelationships
, limitToGalleries
, true, false, false, propertyPath
);
841 public List
<Media
> listMedia(Taxon taxon
, Set
<TaxonRelationshipEdge
> includeRelationships
,
842 Boolean limitToGalleries
, Boolean includeTaxonDescriptions
, Boolean includeOccurrences
,
843 Boolean includeTaxonNameDescriptions
, List
<String
> propertyPath
) {
846 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
848 // logger.setLevel(Level.TRACE);
849 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
851 logger
.trace("listMedia() - START");
853 Set
<Taxon
> taxa
= new HashSet
<>();
854 List
<Media
> taxonMedia
= new ArrayList
<>();
855 List
<Media
> nonImageGalleryImages
= new ArrayList
<>();
857 if (limitToGalleries
== null) {
858 limitToGalleries
= false;
861 // --- resolve related taxa
862 if (includeRelationships
!= null && ! includeRelationships
.isEmpty()) {
863 logger
.trace("listMedia() - resolve related taxa");
864 taxa
= listRelatedTaxa(taxon
, includeRelationships
, null, includeUnpublished
, null, null, null);
867 taxa
.add((Taxon
) dao
.load(taxon
.getUuid()));
869 if(includeTaxonDescriptions
!= null && includeTaxonDescriptions
){
870 logger
.trace("listMedia() - includeTaxonDescriptions");
871 List
<TaxonDescription
> taxonDescriptions
= new ArrayList
<>();
872 // --- TaxonDescriptions
873 for (Taxon t
: taxa
) {
874 taxonDescriptions
.addAll(descriptionService
.listTaxonDescriptions(t
, null, null, null, null, propertyPath
));
876 for (TaxonDescription taxonDescription
: taxonDescriptions
) {
877 if (!limitToGalleries
|| taxonDescription
.isImageGallery()) {
878 for (DescriptionElementBase element
: taxonDescription
.getElements()) {
879 for (Media media
: element
.getMedia()) {
880 if(taxonDescription
.isImageGallery()){
881 taxonMedia
.add(media
);
884 nonImageGalleryImages
.add(media
);
890 //put images from image gallery first (#3242)
891 taxonMedia
.addAll(nonImageGalleryImages
);
895 if(includeOccurrences
!= null && includeOccurrences
) {
896 logger
.trace("listMedia() - includeOccurrences");
897 Set
<SpecimenOrObservationBase
> specimensOrObservations
= new HashSet
<>();
899 for (Taxon t
: taxa
) {
900 specimensOrObservations
.addAll(occurrenceDao
.listByAssociatedTaxon(null, t
, null, null, null, null));
902 for (SpecimenOrObservationBase
<?
> occurrence
: specimensOrObservations
) {
904 // direct media removed from specimen #3597
905 // taxonMedia.addAll(occurrence.getMedia());
907 // SpecimenDescriptions
908 Set
<SpecimenDescription
> specimenDescriptions
= occurrence
.getSpecimenDescriptions();
909 for (DescriptionBase
<?
> specimenDescription
: specimenDescriptions
) {
910 if (!limitToGalleries
|| specimenDescription
.isImageGallery()) {
911 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
912 for (DescriptionElementBase element
: elements
) {
913 for (Media media
: element
.getMedia()) {
914 taxonMedia
.add(media
);
920 if (occurrence
.isInstanceOf(DerivedUnit
.class)) {
921 DerivedUnit derivedUnit
= CdmBase
.deproxy(occurrence
, DerivedUnit
.class);
923 //TODO why may collections have media attached? #
924 if (derivedUnit
.getCollection() != null){
925 taxonMedia
.addAll(derivedUnit
.getCollection().getMedia());
929 taxonMedia
.addAll(occurrenceService
.getMediainHierarchy(occurrence
, null, null, propertyPath
).getRecords());
933 if(includeTaxonNameDescriptions
!= null && includeTaxonNameDescriptions
) {
934 logger
.trace("listMedia() - includeTaxonNameDescriptions");
935 // --- TaxonNameDescription
936 Set
<TaxonNameDescription
> nameDescriptions
= new HashSet
<>();
937 for (Taxon t
: taxa
) {
938 nameDescriptions
.addAll(t
.getName().getDescriptions());
940 for(TaxonNameDescription nameDescription
: nameDescriptions
){
941 if (!limitToGalleries
|| nameDescription
.isImageGallery()) {
942 Set
<DescriptionElementBase
> elements
= nameDescription
.getElements();
943 for (DescriptionElementBase element
: elements
) {
944 for (Media media
: element
.getMedia()) {
945 taxonMedia
.add(media
);
953 logger
.trace("listMedia() - initialize");
954 beanInitializer
.initializeAll(taxonMedia
, propertyPath
);
956 logger
.trace("listMedia() - END");
962 public List
<TaxonBase
> findTaxaByID(Set
<Integer
> listOfIDs
) {
963 return this.dao
.loadList(listOfIDs
, null, null);
967 public TaxonBase
findTaxonByUuid(UUID uuid
, List
<String
> propertyPaths
){
968 return this.dao
.findByUuid(uuid
, null ,propertyPaths
);
972 public long countSynonyms(boolean onlyAttachedToTaxon
){
973 return this.dao
.countSynonyms(onlyAttachedToTaxon
);
977 public List
<TaxonName
> findIdenticalTaxonNames(List
<String
> propertyPath
) {
978 return this.dao
.findIdenticalTaxonNames(propertyPath
);
982 @Transactional(readOnly
=false)
983 public DeleteResult
deleteTaxon(UUID taxonUUID
, TaxonDeletionConfigurator config
, UUID classificationUuid
) {
986 config
= new TaxonDeletionConfigurator();
988 Taxon taxon
= (Taxon
)dao
.load(taxonUUID
);
989 DeleteResult result
= new DeleteResult();
992 result
.addException(new Exception ("The taxon was already deleted."));
995 taxon
= HibernateProxyHelper
.deproxy(taxon
);
996 Classification classification
= HibernateProxyHelper
.deproxy(classificationDao
.load(classificationUuid
), Classification
.class);
997 result
= isDeletable(taxonUUID
, config
);
1000 // --- DeleteSynonymRelations
1001 if (config
.isDeleteSynonymRelations()){
1002 boolean removeSynonymNameFromHomotypicalGroup
= false;
1003 // use tmp Set to avoid concurrent modification
1004 Set
<Synonym
> synsToDelete
= new HashSet
<>();
1005 synsToDelete
.addAll(taxon
.getSynonyms());
1006 for (Synonym synonym
: synsToDelete
){
1007 taxon
.removeSynonym(synonym
, removeSynonymNameFromHomotypicalGroup
);
1009 // --- DeleteSynonymsIfPossible
1010 if (config
.isDeleteSynonymsIfPossible()){
1012 boolean newHomotypicGroupIfNeeded
= true;
1013 SynonymDeletionConfigurator synConfig
= new SynonymDeletionConfigurator();
1014 result
.includeResult(deleteSynonym(synonym
, synConfig
));
1019 // --- DeleteTaxonRelationships
1020 if (! config
.isDeleteTaxonRelationships()){
1021 if (taxon
.getTaxonRelations().size() > 0){
1023 result
.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1024 "Remove taxon from all relations to other taxa prior to deletion."));
1027 TaxonDeletionConfigurator configRelTaxon
= new TaxonDeletionConfigurator();
1028 configRelTaxon
.setDeleteTaxonNodes(false);
1029 configRelTaxon
.setDeleteConceptRelationships(true);
1031 for (TaxonRelationship taxRel
: taxon
.getTaxonRelations()){
1032 if (config
.isDeleteMisappliedNamesAndInvalidDesignations()
1033 && taxRel
.getType().isMisappliedNameOrInvalidDesignation()
1034 && taxon
.equals(taxRel
.getToTaxon())){
1035 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), config
, classificationUuid
);
1036 } else if (config
.isDeleteConceptRelationships() && taxRel
.getType().isConceptRelationship()){
1038 if (taxon
.equals(taxRel
.getToTaxon()) && isDeletable(taxRel
.getFromTaxon().getUuid(), configRelTaxon
).isOk()){
1039 this.deleteTaxon(taxRel
.getFromTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1040 }else if (isDeletable(taxRel
.getToTaxon().getUuid(), configRelTaxon
).isOk()){
1041 this.deleteTaxon(taxRel
.getToTaxon().getUuid(), configRelTaxon
, classificationUuid
);
1044 taxon
.removeTaxonRelation(taxRel
);
1049 if (config
.isDeleteDescriptions()){
1050 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
1051 List
<TaxonDescription
> removeDescriptions
= new ArrayList
<>();
1052 for (TaxonDescription desc
: descriptions
){
1053 //TODO use description delete configurator ?
1054 //FIXME check if description is ALWAYS deletable
1055 if (desc
.getDescribedSpecimenOrObservation() != null){
1057 result
.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1058 " which also describes specimens or observations"));
1061 removeDescriptions
.add(desc
);
1066 for (TaxonDescription desc
: removeDescriptions
){
1067 taxon
.removeDescription(desc
);
1068 descriptionService
.delete(desc
);
1076 if (! config
.isDeleteTaxonNodes() || (!config
.isDeleteInAllClassifications() && classification
== null && taxon
.getTaxonNodes().size() > 1)){
1077 result
.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1079 if (taxon
.getTaxonNodes().size() != 0){
1080 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
1081 Iterator
<TaxonNode
> iterator
= nodes
.iterator();
1082 TaxonNode node
= null;
1083 boolean deleteChildren
;
1084 if (config
.getTaxonNodeConfig().getChildHandling().equals(ChildHandling
.DELETE
)){
1085 deleteChildren
= true;
1087 deleteChildren
= false;
1089 boolean success
= true;
1090 if (!config
.isDeleteInAllClassifications() && !(classification
== null)){
1091 while (iterator
.hasNext()){
1092 node
= iterator
.next();
1093 if (node
.getClassification().equals(classification
)){
1099 HibernateProxyHelper
.deproxy(node
, TaxonNode
.class);
1100 success
=taxon
.removeTaxonNode(node
, deleteChildren
);
1101 nodeService
.delete(node
);
1102 result
.addDeletedObject(node
);
1105 result
.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1107 } else if (config
.isDeleteInAllClassifications()){
1108 List
<TaxonNode
> nodesList
= new ArrayList
<>();
1109 nodesList
.addAll(taxon
.getTaxonNodes());
1110 for (ITaxonTreeNode treeNode
: nodesList
){
1111 TaxonNode taxonNode
= (TaxonNode
) treeNode
;
1112 if(!deleteChildren
){
1113 Object
[] childNodes
= taxonNode
.getChildNodes().toArray();
1114 for (Object childNode
: childNodes
){
1115 TaxonNode childNodeCast
= (TaxonNode
) childNode
;
1116 taxonNode
.getParent().addChildNode(childNodeCast
, childNodeCast
.getReference(), childNodeCast
.getMicroReference());
1120 config
.getTaxonNodeConfig().setDeleteElement(false);
1121 DeleteResult resultNodes
= nodeService
.deleteTaxonNodes(nodesList
, config
);
1122 if (!resultNodes
.isOk()){
1123 result
.addExceptions(resultNodes
.getExceptions());
1124 result
.setStatus(resultNodes
.getStatus());
1126 result
.addUpdatedObjects(resultNodes
.getUpdatedObjects());
1131 result
.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1135 TaxonName name
= taxon
.getName();
1136 taxon
.setName(null);
1137 this.saveOrUpdate(taxon
);
1139 if ((taxon
.getTaxonNodes() == null || taxon
.getTaxonNodes().size()== 0) && result
.isOk()){
1142 result
.addDeletedObject(taxon
);
1143 }catch(Exception e
){
1144 result
.addException(e
);
1149 result
.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1153 if (config
.isDeleteNameIfPossible() && result
.isOk()){
1154 DeleteResult nameResult
= new DeleteResult();
1155 //remove name if possible (and required)
1157 nameResult
= nameService
.delete(name
.getUuid(), config
.getNameDeletionConfig());
1159 if (nameResult
.isError() || nameResult
.isAbort()){
1160 result
.addRelatedObject(name
);
1161 result
.addExceptions(nameResult
.getExceptions());
1163 result
.includeResult(nameResult
);
1173 @Transactional(readOnly
= false)
1174 public DeleteResult
delete(UUID synUUID
){
1175 Synonym syn
= (Synonym
)dao
.load(synUUID
);
1176 return this.deleteSynonym(syn
, null);
1180 @Transactional(readOnly
= false)
1181 public DeleteResult
deleteSynonym(UUID synonymUuid
, SynonymDeletionConfigurator config
) {
1182 return deleteSynonym((Synonym
)dao
.load(synonymUuid
), config
);
1188 @Transactional(readOnly
= false)
1189 public DeleteResult
deleteSynonym(Synonym synonym
, SynonymDeletionConfigurator config
) {
1190 DeleteResult result
= new DeleteResult();
1191 if (synonym
== null){
1193 result
.addException(new Exception("The synonym was already deleted."));
1197 if (config
== null){
1198 config
= new SynonymDeletionConfigurator();
1201 result
= isDeletable(synonym
.getUuid(), config
);
1205 synonym
= HibernateProxyHelper
.deproxy(this.load(synonym
.getUuid()), Synonym
.class);
1208 Taxon accTaxon
= synonym
.getAcceptedTaxon();
1210 if (accTaxon
!= null){
1211 accTaxon
= HibernateProxyHelper
.deproxy(accTaxon
, Taxon
.class);
1212 accTaxon
.removeSynonym(synonym
, false);
1213 this.saveOrUpdate(accTaxon
);
1214 result
.addUpdatedObject(accTaxon
);
1216 this.saveOrUpdate(synonym
);
1220 TaxonName name
= synonym
.getName();
1221 synonym
.setName(null);
1223 dao
.delete(synonym
);
1224 result
.addDeletedObject(synonym
);
1226 //remove name if possible (and required)
1227 if (name
!= null && config
.isDeleteNameIfPossible()){
1229 DeleteResult nameDeleteResult
= nameService
.delete(name
, config
.getNameDeletionConfig());
1230 if (nameDeleteResult
.isAbort() || nameDeleteResult
.isError()){
1231 result
.addExceptions(nameDeleteResult
.getExceptions());
1232 result
.addRelatedObject(name
);
1234 result
.addDeletedObject(name
);
1243 public List
<TaxonName
> findIdenticalTaxonNameIds(List
<String
> propertyPath
) {
1245 return this.dao
.findIdenticalNamesNew(propertyPath
);
1250 public Taxon
findBestMatchingTaxon(String taxonName
) {
1251 MatchingTaxonConfigurator config
= MatchingTaxonConfigurator
.NewInstance();
1252 config
.setTaxonNameTitle(taxonName
);
1253 return findBestMatchingTaxon(config
);
1257 public Taxon
findBestMatchingTaxon(MatchingTaxonConfigurator config
) {
1259 Taxon bestCandidate
= null;
1261 // 1. search for accepted taxa
1262 List
<TaxonBase
> taxonList
= dao
.findByNameTitleCache(true, false, config
.isIncludeUnpublished(),
1263 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1264 boolean bestCandidateMatchesSecUuid
= false;
1265 boolean bestCandidateIsInClassification
= false;
1266 int countEqualCandidates
= 0;
1267 for(TaxonBase
<?
> taxonBaseCandidate
: taxonList
){
1268 if(taxonBaseCandidate
instanceof Taxon
){
1269 Taxon newCanditate
= CdmBase
.deproxy(taxonBaseCandidate
, Taxon
.class);
1270 boolean newCandidateMatchesSecUuid
= isMatchesSecUuid(newCanditate
, config
);
1271 if (! newCandidateMatchesSecUuid
&& config
.isOnlyMatchingSecUuid() ){
1273 }else if(newCandidateMatchesSecUuid
&& ! bestCandidateMatchesSecUuid
){
1274 bestCandidate
= newCanditate
;
1275 countEqualCandidates
= 1;
1276 bestCandidateMatchesSecUuid
= true;
1280 boolean newCandidateInClassification
= isInClassification(newCanditate
, config
);
1281 if (! newCandidateInClassification
&& config
.isOnlyMatchingClassificationUuid()){
1283 }else if (newCandidateInClassification
&& ! bestCandidateIsInClassification
){
1284 bestCandidate
= newCanditate
;
1285 countEqualCandidates
= 1;
1286 bestCandidateIsInClassification
= true;
1289 if (bestCandidate
== null){
1290 bestCandidate
= newCanditate
;
1291 countEqualCandidates
= 1;
1295 }else{ //not Taxon.class
1298 countEqualCandidates
++;
1301 if (bestCandidate
!= null){
1302 if(countEqualCandidates
> 1){
1303 logger
.info(countEqualCandidates
+ " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate
.getTitleCache());
1304 return bestCandidate
;
1306 logger
.info("using accepted Taxon: " + bestCandidate
.getTitleCache());
1307 return bestCandidate
;
1312 // 2. search for synonyms
1313 if (config
.isIncludeSynonyms()){
1314 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, config
.isIncludeUnpublished(),
1315 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1316 for(TaxonBase taxonBase
: synonymList
){
1317 if(taxonBase
instanceof Synonym
){
1318 Synonym synonym
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
1319 bestCandidate
= synonym
.getAcceptedTaxon();
1320 if(bestCandidate
!= null){
1321 logger
.info("using accepted Taxon " + bestCandidate
.getTitleCache() + " for synonym " + taxonBase
.getTitleCache());
1322 return bestCandidate
;
1324 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1329 } catch (Exception e
){
1331 e
.printStackTrace();
1334 return bestCandidate
;
1337 private boolean isInClassification(Taxon taxon
, MatchingTaxonConfigurator config
) {
1338 UUID configClassificationUuid
= config
.getClassificationUuid();
1339 if (configClassificationUuid
== null){
1342 for (TaxonNode node
: taxon
.getTaxonNodes()){
1343 UUID classUuid
= node
.getClassification().getUuid();
1344 if (configClassificationUuid
.equals(classUuid
)){
1351 private boolean isMatchesSecUuid(Taxon taxon
, MatchingTaxonConfigurator config
) {
1352 UUID configSecUuid
= config
.getSecUuid();
1353 if (configSecUuid
== null){
1356 UUID taxonSecUuid
= (taxon
.getSec() == null)?
null : taxon
.getSec().getUuid();
1357 return configSecUuid
.equals(taxonSecUuid
);
1361 public Synonym
findBestMatchingSynonym(String taxonName
, boolean includeUnpublished
) {
1362 List
<TaxonBase
> synonymList
= dao
.findByNameTitleCache(false, true, includeUnpublished
, taxonName
, null, null, MatchMode
.EXACT
, null, null, 0, null, null);
1363 if(! synonymList
.isEmpty()){
1364 Synonym result
= CdmBase
.deproxy(synonymList
.iterator().next(), Synonym
.class);
1365 if(synonymList
.size() == 1){
1366 logger
.info(synonymList
.size() + " Synonym found " + result
.getTitleCache() );
1369 logger
.info("Several matching synonyms found. Using first: " + result
.getTitleCache());
1377 @Transactional(readOnly
= false)
1378 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1380 boolean moveHomotypicGroup
,
1381 SynonymType newSynonymType
) throws HomotypicalGroupChangeException
{
1382 return moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
,
1384 oldSynonym
.getSec(),
1385 oldSynonym
.getSecMicroReference(),
1390 @Transactional(readOnly
= false)
1391 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
,
1393 boolean moveHomotypicGroup
,
1394 SynonymType newSynonymType
,
1395 Reference newSecundum
,
1396 String newSecundumDetail
,
1397 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
1399 Synonym synonym
= CdmBase
.deproxy(dao
.load(oldSynonym
.getUuid()), Synonym
.class);
1400 Taxon oldTaxon
= CdmBase
.deproxy(dao
.load(synonym
.getAcceptedTaxon().getUuid()), Taxon
.class);
1401 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1402 TaxonName synonymName
= synonym
.getName();
1403 TaxonName fromTaxonName
= oldTaxon
.getName();
1404 //set default relationship type
1405 if (newSynonymType
== null){
1406 newSynonymType
= SynonymType
.HETEROTYPIC_SYNONYM_OF();
1408 boolean newRelTypeIsHomotypic
= newSynonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF());
1410 HomotypicalGroup homotypicGroup
= synonymName
.getHomotypicalGroup();
1411 int hgSize
= homotypicGroup
.getTypifiedNames().size();
1412 boolean isSingleInGroup
= !(hgSize
> 1);
1414 if (! isSingleInGroup
){
1415 boolean isHomotypicToAccepted
= synonymName
.isHomotypic(fromTaxonName
);
1416 boolean hasHomotypicSynonymRelatives
= isHomotypicToAccepted ? hgSize
> 2 : hgSize
> 1;
1417 if (isHomotypicToAccepted
){
1418 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.";
1419 String homotypicRelatives
= hasHomotypicSynonymRelatives ?
" and other synonym(s)":"";
1420 message
= String
.format(message
, homotypicRelatives
);
1421 throw new HomotypicalGroupChangeException(message
);
1423 if (! moveHomotypicGroup
){
1424 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.";
1425 throw new HomotypicalGroupChangeException(message
);
1428 moveHomotypicGroup
= true; //single synonym always allows to moveCompleteGroup
1430 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1432 UpdateResult result
= new UpdateResult();
1433 //move all synonyms to new taxon
1434 List
<Synonym
> homotypicSynonyms
= oldTaxon
.getSynonymsInGroup(homotypicGroup
);
1435 for (Synonym synRelation
: homotypicSynonyms
){
1437 newTaxon
= HibernateProxyHelper
.deproxy(newTaxon
, Taxon
.class);
1438 oldTaxon
= HibernateProxyHelper
.deproxy(oldTaxon
, Taxon
.class);
1439 oldTaxon
.removeSynonym(synRelation
, false);
1440 newTaxon
.addSynonym(synRelation
, newSynonymType
);
1442 if (newSecundum
!= null || !keepSecundumIfUndefined
){
1443 synRelation
.setSec(newSecundum
);
1445 if (newSecundumDetail
!= null || !keepSecundumIfUndefined
){
1446 synRelation
.setSecMicroReference(newSecundumDetail
);
1449 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1450 if (!synRelation
.equals(oldSynonym
)){
1455 result
.addUpdatedObject(oldTaxon
);
1456 result
.addUpdatedObject(newTaxon
);
1457 saveOrUpdate(oldTaxon
);
1458 saveOrUpdate(newTaxon
);
1464 public <T
extends TaxonBase
> List
<UuidAndTitleCache
<T
>> getUuidAndTitleCache(Class
<T
> clazz
, Integer limit
, String pattern
) {
1465 return dao
.getUuidAndTitleCache(clazz
, limit
, pattern
);
1469 public Pager
<SearchResult
<TaxonBase
>> findByFullText(
1470 Class
<?
extends TaxonBase
> clazz
, String queryString
,
1471 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1472 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
,
1473 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1475 LuceneSearch luceneSearch
= prepareFindByFullTextSearch(clazz
, queryString
, classification
, subtree
,
1476 null, includeUnpublished
, languages
, highlightFragments
, null);
1478 // --- execute search
1479 TopGroups
<BytesRef
> topDocsResultSet
;
1481 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1482 } catch (ParseException e
) {
1483 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1484 luceneParseException
.setStackTrace(e
.getStackTrace());
1485 throw luceneParseException
;
1488 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1489 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1491 // --- initialize taxa, thighlight matches ....
1492 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1493 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1494 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1496 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1497 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1501 public Pager
<SearchResult
<TaxonBase
>> findByDistribution(List
<NamedArea
> areaFilter
, List
<PresenceAbsenceTerm
> statusFilter
,
1502 Classification classification
, TaxonNode subtree
,
1503 Integer pageSize
, Integer pageNumber
,
1504 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
1506 LuceneSearch luceneSearch
= prepareByDistributionSearch(areaFilter
, statusFilter
, classification
, subtree
);
1508 // --- execute search
1509 TopGroups
<BytesRef
> topDocsResultSet
;
1511 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
1512 } catch (ParseException e
) {
1513 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1514 luceneParseException
.setStackTrace(e
.getStackTrace());
1515 throw luceneParseException
;
1518 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1519 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1521 // --- initialize taxa, thighlight matches ....
1522 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
1523 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1524 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1526 long totalHits
= topDocsResultSet
!= null ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1527 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
1532 * @param queryString
1533 * @param classification
1534 * @param includeUnpublished
1536 * @param highlightFragments
1537 * @param sortFields TODO
1538 * @param directorySelectClass
1541 protected LuceneSearch
prepareFindByFullTextSearch(Class
<?
extends CdmBase
> clazz
, String queryString
,
1542 Classification classification
, TaxonNode subtree
, String className
, boolean includeUnpublished
, List
<Language
> languages
,
1543 boolean highlightFragments
, SortField
[] sortFields
) {
1545 Builder finalQueryBuilder
= new Builder();
1546 Builder textQueryBuilder
= new Builder();
1548 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1549 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1551 if(sortFields
== null){
1552 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1554 luceneSearch
.setSortFields(sortFields
);
1556 // ---- search criteria
1557 luceneSearch
.setCdmTypRestriction(clazz
);
1559 if(!StringUtils
.isEmpty(queryString
) && !queryString
.equals("*") && !queryString
.equals("?") ) {
1560 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
1561 textQueryBuilder
.add(taxonBaseQueryFactory
.newDefinedTermQuery("name.rank", queryString
, languages
), Occur
.SHOULD
);
1563 if(className
!= null){
1564 textQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery("classInfo.name", className
, false), Occur
.MUST
);
1567 BooleanQuery textQuery
= textQueryBuilder
.build();
1568 if(textQuery
.clauses().size() > 0) {
1569 finalQueryBuilder
.add(textQuery
, Occur
.MUST
);
1572 if(classification
!= null){
1573 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1575 if(subtree
!= null){
1576 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1578 if(!includeUnpublished
) {
1579 String accPublishParam
= TaxonBase
.ACC_TAXON_BRIDGE_PREFIX
+ AcceptedTaxonBridge
.DOC_KEY_PUBLISH_SUFFIX
;
1580 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(accPublishParam
, true), Occur
.MUST
);
1581 finalQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1584 luceneSearch
.setQuery(finalQueryBuilder
.build());
1586 if(highlightFragments
){
1587 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1589 return luceneSearch
;
1593 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1594 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1595 * drawback of requiring to do the join an indexing time.
1596 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1598 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1600 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id </li>
1601 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id </li>
1603 * @param queryString
1604 * @param classification
1606 * @param highlightFragments
1607 * @param sortFields TODO
1610 * @throws IOException
1612 protected LuceneSearch
prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge
, String queryString
,
1613 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
,
1614 boolean highlightFragments
, SortField
[] sortFields
) throws IOException
{
1617 String queryTermField
;
1618 String toField
= "id"; // TaxonBase.uuid
1619 String publishField
;
1620 String publishFieldInvers
;
1622 if(edge
.isBidirectional()){
1623 throw new RuntimeException("Bidirectional joining not supported!");
1626 fromField
= "relatedFrom.id";
1627 queryTermField
= "relatedFrom.titleCache";
1628 publishField
= "relatedFrom.publish";
1629 publishFieldInvers
= "relatedTo.publish";
1630 } else if(edge
.isInvers()) {
1631 fromField
= "relatedTo.id";
1632 queryTermField
= "relatedTo.titleCache";
1633 publishField
= "relatedTo.publish";
1634 publishFieldInvers
= "relatedFrom.publish";
1636 throw new RuntimeException("Invalid direction: " + edge
.getDirections());
1639 Builder finalQueryBuilder
= new Builder();
1641 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, TaxonBase
.class);
1642 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1644 Builder joinFromQueryBuilder
= new Builder();
1645 if(!StringUtils
.isEmpty(queryString
)){
1646 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(queryTermField
, queryString
), Occur
.MUST
);
1648 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdsQuery("type.id", edge
.getRelationshipTypes()), Occur
.MUST
);
1649 if(!includeUnpublished
){
1650 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishField
, true), Occur
.MUST
);
1651 joinFromQueryBuilder
.add(taxonBaseQueryFactory
.newBooleanQuery(publishFieldInvers
, true), Occur
.MUST
);
1654 Query joinQuery
= taxonBaseQueryFactory
.newJoinQuery(TaxonRelationship
.class, fromField
, false, joinFromQueryBuilder
.build(), toField
, null, ScoreMode
.Max
);
1656 if(sortFields
== null){
1657 sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
1659 luceneSearch
.setSortFields(sortFields
);
1661 finalQueryBuilder
.add(joinQuery
, Occur
.MUST
);
1663 if(classification
!= null){
1664 finalQueryBuilder
.add(taxonBaseQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
1666 if(subtree
!= null){
1667 finalQueryBuilder
.add(taxonBaseQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
1670 luceneSearch
.setQuery(finalQueryBuilder
.build());
1672 if(highlightFragments
){
1673 luceneSearch
.setHighlightFields(taxonBaseQueryFactory
.getTextFieldNamesAsArray());
1675 return luceneSearch
;
1679 public Pager
<SearchResult
<TaxonBase
>> findTaxaAndNamesByFullText(
1680 EnumSet
<TaxaAndNamesSearchMode
> searchModes
, String queryString
,
1681 Classification classification
, TaxonNode subtree
,
1682 Set
<NamedArea
> namedAreas
, Set
<PresenceAbsenceTerm
> distributionStatus
, List
<Language
> languages
,
1683 boolean highlightFragments
, Integer pageSize
,
1684 Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
)
1685 throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
1687 // FIXME: allow taxonomic ordering
1688 // 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";
1689 // this require building a special sort column by a special classBridge
1690 if(highlightFragments
){
1691 logger
.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1692 "currently not fully supported by this method and thus " +
1693 "may not work with common names and misapplied names.");
1696 // convert sets to lists
1697 List
<NamedArea
> namedAreaList
= null;
1698 List
<PresenceAbsenceTerm
> distributionStatusList
= null;
1699 if(namedAreas
!= null){
1700 namedAreaList
= new ArrayList
<>(namedAreas
.size());
1701 namedAreaList
.addAll(namedAreas
);
1703 if(distributionStatus
!= null){
1704 distributionStatusList
= new ArrayList
<>(distributionStatus
.size());
1705 distributionStatusList
.addAll(distributionStatus
);
1708 // set default if parameter is null
1709 if(searchModes
== null){
1710 searchModes
= EnumSet
.of(TaxaAndNamesSearchMode
.doTaxa
);
1713 // set sort order and thus override any sort orders which may have been
1714 // defined by prepare*Search methods
1715 if(orderHints
== null){
1716 orderHints
= OrderHint
.NOMENCLATURAL_SORT_ORDER
.asList();
1718 SortField
[] sortFields
= new SortField
[orderHints
.size()];
1720 for(OrderHint oh
: orderHints
){
1721 sortFields
[i
++] = oh
.toSortField();
1723 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1724 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1727 boolean addDistributionFilter
= namedAreas
!= null && namedAreas
.size() > 0;
1729 List
<LuceneSearch
> luceneSearches
= new ArrayList
<>();
1730 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
1733 ======== filtering by distribution , HOWTO ========
1735 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1736 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1737 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1738 which will be put into a FilteredQuersy in the end ?
1741 3. how does it work in spatial?
1743 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1744 - http://www.infoq.com/articles/LuceneSpatialSupport
1745 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1746 ------------------------------------------------------------------------
1749 A) use a separate distribution filter per index sub-query/search:
1750 - byTaxonSyonym (query TaxaonBase):
1751 use a join area filter (Distribution -> TaxonBase)
1752 - byCommonName (query DescriptionElementBase): use an area filter on
1753 DescriptionElementBase !!! PROBLEM !!!
1754 This cannot work since the distributions are different entities than the
1755 common names and thus these are different lucene documents.
1756 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1757 use a join area filter (Distribution -> TaxonBase)
1759 B) use a common distribution filter for all index sub-query/searches:
1760 - use a common join area filter (Distribution -> TaxonBase)
1761 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1762 PROBLEM in this case: we are losing the fragment highlighting for the
1763 common names, since the returned documents are always TaxonBases
1766 /* The QueryFactory for creating filter queries on Distributions should
1767 * The query factory used for the common names query cannot be reused
1768 * for this case, since we want to only record the text fields which are
1769 * actually used in the primary query
1771 QueryFactory distributionFilterQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Distribution
.class);
1773 Builder multiIndexByAreaFilterBuilder
= new Builder();
1774 boolean includeUnpublished
= searchModes
.contains(TaxaAndNamesSearchMode
.includeUnpublished
);
1776 // search for taxa or synonyms
1777 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) || searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
) ) {
1778 @SuppressWarnings("rawtypes")
1779 Class
<?
extends TaxonBase
> taxonBaseSubclass
= TaxonBase
.class;
1780 String className
= null;
1781 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && !searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1782 taxonBaseSubclass
= Taxon
.class;
1783 } else if (!searchModes
.contains(TaxaAndNamesSearchMode
.doTaxa
) && searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1784 className
= "eu.etaxonomy.cdm.model.taxon.Synonym";
1786 luceneSearches
.add(prepareFindByFullTextSearch(taxonBaseSubclass
,
1787 queryString
, classification
, subtree
, className
,
1788 includeUnpublished
, languages
, highlightFragments
, sortFields
));
1789 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1790 /* A) does not work!!!!
1791 if(addDistributionFilter){
1792 // in this case we need a filter which uses a join query
1793 // to get the TaxonBase documents for the DescriptionElementBase documents
1794 // which are matching the areas in question
1795 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1797 distributionStatusList,
1798 distributionFilterQueryFactory
1800 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1803 if(addDistributionFilter
&& searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)){
1804 // add additional area filter for synonyms
1805 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1806 String toField
= "accTaxon" + AcceptedTaxonBridge
.DOC_KEY_ID_SUFFIX
; // id in TaxonBase index
1808 //TODO replace by createByDistributionJoinQuery
1809 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1810 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class, fromField
, true, byDistributionQuery
, toField
, Taxon
.class, ScoreMode
.None
);
1811 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1816 // search by CommonTaxonName
1817 if(searchModes
.contains(TaxaAndNamesSearchMode
.doTaxaByCommonNames
)) {
1819 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
1820 Query byCommonNameJoinQuery
= descriptionElementQueryFactory
.newJoinQuery(
1821 CommonTaxonName
.class,
1822 "inDescription.taxon.id",
1824 QueryFactory
.addTypeRestriction(
1825 createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, null, languages
, descriptionElementQueryFactory
)
1826 , CommonTaxonName
.class
1827 ).build(), "id", null, ScoreMode
.Max
);
1828 if (logger
.isDebugEnabled()){logger
.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery
.toString());}
1829 LuceneSearch byCommonNameSearch
= new LuceneSearch(luceneIndexToolProvider
,
1830 GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
1831 byCommonNameSearch
.setCdmTypRestriction(Taxon
.class);
1832 Builder builder
= new BooleanQuery
.Builder();
1833 builder
.add(byCommonNameJoinQuery
, Occur
.MUST
);
1834 if(!includeUnpublished
) {
1835 QueryFactory taxonBaseQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(TaxonBase
.class);
1836 builder
.add(taxonBaseQueryFactory
.newBooleanQuery("publish", true), Occur
.MUST
);
1838 byCommonNameSearch
.setQuery(builder
.build());
1839 byCommonNameSearch
.setSortFields(sortFields
);
1841 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1843 luceneSearches
.add(byCommonNameSearch
);
1845 /* A) does not work!!!!
1847 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1848 queryString, classification, null, languages, highlightFragments)
1850 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1851 if(addDistributionFilter){
1852 // in this case we are able to use DescriptionElementBase documents
1853 // which are matching the areas in question directly
1854 BooleanQuery byDistributionQuery = createByDistributionQuery(
1856 distributionStatusList,
1857 distributionFilterQueryFactory
1859 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1864 // search by misapplied names
1865 //TODO merge with pro parte synonym search once #7487 is fixed
1866 if(searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1868 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1869 // which allows doing query time joins
1870 // finds the misapplied name (Taxon B) which is an misapplication for
1871 // a related Taxon A.
1873 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1874 if (searchModes
.contains(TaxaAndNamesSearchMode
.doMisappliedNames
)){
1875 relTypes
.addAll(TaxonRelationshipType
.allMisappliedNameTypes());
1877 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1878 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
1881 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1882 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1883 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1884 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1886 if(addDistributionFilter
){
1887 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1890 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
1891 * Maybe this is a bug in java itself.
1893 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1896 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1898 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1899 * will execute as expected:
1901 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1902 * String toField = "relation." + misappliedNameForUuid +".to.id";
1904 * Comparing both strings by the String.equals method returns true, so both String are identical.
1906 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1907 * 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)
1908 * The bug is persistent after a reboot of the development computer.
1910 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1911 // String toField = "relation." + misappliedNameForUuid +".to.id";
1912 String toField
= "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1913 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1914 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1916 //TODO replace by createByDistributionJoinQuery
1917 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1918 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1919 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1921 // debug code for bug described above
1922 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1923 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1924 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1926 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1931 // search by pro parte synonyms
1932 if(searchModes
.contains(TaxaAndNamesSearchMode
.doSynonyms
)) {
1933 //TODO merge with misapplied name search once #7487 is fixed
1934 Set
<TaxonRelationshipType
> relTypes
= new HashSet
<>();
1935 relTypes
.addAll(TaxonRelationshipType
.allSynonymTypes());
1937 luceneSearches
.add(prepareFindByTaxonRelationFullTextSearch(
1938 new TaxonRelationshipEdge(relTypes
, Direction
.relatedTo
),
1939 queryString
, classification
, subtree
, includeUnpublished
, languages
, highlightFragments
, sortFields
));
1940 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
1942 if(addDistributionFilter
){
1943 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
1944 String toField
= "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
1945 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, distributionFilterQueryFactory
);
1946 Query taxonAreaJoinQuery
= distributionFilterQueryFactory
.newJoinQuery(Distribution
.class,
1947 fromField
, true, byDistributionQuery
, toField
, null, ScoreMode
.None
);
1948 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1950 }//end pro parte synonyms
1954 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
,
1955 luceneSearches
.toArray(new LuceneSearch
[luceneSearches
.size()]));
1958 if(addDistributionFilter
){
1961 // in this case we need a filter which uses a join query
1962 // to get the TaxonBase documents for the DescriptionElementBase documents
1963 // which are matching the areas in question
1965 // for doTaxa, doByCommonName
1966 Query taxonAreaJoinQuery
= createByDistributionJoinQuery(
1968 distributionStatusList
,
1969 distributionFilterQueryFactory
,
1972 multiIndexByAreaFilterBuilder
.add(taxonAreaJoinQuery
, Occur
.SHOULD
);
1975 if (addDistributionFilter
){
1976 multiSearch
.setFilter(multiIndexByAreaFilterBuilder
.build());
1980 // --- execute search
1981 TopGroups
<BytesRef
> topDocsResultSet
;
1983 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
1984 } catch (ParseException e
) {
1985 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
1986 luceneParseException
.setStackTrace(e
.getStackTrace());
1987 throw luceneParseException
;
1990 // --- initialize taxa, highlight matches ....
1991 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
1994 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
1995 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
1997 long totalHits
= (topDocsResultSet
!= null) ? Long
.valueOf(topDocsResultSet
.totalGroupCount
) : 0;
1998 return new DefaultPagerImpl
<>(pageNumber
, totalHits
, pageSize
, searchResults
);
2002 * @param namedAreaList at least one area must be in the list
2003 * @param distributionStatusList optional
2004 * @param toType toType
2005 * Optional parameter. Only used for debugging to print the toType documents
2006 * @param asFilter TODO
2008 * @throws IOException
2010 protected Query
createByDistributionJoinQuery(
2011 List
<NamedArea
> namedAreaList
,
2012 List
<PresenceAbsenceTerm
> distributionStatusList
,
2013 QueryFactory queryFactory
, Class
<?
extends CdmBase
> toType
, boolean asFilter
2014 ) throws IOException
{
2016 String fromField
= "inDescription.taxon.id"; // in DescriptionElementBase index
2017 String toField
= "id"; // id in toType usually this is the TaxonBase index
2019 BooleanQuery byDistributionQuery
= createByDistributionQuery(namedAreaList
, distributionStatusList
, queryFactory
);
2021 ScoreMode scoreMode
= asFilter ? ScoreMode
.None
: ScoreMode
.Max
;
2023 Query taxonAreaJoinQuery
= queryFactory
.newJoinQuery(Distribution
.class, fromField
, false, byDistributionQuery
, toField
, toType
, scoreMode
);
2025 return taxonAreaJoinQuery
;
2029 * @param namedAreaList
2030 * @param distributionStatusList
2031 * @param queryFactory
2034 private BooleanQuery
createByDistributionQuery(List
<NamedArea
> namedAreaList
,
2035 List
<PresenceAbsenceTerm
> distributionStatusList
, QueryFactory queryFactory
) {
2036 Builder areaQueryBuilder
= new Builder();
2037 // area field from Distribution
2038 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("area.id", namedAreaList
), Occur
.MUST
);
2040 // status field from Distribution
2041 if(distributionStatusList
!= null && distributionStatusList
.size() > 0){
2042 areaQueryBuilder
.add(queryFactory
.newEntityIdsQuery("status.id", distributionStatusList
), Occur
.MUST
);
2045 BooleanQuery areaQuery
= areaQueryBuilder
.build();
2046 logger
.debug("createByDistributionQuery() query: " + areaQuery
.toString());
2051 * This method has been primarily created for testing the area join query but might
2052 * also be useful in other situations
2054 * @param namedAreaList
2055 * @param distributionStatusList
2056 * @param classification
2057 * @param highlightFragments
2059 * @throws IOException
2061 protected LuceneSearch
prepareByDistributionSearch(
2062 List
<NamedArea
> namedAreaList
, List
<PresenceAbsenceTerm
> distributionStatusList
,
2063 Classification classification
, TaxonNode subtree
) throws IOException
{
2065 Builder finalQueryBuilder
= new Builder();
2067 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, Taxon
.class);
2069 // FIXME is this query factory using the wrong type?
2070 QueryFactory taxonQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(Taxon
.class);
2072 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("titleCache__sort", SortField
.Type
.STRING
, false)};
2073 luceneSearch
.setSortFields(sortFields
);
2076 Query byAreaQuery
= createByDistributionJoinQuery(namedAreaList
, distributionStatusList
, taxonQueryFactory
, null, false);
2078 finalQueryBuilder
.add(byAreaQuery
, Occur
.MUST
);
2080 if(classification
!= null){
2081 finalQueryBuilder
.add(taxonQueryFactory
.newEntityIdQuery(AcceptedTaxonBridge
.DOC_KEY_CLASSIFICATION_ID
, classification
), Occur
.MUST
);
2083 if(subtree
!= null){
2084 finalQueryBuilder
.add(taxonQueryFactory
.newTermQuery(AcceptedTaxonBridge
.DOC_KEY_TREEINDEX
, subtree
.treeIndexWc(), true), Occur
.MUST
);
2086 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2087 logger
.info("prepareByAreaSearch() query: " + finalQuery
.toString());
2088 luceneSearch
.setQuery(finalQuery
);
2090 return luceneSearch
;
2094 public Pager
<SearchResult
<TaxonBase
>> findByDescriptionElementFullText(
2095 Class
<?
extends DescriptionElementBase
> clazz
, String queryString
,
2096 Classification classification
, TaxonNode subtree
, List
<Feature
> features
, List
<Language
> languages
,
2097 boolean highlightFragments
, Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
{
2100 LuceneSearch luceneSearch
= prepareByDescriptionElementFullTextSearch(clazz
, queryString
, classification
, subtree
, features
, languages
, highlightFragments
);
2102 // --- execute search
2103 TopGroups
<BytesRef
> topDocsResultSet
;
2105 topDocsResultSet
= luceneSearch
.executeSearch(pageSize
, pageNumber
);
2106 } catch (ParseException e
) {
2107 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2108 luceneParseException
.setStackTrace(e
.getStackTrace());
2109 throw luceneParseException
;
2112 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2113 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2115 // --- initialize taxa, highlight matches ....
2116 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(luceneSearch
, luceneSearch
.getQuery());
2117 @SuppressWarnings("rawtypes")
2118 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2119 topDocsResultSet
, luceneSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2121 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2122 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2128 public Pager
<SearchResult
<TaxonBase
>> findByEverythingFullText(String queryString
,
2129 Classification classification
, TaxonNode subtree
, boolean includeUnpublished
, List
<Language
> languages
, boolean highlightFragments
,
2130 Integer pageSize
, Integer pageNumber
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) throws IOException
, LuceneParseException
, LuceneMultiSearchException
{
2132 LuceneSearch luceneSearchByDescriptionElement
= prepareByDescriptionElementFullTextSearch(null, queryString
,
2133 classification
, subtree
,
2134 null, languages
, highlightFragments
);
2135 LuceneSearch luceneSearchByTaxonBase
= prepareFindByFullTextSearch(null, queryString
, classification
, subtree
, null,
2136 includeUnpublished
, languages
, highlightFragments
, null);
2138 LuceneMultiSearch multiSearch
= new LuceneMultiSearch(luceneIndexToolProvider
, luceneSearchByDescriptionElement
, luceneSearchByTaxonBase
);
2140 // --- execute search
2141 TopGroups
<BytesRef
> topDocsResultSet
;
2143 topDocsResultSet
= multiSearch
.executeSearch(pageSize
, pageNumber
);
2144 } catch (ParseException e
) {
2145 LuceneParseException luceneParseException
= new LuceneParseException(e
.getMessage());
2146 luceneParseException
.setStackTrace(e
.getStackTrace());
2147 throw luceneParseException
;
2150 // --- initialize taxa, highlight matches ....
2151 ISearchResultBuilder searchResultBuilder
= new SearchResultBuilder(multiSearch
, multiSearch
.getQuery());
2153 Map
<CdmBaseType
, String
> idFieldMap
= new HashMap
<>();
2154 idFieldMap
.put(CdmBaseType
.TAXON
, "id");
2155 idFieldMap
.put(CdmBaseType
.DESCRIPTION_ELEMENT
, "inDescription.taxon.id");
2157 List
<SearchResult
<TaxonBase
>> searchResults
= searchResultBuilder
.createResultSet(
2158 topDocsResultSet
, multiSearch
.getHighlightFields(), dao
, idFieldMap
, propertyPaths
);
2160 int totalHits
= topDocsResultSet
!= null ? topDocsResultSet
.totalGroupCount
: 0;
2161 return new DefaultPagerImpl
<>(pageNumber
, Long
.valueOf(totalHits
), pageSize
, searchResults
);
2168 * @param queryString
2169 * @param classification
2172 * @param highlightFragments
2173 * @param directorySelectClass
2176 protected LuceneSearch
prepareByDescriptionElementFullTextSearch(Class
<?
extends CdmBase
> clazz
,
2177 String queryString
, Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2178 List
<Language
> languages
, boolean highlightFragments
) {
2180 LuceneSearch luceneSearch
= new LuceneSearch(luceneIndexToolProvider
, GroupByTaxonClassBridge
.GROUPBY_TAXON_FIELD
, DescriptionElementBase
.class);
2181 QueryFactory descriptionElementQueryFactory
= luceneIndexToolProvider
.newQueryFactoryFor(DescriptionElementBase
.class);
2183 SortField
[] sortFields
= new SortField
[]{SortField
.FIELD_SCORE
, new SortField("inDescription.taxon.titleCache__sort", SortField
.Type
.STRING
, false)};
2185 BooleanQuery finalQuery
= createByDescriptionElementFullTextQuery(queryString
, classification
, subtree
, features
,
2186 languages
, descriptionElementQueryFactory
);
2188 luceneSearch
.setSortFields(sortFields
);
2189 luceneSearch
.setCdmTypRestriction(clazz
);
2190 luceneSearch
.setQuery(finalQuery
);
2191 if(highlightFragments
){
2192 luceneSearch
.setHighlightFields(descriptionElementQueryFactory
.getTextFieldNamesAsArray());
2195 return luceneSearch
;
2199 * @param queryString
2200 * @param classification
2203 * @param descriptionElementQueryFactory
2206 private BooleanQuery
createByDescriptionElementFullTextQuery(String queryString
,
2207 Classification classification
, TaxonNode subtree
, List
<Feature
> features
,
2208 List
<Language
> languages
, QueryFactory descriptionElementQueryFactory
) {
2210 Builder finalQueryBuilder
= new Builder();
2211 Builder textQueryBuilder
= new Builder();
2213 if(!StringUtils
.isEmpty(queryString
)){
2215 textQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("titleCache", queryString
), Occur
.SHOULD
);
2218 Builder nameQueryBuilder
= new Builder();
2219 if(languages
== null || languages
.size() == 0){
2220 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2222 Builder languageSubQueryBuilder
= new Builder();
2223 for(Language lang
: languages
){
2224 languageSubQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("language.uuid", lang
.getUuid().toString(), false), Occur
.SHOULD
);
2226 nameQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("name", queryString
), Occur
.MUST
);
2227 nameQueryBuilder
.add(languageSubQueryBuilder
.build(), Occur
.MUST
);
2229 textQueryBuilder
.add(nameQueryBuilder
.build(), Occur
.SHOULD
);
2232 // text field from TextData
2233 textQueryBuilder
.add(descriptionElementQueryFactory
.newMultilanguageTextQuery("text", queryString
, languages
), Occur
.SHOULD
);
2235 // --- TermBase fields - by representation ----
2236 // state field from CategoricalData
2237 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.state", queryString
, languages
), Occur
.SHOULD
);
2239 // state field from CategoricalData
2240 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("stateData.modifyingText", queryString
, languages
), Occur
.SHOULD
);
2242 // area field from Distribution
2243 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("area", queryString
, languages
), Occur
.SHOULD
);
2245 // status field from Distribution
2246 textQueryBuilder
.add(descriptionElementQueryFactory
.newDefinedTermQuery("status", queryString
, languages
), Occur
.SHOULD
);
2248 finalQueryBuilder
.add(textQueryBuilder
.build(), Occur
.MUST
);
2251 // --- classification ----
2254 if(classification
!= null){
2255 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification
), Occur
.MUST
);
2257 if(subtree
!= null){
2258 finalQueryBuilder
.add(descriptionElementQueryFactory
.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree
.treeIndexWc(), true), Occur
.MUST
);
2261 // --- IdentifieableEntity fields - by uuid
2262 if(features
!= null && features
.size() > 0 ){
2263 finalQueryBuilder
.add(descriptionElementQueryFactory
.newEntityUuidsQuery("feature.uuid", features
), Occur
.MUST
);
2266 // the description must be associated with a taxon
2267 finalQueryBuilder
.add(descriptionElementQueryFactory
.newIsNotNullQuery("inDescription.taxon.id"), Occur
.MUST
);
2269 BooleanQuery finalQuery
= finalQueryBuilder
.build();
2270 logger
.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery
.toString());
2275 public List
<Synonym
> createInferredSynonyms(Taxon taxon
, Classification classification
, SynonymType type
, boolean doWithMisappliedNames
){
2278 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2279 List
<Synonym
> inferredSynonymsToBeRemoved
= new ArrayList
<>();
2281 Map
<UUID
, IZoologicalName
> zooHashMap
= new HashMap
<>();
2282 boolean includeUnpublished
= INCLUDE_UNPUBLISHED
;
2284 UUID nameUuid
= taxon
.getName().getUuid();
2285 IZoologicalName taxonName
= getZoologicalName(nameUuid
, zooHashMap
);
2286 String epithetOfTaxon
= null;
2287 String infragenericEpithetOfTaxon
= null;
2288 String infraspecificEpithetOfTaxon
= null;
2289 if (taxonName
.isSpecies()){
2290 epithetOfTaxon
= taxonName
.getSpecificEpithet();
2291 } else if (taxonName
.isInfraGeneric()){
2292 infragenericEpithetOfTaxon
= taxonName
.getInfraGenericEpithet();
2293 } else if (taxonName
.isInfraSpecific()){
2294 infraspecificEpithetOfTaxon
= taxonName
.getInfraSpecificEpithet();
2296 String genusOfTaxon
= taxonName
.getGenusOrUninomial();
2297 Set
<TaxonNode
> nodes
= taxon
.getTaxonNodes();
2298 List
<String
> taxonNames
= new ArrayList
<>();
2300 for (TaxonNode node
: nodes
){
2301 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2302 // List<String> synonymsEpithet = new ArrayList<>();
2304 if (node
.getClassification().equals(classification
)){
2305 if (!node
.isTopmostNode()){
2306 TaxonNode parent
= node
.getParent();
2307 parent
= CdmBase
.deproxy(parent
);
2308 TaxonName parentName
= parent
.getTaxon().getName();
2309 IZoologicalName zooParentName
= CdmBase
.deproxy(parentName
);
2310 Taxon parentTaxon
= CdmBase
.deproxy(parent
.getTaxon());
2312 //create inferred synonyms for species, subspecies
2313 if ((parentName
.isGenus() || parentName
.isSpecies() || parentName
.getRank().equals(Rank
.SUBGENUS())) ){
2315 Synonym inferredEpithet
= null;
2316 Synonym inferredGenus
= null;
2317 Synonym potentialCombination
= null;
2319 List
<String
> propertyPaths
= new ArrayList
<>();
2320 propertyPaths
.add("synonym");
2321 propertyPaths
.add("synonym.name");
2322 List
<OrderHint
> orderHintsSynonyms
= new ArrayList
<>();
2323 orderHintsSynonyms
.add(new OrderHint("titleCache", SortOrder
.ASCENDING
));
2325 List
<Synonym
> synonyMsOfParent
= dao
.getSynonyms(parentTaxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms
,propertyPaths
);
2326 List
<Synonym
> synonymsOfTaxon
= dao
.getSynonyms(taxon
, SynonymType
.HETEROTYPIC_SYNONYM_OF(),
2327 null, null,orderHintsSynonyms
,propertyPaths
);
2329 List
<TaxonRelationship
> taxonRelListParent
= new ArrayList
<>();
2330 List
<TaxonRelationship
> taxonRelListTaxon
= new ArrayList
<>();
2331 if (doWithMisappliedNames
){
2332 List
<OrderHint
> orderHintsMisapplied
= new ArrayList
<>();
2333 orderHintsMisapplied
.add(new OrderHint("relatedFrom.titleCache", SortOrder
.ASCENDING
));
2334 taxonRelListParent
= dao
.getTaxonRelationships(parentTaxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2335 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2336 taxonRelListTaxon
= dao
.getTaxonRelationships(taxon
, TaxonRelationshipType
.MISAPPLIED_NAME_FOR(),
2337 includeUnpublished
, null, null, orderHintsMisapplied
, propertyPaths
, Direction
.relatedTo
);
2340 if (type
.equals(SynonymType
.INFERRED_EPITHET_OF())){
2341 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2343 inferredEpithet
= createInferredEpithets(taxon
,
2344 zooHashMap
, taxonName
, epithetOfTaxon
,
2345 infragenericEpithetOfTaxon
,
2346 infraspecificEpithetOfTaxon
,
2347 taxonNames
, parentName
,
2348 synonymRelationOfParent
);
2350 inferredSynonyms
.add(inferredEpithet
);
2351 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2352 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2355 if (doWithMisappliedNames
){
2357 for (TaxonRelationship taxonRelationship
: taxonRelListParent
){
2358 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2360 inferredEpithet
= createInferredEpithets(taxon
,
2361 zooHashMap
, taxonName
, epithetOfTaxon
,
2362 infragenericEpithetOfTaxon
,
2363 infraspecificEpithetOfTaxon
,
2364 taxonNames
, parentName
,
2367 inferredSynonyms
.add(inferredEpithet
);
2368 zooHashMap
.put(inferredEpithet
.getName().getUuid(), inferredEpithet
.getName());
2369 taxonNames
.add(inferredEpithet
.getName().getNameCache());
2373 if (!taxonNames
.isEmpty()){
2374 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2375 IZoologicalName name
;
2376 if (!synNotInCDM
.isEmpty()){
2377 inferredSynonymsToBeRemoved
.clear();
2379 for (Synonym syn
:inferredSynonyms
){
2380 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2381 if (!synNotInCDM
.contains(name
.getNameCache())){
2382 inferredSynonymsToBeRemoved
.add(syn
);
2386 // Remove identified Synonyms from inferredSynonyms
2387 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2388 inferredSynonyms
.remove(synonym
);
2393 }else if (type
.equals(SynonymType
.INFERRED_GENUS_OF())){
2395 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2397 inferredGenus
= createInferredGenus(taxon
,
2398 zooHashMap
, taxonName
, epithetOfTaxon
,
2399 genusOfTaxon
, taxonNames
, zooParentName
, synonymRelationOfTaxon
);
2401 inferredSynonyms
.add(inferredGenus
);
2402 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2403 taxonNames
.add(inferredGenus
.getName().getNameCache());
2406 if (doWithMisappliedNames
){
2408 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2409 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2410 inferredGenus
= createInferredGenus(taxon
, zooHashMap
, taxonName
, infraspecificEpithetOfTaxon
, genusOfTaxon
, taxonNames
, zooParentName
, misappliedName
);
2412 inferredSynonyms
.add(inferredGenus
);
2413 zooHashMap
.put(inferredGenus
.getName().getUuid(), inferredGenus
.getName());
2414 taxonNames
.add(inferredGenus
.getName().getNameCache());
2419 if (!taxonNames
.isEmpty()){
2420 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2421 IZoologicalName name
;
2422 if (!synNotInCDM
.isEmpty()){
2423 inferredSynonymsToBeRemoved
.clear();
2425 for (Synonym syn
:inferredSynonyms
){
2426 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2427 if (!synNotInCDM
.contains(name
.getNameCache())){
2428 inferredSynonymsToBeRemoved
.add(syn
);
2432 // Remove identified Synonyms from inferredSynonyms
2433 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2434 inferredSynonyms
.remove(synonym
);
2439 }else if (type
.equals(SynonymType
.POTENTIAL_COMBINATION_OF())){
2441 Reference sourceReference
= null; // TODO: Determination of sourceReference is redundant
2442 //for all synonyms of the parent...
2443 for (Synonym synonymRelationOfParent
:synonyMsOfParent
){
2445 HibernateProxyHelper
.deproxy(synonymRelationOfParent
);
2447 synName
= synonymRelationOfParent
.getName();
2449 // Set the sourceReference
2450 sourceReference
= synonymRelationOfParent
.getSec();
2452 // Determine the idInSource
2453 String idInSourceParent
= getIdInSource(synonymRelationOfParent
);
2455 IZoologicalName parentSynZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2456 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2457 String synParentInfragenericName
= null;
2458 String synParentSpecificEpithet
= null;
2460 if (parentSynZooName
.isInfraGeneric()){
2461 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2463 if (parentSynZooName
.isSpecies()){
2464 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2467 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2468 synonymsGenus.put(synGenusName, idInSource);
2471 //for all synonyms of the taxon
2473 for (Synonym synonymRelationOfTaxon
:synonymsOfTaxon
){
2475 IZoologicalName zooSynName
= getZoologicalName(synonymRelationOfTaxon
.getName().getUuid(), zooHashMap
);
2476 potentialCombination
= createPotentialCombination(idInSourceParent
, parentSynZooName
, zooSynName
,
2478 synParentInfragenericName
,
2479 synParentSpecificEpithet
, synonymRelationOfTaxon
, zooHashMap
);
2481 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2482 inferredSynonyms
.add(potentialCombination
);
2483 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2484 taxonNames
.add(potentialCombination
.getName().getNameCache());
2490 if (doWithMisappliedNames
){
2492 for (TaxonRelationship parentRelationship
: taxonRelListParent
){
2494 TaxonName misappliedParentName
;
2496 Taxon misappliedParent
= parentRelationship
.getFromTaxon();
2497 misappliedParentName
= misappliedParent
.getName();
2499 HibernateProxyHelper
.deproxy(misappliedParent
);
2501 // Set the sourceReference
2502 sourceReference
= misappliedParent
.getSec();
2504 // Determine the idInSource
2505 String idInSourceParent
= getIdInSource(misappliedParent
);
2507 IZoologicalName parentSynZooName
= getZoologicalName(misappliedParentName
.getUuid(), zooHashMap
);
2508 String synParentGenus
= parentSynZooName
.getGenusOrUninomial();
2509 String synParentInfragenericName
= null;
2510 String synParentSpecificEpithet
= null;
2512 if (parentSynZooName
.isInfraGeneric()){
2513 synParentInfragenericName
= parentSynZooName
.getInfraGenericEpithet();
2515 if (parentSynZooName
.isSpecies()){
2516 synParentSpecificEpithet
= parentSynZooName
.getSpecificEpithet();
2520 for (TaxonRelationship taxonRelationship
: taxonRelListTaxon
){
2521 Taxon misappliedName
= taxonRelationship
.getFromTaxon();
2522 IZoologicalName zooMisappliedName
= getZoologicalName(misappliedName
.getName().getUuid(), zooHashMap
);
2523 potentialCombination
= createPotentialCombination(
2524 idInSourceParent
, parentSynZooName
, zooMisappliedName
,
2526 synParentInfragenericName
,
2527 synParentSpecificEpithet
, misappliedName
, zooHashMap
);
2530 taxon
.addSynonym(potentialCombination
, SynonymType
.POTENTIAL_COMBINATION_OF());
2531 inferredSynonyms
.add(potentialCombination
);
2532 zooHashMap
.put(potentialCombination
.getName().getUuid(), potentialCombination
.getName());
2533 taxonNames
.add(potentialCombination
.getName().getNameCache());
2538 if (!taxonNames
.isEmpty()){
2539 List
<String
> synNotInCDM
= dao
.taxaByNameNotInDB(taxonNames
);
2540 IZoologicalName name
;
2541 if (!synNotInCDM
.isEmpty()){
2542 inferredSynonymsToBeRemoved
.clear();
2543 for (Synonym syn
:inferredSynonyms
){
2545 name
= syn
.getName();
2546 }catch (ClassCastException e
){
2547 name
= getZoologicalName(syn
.getName().getUuid(), zooHashMap
);
2549 if (!synNotInCDM
.contains(name
.getNameCache())){
2550 inferredSynonymsToBeRemoved
.add(syn
);
2553 // Remove identified Synonyms from inferredSynonyms
2554 for (Synonym synonym
: inferredSynonymsToBeRemoved
) {
2555 inferredSynonyms
.remove(synonym
);
2561 logger
.info("The synonym type is not defined.");
2562 return inferredSynonyms
;
2569 return inferredSynonyms
;
2572 private Synonym
createPotentialCombination(String idInSourceParent
,
2573 IZoologicalName parentSynZooName
, IZoologicalName zooSynName
, String synParentGenus
,
2574 String synParentInfragenericName
, String synParentSpecificEpithet
,
2575 TaxonBase
<?
> syn
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2576 Synonym potentialCombination
;
2577 Reference sourceReference
;
2578 IZoologicalName inferredSynName
;
2579 HibernateProxyHelper
.deproxy(syn
);
2581 // Set sourceReference
2582 sourceReference
= syn
.getSec();
2583 if (sourceReference
== null){
2584 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2586 if (!parentSynZooName
.getTaxa().isEmpty()){
2587 TaxonBase
<?
> taxon
= parentSynZooName
.getTaxa().iterator().next();
2589 sourceReference
= taxon
.getSec();
2592 String synTaxonSpecificEpithet
= zooSynName
.getSpecificEpithet();
2594 String synTaxonInfraSpecificName
= null;
2596 if (parentSynZooName
.isSpecies()){
2597 synTaxonInfraSpecificName
= zooSynName
.getInfraSpecificEpithet();
2600 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2601 synonymsEpithet.add(epithetName);
2604 //create potential combinations...
2605 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(syn
.getName().getRank());
2607 inferredSynName
.setGenusOrUninomial(synParentGenus
);
2608 if (zooSynName
.isSpecies()){
2609 inferredSynName
.setSpecificEpithet(synTaxonSpecificEpithet
);
2610 if (parentSynZooName
.isInfraGeneric()){
2611 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2614 if (zooSynName
.isInfraSpecific()){
2615 inferredSynName
.setSpecificEpithet(synParentSpecificEpithet
);
2616 inferredSynName
.setInfraSpecificEpithet(synTaxonInfraSpecificName
);
2618 if (parentSynZooName
.isInfraGeneric()){
2619 inferredSynName
.setInfraGenericEpithet(synParentInfragenericName
);
2623 potentialCombination
= Synonym
.NewInstance(inferredSynName
, null);
2625 // Set the sourceReference
2626 potentialCombination
.setSec(sourceReference
);
2629 // Determine the idInSource
2630 String idInSourceSyn
= getIdInSource(syn
);
2632 if (idInSourceParent
!= null && idInSourceSyn
!= null) {
2633 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2634 inferredSynName
.addSource(originalSource
);
2635 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
, idInSourceSyn
+ "; " + idInSourceParent
, POTENTIAL_COMBINATION_NAMESPACE
, sourceReference
, null);
2636 potentialCombination
.addSource(originalSource
);
2639 return potentialCombination
;
2642 private Synonym
createInferredGenus(Taxon taxon
,
2643 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2644 String epithetOfTaxon
, String genusOfTaxon
,
2645 List
<String
> taxonNames
, IZoologicalName zooParentName
,
2648 Synonym inferredGenus
;
2650 IZoologicalName inferredSynName
;
2651 synName
=syn
.getName();
2652 HibernateProxyHelper
.deproxy(syn
);
2654 // Determine the idInSource
2655 String idInSourceSyn
= getIdInSource(syn
);
2656 String idInSourceTaxon
= getIdInSource(taxon
);
2657 // Determine the sourceReference
2658 Reference sourceReference
= syn
.getSec();
2660 //logger.warn(sourceReference.getTitleCache());
2662 synName
= syn
.getName();
2663 IZoologicalName synZooName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2664 String synSpeciesEpithetName
= synZooName
.getSpecificEpithet();
2665 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2666 synonymsEpithet.add(synSpeciesEpithetName);
2669 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2670 //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...
2673 inferredSynName
.setGenusOrUninomial(genusOfTaxon
);
2674 if (zooParentName
.isInfraGeneric()){
2675 inferredSynName
.setInfraGenericEpithet(zooParentName
.getInfraGenericEpithet());
2678 if (taxonName
.isSpecies()){
2679 inferredSynName
.setSpecificEpithet(synSpeciesEpithetName
);
2681 if (taxonName
.isInfraSpecific()){
2682 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2683 inferredSynName
.setInfraSpecificEpithet(synZooName
.getInfraGenericEpithet());
2687 inferredGenus
= Synonym
.NewInstance(inferredSynName
, null);
2689 // Set the sourceReference
2690 inferredGenus
.setSec(sourceReference
);
2692 // Add the original source
2693 if (idInSourceSyn
!= null && idInSourceTaxon
!= null) {
2694 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2695 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2696 inferredGenus
.addSource(originalSource
);
2698 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2699 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2700 inferredSynName
.addSource(originalSource
);
2701 originalSource
= null;
2704 logger
.error("There is an idInSource missing: " + idInSourceSyn
+ " of Synonym or " + idInSourceTaxon
+ " of Taxon");
2705 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2706 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2707 inferredGenus
.addSource(originalSource
);
2709 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2710 idInSourceSyn
+ "; " + idInSourceTaxon
, INFERRED_GENUS_NAMESPACE
, sourceReference
, null);
2711 inferredSynName
.addSource(originalSource
);
2712 originalSource
= null;
2715 taxon
.addSynonym(inferredGenus
, SynonymType
.INFERRED_GENUS_OF());
2717 return inferredGenus
;
2720 private Synonym
createInferredEpithets(Taxon taxon
,
2721 Map
<UUID
, IZoologicalName
> zooHashMap
, IZoologicalName taxonName
,
2722 String epithetOfTaxon
, String infragenericEpithetOfTaxon
,
2723 String infraspecificEpithetOfTaxon
, List
<String
> taxonNames
,
2724 TaxonName parentName
, TaxonBase
<?
> syn
) {
2726 Synonym inferredEpithet
;
2728 IZoologicalName inferredSynName
;
2729 HibernateProxyHelper
.deproxy(syn
);
2731 // Determine the idInSource
2732 String idInSourceSyn
= getIdInSource(syn
);
2733 String idInSourceTaxon
= getIdInSource(taxon
);
2734 // Determine the sourceReference
2735 Reference sourceReference
= syn
.getSec();
2737 if (sourceReference
== null){
2738 logger
.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon
.getSec());
2739 sourceReference
= taxon
.getSec();
2742 synName
= syn
.getName();
2743 IZoologicalName zooSynName
= getZoologicalName(synName
.getUuid(), zooHashMap
);
2744 String synGenusName
= zooSynName
.getGenusOrUninomial();
2745 String synInfraGenericEpithet
= null;
2746 String synSpecificEpithet
= null;
2748 if (zooSynName
.getInfraGenericEpithet() != null){
2749 synInfraGenericEpithet
= zooSynName
.getInfraGenericEpithet();
2752 if (zooSynName
.isInfraSpecific()){
2753 synSpecificEpithet
= zooSynName
.getSpecificEpithet();
2756 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2757 synonymsGenus.put(synGenusName, idInSource);
2760 inferredSynName
= TaxonNameFactory
.NewZoologicalInstance(taxon
.getName().getRank());
2762 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2763 if (epithetOfTaxon
== null && infragenericEpithetOfTaxon
== null && infraspecificEpithetOfTaxon
== null) {
2764 logger
.error("This specificEpithet is NULL" + taxon
.getTitleCache());
2766 inferredSynName
.setGenusOrUninomial(synGenusName
);
2768 if (parentName
.isInfraGeneric()){
2769 inferredSynName
.setInfraGenericEpithet(synInfraGenericEpithet
);
2771 if (taxonName
.isSpecies()){
2772 inferredSynName
.setSpecificEpithet(epithetOfTaxon
);
2773 }else if (taxonName
.isInfraSpecific()){
2774 inferredSynName
.setSpecificEpithet(synSpecificEpithet
);
2775 inferredSynName
.setInfraSpecificEpithet(infraspecificEpithetOfTaxon
);
2778 inferredEpithet
= Synonym
.NewInstance(inferredSynName
, null);
2780 // Set the sourceReference
2781 inferredEpithet
.setSec(sourceReference
);
2783 /* Add the original source
2784 if (idInSource != null) {
2785 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2788 Reference citation = getCitation(syn);
2789 if (citation != null) {
2790 originalSource.setCitation(citation);
2791 inferredEpithet.addSource(originalSource);
2794 String taxonId
= idInSourceTaxon
+ "; " + idInSourceSyn
;
2797 IdentifiableSource originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2798 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2800 inferredEpithet
.addSource(originalSource
);
2802 originalSource
= IdentifiableSource
.NewInstance(OriginalSourceType
.Transformation
,
2803 taxonId
, INFERRED_EPITHET_NAMESPACE
, sourceReference
, null);
2805 inferredSynName
.addSource(originalSource
);
2809 taxon
.addSynonym(inferredEpithet
, SynonymType
.INFERRED_EPITHET_OF());
2811 return inferredEpithet
;
2815 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2816 * Very likely only useful for createInferredSynonyms().
2821 private IZoologicalName
getZoologicalName(UUID uuid
, Map
<UUID
, IZoologicalName
> zooHashMap
) {
2822 IZoologicalName taxonName
=nameDao
.findZoologicalNameByUUID(uuid
);
2823 if (taxonName
== null) {
2824 taxonName
= zooHashMap
.get(uuid
);
2830 * Returns the idInSource for a given Synonym.
2833 private String
getIdInSource(TaxonBase
<?
> taxonBase
) {
2834 String idInSource
= null;
2835 Set
<IdentifiableSource
> sources
= taxonBase
.getSources();
2836 if (sources
.size() == 1) {
2837 IdentifiableSource source
= sources
.iterator().next();
2838 if (source
!= null) {
2839 idInSource
= source
.getIdInSource();
2841 } else if (sources
.size() > 1) {
2844 for (IdentifiableSource source
: sources
) {
2845 idInSource
+= source
.getIdInSource();
2846 if (count
< sources
.size()) {
2851 } else if (sources
.size() == 0){
2852 logger
.warn("No idInSource for TaxonBase " + taxonBase
.getUuid() + " - " + taxonBase
.getTitleCache());
2861 * Returns the citation for a given Synonym.
2864 private Reference
getCitation(Synonym syn
) {
2865 Reference citation
= null;
2866 Set
<IdentifiableSource
> sources
= syn
.getSources();
2867 if (sources
.size() == 1) {
2868 IdentifiableSource source
= sources
.iterator().next();
2869 if (source
!= null) {
2870 citation
= source
.getCitation();
2872 } else if (sources
.size() > 1) {
2873 logger
.warn("This Synonym has more than one source: " + syn
.getUuid() + " (" + syn
.getTitleCache() +")");
2880 public List
<Synonym
> createAllInferredSynonyms(Taxon taxon
, Classification tree
, boolean doWithMisappliedNames
){
2881 List
<Synonym
> inferredSynonyms
= new ArrayList
<>();
2883 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_EPITHET_OF(), doWithMisappliedNames
));
2884 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.INFERRED_GENUS_OF(), doWithMisappliedNames
));
2885 inferredSynonyms
.addAll(createInferredSynonyms(taxon
, tree
, SynonymType
.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames
));
2887 return inferredSynonyms
;
2891 public List
<Classification
> listClassifications(TaxonBase taxonBase
, Integer limit
, Integer start
, List
<String
> propertyPaths
) {
2893 // TODO quickly implemented, create according dao !!!!
2894 Set
<TaxonNode
> nodes
= new HashSet
<>();
2895 Set
<Classification
> classifications
= new HashSet
<>();
2896 List
<Classification
> list
= new ArrayList
<>();
2898 if (taxonBase
== null) {
2902 taxonBase
= load(taxonBase
.getUuid());
2904 if (taxonBase
instanceof Taxon
) {
2905 nodes
.addAll(((Taxon
)taxonBase
).getTaxonNodes());
2907 Taxon taxon
= ((Synonym
)taxonBase
).getAcceptedTaxon();
2909 nodes
.addAll(taxon
.getTaxonNodes());
2912 for (TaxonNode node
: nodes
) {
2913 classifications
.add(node
.getClassification());
2915 list
.addAll(classifications
);
2920 @Transactional(readOnly
= false)
2921 public UpdateResult
changeRelatedTaxonToSynonym(UUID fromTaxonUuid
,
2923 TaxonRelationshipType oldRelationshipType
,
2924 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2925 UpdateResult result
= new UpdateResult();
2926 Taxon fromTaxon
= (Taxon
) dao
.load(fromTaxonUuid
);
2927 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
2928 result
= changeRelatedTaxonToSynonym(fromTaxon
, toTaxon
, oldRelationshipType
, synonymType
);
2930 result
.addUpdatedObject(fromTaxon
);
2931 result
.addUpdatedObject(toTaxon
);
2932 result
.addUpdatedObject(result
.getCdmEntity());
2938 @Transactional(readOnly
= false)
2939 public UpdateResult
changeRelatedTaxonToSynonym(Taxon fromTaxon
, Taxon toTaxon
, TaxonRelationshipType oldRelationshipType
,
2940 SynonymType synonymType
) throws DataChangeNoRollbackException
{
2942 UpdateResult result
= new UpdateResult();
2943 // Create new synonym using concept name
2944 TaxonName synonymName
= fromTaxon
.getName();
2946 // Remove concept relation from taxon
2947 toTaxon
.removeTaxon(fromTaxon
, oldRelationshipType
);
2949 // Create a new synonym for the taxon
2951 if (synonymType
!= null
2952 && synonymType
.equals(SynonymType
.HOMOTYPIC_SYNONYM_OF())){
2953 synonym
= Synonym
.NewInstance(synonymName
, fromTaxon
.getSec());
2954 toTaxon
.addHomotypicSynonym(synonym
);
2956 synonym
= toTaxon
.addHeterotypicSynonymName(synonymName
);
2959 this.saveOrUpdate(toTaxon
);
2960 //TODO: configurator and classification
2961 TaxonDeletionConfigurator config
= new TaxonDeletionConfigurator();
2962 config
.setDeleteNameIfPossible(false);
2963 result
.includeResult(this.deleteTaxon(fromTaxon
.getUuid(), config
, null));
2964 result
.setCdmEntity(synonym
);
2965 result
.addUpdatedObject(toTaxon
);
2966 result
.addUpdatedObject(synonym
);
2971 public DeleteResult
isDeletable(UUID taxonBaseUuid
, DeleteConfiguratorBase config
){
2972 DeleteResult result
= new DeleteResult();
2973 TaxonBase
<?
> taxonBase
= load(taxonBaseUuid
);
2974 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(taxonBase
);
2975 if (taxonBase
instanceof Taxon
){
2976 TaxonDeletionConfigurator taxonConfig
= (TaxonDeletionConfigurator
) config
;
2977 result
= isDeletableForTaxon(references
, taxonConfig
);
2979 SynonymDeletionConfigurator synonymConfig
= (SynonymDeletionConfigurator
) config
;
2980 result
= isDeletableForSynonym(references
, synonymConfig
);
2985 private DeleteResult
isDeletableForSynonym(Set
<CdmBase
> references
, SynonymDeletionConfigurator config
){
2987 DeleteResult result
= new DeleteResult();
2988 for (CdmBase ref
: references
){
2989 if (!(ref
instanceof Taxon
|| ref
instanceof TaxonName
)){
2990 message
= "The Synonym can't be deleted as long as it is referenced by " + ref
.getClass().getSimpleName() + " with id "+ ref
.getId();
2991 result
.addException(new ReferencedObjectUndeletableException(message
));
2992 result
.addRelatedObject(ref
);
3000 private DeleteResult
isDeletableForTaxon(Set
<CdmBase
> references
, TaxonDeletionConfigurator config
){
3001 String message
= null;
3002 DeleteResult result
= new DeleteResult();
3003 for (CdmBase ref
: references
){
3004 if (!(ref
instanceof TaxonName
)){
3006 if (!config
.isDeleteSynonymRelations() && (ref
instanceof Synonym
)){
3007 message
= "The taxon can't be deleted as long as it has synonyms.";
3009 if (!config
.isDeleteDescriptions() && (ref
instanceof DescriptionBase
)){
3010 message
= "The taxon can't be deleted as long as it has factual data.";
3013 if (!config
.isDeleteTaxonNodes() && (ref
instanceof TaxonNode
)){
3014 message
= "The taxon can't be deleted as long as it belongs to a taxon node.";
3016 if (!config
.isDeleteTaxonRelationships() && (ref
instanceof TaxonRelationship
)){
3017 if (!config
.isDeleteMisappliedNamesAndInvalidDesignations() &&
3018 (((TaxonRelationship
)ref
).getType().isMisappliedNameOrInvalidDesignation())){
3019 message
= "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3021 message
= "The taxon can't be deleted as long as it belongs to taxon relationship.";
3024 if (ref
instanceof PolytomousKeyNode
){
3025 message
= "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3028 if (HibernateProxyHelper
.isInstanceOf(ref
, IIdentificationKey
.class)){
3029 message
= "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3033 /* //PolytomousKeyNode
3034 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3035 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3040 if (ref
.isInstanceOf(TaxonInteraction
.class)){
3041 message
= "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3045 if (ref
.isInstanceOf(DeterminationEvent
.class)){
3046 message
= "Taxon can't be deleted as it is used in a determination event";
3049 if (message
!= null){
3050 result
.addException(new ReferencedObjectUndeletableException(message
));
3051 result
.addRelatedObject(ref
);
3060 public IncludedTaxaDTO
listIncludedTaxa(UUID taxonUuid
, IncludedTaxonConfiguration config
) {
3061 IncludedTaxaDTO result
= new IncludedTaxaDTO(taxonUuid
);
3063 //preliminary implementation
3065 Set
<Taxon
> taxa
= new HashSet
<>();
3066 TaxonBase
<?
> taxonBase
= find(taxonUuid
);
3067 if (taxonBase
== null){
3068 return new IncludedTaxaDTO();
3069 }else if (taxonBase
.isInstanceOf(Taxon
.class)){
3070 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3072 }else if (taxonBase
.isInstanceOf(Synonym
.class)){
3073 //TODO partial synonyms ??
3074 //TODO synonyms in general
3075 Synonym syn
= CdmBase
.deproxy(taxonBase
, Synonym
.class);
3076 taxa
.add(syn
.getAcceptedTaxon());
3078 throw new IllegalArgumentException("Unhandled class " + taxonBase
.getClass().getSimpleName());
3081 Set
<Taxon
> related
= makeRelatedIncluded(taxa
, result
, config
);
3083 while((! related
.isEmpty()) && i
++ < 100){ //to avoid
3084 related
= makeRelatedIncluded(related
, result
, config
);
3091 * @param uncheckedTaxa
3092 * @param existingTaxa
3095 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3097 * @return the set of conceptually related taxa for further use
3099 private Set
<Taxon
> makeRelatedIncluded(Set
<Taxon
> uncheckedTaxa
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3102 Set
<TaxonNode
> taxonNodes
= new HashSet
<>();
3103 for (Taxon taxon
: uncheckedTaxa
){
3104 taxonNodes
.addAll(taxon
.getTaxonNodes());
3107 Set
<Taxon
> children
= new HashSet
<>();
3108 if (! config
.onlyCongruent
){
3109 for (TaxonNode node
: taxonNodes
){
3110 List
<TaxonNode
> childNodes
= nodeService
.loadChildNodesOfTaxonNode(node
, null, true, config
.includeUnpublished
, null);
3111 for (TaxonNode child
: childNodes
){
3112 children
.add(child
.getTaxon());
3115 children
.remove(null); // just to be on the save side
3118 Iterator
<Taxon
> it
= children
.iterator();
3119 while(it
.hasNext()){
3120 UUID uuid
= it
.next().getUuid();
3121 if (existingTaxa
.contains(uuid
)){
3124 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3129 Set
<Taxon
> uncheckedAndChildren
= new HashSet
<>(uncheckedTaxa
);
3130 uncheckedAndChildren
.addAll(children
);
3132 Set
<Taxon
> relatedTaxa
= makeConceptIncludedTaxa(uncheckedAndChildren
, existingTaxa
, config
);
3135 Set
<Taxon
> result
= new HashSet
<>(relatedTaxa
);
3140 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3141 * @return the set of these computed taxa
3143 private Set
<Taxon
> makeConceptIncludedTaxa(Set
<Taxon
> unchecked
, IncludedTaxaDTO existingTaxa
, IncludedTaxonConfiguration config
) {
3144 Set
<Taxon
> result
= new HashSet
<>();
3146 for (Taxon taxon
: unchecked
){
3147 Set
<TaxonRelationship
> fromRelations
= taxon
.getRelationsFromThisTaxon();
3148 Set
<TaxonRelationship
> toRelations
= taxon
.getRelationsToThisTaxon();
3150 for (TaxonRelationship fromRel
: fromRelations
){
3151 if (config
.includeDoubtful
== false && fromRel
.isDoubtful()){
3154 TaxonRelationshipType fromRelType
= fromRel
.getType();
3155 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3156 !config
.onlyCongruent
&& (
3157 fromRelType
.equals(TaxonRelationshipType
.INCLUDES()) ||
3158 fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_OR_INCLUDES())
3161 result
.add(fromRel
.getToTaxon());
3165 for (TaxonRelationship toRel
: toRelations
){
3166 if (config
.includeDoubtful
== false && toRel
.isDoubtful()){
3169 TaxonRelationshipType fromRelType
= toRel
.getType();
3170 if (fromRelType
.equals(TaxonRelationshipType
.CONGRUENT_TO()) ||
3171 !config
.includeDoubtful
&& fromRelType
.equals(TaxonRelationshipType
.TAXONOMICALLY_INCLUDED_IN())){
3172 result
.add(toRel
.getFromTaxon());
3177 Iterator
<Taxon
> it
= result
.iterator();
3178 while(it
.hasNext()){
3179 UUID uuid
= it
.next().getUuid();
3180 if (existingTaxa
.contains(uuid
)){
3183 existingTaxa
.addIncludedTaxon(uuid
, new ArrayList
<>(), false);
3190 public List
<TaxonBase
> findTaxaByName(MatchingTaxonConfigurator config
){
3191 List
<TaxonBase
> taxonList
= dao
.getTaxaByName(true, config
.isIncludeSynonyms(), false, false, false,
3192 config
.getTaxonNameTitle(), null, null, MatchMode
.EXACT
, null, config
.isIncludeSynonyms(), null, 0, 0, config
.getPropertyPath());
3197 @Transactional(readOnly
= true)
3198 public <S
extends TaxonBase
> Pager
<IdentifiedEntityDTO
<S
>> findByIdentifier(
3199 Class
<S
> clazz
, String identifier
, DefinedTerm identifierType
, TaxonNode subtreeFilter
,
3200 MatchMode matchmode
, boolean includeEntity
, Integer pageSize
,
3201 Integer pageNumber
, List
<String
> propertyPaths
) {
3202 if (subtreeFilter
== null){
3203 return findByIdentifier(clazz
, identifier
, identifierType
, matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3206 long numberOfResults
= dao
.countByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
, matchmode
);
3207 List
<Object
[]> daoResults
= new ArrayList
<>();
3208 if(numberOfResults
> 0) { // no point checking again
3209 daoResults
= dao
.findByIdentifier(clazz
, identifier
, identifierType
, subtreeFilter
,
3210 matchmode
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3213 List
<IdentifiedEntityDTO
<S
>> result
= new ArrayList
<>();
3214 for (Object
[] daoObj
: daoResults
){
3216 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (S
)daoObj
[2]));
3218 result
.add(new IdentifiedEntityDTO
<>((DefinedTerm
)daoObj
[0], (String
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3], null));
3221 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3225 @Transactional(readOnly
= true)
3226 public <S
extends TaxonBase
> Pager
<MarkedEntityDTO
<S
>> findByMarker(
3227 Class
<S
> clazz
, MarkerType markerType
, Boolean markerValue
,
3228 TaxonNode subtreeFilter
, boolean includeEntity
, TaxonTitleType titleType
,
3229 Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
3230 if (subtreeFilter
== null){
3231 return super.findByMarker (clazz
, markerType
, markerValue
, includeEntity
, pageSize
, pageNumber
, propertyPaths
);
3234 Long numberOfResults
= dao
.countByMarker(clazz
, markerType
, markerValue
, subtreeFilter
);
3235 List
<Object
[]> daoResults
= new ArrayList
<>();
3236 if(numberOfResults
> 0) { // no point checking again
3237 daoResults
= dao
.findByMarker(clazz
, markerType
, markerValue
, subtreeFilter
,
3238 includeEntity
, titleType
, pageSize
, pageNumber
, propertyPaths
);
3241 List
<MarkedEntityDTO
<S
>> result
= new ArrayList
<>();
3242 for (Object
[] daoObj
: daoResults
){
3244 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (S
)daoObj
[2]));
3246 result
.add(new MarkedEntityDTO
<S
>((MarkerType
)daoObj
[0], (Boolean
)daoObj
[1], (UUID
)daoObj
[2], (String
)daoObj
[3]));
3249 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, result
);
3253 @Transactional(readOnly
= false)
3254 public UpdateResult
moveSynonymToAnotherTaxon(Synonym oldSynonym
, UUID newTaxonUUID
, boolean moveHomotypicGroup
,
3255 SynonymType newSynonymType
, Reference newSecundum
, String newSecundumDetail
,
3256 boolean keepSecundumIfUndefined
) throws HomotypicalGroupChangeException
{
3258 UpdateResult result
= new UpdateResult();
3259 Taxon newTaxon
= CdmBase
.deproxy(dao
.load(newTaxonUUID
),Taxon
.class);
3260 result
= moveSynonymToAnotherTaxon(oldSynonym
, newTaxon
, moveHomotypicGroup
, newSynonymType
,
3261 newSecundum
, newSecundumDetail
, keepSecundumIfUndefined
);
3267 public UpdateResult
moveFactualDateToAnotherTaxon(UUID fromTaxonUuid
, UUID toTaxonUuid
){
3268 UpdateResult result
= new UpdateResult();
3270 Taxon fromTaxon
= (Taxon
)dao
.load(fromTaxonUuid
);
3271 Taxon toTaxon
= (Taxon
) dao
.load(toTaxonUuid
);
3272 for(TaxonDescription description
: fromTaxon
.getDescriptions()){
3273 //reload to avoid session conflicts
3274 description
= HibernateProxyHelper
.deproxy(description
, TaxonDescription
.class);
3276 String moveMessage
= String
.format("Description moved from %s", fromTaxon
);
3277 if(description
.isProtectedTitleCache()){
3278 String separator
= "";
3279 if(!StringUtils
.isBlank(description
.getTitleCache())){
3282 description
.setTitleCache(description
.getTitleCache() + separator
+ moveMessage
, true);
3284 Annotation annotation
= Annotation
.NewInstance(moveMessage
, Language
.getDefaultLanguage());
3285 annotation
.setAnnotationType(AnnotationType
.TECHNICAL());
3286 description
.addAnnotation(annotation
);
3287 toTaxon
.addDescription(description
);
3288 dao
.saveOrUpdate(toTaxon
);
3289 dao
.saveOrUpdate(fromTaxon
);
3290 result
.addUpdatedObject(toTaxon
);
3291 result
.addUpdatedObject(fromTaxon
);
3299 @Transactional(readOnly
= false)
3300 public UpdateResult
swapSynonymAndAcceptedTaxon(UUID synonymUUid
,
3301 UUID acceptedTaxonUuid
, boolean setNameInSource
) {
3302 TaxonBase
<?
> base
= this.load(synonymUUid
);
3303 Synonym syn
= HibernateProxyHelper
.deproxy(base
, Synonym
.class);
3304 base
= this.load(acceptedTaxonUuid
);
3305 Taxon taxon
= HibernateProxyHelper
.deproxy(base
, Taxon
.class);
3307 return this.swapSynonymAndAcceptedTaxon(syn
, taxon
, setNameInSource
);
3314 public TaxonRelationshipsDTO
listTaxonRelationships(UUID taxonUuid
, Set
<TaxonRelationshipType
> directTypes
,
3315 Set
<TaxonRelationshipType
> inversTypes
,
3316 Direction direction
, boolean groupMisapplications
,
3317 boolean includeUnpublished
,
3318 Integer pageSize
, Integer pageNumber
) {
3319 TaxonBase
<?
> taxonBase
= dao
.load(taxonUuid
);
3320 if (taxonBase
== null || !taxonBase
.isInstanceOf(TaxonBase
.class)){
3322 throw new RuntimeException("Taxon for uuid " + taxonUuid
+ " not found");
3324 Taxon taxon
= CdmBase
.deproxy(taxonBase
, Taxon
.class);
3325 boolean doDirect
= (direction
== null || direction
== Direction
.relatedTo
);
3326 boolean doInvers
= (direction
== null || direction
== Direction
.relatedFrom
);
3328 TaxonRelationshipsDTO dto
= new TaxonRelationshipsDTO();
3330 //TODO paging is difficult because misapplication string is an attribute
3332 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3333 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3334 // if(numberOfResults > 0) { // no point checking again
3335 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3338 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3341 List
<Language
> languages
= null;
3343 direction
= Direction
.relatedTo
;
3344 //TODO order hints, property path
3345 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, directTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3346 for (TaxonRelationship relation
: relations
){
3347 dto
.addRelation(relation
, direction
, languages
);
3351 direction
= Direction
.relatedFrom
;
3352 //TODO order hints, property path
3353 List
<TaxonRelationship
> relations
= dao
.getTaxonRelationships(taxon
, inversTypes
, includeUnpublished
, pageSize
, pageNumber
, null, null, direction
.invers());
3354 for (TaxonRelationship relation
: relations
){
3355 dto
.addRelation(relation
, direction
, languages
);
3358 if (groupMisapplications
){
3360 dto
.createMisapplicationString();