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