861a67d14c76c6a74de62eb564bbeea31ef2c488
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / TaxonServiceImpl.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9
10 package eu.etaxonomy.cdm.api.service;
11
12 import java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.EnumSet;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.UUID;
23 import java.util.stream.Collectors;
24
25 import javax.persistence.EntityNotFoundException;
26
27 import org.apache.commons.lang3.StringUtils;
28 import org.apache.log4j.Logger;
29 import org.apache.lucene.queryparser.classic.ParseException;
30 import org.apache.lucene.search.BooleanClause.Occur;
31 import org.apache.lucene.search.BooleanQuery;
32 import org.apache.lucene.search.BooleanQuery.Builder;
33 import org.apache.lucene.search.Query;
34 import org.apache.lucene.search.SortField;
35 import org.apache.lucene.search.grouping.TopGroups;
36 import org.apache.lucene.search.join.ScoreMode;
37 import org.apache.lucene.util.BytesRef;
38 import org.hibernate.criterion.Criterion;
39 import org.springframework.beans.factory.annotation.Autowired;
40 import org.springframework.stereotype.Service;
41 import org.springframework.transaction.annotation.Transactional;
42
43 import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
44 import eu.etaxonomy.cdm.api.service.config.IFindTaxaAndNamesConfigurator;
45 import eu.etaxonomy.cdm.api.service.config.IncludedTaxonConfiguration;
46 import eu.etaxonomy.cdm.api.service.config.MatchingTaxonConfigurator;
47 import eu.etaxonomy.cdm.api.service.config.NodeDeletionConfigurator.ChildHandling;
48 import eu.etaxonomy.cdm.api.service.config.SynonymDeletionConfigurator;
49 import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
50 import eu.etaxonomy.cdm.api.service.dto.IdentifiedEntityDTO;
51 import eu.etaxonomy.cdm.api.service.dto.IncludedTaxaDTO;
52 import eu.etaxonomy.cdm.api.service.dto.MarkedEntityDTO;
53 import eu.etaxonomy.cdm.api.service.dto.TaxonRelationshipsDTO;
54 import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
55 import eu.etaxonomy.cdm.api.service.exception.HomotypicalGroupChangeException;
56 import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
57 import eu.etaxonomy.cdm.api.service.pager.Pager;
58 import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
59 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
60 import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
61 import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
62 import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearch;
63 import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearchException;
64 import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
65 import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
66 import eu.etaxonomy.cdm.api.service.search.QueryFactory;
67 import eu.etaxonomy.cdm.api.service.search.SearchResult;
68 import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
69 import eu.etaxonomy.cdm.api.util.TaxonRelationshipEdge;
70 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
71 import eu.etaxonomy.cdm.compare.taxon.HomotypicGroupTaxonComparator;
72 import eu.etaxonomy.cdm.compare.taxon.TaxonComparator;
73 import eu.etaxonomy.cdm.exception.UnpublishedException;
74 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
75 import eu.etaxonomy.cdm.hibernate.search.AcceptedTaxonBridge;
76 import eu.etaxonomy.cdm.hibernate.search.GroupByTaxonClassBridge;
77 import eu.etaxonomy.cdm.model.CdmBaseType;
78 import eu.etaxonomy.cdm.model.common.Annotation;
79 import eu.etaxonomy.cdm.model.common.AnnotationType;
80 import eu.etaxonomy.cdm.model.common.CdmBase;
81 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
82 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
83 import eu.etaxonomy.cdm.model.common.Language;
84 import eu.etaxonomy.cdm.model.common.MarkerType;
85 import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
86 import eu.etaxonomy.cdm.model.description.CommonTaxonName;
87 import eu.etaxonomy.cdm.model.description.DescriptionBase;
88 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
89 import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
90 import eu.etaxonomy.cdm.model.description.Distribution;
91 import eu.etaxonomy.cdm.model.description.Feature;
92 import eu.etaxonomy.cdm.model.description.IIdentificationKey;
93 import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
94 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
95 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
96 import eu.etaxonomy.cdm.model.description.TaxonDescription;
97 import eu.etaxonomy.cdm.model.description.TaxonInteraction;
98 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
99 import eu.etaxonomy.cdm.model.location.NamedArea;
100 import eu.etaxonomy.cdm.model.media.ExternalLink;
101 import eu.etaxonomy.cdm.model.media.ExternalLinkType;
102 import eu.etaxonomy.cdm.model.media.Media;
103 import eu.etaxonomy.cdm.model.metadata.SecReferenceHandlingEnum;
104 import eu.etaxonomy.cdm.model.metadata.SecReferenceHandlingSwapEnum;
105 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
106 import eu.etaxonomy.cdm.model.name.IZoologicalName;
107 import eu.etaxonomy.cdm.model.name.Rank;
108 import eu.etaxonomy.cdm.model.name.TaxonName;
109 import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
110 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
111 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
112 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
113 import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
114 import eu.etaxonomy.cdm.model.reference.Reference;
115 import eu.etaxonomy.cdm.model.taxon.Classification;
116 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
117 import eu.etaxonomy.cdm.model.taxon.SecundumSource;
118 import eu.etaxonomy.cdm.model.taxon.Synonym;
119 import eu.etaxonomy.cdm.model.taxon.SynonymType;
120 import eu.etaxonomy.cdm.model.taxon.Taxon;
121 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
122 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
123 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
124 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
125 import eu.etaxonomy.cdm.model.term.DefinedTerm;
126 import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
127 import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
128 import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
129 import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
130 import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
131 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
132 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
133 import eu.etaxonomy.cdm.persistence.dto.MergeResult;
134 import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
135 import eu.etaxonomy.cdm.persistence.query.MatchMode;
136 import eu.etaxonomy.cdm.persistence.query.OrderHint;
137 import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
138 import eu.etaxonomy.cdm.persistence.query.TaxonTitleType;
139 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
140
141
142 /**
143 * @author a.kohlbecker
144 * @since 10.09.2010
145 */
146 @Service
147 @Transactional(readOnly = true)
148 public class TaxonServiceImpl
149 extends IdentifiableServiceBase<TaxonBase,ITaxonDao>
150 implements ITaxonService{
151
152 private static final Logger logger = Logger.getLogger(TaxonServiceImpl.class);
153
154 public static final String POTENTIAL_COMBINATION_NAMESPACE = "Potential combination";
155
156 public static final String INFERRED_EPITHET_NAMESPACE = "Inferred epithet";
157
158 public static final String INFERRED_GENUS_NAMESPACE = "Inferred genus";
159
160 @Autowired
161 private ITaxonNodeDao taxonNodeDao;
162
163 @Autowired
164 private ITaxonNameDao nameDao;
165
166 @Autowired
167 private INameService nameService;
168
169 @Autowired
170 private IOccurrenceService occurrenceService;
171
172 @Autowired
173 private ITaxonNodeService nodeService;
174
175 @Autowired
176 private IDescriptionService descriptionService;
177
178 @Autowired
179 private IReferenceService referenceService;
180 //
181 // @Autowired
182 // private IOrderedTermVocabularyDao orderedVocabularyDao;
183
184 @Autowired
185 private IOccurrenceDao occurrenceDao;
186
187 @Autowired
188 private IClassificationDao classificationDao;
189
190 @Autowired
191 private AbstractBeanInitializer beanInitializer;
192
193 @Autowired
194 private ILuceneIndexToolProvider luceneIndexToolProvider;
195
196 //************************ CONSTRUCTOR ****************************/
197 public TaxonServiceImpl(){
198 if (logger.isDebugEnabled()) { logger.debug("Load TaxonService Bean"); }
199 }
200
201 // ****************************** METHODS ********************************/
202
203 @Override
204 public TaxonBase load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
205 return dao.load(uuid, includeUnpublished, propertyPaths);
206 }
207
208 @Override
209 public List<TaxonBase> searchByName(String name, boolean includeUnpublished, Reference sec) {
210 return dao.getTaxaByName(name, includeUnpublished, sec);
211 }
212
213 @Override
214 @Transactional(readOnly = false)
215 public UpdateResult swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean setNameInSource, boolean newUuidForAcceptedTaxon, SecReferenceHandlingSwapEnum secHandling, Reference newSecAcc, Reference newSecSyn){
216 if (newUuidForAcceptedTaxon){
217 return swapSynonymAndAcceptedTaxonNewUuid(synonym, acceptedTaxon, setNameInSource, secHandling, newSecAcc, newSecSyn);
218 }else{
219 return swapSynonymAndAcceptedTaxon(synonym, acceptedTaxon, setNameInSource, secHandling, newSecAcc, newSecSyn);
220 }
221 }
222
223 private UpdateResult swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean setNameInSource, SecReferenceHandlingSwapEnum secHandling, Reference newSecAcc, Reference newSecSyn){
224 UpdateResult result = new UpdateResult();
225 String oldTaxonTitleCache = acceptedTaxon.getTitleCache();
226
227 TaxonName synonymName = synonym.getName();
228 TaxonName taxonName = HibernateProxyHelper.deproxy(acceptedTaxon.getName());
229 Reference secAccepted = acceptedTaxon.getSec();
230 String microRefSecAccepted = acceptedTaxon.getSecMicroReference();
231 Reference secSynonym = synonym.getSec();
232 String microRefSecSynonym = synonym.getSecMicroReference();
233
234 // if (secHandling.equals(SecReferenceHandlingSwapEnum.AlwaysDelete) || (secAccepted != null && secSynonym != null && !secAccepted.getUuid().equals(secSynonym.getUuid()) && (secHandling.equals(SecReferenceHandlingSwapEnum.AlwaysSelect) || secHandling.equals(SecReferenceHandlingSwapEnum.KeepOrSelect)))){
235 // secAccepted = null;
236 // microRefSecAccepted = null;
237 // secSynonym = null;
238 // microRefSecSynonym = null;
239 // }
240
241
242 Set<ExternalLink> accLinks = new HashSet<>();
243 if (acceptedTaxon.getSecSource() != null){
244 for (ExternalLink link: acceptedTaxon.getSecSource().getLinks()){
245 accLinks.add(ExternalLink.NewInstance(ExternalLinkType.Unknown, link.getUri()));
246 }
247 }
248 acceptedTaxon.setName(synonymName);
249 acceptedTaxon.setSec(newSecAcc);
250 // acceptedTaxon.setSecMicroReference(synonym.getSecMicroReference());
251
252 // if (synonym.getSecSource()!= null && synonym.getSecSource().getLinks() != null){
253 // acceptedTaxon.getSecSource().getLinks().clear();
254 // for (ExternalLink link: synonym.getSecSource().getLinks()){
255 // acceptedTaxon.getSecSource().addLink(ExternalLink.NewInstance(ExternalLinkType.Unknown, link.getUri()));
256 // }
257 // }
258
259 synonym.setName(taxonName);
260 synonym.setSec(newSecSyn);
261 // synonym.setSecMicroReference(microRefSecAccepted);
262 // if (synonym.getSecSource() != null){
263 // synonym.getSecSource().getLinks().clear();
264 // for (ExternalLink link: accLinks){
265 // synonym.getSecSource().addLink(link);
266 // }
267 // }
268
269 //nameUsedInSource
270 handleNameUsedInSourceForSwap(setNameInSource, taxonName, oldTaxonTitleCache, acceptedTaxon.getDescriptions());
271
272 acceptedTaxon.resetTitleCache();
273 synonym.resetTitleCache();
274
275 MergeResult mergeTaxon = merge(acceptedTaxon, true);
276 MergeResult mergeSynonym = merge(synonym, true);
277 result.setCdmEntity((CdmBase) mergeTaxon.getMergedEntity());
278 result.addUpdatedObject((CdmBase) mergeSynonym.getMergedEntity());
279
280 return result;
281 }
282
283 private UpdateResult swapSynonymAndAcceptedTaxonNewUuid(Synonym synonym, Taxon acceptedTaxon, boolean setNameInSource, SecReferenceHandlingSwapEnum secHandling, Reference newSecAcc, Reference newSecSyn){
284 UpdateResult result = new UpdateResult();
285 acceptedTaxon.removeSynonym(synonym);
286 TaxonName synonymName = synonym.getName();
287 TaxonName taxonName = HibernateProxyHelper.deproxy(acceptedTaxon.getName());
288 String oldTaxonTitleCache = acceptedTaxon.getTitleCache();
289
290 boolean sameHomotypicGroup = synonymName.getHomotypicalGroup().equals(taxonName.getHomotypicalGroup());
291 synonymName.removeTaxonBase(synonym);
292
293 List<Synonym> synonyms = new ArrayList<>();
294 for (Synonym syn: acceptedTaxon.getSynonyms()){
295 syn = HibernateProxyHelper.deproxy(syn, Synonym.class);
296 synonyms.add(syn);
297 }
298 for (Synonym syn: synonyms){
299 acceptedTaxon.removeSynonym(syn);
300 }
301 Taxon newTaxon = acceptedTaxon.clone(true, true, false, true);
302 newTaxon.setSec(newSecAcc);
303
304 //move descriptions
305 Set<TaxonDescription> descriptionsToCopy = new HashSet<>(acceptedTaxon.getDescriptions());
306 for (TaxonDescription description: descriptionsToCopy){
307 newTaxon.addDescription(description);
308 }
309 //nameUsedInSource
310 handleNameUsedInSourceForSwap(setNameInSource, taxonName, oldTaxonTitleCache, newTaxon.getDescriptions());
311
312 newTaxon.setName(synonymName);
313
314 newTaxon.setPublish(synonym.isPublish());
315 for (Synonym syn: synonyms){
316 if (!syn.getName().equals(newTaxon.getName())){
317 newTaxon.addSynonym(syn, syn.getType());
318 }
319 }
320
321 //move all data to new taxon
322 //Move Taxon RelationShips to new Taxon
323 for(TaxonRelationship taxonRelationship : newTaxon.getTaxonRelations()){
324 newTaxon.removeTaxonRelation(taxonRelationship);
325 }
326
327 for(TaxonRelationship taxonRelationship : acceptedTaxon.getTaxonRelations()){
328 Taxon fromTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getFromTaxon());
329 Taxon toTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getToTaxon());
330 if (fromTaxon == acceptedTaxon){
331 newTaxon.addTaxonRelation(taxonRelationship.getToTaxon(), taxonRelationship.getType(),
332 taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
333
334 }else if(toTaxon == acceptedTaxon){
335 fromTaxon.addTaxonRelation(newTaxon, taxonRelationship.getType(),
336 taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
337 saveOrUpdate(fromTaxon);
338
339 }else{
340 logger.warn("Taxon is not part of its own Taxonrelationship");
341 }
342
343 // Remove old relationships
344 fromTaxon.removeTaxonRelation(taxonRelationship);
345 toTaxon.removeTaxonRelation(taxonRelationship);
346 taxonRelationship.setToTaxon(null);
347 taxonRelationship.setFromTaxon(null);
348 }
349
350 //taxon nodes
351 List<TaxonNode> nodes = new ArrayList<>(acceptedTaxon.getTaxonNodes());
352 for (TaxonNode node: nodes){
353 node = HibernateProxyHelper.deproxy(node);
354 TaxonNode parent = node.getParent();
355 acceptedTaxon.removeTaxonNode(node);
356 node.setTaxon(newTaxon);
357 if (parent != null){
358 parent.addChildNode(node, null, null);
359 }
360 }
361
362 //synonym
363 Synonym newSynonym = synonym.clone();
364 newSynonym.setName(taxonName);
365 newSynonym.setPublish(acceptedTaxon.isPublish());
366 newSynonym.setSec(newSecSyn);
367 if (sameHomotypicGroup){
368 newTaxon.addSynonym(newSynonym, SynonymType.HOMOTYPIC_SYNONYM_OF());
369 }else{
370 newTaxon.addSynonym(newSynonym, SynonymType.HETEROTYPIC_SYNONYM_OF());
371 }
372
373 //deletes
374 TaxonDeletionConfigurator conf = new TaxonDeletionConfigurator();
375 conf.setDeleteNameIfPossible(false);
376 SynonymDeletionConfigurator confSyn = new SynonymDeletionConfigurator();
377 confSyn.setDeleteNameIfPossible(false);
378 result.setCdmEntity(newTaxon);
379
380 DeleteResult deleteResult = deleteTaxon(acceptedTaxon.getUuid(), conf, null);
381 if (synonym.isPersited()){
382 synonym.setSecSource(null);
383 deleteResult.includeResult(deleteSynonym(synonym.getUuid(), confSyn));
384 }
385 result.includeResult(deleteResult);
386
387 return result;
388 }
389
390 private void handleNameUsedInSourceForSwap(boolean setNameInSource, TaxonName taxonName, String oldTaxonTitleCache,
391 Set<TaxonDescription> descriptions) {
392 for(TaxonDescription description : descriptions){
393 String message = "Description copied from former accepted taxon: %s (Old title: %s)";
394 message = String.format(message, oldTaxonTitleCache, description.getTitleCache());
395 description.setTitleCache(message, true);
396 if(setNameInSource){
397 for (DescriptionElementBase element: description.getElements()){
398 for (DescriptionElementSource source: element.getSources()){
399 if (source.getNameUsedInSource() == null){
400 source.setNameUsedInSource(taxonName);
401 }
402 }
403 }
404 }
405 }
406 }
407
408 @Override
409 @Transactional(readOnly = false)
410 public UpdateResult changeSynonymToAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, Reference newSecRef, String microRef, SecReferenceHandlingEnum secHandling, boolean deleteSynonym) {
411 UpdateResult result = new UpdateResult();
412 TaxonName acceptedName = acceptedTaxon.getName();
413 TaxonName synonymName = synonym.getName();
414 HomotypicalGroup synonymHomotypicGroup = synonymName.getHomotypicalGroup();
415
416 //check synonym is not homotypic
417 if (acceptedName.getHomotypicalGroup().equals(synonymHomotypicGroup)){
418 String message = "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
419 result.addException(new HomotypicalGroupChangeException(message));
420 result.setAbort();
421 return result;
422 }
423
424 Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, newSecRef, microRef);
425 newAcceptedTaxon.setPublish(synonym.isPublish());
426 dao.save(newAcceptedTaxon);
427 result.setCdmEntity(newAcceptedTaxon);
428 SynonymType relTypeForGroup = SynonymType.HOMOTYPIC_SYNONYM_OF();
429 List<Synonym> heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup);
430
431 for (Synonym heteroSynonym : heteroSynonyms){
432 if (secHandling == null){
433 heteroSynonym.setSec(newSecRef);
434 }
435 if (synonym.equals(heteroSynonym)){
436 acceptedTaxon.removeSynonym(heteroSynonym, false);
437 }else{
438 //move synonyms in same homotypic group to new accepted taxon
439 newAcceptedTaxon.addSynonym(heteroSynonym, relTypeForGroup);
440 }
441 }
442 dao.saveOrUpdate(acceptedTaxon);
443 result.addUpdatedObject(acceptedTaxon);
444 if (deleteSynonym){
445
446 try {
447 this.dao.flush();
448 SynonymDeletionConfigurator config = new SynonymDeletionConfigurator();
449 config.setDeleteNameIfPossible(false);
450 this.deleteSynonym(synonym, config);
451
452 } catch (Exception e) {
453 result.addException(e);
454 }
455 }
456
457 return result;
458 }
459
460 @Override
461 @Transactional(readOnly = false)
462 public UpdateResult changeSynonymToAcceptedTaxon(UUID synonymUuid,
463 UUID acceptedTaxonUuid,
464 UUID newParentNodeUuid,
465 UUID newSec,
466 String microReference,
467 SecReferenceHandlingEnum secHandling,
468 boolean deleteSynonym) {
469 UpdateResult result = new UpdateResult();
470 Synonym synonym = CdmBase.deproxy(dao.load(synonymUuid), Synonym.class);
471 Taxon acceptedTaxon = CdmBase.deproxy(dao.load(acceptedTaxonUuid), Taxon.class);
472 TaxonNode newParentNode = taxonNodeDao.load(newParentNodeUuid);
473 Reference newSecRef = null;
474 switch (secHandling){
475 case AlwaysDelete:
476 newSecRef = null;
477 break;
478 case UseNewParentSec:
479 newSecRef = newParentNode.getTaxon() != null? newParentNode.getTaxon().getSec(): null;
480 break;
481 case KeepOrWarn:
482 Reference parentSec = newParentNode.getTaxon() != null? newParentNode.getTaxon().getSec(): null;
483 Reference synSec = synonym.getSec();
484 if (synSec != null ){
485 newSecRef = CdmBase.deproxy(synSec);
486 }else{
487 newSecRef = CdmBase.deproxy(referenceService.load(newSec));
488 }
489 break;
490 case KeepOrSelect:
491 newSecRef = CdmBase.deproxy(referenceService.load(newSec));
492 break;
493 default:
494 break;
495 }
496
497 result = changeSynonymToAcceptedTaxon(synonym, acceptedTaxon, newSecRef, microReference, secHandling, deleteSynonym);
498 Taxon newTaxon = (Taxon)result.getCdmEntity();
499
500 TaxonNode newNode = newParentNode.addChildTaxon(newTaxon, null, null);
501 taxonNodeDao.save(newNode);
502 result.addUpdatedObject(newTaxon);
503 result.addUpdatedObject(acceptedTaxon);
504 result.setCdmEntity(newNode);
505 return result;
506 }
507
508 @Override
509 @Transactional(readOnly = false)
510 public UpdateResult changeSynonymToRelatedTaxon(UUID synonymUuid,
511 UUID toTaxonUuid,
512 TaxonRelationshipType taxonRelationshipType,
513 Reference citation,
514 String microcitation){
515
516 UpdateResult result = new UpdateResult();
517 Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
518 Synonym synonym = (Synonym) dao.load(synonymUuid);
519 result = changeSynonymToRelatedTaxon(synonym, toTaxon, taxonRelationshipType, citation, microcitation);
520 Taxon relatedTaxon = (Taxon)result.getCdmEntity();
521 // result.setCdmEntity(relatedTaxon);
522 result.addUpdatedObject(relatedTaxon);
523 result.addUpdatedObject(toTaxon);
524 return result;
525 }
526
527 @Override
528 @Transactional(readOnly = false)
529 public UpdateResult changeSynonymToRelatedTaxon(Synonym synonym, Taxon toTaxon, TaxonRelationshipType taxonRelationshipType, Reference citation, String microcitation){
530 // Get name from synonym
531 if (synonym == null){
532 return null;
533 }
534
535 UpdateResult result = new UpdateResult();
536
537 TaxonName synonymName = synonym.getName();
538
539 /* // remove synonym from taxon
540 toTaxon.removeSynonym(synonym);
541 */
542 // Create a taxon with synonym name
543 Taxon fromTaxon = Taxon.NewInstance(synonymName, null);
544 fromTaxon.setPublish(synonym.isPublish());
545 save(fromTaxon);
546 fromTaxon.setAppendedPhrase(synonym.getAppendedPhrase());
547
548 // Add taxon relation
549 fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
550 result.setCdmEntity(fromTaxon);
551 // since we are swapping names, we have to detach the name from the synonym completely.
552 // Otherwise the synonym will still be in the list of typified names.
553 // synonym.getName().removeTaxonBase(synonym);
554 result.includeResult(this.deleteSynonym(synonym, null));
555
556 return result;
557 }
558
559 @Transactional(readOnly = false)
560 @Override
561 public void changeHomotypicalGroupOfSynonym(Synonym synonym, HomotypicalGroup newHomotypicalGroup,
562 Taxon targetTaxon, boolean setBasionymRelationIfApplicable){
563 // Get synonym name
564 TaxonName synonymName = synonym.getName();
565 HomotypicalGroup oldHomotypicalGroup = synonymName.getHomotypicalGroup();
566
567 // Switch groups
568 oldHomotypicalGroup.removeTypifiedName(synonymName, false);
569 newHomotypicalGroup.addTypifiedName(synonymName);
570
571 //remove existing basionym relationships
572 synonymName.removeBasionyms();
573
574 //add basionym relationship
575 if (setBasionymRelationIfApplicable){
576 Set<TaxonName> basionyms = newHomotypicalGroup.getBasionyms();
577 for (TaxonName basionym : basionyms){
578 synonymName.addBasionym(basionym);
579 }
580 }
581
582 //set synonym relationship correctly
583 Taxon acceptedTaxon = synonym.getAcceptedTaxon();
584
585 boolean hasNewTargetTaxon = targetTaxon != null && !targetTaxon.equals(acceptedTaxon);
586 if (acceptedTaxon != null){
587
588 HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
589 boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
590 SynonymType newRelationType = isHomotypicToTaxon? SynonymType.HOMOTYPIC_SYNONYM_OF() : SynonymType.HETEROTYPIC_SYNONYM_OF();
591 synonym.setType(newRelationType);
592
593 if (hasNewTargetTaxon){
594 acceptedTaxon.removeSynonym(synonym, false);
595 }
596 }
597 if (hasNewTargetTaxon ){
598 @SuppressWarnings("null")
599 HomotypicalGroup acceptedGroup = targetTaxon.getHomotypicGroup();
600 boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
601 SynonymType relType = isHomotypicToTaxon? SynonymType.HOMOTYPIC_SYNONYM_OF() : SynonymType.HETEROTYPIC_SYNONYM_OF();
602 targetTaxon.addSynonym(synonym, relType);
603 }
604 }
605
606 @Override
607 @Transactional(readOnly = false)
608 public UpdateResult updateCaches(Class<? extends TaxonBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonBase> cacheStrategy, IProgressMonitor monitor) {
609 if (clazz == null){
610 clazz = TaxonBase.class;
611 }
612 return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);
613 }
614
615 @Override
616 @Autowired
617 protected void setDao(ITaxonDao dao) {
618 this.dao = dao;
619 }
620
621 @Override
622 public <T extends TaxonBase> Pager<T> findTaxaByName(Class<T> clazz, String uninomial, String infragenericEpithet, String specificEpithet,
623 String infraspecificEpithet, String authorshipCache, Rank rank, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {
624 long numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, authorshipCache, rank);
625
626 List<T> results = new ArrayList<>();
627 if(numberOfResults > 0) { // no point checking again
628 results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, authorshipCache, rank,
629 pageSize, pageNumber, propertyPaths);
630 }
631
632 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
633 }
634
635 @Override
636 public <T extends TaxonBase> List<T> listTaxaByName(Class<T> clazz, String uninomial, String infragenericEpithet, String specificEpithet,
637 String infraspecificEpithet, String authorshipCache, Rank rank, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {
638
639 return findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infragenericEpithet, authorshipCache, rank,
640 pageSize, pageNumber, propertyPaths).getRecords();
641 }
642
643 @Override
644 public List<TaxonRelationship> listToTaxonRelationships(Taxon taxon, TaxonRelationshipType type,
645 boolean includeUnpublished, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
646 long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedTo);
647
648 List<TaxonRelationship> results = new ArrayList<>();
649 if(numberOfResults > 0) { // no point checking again
650 results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
651 }
652 return results;
653 }
654
655 @Override
656 public Pager<TaxonRelationship> pageToTaxonRelationships(Taxon taxon, TaxonRelationshipType type,
657 boolean includeUnpublished, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
658 long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedTo);
659
660 List<TaxonRelationship> results = new ArrayList<>();
661 if(numberOfResults > 0) { // no point checking again
662 results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
663 }
664 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
665 }
666
667 @Override
668 public List<TaxonRelationship> listFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type,
669 boolean includeUnpublished, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
670 long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
671
672 List<TaxonRelationship> results = new ArrayList<>();
673 if(numberOfResults > 0) { // no point checking again
674 results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
675 }
676 return results;
677 }
678
679 @Override
680 public Pager<TaxonRelationship> pageFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type,
681 boolean includeUnpublished, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
682 long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
683
684 List<TaxonRelationship> results = new ArrayList<>();
685 if(numberOfResults > 0) { // no point checking again
686 results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
687 }
688 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
689 }
690
691 @Override
692 public List<TaxonRelationship> listTaxonRelationships(Set<TaxonRelationshipType> types,
693 Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
694
695 Long numberOfResults = dao.countTaxonRelationships(types);
696 List<TaxonRelationship> results = new ArrayList<>();
697 if(numberOfResults > 0) {
698 results = dao.getTaxonRelationships(types, pageSize, pageNumber, orderHints, propertyPaths);
699 }
700 return results;
701 }
702
703 @Override
704 public <S extends TaxonBase> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
705 Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths) {
706 return page(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
707 }
708
709 @Override
710 public <S extends TaxonBase> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
711 Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths, boolean includeUnpublished) {
712
713 List<S> records;
714 long resultSize = dao.count(clazz, restrictions);
715 if(AbstractPagerImpl.hasResultsInRange(resultSize, pageIndex, pageSize)){
716 records = dao.list(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, includeUnpublished);
717 } else {
718 records = new ArrayList<>();
719 }
720 Pager<S> pager = new DefaultPagerImpl<>(pageIndex, resultSize, pageSize, records);
721 return pager;
722 }
723
724 @Override
725 public Taxon findAcceptedTaxonFor(UUID synonymUuid, UUID classificationUuid,
726 boolean includeUnpublished, List<String> propertyPaths) throws UnpublishedException{
727
728 Synonym synonym = null;
729
730 try {
731 synonym = (Synonym) dao.load(synonymUuid);
732 checkPublished(synonym, includeUnpublished, "Synoym is unpublished.");
733 } catch (ClassCastException e){
734 throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid + " is not a Synonmy");
735 } catch (NullPointerException e){
736 throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid);
737 }
738
739 Classification classificationFilter = null;
740 if(classificationUuid != null){
741 try {
742 classificationFilter = classificationDao.load(classificationUuid);
743 } catch (NullPointerException e){
744 //TODO not sure, why an NPE should be thrown in the above load method
745 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid);
746 }
747 if(classificationFilter == null){
748 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid);
749 }
750 }
751
752 long count = dao.countAcceptedTaxonFor(synonym, classificationFilter) ;
753 if(count > 0){
754 Taxon result = dao.acceptedTaxonFor(synonym, classificationFilter, propertyPaths);
755 checkPublished(result, includeUnpublished, "Accepted taxon unpublished");
756 return result;
757 }else{
758 return null;
759 }
760 }
761
762 @Override
763 public Set<Taxon> listRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Integer maxDepth,
764 boolean includeUnpublished, Integer limit, Integer start, List<String> propertyPaths) {
765
766 Set<Taxon> relatedTaxa = collectRelatedTaxa(taxon, includeRelationships, new HashSet<>(), includeUnpublished, maxDepth);
767 relatedTaxa.remove(taxon);
768 beanInitializer.initializeAll(relatedTaxa, propertyPaths);
769 return relatedTaxa;
770 }
771
772 /**
773 * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
774 * <code>taxon</code> supplied as parameter.
775 *
776 * @param taxon
777 * @param includeRelationships
778 * @param taxa
779 * @param maxDepth can be <code>null</code> for infinite depth
780 * @return
781 */
782 private Set<Taxon> collectRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Set<Taxon> taxa,
783 boolean includeUnpublished, Integer maxDepth) {
784
785 if(taxa.isEmpty()) {
786 taxa.add(taxon);
787 }
788
789 if(includeRelationships.isEmpty()){
790 return taxa;
791 }
792
793 if(maxDepth != null) {
794 maxDepth--;
795 }
796 if(logger.isDebugEnabled()){
797 logger.debug("collecting related taxa for " + taxon + " with maxDepth=" + maxDepth);
798 }
799 List<TaxonRelationship> taxonRelationships = dao.getTaxonRelationships(taxon,
800 (Set<TaxonRelationshipType>)null, includeUnpublished, null, null, null, null, null);
801 for (TaxonRelationship taxRel : taxonRelationships) {
802
803 // skip invalid data
804 if (taxRel.getToTaxon() == null || taxRel.getFromTaxon() == null || taxRel.getType() == null) {
805 continue;
806 }
807 // filter by includeRelationships
808 for (TaxonRelationshipEdge relationshipEdgeFilter : includeRelationships) {
809 if ( relationshipEdgeFilter.getRelationshipTypes().equals(taxRel.getType()) ) {
810 if (relationshipEdgeFilter.getDirections().contains(Direction.relatedTo) && !taxa.contains(taxRel.getToTaxon())) {
811 if(logger.isDebugEnabled()){
812 logger.debug(maxDepth + ": " + taxon.getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxRel.getToTaxon().getTitleCache());
813 }
814 taxa.add(taxRel.getToTaxon());
815 if(maxDepth == null || maxDepth > 0) {
816 taxa.addAll(collectRelatedTaxa(taxRel.getToTaxon(), includeRelationships, taxa, includeUnpublished, maxDepth));
817 }
818 }
819 if(relationshipEdgeFilter.getDirections().contains(Direction.relatedFrom) && !taxa.contains(taxRel.getFromTaxon())) {
820 taxa.add(taxRel.getFromTaxon());
821 if(logger.isDebugEnabled()){
822 logger.debug(maxDepth + ": " +taxRel.getFromTaxon().getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxon.getTitleCache() );
823 }
824 if(maxDepth == null || maxDepth > 0) {
825 taxa.addAll(collectRelatedTaxa(taxRel.getFromTaxon(), includeRelationships, taxa, includeUnpublished, maxDepth));
826 }
827 }
828 }
829 }
830 }
831 return taxa;
832 }
833
834 @Override
835 public Pager<Synonym> getSynonyms(Taxon taxon, SynonymType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
836 Long numberOfResults = dao.countSynonyms(taxon, type);
837
838 List<Synonym> results = new ArrayList<>();
839 if(numberOfResults > 0) { // no point checking again
840 results = dao.getSynonyms(taxon, type, pageSize, pageNumber, orderHints, propertyPaths);
841 }
842
843 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
844 }
845
846 @Override
847 public List<List<Synonym>> getSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
848 List<List<Synonym>> result = new ArrayList<>();
849 taxon = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
850 HomotypicGroupTaxonComparator comparator = new HomotypicGroupTaxonComparator(taxon);
851
852 //homotypic
853 result.add(taxon.getHomotypicSynonymsByHomotypicGroup(comparator));
854
855 //heterotypic
856 List<HomotypicalGroup> homotypicalGroups = taxon.getHeterotypicSynonymyGroups(); //currently the list is sorted by the Taxon.defaultTaxonComparator
857 for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
858 result.add(taxon.getSynonymsInGroup(homotypicalGroup, comparator));
859 }
860
861 return result;
862 }
863
864 @Override
865 public List<Synonym> getHomotypicSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
866 Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
867 HomotypicGroupTaxonComparator comparator = new HomotypicGroupTaxonComparator(taxon);
868
869 return t.getHomotypicSynonymsByHomotypicGroup(comparator);
870 }
871
872 @Override
873 public List<List<Synonym>> getHeterotypicSynonymyGroups(Taxon taxon, List<String> propertyPaths){
874 Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
875 List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
876 List<List<Synonym>> heterotypicSynonymyGroups = new ArrayList<>(homotypicalGroups.size());
877 for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
878 heterotypicSynonymyGroups.add(t.getSynonymsInGroup(homotypicalGroup));
879 }
880 return heterotypicSynonymyGroups;
881 }
882
883 @Override
884 public List<UuidAndTitleCache<? extends IdentifiableEntity>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator config){
885
886 if (config.isDoSynonyms() || config.isDoTaxa() || config.isDoNamesWithoutTaxa() || config.isDoTaxaByCommonNames()){
887 return dao.getTaxaByNameForEditor(config.isDoTaxa(), config.isDoSynonyms(), config.isDoNamesWithoutTaxa(),
888 config.isDoMisappliedNames(), config.isDoTaxaByCommonNames(), config.isIncludeUnpublished(), config.isDoIncludeAuthors(),
889 config.getTitleSearchStringSqlized(), config.getClassification(), config.getSubtree(),
890 config.getMatchMode(), config.getNamedAreas(), config.getOrder());
891 }else{
892 return new ArrayList<>();
893 }
894 }
895
896 @Override
897 public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
898
899 @SuppressWarnings("rawtypes")
900 List<IdentifiableEntity> results = new ArrayList<>();
901 long numberOfResults = 0; // overall number of results (as opposed to number of results per page)
902 List<TaxonBase> taxa = null;
903
904 // Taxa and synonyms
905 long numberTaxaResults = 0L;
906
907 List<String> propertyPath = new ArrayList<>();
908 if(configurator.getTaxonPropertyPath() != null){
909 propertyPath.addAll(configurator.getTaxonPropertyPath());
910 }
911
912 if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa() || configurator.isDoTaxaByCommonNames()){
913 if(configurator.getPageSize() != null){ // no point counting if we need all anyway
914 numberTaxaResults =
915 dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
916 configurator.isDoTaxaByCommonNames(), configurator.isDoIncludeAuthors(), configurator.getTitleSearchStringSqlized(),
917 configurator.getClassification(), configurator.getSubtree(), configurator.getMatchMode(),
918 configurator.getNamedAreas(), configurator.isIncludeUnpublished());
919 }
920
921 if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
922 taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
923 configurator.isDoMisappliedNames(), configurator.isDoTaxaByCommonNames(), configurator.isDoIncludeAuthors(),
924 configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getSubtree(),
925 configurator.getMatchMode(), configurator.getNamedAreas(), configurator.isIncludeUnpublished(),
926 configurator.getOrder(), configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
927 }
928 }
929
930 if (logger.isDebugEnabled()) { logger.debug(numberTaxaResults + " matching taxa counted"); }
931
932 if(taxa != null){
933 results.addAll(taxa);
934 }
935
936 numberOfResults += numberTaxaResults;
937
938 // Names without taxa
939 if (configurator.isDoNamesWithoutTaxa()) {
940 int numberNameResults = 0;
941
942 List<TaxonName> names =
943 nameDao.findByName(configurator.isDoIncludeAuthors(), configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
944 configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
945 if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
946 if (names.size() > 0) {
947 for (TaxonName taxonName : names) {
948 if (taxonName.getTaxonBases().size() == 0) {
949 results.add(taxonName);
950 numberNameResults++;
951 }
952 }
953 if (logger.isDebugEnabled()) { logger.debug(numberNameResults + " matching name(s) without taxa found"); }
954 numberOfResults += numberNameResults;
955 }
956 }
957
958 return new DefaultPagerImpl<> (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
959 }
960
961 public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(Integer limit, String pattern){
962 return dao.getUuidAndTitleCache(limit, pattern);
963 }
964
965 @Override
966 public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
967 return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
968 }
969
970 @Override
971 public List<Media> listMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships,
972 Boolean limitToGalleries, Boolean includeTaxonDescriptions, Boolean includeOccurrences,
973 Boolean includeTaxonNameDescriptions, List<String> propertyPath) {
974
975 //TODO let inherit
976 boolean includeUnpublished = INCLUDE_UNPUBLISHED;
977
978 // logger.setLevel(Level.TRACE);
979 // Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
980
981 if (logger.isTraceEnabled()){logger.trace("listMedia() - START");}
982
983 Set<Taxon> taxa = new HashSet<>();
984 List<Media> taxonMedia = new ArrayList<>();
985 List<Media> nonImageGalleryImages = new ArrayList<>();
986
987 if (limitToGalleries == null) {
988 limitToGalleries = false;
989 }
990
991 // --- resolve related taxa
992 if (includeRelationships != null && ! includeRelationships.isEmpty()) {
993 if (logger.isTraceEnabled()){logger.trace("listMedia() - resolve related taxa");}
994 taxa = listRelatedTaxa(taxon, includeRelationships, null, includeUnpublished, null, null, null);
995 }
996
997 taxa.add((Taxon) dao.load(taxon.getUuid()));
998
999 if(includeTaxonDescriptions != null && includeTaxonDescriptions){
1000 if (logger.isTraceEnabled()){logger.trace("listMedia() - includeTaxonDescriptions");}
1001 List<TaxonDescription> taxonDescriptions = new ArrayList<>();
1002 // --- TaxonDescriptions
1003 for (Taxon t : taxa) {
1004 taxonDescriptions.addAll(descriptionService.listTaxonDescriptions(t, null, null, null, null, propertyPath));
1005 }
1006 for (TaxonDescription taxonDescription : taxonDescriptions) {
1007 if (!limitToGalleries || taxonDescription.isImageGallery()) {
1008 for (DescriptionElementBase element : taxonDescription.getElements()) {
1009 for (Media media : element.getMedia()) {
1010 if(taxonDescription.isImageGallery()){
1011 taxonMedia.add(media);
1012 }
1013 else{
1014 nonImageGalleryImages.add(media);
1015 }
1016 }
1017 }
1018 }
1019 }
1020 //put images from image gallery first (#3242)
1021 taxonMedia.addAll(nonImageGalleryImages);
1022 }
1023
1024
1025 if(includeOccurrences != null && includeOccurrences) {
1026 if (logger.isTraceEnabled()){logger.trace("listMedia() - includeOccurrences");}
1027 @SuppressWarnings("rawtypes")
1028 Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<>();
1029 // --- Specimens
1030 for (Taxon t : taxa) {
1031 specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
1032 }
1033 for (SpecimenOrObservationBase<?> occurrence : specimensOrObservations) {
1034
1035 // direct media removed from specimen #3597
1036 // taxonMedia.addAll(occurrence.getMedia());
1037
1038 // SpecimenDescriptions
1039 Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
1040 for (DescriptionBase<?> specimenDescription : specimenDescriptions) {
1041 if (!limitToGalleries || specimenDescription.isImageGallery()) {
1042 Set<DescriptionElementBase> elements = specimenDescription.getElements();
1043 for (DescriptionElementBase element : elements) {
1044 for (Media media : element.getMedia()) {
1045 taxonMedia.add(media);
1046 }
1047 }
1048 }
1049 }
1050
1051 if (occurrence.isInstanceOf(DerivedUnit.class)) {
1052 DerivedUnit derivedUnit = CdmBase.deproxy(occurrence, DerivedUnit.class);
1053 // Collection
1054 //TODO why may collections have media attached? #
1055 if (derivedUnit.getCollection() != null){
1056 taxonMedia.addAll(derivedUnit.getCollection().getMedia());
1057 }
1058 }
1059 //media in hierarchy
1060 taxonMedia.addAll(occurrenceService.getMediaInHierarchy(occurrence, null, null, propertyPath).getRecords());
1061 }
1062 }
1063
1064 if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
1065 if (logger.isTraceEnabled()){logger.trace("listMedia() - includeTaxonNameDescriptions");}
1066 // --- TaxonNameDescription
1067 Set<TaxonNameDescription> nameDescriptions = new HashSet<>();
1068 for (Taxon t : taxa) {
1069 nameDescriptions.addAll(t.getName().getDescriptions());
1070 }
1071 for(TaxonNameDescription nameDescription: nameDescriptions){
1072 if (!limitToGalleries || nameDescription.isImageGallery()) {
1073 Set<DescriptionElementBase> elements = nameDescription.getElements();
1074 for (DescriptionElementBase element : elements) {
1075 for (Media media : element.getMedia()) {
1076 taxonMedia.add(media);
1077 }
1078 }
1079 }
1080 }
1081 }
1082
1083 taxonMedia = deduplicateMedia(taxonMedia);
1084
1085 if (logger.isTraceEnabled()){logger.trace("listMedia() - initialize");}
1086 beanInitializer.initializeAll(taxonMedia, propertyPath);
1087
1088 if (logger.isTraceEnabled()){logger.trace("listMedia() - END");}
1089
1090 return taxonMedia;
1091 }
1092
1093 private List<Media> deduplicateMedia(List<Media> taxonMedia) {
1094 return taxonMedia.stream().distinct().collect(Collectors.toList());
1095 }
1096
1097 @Override
1098 public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
1099 return this.dao.loadList(listOfIDs, null, null);
1100 }
1101
1102 @Override
1103 public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
1104 return this.dao.findByUuid(uuid, null ,propertyPaths);
1105 }
1106
1107 @Override
1108 public long countSynonyms(boolean onlyAttachedToTaxon){
1109 return this.dao.countSynonyms(onlyAttachedToTaxon);
1110 }
1111
1112 @Override
1113 @Transactional(readOnly=false)
1114 public DeleteResult deleteTaxon(UUID taxonUUID, TaxonDeletionConfigurator config, UUID classificationUuid) {
1115
1116 if (config == null){
1117 config = new TaxonDeletionConfigurator();
1118 }
1119 Taxon taxon = (Taxon)dao.load(taxonUUID);
1120 DeleteResult result = new DeleteResult();
1121 if (taxon == null){
1122 result.setAbort();
1123 result.addException(new Exception ("The taxon was already deleted."));
1124 return result;
1125 }
1126 taxon = HibernateProxyHelper.deproxy(taxon);
1127 Classification classification = HibernateProxyHelper.deproxy(classificationDao.load(classificationUuid), Classification.class);
1128 config.setClassificationUuid(classificationUuid);
1129 result = isDeletable(taxonUUID, config);
1130
1131 if (result.isOk()){
1132 // --- DeleteSynonymRelations
1133 if (config.isDeleteSynonymRelations()){
1134 boolean removeSynonymNameFromHomotypicalGroup = false;
1135 // use tmp Set to avoid concurrent modification
1136 Set<Synonym> synsToDelete = new HashSet<>();
1137 synsToDelete.addAll(taxon.getSynonyms());
1138 for (Synonym synonym : synsToDelete){
1139 taxon.removeSynonym(synonym, removeSynonymNameFromHomotypicalGroup);
1140
1141 // --- DeleteSynonymsIfPossible
1142 if (config.isDeleteSynonymsIfPossible()){
1143 //TODO which value
1144 boolean newHomotypicGroupIfNeeded = true;
1145 SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
1146 result.includeResult(deleteSynonym(synonym, synConfig));
1147 }
1148 }
1149 }
1150
1151 // --- DeleteTaxonRelationships
1152 if (! config.isDeleteTaxonRelationships()){
1153 if (taxon.getTaxonRelations().size() > 0){
1154 result.setAbort();
1155 result.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1156 "Remove taxon from all relations to other taxa prior to deletion."));
1157 }
1158 } else{
1159 TaxonDeletionConfigurator configRelTaxon = new TaxonDeletionConfigurator();
1160 configRelTaxon.setDeleteTaxonNodes(false);
1161 configRelTaxon.setDeleteConceptRelationships(true);
1162
1163 for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
1164 if (config.isDeleteMisappliedNames()
1165 && taxRel.getType().isMisappliedName()
1166 && taxon.equals(taxRel.getToTaxon())){
1167 this.deleteTaxon(taxRel.getFromTaxon().getUuid(), config, classificationUuid);
1168 } else if (config.isDeleteConceptRelationships() && taxRel.getType().isConceptRelationship()){
1169
1170 if (taxon.equals(taxRel.getToTaxon()) && isDeletable(taxRel.getFromTaxon().getUuid(), configRelTaxon).isOk()){
1171 this.deleteTaxon(taxRel.getFromTaxon().getUuid(), configRelTaxon, classificationUuid);
1172 }else if (isDeletable(taxRel.getToTaxon().getUuid(), configRelTaxon).isOk()){
1173 this.deleteTaxon(taxRel.getToTaxon().getUuid(), configRelTaxon, classificationUuid);
1174 }
1175 }
1176 taxon.removeTaxonRelation(taxRel);
1177 }
1178 }
1179
1180 // TaxonDescription
1181 if (config.isDeleteDescriptions()){
1182 Set<TaxonDescription> descriptions = taxon.getDescriptions();
1183 List<TaxonDescription> removeDescriptions = new ArrayList<>();
1184 for (TaxonDescription desc: descriptions){
1185 //TODO use description delete configurator ?
1186 //FIXME check if description is ALWAYS deletable
1187 if (desc.getDescribedSpecimenOrObservation() != null){
1188 result.setAbort();
1189 result.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1190 " which also describes specimens or observations"));
1191 break;
1192 }
1193 removeDescriptions.add(desc);
1194 }
1195 if (result.isOk()){
1196 for (TaxonDescription desc: removeDescriptions){
1197 taxon.removeDescription(desc);
1198 descriptionService.delete(desc);
1199 }
1200 } else {
1201 return result;
1202 }
1203 }
1204
1205 if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null && taxon.getTaxonNodes().size() > 1)){
1206 result.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1207 }else{
1208 if (taxon.getTaxonNodes().size() != 0){
1209 Set<TaxonNode> nodes = taxon.getTaxonNodes();
1210 Iterator<TaxonNode> iterator = nodes.iterator();
1211 TaxonNode node = null;
1212 boolean deleteChildren;
1213 if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1214 deleteChildren = true;
1215 }else {
1216 deleteChildren = false;
1217 }
1218 boolean success = true;
1219 if (!config.isDeleteInAllClassifications() && !(classification == null)){
1220 while (iterator.hasNext()){
1221 node = iterator.next();
1222 if (node.getClassification().equals(classification)){
1223 break;
1224 }
1225 node = null;
1226 }
1227 if (node != null){
1228 HibernateProxyHelper.deproxy(node, TaxonNode.class);
1229 success =taxon.removeTaxonNode(node, deleteChildren);
1230 nodeService.delete(node);
1231 result.addDeletedObject(node);
1232 } else {
1233 result.setError();
1234 result.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1235 }
1236 } else if (config.isDeleteInAllClassifications()){
1237 List<TaxonNode> nodesList = new ArrayList<>();
1238 nodesList.addAll(taxon.getTaxonNodes());
1239 for (ITaxonTreeNode treeNode: nodesList){
1240 TaxonNode taxonNode = (TaxonNode) treeNode;
1241 if(!deleteChildren){
1242 Object[] childNodes = taxonNode.getChildNodes().toArray();
1243 for (Object childNode: childNodes){
1244 TaxonNode childNodeCast = (TaxonNode) childNode;
1245 taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1246 }
1247 }
1248 }
1249 config.getTaxonNodeConfig().setDeleteElement(false);
1250 DeleteResult resultNodes = nodeService.deleteTaxonNodes(nodesList, config);
1251 if (!resultNodes.isOk()){
1252 result.addExceptions(resultNodes.getExceptions());
1253 result.setStatus(resultNodes.getStatus());
1254 } else {
1255 result.addUpdatedObjects(resultNodes.getUpdatedObjects());
1256 }
1257 }
1258 if (!success){
1259 result.setError();
1260 result.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1261 }
1262 }
1263 }
1264 TaxonName name = taxon.getName();
1265 taxon.setName(null);
1266 this.saveOrUpdate(taxon);
1267
1268 if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && result.isOk()){
1269 try{
1270 dao.delete(taxon);
1271 result.addDeletedObject(taxon);
1272 }catch(Exception e){
1273 result.addException(e);
1274 result.setError();
1275 }
1276 } else {
1277 result.setError();
1278 result.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1279
1280 }
1281 //TaxonName
1282 if (config.isDeleteNameIfPossible() && result.isOk()){
1283 DeleteResult nameResult = new DeleteResult();
1284 //remove name if possible (and required)
1285 if (name != null ){
1286 nameResult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
1287 }
1288 if (nameResult.isError() || nameResult.isAbort()){
1289 result.addRelatedObject(name);
1290 result.addExceptions(nameResult.getExceptions());
1291 }else{
1292 result.includeResult(nameResult);
1293 }
1294 }
1295 }
1296
1297 return result;
1298 }
1299
1300 @Override
1301 @Transactional(readOnly = false)
1302 public DeleteResult delete(UUID synUUID){
1303 Synonym syn = (Synonym)dao.load(synUUID);
1304 return this.deleteSynonym(syn, null);
1305 }
1306
1307 @Override
1308 @Transactional(readOnly = false)
1309 public DeleteResult deleteSynonym(UUID synonymUuid, SynonymDeletionConfigurator config) {
1310 return deleteSynonym((Synonym)dao.load(synonymUuid), config);
1311 }
1312
1313 @Override
1314 @Transactional(readOnly = false)
1315 public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1316 DeleteResult result = new DeleteResult();
1317 if (synonym == null){
1318 result.setAbort();
1319 result.addException(new Exception("The synonym was already deleted."));
1320 return result;
1321 }
1322
1323 if (config == null){
1324 config = new SynonymDeletionConfigurator();
1325 }
1326
1327 result = isDeletable(synonym.getUuid(), config);
1328
1329 if (result.isOk()){
1330
1331 synonym = HibernateProxyHelper.deproxy(this.load(synonym.getUuid()), Synonym.class);
1332
1333 //remove synonym
1334 Taxon accTaxon = synonym.getAcceptedTaxon();
1335
1336 if (accTaxon != null){
1337 accTaxon = HibernateProxyHelper.deproxy(accTaxon, Taxon.class);
1338 accTaxon.removeSynonym(synonym, false);
1339 this.saveOrUpdate(accTaxon);
1340 result.addUpdatedObject(accTaxon);
1341 }
1342 this.saveOrUpdate(synonym);
1343 //#6281
1344 dao.flush();
1345
1346 TaxonName name = synonym.getName();
1347 synonym.setName(null);
1348
1349 dao.delete(synonym);
1350 result.addDeletedObject(synonym);
1351
1352 //remove name if possible (and required)
1353 if (name != null && config.isDeleteNameIfPossible()){
1354
1355 DeleteResult nameDeleteResult = nameService.delete(name, config.getNameDeletionConfig());
1356 if (nameDeleteResult.isAbort() || nameDeleteResult.isError()){
1357 result.addExceptions(nameDeleteResult.getExceptions());
1358 result.addRelatedObject(name);
1359 result.addUpdatedObject(name);
1360 }else{
1361 result.addDeletedObject(name);
1362 }
1363 }
1364 }
1365 return result;
1366 }
1367
1368 @Override
1369 public Map<String, Map<UUID,Set<TaxonName>>> findIdenticalTaxonNames(List<UUID> sourceRefUuids, List<String> propertyPaths) {
1370 return this.dao.findIdenticalNames(sourceRefUuids, propertyPaths);
1371 }
1372
1373 @Override
1374 public Taxon findBestMatchingTaxon(String taxonName) {
1375 MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1376 config.setTaxonNameTitle(taxonName);
1377 return findBestMatchingTaxon(config);
1378 }
1379
1380 @Override
1381 public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1382
1383 Taxon bestCandidate = null;
1384 try{
1385 // 1. search for accepted taxa
1386 List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.isIncludeUnpublished(),
1387 config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, null, 0, null, null);
1388 boolean bestCandidateMatchesSecUuid = false;
1389 boolean bestCandidateIsInClassification = false;
1390 int countEqualCandidates = 0;
1391 for(TaxonBase<?> taxonBaseCandidate : taxonList){
1392 if(taxonBaseCandidate instanceof Taxon){
1393 Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1394 boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1395 if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1396 continue;
1397 }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1398 bestCandidate = newCanditate;
1399 countEqualCandidates = 1;
1400 bestCandidateMatchesSecUuid = true;
1401 continue;
1402 }
1403
1404 boolean newCandidateInClassification = isInClassification(newCanditate, config);
1405 if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1406 continue;
1407 }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1408 bestCandidate = newCanditate;
1409 countEqualCandidates = 1;
1410 bestCandidateIsInClassification = true;
1411 continue;
1412 }
1413 if (bestCandidate == null){
1414 bestCandidate = newCanditate;
1415 countEqualCandidates = 1;
1416 continue;
1417 }
1418 }else{ //not Taxon.class
1419 continue;
1420 }
1421 countEqualCandidates++;
1422
1423 }
1424 if (bestCandidate != null){
1425 if(countEqualCandidates > 1){
1426 logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1427 return bestCandidate;
1428 } else {
1429 logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1430 return bestCandidate;
1431 }
1432 }
1433
1434 // 2. search for synonyms
1435 if (config.isIncludeSynonyms()){
1436 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.isIncludeUnpublished(),
1437 config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, null, 0, null, null);
1438 for(TaxonBase taxonBase : synonymList){
1439 if(taxonBase instanceof Synonym){
1440 Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1441 bestCandidate = synonym.getAcceptedTaxon();
1442 if(bestCandidate != null){
1443 logger.info("using accepted Taxon " + bestCandidate.getTitleCache() + " for synonym " + taxonBase.getTitleCache());
1444 return bestCandidate;
1445 }
1446 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1447 }
1448 }
1449 }
1450
1451 } catch (Exception e){
1452 logger.error(e);
1453 e.printStackTrace();
1454 }
1455
1456 return bestCandidate;
1457 }
1458
1459 private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1460 UUID configClassificationUuid = config.getClassificationUuid();
1461 if (configClassificationUuid == null){
1462 return false;
1463 }
1464 for (TaxonNode node : taxon.getTaxonNodes()){
1465 UUID classUuid = node.getClassification().getUuid();
1466 if (configClassificationUuid.equals(classUuid)){
1467 return true;
1468 }
1469 }
1470 return false;
1471 }
1472
1473 private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1474 UUID configSecUuid = config.getSecUuid();
1475 if (configSecUuid == null){
1476 return false;
1477 }
1478 UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1479 return configSecUuid.equals(taxonSecUuid);
1480 }
1481
1482 @Override
1483 public Synonym findBestMatchingSynonym(String taxonName, boolean includeUnpublished) {
1484 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, includeUnpublished, taxonName, null, null, MatchMode.EXACT, null, null, 0, null, null);
1485 if(! synonymList.isEmpty()){
1486 Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1487 if(synonymList.size() == 1){
1488 logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1489 return result;
1490 } else {
1491 logger.info("Several matching synonyms found. Using first: " + result.getTitleCache());
1492 return result;
1493 }
1494 }
1495 return null;
1496 }
1497
1498 @Override
1499 @Transactional(readOnly = false)
1500 public UpdateResult moveSynonymToAnotherTaxon(Synonym oldSynonym, UUID newTaxonUUID, boolean moveHomotypicGroup,
1501 SynonymType newSynonymType, UUID newSecundumUuid, String newSecundumDetail,
1502 boolean keepSecundumIfUndefined) throws HomotypicalGroupChangeException {
1503
1504 UpdateResult result = new UpdateResult();
1505 Taxon newTaxon = CdmBase.deproxy(dao.load(newTaxonUUID), Taxon.class);
1506 result = moveSynonymToAnotherTaxon(oldSynonym, newTaxon, moveHomotypicGroup, newSynonymType,
1507 newSecundumUuid, newSecundumDetail, keepSecundumIfUndefined);
1508
1509 return result;
1510 }
1511
1512 @Override
1513 @Transactional(readOnly = false)
1514 public UpdateResult moveSynonymToAnotherTaxon(Synonym oldSynonym,
1515 Taxon newTaxon,
1516 boolean moveHomotypicGroup,
1517 SynonymType newSynonymType) throws HomotypicalGroupChangeException {
1518 return moveSynonymToAnotherTaxon(oldSynonym, newTaxon, moveHomotypicGroup,
1519 newSynonymType,
1520 oldSynonym.getSec()!= null? oldSynonym.getSec().getUuid(): null,
1521 oldSynonym.getSecMicroReference(),
1522 true);
1523 }
1524
1525 @Override
1526 @Transactional(readOnly = false)
1527 public UpdateResult moveSynonymToAnotherTaxon(Synonym oldSynonym,
1528 Taxon newTaxon,
1529 boolean moveHomotypicGroup,
1530 SynonymType newSynonymType,
1531 UUID newSecundumUuid,
1532 String newSecundumDetail,
1533 boolean keepSecundumIfUndefined) throws HomotypicalGroupChangeException {
1534
1535 Synonym synonym = CdmBase.deproxy(dao.load(oldSynonym.getUuid()), Synonym.class);
1536 Taxon oldTaxon = CdmBase.deproxy(dao.load(synonym.getAcceptedTaxon().getUuid()), Taxon.class);
1537 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1538 TaxonName synonymName = synonym.getName();
1539 TaxonName fromTaxonName = oldTaxon.getName();
1540 //set default relationship type
1541 if (newSynonymType == null){
1542 newSynonymType = SynonymType.HETEROTYPIC_SYNONYM_OF();
1543 }
1544 boolean newRelTypeIsHomotypic = newSynonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF());
1545
1546 HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1547 int hgSize = homotypicGroup.getTypifiedNames().size();
1548 boolean isSingleInGroup = !(hgSize > 1);
1549
1550 if (! isSingleInGroup){
1551 boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1552 boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1553 if (isHomotypicToAccepted){
1554 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.";
1555 String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1556 message = String.format(message, homotypicRelatives);
1557 throw new HomotypicalGroupChangeException(message);
1558 }
1559 if (! moveHomotypicGroup){
1560 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.";
1561 throw new HomotypicalGroupChangeException(message);
1562 }
1563 }else{
1564 moveHomotypicGroup = true; //single synonym always allows to moveCompleteGroup
1565 }
1566 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1567
1568 UpdateResult result = new UpdateResult();
1569 //move all synonyms to new taxon
1570 List<Synonym> homotypicSynonyms = oldTaxon.getSynonymsInGroup(homotypicGroup);
1571 Reference newSecundum = referenceService.load(newSecundumUuid);
1572 for (Synonym synRelation: homotypicSynonyms){
1573
1574 newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
1575 oldTaxon = HibernateProxyHelper.deproxy(oldTaxon, Taxon.class);
1576 oldTaxon.removeSynonym(synRelation, false);
1577 newTaxon.addSynonym(synRelation, newSynonymType);
1578
1579 if (newSecundum != null || !keepSecundumIfUndefined){
1580 synRelation.setSec(newSecundum);
1581 }
1582 if (newSecundumDetail != null || !keepSecundumIfUndefined){
1583 synRelation.setSecMicroReference(newSecundumDetail);
1584 }
1585
1586 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1587 if (!synRelation.equals(oldSynonym)){
1588 result.setError();
1589 }
1590 }
1591
1592 result.addUpdatedObject(oldTaxon);
1593 result.addUpdatedObject(newTaxon);
1594 result.addUpdatedObject(homotypicGroup);
1595 result.addUpdatedObject(synonym);
1596 saveOrUpdate(oldTaxon);
1597 saveOrUpdate(newTaxon);
1598
1599 return result;
1600 }
1601
1602 @Override
1603 public <T extends TaxonBase>List<UuidAndTitleCache<T>> getUuidAndTitleCache(Class<T> clazz, Integer limit, String pattern) {
1604
1605 return dao.getUuidAndTitleCache(clazz, limit, pattern);
1606 }
1607
1608 @Override
1609 public Pager<SearchResult<TaxonBase>> findByFullText(
1610 Class<? extends TaxonBase> clazz, String queryString,
1611 Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages,
1612 boolean highlightFragments, Integer pageSize, Integer pageNumber,
1613 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
1614
1615 LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, subtree,
1616 null, includeUnpublished, languages, highlightFragments, null);
1617
1618 // --- execute search
1619 TopGroups<BytesRef> topDocsResultSet;
1620 try {
1621 topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1622 } catch (ParseException e) {
1623 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
1624 luceneParseException.setStackTrace(e.getStackTrace());
1625 throw luceneParseException;
1626 }
1627
1628 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
1629 idFieldMap.put(CdmBaseType.TAXON_BASE, "id");
1630
1631 // --- initialize taxa, thighlight matches ....
1632 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1633 @SuppressWarnings("rawtypes")
1634 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1635 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1636
1637 long totalHits = topDocsResultSet != null ? Long.valueOf(topDocsResultSet.totalGroupCount) : 0;
1638 return new DefaultPagerImpl<>(pageNumber, totalHits, pageSize, searchResults);
1639 }
1640
1641 @Transactional(readOnly = true)
1642 @Override
1643 public <S extends TaxonBase> Pager<S> findByTitleWithRestrictions(Class<S> clazz, String queryString, MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
1644 long numberOfResults = dao.countByTitleWithRestrictions(clazz, queryString, matchmode, restrictions);
1645
1646 long numberOfResults_doubtful = dao.countByTitleWithRestrictions(clazz, "?".concat(queryString), matchmode, restrictions);
1647 List<S> results = new ArrayList<>();
1648 if(numberOfResults > 0 || numberOfResults_doubtful > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1649
1650 results = dao.findByTitleWithRestrictions(clazz, queryString, matchmode, restrictions, pageSize, pageNumber, orderHints, propertyPaths);
1651 results.addAll(dao.findByTitleWithRestrictions(clazz, "?".concat(queryString), matchmode, restrictions, pageSize, pageNumber, orderHints, propertyPaths));
1652 }
1653 Collections.sort(results, new TaxonComparator());
1654 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
1655 }
1656
1657 @Transactional(readOnly = true)
1658 @Override
1659 public <S extends TaxonBase> Pager<S> findByTitle(Class<S> clazz, String queryString,MatchMode matchmode, List<Criterion> criteria, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
1660 long numberOfResults = dao.countByTitle(clazz, queryString, matchmode, criteria);
1661 //check whether there are doubtful taxa matching
1662 long numberOfResults_doubtful = dao.countByTitle(clazz, "?".concat(queryString), matchmode, criteria);
1663 List<S> results = new ArrayList<>();
1664 if(numberOfResults > 0 || numberOfResults_doubtful > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1665 if (numberOfResults > 0){
1666 results = dao.findByTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
1667 }else{
1668 results = new ArrayList<>();
1669 }
1670 if (numberOfResults_doubtful > 0){
1671 results.addAll(dao.findByTitle(clazz, "?".concat(queryString), matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths));
1672 }
1673 }
1674 Collections.sort(results, new TaxonComparator());
1675 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
1676 }
1677
1678 @Override
1679 public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
1680 Classification classification, TaxonNode subtree,
1681 Integer pageSize, Integer pageNumber,
1682 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
1683
1684 LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification, subtree);
1685
1686 // --- execute search
1687 TopGroups<BytesRef> topDocsResultSet;
1688 try {
1689 topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1690 } catch (ParseException e) {
1691 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
1692 luceneParseException.setStackTrace(e.getStackTrace());
1693 throw luceneParseException;
1694 }
1695
1696 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
1697 idFieldMap.put(CdmBaseType.TAXON_BASE, "id");
1698
1699 // --- initialize taxa, thighlight matches ....
1700 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1701 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1702 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1703
1704 long totalHits = topDocsResultSet != null ? Long.valueOf(topDocsResultSet.totalGroupCount) : 0;
1705 return new DefaultPagerImpl<>(pageNumber, totalHits, pageSize, searchResults);
1706 }
1707
1708 /**
1709 * @param clazz
1710 * @param queryString
1711 * @param classification
1712 * @param includeUnpublished
1713 * @param languages
1714 * @param highlightFragments
1715 * @param sortFields TODO
1716 * @param directorySelectClass
1717 * @return
1718 */
1719 protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString,
1720 Classification classification, TaxonNode subtree, String className, boolean includeUnpublished, List<Language> languages,
1721 boolean highlightFragments, SortField[] sortFields) {
1722
1723 Builder finalQueryBuilder = new Builder();
1724 Builder textQueryBuilder = new Builder();
1725
1726 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1727 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1728
1729 if(sortFields == null){
1730 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false)};
1731 }
1732 luceneSearch.setSortFields(sortFields);
1733
1734 // ---- search criteria
1735 luceneSearch.setCdmTypRestriction(clazz);
1736
1737 if(!StringUtils.isEmpty(queryString) && !queryString.equals("*") && !queryString.equals("?") ) {
1738 textQueryBuilder.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1739 textQueryBuilder.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1740 }
1741 if(className != null){
1742 textQueryBuilder.add(taxonBaseQueryFactory.newTermQuery("classInfo.name", className, false), Occur.MUST);
1743 }
1744
1745 BooleanQuery textQuery = textQueryBuilder.build();
1746 if(textQuery.clauses().size() > 0) {
1747 finalQueryBuilder.add(textQuery, Occur.MUST);
1748 }
1749
1750 if(classification != null){
1751 finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
1752 }
1753 if(subtree != null){
1754 finalQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
1755 }
1756 if(!includeUnpublished) {
1757 String accPublishParam = TaxonBase.ACC_TAXON_BRIDGE_PREFIX + AcceptedTaxonBridge.DOC_KEY_PUBLISH_SUFFIX;
1758 finalQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery(accPublishParam, true), Occur.MUST);
1759 finalQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery("publish", true), Occur.MUST);
1760 }
1761
1762 luceneSearch.setQuery(finalQueryBuilder.build());
1763
1764 if(highlightFragments){
1765 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1766 }
1767 return luceneSearch;
1768 }
1769
1770 /**
1771 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1772 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1773 * drawback of requiring to do the join an indexing time.
1774 * see https://dev.e-taxonomy.eu/redmine/projects/edit/wiki/LuceneNotes#JoinsinLucene for more information on this.
1775 *
1776 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1777 * <ul>
1778 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1779 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1780 * <ul>
1781 * @param queryString
1782 * @param classification
1783 * @param languages
1784 * @param highlightFragments
1785 * @param sortFields TODO
1786 *
1787 * @return
1788 * @throws IOException
1789 */
1790 protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString,
1791 Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages,
1792 boolean highlightFragments, SortField[] sortFields) throws IOException {
1793
1794 String fromField;
1795 String queryTermField;
1796 String toField = "id"; // TaxonBase.uuid
1797 String publishField;
1798 String publishFieldInvers;
1799
1800 if(edge.isBidirectional()){
1801 throw new RuntimeException("Bidirectional joining not supported!");
1802 }
1803 if(edge.isEvers()){
1804 fromField = "relatedFrom.id";
1805 queryTermField = "relatedFrom.titleCache";
1806 publishField = "relatedFrom.publish";
1807 publishFieldInvers = "relatedTo.publish";
1808 } else if(edge.isInvers()) {
1809 fromField = "relatedTo.id";
1810 queryTermField = "relatedTo.titleCache";
1811 publishField = "relatedTo.publish";
1812 publishFieldInvers = "relatedFrom.publish";
1813 } else {
1814 throw new RuntimeException("Invalid direction: " + edge.getDirections());
1815 }
1816
1817 Builder finalQueryBuilder = new Builder();
1818
1819 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1820 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1821
1822 Builder joinFromQueryBuilder = new Builder();
1823 if(!StringUtils.isEmpty(queryString)){
1824 joinFromQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1825 }
1826 joinFromQueryBuilder.add(taxonBaseQueryFactory.newEntityIdsQuery("type.id", edge.getRelationshipTypes()), Occur.MUST);
1827 if(!includeUnpublished){
1828 joinFromQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery(publishField, true), Occur.MUST);
1829 joinFromQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery(publishFieldInvers, true), Occur.MUST);
1830 }
1831
1832 Query joinQuery = taxonBaseQueryFactory.newJoinQuery(TaxonRelationship.class, fromField, false, joinFromQueryBuilder.build(), toField, null, ScoreMode.Max);
1833
1834 if(sortFields == null){
1835 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false)};
1836 }
1837 luceneSearch.setSortFields(sortFields);
1838
1839 finalQueryBuilder.add(joinQuery, Occur.MUST);
1840
1841 if(classification != null){
1842 finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
1843 }
1844 if(subtree != null){
1845 finalQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
1846 }
1847
1848 luceneSearch.setQuery(finalQueryBuilder.build());
1849
1850 if(highlightFragments){
1851 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1852 }
1853 return luceneSearch;
1854 }
1855
1856 @Override
1857 public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1858 EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString,
1859 Classification classification, TaxonNode subtree,
1860 Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
1861 boolean highlightFragments, Integer pageSize,
1862 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1863 throws IOException, LuceneParseException, LuceneMultiSearchException {
1864
1865 // FIXME: allow taxonomic ordering
1866 // hql equivalent: order by t.name.genusOrUninomial, case when t.name.specificEpithet
1867 // like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache";
1868 // this requires building a special sort column by a special classBridge
1869 if(highlightFragments){
1870 logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1871 "currently not fully supported by this method and thus " +
1872 "may not work with common names and misapplied names.");
1873 }
1874
1875 // convert sets to lists
1876 List<NamedArea> namedAreaList = null;
1877 List<PresenceAbsenceTerm> distributionStatusList = null;
1878 if(namedAreas != null){
1879 namedAreaList = new ArrayList<>(namedAreas.size());
1880 namedAreaList.addAll(namedAreas);
1881 }
1882 if(distributionStatus != null){
1883 distributionStatusList = new ArrayList<>(distributionStatus.size());
1884 distributionStatusList.addAll(distributionStatus);
1885 }
1886
1887 // set default if parameter is null
1888 if(searchModes == null){
1889 searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1890 }
1891
1892 // set sort order and thus override any sort orders which may have been
1893 // defined by prepare*Search methods
1894 if(orderHints == null){
1895 orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER.asList();
1896 }
1897 SortField[] sortFields = new SortField[orderHints.size()];
1898 int i = 0;
1899 for(OrderHint oh : orderHints){
1900 sortFields[i++] = oh.toSortField();
1901 }
1902 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1903 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1904
1905 boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1906
1907 List<LuceneSearch> luceneSearches = new ArrayList<>();
1908 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
1909
1910 /*
1911 ======== filtering by distribution , HOWTO ========
1912
1913 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1914 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1915 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1916 which will be put into a FilteredQuersy in the end ?
1917
1918
1919 3. how does it work in spatial?
1920 see
1921 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1922 - http://www.infoq.com/articles/LuceneSpatialSupport
1923 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1924 ------------------------------------------------------------------------
1925
1926 filter strategies:
1927 A) use a separate distribution filter per index sub-query/search:
1928 - byTaxonSyonym (query TaxaonBase):
1929 use a join area filter (Distribution -> TaxonBase)
1930 - byCommonName (query DescriptionElementBase): use an area filter on
1931 DescriptionElementBase !!! PROBLEM !!!
1932 This cannot work since the distributions are different entities than the
1933 common names and thus these are different lucene documents.
1934 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1935 use a join area filter (Distribution -> TaxonBase)
1936
1937 B) use a common distribution filter for all index sub-query/searches:
1938 - use a common join area filter (Distribution -> TaxonBase)
1939 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1940 PROBLEM in this case: we are losing the fragment highlighting for the
1941 common names, since the returned documents are always TaxonBases
1942 */
1943
1944 /* The QueryFactory for creating filter queries on Distributions should
1945 * The query factory used for the common names query cannot be reused
1946 * for this case, since we want to only record the text fields which are
1947 * actually used in the primary query
1948 */
1949 QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1950
1951 Builder multiIndexByAreaFilterBuilder = new Builder();
1952 boolean includeUnpublished = searchModes.contains(TaxaAndNamesSearchMode.includeUnpublished);
1953
1954 // search for taxa or synonyms
1955 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) ) {
1956 @SuppressWarnings("rawtypes")
1957 Class<? extends TaxonBase> taxonBaseSubclass = TaxonBase.class;
1958 String className = null;
1959 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1960 taxonBaseSubclass = Taxon.class;
1961 } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1962 className = "eu.etaxonomy.cdm.model.taxon.Synonym";
1963 }
1964 luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass,
1965 queryString, classification, subtree, className,
1966 includeUnpublished, languages, highlightFragments, sortFields));
1967 idFieldMap.put(CdmBaseType.TAXON_BASE, "id");
1968 /* A) does not work!!!!
1969 if(addDistributionFilter){
1970 // in this case we need a filter which uses a join query
1971 // to get the TaxonBase documents for the DescriptionElementBase documents
1972 // which are matching the areas in question
1973 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1974 namedAreaList,
1975 distributionStatusList,
1976 distributionFilterQueryFactory
1977 );
1978 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1979 }
1980 */
1981 if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1982 // add additional area filter for synonyms
1983 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1984 String toField = "accTaxon" + AcceptedTaxonBridge.DOC_KEY_ID_SUFFIX; // id in TaxonBase index
1985
1986 //TODO replace by createByDistributionJoinQuery
1987 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1988 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class, fromField, true, byDistributionQuery, toField, Taxon.class, ScoreMode.None);
1989 multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
1990
1991 }
1992 }
1993
1994 // search by CommonTaxonName
1995 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1996 // B)
1997 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1998 Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1999 CommonTaxonName.class,
2000 "inDescription.taxon.id",
2001 true,
2002 QueryFactory.addTypeRestriction(
2003 createByDescriptionElementFullTextQuery(queryString, classification, subtree, null, languages, descriptionElementQueryFactory)
2004 , CommonTaxonName.class
2005 ).build(), "id", null, ScoreMode.Max);
2006 if (logger.isDebugEnabled()){logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());}
2007 LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider,
2008 GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2009 byCommonNameSearch.setCdmTypRestriction(Taxon.class);
2010 Builder builder = new BooleanQuery.Builder();
2011 builder.add(byCommonNameJoinQuery, Occur.MUST);
2012 if(!includeUnpublished) {
2013 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
2014 builder.add(taxonBaseQueryFactory.newBooleanQuery("publish", true), Occur.MUST);
2015 }
2016 byCommonNameSearch.setQuery(builder.build());
2017 byCommonNameSearch.setSortFields(sortFields);
2018
2019 idFieldMap.put(CdmBaseType.TAXON_BASE, "id");
2020
2021 luceneSearches.add(byCommonNameSearch);
2022
2023 /* A) does not work!!!!
2024 luceneSearches.add(
2025 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
2026 queryString, classification, null, languages, highlightFragments)
2027 );
2028 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2029 if(addDistributionFilter){
2030 // in this case we are able to use DescriptionElementBase documents
2031 // which are matching the areas in question directly
2032 BooleanQuery byDistributionQuery = createByDistributionQuery(
2033 namedAreaList,
2034 distributionStatusList,
2035 distributionFilterQueryFactory
2036 );
2037 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
2038 } */
2039 }
2040
2041
2042 // search by misapplied names
2043 //TODO merge with pro parte synonym search once #7487 is fixed
2044 if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
2045 // NOTE:
2046 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
2047 // which allows doing query time joins
2048 // finds the misapplied name (Taxon B) which is an misapplication for
2049 // a related Taxon A.
2050 //
2051 Set<TaxonRelationshipType> relTypes = new HashSet<>();
2052 if (searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)){
2053 relTypes.addAll(TaxonRelationshipType.allMisappliedNameTypes());
2054 }
2055 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
2056 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
2057 // }
2058
2059 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
2060 new TaxonRelationshipEdge(relTypes, Direction.relatedTo),
2061 queryString, classification, subtree, includeUnpublished, languages, highlightFragments, sortFields));
2062 idFieldMap.put(CdmBaseType.TAXON_BASE, "id");
2063
2064 if(addDistributionFilter){
2065 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2066
2067 /*
2068 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
2069 * Maybe this is a bug in java itself.
2070 *
2071 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2072 * directly:
2073 *
2074 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2075 *
2076 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2077 * will execute as expected:
2078 *
2079 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2080 * String toField = "relation." + misappliedNameForUuid +".to.id";
2081 *
2082 * Comparing both strings by the String.equals method returns true, so both String are identical.
2083 *
2084 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2085 * 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)
2086 * The bug is persistent after a reboot of the development computer.
2087 */
2088 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2089 // String toField = "relation." + misappliedNameForUuid +".to.id";
2090 String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2091 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2092 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2093
2094 //TODO replace by createByDistributionJoinQuery
2095 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2096 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class,
2097 fromField, true, byDistributionQuery, toField, null, ScoreMode.None);
2098
2099 // debug code for bug described above
2100 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
2101 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2102 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2103
2104 multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
2105 }
2106 }
2107
2108 // search by pro parte synonyms
2109 if(searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
2110 //TODO merge with misapplied name search once #7487 is fixed
2111 Set<TaxonRelationshipType> relTypes = new HashSet<>();
2112 relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
2113
2114 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
2115 new TaxonRelationshipEdge(relTypes, Direction.relatedTo),
2116 queryString, classification, subtree, includeUnpublished, languages, highlightFragments, sortFields));
2117 idFieldMap.put(CdmBaseType.TAXON_BASE, "id");
2118
2119 if(addDistributionFilter){
2120 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2121 String toField = "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
2122 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2123 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class,
2124 fromField, true, byDistributionQuery, toField, null, ScoreMode.None);
2125 multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
2126 }
2127 }//end pro parte synonyms
2128
2129 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
2130 luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
2131
2132 if(addDistributionFilter){
2133
2134 // B)
2135 // in this case we need a filter which uses a join query
2136 // to get the TaxonBase documents for the DescriptionElementBase documents
2137 // which are matching the areas in question
2138 //
2139 // for doTaxa, doByCommonName
2140 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2141 namedAreaList,
2142 distributionStatusList,
2143 distributionFilterQueryFactory,
2144 Taxon.class, true
2145 );
2146 multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
2147 }
2148
2149 if (addDistributionFilter){
2150 multiSearch.setFilter(multiIndexByAreaFilterBuilder.build());
2151 }
2152
2153
2154 // --- execute search
2155 TopGroups<BytesRef> topDocsResultSet;
2156 try {
2157 topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2158 } catch (ParseException e) {
2159 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
2160 luceneParseException.setStackTrace(e.getStackTrace());
2161 throw luceneParseException;
2162 }
2163
2164 // --- initialize taxa, highlight matches ....
2165 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2166
2167
2168 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2169 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2170
2171 long totalHits = (topDocsResultSet != null) ? Long.valueOf(topDocsResultSet.totalGroupCount) : 0;
2172 return new DefaultPagerImpl<>(pageNumber, totalHits, pageSize, searchResults);
2173 }
2174
2175 /**
2176 * @param namedAreaList at least one area must be in the list
2177 * @param distributionStatusList optional
2178 * @param toType toType
2179 * Optional parameter. Only used for debugging to print the toType documents
2180 * @param asFilter TODO
2181 * @return
2182 * @throws IOException
2183 */
2184 protected Query createByDistributionJoinQuery(
2185 List<NamedArea> namedAreaList,
2186 List<PresenceAbsenceTerm> distributionStatusList,
2187 QueryFactory queryFactory, Class<? extends CdmBase> toType, boolean asFilter
2188 ) throws IOException {
2189
2190 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2191 String toField = "id"; // id in toType usually this is the TaxonBase index
2192
2193 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2194
2195 ScoreMode scoreMode = asFilter ? ScoreMode.None : ScoreMode.Max;
2196
2197 Query taxonAreaJoinQuery = queryFactory.newJoinQuery(Distribution.class, fromField, false, byDistributionQuery, toField, toType, scoreMode);
2198
2199 return taxonAreaJoinQuery;
2200 }
2201
2202 /**
2203 * @param namedAreaList
2204 * @param distributionStatusList
2205 * @param queryFactory
2206 * @return
2207 */
2208 private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2209 List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2210 Builder areaQueryBuilder = new Builder();
2211 // area field from Distribution
2212 areaQueryBuilder.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2213
2214 // status field from Distribution
2215 if(distributionStatusList != null && distributionStatusList.size() > 0){
2216 areaQueryBuilder.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2217 }
2218
2219 BooleanQuery areaQuery = areaQueryBuilder.build();
2220 logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2221 return areaQuery;
2222 }
2223
2224 /**
2225 * This method has been primarily created for testing the area join query but might
2226 * also be useful in other situations
2227 *
2228 * @param namedAreaList
2229 * @param distributionStatusList
2230 * @param classification
2231 * @param highlightFragments
2232 * @return
2233 * @throws IOException
2234 */
2235 protected LuceneSearch prepareByDistributionSearch(
2236 List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
2237 Classification classification, TaxonNode subtree) throws IOException {
2238
2239 Builder finalQueryBuilder = new Builder();
2240
2241 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2242
2243 // FIXME is this query factory using the wrong type?
2244 QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2245
2246 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false)};
2247 luceneSearch.setSortFields(sortFields);
2248
2249
2250 Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory, null, false);
2251
2252 finalQueryBuilder.add(byAreaQuery, Occur.MUST);
2253
2254 if(classification != null){
2255 finalQueryBuilder.add(taxonQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
2256 }
2257 if(subtree != null){
2258 finalQueryBuilder.add(taxonQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
2259 }
2260 BooleanQuery finalQuery = finalQueryBuilder.build();
2261 logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2262 luceneSearch.setQuery(finalQuery);
2263
2264 return luceneSearch;
2265 }
2266
2267 @Override
2268 public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2269 Class<? extends DescriptionElementBase> clazz, String queryString,
2270 Classification classification, TaxonNode subtree, List<Feature> features, List<Language> languages,
2271 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
2272
2273 LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, subtree, features, languages, highlightFragments);
2274
2275 // --- execute search
2276 TopGroups<BytesRef> topDocsResultSet;
2277 try {
2278 topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2279 } catch (ParseException e) {
2280 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
2281 luceneParseException.setStackTrace(e.getStackTrace());
2282 throw luceneParseException;
2283 }
2284
2285 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
2286 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2287
2288 // --- initialize taxa, highlight matches ....
2289 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2290 @SuppressWarnings("rawtypes")
2291 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2292 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2293
2294 int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
2295 return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
2296 }
2297
2298 @Override
2299 public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2300 Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages, boolean highlightFragments,
2301 Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException, LuceneMultiSearchException {
2302
2303 LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString,
2304 classification, subtree,
2305 null, languages, highlightFragments);
2306 LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, subtree, null,
2307 includeUnpublished, languages, highlightFragments, null);
2308
2309 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2310
2311 // --- execute search
2312 TopGroups<BytesRef> topDocsResultSet;
2313 try {
2314 topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2315 } catch (ParseException e) {
2316 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
2317 luceneParseException.setStackTrace(e.getStackTrace());
2318 throw luceneParseException;
2319 }
2320
2321 // --- initialize taxa, highlight matches ....
2322 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2323
2324 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
2325 idFieldMap.put(CdmBaseType.TAXON_BASE, "id");
2326 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2327
2328 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2329 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2330
2331 int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
2332 return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
2333 }
2334
2335 /**
2336 * @param clazz
2337 * @param queryString
2338 * @param classification
2339 * @param features
2340 * @param languages
2341 * @param highlightFragments
2342 * @param directorySelectClass
2343 * @return
2344 */
2345 protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2346 String queryString, Classification classification, TaxonNode subtree, List<Feature> features,
2347 List<Language> languages, boolean highlightFragments) {
2348
2349 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2350 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2351
2352 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.Type.STRING, false)};
2353
2354 BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, subtree, features,
2355 languages, descriptionElementQueryFactory);
2356
2357 luceneSearch.setSortFields(sortFields);
2358 luceneSearch.setCdmTypRestriction(clazz);
2359 luceneSearch.setQuery(finalQuery);
2360 if(highlightFragments){
2361 luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2362 }
2363
2364 return luceneSearch;
2365 }
2366
2367 /**
2368 * @param queryString
2369 * @param classification
2370 * @param features
2371 * @param languages
2372 * @param descriptionElementQueryFactory
2373 * @return
2374 */
2375 private BooleanQuery createByDescriptionElementFullTextQuery(String queryString,
2376 Classification classification, TaxonNode subtree, List<Feature> features,
2377 List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2378
2379 Builder finalQueryBuilder = new Builder();
2380 Builder textQueryBuilder = new Builder();
2381
2382 if(!StringUtils.isEmpty(queryString)){
2383
2384 textQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2385
2386 // common name
2387 Builder nameQueryBuilder = new Builder();
2388 if(languages == null || languages.size() == 0){
2389 nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2390 } else {
2391 Builder languageSubQueryBuilder = new Builder();
2392 for(Language lang : languages){
2393 languageSubQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD);
2394 }
2395 nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2396 nameQueryBuilder.add(languageSubQueryBuilder.build(), Occur.MUST);
2397 }
2398 textQueryBuilder.add(nameQueryBuilder.build(), Occur.SHOULD);
2399
2400
2401 // text field from TextData
2402 textQueryBuilder.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2403
2404 // --- TermBase fields - by representation ----
2405 // state field from CategoricalData
2406 textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2407
2408 // state field from CategoricalData
2409 textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2410
2411 // area field from Distribution
2412 textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2413
2414 // status field from Distribution
2415 textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2416
2417 finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
2418
2419 }
2420 // --- classification ----
2421
2422
2423 if(classification != null){
2424 finalQueryBuilder.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2425 }
2426 if(subtree != null){
2427 finalQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree.treeIndexWc(), true), Occur.MUST);
2428 }
2429
2430 // --- IdentifieableEntity fields - by uuid
2431 if(features != null && features.size() > 0 ){
2432 finalQueryBuilder.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2433 }
2434
2435 // the description must be associated with a taxon
2436 finalQueryBuilder.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2437
2438 BooleanQuery finalQuery = finalQueryBuilder.build();
2439 logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2440 return finalQuery;
2441 }
2442
2443 @Override
2444 public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymType type, boolean doWithMisappliedNames){
2445
2446 List <Synonym> inferredSynonyms = new ArrayList<>();
2447 List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<>();
2448
2449 Map <UUID, IZoologicalName> zooHashMap = new HashMap<>();
2450 boolean includeUnpublished = INCLUDE_UNPUBLISHED;
2451
2452 UUID nameUuid= taxon.getName().getUuid();
2453 IZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2454 String epithetOfTaxon = null;
2455 String infragenericEpithetOfTaxon = null;
2456 String infraspecificEpithetOfTaxon = null;
2457 if (taxonName.isSpecies()){
2458 epithetOfTaxon= taxonName.getSpecificEpithet();
2459 } else if (taxonName.isInfraGeneric()){
2460 infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2461 } else if (taxonName.isInfraSpecific()){
2462 infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2463 }
2464 String genusOfTaxon = taxonName.getGenusOrUninomial();
2465 Set<TaxonNode> nodes = taxon.getTaxonNodes();
2466 List<String> taxonNames = new ArrayList<>();
2467
2468 for (TaxonNode node: nodes){
2469 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2470 // List<String> synonymsEpithet = new ArrayList<>();
2471
2472 if (node.getClassification().equals(classification)){
2473 if (!node.isTopmostNode()){
2474 TaxonNode parent = node.getParent();
2475 parent = CdmBase.deproxy(parent);
2476 TaxonName parentName = parent.getTaxon().getName();
2477 IZoologicalName zooParentName = CdmBase.deproxy(parentName);
2478 Taxon parentTaxon = CdmBase.deproxy(parent.getTaxon());
2479
2480 //create inferred synonyms for species, subspecies
2481 if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2482
2483 Synonym inferredEpithet = null;
2484 Synonym inferredGenus = null;
2485 Synonym potentialCombination = null;
2486
2487 List<String> propertyPaths = new ArrayList<>();
2488 propertyPaths.add("synonym");
2489 propertyPaths.add("synonym.name");
2490 List<OrderHint> orderHintsSynonyms = new ArrayList<>();
2491 orderHintsSynonyms.add(new OrderHint("titleCache", SortOrder.ASCENDING));
2492
2493 List<Synonym> synonyMsOfParent = dao.getSynonyms(parentTaxon, SynonymType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms,propertyPaths);
2494 List<Synonym> synonymsOfTaxon= dao.getSynonyms(taxon, SynonymType.HETEROTYPIC_SYNONYM_OF(),
2495 null, null,orderHintsSynonyms,propertyPaths);
2496
2497 List<TaxonRelationship> taxonRelListParent = new ArrayList<>();
2498 List<TaxonRelationship> taxonRelListTaxon = new ArrayList<>();
2499 if (doWithMisappliedNames){
2500 List<OrderHint> orderHintsMisapplied = new ArrayList<>();
2501 orderHintsMisapplied.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2502 taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(),
2503 includeUnpublished, null, null, orderHintsMisapplied, propertyPaths, Direction.relatedTo);
2504 taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(),
2505 includeUnpublished, null, null, orderHintsMisapplied, propertyPaths, Direction.relatedTo);
2506 }
2507
2508 if (type.equals(SynonymType.INFERRED_EPITHET_OF())){
2509 for (Synonym synonymRelationOfParent:synonyMsOfParent){
2510
2511 inferredEpithet = createInferredEpithets(taxon,
2512 zooHashMap, taxonName, epithetOfTaxon,
2513 infragenericEpithetOfTaxon,
2514 infraspecificEpithetOfTaxon,
2515 taxonNames, parentName,
2516 synonymRelationOfParent);
2517
2518 inferredSynonyms.add(inferredEpithet);
2519 zooHashMap.put(inferredEpithet.getName().getUuid(), inferredEpithet.getName());
2520 taxonNames.add(inferredEpithet.getName().getNameCache());
2521 }
2522
2523 if (doWithMisappliedNames){
2524
2525 for (TaxonRelationship taxonRelationship: taxonRelListParent){
2526 Taxon misappliedName = taxonRelationship.getFromTaxon();
2527
2528 inferredEpithet = createInferredEpithets(taxon,
2529 zooHashMap, taxonName, epithetOfTaxon,
2530 infragenericEpithetOfTaxon,
2531 infraspecificEpithetOfTaxon,
2532 taxonNames, parentName,
2533 misappliedName);
2534
2535 inferredSynonyms.add(inferredEpithet);
2536 zooHashMap.put(inferredEpithet.getName().getUuid(), inferredEpithet.getName());
2537 taxonNames.add(inferredEpithet.getName().getNameCache());
2538 }
2539 }
2540
2541 if (!taxonNames.isEmpty()){
2542 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2543 if (!synNotInCDM.isEmpty()){
2544 inferredSynonymsToBeRemoved.clear();
2545
2546 for (Synonym syn :inferredSynonyms){
2547 IZoologicalName name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2548 if (!synNotInCDM.contains(name.getNameCache())){
2549 inferredSynonymsToBeRemoved.add(syn);
2550 }
2551 }
2552
2553 // Remove identified Synonyms from inferredSynonyms
2554 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2555 inferredSynonyms.remove(synonym);
2556 }
2557 }
2558 }
2559
2560 }else if (type.equals(SynonymType.INFERRED_GENUS_OF())){
2561
2562 for (Synonym synonymRelationOfTaxon:synonymsOfTaxon){
2563
2564 inferredGenus = createInferredGenus(taxon,
2565 zooHashMap, taxonName, epithetOfTaxon,
2566 genusOfTaxon, taxonNames, zooParentName, synonymRelationOfTaxon);
2567
2568 inferredSynonyms.add(inferredGenus);
2569 zooHashMap.put(inferredGenus.getName().getUuid(), inferredGenus.getName());
2570 taxonNames.add(inferredGenus.getName().getNameCache());
2571 }
2572
2573 if (doWithMisappliedNames){
2574
2575 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2576 Taxon misappliedName = taxonRelationship.getFromTaxon();
2577 inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName, misappliedName);
2578
2579 inferredSynonyms.add(inferredGenus);
2580 zooHashMap.put(inferredGenus.getName().getUuid(), inferredGenus.getName());
2581 taxonNames.add(inferredGenus.getName().getNameCache());
2582 }
2583 }
2584
2585
2586 if (!taxonNames.isEmpty()){
2587 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2588 IZoologicalName name;
2589 if (!synNotInCDM.isEmpty()){
2590 inferredSynonymsToBeRemoved.clear();
2591
2592 for (Synonym syn :inferredSynonyms){
2593 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2594 if (!synNotInCDM.contains(name.getNameCache())){
2595 inferredSynonymsToBeRemoved.add(syn);
2596 }
2597 }
2598
2599 // Remove identified Synonyms from inferredSynonyms
2600 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2601 inferredSynonyms.remove(synonym);
2602 }
2603 }
2604 }
2605
2606 }else if (type.equals(SynonymType.POTENTIAL_COMBINATION_OF())){
2607
2608 Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2609 //for all synonyms of the parent...
2610 for (Synonym synonymRelationOfParent:synonyMsOfParent){
2611 TaxonName synName;
2612 HibernateProxyHelper.deproxy(synonymRelationOfParent);
2613
2614 synName = synonymRelationOfParent.getName();
2615
2616 // Set the sourceReference
2617 sourceReference = synonymRelationOfParent.getSec();
2618
2619 // Determine the idInSource
2620 String idInSourceParent = getIdInSource(synonymRelationOfParent);
2621
2622 IZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2623 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2624 String synParentInfragenericName = null;
2625 String synParentSpecificEpithet = null;
2626
2627 if (parentSynZooName.isInfraGeneric()){
2628 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2629 }
2630 if (parentSynZooName.isSpecies()){
2631 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2632 }
2633
2634 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2635 synonymsGenus.put(synGenusName, idInSource);
2636 }*/
2637
2638 //for all synonyms of the taxon
2639
2640 for (Synonym synonymRelationOfTaxon:synonymsOfTaxon){
2641
2642 IZoologicalName zooSynName = getZoologicalName(synonymRelationOfTaxon.getName().getUuid(), zooHashMap);
2643 potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2644 synParentGenus,
2645 synParentInfragenericName,
2646 synParentSpecificEpithet, synonymRelationOfTaxon, zooHashMap);
2647
2648 taxon.addSynonym(potentialCombination, SynonymType.POTENTIAL_COMBINATION_OF());
2649 inferredSynonyms.add(potentialCombination);
2650 zooHashMap.put(potentialCombination.getName().getUuid(), potentialCombination.getName());
2651 taxonNames.add(potentialCombination.getName().getNameCache());
2652 }
2653 }
2654
2655 if (doWithMisappliedNames){
2656
2657 for (TaxonRelationship parentRelationship: taxonRelListParent){
2658
2659 TaxonName misappliedParentName;
2660
2661 Taxon misappliedParent = parentRelationship.getFromTaxon();
2662 misappliedParentName = misappliedParent.getName();
2663
2664 HibernateProxyHelper.deproxy(misappliedParent);
2665
2666 // Set the sourceReference
2667 sourceReference = misappliedParent.getSec();
2668
2669 // Determine the idInSource
2670 String idInSourceParent = getIdInSource(misappliedParent);
2671
2672 IZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2673 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2674 String synParentInfragenericName = null;
2675 String synParentSpecificEpithet = null;
2676
2677 if (parentSynZooName.isInfraGeneric()){
2678 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2679 }
2680 if (parentSynZooName.isSpecies()){
2681 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2682 }
2683
2684 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2685 Taxon misappliedName = taxonRelationship.getFromTaxon();
2686 IZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2687 potentialCombination = createPotentialCombination(
2688 idInSourceParent, parentSynZooName, zooMisappliedName,
2689 synParentGenus,
2690 synParentInfragenericName,
2691 synParentSpecificEpithet, misappliedName, zooHashMap);
2692
2693 taxon.addSynonym(potentialCombination, SynonymType.POTENTIAL_COMBINATION_OF());
2694 inferredSynonyms.add(potentialCombination);
2695 zooHashMap.put(potentialCombination.getName().getUuid(), potentialCombination.getName());
2696 taxonNames.add(potentialCombination.getName().getNameCache());
2697 }
2698 }
2699 }
2700
2701 if (!taxonNames.isEmpty()){
2702 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2703 IZoologicalName name;
2704 if (!synNotInCDM.isEmpty()){
2705 inferredSynonymsToBeRemoved.clear();
2706 for (Synonym syn :inferredSynonyms){
2707 try{
2708 name = syn.getName();
2709 }catch (ClassCastException e){
2710 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2711 }
2712 if (!synNotInCDM.contains(name.getNameCache())){
2713 inferredSynonymsToBeRemoved.add(syn);
2714 }
2715 }
2716 // Remove identified Synonyms from inferredSynonyms
2717 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2718 inferredSynonyms.remove(synonym);
2719 }
2720 }
2721 }
2722 }
2723 }else {
2724 logger.info("The synonym type is not defined.");
2725 return inferredSynonyms;
2726 }
2727 }
2728 }
2729 }
2730
2731 return inferredSynonyms;
2732 }
2733
2734 private Synonym createPotentialCombination(String idInSourceParent,
2735 IZoologicalName parentSynZooName, IZoologicalName zooSynName, String synParentGenus,
2736 String synParentInfragenericName, String synParentSpecificEpithet,
2737 TaxonBase<?> syn, Map<UUID, IZoologicalName> zooHashMap) {
2738 Synonym potentialCombination;
2739 Reference sourceReference;
2740 IZoologicalName inferredSynName;
2741 HibernateProxyHelper.deproxy(syn);
2742
2743 // Set sourceReference
2744 sourceReference = syn.getSec();
2745 if (sourceReference == null){
2746 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2747 //TODO:Remove
2748 if (!parentSynZooName.getTaxa().isEmpty()){
2749 TaxonBase<?> taxon = parentSynZooName.getTaxa().iterator().next();
2750
2751 sourceReference = taxon.getSec();
2752 }
2753 }
2754 String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2755
2756 String synTaxonInfraSpecificName= null;
2757
2758 if (parentSynZooName.isSpecies()){
2759 synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2760 }
2761
2762 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2763 synonymsEpithet.add(epithetName);
2764 }*/
2765
2766 //create potential combinations...
2767 inferredSynName = TaxonNameFactory.NewZoologicalInstance(syn.getName().getRank());
2768
2769 inferredSynName.setGenusOrUninomial(synParentGenus);
2770 if (zooSynName.isSpecies()){
2771 inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2772 if (parentSynZooName.isInfraGeneric()){
2773 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2774 }
2775 }
2776 if (zooSynName.isInfraSpecific()){
2777 inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2778 inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2779 }
2780 if (parentSynZooName.isInfraGeneric()){
2781 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2782 }
2783
2784 potentialCombination = Synonym.NewInstance(inferredSynName, null);
2785
2786 // Set the sourceReference
2787 potentialCombination.setSec(sourceReference);
2788
2789
2790 // Determine the idInSource
2791 String idInSourceSyn= getIdInSource(syn);
2792
2793 if (idInSourceParent != null && idInSourceSyn != null) {
2794 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2795 inferredSynName.addSource(originalSource);
2796 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2797 potentialCombination.addSource(originalSource);
2798 }
2799
2800 return potentialCombination;
2801 }
2802
2803 private Synonym createInferredGenus(Taxon taxon,
2804 Map<UUID, IZoologicalName> zooHashMap, IZoologicalName taxonName,
2805 String epithetOfTaxon, String genusOfTaxon,
2806 List<String> taxonNames, IZoologicalName zooParentName,
2807 TaxonBase syn) {
2808
2809 Synonym inferredGenus;
2810 TaxonName synName;
2811 IZoologicalName inferredSynName;
2812 synName =syn.getName();
2813 HibernateProxyHelper.deproxy(syn);
2814
2815 // Determine the idInSource
2816 String idInSourceSyn = getIdInSource(syn);
2817 String idInSourceTaxon = getIdInSource(taxon);
2818 // Determine the sourceReference
2819 Reference sourceReference = syn.getSec();
2820
2821 //logger.warn(sourceReference.getTitleCache());
2822
2823 synName = syn.getName();
2824 IZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2825 String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2826 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2827 synonymsEpithet.add(synSpeciesEpithetName);
2828 }*/
2829
2830 inferredSynName = TaxonNameFactory.NewZoologicalInstance(taxon.getName().getRank());
2831 //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...
2832
2833 inferredSynName.setGenusOrUninomial(genusOfTaxon);
2834 if (zooParentName.isInfraGeneric()){
2835 inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2836 }
2837
2838 if (taxonName.isSpecies()){
2839 inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2840 }
2841 if (taxonName.isInfraSpecific()){
2842 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2843 inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2844 }
2845
2846 inferredGenus = Synonym.NewInstance(inferredSynName, null);
2847
2848 // Set the sourceReference
2849 inferredGenus.setSec(sourceReference);
2850
2851 // Add the original source
2852 if (idInSourceSyn != null && idInSourceTaxon != null) {
2853 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2854 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2855 inferredGenus.addSource(originalSource);
2856
2857 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2858 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2859 inferredSynName.addSource(originalSource);
2860 originalSource = null;
2861
2862 }else{
2863 logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2864 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2865 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2866 inferredGenus.addSource(originalSource);
2867
2868 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2869 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2870 inferredSynName.addSource(originalSource);
2871 originalSource = null;
2872 }
2873
2874 taxon.addSynonym(inferredGenus, SynonymType.INFERRED_GENUS_OF());
2875
2876 return inferredGenus;
2877 }
2878
2879 private Synonym createInferredEpithets(Taxon taxon,
2880 Map<UUID, IZoologicalName> zooHashMap, IZoologicalName taxonName,
2881 String epithetOfTaxon, String infragenericEpithetOfTaxon,
2882 String infraspecificEpithetOfTaxon, List<String> taxonNames,
2883 TaxonName parentName, TaxonBase<?> syn) {
2884
2885 Synonym inferredEpithet;
2886 TaxonName synName;
2887 IZoologicalName inferredSynName;
2888 HibernateProxyHelper.deproxy(syn);
2889
2890 // Determine the idInSource
2891 String idInSourceSyn = getIdInSource(syn);
2892 String idInSourceTaxon = getIdInSource(taxon);
2893 // Determine the sourceReference
2894 Reference sourceReference = syn.getSec();
2895
2896 if (sourceReference == null){
2897 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2898 sourceReference = taxon.getSec();
2899 }
2900
2901 synName = syn.getName();
2902 IZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2903 String synGenusName = zooSynName.getGenusOrUninomial();
2904 String synInfraGenericEpithet = null;
2905 String synSpecificEpithet = null;
2906
2907 if (zooSynName.getInfraGenericEpithet() != null){
2908 synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2909 }
2910
2911 if (zooSynName.isInfraSpecific()){
2912 synSpecificEpithet = zooSynName.getSpecificEpithet();
2913 }
2914
2915 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2916 synonymsGenus.put(synGenusName, idInSource);
2917 }*/
2918
2919 inferredSynName = TaxonNameFactory.NewZoologicalInstance(taxon.getName().getRank());
2920
2921 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2922 if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2923 logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2924 }
2925 inferredSynName.setGenusOrUninomial(synGenusName);
2926
2927 if (parentName.isInfraGeneric()){
2928 inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2929 }
2930 if (taxonName.isSpecies()){
2931 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2932 }else if (taxonName.isInfraSpecific()){
2933 inferredSynName.setSpecificEpithet(synSpecificEpithet);
2934 inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2935 }
2936
2937 inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2938
2939 // Set the sourceReference
2940 inferredEpithet.setSec(sourceReference);
2941
2942 /* Add the original source
2943 if (idInSource != null) {
2944 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2945
2946 // Add the citation
2947 Reference citation = getCitation(syn);
2948 if (citation != null) {
2949 originalSource.setCitation(citation);
2950 inferredEpithet.addSource(originalSource);
2951 }
2952 }*/
2953 String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2954
2955
2956 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2957 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2958
2959 inferredEpithet.addSource(originalSource);
2960
2961 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2962 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2963
2964 inferredSynName.addSource(originalSource);
2965
2966 taxon.addSynonym(inferredEpithet, SynonymType.INFERRED_EPITHET_OF());
2967
2968 return inferredEpithet;
2969 }
2970
2971 /**
2972 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2973 * Very likely only useful for createInferredSynonyms().
2974 * @param uuid
2975 * @param zooHashMap
2976 * @return
2977 */
2978 private IZoologicalName getZoologicalName(UUID uuid, Map <UUID, IZoologicalName> zooHashMap) {
2979 IZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2980 if (taxonName == null) {
2981 taxonName = zooHashMap.get(uuid);
2982 }
2983 return taxonName;
2984 }
2985
2986 /**
2987 * Returns the idInSource for a given Synonym.
2988 * @param syn
2989 */
2990 private String getIdInSource(TaxonBase<?> taxonBase) {
2991 String idInSource = null;
2992 Set<IdentifiableSource> sources = taxonBase.getSources();
2993 if (sources.size() == 1) {
2994 IdentifiableSource source = sources.iterator().next();
2995 if (source != null) {
2996 idInSource = source.getIdInSource();
2997 }
2998 } else if (sources.size() > 1) {
2999 int count = 1;
3000 idInSource = "";
3001 for (IdentifiableSource source : sources) {
3002 idInSource += source.getIdInSource();
3003 if (count < sources.size()) {
3004 idInSource += "; ";
3005 }
3006 count++;
3007 }
3008 } else if (sources.size() == 0){
3009 logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
3010 }
3011
3012 return idInSource;
3013 }
3014
3015 /**
3016 * Returns the citation for a given Synonym.
3017 * @param syn
3018 */
3019 private Reference getCitation(Synonym syn) {
3020 Reference citation = null;
3021 Set<IdentifiableSource> sources = syn.getSources();
3022 if (sources.size() == 1) {
3023 IdentifiableSource source = sources.iterator().next();
3024 if (source != null) {
3025 citation = source.getCitation();
3026 }
3027 } else if (sources.size() > 1) {
3028 logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
3029 }
3030
3031 return citation;
3032 }
3033
3034 @Override
3035 public List<Synonym> createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
3036 List <Synonym> inferredSynonyms = new ArrayList<>();
3037
3038 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
3039 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymType.INFERRED_GENUS_OF(), doWithMisappliedNames));
3040 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
3041
3042 return inferredSynonyms;
3043 }
3044
3045 @Override
3046 public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
3047
3048 // TODO quickly implemented, create according dao !!!!
3049 Set<TaxonNode> nodes = new HashSet<>();
3050 Set<Classification> classifications = new HashSet<>();
3051 List<Classification> list = new ArrayList<>();
3052
3053 if (taxonBase == null) {
3054 return list;
3055 }
3056
3057 taxonBase = load(taxonBase.getUuid());
3058
3059 if (taxonBase instanceof Taxon) {
3060 nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
3061 } else {
3062 Taxon taxon = ((Synonym)taxonBase).getAcceptedTaxon();
3063 if (taxon != null){
3064 nodes.addAll(taxon.getTaxonNodes());
3065 }
3066 }
3067 for (TaxonNode node : nodes) {
3068 classifications.add(node.getClassification());
3069 }
3070 list.addAll(classifications);
3071 return list;
3072 }
3073
3074 @Override
3075 @Transactional(readOnly = false)
3076 public UpdateResult changeRelatedTaxonToSynonym(UUID fromTaxonUuid,
3077 UUID toTaxonUuid,
3078 TaxonRelationshipType oldRelationshipType,
3079 SynonymType synonymType) throws DataChangeNoRollbackException {
3080 UpdateResult result = new UpdateResult();
3081 Taxon fromTaxon = (Taxon) dao.load(fromTaxonUuid);
3082 Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
3083 result = changeRelatedTaxonToSynonym(fromTaxon, toTaxon, oldRelationshipType, synonymType);
3084
3085 result.addUpdatedObject(toTaxon);
3086 result.addUpdatedObject(result.getCdmEntity());
3087
3088 return result;
3089 }
3090
3091 @Override
3092 @Transactional(readOnly = false)
3093 public UpdateResult changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
3094 SynonymType synonymType) throws DataChangeNoRollbackException {
3095
3096 UpdateResult result = new UpdateResult();
3097 // Create new synonym using concept name
3098 TaxonName synonymName = fromTaxon.getName();
3099
3100 // Remove concept relation from taxon
3101 toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
3102
3103 // Create a new synonym for the taxon
3104 Synonym synonym;
3105 if (synonymType != null
3106 && synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
3107 synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
3108 toTaxon.addHomotypicSynonym(synonym);
3109 } else{
3110 synonym = toTaxon.addHeterotypicSynonymName(synonymName);
3111 }
3112 //keep the publish flag
3113 synonym.setPublish(fromTaxon.isPublish());
3114 this.saveOrUpdate(toTaxon);
3115 //TODO: configurator and classification
3116 TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
3117 config.setDeleteNameIfPossible(false);
3118 result.includeResult(this.deleteTaxon(fromTaxon.getUuid(), config, null));
3119 result.setCdmEntity(synonym);
3120 result.addUpdatedObject(toTaxon);
3121 result.addUpdatedObject(synonym);
3122 return result;
3123 }
3124
3125 @Override
3126 public DeleteResult isDeletable(UUID taxonBaseUuid, DeleteConfiguratorBase config){
3127 DeleteResult result = new DeleteResult();
3128 TaxonBase<?> taxonBase = load(taxonBaseUuid);
3129 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
3130 if (taxonBase instanceof Taxon){
3131 TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3132 List<String> propertyPaths = new ArrayList<>();
3133 propertyPaths.add("taxonNodes");
3134 Taxon taxon = (Taxon)load(taxonBaseUuid, propertyPaths);
3135
3136 result = isDeletableForTaxon(references, taxonConfig );
3137
3138 if (taxonConfig.isDeleteNameIfPossible()){
3139 if (taxonBase.getName() != null){
3140 DeleteResult nameResult = nameService.isDeletable(taxonBase.getName().getUuid(), taxonConfig.getNameDeletionConfig(), taxon.getUuid());
3141 if (!nameResult.isOk()){
3142 result.addExceptions(nameResult.getExceptions());
3143 }
3144 }
3145
3146 }
3147 }else{
3148 SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3149 result = isDeletableForSynonym(references, synonymConfig);
3150 if (synonymConfig.isDeleteNameIfPossible() && taxonBase.getName() != null){
3151 DeleteResult nameResult = nameService.isDeletable(taxonBase.getName().getUuid(), synonymConfig.getNameDeletionConfig(), taxonBase.getUuid());
3152 if (!nameResult.isOk()){
3153 result.addExceptions(nameResult.getExceptions());
3154
3155 }
3156 }
3157 }
3158
3159 return result;
3160 }
3161
3162 private DeleteResult isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3163
3164 DeleteResult result = new DeleteResult();
3165 for (CdmBase ref: references){
3166 if (!(ref instanceof Taxon || ref instanceof TaxonName || ref instanceof SecundumSource)){
3167 String message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3168 result.addException(new ReferencedObjectUndeletableException(message));
3169 result.addRelatedObject(ref);
3170 result.setAbort();
3171 }
3172 }
3173
3174 return result;
3175 }
3176
3177 private DeleteResult isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3178 String message = null;
3179 DeleteResult result = new DeleteResult();
3180 for (CdmBase ref: references){
3181 if (!(ref instanceof TaxonName || ref instanceof SecundumSource)){
3182 message = null;
3183 if (!config.isDeleteSynonymRelations() && (ref instanceof Synonym)){
3184 message = "The taxon can't be deleted as long as it has synonyms.";
3185 }
3186 if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3187 message = "The taxon can't be deleted as long as it has factual data.";
3188 }
3189
3190 if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
3191 message = "The taxon can't be deleted as long as it belongs to a taxon node.";
3192 }
3193 if (ref instanceof TaxonNode && config.getClassificationUuid() != null && !config.isDeleteInAllClassifications() && !((TaxonNode)ref).getClassification().getUuid().equals(config.getClassificationUuid())){
3194 message = "The taxon can't be deleted as long as it is used in more than one classification";
3195
3196 }
3197 if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonRelationship)){
3198 if (!config.isDeleteMisappliedNames() &&
3199 (((TaxonRelationship)ref).getType().isMisappliedName())){
3200 message = "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3201 } else{
3202 message = "The taxon can't be deleted as long as it belongs to taxon relationship.";
3203 }
3204 }
3205 if (ref instanceof PolytomousKeyNode){
3206 message = "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3207 }
3208
3209 if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){
3210 message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3211 }
3212
3213 /* //PolytomousKeyNode
3214 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3215 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3216 return message;
3217 }*/
3218
3219 //TaxonInteraction
3220 if (ref.isInstanceOf(TaxonInteraction.class)){
3221 message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3222 }
3223
3224 //TaxonInteraction
3225 if (ref.isInstanceOf(DeterminationEvent.class)){
3226 message = "Taxon can't be deleted as it is used in a determination event";
3227 }
3228 }
3229 if (message != null){
3230 result.addException(new ReferencedObjectUndeletableException(message));
3231 result.addRelatedObject(ref);
3232 result.setAbort();
3233 }
3234 }
3235
3236 return result;
3237 }
3238
3239 @Override
3240 public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3241 IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3242
3243 //preliminary implementation
3244
3245 Set<Taxon> taxa = new HashSet<>();
3246 TaxonBase<?> taxonBase = find(taxonUuid);
3247 if (taxonBase == null){
3248 return new IncludedTaxaDTO();
3249 }else if (taxonBase.isInstanceOf(Taxon.class)){
3250 Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3251 taxa.add(taxon);
3252 }else if (taxonBase.isInstanceOf(Synonym.class)){
3253 //TODO partial synonyms ??
3254 //TODO synonyms in general
3255 Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3256 taxa.add(syn.getAcceptedTaxon());
3257 }else{
3258 throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3259 }
3260
3261 Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3262 int i = 0;
3263 while((! related.isEmpty()) && i++ < 100){ //to avoid
3264 related = makeRelatedIncluded(related, result, config);
3265 }
3266
3267 return result;
3268 }
3269
3270 /**
3271 * @param uncheckedTaxa
3272 * @param existingTaxa
3273 * @param config
3274 *
3275 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3276 * data structure.
3277 * @return the set of conceptually related taxa for further use
3278 */
3279 private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3280
3281 //children
3282 Set<TaxonNode> taxonNodes = new HashSet<>();
3283 for (Taxon taxon: uncheckedTaxa){
3284 taxonNodes.addAll(taxon.getTaxonNodes());
3285 }
3286
3287 Set<Taxon> children = new HashSet<>();
3288 if (! config.onlyCongruent){
3289 for (TaxonNode node: taxonNodes){
3290 List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, config.includeUnpublished, null);
3291 for (TaxonNode child : childNodes){
3292 children.add(child.getTaxon());
3293 }
3294 }
3295 children.remove(null); // just to be on the save side
3296 }
3297
3298 Iterator<Taxon> it = children.iterator();
3299 while(it.hasNext()){
3300 UUID uuid = it.next().getUuid();
3301 if (existingTaxa.contains(uuid)){
3302 it.remove();
3303 }else{
3304 existingTaxa.addIncludedTaxon(uuid, new ArrayList<>(), false);
3305 }
3306 }
3307
3308 //concept relations
3309 Set<Taxon> uncheckedAndChildren = new HashSet<>(uncheckedTaxa);
3310 uncheckedAndChildren.addAll(children);
3311
3312 Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3313
3314
3315 Set<Taxon> result = new HashSet<>(relatedTaxa);
3316 return result;
3317 }
3318
3319 /**
3320 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3321 * @return the set of these computed taxa
3322 */
3323 private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3324 Set<Taxon> result = new HashSet<>();
3325
3326 for (Taxon taxon : unchecked){
3327 Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3328 Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3329
3330 for (TaxonRelationship fromRel : fromRelations){
3331 if (config.includeDoubtful == false && fromRel.isDoubtful()){
3332 continue;
3333 }
3334 TaxonRelationshipType fromRelType = fromRel.getType();
3335 if (fromRelType.equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3336 !config.onlyCongruent && (
3337 fromRelType.equals(TaxonRelationshipType.INCLUDES()) ||
3338 fromRelType.equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3339 )
3340 ){
3341 result.add(fromRel.getToTaxon());
3342 }
3343 }
3344
3345 for (TaxonRelationship toRel : toRelations){
3346 if (config.includeDoubtful == false && toRel.isDoubtful()){
3347 continue;
3348 }
3349 TaxonRelationshipType fromRelType = toRel.getType();
3350 if (fromRelType.equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3351 !config.includeDoubtful && fromRelType.equals(TaxonRelationshipType.TAXONOMICALLY_INCLUDED_IN())){
3352 result.add(toRel.getFromTaxon());
3353 }
3354 }
3355 }
3356
3357 Iterator<Taxon> it = result.iterator();
3358 while(it.hasNext()){
3359 UUID uuid = it.next().getUuid();
3360 if (existingTaxa.contains(uuid)){
3361 it.remove();
3362 }else{
3363 existingTaxa.addIncludedTaxon(uuid, new ArrayList<>(), false);
3364 }
3365 }
3366 return result;
3367 }
3368
3369 @Override
3370 public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3371 @SuppressWarnings("rawtypes")
3372 List<TaxonBase> taxonList = dao.getTaxaByName(true, config.isIncludeSynonyms(), false, false, false,
3373 config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, config.isIncludeSynonyms(), null, 0, 0, config.getPropertyPath());
3374 return taxonList;
3375 }
3376
3377 @Override
3378 @Transactional(readOnly = true)
3379 public <S extends TaxonBase> Pager<IdentifiedEntityDTO<S>> findByIdentifier(
3380 Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
3381 MatchMode matchmode, boolean includeEntity, Integer pageSize,
3382 Integer pageNumber, List<String> propertyPaths) {
3383 if (subtreeFilter == null){
3384 return findByIdentifier(clazz, identifier, identifierType, matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3385 }
3386
3387 long numberOfResults = dao.countByIdentifier(clazz, identifier, identifierType, subtreeFilter, matchmode);
3388 List<Object[]> daoResults = new ArrayList<>();
3389 if(numberOfResults > 0) { // no point checking again
3390 daoResults = dao.findByIdentifier(clazz, identifier, identifierType, subtreeFilter,
3391 matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3392 }
3393
3394 List<IdentifiedEntityDTO<S>> result = new ArrayList<>();
3395 for (Object[] daoObj : daoResults){
3396 if (includeEntity){
3397 result.add(new IdentifiedEntityDTO<>((DefinedTerm)daoObj[0], (String)daoObj[1], (S)daoObj[2]));
3398 }else{
3399 result.add(new IdentifiedEntityDTO<>((DefinedTerm)daoObj[0], (String)daoObj[1], (UUID)daoObj[2], (String)daoObj[3], null));
3400 }
3401 }
3402 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, result);
3403 }
3404
3405 @Override
3406 @Transactional(readOnly = true)
3407 public <S extends TaxonBase> Pager<MarkedEntityDTO<S>> findByMarker(
3408 Class<S> clazz, MarkerType markerType, Boolean markerValue,
3409 TaxonNode subtreeFilter, boolean includeEntity, TaxonTitleType titleType,
3410 Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
3411 if (subtreeFilter == null){
3412 return super.findByMarker (clazz, markerType, markerValue, includeEntity, pageSize, pageNumber, propertyPaths);
3413 }
3414
3415 Long numberOfResults = dao.countByMarker(clazz, markerType, markerValue, subtreeFilter);
3416 List<Object[]> daoResults = new ArrayList<>();
3417 if(numberOfResults > 0) { // no point checking again
3418 daoResults = dao.findByMarker(clazz, markerType, markerValue, subtreeFilter,
3419 includeEntity, titleType, pageSize, pageNumber, propertyPaths);
3420 }
3421
3422 List<MarkedEntityDTO<S>> result = new ArrayList<>();
3423 for (Object[] daoObj : daoResults){
3424 if (includeEntity){
3425 result.add(new MarkedEntityDTO<S>((MarkerType)daoObj[0], (Boolean)daoObj[1], (S)daoObj[2]));
3426 }else{
3427 result.add(new MarkedEntityDTO<S>((MarkerType)daoObj[0], (Boolean)daoObj[1], (UUID)daoObj[2], (String)daoObj[3]));
3428 }
3429 }
3430 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, result);
3431 }
3432
3433 @Override
3434 public UpdateResult moveFactualDateToAnotherTaxon(UUID fromTaxonUuid, UUID toTaxonUuid){
3435 UpdateResult result = new UpdateResult();
3436
3437 Taxon fromTaxon = (Taxon)dao.load(fromTaxonUuid);
3438 Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
3439 for(TaxonDescription description : fromTaxon.getDescriptions()){
3440 //reload to avoid session conflicts
3441 description = HibernateProxyHelper.deproxy(description, TaxonDescription.class);
3442
3443 String moveMessage = String.format("Description moved from %s", fromTaxon);
3444 if(description.isProtectedTitleCache()){
3445 String separator = "";
3446 if(!StringUtils.isBlank(description.getTitleCache())){
3447 separator = " - ";
3448 }
3449 description.setTitleCache(description.getTitleCache() + separator + moveMessage, true);
3450 }
3451 Annotation annotation = Annotation.NewInstance(moveMessage, Language.getDefaultLanguage());
3452 annotation.setAnnotationType(AnnotationType.TECHNICAL());
3453 description.addAnnotation(annotation);
3454 toTaxon.addDescription(description);
3455 dao.saveOrUpdate(toTaxon);
3456 dao.saveOrUpdate(fromTaxon);
3457 result.addUpdatedObject(toTaxon);
3458 result.addUpdatedObject(fromTaxon);
3459
3460 }
3461
3462 return result;
3463 }
3464
3465 @Override
3466 @Transactional(readOnly = false)
3467 public UpdateResult swapSynonymAndAcceptedTaxon(UUID synonymUUid,
3468 UUID acceptedTaxonUuid, boolean setNameInSource, boolean newUuidForAcceptedTaxon, SecReferenceHandlingSwapEnum secHandling, UUID newSecAcc, UUID newSecSyn) {
3469 TaxonBase<?> base = this.load(synonymUUid);
3470 Synonym syn = HibernateProxyHelper.deproxy(base, Synonym.class);
3471 base = this.load(acceptedTaxonUuid);
3472 Taxon taxon = HibernateProxyHelper.deproxy(base, Taxon.class);
3473
3474 Reference refAcc = referenceService.load(newSecAcc);
3475 Reference refSyn = referenceService.load(newSecSyn);
3476
3477 return this.swapSynonymAndAcceptedTaxon(syn, taxon, setNameInSource, newUuidForAcceptedTaxon, secHandling, refAcc, refSyn);
3478 }
3479
3480 @Override
3481 public TaxonRelationshipsDTO listTaxonRelationships(UUID taxonUuid, Set<TaxonRelationshipType> directTypes,
3482 Set<TaxonRelationshipType> inversTypes,
3483 Direction direction, boolean groupMisapplications,
3484 boolean includeUnpublished,
3485 Integer pageSize, Integer pageNumber) {
3486 TaxonBase<?> taxonBase = dao.load(taxonUuid);
3487 if (taxonBase == null || !taxonBase.isInstanceOf(TaxonBase.class)){
3488 //TODO handle
3489 throw new RuntimeException("Taxon for uuid " + taxonUuid + " not found");
3490 }else{
3491 Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3492 boolean doDirect = (direction == null || direction == Direction.relatedTo);
3493 boolean doInvers = (direction == null || direction == Direction.relatedFrom);
3494
3495 TaxonRelationshipsDTO dto = new TaxonRelationshipsDTO();
3496
3497 //TODO paging is difficult because misapplication string is an attribute
3498 //of toplevel dto
3499 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3500 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3501 // if(numberOfResults > 0) { // no point checking again
3502 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3503 // }
3504 //
3505 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3506
3507 //TODO languages
3508 List<Language> languages = null;
3509 if (doDirect){
3510 direction = Direction.relatedTo;
3511 //TODO order hints, property path
3512 List<TaxonRelationship> relations = dao.getTaxonRelationships(taxon, directTypes, includeUnpublished, pageSize, pageNumber, null, null, direction.invers());
3513 for (TaxonRelationship relation : relations){
3514 dto.addRelation(relation, direction, languages);
3515 }
3516 }
3517 if (doInvers){
3518 direction = Direction.relatedFrom;
3519 //TODO order hints, property path
3520 List<TaxonRelationship> relations = dao.getTaxonRelationships(taxon, inversTypes, includeUnpublished, pageSize, pageNumber, null, null, direction.invers());
3521 for (TaxonRelationship relation : relations){
3522 dto.addRelation(relation, direction, languages);
3523 }
3524 }
3525 if (groupMisapplications){
3526 //TODO
3527 dto.createMisapplicationString();
3528 }
3529 return dto;
3530 }
3531 }
3532 }