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