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