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