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