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