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