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