bcd28815fca08224f647892a18898ed475b3e643
[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 result.addUpdatedObject(name);
1312 }else{
1313 result.addDeletedObject(name);
1314 }
1315 }
1316 }
1317 return result;
1318 }
1319
1320 @Override
1321 public Map<String, Map<UUID,Set<TaxonName>>> findIdenticalTaxonNames(List<UUID> sourceRefUuids, List<String> propertyPaths) {
1322 return this.dao.findIdenticalNames(sourceRefUuids, propertyPaths);
1323 }
1324
1325 @Override
1326 public Taxon findBestMatchingTaxon(String taxonName) {
1327 MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1328 config.setTaxonNameTitle(taxonName);
1329 return findBestMatchingTaxon(config);
1330 }
1331
1332 @Override
1333 public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1334
1335 Taxon bestCandidate = null;
1336 try{
1337 // 1. search for accepted taxa
1338 List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.isIncludeUnpublished(),
1339 config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, null, 0, null, null);
1340 boolean bestCandidateMatchesSecUuid = false;
1341 boolean bestCandidateIsInClassification = false;
1342 int countEqualCandidates = 0;
1343 for(TaxonBase<?> taxonBaseCandidate : taxonList){
1344 if(taxonBaseCandidate instanceof Taxon){
1345 Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1346 boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1347 if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1348 continue;
1349 }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1350 bestCandidate = newCanditate;
1351 countEqualCandidates = 1;
1352 bestCandidateMatchesSecUuid = true;
1353 continue;
1354 }
1355
1356 boolean newCandidateInClassification = isInClassification(newCanditate, config);
1357 if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1358 continue;
1359 }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1360 bestCandidate = newCanditate;
1361 countEqualCandidates = 1;
1362 bestCandidateIsInClassification = true;
1363 continue;
1364 }
1365 if (bestCandidate == null){
1366 bestCandidate = newCanditate;
1367 countEqualCandidates = 1;
1368 continue;
1369 }
1370 }else{ //not Taxon.class
1371 continue;
1372 }
1373 countEqualCandidates++;
1374
1375 }
1376 if (bestCandidate != null){
1377 if(countEqualCandidates > 1){
1378 logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1379 return bestCandidate;
1380 } else {
1381 logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1382 return bestCandidate;
1383 }
1384 }
1385
1386 // 2. search for synonyms
1387 if (config.isIncludeSynonyms()){
1388 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.isIncludeUnpublished(),
1389 config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, null, 0, null, null);
1390 for(TaxonBase taxonBase : synonymList){
1391 if(taxonBase instanceof Synonym){
1392 Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1393 bestCandidate = synonym.getAcceptedTaxon();
1394 if(bestCandidate != null){
1395 logger.info("using accepted Taxon " + bestCandidate.getTitleCache() + " for synonym " + taxonBase.getTitleCache());
1396 return bestCandidate;
1397 }
1398 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1399 }
1400 }
1401 }
1402
1403 } catch (Exception e){
1404 logger.error(e);
1405 e.printStackTrace();
1406 }
1407
1408 return bestCandidate;
1409 }
1410
1411 private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1412 UUID configClassificationUuid = config.getClassificationUuid();
1413 if (configClassificationUuid == null){
1414 return false;
1415 }
1416 for (TaxonNode node : taxon.getTaxonNodes()){
1417 UUID classUuid = node.getClassification().getUuid();
1418 if (configClassificationUuid.equals(classUuid)){
1419 return true;
1420 }
1421 }
1422 return false;
1423 }
1424
1425 private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1426 UUID configSecUuid = config.getSecUuid();
1427 if (configSecUuid == null){
1428 return false;
1429 }
1430 UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1431 return configSecUuid.equals(taxonSecUuid);
1432 }
1433
1434 @Override
1435 public Synonym findBestMatchingSynonym(String taxonName, boolean includeUnpublished) {
1436 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, includeUnpublished, taxonName, null, null, MatchMode.EXACT, null, null, 0, null, null);
1437 if(! synonymList.isEmpty()){
1438 Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1439 if(synonymList.size() == 1){
1440 logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1441 return result;
1442 } else {
1443 logger.info("Several matching synonyms found. Using first: " + result.getTitleCache());
1444 return result;
1445 }
1446 }
1447 return null;
1448 }
1449
1450 @Override
1451 @Transactional(readOnly = false)
1452 public UpdateResult moveSynonymToAnotherTaxon(Synonym oldSynonym, UUID newTaxonUUID, boolean moveHomotypicGroup,
1453 SynonymType newSynonymType, UUID newSecundumUuid, String newSecundumDetail,
1454 boolean keepSecundumIfUndefined) throws HomotypicalGroupChangeException {
1455
1456 UpdateResult result = new UpdateResult();
1457 Taxon newTaxon = CdmBase.deproxy(dao.load(newTaxonUUID), Taxon.class);
1458 result = moveSynonymToAnotherTaxon(oldSynonym, newTaxon, moveHomotypicGroup, newSynonymType,
1459 newSecundumUuid, newSecundumDetail, keepSecundumIfUndefined);
1460
1461 return result;
1462 }
1463
1464 @Override
1465 @Transactional(readOnly = false)
1466 public UpdateResult moveSynonymToAnotherTaxon(Synonym oldSynonym,
1467 Taxon newTaxon,
1468 boolean moveHomotypicGroup,
1469 SynonymType newSynonymType) throws HomotypicalGroupChangeException {
1470 return moveSynonymToAnotherTaxon(oldSynonym, newTaxon, moveHomotypicGroup,
1471 newSynonymType,
1472 oldSynonym.getSec()!= null? oldSynonym.getSec().getUuid(): null,
1473 oldSynonym.getSecMicroReference(),
1474 true);
1475 }
1476
1477 @Override
1478 @Transactional(readOnly = false)
1479 public UpdateResult moveSynonymToAnotherTaxon(Synonym oldSynonym,
1480 Taxon newTaxon,
1481 boolean moveHomotypicGroup,
1482 SynonymType newSynonymType,
1483 UUID newSecundumUuid,
1484 String newSecundumDetail,
1485 boolean keepSecundumIfUndefined) throws HomotypicalGroupChangeException {
1486
1487 Synonym synonym = CdmBase.deproxy(dao.load(oldSynonym.getUuid()), Synonym.class);
1488 Taxon oldTaxon = CdmBase.deproxy(dao.load(synonym.getAcceptedTaxon().getUuid()), Taxon.class);
1489 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1490 TaxonName synonymName = synonym.getName();
1491 TaxonName fromTaxonName = oldTaxon.getName();
1492 //set default relationship type
1493 if (newSynonymType == null){
1494 newSynonymType = SynonymType.HETEROTYPIC_SYNONYM_OF();
1495 }
1496 boolean newRelTypeIsHomotypic = newSynonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF());
1497
1498 HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1499 int hgSize = homotypicGroup.getTypifiedNames().size();
1500 boolean isSingleInGroup = !(hgSize > 1);
1501
1502 if (! isSingleInGroup){
1503 boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1504 boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1505 if (isHomotypicToAccepted){
1506 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.";
1507 String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1508 message = String.format(message, homotypicRelatives);
1509 throw new HomotypicalGroupChangeException(message);
1510 }
1511 if (! moveHomotypicGroup){
1512 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.";
1513 throw new HomotypicalGroupChangeException(message);
1514 }
1515 }else{
1516 moveHomotypicGroup = true; //single synonym always allows to moveCompleteGroup
1517 }
1518 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1519
1520 UpdateResult result = new UpdateResult();
1521 //move all synonyms to new taxon
1522 List<Synonym> homotypicSynonyms = oldTaxon.getSynonymsInGroup(homotypicGroup);
1523 Reference newSecundum = referenceService.load(newSecundumUuid);
1524 for (Synonym synRelation: homotypicSynonyms){
1525
1526 newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
1527 oldTaxon = HibernateProxyHelper.deproxy(oldTaxon, Taxon.class);
1528 oldTaxon.removeSynonym(synRelation, false);
1529 newTaxon.addSynonym(synRelation, newSynonymType);
1530
1531 if (newSecundum != null || !keepSecundumIfUndefined){
1532 synRelation.setSec(newSecundum);
1533 }
1534 if (newSecundumDetail != null || !keepSecundumIfUndefined){
1535 synRelation.setSecMicroReference(newSecundumDetail);
1536 }
1537
1538 //set result //why is this needed? Seems wrong to me (AM 10.10.2016)
1539 if (!synRelation.equals(oldSynonym)){
1540 result.setError();
1541 }
1542 }
1543
1544 result.addUpdatedObject(oldTaxon);
1545 result.addUpdatedObject(newTaxon);
1546 saveOrUpdate(oldTaxon);
1547 saveOrUpdate(newTaxon);
1548
1549 return result;
1550 }
1551
1552 @Override
1553 public <T extends TaxonBase>List<UuidAndTitleCache<T>> getUuidAndTitleCache(Class<T> clazz, Integer limit, String pattern) {
1554
1555 return dao.getUuidAndTitleCache(clazz, limit, pattern);
1556 }
1557
1558 @Override
1559 public Pager<SearchResult<TaxonBase>> findByFullText(
1560 Class<? extends TaxonBase> clazz, String queryString,
1561 Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages,
1562 boolean highlightFragments, Integer pageSize, Integer pageNumber,
1563 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
1564
1565 LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, subtree,
1566 null, includeUnpublished, languages, highlightFragments, null);
1567
1568 // --- execute search
1569 TopGroups<BytesRef> topDocsResultSet;
1570 try {
1571 topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1572 } catch (ParseException e) {
1573 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
1574 luceneParseException.setStackTrace(e.getStackTrace());
1575 throw luceneParseException;
1576 }
1577
1578 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
1579 idFieldMap.put(CdmBaseType.TAXON, "id");
1580
1581 // --- initialize taxa, thighlight matches ....
1582 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1583 @SuppressWarnings("rawtypes")
1584 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1585 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1586
1587 long totalHits = topDocsResultSet != null ? Long.valueOf(topDocsResultSet.totalGroupCount) : 0;
1588 return new DefaultPagerImpl<>(pageNumber, totalHits, pageSize, searchResults);
1589 }
1590
1591 @Transactional(readOnly = true)
1592 @Override
1593 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) {
1594 long numberOfResults = dao.countByTitleWithRestrictions(clazz, queryString, matchmode, restrictions);
1595
1596 long numberOfResults_doubtful = dao.countByTitleWithRestrictions(clazz, "?".concat(queryString), matchmode, restrictions);
1597 List<S> results = new ArrayList<>();
1598 if(numberOfResults > 0 || numberOfResults_doubtful > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1599
1600 results = dao.findByTitleWithRestrictions(clazz, queryString, matchmode, restrictions, pageSize, pageNumber, orderHints, propertyPaths);
1601 results.addAll(dao.findByTitleWithRestrictions(clazz, "?".concat(queryString), matchmode, restrictions, pageSize, pageNumber, orderHints, propertyPaths));
1602 }
1603 Collections.sort(results, new TaxonComparator());
1604 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
1605 }
1606
1607 @Transactional(readOnly = true)
1608 @Override
1609 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) {
1610 long numberOfResults = dao.countByTitle(clazz, queryString, matchmode, criteria);
1611 //check whether there are doubtful taxa matching
1612 long numberOfResults_doubtful = dao.countByTitle(clazz, "?".concat(queryString), matchmode, criteria);
1613 List<S> results = new ArrayList<>();
1614 if(numberOfResults > 0 || numberOfResults_doubtful > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
1615 if (numberOfResults > 0){
1616 results = dao.findByTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
1617 }else{
1618 results = new ArrayList<>();
1619 }
1620 if (numberOfResults_doubtful > 0){
1621 results.addAll(dao.findByTitle(clazz, "?".concat(queryString), matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths));
1622 }
1623 }
1624 Collections.sort(results, new TaxonComparator());
1625 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
1626 }
1627
1628 @Override
1629 public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
1630 Classification classification, TaxonNode subtree,
1631 Integer pageSize, Integer pageNumber,
1632 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
1633
1634 LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification, subtree);
1635
1636 // --- execute search
1637 TopGroups<BytesRef> topDocsResultSet;
1638 try {
1639 topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1640 } catch (ParseException e) {
1641 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
1642 luceneParseException.setStackTrace(e.getStackTrace());
1643 throw luceneParseException;
1644 }
1645
1646 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
1647 idFieldMap.put(CdmBaseType.TAXON, "id");
1648
1649 // --- initialize taxa, thighlight matches ....
1650 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1651 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1652 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1653
1654 long totalHits = topDocsResultSet != null ? Long.valueOf(topDocsResultSet.totalGroupCount) : 0;
1655 return new DefaultPagerImpl<>(pageNumber, totalHits, pageSize, searchResults);
1656 }
1657
1658 /**
1659 * @param clazz
1660 * @param queryString
1661 * @param classification
1662 * @param includeUnpublished
1663 * @param languages
1664 * @param highlightFragments
1665 * @param sortFields TODO
1666 * @param directorySelectClass
1667 * @return
1668 */
1669 protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString,
1670 Classification classification, TaxonNode subtree, String className, boolean includeUnpublished, List<Language> languages,
1671 boolean highlightFragments, SortField[] sortFields) {
1672
1673 Builder finalQueryBuilder = new Builder();
1674 Builder textQueryBuilder = new Builder();
1675
1676 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1677 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1678
1679 if(sortFields == null){
1680 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false)};
1681 }
1682 luceneSearch.setSortFields(sortFields);
1683
1684 // ---- search criteria
1685 luceneSearch.setCdmTypRestriction(clazz);
1686
1687 if(!StringUtils.isEmpty(queryString) && !queryString.equals("*") && !queryString.equals("?") ) {
1688 textQueryBuilder.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1689 textQueryBuilder.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1690 }
1691 if(className != null){
1692 textQueryBuilder.add(taxonBaseQueryFactory.newTermQuery("classInfo.name", className, false), Occur.MUST);
1693 }
1694
1695 BooleanQuery textQuery = textQueryBuilder.build();
1696 if(textQuery.clauses().size() > 0) {
1697 finalQueryBuilder.add(textQuery, Occur.MUST);
1698 }
1699
1700 if(classification != null){
1701 finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
1702 }
1703 if(subtree != null){
1704 finalQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
1705 }
1706 if(!includeUnpublished) {
1707 String accPublishParam = TaxonBase.ACC_TAXON_BRIDGE_PREFIX + AcceptedTaxonBridge.DOC_KEY_PUBLISH_SUFFIX;
1708 finalQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery(accPublishParam, true), Occur.MUST);
1709 finalQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery("publish", true), Occur.MUST);
1710 }
1711
1712 luceneSearch.setQuery(finalQueryBuilder.build());
1713
1714 if(highlightFragments){
1715 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1716 }
1717 return luceneSearch;
1718 }
1719
1720 /**
1721 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1722 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1723 * drawback of requiring to do the join an indexing time.
1724 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1725 *
1726 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1727 * <ul>
1728 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1729 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1730 * <ul>
1731 * @param queryString
1732 * @param classification
1733 * @param languages
1734 * @param highlightFragments
1735 * @param sortFields TODO
1736 *
1737 * @return
1738 * @throws IOException
1739 */
1740 protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString,
1741 Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages,
1742 boolean highlightFragments, SortField[] sortFields) throws IOException {
1743
1744 String fromField;
1745 String queryTermField;
1746 String toField = "id"; // TaxonBase.uuid
1747 String publishField;
1748 String publishFieldInvers;
1749
1750 if(edge.isBidirectional()){
1751 throw new RuntimeException("Bidirectional joining not supported!");
1752 }
1753 if(edge.isEvers()){
1754 fromField = "relatedFrom.id";
1755 queryTermField = "relatedFrom.titleCache";
1756 publishField = "relatedFrom.publish";
1757 publishFieldInvers = "relatedTo.publish";
1758 } else if(edge.isInvers()) {
1759 fromField = "relatedTo.id";
1760 queryTermField = "relatedTo.titleCache";
1761 publishField = "relatedTo.publish";
1762 publishFieldInvers = "relatedFrom.publish";
1763 } else {
1764 throw new RuntimeException("Invalid direction: " + edge.getDirections());
1765 }
1766
1767 Builder finalQueryBuilder = new Builder();
1768
1769 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1770 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1771
1772 Builder joinFromQueryBuilder = new Builder();
1773 if(!StringUtils.isEmpty(queryString)){
1774 joinFromQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1775 }
1776 joinFromQueryBuilder.add(taxonBaseQueryFactory.newEntityIdsQuery("type.id", edge.getRelationshipTypes()), Occur.MUST);
1777 if(!includeUnpublished){
1778 joinFromQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery(publishField, true), Occur.MUST);
1779 joinFromQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery(publishFieldInvers, true), Occur.MUST);
1780 }
1781
1782 Query joinQuery = taxonBaseQueryFactory.newJoinQuery(TaxonRelationship.class, fromField, false, joinFromQueryBuilder.build(), toField, null, ScoreMode.Max);
1783
1784 if(sortFields == null){
1785 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false)};
1786 }
1787 luceneSearch.setSortFields(sortFields);
1788
1789 finalQueryBuilder.add(joinQuery, Occur.MUST);
1790
1791 if(classification != null){
1792 finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
1793 }
1794 if(subtree != null){
1795 finalQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
1796 }
1797
1798 luceneSearch.setQuery(finalQueryBuilder.build());
1799
1800 if(highlightFragments){
1801 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1802 }
1803 return luceneSearch;
1804 }
1805
1806 @Override
1807 public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1808 EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString,
1809 Classification classification, TaxonNode subtree,
1810 Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
1811 boolean highlightFragments, Integer pageSize,
1812 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1813 throws IOException, LuceneParseException, LuceneMultiSearchException {
1814
1815 // FIXME: allow taxonomic ordering
1816 // 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";
1817 // this require building a special sort column by a special classBridge
1818 if(highlightFragments){
1819 logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1820 "currently not fully supported by this method and thus " +
1821 "may not work with common names and misapplied names.");
1822 }
1823
1824 // convert sets to lists
1825 List<NamedArea> namedAreaList = null;
1826 List<PresenceAbsenceTerm> distributionStatusList = null;
1827 if(namedAreas != null){
1828 namedAreaList = new ArrayList<>(namedAreas.size());
1829 namedAreaList.addAll(namedAreas);
1830 }
1831 if(distributionStatus != null){
1832 distributionStatusList = new ArrayList<>(distributionStatus.size());
1833 distributionStatusList.addAll(distributionStatus);
1834 }
1835
1836 // set default if parameter is null
1837 if(searchModes == null){
1838 searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1839 }
1840
1841 // set sort order and thus override any sort orders which may have been
1842 // defined by prepare*Search methods
1843 if(orderHints == null){
1844 orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER.asList();
1845 }
1846 SortField[] sortFields = new SortField[orderHints.size()];
1847 int i = 0;
1848 for(OrderHint oh : orderHints){
1849 sortFields[i++] = oh.toSortField();
1850 }
1851 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1852 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1853
1854 boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1855
1856 List<LuceneSearch> luceneSearches = new ArrayList<>();
1857 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
1858
1859 /*
1860 ======== filtering by distribution , HOWTO ========
1861
1862 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1863 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1864 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1865 which will be put into a FilteredQuersy in the end ?
1866
1867
1868 3. how does it work in spatial?
1869 see
1870 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1871 - http://www.infoq.com/articles/LuceneSpatialSupport
1872 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1873 ------------------------------------------------------------------------
1874
1875 filter strategies:
1876 A) use a separate distribution filter per index sub-query/search:
1877 - byTaxonSyonym (query TaxaonBase):
1878 use a join area filter (Distribution -> TaxonBase)
1879 - byCommonName (query DescriptionElementBase): use an area filter on
1880 DescriptionElementBase !!! PROBLEM !!!
1881 This cannot work since the distributions are different entities than the
1882 common names and thus these are different lucene documents.
1883 - byMisaplliedNames (join query TaxonRelationship -> TaxonBase):
1884 use a join area filter (Distribution -> TaxonBase)
1885
1886 B) use a common distribution filter for all index sub-query/searches:
1887 - use a common join area filter (Distribution -> TaxonBase)
1888 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1889 PROBLEM in this case: we are losing the fragment highlighting for the
1890 common names, since the returned documents are always TaxonBases
1891 */
1892
1893 /* The QueryFactory for creating filter queries on Distributions should
1894 * The query factory used for the common names query cannot be reused
1895 * for this case, since we want to only record the text fields which are
1896 * actually used in the primary query
1897 */
1898 QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1899
1900 Builder multiIndexByAreaFilterBuilder = new Builder();
1901 boolean includeUnpublished = searchModes.contains(TaxaAndNamesSearchMode.includeUnpublished);
1902
1903 // search for taxa or synonyms
1904 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) ) {
1905 @SuppressWarnings("rawtypes")
1906 Class<? extends TaxonBase> taxonBaseSubclass = TaxonBase.class;
1907 String className = null;
1908 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1909 taxonBaseSubclass = Taxon.class;
1910 } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1911 className = "eu.etaxonomy.cdm.model.taxon.Synonym";
1912 }
1913 luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass,
1914 queryString, classification, subtree, className,
1915 includeUnpublished, languages, highlightFragments, sortFields));
1916 idFieldMap.put(CdmBaseType.TAXON, "id");
1917 /* A) does not work!!!!
1918 if(addDistributionFilter){
1919 // in this case we need a filter which uses a join query
1920 // to get the TaxonBase documents for the DescriptionElementBase documents
1921 // which are matching the areas in question
1922 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1923 namedAreaList,
1924 distributionStatusList,
1925 distributionFilterQueryFactory
1926 );
1927 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1928 }
1929 */
1930 if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1931 // add additional area filter for synonyms
1932 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1933 String toField = "accTaxon" + AcceptedTaxonBridge.DOC_KEY_ID_SUFFIX; // id in TaxonBase index
1934
1935 //TODO replace by createByDistributionJoinQuery
1936 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1937 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class, fromField, true, byDistributionQuery, toField, Taxon.class, ScoreMode.None);
1938 multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
1939
1940 }
1941 }
1942
1943 // search by CommonTaxonName
1944 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1945 // B)
1946 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1947 Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1948 CommonTaxonName.class,
1949 "inDescription.taxon.id",
1950 true,
1951 QueryFactory.addTypeRestriction(
1952 createByDescriptionElementFullTextQuery(queryString, classification, subtree, null, languages, descriptionElementQueryFactory)
1953 , CommonTaxonName.class
1954 ).build(), "id", null, ScoreMode.Max);
1955 if (logger.isDebugEnabled()){logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());}
1956 LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider,
1957 GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
1958 byCommonNameSearch.setCdmTypRestriction(Taxon.class);
1959 Builder builder = new BooleanQuery.Builder();
1960 builder.add(byCommonNameJoinQuery, Occur.MUST);
1961 if(!includeUnpublished) {
1962 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1963 builder.add(taxonBaseQueryFactory.newBooleanQuery("publish", true), Occur.MUST);
1964 }
1965 byCommonNameSearch.setQuery(builder.build());
1966 byCommonNameSearch.setSortFields(sortFields);
1967
1968 idFieldMap.put(CdmBaseType.TAXON, "id");
1969
1970 luceneSearches.add(byCommonNameSearch);
1971
1972 /* A) does not work!!!!
1973 luceneSearches.add(
1974 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1975 queryString, classification, null, languages, highlightFragments)
1976 );
1977 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1978 if(addDistributionFilter){
1979 // in this case we are able to use DescriptionElementBase documents
1980 // which are matching the areas in question directly
1981 BooleanQuery byDistributionQuery = createByDistributionQuery(
1982 namedAreaList,
1983 distributionStatusList,
1984 distributionFilterQueryFactory
1985 );
1986 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1987 } */
1988 }
1989
1990
1991 // search by misapplied names
1992 //TODO merge with pro parte synonym search once #7487 is fixed
1993 if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames) /*|| searchModes.contains(TaxaAndNamesSearchMode.doSynonyms) */) {
1994 // NOTE:
1995 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1996 // which allows doing query time joins
1997 // finds the misapplied name (Taxon B) which is an misapplication for
1998 // a related Taxon A.
1999 //
2000 Set<TaxonRelationshipType> relTypes = new HashSet<>();
2001 if (searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)){
2002 relTypes.addAll(TaxonRelationshipType.allMisappliedNameTypes());
2003 }
2004 // if (searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
2005 // relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
2006 // }
2007
2008 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
2009 new TaxonRelationshipEdge(relTypes, Direction.relatedTo),
2010 queryString, classification, subtree, includeUnpublished, languages, highlightFragments, sortFields));
2011 idFieldMap.put(CdmBaseType.TAXON, "id");
2012
2013 if(addDistributionFilter){
2014 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2015
2016 /*
2017 * Here I was facing a weird and nasty bug which took me bugging be really for hours until I found this solution.
2018 * Maybe this is a bug in java itself.
2019 *
2020 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2021 * directly:
2022 *
2023 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2024 *
2025 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2026 * will execute as expected:
2027 *
2028 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2029 * String toField = "relation." + misappliedNameForUuid +".to.id";
2030 *
2031 * Comparing both strings by the String.equals method returns true, so both String are identical.
2032 *
2033 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2034 * 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)
2035 * The bug is persistent after a reboot of the development computer.
2036 */
2037 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2038 // String toField = "relation." + misappliedNameForUuid +".to.id";
2039 String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2040 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2041 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2042
2043 //TODO replace by createByDistributionJoinQuery
2044 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2045 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class,
2046 fromField, true, byDistributionQuery, toField, null, ScoreMode.None);
2047
2048 // debug code for bug described above
2049 //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
2050 // DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2051 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2052
2053 multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
2054 }
2055 }
2056
2057 // search by pro parte synonyms
2058 if(searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
2059 //TODO merge with misapplied name search once #7487 is fixed
2060 Set<TaxonRelationshipType> relTypes = new HashSet<>();
2061 relTypes.addAll(TaxonRelationshipType.allSynonymTypes());
2062
2063 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
2064 new TaxonRelationshipEdge(relTypes, Direction.relatedTo),
2065 queryString, classification, subtree, includeUnpublished, languages, highlightFragments, sortFields));
2066 idFieldMap.put(CdmBaseType.TAXON, "id");
2067
2068 if(addDistributionFilter){
2069 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2070 String toField = "relation.8a896603-0fa3-44c6-9cd7-df2d8792e577.to.id";
2071 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2072 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class,
2073 fromField, true, byDistributionQuery, toField, null, ScoreMode.None);
2074 multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
2075 }
2076 }//end pro parte synonyms
2077
2078 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
2079 luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
2080
2081 if(addDistributionFilter){
2082
2083 // B)
2084 // in this case we need a filter which uses a join query
2085 // to get the TaxonBase documents for the DescriptionElementBase documents
2086 // which are matching the areas in question
2087 //
2088 // for doTaxa, doByCommonName
2089 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2090 namedAreaList,
2091 distributionStatusList,
2092 distributionFilterQueryFactory,
2093 Taxon.class, true
2094 );
2095 multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
2096 }
2097
2098 if (addDistributionFilter){
2099 multiSearch.setFilter(multiIndexByAreaFilterBuilder.build());
2100 }
2101
2102
2103 // --- execute search
2104 TopGroups<BytesRef> topDocsResultSet;
2105 try {
2106 topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2107 } catch (ParseException e) {
2108 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
2109 luceneParseException.setStackTrace(e.getStackTrace());
2110 throw luceneParseException;
2111 }
2112
2113 // --- initialize taxa, highlight matches ....
2114 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2115
2116
2117 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2118 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2119
2120 long totalHits = (topDocsResultSet != null) ? Long.valueOf(topDocsResultSet.totalGroupCount) : 0;
2121 return new DefaultPagerImpl<>(pageNumber, totalHits, pageSize, searchResults);
2122 }
2123
2124 /**
2125 * @param namedAreaList at least one area must be in the list
2126 * @param distributionStatusList optional
2127 * @param toType toType
2128 * Optional parameter. Only used for debugging to print the toType documents
2129 * @param asFilter TODO
2130 * @return
2131 * @throws IOException
2132 */
2133 protected Query createByDistributionJoinQuery(
2134 List<NamedArea> namedAreaList,
2135 List<PresenceAbsenceTerm> distributionStatusList,
2136 QueryFactory queryFactory, Class<? extends CdmBase> toType, boolean asFilter
2137 ) throws IOException {
2138
2139 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2140 String toField = "id"; // id in toType usually this is the TaxonBase index
2141
2142 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2143
2144 ScoreMode scoreMode = asFilter ? ScoreMode.None : ScoreMode.Max;
2145
2146 Query taxonAreaJoinQuery = queryFactory.newJoinQuery(Distribution.class, fromField, false, byDistributionQuery, toField, toType, scoreMode);
2147
2148 return taxonAreaJoinQuery;
2149 }
2150
2151 /**
2152 * @param namedAreaList
2153 * @param distributionStatusList
2154 * @param queryFactory
2155 * @return
2156 */
2157 private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2158 List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2159 Builder areaQueryBuilder = new Builder();
2160 // area field from Distribution
2161 areaQueryBuilder.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2162
2163 // status field from Distribution
2164 if(distributionStatusList != null && distributionStatusList.size() > 0){
2165 areaQueryBuilder.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2166 }
2167
2168 BooleanQuery areaQuery = areaQueryBuilder.build();
2169 logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2170 return areaQuery;
2171 }
2172
2173 /**
2174 * This method has been primarily created for testing the area join query but might
2175 * also be useful in other situations
2176 *
2177 * @param namedAreaList
2178 * @param distributionStatusList
2179 * @param classification
2180 * @param highlightFragments
2181 * @return
2182 * @throws IOException
2183 */
2184 protected LuceneSearch prepareByDistributionSearch(
2185 List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
2186 Classification classification, TaxonNode subtree) throws IOException {
2187
2188 Builder finalQueryBuilder = new Builder();
2189
2190 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2191
2192 // FIXME is this query factory using the wrong type?
2193 QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2194
2195 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false)};
2196 luceneSearch.setSortFields(sortFields);
2197
2198
2199 Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory, null, false);
2200
2201 finalQueryBuilder.add(byAreaQuery, Occur.MUST);
2202
2203 if(classification != null){
2204 finalQueryBuilder.add(taxonQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
2205 }
2206 if(subtree != null){
2207 finalQueryBuilder.add(taxonQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
2208 }
2209 BooleanQuery finalQuery = finalQueryBuilder.build();
2210 logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2211 luceneSearch.setQuery(finalQuery);
2212
2213 return luceneSearch;
2214 }
2215
2216 @Override
2217 public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2218 Class<? extends DescriptionElementBase> clazz, String queryString,
2219 Classification classification, TaxonNode subtree, List<Feature> features, List<Language> languages,
2220 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
2221
2222 LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, subtree, features, languages, highlightFragments);
2223
2224 // --- execute search
2225 TopGroups<BytesRef> topDocsResultSet;
2226 try {
2227 topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2228 } catch (ParseException e) {
2229 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
2230 luceneParseException.setStackTrace(e.getStackTrace());
2231 throw luceneParseException;
2232 }
2233
2234 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
2235 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2236
2237 // --- initialize taxa, highlight matches ....
2238 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2239 @SuppressWarnings("rawtypes")
2240 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2241 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2242
2243 int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
2244 return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
2245 }
2246
2247 @Override
2248 public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2249 Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages, boolean highlightFragments,
2250 Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException, LuceneMultiSearchException {
2251
2252 LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString,
2253 classification, subtree,
2254 null, languages, highlightFragments);
2255 LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, subtree, null,
2256 includeUnpublished, languages, highlightFragments, null);
2257
2258 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2259
2260 // --- execute search
2261 TopGroups<BytesRef> topDocsResultSet;
2262 try {
2263 topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2264 } catch (ParseException e) {
2265 LuceneParseException luceneParseException = new LuceneParseException(e.getMessage());
2266 luceneParseException.setStackTrace(e.getStackTrace());
2267 throw luceneParseException;
2268 }
2269
2270 // --- initialize taxa, highlight matches ....
2271 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2272
2273 Map<CdmBaseType, String> idFieldMap = new HashMap<>();
2274 idFieldMap.put(CdmBaseType.TAXON, "id");
2275 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2276
2277 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2278 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2279
2280 int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
2281 return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
2282 }
2283
2284 /**
2285 * @param clazz
2286 * @param queryString
2287 * @param classification
2288 * @param features
2289 * @param languages
2290 * @param highlightFragments
2291 * @param directorySelectClass
2292 * @return
2293 */
2294 protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2295 String queryString, Classification classification, TaxonNode subtree, List<Feature> features,
2296 List<Language> languages, boolean highlightFragments) {
2297
2298 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2299 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2300
2301 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.Type.STRING, false)};
2302
2303 BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, subtree, features,
2304 languages, descriptionElementQueryFactory);
2305
2306 luceneSearch.setSortFields(sortFields);
2307 luceneSearch.setCdmTypRestriction(clazz);
2308 luceneSearch.setQuery(finalQuery);
2309 if(highlightFragments){
2310 luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2311 }
2312
2313 return luceneSearch;
2314 }
2315
2316 /**
2317 * @param queryString
2318 * @param classification
2319 * @param features
2320 * @param languages
2321 * @param descriptionElementQueryFactory
2322 * @return
2323 */
2324 private BooleanQuery createByDescriptionElementFullTextQuery(String queryString,
2325 Classification classification, TaxonNode subtree, List<Feature> features,
2326 List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2327
2328 Builder finalQueryBuilder = new Builder();
2329 Builder textQueryBuilder = new Builder();
2330
2331 if(!StringUtils.isEmpty(queryString)){
2332
2333 textQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2334
2335 // common name
2336 Builder nameQueryBuilder = new Builder();
2337 if(languages == null || languages.size() == 0){
2338 nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2339 } else {
2340 Builder languageSubQueryBuilder = new Builder();
2341 for(Language lang : languages){
2342 languageSubQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD);
2343 }
2344 nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2345 nameQueryBuilder.add(languageSubQueryBuilder.build(), Occur.MUST);
2346 }
2347 textQueryBuilder.add(nameQueryBuilder.build(), Occur.SHOULD);
2348
2349
2350 // text field from TextData
2351 textQueryBuilder.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2352
2353 // --- TermBase fields - by representation ----
2354 // state field from CategoricalData
2355 textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2356
2357 // state field from CategoricalData
2358 textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2359
2360 // area field from Distribution
2361 textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2362
2363 // status field from Distribution
2364 textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2365
2366 finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
2367
2368 }
2369 // --- classification ----
2370
2371
2372 if(classification != null){
2373 finalQueryBuilder.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2374 }
2375 if(subtree != null){
2376 finalQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree.treeIndexWc(), true), Occur.MUST);
2377 }
2378
2379 // --- IdentifieableEntity fields - by uuid
2380 if(features != null && features.size() > 0 ){
2381 finalQueryBuilder.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2382 }
2383
2384 // the description must be associated with a taxon
2385 finalQueryBuilder.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2386
2387 BooleanQuery finalQuery = finalQueryBuilder.build();
2388 logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2389 return finalQuery;
2390 }
2391
2392 @Override
2393 public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymType type, boolean doWithMisappliedNames){
2394
2395 List <Synonym> inferredSynonyms = new ArrayList<>();
2396 List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<>();
2397
2398 Map <UUID, IZoologicalName> zooHashMap = new HashMap<>();
2399 boolean includeUnpublished = INCLUDE_UNPUBLISHED;
2400
2401 UUID nameUuid= taxon.getName().getUuid();
2402 IZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2403 String epithetOfTaxon = null;
2404 String infragenericEpithetOfTaxon = null;
2405 String infraspecificEpithetOfTaxon = null;
2406 if (taxonName.isSpecies()){
2407 epithetOfTaxon= taxonName.getSpecificEpithet();
2408 } else if (taxonName.isInfraGeneric()){
2409 infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2410 } else if (taxonName.isInfraSpecific()){
2411 infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2412 }
2413 String genusOfTaxon = taxonName.getGenusOrUninomial();
2414 Set<TaxonNode> nodes = taxon.getTaxonNodes();
2415 List<String> taxonNames = new ArrayList<>();
2416
2417 for (TaxonNode node: nodes){
2418 // Map<String, String> synonymsGenus = new HashMap<>(); // Changed this to be able to store the idInSource to a genusName
2419 // List<String> synonymsEpithet = new ArrayList<>();
2420
2421 if (node.getClassification().equals(classification)){
2422 if (!node.isTopmostNode()){
2423 TaxonNode parent = node.getParent();
2424 parent = CdmBase.deproxy(parent);
2425 TaxonName parentName = parent.getTaxon().getName();
2426 IZoologicalName zooParentName = CdmBase.deproxy(parentName);
2427 Taxon parentTaxon = CdmBase.deproxy(parent.getTaxon());
2428
2429 //create inferred synonyms for species, subspecies
2430 if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2431
2432 Synonym inferredEpithet = null;
2433 Synonym inferredGenus = null;
2434 Synonym potentialCombination = null;
2435
2436 List<String> propertyPaths = new ArrayList<>();
2437 propertyPaths.add("synonym");
2438 propertyPaths.add("synonym.name");
2439 List<OrderHint> orderHintsSynonyms = new ArrayList<>();
2440 orderHintsSynonyms.add(new OrderHint("titleCache", SortOrder.ASCENDING));
2441
2442 List<Synonym> synonyMsOfParent = dao.getSynonyms(parentTaxon, SynonymType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHintsSynonyms,propertyPaths);
2443 List<Synonym> synonymsOfTaxon= dao.getSynonyms(taxon, SynonymType.HETEROTYPIC_SYNONYM_OF(),
2444 null, null,orderHintsSynonyms,propertyPaths);
2445
2446 List<TaxonRelationship> taxonRelListParent = new ArrayList<>();
2447 List<TaxonRelationship> taxonRelListTaxon = new ArrayList<>();
2448 if (doWithMisappliedNames){
2449 List<OrderHint> orderHintsMisapplied = new ArrayList<>();
2450 orderHintsMisapplied.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2451 taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(),
2452 includeUnpublished, null, null, orderHintsMisapplied, propertyPaths, Direction.relatedTo);
2453 taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(),
2454 includeUnpublished, null, null, orderHintsMisapplied, propertyPaths, Direction.relatedTo);
2455 }
2456
2457 if (type.equals(SynonymType.INFERRED_EPITHET_OF())){
2458 for (Synonym synonymRelationOfParent:synonyMsOfParent){
2459
2460 inferredEpithet = createInferredEpithets(taxon,
2461 zooHashMap, taxonName, epithetOfTaxon,
2462 infragenericEpithetOfTaxon,
2463 infraspecificEpithetOfTaxon,
2464 taxonNames, parentName,
2465 synonymRelationOfParent);
2466
2467 inferredSynonyms.add(inferredEpithet);
2468 zooHashMap.put(inferredEpithet.getName().getUuid(), inferredEpithet.getName());
2469 taxonNames.add(inferredEpithet.getName().getNameCache());
2470 }
2471
2472 if (doWithMisappliedNames){
2473
2474 for (TaxonRelationship taxonRelationship: taxonRelListParent){
2475 Taxon misappliedName = taxonRelationship.getFromTaxon();
2476
2477 inferredEpithet = createInferredEpithets(taxon,
2478 zooHashMap, taxonName, epithetOfTaxon,
2479 infragenericEpithetOfTaxon,
2480 infraspecificEpithetOfTaxon,
2481 taxonNames, parentName,
2482 misappliedName);
2483
2484 inferredSynonyms.add(inferredEpithet);
2485 zooHashMap.put(inferredEpithet.getName().getUuid(), inferredEpithet.getName());
2486 taxonNames.add(inferredEpithet.getName().getNameCache());
2487 }
2488 }
2489
2490 if (!taxonNames.isEmpty()){
2491 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2492 if (!synNotInCDM.isEmpty()){
2493 inferredSynonymsToBeRemoved.clear();
2494
2495 for (Synonym syn :inferredSynonyms){
2496 IZoologicalName name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2497 if (!synNotInCDM.contains(name.getNameCache())){
2498 inferredSynonymsToBeRemoved.add(syn);
2499 }
2500 }
2501
2502 // Remove identified Synonyms from inferredSynonyms
2503 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2504 inferredSynonyms.remove(synonym);
2505 }
2506 }
2507 }
2508
2509 }else if (type.equals(SynonymType.INFERRED_GENUS_OF())){
2510
2511 for (Synonym synonymRelationOfTaxon:synonymsOfTaxon){
2512
2513 inferredGenus = createInferredGenus(taxon,
2514 zooHashMap, taxonName, epithetOfTaxon,
2515 genusOfTaxon, taxonNames, zooParentName, synonymRelationOfTaxon);
2516
2517 inferredSynonyms.add(inferredGenus);
2518 zooHashMap.put(inferredGenus.getName().getUuid(), inferredGenus.getName());
2519 taxonNames.add(inferredGenus.getName().getNameCache());
2520 }
2521
2522 if (doWithMisappliedNames){
2523
2524 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2525 Taxon misappliedName = taxonRelationship.getFromTaxon();
2526 inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName, misappliedName);
2527
2528 inferredSynonyms.add(inferredGenus);
2529 zooHashMap.put(inferredGenus.getName().getUuid(), inferredGenus.getName());
2530 taxonNames.add(inferredGenus.getName().getNameCache());
2531 }
2532 }
2533
2534
2535 if (!taxonNames.isEmpty()){
2536 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2537 IZoologicalName name;
2538 if (!synNotInCDM.isEmpty()){
2539 inferredSynonymsToBeRemoved.clear();
2540
2541 for (Synonym syn :inferredSynonyms){
2542 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2543 if (!synNotInCDM.contains(name.getNameCache())){
2544 inferredSynonymsToBeRemoved.add(syn);
2545 }
2546 }
2547
2548 // Remove identified Synonyms from inferredSynonyms
2549 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2550 inferredSynonyms.remove(synonym);
2551 }
2552 }
2553 }
2554
2555 }else if (type.equals(SynonymType.POTENTIAL_COMBINATION_OF())){
2556
2557 Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2558 //for all synonyms of the parent...
2559 for (Synonym synonymRelationOfParent:synonyMsOfParent){
2560 TaxonName synName;
2561 HibernateProxyHelper.deproxy(synonymRelationOfParent);
2562
2563 synName = synonymRelationOfParent.getName();
2564
2565 // Set the sourceReference
2566 sourceReference = synonymRelationOfParent.getSec();
2567
2568 // Determine the idInSource
2569 String idInSourceParent = getIdInSource(synonymRelationOfParent);
2570
2571 IZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2572 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2573 String synParentInfragenericName = null;
2574 String synParentSpecificEpithet = null;
2575
2576 if (parentSynZooName.isInfraGeneric()){
2577 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2578 }
2579 if (parentSynZooName.isSpecies()){
2580 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2581 }
2582
2583 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2584 synonymsGenus.put(synGenusName, idInSource);
2585 }*/
2586
2587 //for all synonyms of the taxon
2588
2589 for (Synonym synonymRelationOfTaxon:synonymsOfTaxon){
2590
2591 IZoologicalName zooSynName = getZoologicalName(synonymRelationOfTaxon.getName().getUuid(), zooHashMap);
2592 potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2593 synParentGenus,
2594 synParentInfragenericName,
2595 synParentSpecificEpithet, synonymRelationOfTaxon, zooHashMap);
2596
2597 taxon.addSynonym(potentialCombination, SynonymType.POTENTIAL_COMBINATION_OF());
2598 inferredSynonyms.add(potentialCombination);
2599 zooHashMap.put(potentialCombination.getName().getUuid(), potentialCombination.getName());
2600 taxonNames.add(potentialCombination.getName().getNameCache());
2601 }
2602 }
2603
2604 if (doWithMisappliedNames){
2605
2606 for (TaxonRelationship parentRelationship: taxonRelListParent){
2607
2608 TaxonName misappliedParentName;
2609
2610 Taxon misappliedParent = parentRelationship.getFromTaxon();
2611 misappliedParentName = misappliedParent.getName();
2612
2613 HibernateProxyHelper.deproxy(misappliedParent);
2614
2615 // Set the sourceReference
2616 sourceReference = misappliedParent.getSec();
2617
2618 // Determine the idInSource
2619 String idInSourceParent = getIdInSource(misappliedParent);
2620
2621 IZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2622 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2623 String synParentInfragenericName = null;
2624 String synParentSpecificEpithet = null;
2625
2626 if (parentSynZooName.isInfraGeneric()){
2627 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2628 }
2629 if (parentSynZooName.isSpecies()){
2630 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2631 }
2632
2633 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2634 Taxon misappliedName = taxonRelationship.getFromTaxon();
2635 IZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2636 potentialCombination = createPotentialCombination(
2637 idInSourceParent, parentSynZooName, zooMisappliedName,
2638 synParentGenus,
2639 synParentInfragenericName,
2640 synParentSpecificEpithet, misappliedName, zooHashMap);
2641
2642 taxon.addSynonym(potentialCombination, SynonymType.POTENTIAL_COMBINATION_OF());
2643 inferredSynonyms.add(potentialCombination);
2644 zooHashMap.put(potentialCombination.getName().getUuid(), potentialCombination.getName());
2645 taxonNames.add(potentialCombination.getName().getNameCache());
2646 }
2647 }
2648 }
2649
2650 if (!taxonNames.isEmpty()){
2651 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2652 IZoologicalName name;
2653 if (!synNotInCDM.isEmpty()){
2654 inferredSynonymsToBeRemoved.clear();
2655 for (Synonym syn :inferredSynonyms){
2656 try{
2657 name = syn.getName();
2658 }catch (ClassCastException e){
2659 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2660 }
2661 if (!synNotInCDM.contains(name.getNameCache())){
2662 inferredSynonymsToBeRemoved.add(syn);
2663 }
2664 }
2665 // Remove identified Synonyms from inferredSynonyms
2666 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2667 inferredSynonyms.remove(synonym);
2668 }
2669 }
2670 }
2671 }
2672 }else {
2673 logger.info("The synonym type is not defined.");
2674 return inferredSynonyms;
2675 }
2676 }
2677 }
2678 }
2679
2680 return inferredSynonyms;
2681 }
2682
2683 private Synonym createPotentialCombination(String idInSourceParent,
2684 IZoologicalName parentSynZooName, IZoologicalName zooSynName, String synParentGenus,
2685 String synParentInfragenericName, String synParentSpecificEpithet,
2686 TaxonBase<?> syn, Map<UUID, IZoologicalName> zooHashMap) {
2687 Synonym potentialCombination;
2688 Reference sourceReference;
2689 IZoologicalName inferredSynName;
2690 HibernateProxyHelper.deproxy(syn);
2691
2692 // Set sourceReference
2693 sourceReference = syn.getSec();
2694 if (sourceReference == null){
2695 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2696 //TODO:Remove
2697 if (!parentSynZooName.getTaxa().isEmpty()){
2698 TaxonBase<?> taxon = parentSynZooName.getTaxa().iterator().next();
2699
2700 sourceReference = taxon.getSec();
2701 }
2702 }
2703 String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2704
2705 String synTaxonInfraSpecificName= null;
2706
2707 if (parentSynZooName.isSpecies()){
2708 synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2709 }
2710
2711 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2712 synonymsEpithet.add(epithetName);
2713 }*/
2714
2715 //create potential combinations...
2716 inferredSynName = TaxonNameFactory.NewZoologicalInstance(syn.getName().getRank());
2717
2718 inferredSynName.setGenusOrUninomial(synParentGenus);
2719 if (zooSynName.isSpecies()){
2720 inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2721 if (parentSynZooName.isInfraGeneric()){
2722 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2723 }
2724 }
2725 if (zooSynName.isInfraSpecific()){
2726 inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2727 inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2728 }
2729 if (parentSynZooName.isInfraGeneric()){
2730 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2731 }
2732
2733 potentialCombination = Synonym.NewInstance(inferredSynName, null);
2734
2735 // Set the sourceReference
2736 potentialCombination.setSec(sourceReference);
2737
2738
2739 // Determine the idInSource
2740 String idInSourceSyn= getIdInSource(syn);
2741
2742 if (idInSourceParent != null && idInSourceSyn != null) {
2743 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2744 inferredSynName.addSource(originalSource);
2745 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2746 potentialCombination.addSource(originalSource);
2747 }
2748
2749 return potentialCombination;
2750 }
2751
2752 private Synonym createInferredGenus(Taxon taxon,
2753 Map<UUID, IZoologicalName> zooHashMap, IZoologicalName taxonName,
2754 String epithetOfTaxon, String genusOfTaxon,
2755 List<String> taxonNames, IZoologicalName zooParentName,
2756 TaxonBase syn) {
2757
2758 Synonym inferredGenus;
2759 TaxonName synName;
2760 IZoologicalName inferredSynName;
2761 synName =syn.getName();
2762 HibernateProxyHelper.deproxy(syn);
2763
2764 // Determine the idInSource
2765 String idInSourceSyn = getIdInSource(syn);
2766 String idInSourceTaxon = getIdInSource(taxon);
2767 // Determine the sourceReference
2768 Reference sourceReference = syn.getSec();
2769
2770 //logger.warn(sourceReference.getTitleCache());
2771
2772 synName = syn.getName();
2773 IZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2774 String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2775 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2776 synonymsEpithet.add(synSpeciesEpithetName);
2777 }*/
2778
2779 inferredSynName = TaxonNameFactory.NewZoologicalInstance(taxon.getName().getRank());
2780 //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...
2781
2782 inferredSynName.setGenusOrUninomial(genusOfTaxon);
2783 if (zooParentName.isInfraGeneric()){
2784 inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2785 }
2786
2787 if (taxonName.isSpecies()){
2788 inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2789 }
2790 if (taxonName.isInfraSpecific()){
2791 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2792 inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2793 }
2794
2795 inferredGenus = Synonym.NewInstance(inferredSynName, null);
2796
2797 // Set the sourceReference
2798 inferredGenus.setSec(sourceReference);
2799
2800 // Add the original source
2801 if (idInSourceSyn != null && idInSourceTaxon != null) {
2802 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2803 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2804 inferredGenus.addSource(originalSource);
2805
2806 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2807 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2808 inferredSynName.addSource(originalSource);
2809 originalSource = null;
2810
2811 }else{
2812 logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2813 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2814 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2815 inferredGenus.addSource(originalSource);
2816
2817 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2818 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2819 inferredSynName.addSource(originalSource);
2820 originalSource = null;
2821 }
2822
2823 taxon.addSynonym(inferredGenus, SynonymType.INFERRED_GENUS_OF());
2824
2825 return inferredGenus;
2826 }
2827
2828 private Synonym createInferredEpithets(Taxon taxon,
2829 Map<UUID, IZoologicalName> zooHashMap, IZoologicalName taxonName,
2830 String epithetOfTaxon, String infragenericEpithetOfTaxon,
2831 String infraspecificEpithetOfTaxon, List<String> taxonNames,
2832 TaxonName parentName, TaxonBase<?> syn) {
2833
2834 Synonym inferredEpithet;
2835 TaxonName synName;
2836 IZoologicalName inferredSynName;
2837 HibernateProxyHelper.deproxy(syn);
2838
2839 // Determine the idInSource
2840 String idInSourceSyn = getIdInSource(syn);
2841 String idInSourceTaxon = getIdInSource(taxon);
2842 // Determine the sourceReference
2843 Reference sourceReference = syn.getSec();
2844
2845 if (sourceReference == null){
2846 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2847 sourceReference = taxon.getSec();
2848 }
2849
2850 synName = syn.getName();
2851 IZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2852 String synGenusName = zooSynName.getGenusOrUninomial();
2853 String synInfraGenericEpithet = null;
2854 String synSpecificEpithet = null;
2855
2856 if (zooSynName.getInfraGenericEpithet() != null){
2857 synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2858 }
2859
2860 if (zooSynName.isInfraSpecific()){
2861 synSpecificEpithet = zooSynName.getSpecificEpithet();
2862 }
2863
2864 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2865 synonymsGenus.put(synGenusName, idInSource);
2866 }*/
2867
2868 inferredSynName = TaxonNameFactory.NewZoologicalInstance(taxon.getName().getRank());
2869
2870 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2871 if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2872 logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2873 }
2874 inferredSynName.setGenusOrUninomial(synGenusName);
2875
2876 if (parentName.isInfraGeneric()){
2877 inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2878 }
2879 if (taxonName.isSpecies()){
2880 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2881 }else if (taxonName.isInfraSpecific()){
2882 inferredSynName.setSpecificEpithet(synSpecificEpithet);
2883 inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2884 }
2885
2886 inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2887
2888 // Set the sourceReference
2889 inferredEpithet.setSec(sourceReference);
2890
2891 /* Add the original source
2892 if (idInSource != null) {
2893 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2894
2895 // Add the citation
2896 Reference citation = getCitation(syn);
2897 if (citation != null) {
2898 originalSource.setCitation(citation);
2899 inferredEpithet.addSource(originalSource);
2900 }
2901 }*/
2902 String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2903
2904
2905 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2906 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2907
2908 inferredEpithet.addSource(originalSource);
2909
2910 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2911 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2912
2913 inferredSynName.addSource(originalSource);
2914
2915 taxon.addSynonym(inferredEpithet, SynonymType.INFERRED_EPITHET_OF());
2916
2917 return inferredEpithet;
2918 }
2919
2920 /**
2921 * Returns an existing IZoologicalName or extends an internal hashmap if it does not exist.
2922 * Very likely only useful for createInferredSynonyms().
2923 * @param uuid
2924 * @param zooHashMap
2925 * @return
2926 */
2927 private IZoologicalName getZoologicalName(UUID uuid, Map <UUID, IZoologicalName> zooHashMap) {
2928 IZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2929 if (taxonName == null) {
2930 taxonName = zooHashMap.get(uuid);
2931 }
2932 return taxonName;
2933 }
2934
2935 /**
2936 * Returns the idInSource for a given Synonym.
2937 * @param syn
2938 */
2939 private String getIdInSource(TaxonBase<?> taxonBase) {
2940 String idInSource = null;
2941 Set<IdentifiableSource> sources = taxonBase.getSources();
2942 if (sources.size() == 1) {
2943 IdentifiableSource source = sources.iterator().next();
2944 if (source != null) {
2945 idInSource = source.getIdInSource();
2946 }
2947 } else if (sources.size() > 1) {
2948 int count = 1;
2949 idInSource = "";
2950 for (IdentifiableSource source : sources) {
2951 idInSource += source.getIdInSource();
2952 if (count < sources.size()) {
2953 idInSource += "; ";
2954 }
2955 count++;
2956 }
2957 } else if (sources.size() == 0){
2958 logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2959 }
2960
2961 return idInSource;
2962 }
2963
2964 /**
2965 * Returns the citation for a given Synonym.
2966 * @param syn
2967 */
2968 private Reference getCitation(Synonym syn) {
2969 Reference citation = null;
2970 Set<IdentifiableSource> sources = syn.getSources();
2971 if (sources.size() == 1) {
2972 IdentifiableSource source = sources.iterator().next();
2973 if (source != null) {
2974 citation = source.getCitation();
2975 }
2976 } else if (sources.size() > 1) {
2977 logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2978 }
2979
2980 return citation;
2981 }
2982
2983 @Override
2984 public List<Synonym> createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2985 List <Synonym> inferredSynonyms = new ArrayList<>();
2986
2987 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2988 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2989 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2990
2991 return inferredSynonyms;
2992 }
2993
2994 @Override
2995 public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2996
2997 // TODO quickly implemented, create according dao !!!!
2998 Set<TaxonNode> nodes = new HashSet<>();
2999 Set<Classification> classifications = new HashSet<>();
3000 List<Classification> list = new ArrayList<>();
3001
3002 if (taxonBase == null) {
3003 return list;
3004 }
3005
3006 taxonBase = load(taxonBase.getUuid());
3007
3008 if (taxonBase instanceof Taxon) {
3009 nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
3010 } else {
3011 Taxon taxon = ((Synonym)taxonBase).getAcceptedTaxon();
3012 if (taxon != null){
3013 nodes.addAll(taxon.getTaxonNodes());
3014 }
3015 }
3016 for (TaxonNode node : nodes) {
3017 classifications.add(node.getClassification());
3018 }
3019 list.addAll(classifications);
3020 return list;
3021 }
3022
3023 @Override
3024 @Transactional(readOnly = false)
3025 public UpdateResult changeRelatedTaxonToSynonym(UUID fromTaxonUuid,
3026 UUID toTaxonUuid,
3027 TaxonRelationshipType oldRelationshipType,
3028 SynonymType synonymType) throws DataChangeNoRollbackException {
3029 UpdateResult result = new UpdateResult();
3030 Taxon fromTaxon = (Taxon) dao.load(fromTaxonUuid);
3031 Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
3032 result = changeRelatedTaxonToSynonym(fromTaxon, toTaxon, oldRelationshipType, synonymType);
3033
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() && taxonBase.getName() != null){
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
3108 return result;
3109 }
3110
3111 private DeleteResult isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3112
3113 DeleteResult result = new DeleteResult();
3114 for (CdmBase ref: references){
3115 if (!(ref instanceof Taxon || ref instanceof TaxonName || ref instanceof SecundumSource)){
3116 String message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3117 result.addException(new ReferencedObjectUndeletableException(message));
3118 result.addRelatedObject(ref);
3119 result.setAbort();
3120 }
3121 }
3122
3123 return result;
3124 }
3125
3126 private DeleteResult isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3127 String message = null;
3128 DeleteResult result = new DeleteResult();
3129 for (CdmBase ref: references){
3130 if (!(ref instanceof TaxonName || ref instanceof SecundumSource)){
3131 message = null;
3132 if (!config.isDeleteSynonymRelations() && (ref instanceof Synonym)){
3133 message = "The taxon can't be deleted as long as it has synonyms.";
3134 }
3135 if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3136 message = "The taxon can't be deleted as long as it has factual data.";
3137 }
3138
3139 if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
3140 message = "The taxon can't be deleted as long as it belongs to a taxon node.";
3141 }
3142 if (ref instanceof TaxonNode && config.getClassificationUuid() != null && !config.isDeleteInAllClassifications() && !((TaxonNode)ref).getClassification().getUuid().equals(config.getClassificationUuid())){
3143 message = "The taxon can't be deleted as long as it is used in more than one classification";
3144
3145 }
3146 if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonRelationship)){
3147 if (!config.isDeleteMisappliedNames() &&
3148 (((TaxonRelationship)ref).getType().isMisappliedName())){
3149 message = "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3150 } else{
3151 message = "The taxon can't be deleted as long as it belongs to taxon relationship.";
3152 }
3153 }
3154 if (ref instanceof PolytomousKeyNode){
3155 message = "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3156 }
3157
3158 if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){
3159 message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3160 }
3161
3162 /* //PolytomousKeyNode
3163 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3164 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3165 return message;
3166 }*/
3167
3168 //TaxonInteraction
3169 if (ref.isInstanceOf(TaxonInteraction.class)){
3170 message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3171 }
3172
3173 //TaxonInteraction
3174 if (ref.isInstanceOf(DeterminationEvent.class)){
3175 message = "Taxon can't be deleted as it is used in a determination event";
3176 }
3177 }
3178 if (message != null){
3179 result.addException(new ReferencedObjectUndeletableException(message));
3180 result.addRelatedObject(ref);
3181 result.setAbort();
3182 }
3183 }
3184
3185 return result;
3186 }
3187
3188 @Override
3189 public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3190 IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3191
3192 //preliminary implementation
3193
3194 Set<Taxon> taxa = new HashSet<>();
3195 TaxonBase<?> taxonBase = find(taxonUuid);
3196 if (taxonBase == null){
3197 return new IncludedTaxaDTO();
3198 }else if (taxonBase.isInstanceOf(Taxon.class)){
3199 Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3200 taxa.add(taxon);
3201 }else if (taxonBase.isInstanceOf(Synonym.class)){
3202 //TODO partial synonyms ??
3203 //TODO synonyms in general
3204 Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3205 taxa.add(syn.getAcceptedTaxon());
3206 }else{
3207 throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3208 }
3209
3210 Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3211 int i = 0;
3212 while((! related.isEmpty()) && i++ < 100){ //to avoid
3213 related = makeRelatedIncluded(related, result, config);
3214 }
3215
3216 return result;
3217 }
3218
3219 /**
3220 * @param uncheckedTaxa
3221 * @param existingTaxa
3222 * @param config
3223 *
3224 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3225 * data structure.
3226 * @return the set of conceptually related taxa for further use
3227 */
3228 private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3229
3230 //children
3231 Set<TaxonNode> taxonNodes = new HashSet<>();
3232 for (Taxon taxon: uncheckedTaxa){
3233 taxonNodes.addAll(taxon.getTaxonNodes());
3234 }
3235
3236 Set<Taxon> children = new HashSet<>();
3237 if (! config.onlyCongruent){
3238 for (TaxonNode node: taxonNodes){
3239 List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, config.includeUnpublished, null);
3240 for (TaxonNode child : childNodes){
3241 children.add(child.getTaxon());
3242 }
3243 }
3244 children.remove(null); // just to be on the save side
3245 }
3246
3247 Iterator<Taxon> it = children.iterator();
3248 while(it.hasNext()){
3249 UUID uuid = it.next().getUuid();
3250 if (existingTaxa.contains(uuid)){
3251 it.remove();
3252 }else{
3253 existingTaxa.addIncludedTaxon(uuid, new ArrayList<>(), false);
3254 }
3255 }
3256
3257 //concept relations
3258 Set<Taxon> uncheckedAndChildren = new HashSet<>(uncheckedTaxa);
3259 uncheckedAndChildren.addAll(children);
3260
3261 Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3262
3263
3264 Set<Taxon> result = new HashSet<>(relatedTaxa);
3265 return result;
3266 }
3267
3268 /**
3269 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3270 * @return the set of these computed taxa
3271 */
3272 private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3273 Set<Taxon> result = new HashSet<>();
3274
3275 for (Taxon taxon : unchecked){
3276 Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3277 Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3278
3279 for (TaxonRelationship fromRel : fromRelations){
3280 if (config.includeDoubtful == false && fromRel.isDoubtful()){
3281 continue;
3282 }
3283 TaxonRelationshipType fromRelType = fromRel.getType();
3284 if (fromRelType.equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3285 !config.onlyCongruent && (
3286 fromRelType.equals(TaxonRelationshipType.INCLUDES()) ||
3287 fromRelType.equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3288 )
3289 ){
3290 result.add(fromRel.getToTaxon());
3291 }
3292 }
3293
3294 for (TaxonRelationship toRel : toRelations){
3295 if (config.includeDoubtful == false && toRel.isDoubtful()){
3296 continue;
3297 }
3298 TaxonRelationshipType fromRelType = toRel.getType();
3299 if (fromRelType.equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3300 !config.includeDoubtful && fromRelType.equals(TaxonRelationshipType.TAXONOMICALLY_INCLUDED_IN())){
3301 result.add(toRel.getFromTaxon());
3302 }
3303 }
3304 }
3305
3306 Iterator<Taxon> it = result.iterator();
3307 while(it.hasNext()){
3308 UUID uuid = it.next().getUuid();
3309 if (existingTaxa.contains(uuid)){
3310 it.remove();
3311 }else{
3312 existingTaxa.addIncludedTaxon(uuid, new ArrayList<>(), false);
3313 }
3314 }
3315 return result;
3316 }
3317
3318 @Override
3319 public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3320 @SuppressWarnings("rawtypes")
3321 List<TaxonBase> taxonList = dao.getTaxaByName(true, config.isIncludeSynonyms(), false, false, false,
3322 config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, config.isIncludeSynonyms(), null, 0, 0, config.getPropertyPath());
3323 return taxonList;
3324 }
3325
3326 @Override
3327 @Transactional(readOnly = true)
3328 public <S extends TaxonBase> Pager<IdentifiedEntityDTO<S>> findByIdentifier(
3329 Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
3330 MatchMode matchmode, boolean includeEntity, Integer pageSize,
3331 Integer pageNumber, List<String> propertyPaths) {
3332 if (subtreeFilter == null){
3333 return findByIdentifier(clazz, identifier, identifierType, matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3334 }
3335
3336 long numberOfResults = dao.countByIdentifier(clazz, identifier, identifierType, subtreeFilter, matchmode);
3337 List<Object[]> daoResults = new ArrayList<>();
3338 if(numberOfResults > 0) { // no point checking again
3339 daoResults = dao.findByIdentifier(clazz, identifier, identifierType, subtreeFilter,
3340 matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3341 }
3342
3343 List<IdentifiedEntityDTO<S>> result = new ArrayList<>();
3344 for (Object[] daoObj : daoResults){
3345 if (includeEntity){
3346 result.add(new IdentifiedEntityDTO<>((DefinedTerm)daoObj[0], (String)daoObj[1], (S)daoObj[2]));
3347 }else{
3348 result.add(new IdentifiedEntityDTO<>((DefinedTerm)daoObj[0], (String)daoObj[1], (UUID)daoObj[2], (String)daoObj[3], null));
3349 }
3350 }
3351 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, result);
3352 }
3353
3354 @Override
3355 @Transactional(readOnly = true)
3356 public <S extends TaxonBase> Pager<MarkedEntityDTO<S>> findByMarker(
3357 Class<S> clazz, MarkerType markerType, Boolean markerValue,
3358 TaxonNode subtreeFilter, boolean includeEntity, TaxonTitleType titleType,
3359 Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
3360 if (subtreeFilter == null){
3361 return super.findByMarker (clazz, markerType, markerValue, includeEntity, pageSize, pageNumber, propertyPaths);
3362 }
3363
3364 Long numberOfResults = dao.countByMarker(clazz, markerType, markerValue, subtreeFilter);
3365 List<Object[]> daoResults = new ArrayList<>();
3366 if(numberOfResults > 0) { // no point checking again
3367 daoResults = dao.findByMarker(clazz, markerType, markerValue, subtreeFilter,
3368 includeEntity, titleType, pageSize, pageNumber, propertyPaths);
3369 }
3370
3371 List<MarkedEntityDTO<S>> result = new ArrayList<>();
3372 for (Object[] daoObj : daoResults){
3373 if (includeEntity){
3374 result.add(new MarkedEntityDTO<S>((MarkerType)daoObj[0], (Boolean)daoObj[1], (S)daoObj[2]));
3375 }else{
3376 result.add(new MarkedEntityDTO<S>((MarkerType)daoObj[0], (Boolean)daoObj[1], (UUID)daoObj[2], (String)daoObj[3]));
3377 }
3378 }
3379 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, result);
3380 }
3381
3382 @Override
3383 public UpdateResult moveFactualDateToAnotherTaxon(UUID fromTaxonUuid, UUID toTaxonUuid){
3384 UpdateResult result = new UpdateResult();
3385
3386 Taxon fromTaxon = (Taxon)dao.load(fromTaxonUuid);
3387 Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
3388 for(TaxonDescription description : fromTaxon.getDescriptions()){
3389 //reload to avoid session conflicts
3390 description = HibernateProxyHelper.deproxy(description, TaxonDescription.class);
3391
3392 String moveMessage = String.format("Description moved from %s", fromTaxon);
3393 if(description.isProtectedTitleCache()){
3394 String separator = "";
3395 if(!StringUtils.isBlank(description.getTitleCache())){
3396 separator = " - ";
3397 }
3398 description.setTitleCache(description.getTitleCache() + separator + moveMessage, true);
3399 }
3400 Annotation annotation = Annotation.NewInstance(moveMessage, Language.getDefaultLanguage());
3401 annotation.setAnnotationType(AnnotationType.TECHNICAL());
3402 description.addAnnotation(annotation);
3403 toTaxon.addDescription(description);
3404 dao.saveOrUpdate(toTaxon);
3405 dao.saveOrUpdate(fromTaxon);
3406 result.addUpdatedObject(toTaxon);
3407 result.addUpdatedObject(fromTaxon);
3408
3409 }
3410
3411 return result;
3412 }
3413
3414 @Override
3415 @Transactional(readOnly = false)
3416 public UpdateResult swapSynonymAndAcceptedTaxon(UUID synonymUUid,
3417 UUID acceptedTaxonUuid, boolean setNameInSource, boolean newUuidForAcceptedTaxon) {
3418 TaxonBase<?> base = this.load(synonymUUid);
3419 Synonym syn = HibernateProxyHelper.deproxy(base, Synonym.class);
3420 base = this.load(acceptedTaxonUuid);
3421 Taxon taxon = HibernateProxyHelper.deproxy(base, Taxon.class);
3422
3423 return this.swapSynonymAndAcceptedTaxon(syn, taxon, setNameInSource, newUuidForAcceptedTaxon);
3424 }
3425
3426 @Override
3427 public TaxonRelationshipsDTO listTaxonRelationships(UUID taxonUuid, Set<TaxonRelationshipType> directTypes,
3428 Set<TaxonRelationshipType> inversTypes,
3429 Direction direction, boolean groupMisapplications,
3430 boolean includeUnpublished,
3431 Integer pageSize, Integer pageNumber) {
3432 TaxonBase<?> taxonBase = dao.load(taxonUuid);
3433 if (taxonBase == null || !taxonBase.isInstanceOf(TaxonBase.class)){
3434 //TODO handle
3435 throw new RuntimeException("Taxon for uuid " + taxonUuid + " not found");
3436 }else{
3437 Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3438 boolean doDirect = (direction == null || direction == Direction.relatedTo);
3439 boolean doInvers = (direction == null || direction == Direction.relatedFrom);
3440
3441 TaxonRelationshipsDTO dto = new TaxonRelationshipsDTO();
3442
3443 //TODO paging is difficult because misapplication string is an attribute
3444 //of toplevel dto
3445 // long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
3446 // List<TaxonRelationshipsDTO> results = new ArrayList<>();
3447 // if(numberOfResults > 0) { // no point checking again
3448 // results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
3449 // }
3450 //
3451 // return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
3452
3453 //TODO languages
3454 List<Language> languages = null;
3455 if (doDirect){
3456 direction = Direction.relatedTo;
3457 //TODO order hints, property path
3458 List<TaxonRelationship> relations = dao.getTaxonRelationships(taxon, directTypes, includeUnpublished, pageSize, pageNumber, null, null, direction.invers());
3459 for (TaxonRelationship relation : relations){
3460 dto.addRelation(relation, direction, languages);
3461 }
3462 }
3463 if (doInvers){
3464 direction = Direction.relatedFrom;
3465 //TODO order hints, property path
3466 List<TaxonRelationship> relations = dao.getTaxonRelationships(taxon, inversTypes, includeUnpublished, pageSize, pageNumber, null, null, direction.invers());
3467 for (TaxonRelationship relation : relations){
3468 dto.addRelation(relation, direction, languages);
3469 }
3470 }
3471 if (groupMisapplications){
3472 //TODO
3473 dto.createMisapplicationString();
3474 }
3475 return dto;
3476 }
3477 }
3478 }