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