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