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