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