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