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