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