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