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