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