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