(no commit message)
[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.OrderedTermVocabulary;
70 import eu.etaxonomy.cdm.model.common.OriginalSourceType;
71 import eu.etaxonomy.cdm.model.common.RelationshipBase;
72 import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
73 import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
74 import eu.etaxonomy.cdm.model.description.CommonTaxonName;
75 import eu.etaxonomy.cdm.model.description.DescriptionBase;
76 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
77 import eu.etaxonomy.cdm.model.description.Distribution;
78 import eu.etaxonomy.cdm.model.description.Feature;
79 import eu.etaxonomy.cdm.model.description.IIdentificationKey;
80 import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
81 import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
82 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
83 import eu.etaxonomy.cdm.model.description.TaxonDescription;
84 import eu.etaxonomy.cdm.model.description.TaxonInteraction;
85 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
86 import eu.etaxonomy.cdm.model.location.NamedArea;
87 import eu.etaxonomy.cdm.model.media.Media;
88 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
89 import eu.etaxonomy.cdm.model.media.MediaUtils;
90 import eu.etaxonomy.cdm.model.molecular.Amplification;
91 import eu.etaxonomy.cdm.model.molecular.DnaSample;
92 import eu.etaxonomy.cdm.model.molecular.Sequence;
93 import eu.etaxonomy.cdm.model.molecular.SingleRead;
94 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
95 import eu.etaxonomy.cdm.model.name.Rank;
96 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
97 import eu.etaxonomy.cdm.model.name.ZoologicalName;
98 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
99 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
100 import eu.etaxonomy.cdm.model.reference.Reference;
101 import eu.etaxonomy.cdm.model.taxon.Classification;
102 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
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 List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
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 removeDescriptions.add(desc);
1027 descriptionService.delete(desc);
1028
1029 }
1030 for (TaxonDescription desc: removeDescriptions){
1031 taxon.removeDescription(desc);
1032 }
1033 }
1034
1035
1036 //check references with only reverse mapping
1037 String message = checkForReferences(taxon);
1038 if (message != null){
1039 throw new ReferencedObjectUndeletableException(message.toString());
1040 }
1041
1042 if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null )){
1043 if (taxon.getTaxonNodes().size() > 0){
1044 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.";
1045 throw new ReferencedObjectUndeletableException(message);
1046 }
1047 }else{
1048 if (taxon.getTaxonNodes().size() != 0){
1049 Set<TaxonNode> nodes = taxon.getTaxonNodes();
1050 Iterator<TaxonNode> iterator = nodes.iterator();
1051 TaxonNode node = null;
1052 boolean deleteChildren;
1053 if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1054 deleteChildren = true;
1055 }else {
1056 deleteChildren = false;
1057 }
1058 boolean success = true;
1059 if (!config.isDeleteInAllClassifications() && !(classification == null)){
1060 while (iterator.hasNext()){
1061 node = iterator.next();
1062 if (node.getClassification().equals(classification)){
1063 break;
1064 }
1065 node = null;
1066 }
1067 if (node != null){
1068 success =taxon.removeTaxonNode(node, deleteChildren);
1069 nodeService.delete(node);
1070 } else {
1071 message = "Taxon is not used in defined classification";
1072 throw new DataChangeNoRollbackException(message);
1073 }
1074 } else if (config.isDeleteInAllClassifications()){
1075 Set<ITaxonTreeNode> nodesList = new HashSet<ITaxonTreeNode>();
1076 nodesList.addAll(taxon.getTaxonNodes());
1077
1078 for (ITaxonTreeNode treeNode: nodesList){
1079 TaxonNode taxonNode = (TaxonNode) treeNode;
1080 if(!deleteChildren){
1081 /* Object[] childNodes = taxonNode.getChildNodes().toArray();
1082 //nodesList.addAll(taxonNode.getChildNodes());
1083 for (Object childNode: childNodes){
1084 TaxonNode childNodeCast = (TaxonNode) childNode;
1085 deleteTaxon(childNodeCast.getTaxon(), config, classification);
1086
1087 }
1088
1089 /*for (TaxonNode childNode: taxonNode.getChildNodes()){
1090 deleteTaxon(childNode.getTaxon(), config, classification);
1091
1092 }
1093 // taxon.removeTaxonNode(taxonNode);
1094 //nodeService.delete(taxonNode);
1095 } else{
1096 */
1097 Object[] childNodes = taxonNode.getChildNodes().toArray();
1098 for (Object childNode: childNodes){
1099 TaxonNode childNodeCast = (TaxonNode) childNode;
1100 taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1101 }
1102
1103 //taxon.removeTaxonNode(taxonNode);
1104 }
1105 }
1106 config.getTaxonNodeConfig().setDeleteTaxon(false);
1107 nodeService.deleteTaxonNodes(nodesList, config);
1108 }
1109 if (!success){
1110 message = "The taxon node could not be deleted.";
1111 throw new DataChangeNoRollbackException(message);
1112 }
1113 }
1114 }
1115 //TaxonNameBase
1116 if (config.isDeleteNameIfPossible()){
1117 try {
1118
1119 //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1120 TaxonNameBase name = (TaxonNameBase)HibernateProxyHelper.deproxy(taxon.getName());
1121 //check whether taxon will be deleted or not
1122 if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null){
1123 taxon = (Taxon) HibernateProxyHelper.deproxy(taxon);
1124 name.removeTaxonBase(taxon);
1125 nameService.save(name);
1126 nameService.delete(name, config.getNameDeletionConfig());
1127 }
1128 } catch (ReferencedObjectUndeletableException e) {
1129 //do nothing
1130 if (logger.isDebugEnabled()){logger.debug("Name could not be deleted");}
1131
1132 }
1133 }
1134
1135 // TaxonDescription
1136 /* Set<TaxonDescription> descriptions = taxon.getDescriptions();
1137
1138 for (TaxonDescription desc: descriptions){
1139 if (config.isDeleteDescriptions()){
1140 //TODO use description delete configurator ?
1141 //FIXME check if description is ALWAYS deletable
1142 taxon.removeDescription(desc);
1143 descriptionService.delete(desc);
1144 }else{
1145 if (desc.getDescribedSpecimenOrObservations().size()>0){
1146 String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1147 " which also describes specimens or observations";
1148 throw new ReferencedObjectUndeletableException(message);
1149 }
1150 }
1151 }*/
1152
1153 if (taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0){
1154 dao.delete(taxon);
1155 return taxon.getUuid();
1156 } else{
1157 message = "Taxon can't be deleted as it is used in another Taxonnode";
1158 if (!config.isDeleteInAllClassifications() && classification != null) {
1159 message += "The Taxonnode in " + classification.getTitleCache() + " was deleted.";
1160 }
1161 throw new ReferencedObjectUndeletableException(message);
1162 }
1163
1164
1165 }
1166
1167 private String checkForReferences(Taxon taxon){
1168 Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1169 for (CdmBase referencingObject : referencingObjects){
1170 //IIdentificationKeys (Media, Polytomous, MultiAccess)
1171 if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1172 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";
1173
1174 return message;
1175 }
1176
1177
1178 //PolytomousKeyNode
1179 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1180 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1181 return message;
1182 }
1183
1184 //TaxonInteraction
1185 if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1186 String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1187 return message;
1188 }
1189 }
1190 referencingObjects = null;
1191 return null;
1192 }
1193
1194 @Transactional(readOnly = false)
1195 public UUID delete(Synonym syn){
1196 UUID result = syn.getUuid();
1197 this.deleteSynonym(syn, null);
1198 return result;
1199 }
1200
1201 /* (non-Javadoc)
1202 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1203 */
1204 @Transactional(readOnly = false)
1205 @Override
1206 public void deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1207 deleteSynonym(synonym, null, config);
1208
1209 }
1210
1211
1212 /* (non-Javadoc)
1213 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1214 */
1215 @Transactional(readOnly = false)
1216 @Override
1217 public void deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1218 if (synonym == null){
1219 return;
1220 }
1221 if (config == null){
1222 config = new SynonymDeletionConfigurator();
1223 }
1224 synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
1225
1226 //remove synonymRelationship
1227 Set<Taxon> taxonSet = new HashSet<Taxon>();
1228 if (taxon != null){
1229 taxonSet.add(taxon);
1230 }else{
1231 taxonSet.addAll(synonym.getAcceptedTaxa());
1232 }
1233 for (Taxon relatedTaxon : taxonSet){
1234 // dao.deleteSynonymRelationships(synonym, relatedTaxon);
1235 relatedTaxon.removeSynonym(synonym, config.isNewHomotypicGroupIfNeeded());
1236 }
1237 this.saveOrUpdate(synonym);
1238
1239 //TODO remove name from homotypical group?
1240
1241 //remove synonym (if necessary)
1242
1243
1244 if (synonym.getSynonymRelations().isEmpty()){
1245 TaxonNameBase<?,?> name = synonym.getName();
1246 synonym.setName(null);
1247 dao.delete(synonym);
1248
1249 //remove name if possible (and required)
1250 if (name != null && config.isDeleteNameIfPossible()){
1251 try{
1252 nameService.delete(name, config.getNameDeletionConfig());
1253 }catch (ReferencedObjectUndeletableException ex){
1254 if (logger.isDebugEnabled()) {
1255 logger.debug("Name wasn't deleted as it is referenced");
1256 }
1257 }
1258 }
1259 }
1260 }
1261
1262
1263 /* (non-Javadoc)
1264 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNameIds(java.util.List)
1265 */
1266 @Override
1267 public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1268
1269 return this.dao.findIdenticalNamesNew(propertyPath);
1270 }
1271
1272 /* (non-Javadoc)
1273 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getPhylumName(eu.etaxonomy.cdm.model.name.TaxonNameBase)
1274 */
1275 @Override
1276 public String getPhylumName(TaxonNameBase name){
1277 return this.dao.getPhylumName(name);
1278 }
1279
1280 /* (non-Javadoc)
1281 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
1282 */
1283 @Override
1284 public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1285 return dao.deleteSynonymRelationships(syn, taxon);
1286 }
1287
1288 /* (non-Javadoc)
1289 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym)
1290 */
1291 @Override
1292 public long deleteSynonymRelationships(Synonym syn) {
1293 return dao.deleteSynonymRelationships(syn, null);
1294 }
1295
1296
1297 /* (non-Javadoc)
1298 * @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)
1299 */
1300 @Override
1301 public List<SynonymRelationship> listSynonymRelationships(
1302 TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1303 List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1304 Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1305
1306 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1307 if(numberOfResults > 0) { // no point checking again
1308 results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1309 }
1310 return results;
1311 }
1312
1313 /* (non-Javadoc)
1314 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingTaxon(java.lang.String)
1315 */
1316 @Override
1317 public Taxon findBestMatchingTaxon(String taxonName) {
1318 MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1319 config.setTaxonNameTitle(taxonName);
1320 return findBestMatchingTaxon(config);
1321 }
1322
1323
1324
1325 @Override
1326 public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1327
1328 Taxon bestCandidate = null;
1329 try{
1330 // 1. search for acceptet taxa
1331 List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1332 boolean bestCandidateMatchesSecUuid = false;
1333 boolean bestCandidateIsInClassification = false;
1334 int countEqualCandidates = 0;
1335 for(TaxonBase taxonBaseCandidate : taxonList){
1336 if(taxonBaseCandidate instanceof Taxon){
1337 Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1338 boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1339 if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1340 continue;
1341 }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1342 bestCandidate = newCanditate;
1343 countEqualCandidates = 1;
1344 bestCandidateMatchesSecUuid = true;
1345 continue;
1346 }
1347
1348 boolean newCandidateInClassification = isInClassification(newCanditate, config);
1349 if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1350 continue;
1351 }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1352 bestCandidate = newCanditate;
1353 countEqualCandidates = 1;
1354 bestCandidateIsInClassification = true;
1355 continue;
1356 }
1357 if (bestCandidate == null){
1358 bestCandidate = newCanditate;
1359 countEqualCandidates = 1;
1360 continue;
1361 }
1362
1363 }else{ //not Taxon.class
1364 continue;
1365 }
1366 countEqualCandidates++;
1367
1368 }
1369 if (bestCandidate != null){
1370 if(countEqualCandidates > 1){
1371 logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1372 return bestCandidate;
1373 } else {
1374 logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1375 return bestCandidate;
1376 }
1377 }
1378
1379
1380 // 2. search for synonyms
1381 if (config.isIncludeSynonyms()){
1382 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1383 for(TaxonBase taxonBase : synonymList){
1384 if(taxonBase instanceof Synonym){
1385 Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1386 Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1387 if(!acceptetdCandidates.isEmpty()){
1388 bestCandidate = acceptetdCandidates.iterator().next();
1389 if(acceptetdCandidates.size() == 1){
1390 logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1391 return bestCandidate;
1392 } else {
1393 logger.info("using accepted Taxon " + bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1394 return bestCandidate;
1395 }
1396 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1397 }
1398 }
1399 }
1400 }
1401
1402 } catch (Exception e){
1403 logger.error(e);
1404 e.printStackTrace();
1405 }
1406
1407 return bestCandidate;
1408 }
1409
1410 private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1411 UUID configClassificationUuid = config.getClassificationUuid();
1412 if (configClassificationUuid == null){
1413 return false;
1414 }
1415 for (TaxonNode node : taxon.getTaxonNodes()){
1416 UUID classUuid = node.getClassification().getUuid();
1417 if (configClassificationUuid.equals(classUuid)){
1418 return true;
1419 }
1420 }
1421 return false;
1422 }
1423
1424 private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1425 UUID configSecUuid = config.getSecUuid();
1426 if (configSecUuid == null){
1427 return false;
1428 }
1429 UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1430 return configSecUuid.equals(taxonSecUuid);
1431 }
1432
1433 /* (non-Javadoc)
1434 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingSynonym(java.lang.String)
1435 */
1436 @Override
1437 public Synonym findBestMatchingSynonym(String taxonName) {
1438 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1439 if(! synonymList.isEmpty()){
1440 Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1441 if(synonymList.size() == 1){
1442 logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1443 return result;
1444 } else {
1445 logger.info("Several matching synonyms found. Using first: " + result.getTitleCache());
1446 return result;
1447 }
1448 }
1449 return null;
1450 }
1451
1452
1453 /* (non-Javadoc)
1454 * @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)
1455 */
1456 @Override
1457 public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, Taxon newTaxon, boolean moveHomotypicGroup,
1458 SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
1459
1460 Synonym synonym = oldSynonymRelation.getSynonym();
1461 Taxon fromTaxon = oldSynonymRelation.getAcceptedTaxon();
1462 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1463 TaxonNameBase<?,?> synonymName = synonym.getName();
1464 TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1465 //set default relationship type
1466 if (newSynonymRelationshipType == null){
1467 newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1468 }
1469 boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1470
1471 HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1472 int hgSize = homotypicGroup.getTypifiedNames().size();
1473 boolean isSingleInGroup = !(hgSize > 1);
1474
1475 if (! isSingleInGroup){
1476 boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1477 boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1478 if (isHomotypicToAccepted){
1479 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.";
1480 String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1481 message = String.format(message, homotypicRelatives);
1482 throw new HomotypicalGroupChangeException(message);
1483 }
1484 if (! moveHomotypicGroup){
1485 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.";
1486 throw new HomotypicalGroupChangeException(message);
1487 }
1488 }else{
1489 moveHomotypicGroup = true; //single synonym always allows to moveCompleteGroup
1490 }
1491 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1492
1493 SynonymRelationship result = null;
1494 //move all synonyms to new taxon
1495 List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1496 for (Synonym syn: homotypicSynonyms){
1497 Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1498 for (SynonymRelationship synRelation : synRelations){
1499 if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1500 Reference<?> newReference = reference;
1501 if (newReference == null && keepReference){
1502 newReference = synRelation.getCitation();
1503 }
1504 String newRefDetail = referenceDetail;
1505 if (newRefDetail == null && keepReference){
1506 newRefDetail = synRelation.getCitationMicroReference();
1507 }
1508 SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1509 fromTaxon.removeSynonymRelation(synRelation, false);
1510 //
1511 //change homotypic group of synonym if relType is 'homotypic'
1512 // if (newRelTypeIsHomotypic){
1513 // newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1514 // }
1515 //set result
1516 if (synRelation.equals(oldSynonymRelation)){
1517 result = newSynRelation;
1518 }
1519 }
1520 }
1521
1522 }
1523 saveOrUpdate(newTaxon);
1524 //Assert that there is a result
1525 if (result == null){
1526 String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1527 throw new IllegalStateException(message);
1528 }
1529 return result;
1530 }
1531
1532 /* (non-Javadoc)
1533 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheTaxon()
1534 */
1535 @Override
1536 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1537 return dao.getUuidAndTitleCacheTaxon();
1538 }
1539
1540 /* (non-Javadoc)
1541 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheSynonym()
1542 */
1543 @Override
1544 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1545 return dao.getUuidAndTitleCacheSynonym();
1546 }
1547
1548 /* (non-Javadoc)
1549 * @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)
1550 */
1551 @Override
1552 public Pager<SearchResult<TaxonBase>> findByFullText(
1553 Class<? extends TaxonBase> clazz, String queryString,
1554 Classification classification, List<Language> languages,
1555 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1556
1557
1558 LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1559
1560 // --- execute search
1561 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1562
1563 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1564 idFieldMap.put(CdmBaseType.TAXON, "id");
1565
1566 // --- initialize taxa, thighlight matches ....
1567 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1568 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1569 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1570
1571 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1572 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1573 }
1574
1575 @Override
1576 public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTermBase<?>> statusFilter,
1577 Classification classification,
1578 Integer pageSize, Integer pageNumber,
1579 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1580
1581 LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1582
1583 // --- execute search
1584 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1585
1586 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1587 idFieldMap.put(CdmBaseType.TAXON, "id");
1588
1589 // --- initialize taxa, thighlight matches ....
1590 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1591 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1592 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1593
1594 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1595 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1596 }
1597
1598 /**
1599 * @param clazz
1600 * @param queryString
1601 * @param classification
1602 * @param languages
1603 * @param highlightFragments
1604 * @param sortFields TODO
1605 * @param directorySelectClass
1606 * @return
1607 */
1608 protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1609 boolean highlightFragments, SortField[] sortFields) {
1610 BooleanQuery finalQuery = new BooleanQuery();
1611 BooleanQuery textQuery = new BooleanQuery();
1612
1613 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1614 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1615
1616 if(sortFields == null){
1617 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1618 }
1619 luceneSearch.setSortFields(sortFields);
1620
1621 // ---- search criteria
1622 luceneSearch.setCdmTypRestriction(clazz);
1623
1624 textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1625 textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1626
1627 finalQuery.add(textQuery, Occur.MUST);
1628
1629 if(classification != null){
1630 finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1631 }
1632 luceneSearch.setQuery(finalQuery);
1633
1634 if(highlightFragments){
1635 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1636 }
1637 return luceneSearch;
1638 }
1639
1640 /**
1641 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1642 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1643 * drawback of requiring to do the join an indexing time.
1644 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1645 *
1646 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1647 * <ul>
1648 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1649 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1650 * <ul>
1651 * @param queryString
1652 * @param classification
1653 * @param languages
1654 * @param highlightFragments
1655 * @param sortFields TODO
1656 *
1657 * @return
1658 * @throws IOException
1659 */
1660 protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1661 boolean highlightFragments, SortField[] sortFields) throws IOException {
1662
1663 String fromField;
1664 String queryTermField;
1665 String toField = "id"; // TaxonBase.uuid
1666
1667 if(edge.isBidirectional()){
1668 throw new RuntimeException("Bidirectional joining not supported!");
1669 }
1670 if(edge.isEvers()){
1671 fromField = "relatedFrom.id";
1672 queryTermField = "relatedFrom.titleCache";
1673 } else if(edge.isInvers()) {
1674 fromField = "relatedTo.id";
1675 queryTermField = "relatedTo.titleCache";
1676 } else {
1677 throw new RuntimeException("Invalid direction: " + edge.getDirections());
1678 }
1679
1680 BooleanQuery finalQuery = new BooleanQuery();
1681
1682 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1683 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1684
1685 BooleanQuery joinFromQuery = new BooleanQuery();
1686 joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1687 joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1688 Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1689
1690 if(sortFields == null){
1691 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1692 }
1693 luceneSearch.setSortFields(sortFields);
1694
1695 finalQuery.add(joinQuery, Occur.MUST);
1696
1697 if(classification != null){
1698 finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1699 }
1700 luceneSearch.setQuery(finalQuery);
1701
1702 if(highlightFragments){
1703 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1704 }
1705 return luceneSearch;
1706 }
1707
1708
1709
1710
1711 /* (non-Javadoc)
1712 * @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)
1713 */
1714 @Override
1715 public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1716 EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1717 Set<NamedArea> namedAreas, Set<PresenceAbsenceTermBase<?>> distributionStatus, List<Language> languages,
1718 boolean highlightFragments, Integer pageSize,
1719 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1720 throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1721
1722 // FIXME: allow taxonomic ordering
1723 // 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";
1724 // this require building a special sort column by a special classBridge
1725 if(highlightFragments){
1726 logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1727 "currently not fully supported by this method and thus " +
1728 "may not work with common names and misapplied names.");
1729 }
1730
1731 // convert sets to lists
1732 List<NamedArea> namedAreaList = null;
1733 List<PresenceAbsenceTermBase<?>>distributionStatusList = null;
1734 if(namedAreas != null){
1735 namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1736 namedAreaList.addAll(namedAreas);
1737 }
1738 if(distributionStatus != null){
1739 distributionStatusList = new ArrayList<PresenceAbsenceTermBase<?>>(distributionStatus.size());
1740 distributionStatusList.addAll(distributionStatus);
1741 }
1742
1743 // set default if parameter is null
1744 if(searchModes == null){
1745 searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1746 }
1747
1748 // set sort order and thus override any sort orders which may have been
1749 // defindes by prepare*Search methods
1750 if(orderHints == null){
1751 orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER;
1752 }
1753 SortField[] sortFields = new SortField[orderHints.size()];
1754 int i = 0;
1755 for(OrderHint oh : orderHints){
1756 sortFields[i++] = oh.toSortField();
1757 }
1758 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1759 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1760
1761
1762 boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1763
1764 List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1765 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1766
1767 /*
1768 ======== filtering by distribution , HOWTO ========
1769
1770 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1771 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1772 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1773 which will be put into a FilteredQuersy in the end ?
1774
1775
1776 3. how does it work in spatial?
1777 see
1778 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1779 - http://www.infoq.com/articles/LuceneSpatialSupport
1780 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1781 ------------------------------------------------------------------------
1782
1783 filter strategies:
1784 A) use a separate distribution filter per index sub-query/search:
1785 - byTaxonSyonym (query TaxaonBase):
1786 use a join area filter (Distribution -> TaxonBase)
1787 - byCommonName (query DescriptionElementBase): use an area filter on
1788 DescriptionElementBase !!! PROBLEM !!!
1789 This cannot work since the distributions are different entities than the
1790 common names and thus these are different lucene documents.
1791 - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1792 use a join area filter (Distribution -> TaxonBase)
1793
1794 B) use a common distribution filter for all index sub-query/searches:
1795 - use a common join area filter (Distribution -> TaxonBase)
1796 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1797 PROBLEM in this case: we are losing the fragment highlighting for the
1798 common names, since the returned documents are always TaxonBases
1799 */
1800
1801 /* The QueryFactory for creating filter queries on Distributions should
1802 * The query factory used for the common names query cannot be reused
1803 * for this case, since we want to only record the text fields which are
1804 * actually used in the primary query
1805 */
1806 QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1807
1808 BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1809
1810
1811 // search for taxa or synonyms
1812 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1813 Class taxonBaseSubclass = TaxonBase.class;
1814 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1815 taxonBaseSubclass = Taxon.class;
1816 } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1817 taxonBaseSubclass = Synonym.class;
1818 }
1819 luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1820 idFieldMap.put(CdmBaseType.TAXON, "id");
1821 /* A) does not work!!!!
1822 if(addDistributionFilter){
1823 // in this case we need a filter which uses a join query
1824 // to get the TaxonBase documents for the DescriptionElementBase documents
1825 // which are matching the areas in question
1826 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1827 namedAreaList,
1828 distributionStatusList,
1829 distributionFilterQueryFactory
1830 );
1831 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1832 }
1833 */
1834 if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1835 // add additional area filter for synonyms
1836 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1837 String toField = "accTaxon.id"; // id in TaxonBase index
1838
1839 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1840
1841 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1842 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1843
1844 }
1845 }
1846
1847 // search by CommonTaxonName
1848 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1849 // B)
1850 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1851 Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1852 "inDescription.taxon.id",
1853 "id",
1854 QueryFactory.addTypeRestriction(
1855 createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
1856 , CommonTaxonName.class
1857 ),
1858 CommonTaxonName.class);
1859 logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
1860 LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
1861 byCommonNameSearch.setCdmTypRestriction(Taxon.class);
1862 byCommonNameSearch.setQuery(byCommonNameJoinQuery);
1863 byCommonNameSearch.setSortFields(sortFields);
1864 idFieldMap.put(CdmBaseType.TAXON, "id");
1865
1866 luceneSearches.add(byCommonNameSearch);
1867
1868 /* A) does not work!!!!
1869 luceneSearches.add(
1870 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1871 queryString, classification, null, languages, highlightFragments)
1872 );
1873 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1874 if(addDistributionFilter){
1875 // in this case we are able to use DescriptionElementBase documents
1876 // which are matching the areas in question directly
1877 BooleanQuery byDistributionQuery = createByDistributionQuery(
1878 namedAreaList,
1879 distributionStatusList,
1880 distributionFilterQueryFactory
1881 );
1882 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1883 } */
1884 }
1885
1886 // search by misapplied names
1887 if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
1888 // NOTE:
1889 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1890 // which allows doing query time joins
1891 // finds the misapplied name (Taxon B) which is an misapplication for
1892 // a related Taxon A.
1893 //
1894 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
1895 new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
1896 queryString, classification, languages, highlightFragments, sortFields));
1897 idFieldMap.put(CdmBaseType.TAXON, "id");
1898
1899 if(addDistributionFilter){
1900 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1901
1902 /*
1903 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
1904 * Maybe this is a but in java itself java.
1905 *
1906 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1907 * directly:
1908 *
1909 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1910 *
1911 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1912 * will execute as expected:
1913 *
1914 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1915 * String toField = "relation." + misappliedNameForUuid +".to.id";
1916 *
1917 * Comparing both strings by the String.equals method returns true, so both String are identical.
1918 *
1919 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1920 * 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)
1921 * The bug is persistent after a reboot of the development computer.
1922 */
1923 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1924 // String toField = "relation." + misappliedNameForUuid +".to.id";
1925 String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1926 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1927 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1928
1929 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1930 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1931 QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery);
1932
1933 // debug code for bug described above
1934 DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1935 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1936
1937 multiIndexByAreaFilter.add(filter, Occur.SHOULD);
1938 }
1939 }
1940
1941 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
1942 luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
1943
1944
1945 if(addDistributionFilter){
1946
1947 // B)
1948 // in this case we need a filter which uses a join query
1949 // to get the TaxonBase documents for the DescriptionElementBase documents
1950 // which are matching the areas in question
1951 //
1952 // for toTaxa, doByCommonName
1953 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1954 namedAreaList,
1955 distributionStatusList,
1956 distributionFilterQueryFactory
1957 );
1958 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1959 }
1960
1961 if (addDistributionFilter){
1962 multiSearch.setFilter(multiIndexByAreaFilter);
1963 }
1964
1965
1966 // --- execute search
1967 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
1968
1969 // --- initialize taxa, highlight matches ....
1970 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
1971
1972
1973 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1974 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1975
1976 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1977 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1978 }
1979
1980 /**
1981 * @param namedAreaList at least one area must be in the list
1982 * @param distributionStatusList optional
1983 * @return
1984 * @throws IOException
1985 */
1986 protected Query createByDistributionJoinQuery(
1987 List<NamedArea> namedAreaList,
1988 List<PresenceAbsenceTermBase<?>> distributionStatusList,
1989 QueryFactory queryFactory
1990 ) throws IOException {
1991
1992 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1993 String toField = "id"; // id in TaxonBase index
1994
1995 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
1996
1997 Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1998
1999 return taxonAreaJoinQuery;
2000 }
2001
2002 /**
2003 * @param namedAreaList
2004 * @param distributionStatusList
2005 * @param queryFactory
2006 * @return
2007 */
2008 private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2009 List<PresenceAbsenceTermBase<?>> distributionStatusList, QueryFactory queryFactory) {
2010 BooleanQuery areaQuery = new BooleanQuery();
2011 // area field from Distribution
2012 areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2013
2014 // status field from Distribution
2015 if(distributionStatusList != null && distributionStatusList.size() > 0){
2016 areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2017 }
2018
2019 logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2020 return areaQuery;
2021 }
2022
2023 /**
2024 * This method has been primarily created for testing the area join query but might
2025 * also be useful in other situations
2026 *
2027 * @param namedAreaList
2028 * @param distributionStatusList
2029 * @param classification
2030 * @param highlightFragments
2031 * @return
2032 * @throws IOException
2033 */
2034 protected LuceneSearch prepareByDistributionSearch(
2035 List<NamedArea> namedAreaList, List<PresenceAbsenceTermBase<?>> distributionStatusList,
2036 Classification classification) throws IOException {
2037
2038 BooleanQuery finalQuery = new BooleanQuery();
2039
2040 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2041
2042 // FIXME is this query factory using the wrong type?
2043 QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2044
2045 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
2046 luceneSearch.setSortFields(sortFields);
2047
2048
2049 Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory);
2050
2051 finalQuery.add(byAreaQuery, Occur.MUST);
2052
2053 if(classification != null){
2054 finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2055 }
2056
2057 logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2058 luceneSearch.setQuery(finalQuery);
2059
2060 return luceneSearch;
2061 }
2062
2063
2064
2065 /* (non-Javadoc)
2066 * @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)
2067 */
2068 @Override
2069 public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2070 Class<? extends DescriptionElementBase> clazz, String queryString,
2071 Classification classification, List<Feature> features, List<Language> languages,
2072 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2073
2074
2075 LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2076
2077 // --- execute search
2078 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2079
2080 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2081 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2082
2083 // --- initialize taxa, highlight matches ....
2084 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2085 @SuppressWarnings("rawtypes")
2086 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2087 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2088
2089 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2090 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2091
2092 }
2093
2094
2095 @Override
2096 public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2097 Classification classification, List<Language> languages, boolean highlightFragments,
2098 Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2099
2100 LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2101 LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2102
2103 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2104
2105 // --- execute search
2106 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2107
2108 // --- initialize taxa, highlight matches ....
2109 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2110
2111 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2112 idFieldMap.put(CdmBaseType.TAXON, "id");
2113 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2114
2115 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2116 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2117
2118 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2119 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2120
2121 }
2122
2123
2124 /**
2125 * @param clazz
2126 * @param queryString
2127 * @param classification
2128 * @param features
2129 * @param languages
2130 * @param highlightFragments
2131 * @param directorySelectClass
2132 * @return
2133 */
2134 protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2135 String queryString, Classification classification, List<Feature> features,
2136 List<Language> languages, boolean highlightFragments) {
2137
2138 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2139 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2140
2141 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.STRING, false)};
2142
2143 BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2144 languages, descriptionElementQueryFactory);
2145
2146 luceneSearch.setSortFields(sortFields);
2147 luceneSearch.setCdmTypRestriction(clazz);
2148 luceneSearch.setQuery(finalQuery);
2149 if(highlightFragments){
2150 luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2151 }
2152
2153 return luceneSearch;
2154 }
2155
2156 /**
2157 * @param queryString
2158 * @param classification
2159 * @param features
2160 * @param languages
2161 * @param descriptionElementQueryFactory
2162 * @return
2163 */
2164 private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2165 List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2166 BooleanQuery finalQuery = new BooleanQuery();
2167 BooleanQuery textQuery = new BooleanQuery();
2168 textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2169
2170 // common name
2171 Query nameQuery;
2172 if(languages == null || languages.size() == 0){
2173 nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString);
2174 } else {
2175 nameQuery = new BooleanQuery();
2176 BooleanQuery languageSubQuery = new BooleanQuery();
2177 for(Language lang : languages){
2178 languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD);
2179 }
2180 ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2181 ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
2182 }
2183 textQuery.add(nameQuery, Occur.SHOULD);
2184
2185
2186 // text field from TextData
2187 textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2188
2189 // --- TermBase fields - by representation ----
2190 // state field from CategoricalData
2191 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2192
2193 // state field from CategoricalData
2194 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2195
2196 // area field from Distribution
2197 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2198
2199 // status field from Distribution
2200 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2201
2202 finalQuery.add(textQuery, Occur.MUST);
2203 // --- classification ----
2204
2205 if(classification != null){
2206 finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2207 }
2208
2209 // --- IdentifieableEntity fields - by uuid
2210 if(features != null && features.size() > 0 ){
2211 finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2212 }
2213
2214 // the description must be associated with a taxon
2215 finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2216
2217 logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2218 return finalQuery;
2219 }
2220
2221 /**
2222 * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2223 * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2224 * This method is a convenient means to retrieve a Lucene query string for such the fields.
2225 *
2226 * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2227 * or {@link MultilanguageTextFieldBridge }
2228 * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2229 * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2230 * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2231 *
2232 * TODO move to utiliy class !!!!!!!!
2233 */
2234 private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2235
2236 if(stringBuilder == null){
2237 stringBuilder = new StringBuilder();
2238 }
2239 if(languages == null || languages.size() == 0){
2240 stringBuilder.append(name + ".ALL:(%1$s) ");
2241 } else {
2242 for(Language lang : languages){
2243 stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2244 }
2245 }
2246 return stringBuilder;
2247 }
2248
2249 @Override
2250 public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2251 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2252 List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2253
2254 HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2255
2256
2257 UUID nameUuid= taxon.getName().getUuid();
2258 ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2259 String epithetOfTaxon = null;
2260 String infragenericEpithetOfTaxon = null;
2261 String infraspecificEpithetOfTaxon = null;
2262 if (taxonName.isSpecies()){
2263 epithetOfTaxon= taxonName.getSpecificEpithet();
2264 } else if (taxonName.isInfraGeneric()){
2265 infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2266 } else if (taxonName.isInfraSpecific()){
2267 infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2268 }
2269 String genusOfTaxon = taxonName.getGenusOrUninomial();
2270 Set<TaxonNode> nodes = taxon.getTaxonNodes();
2271 List<String> taxonNames = new ArrayList<String>();
2272
2273 for (TaxonNode node: nodes){
2274 // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2275 // List<String> synonymsEpithet = new ArrayList<String>();
2276
2277 if (node.getClassification().equals(classification)){
2278 if (!node.isTopmostNode()){
2279 TaxonNode parent = node.getParent();
2280 parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
2281 TaxonNameBase<?,?> parentName = parent.getTaxon().getName();
2282 ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2283 Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
2284 Rank rankOfTaxon = taxonName.getRank();
2285
2286
2287 //create inferred synonyms for species, subspecies
2288 if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2289
2290 Synonym inferredEpithet = null;
2291 Synonym inferredGenus = null;
2292 Synonym potentialCombination = null;
2293
2294 List<String> propertyPaths = new ArrayList<String>();
2295 propertyPaths.add("synonym");
2296 propertyPaths.add("synonym.name");
2297 List<OrderHint> orderHints = new ArrayList<OrderHint>();
2298 orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2299
2300 List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2301 List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2302
2303 List<TaxonRelationship> taxonRelListParent = null;
2304 List<TaxonRelationship> taxonRelListTaxon = null;
2305 if (doWithMisappliedNames){
2306 taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2307 taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2308 }
2309
2310
2311 if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2312
2313
2314 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2315 Synonym syn = synonymRelationOfParent.getSynonym();
2316
2317 inferredEpithet = createInferredEpithets(taxon,
2318 zooHashMap, taxonName, epithetOfTaxon,
2319 infragenericEpithetOfTaxon,
2320 infraspecificEpithetOfTaxon,
2321 taxonNames, parentName,
2322 syn);
2323
2324
2325 inferredSynonyms.add(inferredEpithet);
2326 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2327 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2328 }
2329
2330 if (doWithMisappliedNames){
2331
2332 for (TaxonRelationship taxonRelationship: taxonRelListParent){
2333 Taxon misappliedName = taxonRelationship.getFromTaxon();
2334
2335 inferredEpithet = createInferredEpithets(taxon,
2336 zooHashMap, taxonName, epithetOfTaxon,
2337 infragenericEpithetOfTaxon,
2338 infraspecificEpithetOfTaxon,
2339 taxonNames, parentName,
2340 misappliedName);
2341
2342 inferredSynonyms.add(inferredEpithet);
2343 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2344 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2345 }
2346 }
2347
2348 if (!taxonNames.isEmpty()){
2349 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2350 ZoologicalName name;
2351 if (!synNotInCDM.isEmpty()){
2352 inferredSynonymsToBeRemoved.clear();
2353
2354 for (Synonym syn :inferredSynonyms){
2355 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2356 if (!synNotInCDM.contains(name.getNameCache())){
2357 inferredSynonymsToBeRemoved.add(syn);
2358 }
2359 }
2360
2361 // Remove identified Synonyms from inferredSynonyms
2362 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2363 inferredSynonyms.remove(synonym);
2364 }
2365 }
2366 }
2367
2368 }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2369
2370
2371 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2372 TaxonNameBase synName;
2373 ZoologicalName inferredSynName;
2374
2375 Synonym syn = synonymRelationOfTaxon.getSynonym();
2376 inferredGenus = createInferredGenus(taxon,
2377 zooHashMap, taxonName, epithetOfTaxon,
2378 genusOfTaxon, taxonNames, zooParentName, syn);
2379
2380 inferredSynonyms.add(inferredGenus);
2381 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2382 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2383
2384
2385 }
2386
2387 if (doWithMisappliedNames){
2388
2389 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2390 Taxon misappliedName = taxonRelationship.getFromTaxon();
2391 inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName, misappliedName);
2392
2393 inferredSynonyms.add(inferredGenus);
2394 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2395 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2396 }
2397 }
2398
2399
2400 if (!taxonNames.isEmpty()){
2401 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2402 ZoologicalName name;
2403 if (!synNotInCDM.isEmpty()){
2404 inferredSynonymsToBeRemoved.clear();
2405
2406 for (Synonym syn :inferredSynonyms){
2407 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2408 if (!synNotInCDM.contains(name.getNameCache())){
2409 inferredSynonymsToBeRemoved.add(syn);
2410 }
2411 }
2412
2413 // Remove identified Synonyms from inferredSynonyms
2414 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2415 inferredSynonyms.remove(synonym);
2416 }
2417 }
2418 }
2419
2420 }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2421
2422 Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2423 ZoologicalName inferredSynName;
2424 //for all synonyms of the parent...
2425 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2426 TaxonNameBase synName;
2427 Synonym synParent = synonymRelationOfParent.getSynonym();
2428 synName = synParent.getName();
2429
2430 HibernateProxyHelper.deproxy(synParent);
2431
2432 // Set the sourceReference
2433 sourceReference = synParent.getSec();
2434
2435 // Determine the idInSource
2436 String idInSourceParent = getIdInSource(synParent);
2437
2438 ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2439 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2440 String synParentInfragenericName = null;
2441 String synParentSpecificEpithet = null;
2442
2443 if (parentSynZooName.isInfraGeneric()){
2444 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2445 }
2446 if (parentSynZooName.isSpecies()){
2447 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2448 }
2449
2450 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2451 synonymsGenus.put(synGenusName, idInSource);
2452 }*/
2453
2454 //for all synonyms of the taxon
2455
2456 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2457
2458 Synonym syn = synonymRelationOfTaxon.getSynonym();
2459 ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2460 potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2461 synParentGenus,
2462 synParentInfragenericName,
2463 synParentSpecificEpithet, syn, zooHashMap);
2464
2465 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2466 inferredSynonyms.add(potentialCombination);
2467 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2468 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2469
2470 }
2471
2472
2473 }
2474
2475 if (doWithMisappliedNames){
2476
2477 for (TaxonRelationship parentRelationship: taxonRelListParent){
2478
2479 TaxonNameBase misappliedParentName;
2480
2481 Taxon misappliedParent = parentRelationship.getFromTaxon();
2482 misappliedParentName = misappliedParent.getName();
2483
2484 HibernateProxyHelper.deproxy(misappliedParent);
2485
2486 // Set the sourceReference
2487 sourceReference = misappliedParent.getSec();
2488
2489 // Determine the idInSource
2490 String idInSourceParent = getIdInSource(misappliedParent);
2491
2492 ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2493 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2494 String synParentInfragenericName = null;
2495 String synParentSpecificEpithet = null;
2496
2497 if (parentSynZooName.isInfraGeneric()){
2498 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2499 }
2500 if (parentSynZooName.isSpecies()){
2501 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2502 }
2503
2504
2505 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2506 Taxon misappliedName = taxonRelationship.getFromTaxon();
2507 ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2508 potentialCombination = createPotentialCombination(
2509 idInSourceParent, parentSynZooName, zooMisappliedName,
2510 synParentGenus,
2511 synParentInfragenericName,
2512 synParentSpecificEpithet, misappliedName, zooHashMap);
2513
2514
2515 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2516 inferredSynonyms.add(potentialCombination);
2517 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2518 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2519 }
2520 }
2521 }
2522
2523 if (!taxonNames.isEmpty()){
2524 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2525 ZoologicalName name;
2526 if (!synNotInCDM.isEmpty()){
2527 inferredSynonymsToBeRemoved.clear();
2528 for (Synonym syn :inferredSynonyms){
2529 try{
2530 name = (ZoologicalName) syn.getName();
2531 }catch (ClassCastException e){
2532 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2533 }
2534 if (!synNotInCDM.contains(name.getNameCache())){
2535 inferredSynonymsToBeRemoved.add(syn);
2536 }
2537 }
2538 // Remove identified Synonyms from inferredSynonyms
2539 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2540 inferredSynonyms.remove(synonym);
2541 }
2542 }
2543 }
2544 }
2545 }else {
2546 logger.info("The synonymrelationship type is not defined.");
2547 return inferredSynonyms;
2548 }
2549 }
2550 }
2551
2552 }
2553
2554 return inferredSynonyms;
2555 }
2556
2557 private Synonym createPotentialCombination(String idInSourceParent,
2558 ZoologicalName parentSynZooName, ZoologicalName zooSynName, String synParentGenus,
2559 String synParentInfragenericName, String synParentSpecificEpithet,
2560 TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2561 Synonym potentialCombination;
2562 Reference sourceReference;
2563 ZoologicalName inferredSynName;
2564 HibernateProxyHelper.deproxy(syn);
2565
2566 // Set sourceReference
2567 sourceReference = syn.getSec();
2568 if (sourceReference == null){
2569 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2570 //TODO:Remove
2571 if (!parentSynZooName.getTaxa().isEmpty()){
2572 TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2573
2574 sourceReference = taxon.getSec();
2575 }
2576 }
2577 String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2578
2579 String synTaxonInfraSpecificName= null;
2580
2581 if (parentSynZooName.isSpecies()){
2582 synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2583 }
2584
2585 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2586 synonymsEpithet.add(epithetName);
2587 }*/
2588
2589 //create potential combinations...
2590 inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2591
2592 inferredSynName.setGenusOrUninomial(synParentGenus);
2593 if (zooSynName.isSpecies()){
2594 inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2595 if (parentSynZooName.isInfraGeneric()){
2596 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2597 }
2598 }
2599 if (zooSynName.isInfraSpecific()){
2600 inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2601 inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2602 }
2603 if (parentSynZooName.isInfraGeneric()){
2604 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2605 }
2606
2607
2608 potentialCombination = Synonym.NewInstance(inferredSynName, null);
2609
2610 // Set the sourceReference
2611 potentialCombination.setSec(sourceReference);
2612
2613
2614 // Determine the idInSource
2615 String idInSourceSyn= getIdInSource(syn);
2616
2617 if (idInSourceParent != null && idInSourceSyn != null) {
2618 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2619 inferredSynName.addSource(originalSource);
2620 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2621 potentialCombination.addSource(originalSource);
2622 }
2623
2624 inferredSynName.generateTitle();
2625
2626 return potentialCombination;
2627 }
2628
2629 private Synonym createInferredGenus(Taxon taxon,
2630 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2631 String epithetOfTaxon, String genusOfTaxon,
2632 List<String> taxonNames, ZoologicalName zooParentName,
2633 TaxonBase syn) {
2634
2635 Synonym inferredGenus;
2636 TaxonNameBase synName;
2637 ZoologicalName inferredSynName;
2638 synName =syn.getName();
2639 HibernateProxyHelper.deproxy(syn);
2640
2641 // Determine the idInSource
2642 String idInSourceSyn = getIdInSource(syn);
2643 String idInSourceTaxon = getIdInSource(taxon);
2644 // Determine the sourceReference
2645 Reference sourceReference = syn.getSec();
2646
2647 //logger.warn(sourceReference.getTitleCache());
2648
2649 synName = syn.getName();
2650 ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2651 String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2652 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2653 synonymsEpithet.add(synSpeciesEpithetName);
2654 }*/
2655
2656 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2657 //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...
2658
2659
2660 inferredSynName.setGenusOrUninomial(genusOfTaxon);
2661 if (zooParentName.isInfraGeneric()){
2662 inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2663 }
2664
2665 if (taxonName.isSpecies()){
2666 inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2667 }
2668 if (taxonName.isInfraSpecific()){
2669 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2670 inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2671 }
2672
2673
2674 inferredGenus = Synonym.NewInstance(inferredSynName, null);
2675
2676 // Set the sourceReference
2677 inferredGenus.setSec(sourceReference);
2678
2679 // Add the original source
2680 if (idInSourceSyn != null && idInSourceTaxon != null) {
2681 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2682 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2683 inferredGenus.addSource(originalSource);
2684
2685 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2686 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2687 inferredSynName.addSource(originalSource);
2688 originalSource = null;
2689
2690 }else{
2691 logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2692 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2693 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2694 inferredGenus.addSource(originalSource);
2695
2696 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2697 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2698 inferredSynName.addSource(originalSource);
2699 originalSource = null;
2700 }
2701
2702 taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2703
2704 inferredSynName.generateTitle();
2705
2706
2707 return inferredGenus;
2708 }
2709
2710 private Synonym createInferredEpithets(Taxon taxon,
2711 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2712 String epithetOfTaxon, String infragenericEpithetOfTaxon,
2713 String infraspecificEpithetOfTaxon, List<String> taxonNames,
2714 TaxonNameBase parentName, TaxonBase syn) {
2715
2716 Synonym inferredEpithet;
2717 TaxonNameBase<?,?> synName;
2718 ZoologicalName inferredSynName;
2719 HibernateProxyHelper.deproxy(syn);
2720
2721 // Determine the idInSource
2722 String idInSourceSyn = getIdInSource(syn);
2723 String idInSourceTaxon = getIdInSource(taxon);
2724 // Determine the sourceReference
2725 Reference<?> sourceReference = syn.getSec();
2726
2727 if (sourceReference == null){
2728 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2729 sourceReference = taxon.getSec();
2730 }
2731
2732 synName = syn.getName();
2733 ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2734 String synGenusName = zooSynName.getGenusOrUninomial();
2735 String synInfraGenericEpithet = null;
2736 String synSpecificEpithet = null;
2737
2738 if (zooSynName.getInfraGenericEpithet() != null){
2739 synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2740 }
2741
2742 if (zooSynName.isInfraSpecific()){
2743 synSpecificEpithet = zooSynName.getSpecificEpithet();
2744 }
2745
2746 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2747 synonymsGenus.put(synGenusName, idInSource);
2748 }*/
2749
2750 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2751
2752 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2753 if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2754 logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2755 }
2756 inferredSynName.setGenusOrUninomial(synGenusName);
2757
2758 if (parentName.isInfraGeneric()){
2759 inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2760 }
2761 if (taxonName.isSpecies()){
2762 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2763 }else if (taxonName.isInfraSpecific()){
2764 inferredSynName.setSpecificEpithet(synSpecificEpithet);
2765 inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2766 }
2767
2768 inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2769
2770 // Set the sourceReference
2771 inferredEpithet.setSec(sourceReference);
2772
2773 /* Add the original source
2774 if (idInSource != null) {
2775 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2776
2777 // Add the citation
2778 Reference citation = getCitation(syn);
2779 if (citation != null) {
2780 originalSource.setCitation(citation);
2781 inferredEpithet.addSource(originalSource);
2782 }
2783 }*/
2784 String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2785
2786
2787 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2788 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2789
2790 inferredEpithet.addSource(originalSource);
2791
2792 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2793 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2794
2795 inferredSynName.addSource(originalSource);
2796
2797
2798
2799 taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2800
2801 inferredSynName.generateTitle();
2802 return inferredEpithet;
2803 }
2804
2805 /**
2806 * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2807 * Very likely only useful for createInferredSynonyms().
2808 * @param uuid
2809 * @param zooHashMap
2810 * @return
2811 */
2812 private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2813 ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2814 if (taxonName == null) {
2815 taxonName = zooHashMap.get(uuid);
2816 }
2817 return taxonName;
2818 }
2819
2820 /**
2821 * Returns the idInSource for a given Synonym.
2822 * @param syn
2823 */
2824 private String getIdInSource(TaxonBase taxonBase) {
2825 String idInSource = null;
2826 Set<IdentifiableSource> sources = taxonBase.getSources();
2827 if (sources.size() == 1) {
2828 IdentifiableSource source = sources.iterator().next();
2829 if (source != null) {
2830 idInSource = source.getIdInSource();
2831 }
2832 } else if (sources.size() > 1) {
2833 int count = 1;
2834 idInSource = "";
2835 for (IdentifiableSource source : sources) {
2836 idInSource += source.getIdInSource();
2837 if (count < sources.size()) {
2838 idInSource += "; ";
2839 }
2840 count++;
2841 }
2842 } else if (sources.size() == 0){
2843 logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2844 }
2845
2846
2847 return idInSource;
2848 }
2849
2850
2851 /**
2852 * Returns the citation for a given Synonym.
2853 * @param syn
2854 */
2855 private Reference getCitation(Synonym syn) {
2856 Reference citation = null;
2857 Set<IdentifiableSource> sources = syn.getSources();
2858 if (sources.size() == 1) {
2859 IdentifiableSource source = sources.iterator().next();
2860 if (source != null) {
2861 citation = source.getCitation();
2862 }
2863 } else if (sources.size() > 1) {
2864 logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2865 }
2866
2867 return citation;
2868 }
2869
2870 @Override
2871 public List<Synonym> createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2872 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2873
2874 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2875 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2876 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2877
2878 return inferredSynonyms;
2879 }
2880
2881 /* (non-Javadoc)
2882 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listClassifications(eu.etaxonomy.cdm.model.taxon.TaxonBase, java.lang.Integer, java.lang.Integer, java.util.List)
2883 */
2884 @Override
2885 public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2886
2887 // TODO quickly implemented, create according dao !!!!
2888 Set<TaxonNode> nodes = new HashSet<TaxonNode>();
2889 Set<Classification> classifications = new HashSet<Classification>();
2890 List<Classification> list = new ArrayList<Classification>();
2891
2892 if (taxonBase == null) {
2893 return list;
2894 }
2895
2896 taxonBase = load(taxonBase.getUuid());
2897
2898 if (taxonBase instanceof Taxon) {
2899 nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
2900 } else {
2901 for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
2902 nodes.addAll(taxon.getTaxonNodes());
2903 }
2904 }
2905 for (TaxonNode node : nodes) {
2906 classifications.add(node.getClassification());
2907 }
2908 list.addAll(classifications);
2909 return list;
2910 }
2911
2912
2913
2914
2915
2916
2917 }