filtering of lucene searches by distributions basically implemented, needs to be...
[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.List;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.UUID;
22
23 import org.apache.log4j.Logger;
24 import org.apache.lucene.index.CorruptIndexException;
25 import org.apache.lucene.queryParser.ParseException;
26 import org.apache.lucene.search.BooleanClause.Occur;
27 import org.apache.lucene.search.BooleanQuery;
28 import org.apache.lucene.search.IndexSearcher;
29 import org.apache.lucene.search.Query;
30 import org.apache.lucene.search.QueryWrapperFilter;
31 import org.apache.lucene.search.SortField;
32 import org.springframework.beans.factory.annotation.Autowired;
33 import org.springframework.stereotype.Service;
34 import org.springframework.transaction.annotation.Transactional;
35
36 import eu.etaxonomy.cdm.api.service.config.IFindTaxaAndNamesConfigurator;
37 import eu.etaxonomy.cdm.api.service.config.MatchingTaxonConfigurator;
38 import eu.etaxonomy.cdm.api.service.config.NameDeletionConfigurator;
39 import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
40 import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
41 import eu.etaxonomy.cdm.api.service.exception.HomotypicalGroupChangeException;
42 import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
43 import eu.etaxonomy.cdm.api.service.pager.Pager;
44 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
45 import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
46 import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearch;
47 import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearchException;
48 import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
49 import eu.etaxonomy.cdm.api.service.search.LuceneSearch.TopGroupsWithMaxScore;
50 import eu.etaxonomy.cdm.api.service.search.QueryFactory;
51 import eu.etaxonomy.cdm.api.service.search.SearchResult;
52 import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
53 import eu.etaxonomy.cdm.api.service.util.TaxonRelationshipEdge;
54 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
55 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
56 import eu.etaxonomy.cdm.hibernate.search.DefinedTermBaseClassBridge;
57 import eu.etaxonomy.cdm.hibernate.search.GroupByTaxonClassBridge;
58 import eu.etaxonomy.cdm.hibernate.search.MultilanguageTextFieldBridge;
59 import eu.etaxonomy.cdm.model.CdmBaseType;
60 import eu.etaxonomy.cdm.model.common.CdmBase;
61 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
62 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
63 import eu.etaxonomy.cdm.model.common.Language;
64 import eu.etaxonomy.cdm.model.common.OrderedTermVocabulary;
65 import eu.etaxonomy.cdm.model.common.RelationshipBase;
66 import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
67 import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
68 import eu.etaxonomy.cdm.model.description.CommonTaxonName;
69 import eu.etaxonomy.cdm.model.description.DescriptionBase;
70 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
71 import eu.etaxonomy.cdm.model.description.Distribution;
72 import eu.etaxonomy.cdm.model.description.Feature;
73 import eu.etaxonomy.cdm.model.description.IIdentificationKey;
74 import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
75 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
76 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
77 import eu.etaxonomy.cdm.model.description.TaxonDescription;
78 import eu.etaxonomy.cdm.model.description.TaxonInteraction;
79 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
80 import eu.etaxonomy.cdm.model.location.NamedArea;
81 import eu.etaxonomy.cdm.model.media.Media;
82 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
83 import eu.etaxonomy.cdm.model.media.MediaUtils;
84 import eu.etaxonomy.cdm.model.molecular.DnaSample;
85 import eu.etaxonomy.cdm.model.molecular.Sequence;
86 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
87 import eu.etaxonomy.cdm.model.name.Rank;
88 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
89 import eu.etaxonomy.cdm.model.name.ZoologicalName;
90 import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;
91 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
92 import eu.etaxonomy.cdm.model.reference.Reference;
93 import eu.etaxonomy.cdm.model.taxon.Classification;
94 import eu.etaxonomy.cdm.model.taxon.Synonym;
95 import eu.etaxonomy.cdm.model.taxon.SynonymRelationship;
96 import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;
97 import eu.etaxonomy.cdm.model.taxon.Taxon;
98 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
99 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
100 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
101 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
102 import eu.etaxonomy.cdm.persistence.dao.AbstractBeanInitializer;
103 import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
104 import eu.etaxonomy.cdm.persistence.dao.common.IOrderedTermVocabularyDao;
105 import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
106 import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
107 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
108 import eu.etaxonomy.cdm.persistence.fetch.CdmFetch;
109 import eu.etaxonomy.cdm.persistence.query.MatchMode;
110 import eu.etaxonomy.cdm.persistence.query.OrderHint;
111 import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
112 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
113
114
115 /**
116 * @author a.kohlbecker
117 * @date 10.09.2010
118 *
119 */
120 @Service
121 @Transactional(readOnly = true)
122 public class TaxonServiceImpl extends IdentifiableServiceBase<TaxonBase,ITaxonDao> implements ITaxonService{
123 private static final Logger logger = Logger.getLogger(TaxonServiceImpl.class);
124
125 public static final String POTENTIAL_COMBINATION_NAMESPACE = "Potential combination";
126
127 public static final String INFERRED_EPITHET_NAMESPACE = "Inferred epithet";
128
129 public static final String INFERRED_GENUS_NAMESPACE = "Inferred genus";
130
131
132 @Autowired
133 private ITaxonNameDao nameDao;
134
135 @Autowired
136 private INameService nameService;
137
138 @Autowired
139 private ICdmGenericDao genericDao;
140
141 @Autowired
142 private IDescriptionService descriptionService;
143
144 @Autowired
145 private IOrderedTermVocabularyDao orderedVocabularyDao;
146
147 @Autowired
148 private IOccurrenceDao occurrenceDao;
149
150 @Autowired
151 private AbstractBeanInitializer beanInitializer;
152
153 private static IndexSearcher taxonRelationshipSearcher;
154
155 /**
156 * Constructor
157 */
158 public TaxonServiceImpl(){
159 if (logger.isDebugEnabled()) { logger.debug("Load TaxonService Bean"); }
160 }
161
162 /**
163 * FIXME Candidate for harmonization
164 * rename searchByName ?
165 */
166 @Override
167 public List<TaxonBase> searchTaxaByName(String name, Reference sec) {
168 return dao.getTaxaByName(name, sec);
169 }
170
171 /**
172 * FIXME Candidate for harmonization
173 * list(Synonym.class, ...)
174 * (non-Javadoc)
175 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllSynonyms(int, int)
176 */
177 @Override
178 public List<Synonym> getAllSynonyms(int limit, int start) {
179 return dao.getAllSynonyms(limit, start);
180 }
181
182 /**
183 * FIXME Candidate for harmonization
184 * list(Taxon.class, ...)
185 * (non-Javadoc)
186 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllTaxa(int, int)
187 */
188 @Override
189 public List<Taxon> getAllTaxa(int limit, int start) {
190 return dao.getAllTaxa(limit, start);
191 }
192
193 /**
194 * FIXME Candidate for harmonization
195 * merge with getRootTaxa(Reference sec, ..., ...)
196 * (non-Javadoc)
197 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getRootTaxa(eu.etaxonomy.cdm.model.reference.Reference, boolean)
198 */
199 @Override
200 public List<Taxon> getRootTaxa(Reference sec, CdmFetch cdmFetch, boolean onlyWithChildren) {
201 if (cdmFetch == null){
202 cdmFetch = CdmFetch.NO_FETCH();
203 }
204 return dao.getRootTaxa(sec, cdmFetch, onlyWithChildren, false);
205 }
206
207
208 /* (non-Javadoc)
209 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getRootTaxa(eu.etaxonomy.cdm.model.name.Rank, eu.etaxonomy.cdm.model.reference.Reference, boolean, boolean)
210 */
211 @Override
212 public List<Taxon> getRootTaxa(Rank rank, Reference sec, boolean onlyWithChildren,boolean withMisapplications, List<String> propertyPaths) {
213 return dao.getRootTaxa(rank, sec, null, onlyWithChildren, withMisapplications, propertyPaths);
214 }
215
216 /* (non-Javadoc)
217 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllRelationships(int, int)
218 */
219 @Override
220 public List<RelationshipBase> getAllRelationships(int limit, int start){
221 return dao.getAllRelationships(limit, start);
222 }
223
224 /**
225 * FIXME Candidate for harmonization
226 * is this the same as termService.getVocabulary(VocabularyEnum.TaxonRelationshipType) ?
227 */
228 @Override
229 @Deprecated
230 public OrderedTermVocabulary<TaxonRelationshipType> getTaxonRelationshipTypeVocabulary() {
231
232 String taxonRelTypeVocabularyId = "15db0cf7-7afc-4a86-a7d4-221c73b0c9ac";
233 UUID uuid = UUID.fromString(taxonRelTypeVocabularyId);
234 OrderedTermVocabulary<TaxonRelationshipType> taxonRelTypeVocabulary =
235 (OrderedTermVocabulary)orderedVocabularyDao.findByUuid(uuid);
236 return taxonRelTypeVocabulary;
237 }
238
239
240
241 /*
242 * (non-Javadoc)
243 * @see eu.etaxonomy.cdm.api.service.ITaxonService#swapSynonymWithAcceptedTaxon(eu.etaxonomy.cdm.model.taxon.Synonym)
244 */
245 @Override
246 @Transactional(readOnly = false)
247 public void swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon){
248
249 TaxonNameBase<?,?> synonymName = synonym.getName();
250 synonymName.removeTaxonBase(synonym);
251 TaxonNameBase<?,?> taxonName = acceptedTaxon.getName();
252 taxonName.removeTaxonBase(acceptedTaxon);
253
254 synonym.setName(taxonName);
255 acceptedTaxon.setName(synonymName);
256
257 // the accepted taxon needs a new uuid because the concept has changed
258 // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
259 //acceptedTaxon.setUuid(UUID.randomUUID());
260 }
261
262
263 /* (non-Javadoc)
264 * @see eu.etaxonomy.cdm.api.service.ITaxonService#changeSynonymToAcceptedTaxon(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
265 */
266 //TODO correct delete handling still needs to be implemented / checked
267 @Override
268 @Transactional(readOnly = false)
269 public Taxon changeSynonymToAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean deleteSynonym, boolean copyCitationInfo, Reference citation, String microCitation) throws HomotypicalGroupChangeException{
270
271 TaxonNameBase<?,?> acceptedName = acceptedTaxon.getName();
272 TaxonNameBase<?,?> synonymName = synonym.getName();
273 HomotypicalGroup synonymHomotypicGroup = synonymName.getHomotypicalGroup();
274
275 //check synonym is not homotypic
276 if (acceptedName.getHomotypicalGroup().equals(synonymHomotypicGroup)){
277 String message = "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
278 throw new HomotypicalGroupChangeException(message);
279 }
280
281 Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, acceptedTaxon.getSec());
282
283 SynonymRelationshipType relTypeForGroup = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
284 List<Synonym> heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup);
285
286 for (Synonym heteroSynonym : heteroSynonyms){
287 if (synonym.equals(heteroSynonym)){
288 acceptedTaxon.removeSynonym(heteroSynonym, false);
289 }else{
290 //move synonyms in same homotypic group to new accepted taxon
291 heteroSynonym.replaceAcceptedTaxon(newAcceptedTaxon, relTypeForGroup, copyCitationInfo, citation, microCitation);
292 }
293 }
294
295 //synonym.getName().removeTaxonBase(synonym);
296 //TODO correct delete handling still needs to be implemented / checked
297 if (deleteSynonym){
298 // deleteSynonym(synonym, taxon, false);
299 try {
300 this.dao.flush();
301 this.delete(synonym);
302
303 } catch (Exception e) {
304 logger.info("Can't delete old synonym from database");
305 }
306 }
307
308 return newAcceptedTaxon;
309 }
310
311
312 @Override
313 public Taxon changeSynonymToRelatedTaxon(Synonym synonym, Taxon toTaxon, TaxonRelationshipType taxonRelationshipType, Reference citation, String microcitation){
314
315 // Get name from synonym
316 TaxonNameBase<?, ?> synonymName = synonym.getName();
317
318 // remove synonym from taxon
319 toTaxon.removeSynonym(synonym);
320
321 // Create a taxon with synonym name
322 Taxon fromTaxon = Taxon.NewInstance(synonymName, null);
323
324 // Add taxon relation
325 fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
326
327 // since we are swapping names, we have to detach the name from the synonym completely.
328 // Otherwise the synonym will still be in the list of typified names.
329 synonym.getName().removeTaxonBase(synonym);
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,
422 String uninomial, String infragenericEpithet, String specificEpithet,
423 String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
424 Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
425
426 List<TaxonBase> results = new ArrayList<TaxonBase>();
427 if(numberOfResults > 0) { // no point checking again
428 results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
429 }
430
431 return new DefaultPagerImpl<TaxonBase>(pageNumber, numberOfResults, pageSize, results);
432 }
433
434
435 /* (non-Javadoc)
436 * @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)
437 */
438 @Override
439 public List<TaxonBase> listTaxaByName(Class<? extends TaxonBase> clazz, String uninomial, String infragenericEpithet, String specificEpithet, String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
440 Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
441
442 List<TaxonBase> results = new ArrayList<TaxonBase>();
443 if(numberOfResults > 0) { // no point checking again
444 results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
445 }
446
447 return results;
448 }
449
450 /* (non-Javadoc)
451 * @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)
452 */
453 @Override
454 public List<TaxonRelationship> listToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
455 Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
456
457 List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
458 if(numberOfResults > 0) { // no point checking again
459 results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
460 }
461 return results;
462 }
463
464 /* (non-Javadoc)
465 * @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)
466 */
467 @Override
468 public Pager<TaxonRelationship> pageToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
469 Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
470
471 List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
472 if(numberOfResults > 0) { // no point checking again
473 results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
474 }
475 return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
476 }
477
478 /* (non-Javadoc)
479 * @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)
480 */
481 @Override
482 public List<TaxonRelationship> listFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
483 Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
484
485 List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
486 if(numberOfResults > 0) { // no point checking again
487 results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
488 }
489 return results;
490 }
491
492 /* (non-Javadoc)
493 * @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)
494 */
495 @Override
496 public Pager<TaxonRelationship> pageFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
497 Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
498
499 List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
500 if(numberOfResults > 0) { // no point checking again
501 results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
502 }
503 return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
504 }
505
506 /**
507 * @param taxon
508 * @param includeRelationships
509 * @param maxDepth
510 * @param limit
511 * @param starts
512 * @param propertyPaths
513 * @return an List which is not specifically ordered
514 */
515 @Override
516 public Set<Taxon> listRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Integer maxDepth,
517 Integer limit, Integer start, List<String> propertyPaths) {
518
519 Set<Taxon> relatedTaxa = collectRelatedTaxa(taxon, includeRelationships, new HashSet<Taxon>(), maxDepth);
520 relatedTaxa.remove(taxon);
521 beanInitializer.initializeAll(relatedTaxa, propertyPaths);
522 return relatedTaxa;
523 }
524
525
526 /**
527 * recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
528 * <code>taxon</code> supplied as parameter.
529 *
530 * @param taxon
531 * @param includeRelationships
532 * @param taxa
533 * @param maxDepth can be <code>null</code> for infinite depth
534 * @return
535 */
536 private Set<Taxon> collectRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Set<Taxon> taxa, Integer maxDepth) {
537
538 if(taxa.isEmpty()) {
539 taxa.add(taxon);
540 }
541
542 if(maxDepth != null) {
543 maxDepth--;
544 }
545 if(logger.isDebugEnabled()){
546 logger.debug("collecting related taxa for " + taxon + " with maxDepth=" + maxDepth);
547 }
548 List<TaxonRelationship> taxonRelationships = dao.getTaxonRelationships(taxon, null, null, null, null, null, null);
549 for (TaxonRelationship taxRel : taxonRelationships) {
550
551 // skip invalid data
552 if (taxRel.getToTaxon() == null || taxRel.getFromTaxon() == null || taxRel.getType() == null) {
553 continue;
554 }
555 // filter by includeRelationships
556 for (TaxonRelationshipEdge relationshipEdgeFilter : includeRelationships) {
557 if ( relationshipEdgeFilter.getTaxonRelationshipType().equals(taxRel.getType()) ) {
558 if (relationshipEdgeFilter.getDirections().contains(Direction.relatedTo) && !taxa.contains(taxRel.getToTaxon())) {
559 if(logger.isDebugEnabled()){
560 logger.debug(maxDepth + ": " + taxon.getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxRel.getToTaxon().getTitleCache());
561 }
562 taxa.add(taxRel.getToTaxon());
563 if(maxDepth == null || maxDepth > 0) {
564 taxa.addAll(collectRelatedTaxa(taxRel.getToTaxon(), includeRelationships, taxa, maxDepth));
565 }
566 }
567 if(relationshipEdgeFilter.getDirections().contains(Direction.relatedFrom) && !taxa.contains(taxRel.getFromTaxon())) {
568 taxa.add(taxRel.getFromTaxon());
569 if(logger.isDebugEnabled()){
570 logger.debug(maxDepth + ": " +taxRel.getFromTaxon().getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxon.getTitleCache() );
571 }
572 if(maxDepth == null || maxDepth > 0) {
573 taxa.addAll(collectRelatedTaxa(taxRel.getFromTaxon(), includeRelationships, taxa, maxDepth));
574 }
575 }
576 }
577 }
578 }
579 return taxa;
580 }
581
582 /* (non-Javadoc)
583 * @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)
584 */
585 @Override
586 public Pager<SynonymRelationship> getSynonyms(Taxon taxon, SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
587 Integer numberOfResults = dao.countSynonyms(taxon, type);
588
589 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
590 if(numberOfResults > 0) { // no point checking again
591 results = dao.getSynonyms(taxon, type, pageSize, pageNumber, orderHints, propertyPaths);
592 }
593
594 return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
595 }
596
597 /* (non-Javadoc)
598 * @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)
599 */
600 @Override
601 public Pager<SynonymRelationship> getSynonyms(Synonym synonym, SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
602 Integer numberOfResults = dao.countSynonyms(synonym, type);
603
604 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
605 if(numberOfResults > 0) { // no point checking again
606 results = dao.getSynonyms(synonym, type, pageSize, pageNumber, orderHints, propertyPaths);
607 }
608
609 return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
610 }
611
612 /* (non-Javadoc)
613 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHomotypicSynonymsByHomotypicGroup(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
614 */
615 @Override
616 public List<Synonym> getHomotypicSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
617 Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
618 return t.getHomotypicSynonymsByHomotypicGroup();
619 }
620
621 /* (non-Javadoc)
622 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHeterotypicSynonymyGroups(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
623 */
624 @Override
625 public List<List<Synonym>> getHeterotypicSynonymyGroups(Taxon taxon, List<String> propertyPaths){
626 Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
627 List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
628 List<List<Synonym>> heterotypicSynonymyGroups = new ArrayList<List<Synonym>>(homotypicalGroups.size());
629 for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
630 heterotypicSynonymyGroups.add(t.getSynonymsInGroup(homotypicalGroup));
631 }
632 return heterotypicSynonymyGroups;
633 }
634
635 @Override
636 public List<UuidAndTitleCache<TaxonBase>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator){
637
638 List<UuidAndTitleCache<TaxonBase>> result = new ArrayList<UuidAndTitleCache<TaxonBase>>();
639 // Class<? extends TaxonBase> clazz = null;
640 // if ((configurator.isDoTaxa() && configurator.isDoSynonyms())) {
641 // clazz = TaxonBase.class;
642 // //propertyPath.addAll(configurator.getTaxonPropertyPath());
643 // //propertyPath.addAll(configurator.getSynonymPropertyPath());
644 // } else if(configurator.isDoTaxa()) {
645 // clazz = Taxon.class;
646 // //propertyPath = configurator.getTaxonPropertyPath();
647 // } else if (configurator.isDoSynonyms()) {
648 // clazz = Synonym.class;
649 // //propertyPath = configurator.getSynonymPropertyPath();
650 // }
651
652
653 result = dao.getTaxaByNameForEditor(configurator.isDoTaxa(), configurator.isDoSynonyms(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
654 return result;
655 }
656
657 /* (non-Javadoc)
658 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaAndNames(eu.etaxonomy.cdm.api.service.config.ITaxonServiceConfigurator)
659 */
660 @Override
661 public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
662
663 List<IdentifiableEntity> results = new ArrayList<IdentifiableEntity>();
664 int numberOfResults = 0; // overall number of results (as opposed to number of results per page)
665 List<TaxonBase> taxa = null;
666
667 // Taxa and synonyms
668 long numberTaxaResults = 0L;
669
670
671 List<String> propertyPath = new ArrayList<String>();
672 if(configurator.getTaxonPropertyPath() != null){
673 propertyPath.addAll(configurator.getTaxonPropertyPath());
674 }
675
676
677 if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa()){
678 if(configurator.getPageSize() != null){ // no point counting if we need all anyway
679 numberTaxaResults =
680 dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
681 configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(),
682 configurator.getNamedAreas());
683 }
684
685 if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
686 taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
687 configurator.isDoMisappliedNames(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(),
688 configurator.getMatchMode(), configurator.getNamedAreas(),
689 configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
690 }
691 }
692
693 if (logger.isDebugEnabled()) { logger.debug(numberTaxaResults + " matching taxa counted"); }
694
695 if(taxa != null){
696 results.addAll(taxa);
697 }
698
699 numberOfResults += numberTaxaResults;
700
701 // Names without taxa
702 if (configurator.isDoNamesWithoutTaxa()) {
703 int numberNameResults = 0;
704
705 List<? extends TaxonNameBase<?,?>> names =
706 nameDao.findByName(configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
707 configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
708 if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
709 if (names.size() > 0) {
710 for (TaxonNameBase<?,?> taxonName : names) {
711 if (taxonName.getTaxonBases().size() == 0) {
712 results.add(taxonName);
713 numberNameResults++;
714 }
715 }
716 if (logger.isDebugEnabled()) { logger.debug(numberNameResults + " matching name(s) without taxa found"); }
717 numberOfResults += numberNameResults;
718 }
719 }
720
721 // Taxa from common names
722
723 if (configurator.isDoTaxaByCommonNames()) {
724 taxa = new ArrayList<TaxonBase>();
725 numberTaxaResults = 0;
726 if(configurator.getPageSize() != null){// no point counting if we need all anyway
727 numberTaxaResults = dao.countTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
728 }
729 if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){
730 List<Object[]> commonNameResults = dao.getTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas(), configurator.getPageSize(), configurator.getPageNumber(), configurator.getTaxonPropertyPath());
731 for( Object[] entry : commonNameResults ) {
732 taxa.add((TaxonBase) entry[0]);
733 }
734 }
735 if(taxa != null){
736 results.addAll(taxa);
737 }
738 numberOfResults += numberTaxaResults;
739
740 }
741
742 return new DefaultPagerImpl<IdentifiableEntity>
743 (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
744 }
745
746 public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(){
747 return dao.getUuidAndTitleCache();
748 }
749
750 /* (non-Javadoc)
751 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllMedia(eu.etaxonomy.cdm.model.taxon.Taxon, int, int, int, java.lang.String[])
752 */
753 @Override
754 public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
755 List<MediaRepresentation> medRep = new ArrayList<MediaRepresentation>();
756 taxon = (Taxon)dao.load(taxon.getUuid());
757 Set<TaxonDescription> descriptions = taxon.getDescriptions();
758 for (TaxonDescription taxDesc: descriptions){
759 Set<DescriptionElementBase> elements = taxDesc.getElements();
760 for (DescriptionElementBase descElem: elements){
761 for(Media media : descElem.getMedia()){
762
763 //find the best matching representation
764 medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
765
766 }
767 }
768 }
769 return medRep;
770 }
771
772 /* (non-Javadoc)
773 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listTaxonDescriptionMedia(eu.etaxonomy.cdm.model.taxon.Taxon, boolean)
774 */
775 @Override
776 public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
777 return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
778 }
779
780
781 /* (non-Javadoc)
782 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listMedia(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.Set, boolean, java.util.List)
783 */
784 @Override
785 public List<Media> listMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships,
786 Boolean limitToGalleries, Boolean includeTaxonDescriptions, Boolean includeOccurrences,
787 Boolean includeTaxonNameDescriptions, List<String> propertyPath) {
788
789 Set<Taxon> taxa = new HashSet<Taxon>();
790 List<Media> taxonMedia = new ArrayList<Media>();
791
792 if (limitToGalleries == null) {
793 limitToGalleries = false;
794 }
795
796 // --- resolve related taxa
797 if (includeRelationships != null) {
798 taxa = listRelatedTaxa(taxon, includeRelationships, null, null, null, null);
799 }
800
801 taxa.add((Taxon) dao.load(taxon.getUuid()));
802
803 if(includeTaxonDescriptions != null && includeTaxonDescriptions){
804 List<TaxonDescription> taxonDescriptions = new ArrayList<TaxonDescription>();
805 // --- TaxonDescriptions
806 for (Taxon t : taxa) {
807 taxonDescriptions.addAll(descriptionService.listTaxonDescriptions(t, null, null, null, null, propertyPath));
808 }
809 for (TaxonDescription taxonDescription : taxonDescriptions) {
810 if (!limitToGalleries || taxonDescription.isImageGallery()) {
811 for (DescriptionElementBase element : taxonDescription.getElements()) {
812 for (Media media : element.getMedia()) {
813 taxonMedia.add(media);
814 }
815 }
816 }
817 }
818 }
819
820 if(includeOccurrences != null && includeOccurrences) {
821 Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<SpecimenOrObservationBase>();
822 // --- Specimens
823 for (Taxon t : taxa) {
824 specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
825 }
826 for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
827
828 taxonMedia.addAll(occurrence.getMedia());
829
830 // SpecimenDescriptions
831 Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
832 for (DescriptionBase specimenDescription : specimenDescriptions) {
833 if (!limitToGalleries || specimenDescription.isImageGallery()) {
834 Set<DescriptionElementBase> elements = specimenDescription.getElements();
835 for (DescriptionElementBase element : elements) {
836 for (Media media : element.getMedia()) {
837 taxonMedia.add(media);
838 }
839 }
840 }
841 }
842
843 // Collection
844 if (occurrence instanceof DerivedUnitBase) {
845 if (((DerivedUnitBase) occurrence).getCollection() != null){
846 taxonMedia.addAll(((DerivedUnitBase) occurrence).getCollection().getMedia());
847 }
848 }
849
850 // Chromatograms
851 if (occurrence instanceof DnaSample) {
852 Set<Sequence> sequences = ((DnaSample) occurrence).getSequences();
853 for (Sequence sequence : sequences) {
854 taxonMedia.addAll(sequence.getChromatograms());
855 }
856 }
857
858 }
859 }
860
861 if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
862 // --- TaxonNameDescription
863 Set<TaxonNameDescription> nameDescriptions = new HashSet<TaxonNameDescription>();
864 for (Taxon t : taxa) {
865 nameDescriptions .addAll(t.getName().getDescriptions());
866 }
867 for(TaxonNameDescription nameDescription: nameDescriptions){
868 if (!limitToGalleries || nameDescription.isImageGallery()) {
869 Set<DescriptionElementBase> elements = nameDescription.getElements();
870 for (DescriptionElementBase element : elements) {
871 for (Media media : element.getMedia()) {
872 taxonMedia.add(media);
873 }
874 }
875 }
876 }
877 }
878
879 beanInitializer.initializeAll(taxonMedia, propertyPath);
880 return taxonMedia;
881 }
882
883 /* (non-Javadoc)
884 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaByID(java.util.Set)
885 */
886 @Override
887 public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
888 return this.dao.listByIds(listOfIDs, null, null, null, null);
889 }
890
891 /* (non-Javadoc)
892 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxonByUuid(UUID uuid, List<String> propertyPaths)
893 */
894 @Override
895 public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
896 return this.dao.findByUuid(uuid, null ,propertyPaths);
897 }
898
899 /* (non-Javadoc)
900 * @see eu.etaxonomy.cdm.api.service.ITaxonService#countAllRelationships()
901 */
902 @Override
903 public int countAllRelationships() {
904 return this.dao.countAllRelationships();
905 }
906
907
908
909
910 /* (non-Javadoc)
911 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNames(java.util.List)
912 */
913 @Override
914 public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
915 return this.dao.findIdenticalTaxonNames(propertyPath);
916 }
917
918
919 /* (non-Javadoc)
920 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator)
921 */
922 @Override
923 public void deleteTaxon(Taxon taxon, TaxonDeletionConfigurator config) throws ReferencedObjectUndeletableException {
924 if (config == null){
925 config = new TaxonDeletionConfigurator();
926 }
927
928 // TaxonNode
929 if (! config.isDeleteTaxonNodes()){
930 if (taxon.getTaxonNodes().size() > 0){
931 String message = "Taxon can't be deleted as it is used in a classification node. Remove taxon from all classifications prior to deletion.";
932 throw new ReferencedObjectUndeletableException(message);
933 }
934 }
935
936
937 // SynonymRelationShip
938 if (config.isDeleteSynonymRelations()){
939 boolean removeSynonymNameFromHomotypicalGroup = false;
940 for (SynonymRelationship synRel : taxon.getSynonymRelations()){
941 Synonym synonym = synRel.getSynonym();
942 taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
943 if (config.isDeleteSynonymsIfPossible()){
944 //TODO which value
945 boolean newHomotypicGroupIfNeeded = true;
946 deleteSynonym(synonym, taxon, config.isDeleteNameIfPossible(), newHomotypicGroupIfNeeded);
947 }else{
948 deleteSynonymRelationships(synonym, taxon);
949 }
950 }
951 }
952
953 // TaxonRelationship
954 if (! config.isDeleteTaxonRelationships()){
955 if (taxon.getTaxonRelations().size() > 0){
956 String message = "Taxon can't be deleted as it is related to another taxon. Remove taxon from all relations to other taxa prior to deletion.";
957 throw new ReferencedObjectUndeletableException(message);
958 }
959 }
960
961
962 // TaxonDescription
963 Set<TaxonDescription> descriptions = taxon.getDescriptions();
964
965 for (TaxonDescription desc: descriptions){
966 if (config.isDeleteDescriptions()){
967 //TODO use description delete configurator ?
968 //FIXME check if description is ALWAYS deletable
969 descriptionService.delete(desc);
970 }else{
971 if (desc.getDescribedSpecimenOrObservations().size()>0){
972 String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
973 " which also describes specimens or abservations";
974 throw new ReferencedObjectUndeletableException(message);
975 }
976 }
977 }
978
979
980 //check references with only reverse mapping
981 Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
982 for (CdmBase referencingObject : referencingObjects){
983 //IIdentificationKeys (Media, Polytomous, MultiAccess)
984 if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
985 String message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
986 message = String.format(message, CdmBase.deproxy(referencingObject, DerivedUnitBase.class).getTitleCache());
987 throw new ReferencedObjectUndeletableException(message);
988 }
989
990
991 //PolytomousKeyNode
992 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
993 String message = "Taxon can't be deleted as it is used in polytomous key node";
994 throw new ReferencedObjectUndeletableException(message);
995 }
996
997 //TaxonInteraction
998 if (referencingObject.isInstanceOf(TaxonInteraction.class)){
999 String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1000 throw new ReferencedObjectUndeletableException(message);
1001 }
1002 }
1003
1004
1005 //TaxonNameBase
1006 if (config.isDeleteNameIfPossible()){
1007 try {
1008 nameService.delete(taxon.getName(), config.getNameDeletionConfig());
1009 } catch (ReferencedObjectUndeletableException e) {
1010 //do nothing
1011 if (logger.isDebugEnabled()){logger.debug("Name could not be deleted");}
1012 }
1013 }
1014
1015 }
1016
1017 /* (non-Javadoc)
1018 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1019 */
1020 @Transactional(readOnly = false)
1021 @Override
1022 public void deleteSynonym(Synonym synonym, Taxon taxon, boolean removeNameIfPossible,boolean newHomotypicGroupIfNeeded) {
1023 if (synonym == null){
1024 return;
1025 }
1026 synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
1027
1028 //remove synonymRelationship
1029 Set<Taxon> taxonSet = new HashSet<Taxon>();
1030 if (taxon != null){
1031 taxonSet.add(taxon);
1032 }else{
1033 taxonSet.addAll(synonym.getAcceptedTaxa());
1034 }
1035 for (Taxon relatedTaxon : taxonSet){
1036 // dao.deleteSynonymRelationships(synonym, relatedTaxon);
1037 relatedTaxon.removeSynonym(synonym, newHomotypicGroupIfNeeded);
1038 }
1039 this.saveOrUpdate(synonym);
1040
1041 //TODO remove name from homotypical group?
1042
1043 //remove synonym (if necessary)
1044 if (synonym.getSynonymRelations().isEmpty()){
1045 TaxonNameBase<?,?> name = synonym.getName();
1046 synonym.setName(null);
1047 dao.delete(synonym);
1048
1049 //remove name if possible (and required)
1050 if (name != null && removeNameIfPossible){
1051 try{
1052 nameService.delete(name, new NameDeletionConfigurator());
1053 }catch (DataChangeNoRollbackException ex){
1054 if (logger.isDebugEnabled()) {
1055 logger.debug("Name wasn't deleted as it is referenced");
1056 }
1057 }
1058 }
1059 }
1060 }
1061
1062
1063 /* (non-Javadoc)
1064 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNameIds(java.util.List)
1065 */
1066 @Override
1067 public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1068
1069 return this.dao.findIdenticalNamesNew(propertyPath);
1070 }
1071
1072 /* (non-Javadoc)
1073 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getPhylumName(eu.etaxonomy.cdm.model.name.TaxonNameBase)
1074 */
1075 @Override
1076 public String getPhylumName(TaxonNameBase name){
1077 return this.dao.getPhylumName(name);
1078 }
1079
1080 /* (non-Javadoc)
1081 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
1082 */
1083 @Override
1084 public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1085 return dao.deleteSynonymRelationships(syn, taxon);
1086 }
1087
1088 /* (non-Javadoc)
1089 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym)
1090 */
1091 @Override
1092 public long deleteSynonymRelationships(Synonym syn) {
1093 return dao.deleteSynonymRelationships(syn, null);
1094 }
1095
1096
1097 /* (non-Javadoc)
1098 * @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)
1099 */
1100 @Override
1101 public List<SynonymRelationship> listSynonymRelationships(
1102 TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1103 List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1104 Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1105
1106 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1107 if(numberOfResults > 0) { // no point checking again
1108 results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1109 }
1110 return results;
1111 }
1112
1113 /* (non-Javadoc)
1114 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingTaxon(java.lang.String)
1115 */
1116 @Override
1117 public Taxon findBestMatchingTaxon(String taxonName) {
1118 MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1119 config.setTaxonNameTitle(taxonName);
1120 return findBestMatchingTaxon(config);
1121 }
1122
1123
1124
1125 @Override
1126 public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1127
1128 Taxon bestCandidate = null;
1129 try{
1130 // 1. search for acceptet taxa
1131 List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1132 boolean bestCandidateMatchesSecUuid = false;
1133 boolean bestCandidateIsInClassification = false;
1134 int countEqualCandidates = 0;
1135 for(TaxonBase taxonBaseCandidate : taxonList){
1136 if(taxonBaseCandidate instanceof Taxon){
1137 Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1138 boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1139 if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1140 continue;
1141 }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1142 bestCandidate = newCanditate;
1143 countEqualCandidates = 1;
1144 bestCandidateMatchesSecUuid = true;
1145 continue;
1146 }
1147
1148 boolean newCandidateInClassification = isInClassification(newCanditate, config);
1149 if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1150 continue;
1151 }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1152 bestCandidate = newCanditate;
1153 countEqualCandidates = 1;
1154 bestCandidateIsInClassification = true;
1155 continue;
1156 }
1157 if (bestCandidate == null){
1158 bestCandidate = newCanditate;
1159 countEqualCandidates = 1;
1160 continue;
1161 }
1162
1163 }else{ //not Taxon.class
1164 continue;
1165 }
1166 countEqualCandidates++;
1167
1168 }
1169 if (bestCandidate != null){
1170 if(countEqualCandidates > 1){
1171 logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1172 return bestCandidate;
1173 } else {
1174 logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1175 return bestCandidate;
1176 }
1177 }
1178
1179
1180 // 2. search for synonyms
1181 if (config.isIncludeSynonyms()){
1182 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1183 for(TaxonBase taxonBase : synonymList){
1184 if(taxonBase instanceof Synonym){
1185 Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1186 Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1187 if(!acceptetdCandidates.isEmpty()){
1188 bestCandidate = acceptetdCandidates.iterator().next();
1189 if(acceptetdCandidates.size() == 1){
1190 logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1191 return bestCandidate;
1192 } else {
1193 logger.info("using accepted Taxon " + bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1194 return bestCandidate;
1195 }
1196 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1197 }
1198 }
1199 }
1200 }
1201
1202 } catch (Exception e){
1203 logger.error(e);
1204 e.printStackTrace();
1205 }
1206
1207 return bestCandidate;
1208 }
1209
1210 private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1211 UUID configClassificationUuid = config.getClassificationUuid();
1212 if (configClassificationUuid == null){
1213 return false;
1214 }
1215 for (TaxonNode node : taxon.getTaxonNodes()){
1216 UUID classUuid = node.getClassification().getUuid();
1217 if (configClassificationUuid.equals(classUuid)){
1218 return true;
1219 }
1220 }
1221 return false;
1222 }
1223
1224 private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1225 UUID configSecUuid = config.getSecUuid();
1226 if (configSecUuid == null){
1227 return false;
1228 }
1229 UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1230 return configSecUuid.equals(taxonSecUuid);
1231 }
1232
1233 /* (non-Javadoc)
1234 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingSynonym(java.lang.String)
1235 */
1236 @Override
1237 public Synonym findBestMatchingSynonym(String taxonName) {
1238 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1239 if(! synonymList.isEmpty()){
1240 Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1241 if(synonymList.size() == 1){
1242 logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1243 return result;
1244 } else {
1245 logger.info("Several matching synonyms found. Using first: " + result.getTitleCache());
1246 return result;
1247 }
1248 }
1249 return null;
1250 }
1251
1252
1253 /* (non-Javadoc)
1254 * @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)
1255 */
1256 @Override
1257 public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, Taxon newTaxon, boolean moveHomotypicGroup,
1258 SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
1259
1260 Synonym synonym = oldSynonymRelation.getSynonym();
1261 Taxon fromTaxon = oldSynonymRelation.getAcceptedTaxon();
1262 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1263 TaxonNameBase<?,?> synonymName = synonym.getName();
1264 TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1265 //set default relationship type
1266 if (newSynonymRelationshipType == null){
1267 newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1268 }
1269 boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1270
1271 HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1272 int hgSize = homotypicGroup.getTypifiedNames().size();
1273 boolean isSingleInGroup = !(hgSize > 1);
1274
1275 if (! isSingleInGroup){
1276 boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1277 boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1278 if (isHomotypicToAccepted){
1279 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.";
1280 String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1281 message = String.format(message, homotypicRelatives);
1282 throw new HomotypicalGroupChangeException(message);
1283 }
1284 if (! moveHomotypicGroup){
1285 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.";
1286 throw new HomotypicalGroupChangeException(message);
1287 }
1288 }else{
1289 moveHomotypicGroup = true; //single synonym always allows to moveCompleteGroup
1290 }
1291 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1292
1293 SynonymRelationship result = null;
1294 //move all synonyms to new taxon
1295 List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1296 for (Synonym syn: homotypicSynonyms){
1297 Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1298 for (SynonymRelationship synRelation : synRelations){
1299 if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1300 Reference<?> newReference = reference;
1301 if (newReference == null && keepReference){
1302 newReference = synRelation.getCitation();
1303 }
1304 String newRefDetail = referenceDetail;
1305 if (newRefDetail == null && keepReference){
1306 newRefDetail = synRelation.getCitationMicroReference();
1307 }
1308 SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1309 fromTaxon.removeSynonymRelation(synRelation, false);
1310 //
1311 //change homotypic group of synonym if relType is 'homotypic'
1312 // if (newRelTypeIsHomotypic){
1313 // newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1314 // }
1315 //set result
1316 if (synRelation.equals(oldSynonymRelation)){
1317 result = newSynRelation;
1318 }
1319 }
1320 }
1321
1322 }
1323 saveOrUpdate(newTaxon);
1324 //Assert that there is a result
1325 if (result == null){
1326 String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1327 throw new IllegalStateException(message);
1328 }
1329 return result;
1330 }
1331
1332 /* (non-Javadoc)
1333 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheTaxon()
1334 */
1335 @Override
1336 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1337 return dao.getUuidAndTitleCacheTaxon();
1338 }
1339
1340 /* (non-Javadoc)
1341 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheSynonym()
1342 */
1343 @Override
1344 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1345 return dao.getUuidAndTitleCacheSynonym();
1346 }
1347
1348 /* (non-Javadoc)
1349 * @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)
1350 */
1351 @Override
1352 public Pager<SearchResult<TaxonBase>> findByFullText(
1353 Class<? extends TaxonBase> clazz, String queryString,
1354 Classification classification, List<Language> languages,
1355 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1356
1357
1358 LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments);
1359
1360 // --- execute search
1361 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1362
1363 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1364 idFieldMap.put(CdmBaseType.TAXON, "id");
1365
1366 // --- initialize taxa, thighlight matches ....
1367 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1368 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1369 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1370
1371 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1372 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1373 }
1374
1375 @Override
1376 public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTermBase<?>> statusFilter,
1377 Classification classification,
1378 Integer pageSize, Integer pageNumber,
1379 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1380
1381 LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1382
1383 // --- execute search
1384 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1385
1386 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1387 idFieldMap.put(CdmBaseType.TAXON, "id");
1388
1389 // --- initialize taxa, thighlight matches ....
1390 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1391 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1392 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1393
1394 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1395 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1396 }
1397
1398 /**
1399 * @param clazz
1400 * @param queryString
1401 * @param classification
1402 * @param languages
1403 * @param highlightFragments
1404 * @param directorySelectClass
1405 * @return
1406 */
1407 protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1408 boolean highlightFragments) {
1409 BooleanQuery finalQuery = new BooleanQuery();
1410 BooleanQuery textQuery = new BooleanQuery();
1411
1412 LuceneSearch luceneSearch = new LuceneSearch(getSession(), GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1413 QueryFactory queryFactory = new QueryFactory(luceneSearch);
1414
1415 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1416 luceneSearch.setSortFields(sortFields);
1417
1418 // ---- search criteria
1419 luceneSearch.setClazz(clazz);
1420
1421 textQuery.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1422 textQuery.add(queryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1423
1424 finalQuery.add(textQuery, Occur.MUST);
1425
1426 if(classification != null){
1427 finalQuery.add(queryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1428 }
1429 luceneSearch.setQuery(finalQuery);
1430
1431 if(highlightFragments){
1432 luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
1433 }
1434 return luceneSearch;
1435 }
1436
1437 /**
1438 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1439 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1440 * drawback of requiring to do the join an indexing time.
1441 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1442 *
1443 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1444 * <ul>
1445 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1446 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1447 * <ul>
1448 *
1449 * @param queryString
1450 * @param classification
1451 * @param languages
1452 * @param highlightFragments
1453 * @return
1454 * @throws IOException
1455 */
1456 protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1457 boolean highlightFragments) throws IOException {
1458
1459 String fromField;
1460 String queryTermField;
1461 String toField = "id"; // TaxonBase.uuid
1462
1463 if(edge.isBidirectional()){
1464 throw new RuntimeException("Bidirectional joining not supported!");
1465 }
1466 if(edge.isEvers()){
1467 fromField = "relatedFrom.id";
1468 queryTermField = "relatedFrom.titleCache";
1469 } else if(edge.isInvers()) {
1470 fromField = "relatedTo.id";
1471 queryTermField = "relatedTo.titleCache";
1472 } else {
1473 throw new RuntimeException("Invalid direction: " + edge.getDirections());
1474 }
1475
1476 BooleanQuery finalQuery = new BooleanQuery();
1477
1478 LuceneSearch luceneSearch = new LuceneSearch(getSession(), TaxonBase.class);
1479 QueryFactory queryFactory = new QueryFactory(luceneSearch);
1480
1481 BooleanQuery joinFromQuery = new BooleanQuery();
1482 joinFromQuery.add(queryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1483 joinFromQuery.add(queryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1484 Query joinQuery = queryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1485
1486 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1487 luceneSearch.setSortFields(sortFields);
1488
1489 finalQuery.add(joinQuery, Occur.MUST);
1490
1491 if(classification != null){
1492 finalQuery.add(queryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1493 }
1494 luceneSearch.setQuery(finalQuery);
1495
1496 if(highlightFragments){
1497 luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
1498 }
1499 return luceneSearch;
1500 }
1501
1502
1503
1504
1505 /* (non-Javadoc)
1506 * @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)
1507 */
1508 @Override
1509 public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1510 EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1511 Set<NamedArea> namedAreas, Set<PresenceAbsenceTermBase<?>> distributionStatus, List<Language> languages, boolean highlightFragments, Integer pageSize,
1512 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1513 throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1514
1515 // convert sets to lists
1516 List<NamedArea> namedAreaList = null;
1517 List<PresenceAbsenceTermBase<?>>distributionStatusList = null;
1518 if(namedAreas != null){
1519 namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1520 namedAreaList.addAll(namedAreas);
1521 }
1522 if(distributionStatus != null){
1523 distributionStatusList = new ArrayList<PresenceAbsenceTermBase<?>>(distributionStatus.size());
1524 distributionStatusList.addAll(distributionStatus);
1525 }
1526
1527 // set default if parameter is null
1528 if(searchModes == null){
1529 searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1530 }
1531
1532 List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1533 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1534
1535
1536 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1537 Class taxonBaseSubclass = TaxonBase.class;
1538 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1539 taxonBaseSubclass = Taxon.class;
1540 } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1541 taxonBaseSubclass = Synonym.class;
1542 }
1543 luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments));
1544 idFieldMap.put(CdmBaseType.TAXON, "id");
1545 }
1546 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1547 luceneSearches.add(prepareByDescriptionElementFullTextSearch(CommonTaxonName.class, queryString, classification, null, languages, highlightFragments));
1548 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1549 }
1550 if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
1551 // NOTE:
1552 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1553 // which allows doing query time joins
1554 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
1555 new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
1556 queryString, classification, languages, highlightFragments));
1557 idFieldMap.put(CdmBaseType.TAXON, "id");
1558 }
1559
1560 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
1561
1562 if(namedAreas != null && namedAreas.size() > 0){
1563 // - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1564 // - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1565 // add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1566 // which will be put into a FilteredQuersy in the end ?
1567
1568 //
1569 // 3. how does it work in spacial?
1570 // see
1571 // - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1572 // - http://www.infoq.com/articles/LuceneSpatialSupport
1573 // - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1574
1575
1576 // TODO can't we use a static QueryFactory field?
1577 QueryFactory queryFactory = new QueryFactory(luceneSearches.get(0));
1578
1579 Query taxonAreaJoinQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, queryFactory);
1580 multiSearch.setFilter(new QueryWrapperFilter(taxonAreaJoinQuery));
1581 }
1582
1583 // --- execute search
1584 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
1585
1586 // --- initialize taxa, highlight matches ....
1587 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
1588
1589
1590 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1591 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1592
1593 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1594 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1595 }
1596
1597 /**
1598 * @param namedAreaList at least one area must be in the list
1599 * @param distributionStatusList optional
1600 * @return
1601 * @throws IOException
1602 */
1603 protected Query createByDistributionJoinQuery(List<NamedArea> namedAreaList, List<PresenceAbsenceTermBase<?>> distributionStatusList,
1604 QueryFactory queryFactory) throws IOException {
1605
1606 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1607 String toField = "id"; // id in TaxonBase index
1608
1609 BooleanQuery areaQuery = new BooleanQuery();
1610 // area field from Distribution
1611 areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
1612
1613 // status field from Distribution
1614 if(distributionStatusList != null && distributionStatusList.size() > 0){
1615 areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
1616 }
1617
1618 logger.debug("prepareByAreaSearch() query: " + areaQuery.toString());
1619
1620 Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, areaQuery, Distribution.class);
1621
1622 return taxonAreaJoinQuery;
1623 }
1624
1625 /**
1626 * This method has been primarily created for testing the area join query but might
1627 * also be useful in other situations
1628 *
1629 * @param namedAreaList
1630 * @param distributionStatusList
1631 * @param classification
1632 * @param highlightFragments
1633 * @return
1634 * @throws IOException
1635 */
1636 protected LuceneSearch prepareByDistributionSearch(
1637 List<NamedArea> namedAreaList, List<PresenceAbsenceTermBase<?>> distributionStatusList,
1638 Classification classification) throws IOException {
1639
1640 BooleanQuery finalQuery = new BooleanQuery();
1641
1642 LuceneSearch luceneSearch = new LuceneSearch(getSession(), GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
1643 QueryFactory queryFactory = new QueryFactory(luceneSearch);
1644
1645 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1646 luceneSearch.setSortFields(sortFields);
1647
1648
1649 Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, queryFactory);
1650
1651 finalQuery.add(byAreaQuery, Occur.MUST);
1652
1653 if(classification != null){
1654 finalQuery.add(queryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1655 }
1656
1657 logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
1658 luceneSearch.setQuery(finalQuery);
1659
1660 return luceneSearch;
1661 }
1662
1663
1664
1665 /* (non-Javadoc)
1666 * @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)
1667 */
1668 @Override
1669 public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
1670 Class<? extends DescriptionElementBase> clazz, String queryString,
1671 Classification classification, List<Feature> features, List<Language> languages,
1672 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1673
1674
1675 LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
1676
1677 // --- execute search
1678 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1679
1680 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1681 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1682
1683 // --- initialize taxa, highlight matches ....
1684 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1685 @SuppressWarnings("rawtypes")
1686 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1687 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1688
1689 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1690 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1691
1692 }
1693
1694
1695 @Override
1696 public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
1697 Classification classification, List<Language> languages, boolean highlightFragments,
1698 Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1699
1700 LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
1701 LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments);
1702
1703 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
1704
1705 // --- execute search
1706 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
1707
1708 // --- initialize taxa, highlight matches ....
1709 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
1710
1711 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1712 idFieldMap.put(CdmBaseType.TAXON, "id");
1713 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1714
1715 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1716 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1717
1718 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1719 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1720
1721 }
1722
1723
1724 /**
1725 * @param clazz
1726 * @param queryString
1727 * @param classification
1728 * @param features
1729 * @param languages
1730 * @param highlightFragments
1731 * @param directorySelectClass
1732 * @return
1733 */
1734 protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
1735 String queryString, Classification classification, List<Feature> features,
1736 List<Language> languages, boolean highlightFragments) {
1737 BooleanQuery finalQuery = new BooleanQuery();
1738 BooleanQuery textQuery = new BooleanQuery();
1739
1740 LuceneSearch luceneSearch = new LuceneSearch(getSession(), GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
1741 QueryFactory queryFactory = new QueryFactory(luceneSearch);
1742
1743 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.STRING, false)};
1744 luceneSearch.setSortFields(sortFields);
1745
1746 // ---- search criteria
1747 luceneSearch.setClazz(clazz);
1748 textQuery.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1749
1750 // common name
1751 Query nameQuery;
1752 if(languages == null || languages.size() == 0){
1753 nameQuery = queryFactory.newTermQuery("name", queryString);
1754 } else {
1755 nameQuery = new BooleanQuery();
1756 BooleanQuery languageSubQuery = new BooleanQuery();
1757 for(Language lang : languages){
1758 languageSubQuery.add(queryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD);
1759 }
1760 ((BooleanQuery) nameQuery).add(queryFactory.newTermQuery("name", queryString), Occur.MUST);
1761 ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
1762 }
1763 textQuery.add(nameQuery, Occur.SHOULD);
1764
1765
1766 // text field from TextData
1767 textQuery.add(queryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
1768
1769 // --- TermBase fields - by representation ----
1770 // state field from CategoricalData
1771 textQuery.add(queryFactory.newDefinedTermQuery("states.state", queryString, languages), Occur.SHOULD);
1772
1773 // state field from CategoricalData
1774 textQuery.add(queryFactory.newDefinedTermQuery("states.modifyingText", queryString, languages), Occur.SHOULD);
1775
1776 // area field from Distribution
1777 textQuery.add(queryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
1778
1779 // status field from Distribution
1780 textQuery.add(queryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
1781
1782 finalQuery.add(textQuery, Occur.MUST);
1783 // --- classification ----
1784
1785 if(classification != null){
1786 finalQuery.add(queryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
1787 }
1788
1789 // --- IdentifieableEntity fields - by uuid
1790 if(features != null && features.size() > 0 ){
1791 finalQuery.add(queryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
1792 }
1793
1794 // the description must be associated with a taxon
1795 finalQuery.add(queryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
1796
1797 logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
1798 luceneSearch.setQuery(finalQuery);
1799
1800 if(highlightFragments){
1801 luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
1802 }
1803 return luceneSearch;
1804 }
1805
1806 /**
1807 * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
1808 * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
1809 * This method is a convenient means to retrieve a Lucene query string for such the fields.
1810 *
1811 * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
1812 * or {@link MultilanguageTextFieldBridge }
1813 * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
1814 * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
1815 * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
1816 *
1817 * TODO move to utiliy class !!!!!!!!
1818 */
1819 private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
1820
1821 if(stringBuilder == null){
1822 stringBuilder = new StringBuilder();
1823 }
1824 if(languages == null || languages.size() == 0){
1825 stringBuilder.append(name + ".ALL:(%1$s) ");
1826 } else {
1827 for(Language lang : languages){
1828 stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
1829 }
1830 }
1831 return stringBuilder;
1832 }
1833
1834 @Override
1835 public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
1836 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
1837 List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
1838
1839 HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
1840
1841
1842 UUID nameUuid= taxon.getName().getUuid();
1843 ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
1844 String epithetOfTaxon = null;
1845 String infragenericEpithetOfTaxon = null;
1846 String infraspecificEpithetOfTaxon = null;
1847 if (taxonName.isSpecies()){
1848 epithetOfTaxon= taxonName.getSpecificEpithet();
1849 } else if (taxonName.isInfraGeneric()){
1850 infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
1851 } else if (taxonName.isInfraSpecific()){
1852 infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
1853 }
1854 String genusOfTaxon = taxonName.getGenusOrUninomial();
1855 Set<TaxonNode> nodes = taxon.getTaxonNodes();
1856 List<String> taxonNames = new ArrayList<String>();
1857
1858 for (TaxonNode node: nodes){
1859 // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
1860 // List<String> synonymsEpithet = new ArrayList<String>();
1861
1862 if (node.getClassification().equals(classification)){
1863 if (!node.isTopmostNode()){
1864 TaxonNode parent = node.getParent();
1865 parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
1866 TaxonNameBase<?,?> parentName = parent.getTaxon().getName();
1867 ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
1868 Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
1869 Rank rankOfTaxon = taxonName.getRank();
1870
1871
1872 //create inferred synonyms for species, subspecies
1873 if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
1874
1875 Synonym inferredEpithet = null;
1876 Synonym inferredGenus = null;
1877 Synonym potentialCombination = null;
1878
1879 List<String> propertyPaths = new ArrayList<String>();
1880 propertyPaths.add("synonym");
1881 propertyPaths.add("synonym.name");
1882 List<OrderHint> orderHints = new ArrayList<OrderHint>();
1883 orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
1884
1885 List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
1886 List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
1887
1888 List<TaxonRelationship> taxonRelListParent = null;
1889 List<TaxonRelationship> taxonRelListTaxon = null;
1890 if (doWithMisappliedNames){
1891 taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
1892 taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
1893 }
1894
1895
1896 if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
1897
1898
1899 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
1900 Synonym syn = synonymRelationOfParent.getSynonym();
1901
1902 inferredEpithet = createInferredEpithets(taxon,
1903 zooHashMap, taxonName, epithetOfTaxon,
1904 infragenericEpithetOfTaxon,
1905 infraspecificEpithetOfTaxon,
1906 taxonNames, parentName,
1907 syn);
1908
1909
1910 inferredSynonyms.add(inferredEpithet);
1911 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
1912 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
1913 }
1914
1915 if (doWithMisappliedNames){
1916
1917 for (TaxonRelationship taxonRelationship: taxonRelListParent){
1918 Taxon misappliedName = taxonRelationship.getFromTaxon();
1919
1920 inferredEpithet = createInferredEpithets(taxon,
1921 zooHashMap, taxonName, epithetOfTaxon,
1922 infragenericEpithetOfTaxon,
1923 infraspecificEpithetOfTaxon,
1924 taxonNames, parentName,
1925 misappliedName);
1926
1927 inferredSynonyms.add(inferredEpithet);
1928 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
1929 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
1930 }
1931 }
1932
1933 if (!taxonNames.isEmpty()){
1934 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
1935 ZoologicalName name;
1936 if (!synNotInCDM.isEmpty()){
1937 inferredSynonymsToBeRemoved.clear();
1938
1939 for (Synonym syn :inferredSynonyms){
1940 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1941 if (!synNotInCDM.contains(name.getNameCache())){
1942 inferredSynonymsToBeRemoved.add(syn);
1943 }
1944 }
1945
1946 // Remove identified Synonyms from inferredSynonyms
1947 for (Synonym synonym : inferredSynonymsToBeRemoved) {
1948 inferredSynonyms.remove(synonym);
1949 }
1950 }
1951 }
1952
1953 }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
1954
1955
1956 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
1957 TaxonNameBase synName;
1958 ZoologicalName inferredSynName;
1959
1960 Synonym syn = synonymRelationOfTaxon.getSynonym();
1961 inferredGenus = createInferredGenus(taxon,
1962 zooHashMap, taxonName, epithetOfTaxon,
1963 genusOfTaxon, taxonNames, zooParentName, syn);
1964
1965 inferredSynonyms.add(inferredGenus);
1966 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
1967 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
1968
1969
1970 }
1971
1972 if (doWithMisappliedNames){
1973
1974 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
1975 Taxon misappliedName = taxonRelationship.getFromTaxon();
1976 inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName, misappliedName);
1977
1978 inferredSynonyms.add(inferredGenus);
1979 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
1980 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
1981 }
1982 }
1983
1984
1985 if (!taxonNames.isEmpty()){
1986 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
1987 ZoologicalName name;
1988 if (!synNotInCDM.isEmpty()){
1989 inferredSynonymsToBeRemoved.clear();
1990
1991 for (Synonym syn :inferredSynonyms){
1992 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1993 if (!synNotInCDM.contains(name.getNameCache())){
1994 inferredSynonymsToBeRemoved.add(syn);
1995 }
1996 }
1997
1998 // Remove identified Synonyms from inferredSynonyms
1999 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2000 inferredSynonyms.remove(synonym);
2001 }
2002 }
2003 }
2004
2005 }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2006
2007 Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2008 ZoologicalName inferredSynName;
2009 //for all synonyms of the parent...
2010 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2011 TaxonNameBase synName;
2012 Synonym synParent = synonymRelationOfParent.getSynonym();
2013 synName = synParent.getName();
2014
2015 HibernateProxyHelper.deproxy(synParent);
2016
2017 // Set the sourceReference
2018 sourceReference = synParent.getSec();
2019
2020 // Determine the idInSource
2021 String idInSourceParent = getIdInSource(synParent);
2022
2023 ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2024 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2025 String synParentInfragenericName = null;
2026 String synParentSpecificEpithet = null;
2027
2028 if (parentSynZooName.isInfraGeneric()){
2029 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2030 }
2031 if (parentSynZooName.isSpecies()){
2032 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2033 }
2034
2035 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2036 synonymsGenus.put(synGenusName, idInSource);
2037 }*/
2038
2039 //for all synonyms of the taxon
2040
2041 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2042
2043 Synonym syn = synonymRelationOfTaxon.getSynonym();
2044 ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2045 potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2046 synParentGenus,
2047 synParentInfragenericName,
2048 synParentSpecificEpithet, syn, zooHashMap);
2049
2050 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2051 inferredSynonyms.add(potentialCombination);
2052 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2053 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2054
2055 }
2056
2057
2058 }
2059
2060 if (doWithMisappliedNames){
2061
2062 for (TaxonRelationship parentRelationship: taxonRelListParent){
2063
2064 TaxonNameBase misappliedParentName;
2065
2066 Taxon misappliedParent = parentRelationship.getFromTaxon();
2067 misappliedParentName = misappliedParent.getName();
2068
2069 HibernateProxyHelper.deproxy(misappliedParent);
2070
2071 // Set the sourceReference
2072 sourceReference = misappliedParent.getSec();
2073
2074 // Determine the idInSource
2075 String idInSourceParent = getIdInSource(misappliedParent);
2076
2077 ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2078 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2079 String synParentInfragenericName = null;
2080 String synParentSpecificEpithet = null;
2081
2082 if (parentSynZooName.isInfraGeneric()){
2083 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2084 }
2085 if (parentSynZooName.isSpecies()){
2086 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2087 }
2088
2089
2090 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2091 Taxon misappliedName = taxonRelationship.getFromTaxon();
2092 ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2093 potentialCombination = createPotentialCombination(
2094 idInSourceParent, parentSynZooName, zooMisappliedName,
2095 synParentGenus,
2096 synParentInfragenericName,
2097 synParentSpecificEpithet, misappliedName, zooHashMap);
2098
2099
2100 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2101 inferredSynonyms.add(potentialCombination);
2102 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2103 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2104 }
2105 }
2106 }
2107
2108 if (!taxonNames.isEmpty()){
2109 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2110 ZoologicalName name;
2111 if (!synNotInCDM.isEmpty()){
2112 inferredSynonymsToBeRemoved.clear();
2113 for (Synonym syn :inferredSynonyms){
2114 try{
2115 name = (ZoologicalName) syn.getName();
2116 }catch (ClassCastException e){
2117 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2118 }
2119 if (!synNotInCDM.contains(name.getNameCache())){
2120 inferredSynonymsToBeRemoved.add(syn);
2121 }
2122 }
2123 // Remove identified Synonyms from inferredSynonyms
2124 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2125 inferredSynonyms.remove(synonym);
2126 }
2127 }
2128 }
2129 }
2130 }else {
2131 logger.info("The synonymrelationship type is not defined.");
2132 return inferredSynonyms;
2133 }
2134 }
2135 }
2136
2137 }
2138
2139 return inferredSynonyms;
2140 }
2141
2142 private Synonym createPotentialCombination(String idInSourceParent,
2143 ZoologicalName parentSynZooName, ZoologicalName zooSynName, String synParentGenus,
2144 String synParentInfragenericName, String synParentSpecificEpithet,
2145 TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2146 Synonym potentialCombination;
2147 Reference sourceReference;
2148 ZoologicalName inferredSynName;
2149 HibernateProxyHelper.deproxy(syn);
2150
2151 // Set sourceReference
2152 sourceReference = syn.getSec();
2153 if (sourceReference == null){
2154 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2155 //TODO:Remove
2156 if (!parentSynZooName.getTaxa().isEmpty()){
2157 TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2158
2159 sourceReference = taxon.getSec();
2160 }
2161 }
2162 String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2163
2164 String synTaxonInfraSpecificName= null;
2165
2166 if (parentSynZooName.isSpecies()){
2167 synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2168 }
2169
2170 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2171 synonymsEpithet.add(epithetName);
2172 }*/
2173
2174 //create potential combinations...
2175 inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2176
2177 inferredSynName.setGenusOrUninomial(synParentGenus);
2178 if (zooSynName.isSpecies()){
2179 inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2180 if (parentSynZooName.isInfraGeneric()){
2181 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2182 }
2183 }
2184 if (zooSynName.isInfraSpecific()){
2185 inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2186 inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2187 }
2188 if (parentSynZooName.isInfraGeneric()){
2189 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2190 }
2191
2192
2193 potentialCombination = Synonym.NewInstance(inferredSynName, null);
2194
2195 // Set the sourceReference
2196 potentialCombination.setSec(sourceReference);
2197
2198
2199 // Determine the idInSource
2200 String idInSourceSyn= getIdInSource(syn);
2201
2202 if (idInSourceParent != null && idInSourceSyn != null) {
2203 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2204 inferredSynName.addSource(originalSource);
2205 originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2206 potentialCombination.addSource(originalSource);
2207 }
2208
2209 inferredSynName.generateTitle();
2210
2211 return potentialCombination;
2212 }
2213
2214 private Synonym createInferredGenus(Taxon taxon,
2215 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2216 String epithetOfTaxon, String genusOfTaxon,
2217 List<String> taxonNames, ZoologicalName zooParentName,
2218 TaxonBase syn) {
2219
2220 Synonym inferredGenus;
2221 TaxonNameBase synName;
2222 ZoologicalName inferredSynName;
2223 synName =syn.getName();
2224 HibernateProxyHelper.deproxy(syn);
2225
2226 // Determine the idInSource
2227 String idInSourceSyn = getIdInSource(syn);
2228 String idInSourceTaxon = getIdInSource(taxon);
2229 // Determine the sourceReference
2230 Reference sourceReference = syn.getSec();
2231
2232 //logger.warn(sourceReference.getTitleCache());
2233
2234 synName = syn.getName();
2235 ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2236 String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2237 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2238 synonymsEpithet.add(synSpeciesEpithetName);
2239 }*/
2240
2241 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2242 //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...
2243
2244
2245 inferredSynName.setGenusOrUninomial(genusOfTaxon);
2246 if (zooParentName.isInfraGeneric()){
2247 inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2248 }
2249
2250 if (taxonName.isSpecies()){
2251 inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2252 }
2253 if (taxonName.isInfraSpecific()){
2254 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2255 inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2256 }
2257
2258
2259 inferredGenus = Synonym.NewInstance(inferredSynName, null);
2260
2261 // Set the sourceReference
2262 inferredGenus.setSec(sourceReference);
2263
2264 // Add the original source
2265 if (idInSourceSyn != null && idInSourceTaxon != null) {
2266 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2267 inferredGenus.addSource(originalSource);
2268
2269 originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2270 inferredSynName.addSource(originalSource);
2271 originalSource = null;
2272
2273 }else{
2274 logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2275 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2276 inferredGenus.addSource(originalSource);
2277
2278 originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2279 inferredSynName.addSource(originalSource);
2280 originalSource = null;
2281 }
2282
2283 taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2284
2285 inferredSynName.generateTitle();
2286
2287
2288 return inferredGenus;
2289 }
2290
2291 private Synonym createInferredEpithets(Taxon taxon,
2292 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2293 String epithetOfTaxon, String infragenericEpithetOfTaxon,
2294 String infraspecificEpithetOfTaxon, List<String> taxonNames,
2295 TaxonNameBase parentName, TaxonBase syn) {
2296
2297 Synonym inferredEpithet;
2298 TaxonNameBase<?,?> synName;
2299 ZoologicalName inferredSynName;
2300 HibernateProxyHelper.deproxy(syn);
2301
2302 // Determine the idInSource
2303 String idInSourceSyn = getIdInSource(syn);
2304 String idInSourceTaxon = getIdInSource(taxon);
2305 // Determine the sourceReference
2306 Reference<?> sourceReference = syn.getSec();
2307
2308 if (sourceReference == null){
2309 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2310 sourceReference = taxon.getSec();
2311 }
2312
2313 synName = syn.getName();
2314 ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2315 String synGenusName = zooSynName.getGenusOrUninomial();
2316 String synInfraGenericEpithet = null;
2317 String synSpecificEpithet = null;
2318
2319 if (zooSynName.getInfraGenericEpithet() != null){
2320 synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2321 }
2322
2323 if (zooSynName.isInfraSpecific()){
2324 synSpecificEpithet = zooSynName.getSpecificEpithet();
2325 }
2326
2327 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2328 synonymsGenus.put(synGenusName, idInSource);
2329 }*/
2330
2331 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2332
2333 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2334 if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2335 logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2336 }
2337 inferredSynName.setGenusOrUninomial(synGenusName);
2338
2339 if (parentName.isInfraGeneric()){
2340 inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2341 }
2342 if (taxonName.isSpecies()){
2343 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2344 }else if (taxonName.isInfraSpecific()){
2345 inferredSynName.setSpecificEpithet(synSpecificEpithet);
2346 inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2347 }
2348
2349 inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2350
2351 // Set the sourceReference
2352 inferredEpithet.setSec(sourceReference);
2353
2354 /* Add the original source
2355 if (idInSource != null) {
2356 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2357
2358 // Add the citation
2359 Reference citation = getCitation(syn);
2360 if (citation != null) {
2361 originalSource.setCitation(citation);
2362 inferredEpithet.addSource(originalSource);
2363 }
2364 }*/
2365 String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2366
2367
2368 IdentifiableSource originalSource = IdentifiableSource.NewInstance(taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2369
2370 inferredEpithet.addSource(originalSource);
2371
2372 originalSource = IdentifiableSource.NewInstance(taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2373
2374 inferredSynName.addSource(originalSource);
2375
2376
2377
2378 taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2379
2380 inferredSynName.generateTitle();
2381 return inferredEpithet;
2382 }
2383
2384 /**
2385 * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2386 * Very likely only useful for createInferredSynonyms().
2387 * @param uuid
2388 * @param zooHashMap
2389 * @return
2390 */
2391 private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2392 ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2393 if (taxonName == null) {
2394 taxonName = zooHashMap.get(uuid);
2395 }
2396 return taxonName;
2397 }
2398
2399 /**
2400 * Returns the idInSource for a given Synonym.
2401 * @param syn
2402 */
2403 private String getIdInSource(TaxonBase taxonBase) {
2404 String idInSource = null;
2405 Set<IdentifiableSource> sources = taxonBase.getSources();
2406 if (sources.size() == 1) {
2407 IdentifiableSource source = sources.iterator().next();
2408 if (source != null) {
2409 idInSource = source.getIdInSource();
2410 }
2411 } else if (sources.size() > 1) {
2412 int count = 1;
2413 idInSource = "";
2414 for (IdentifiableSource source : sources) {
2415 idInSource += source.getIdInSource();
2416 if (count < sources.size()) {
2417 idInSource += "; ";
2418 }
2419 count++;
2420 }
2421 } else if (sources.size() == 0){
2422 logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2423 }
2424
2425
2426 return idInSource;
2427 }
2428
2429
2430 /**
2431 * Returns the citation for a given Synonym.
2432 * @param syn
2433 */
2434 private Reference getCitation(Synonym syn) {
2435 Reference citation = null;
2436 Set<IdentifiableSource> sources = syn.getSources();
2437 if (sources.size() == 1) {
2438 IdentifiableSource source = sources.iterator().next();
2439 if (source != null) {
2440 citation = source.getCitation();
2441 }
2442 } else if (sources.size() > 1) {
2443 logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2444 }
2445
2446 return citation;
2447 }
2448
2449 @Override
2450 public List<Synonym> createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2451 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2452
2453 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2454 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2455 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2456
2457 return inferredSynonyms;
2458 }
2459
2460 /* (non-Javadoc)
2461 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listClassifications(eu.etaxonomy.cdm.model.taxon.TaxonBase, java.lang.Integer, java.lang.Integer, java.util.List)
2462 */
2463 @Override
2464 public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2465
2466 // TODO quickly implemented, create according dao !!!!
2467 Set<TaxonNode> nodes = new HashSet<TaxonNode>();
2468 Set<Classification> classifications = new HashSet<Classification>();
2469 List<Classification> list = new ArrayList<Classification>();
2470
2471 if (taxonBase == null) {
2472 return list;
2473 }
2474
2475 taxonBase = load(taxonBase.getUuid());
2476
2477 if (taxonBase instanceof Taxon) {
2478 nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
2479 } else {
2480 for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
2481 nodes.addAll(taxon.getTaxonNodes());
2482 }
2483 }
2484 for (TaxonNode node : nodes) {
2485 classifications.add(node.getClassification());
2486 }
2487 list.addAll(classifications);
2488 return list;
2489 }
2490
2491
2492
2493
2494
2495
2496 }