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