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