fixing #4273 (java.lang.ClassCastException in TaxonDaoHibernateImpl.countTaxaByCommo...
[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<Taxon> commonNameResults = dao.getTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas(), configurator.getPageSize(), configurator.getPageNumber(), configurator.getTaxonPropertyPath());
795 taxa.addAll(commonNameResults);
796 }
797 if(taxa != null){
798 results.addAll(taxa);
799 }
800 numberOfResults += numberTaxaResults;
801
802 }
803
804 return new DefaultPagerImpl<IdentifiableEntity>
805 (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
806 }
807
808 public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(){
809 return dao.getUuidAndTitleCache();
810 }
811
812 /* (non-Javadoc)
813 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllMedia(eu.etaxonomy.cdm.model.taxon.Taxon, int, int, int, java.lang.String[])
814 */
815 @Override
816 public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
817 List<MediaRepresentation> medRep = new ArrayList<MediaRepresentation>();
818 taxon = (Taxon)dao.load(taxon.getUuid());
819 Set<TaxonDescription> descriptions = taxon.getDescriptions();
820 for (TaxonDescription taxDesc: descriptions){
821 Set<DescriptionElementBase> elements = taxDesc.getElements();
822 for (DescriptionElementBase descElem: elements){
823 for(Media media : descElem.getMedia()){
824
825 //find the best matching representation
826 medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
827
828 }
829 }
830 }
831 return medRep;
832 }
833
834 /* (non-Javadoc)
835 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listTaxonDescriptionMedia(eu.etaxonomy.cdm.model.taxon.Taxon, boolean)
836 */
837 @Override
838 public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
839 return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
840 }
841
842
843 /* (non-Javadoc)
844 * @see eu.etaxonomy.cdm.api.service.ITaxonService#listMedia(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.Set, boolean, java.util.List)
845 */
846 @Override
847 public List<Media> listMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships,
848 Boolean limitToGalleries, Boolean includeTaxonDescriptions, Boolean includeOccurrences,
849 Boolean includeTaxonNameDescriptions, List<String> propertyPath) {
850
851 logger.trace("listMedia() - START");
852
853 Set<Taxon> taxa = new HashSet<Taxon>();
854 List<Media> taxonMedia = new ArrayList<Media>();
855
856 if (limitToGalleries == null) {
857 limitToGalleries = false;
858 }
859
860 // --- resolve related taxa
861 if (includeRelationships != null && ! includeRelationships.isEmpty()) {
862 logger.trace("listMedia() - resolve related taxa");
863 taxa = listRelatedTaxa(taxon, includeRelationships, null, null, null, null);
864 }
865
866 taxa.add((Taxon) dao.load(taxon.getUuid()));
867
868 if(includeTaxonDescriptions != null && includeTaxonDescriptions){
869 logger.trace("listMedia() - includeTaxonDescriptions");
870 List<TaxonDescription> taxonDescriptions = new ArrayList<TaxonDescription>();
871 // --- TaxonDescriptions
872 for (Taxon t : taxa) {
873 taxonDescriptions.addAll(descriptionService.listTaxonDescriptions(t, null, null, null, null, propertyPath));
874 }
875 for (TaxonDescription taxonDescription : taxonDescriptions) {
876 if (!limitToGalleries || taxonDescription.isImageGallery()) {
877 for (DescriptionElementBase element : taxonDescription.getElements()) {
878 for (Media media : element.getMedia()) {
879 taxonMedia.add(media);
880 }
881 }
882 }
883 }
884 }
885
886
887 if(includeOccurrences != null && includeOccurrences) {
888 logger.trace("listMedia() - includeOccurrences");
889 Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<SpecimenOrObservationBase>();
890 // --- Specimens
891 for (Taxon t : taxa) {
892 specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
893 }
894 for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
895
896 // direct media removed from specimen #3597
897 // taxonMedia.addAll(occurrence.getMedia());
898
899 // SpecimenDescriptions
900 Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
901 for (DescriptionBase specimenDescription : specimenDescriptions) {
902 if (!limitToGalleries || specimenDescription.isImageGallery()) {
903 Set<DescriptionElementBase> elements = specimenDescription.getElements();
904 for (DescriptionElementBase element : elements) {
905 for (Media media : element.getMedia()) {
906 taxonMedia.add(media);
907 }
908 }
909 }
910 }
911
912 // Collection
913 //TODO why may collections have media attached? #
914 if (occurrence.isInstanceOf(DerivedUnit.class)) {
915 DerivedUnit derivedUnit = CdmBase.deproxy(occurrence, DerivedUnit.class);
916 if (derivedUnit.getCollection() != null){
917 taxonMedia.addAll(derivedUnit.getCollection().getMedia());
918 }
919 }
920
921 // pherograms & gelPhotos
922 if (occurrence.isInstanceOf(DnaSample.class)) {
923 DnaSample dnaSample = CdmBase.deproxy(occurrence, DnaSample.class);
924 Set<Sequence> sequences = dnaSample.getSequences();
925 //we do show only those gelPhotos which lead to a consensus sequence
926 for (Sequence sequence : sequences) {
927 Set<Media> dnaRelatedMedia = new HashSet<Media>();
928 for (SingleRead singleRead : sequence.getSingleReads()){
929 Amplification amplification = singleRead.getAmplification();
930 dnaRelatedMedia.add(amplification.getGelPhoto());
931 dnaRelatedMedia.add(singleRead.getPherogram());
932 dnaRelatedMedia.remove(null);
933 }
934 taxonMedia.addAll(dnaRelatedMedia);
935 }
936 }
937
938 }
939 }
940
941 if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
942 logger.trace("listMedia() - includeTaxonNameDescriptions");
943 // --- TaxonNameDescription
944 Set<TaxonNameDescription> nameDescriptions = new HashSet<TaxonNameDescription>();
945 for (Taxon t : taxa) {
946 nameDescriptions .addAll(t.getName().getDescriptions());
947 }
948 for(TaxonNameDescription nameDescription: nameDescriptions){
949 if (!limitToGalleries || nameDescription.isImageGallery()) {
950 Set<DescriptionElementBase> elements = nameDescription.getElements();
951 for (DescriptionElementBase element : elements) {
952 for (Media media : element.getMedia()) {
953 taxonMedia.add(media);
954 }
955 }
956 }
957 }
958 }
959
960
961 logger.trace("listMedia() - initialize");
962 beanInitializer.initializeAll(taxonMedia, propertyPath);
963
964 logger.trace("listMedia() - END");
965
966 return taxonMedia;
967 }
968
969 /* (non-Javadoc)
970 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaByID(java.util.Set)
971 */
972 @Override
973 public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
974 return this.dao.listByIds(listOfIDs, null, null, null, null);
975 }
976
977 /* (non-Javadoc)
978 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxonByUuid(UUID uuid, List<String> propertyPaths)
979 */
980 @Override
981 public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
982 return this.dao.findByUuid(uuid, null ,propertyPaths);
983 }
984
985 /* (non-Javadoc)
986 * @see eu.etaxonomy.cdm.api.service.ITaxonService#countAllRelationships()
987 */
988 @Override
989 public int countAllRelationships() {
990 return this.dao.countAllRelationships();
991 }
992
993
994
995
996 /* (non-Javadoc)
997 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNames(java.util.List)
998 */
999 @Override
1000 public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
1001 return this.dao.findIdenticalTaxonNames(propertyPath);
1002 }
1003
1004
1005 /* (non-Javadoc)
1006 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator)
1007 */
1008 @Override
1009 public String deleteTaxon(Taxon taxon, TaxonDeletionConfigurator config, Classification classification) {
1010 if (config == null){
1011 config = new TaxonDeletionConfigurator();
1012 }
1013
1014 List<String> referencedObjects = isDeletable(taxon, config);
1015
1016 if (referencedObjects.isEmpty()){
1017 // --- DeleteSynonymRelations
1018 if (config.isDeleteSynonymRelations()){
1019 boolean removeSynonymNameFromHomotypicalGroup = false;
1020 // use tmp Set to avoid concurrent modification
1021 Set<SynonymRelationship> synRelsToDelete = new HashSet<SynonymRelationship>();
1022 synRelsToDelete.addAll(taxon.getSynonymRelations());
1023 for (SynonymRelationship synRel : synRelsToDelete){
1024 Synonym synonym = synRel.getSynonym();
1025 // taxon.removeSynonymRelation will set the accepted taxon and the synonym to NULL
1026 // this will cause hibernate to delete the relationship since
1027 // the SynonymRelationship field on both is annotated with removeOrphan
1028 // so no further explicit deleting of the relationship should be done here
1029 taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
1030
1031 // --- DeleteSynonymsIfPossible
1032 if (config.isDeleteSynonymsIfPossible()){
1033 //TODO which value
1034 boolean newHomotypicGroupIfNeeded = true;
1035 SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
1036 deleteSynonym(synonym, taxon, synConfig);
1037 }
1038 // relationship will be deleted by hibernate automatically,
1039 // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797
1040 // else{
1041 // deleteSynonymRelationships(synonym, taxon);
1042 // }
1043 }
1044 }
1045
1046 // --- DeleteTaxonRelationships
1047 if (! config.isDeleteTaxonRelationships()){
1048 if (taxon.getTaxonRelations().size() > 0){
1049 String message = "Taxon can't be deleted as it is related to another taxon. " +
1050 "Remove taxon from all relations to other taxa prior to deletion.";
1051 // throw new ReferencedObjectUndeletableException(message);
1052 }
1053 } else{
1054 for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
1055 if (config.isDeleteMisappliedNamesAndInvalidDesignations()){
1056 if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){
1057 if (taxon.equals(taxRel.getToTaxon())){
1058 this.deleteTaxon(taxRel.getFromTaxon(), config, classification);
1059 }
1060 }
1061 }
1062 taxon.removeTaxonRelation(taxRel);
1063 /*if (taxFrom.equals(taxon)){
1064 try{
1065 this.deleteTaxon(taxTo, taxConf, classification);
1066 } catch(DataChangeNoRollbackException e){
1067 logger.debug("A related taxon will not be deleted." + e.getMessage());
1068 }
1069 } else {
1070 try{
1071 this.deleteTaxon(taxFrom, taxConf, classification);
1072 } catch(DataChangeNoRollbackException e){
1073 logger.debug("A related taxon will not be deleted." + e.getMessage());
1074 }
1075
1076 }*/
1077 }
1078 }
1079
1080 // TaxonDescription
1081 if (config.isDeleteDescriptions()){
1082 Set<TaxonDescription> descriptions = taxon.getDescriptions();
1083 List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1084 for (TaxonDescription desc: descriptions){
1085 //TODO use description delete configurator ?
1086 //FIXME check if description is ALWAYS deletable
1087 if (desc.getDescribedSpecimenOrObservation() != null){
1088 String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1089 " which also describes specimens or abservations";
1090 //throw new ReferencedObjectUndeletableException(message);
1091 }
1092 removeDescriptions.add(desc);
1093 descriptionService.delete(desc);
1094
1095 }
1096 for (TaxonDescription desc: removeDescriptions){
1097 taxon.removeDescription(desc);
1098 }
1099 }
1100
1101
1102 /* //check references with only reverse mapping
1103 String message = checkForReferences(taxon);
1104 if (message != null){
1105 //throw new ReferencedObjectUndeletableException(message.toString());
1106 }*/
1107
1108 if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null )){
1109 //if (taxon.getTaxonNodes().size() > 0){
1110 // 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.";
1111 // throw new ReferencedObjectUndeletableException(message);
1112 //}
1113 }else{
1114 if (taxon.getTaxonNodes().size() != 0){
1115 Set<TaxonNode> nodes = taxon.getTaxonNodes();
1116 Iterator<TaxonNode> iterator = nodes.iterator();
1117 TaxonNode node = null;
1118 boolean deleteChildren;
1119 if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1120 deleteChildren = true;
1121 }else {
1122 deleteChildren = false;
1123 }
1124 boolean success = true;
1125 if (!config.isDeleteInAllClassifications() && !(classification == null)){
1126 while (iterator.hasNext()){
1127 node = iterator.next();
1128 if (node.getClassification().equals(classification)){
1129 break;
1130 }
1131 node = null;
1132 }
1133 if (node != null){
1134 success =taxon.removeTaxonNode(node, deleteChildren);
1135 nodeService.delete(node);
1136 } else {
1137 // message = "Taxon is not used in defined classification";
1138 // throw new DataChangeNoRollbackException(message);
1139 }
1140 } else if (config.isDeleteInAllClassifications()){
1141 Set<ITaxonTreeNode> nodesList = new HashSet<ITaxonTreeNode>();
1142 nodesList.addAll(taxon.getTaxonNodes());
1143
1144 for (ITaxonTreeNode treeNode: nodesList){
1145 TaxonNode taxonNode = (TaxonNode) treeNode;
1146 if(!deleteChildren){
1147 /* Object[] childNodes = taxonNode.getChildNodes().toArray();
1148 //nodesList.addAll(taxonNode.getChildNodes());
1149 for (Object childNode: childNodes){
1150 TaxonNode childNodeCast = (TaxonNode) childNode;
1151 deleteTaxon(childNodeCast.getTaxon(), config, classification);
1152
1153 }
1154
1155 /*for (TaxonNode childNode: taxonNode.getChildNodes()){
1156 deleteTaxon(childNode.getTaxon(), config, classification);
1157
1158 }
1159 // taxon.removeTaxonNode(taxonNode);
1160 //nodeService.delete(taxonNode);
1161 } else{
1162 */
1163 Object[] childNodes = taxonNode.getChildNodes().toArray();
1164 for (Object childNode: childNodes){
1165 TaxonNode childNodeCast = (TaxonNode) childNode;
1166 taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1167 }
1168
1169 //taxon.removeTaxonNode(taxonNode);
1170 }
1171 }
1172 config.getTaxonNodeConfig().setDeleteTaxon(false);
1173 nodeService.deleteTaxonNodes(nodesList, config);
1174 }
1175 if (!success){
1176 // message = "The taxon node could not be deleted.";
1177 //throw new DataChangeNoRollbackException(message);
1178 }
1179 }
1180 }
1181
1182
1183 //PolytomousKey TODO
1184
1185 boolean usedInPolytomousKey = checkForPolytomousKeys(taxon);
1186 //TaxonNameBase
1187 if (config.isDeleteNameIfPossible()){
1188
1189
1190 //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1191 TaxonNameBase name = (TaxonNameBase)HibernateProxyHelper.deproxy(taxon.getName());
1192 //check whether taxon will be deleted or not
1193 if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){
1194 taxon = (Taxon) HibernateProxyHelper.deproxy(taxon);
1195 name.removeTaxonBase(taxon);
1196 nameService.merge(name);
1197 String uuidString = nameService.delete(name, config.getNameDeletionConfig());
1198 logger.debug(uuidString);
1199 }
1200
1201 }
1202
1203 // TaxonDescription
1204 /* Set<TaxonDescription> descriptions = taxon.getDescriptions();
1205
1206 for (TaxonDescription desc: descriptions){
1207 if (config.isDeleteDescriptions()){
1208 //TODO use description delete configurator ?
1209 //FIXME check if description is ALWAYS deletable
1210 taxon.removeDescription(desc);
1211 descriptionService.delete(desc);
1212 }else{
1213 if (desc.getDescribedSpecimenOrObservations().size()>0){
1214 String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1215 " which also describes specimens or observations";
1216 throw new ReferencedObjectUndeletableException(message);
1217 }
1218 }
1219 }*/
1220
1221 if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) ){
1222 UUID uuid = dao.delete(taxon);
1223 return uuid.toString();
1224 } else {
1225 return "The Taxon can't be deleted.";
1226 }
1227 }else {
1228 return referencedObjects.toString();
1229 }
1230
1231
1232 }
1233
1234 private String checkForReferences(Taxon taxon){
1235 Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1236 for (CdmBase referencingObject : referencingObjects){
1237 //IIdentificationKeys (Media, Polytomous, MultiAccess)
1238 if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1239 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";
1240
1241 return message;
1242 }
1243
1244
1245 /* //PolytomousKeyNode
1246 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1247 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1248 return message;
1249 }*/
1250
1251 //TaxonInteraction
1252 if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1253 String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1254 return message;
1255 }
1256
1257 //TaxonInteraction
1258 if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1259 String message = "Taxon can't be deleted as it is used in a determination event";
1260 return message;
1261 }
1262
1263 }
1264
1265 referencingObjects = null;
1266 return null;
1267 }
1268
1269 private boolean checkForPolytomousKeys(Taxon taxon){
1270 boolean result = false;
1271 List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1272 if (!list.isEmpty()) {
1273 result = true;
1274 }
1275 return result;
1276 }
1277
1278 @Transactional(readOnly = false)
1279 public UUID delete(Synonym syn){
1280 UUID result = syn.getUuid();
1281 this.deleteSynonym(syn, null);
1282 return result;
1283 }
1284
1285 /* (non-Javadoc)
1286 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1287 */
1288 @Transactional(readOnly = false)
1289 @Override
1290 public String deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1291 return deleteSynonym(synonym, null, config);
1292
1293 }
1294
1295
1296 /* (non-Javadoc)
1297 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1298 */
1299 @Transactional(readOnly = false)
1300 @Override
1301 public String deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1302 if (synonym == null){
1303 return null;
1304 }
1305
1306 if (config == null){
1307 config = new SynonymDeletionConfigurator();
1308 }
1309 List<String> messages = isDeletable(synonym, config);
1310 if (messages.isEmpty()){
1311 synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
1312
1313 //remove synonymRelationship
1314 Set<Taxon> taxonSet = new HashSet<Taxon>();
1315 if (taxon != null){
1316 taxonSet.add(taxon);
1317 }else{
1318 taxonSet.addAll(synonym.getAcceptedTaxa());
1319 }
1320 for (Taxon relatedTaxon : taxonSet){
1321 // dao.deleteSynonymRelationships(synonym, relatedTaxon);
1322 relatedTaxon.removeSynonym(synonym, config.isNewHomotypicGroupIfNeeded());
1323 }
1324 this.saveOrUpdate(synonym);
1325
1326 //TODO remove name from homotypical group?
1327
1328 //remove synonym (if necessary)
1329
1330 UUID uuid = null;
1331 if (synonym.getSynonymRelations().isEmpty()){
1332 TaxonNameBase<?,?> name = synonym.getName();
1333 synonym.setName(null);
1334 uuid = dao.delete(synonym);
1335
1336 //remove name if possible (and required)
1337 if (name != null && config.isDeleteNameIfPossible()){
1338
1339 nameService.delete(name, config.getNameDeletionConfig());
1340
1341 }
1342
1343 }else {
1344 return null;
1345 }
1346 return uuid.toString();
1347 }else{
1348 return messages.toString();
1349 }
1350
1351
1352 }
1353
1354
1355 /* (non-Javadoc)
1356 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNameIds(java.util.List)
1357 */
1358 @Override
1359 public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1360
1361 return this.dao.findIdenticalNamesNew(propertyPath);
1362 }
1363
1364 /* (non-Javadoc)
1365 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getPhylumName(eu.etaxonomy.cdm.model.name.TaxonNameBase)
1366 */
1367 @Override
1368 public String getPhylumName(TaxonNameBase name){
1369 return this.dao.getPhylumName(name);
1370 }
1371
1372 /* (non-Javadoc)
1373 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
1374 */
1375 @Override
1376 public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1377 return dao.deleteSynonymRelationships(syn, taxon);
1378 }
1379
1380 /* (non-Javadoc)
1381 * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym)
1382 */
1383 @Override
1384 public long deleteSynonymRelationships(Synonym syn) {
1385 return dao.deleteSynonymRelationships(syn, null);
1386 }
1387
1388
1389 /* (non-Javadoc)
1390 * @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)
1391 */
1392 @Override
1393 public List<SynonymRelationship> listSynonymRelationships(
1394 TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1395 List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1396 Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1397
1398 List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1399 if(numberOfResults > 0) { // no point checking again
1400 results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1401 }
1402 return results;
1403 }
1404
1405 /* (non-Javadoc)
1406 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingTaxon(java.lang.String)
1407 */
1408 @Override
1409 public Taxon findBestMatchingTaxon(String taxonName) {
1410 MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1411 config.setTaxonNameTitle(taxonName);
1412 return findBestMatchingTaxon(config);
1413 }
1414
1415
1416
1417 @Override
1418 public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1419
1420 Taxon bestCandidate = null;
1421 try{
1422 // 1. search for acceptet taxa
1423 List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1424 boolean bestCandidateMatchesSecUuid = false;
1425 boolean bestCandidateIsInClassification = false;
1426 int countEqualCandidates = 0;
1427 for(TaxonBase taxonBaseCandidate : taxonList){
1428 if(taxonBaseCandidate instanceof Taxon){
1429 Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1430 boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1431 if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1432 continue;
1433 }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1434 bestCandidate = newCanditate;
1435 countEqualCandidates = 1;
1436 bestCandidateMatchesSecUuid = true;
1437 continue;
1438 }
1439
1440 boolean newCandidateInClassification = isInClassification(newCanditate, config);
1441 if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1442 continue;
1443 }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1444 bestCandidate = newCanditate;
1445 countEqualCandidates = 1;
1446 bestCandidateIsInClassification = true;
1447 continue;
1448 }
1449 if (bestCandidate == null){
1450 bestCandidate = newCanditate;
1451 countEqualCandidates = 1;
1452 continue;
1453 }
1454
1455 }else{ //not Taxon.class
1456 continue;
1457 }
1458 countEqualCandidates++;
1459
1460 }
1461 if (bestCandidate != null){
1462 if(countEqualCandidates > 1){
1463 logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1464 return bestCandidate;
1465 } else {
1466 logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1467 return bestCandidate;
1468 }
1469 }
1470
1471
1472 // 2. search for synonyms
1473 if (config.isIncludeSynonyms()){
1474 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1475 for(TaxonBase taxonBase : synonymList){
1476 if(taxonBase instanceof Synonym){
1477 Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1478 Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1479 if(!acceptetdCandidates.isEmpty()){
1480 bestCandidate = acceptetdCandidates.iterator().next();
1481 if(acceptetdCandidates.size() == 1){
1482 logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1483 return bestCandidate;
1484 } else {
1485 logger.info("using accepted Taxon " + bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1486 return bestCandidate;
1487 }
1488 //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1489 }
1490 }
1491 }
1492 }
1493
1494 } catch (Exception e){
1495 logger.error(e);
1496 e.printStackTrace();
1497 }
1498
1499 return bestCandidate;
1500 }
1501
1502 private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1503 UUID configClassificationUuid = config.getClassificationUuid();
1504 if (configClassificationUuid == null){
1505 return false;
1506 }
1507 for (TaxonNode node : taxon.getTaxonNodes()){
1508 UUID classUuid = node.getClassification().getUuid();
1509 if (configClassificationUuid.equals(classUuid)){
1510 return true;
1511 }
1512 }
1513 return false;
1514 }
1515
1516 private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1517 UUID configSecUuid = config.getSecUuid();
1518 if (configSecUuid == null){
1519 return false;
1520 }
1521 UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1522 return configSecUuid.equals(taxonSecUuid);
1523 }
1524
1525 /* (non-Javadoc)
1526 * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingSynonym(java.lang.String)
1527 */
1528 @Override
1529 public Synonym findBestMatchingSynonym(String taxonName) {
1530 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1531 if(! synonymList.isEmpty()){
1532 Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1533 if(synonymList.size() == 1){
1534 logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1535 return result;
1536 } else {
1537 logger.info("Several matching synonyms found. Using first: " + result.getTitleCache());
1538 return result;
1539 }
1540 }
1541 return null;
1542 }
1543
1544
1545 /* (non-Javadoc)
1546 * @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)
1547 */
1548 @Override
1549 public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, Taxon newTaxon, boolean moveHomotypicGroup,
1550 SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
1551
1552 Synonym synonym = oldSynonymRelation.getSynonym();
1553 Taxon fromTaxon = oldSynonymRelation.getAcceptedTaxon();
1554 //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1555 TaxonNameBase<?,?> synonymName = synonym.getName();
1556 TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1557 //set default relationship type
1558 if (newSynonymRelationshipType == null){
1559 newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1560 }
1561 boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1562
1563 HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1564 int hgSize = homotypicGroup.getTypifiedNames().size();
1565 boolean isSingleInGroup = !(hgSize > 1);
1566
1567 if (! isSingleInGroup){
1568 boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1569 boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1570 if (isHomotypicToAccepted){
1571 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.";
1572 String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1573 message = String.format(message, homotypicRelatives);
1574 throw new HomotypicalGroupChangeException(message);
1575 }
1576 if (! moveHomotypicGroup){
1577 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.";
1578 throw new HomotypicalGroupChangeException(message);
1579 }
1580 }else{
1581 moveHomotypicGroup = true; //single synonym always allows to moveCompleteGroup
1582 }
1583 // Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1584
1585 SynonymRelationship result = null;
1586 //move all synonyms to new taxon
1587 List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1588 for (Synonym syn: homotypicSynonyms){
1589 Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1590 for (SynonymRelationship synRelation : synRelations){
1591 if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1592 Reference<?> newReference = reference;
1593 if (newReference == null && keepReference){
1594 newReference = synRelation.getCitation();
1595 }
1596 String newRefDetail = referenceDetail;
1597 if (newRefDetail == null && keepReference){
1598 newRefDetail = synRelation.getCitationMicroReference();
1599 }
1600 SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1601 fromTaxon.removeSynonymRelation(synRelation, false);
1602 //
1603 //change homotypic group of synonym if relType is 'homotypic'
1604 // if (newRelTypeIsHomotypic){
1605 // newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1606 // }
1607 //set result
1608 if (synRelation.equals(oldSynonymRelation)){
1609 result = newSynRelation;
1610 }
1611 }
1612 }
1613
1614 }
1615 saveOrUpdate(newTaxon);
1616 //Assert that there is a result
1617 if (result == null){
1618 String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1619 throw new IllegalStateException(message);
1620 }
1621 return result;
1622 }
1623
1624 /* (non-Javadoc)
1625 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheTaxon()
1626 */
1627 @Override
1628 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1629 return dao.getUuidAndTitleCacheTaxon();
1630 }
1631
1632 /* (non-Javadoc)
1633 * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheSynonym()
1634 */
1635 @Override
1636 public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1637 return dao.getUuidAndTitleCacheSynonym();
1638 }
1639
1640 /* (non-Javadoc)
1641 * @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)
1642 */
1643 @Override
1644 public Pager<SearchResult<TaxonBase>> findByFullText(
1645 Class<? extends TaxonBase> clazz, String queryString,
1646 Classification classification, List<Language> languages,
1647 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1648
1649
1650 LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1651
1652 // --- execute search
1653 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1654
1655 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1656 idFieldMap.put(CdmBaseType.TAXON, "id");
1657
1658 // --- initialize taxa, thighlight matches ....
1659 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1660 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1661 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1662
1663 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1664 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1665 }
1666
1667 @Override
1668 public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTermBase<?>> statusFilter,
1669 Classification classification,
1670 Integer pageSize, Integer pageNumber,
1671 List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1672
1673 LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1674
1675 // --- execute search
1676 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1677
1678 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1679 idFieldMap.put(CdmBaseType.TAXON, "id");
1680
1681 // --- initialize taxa, thighlight matches ....
1682 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1683 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1684 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1685
1686 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1687 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1688 }
1689
1690 /**
1691 * @param clazz
1692 * @param queryString
1693 * @param classification
1694 * @param languages
1695 * @param highlightFragments
1696 * @param sortFields TODO
1697 * @param directorySelectClass
1698 * @return
1699 */
1700 protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1701 boolean highlightFragments, SortField[] sortFields) {
1702 BooleanQuery finalQuery = new BooleanQuery();
1703 BooleanQuery textQuery = new BooleanQuery();
1704
1705 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1706 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1707
1708 if(sortFields == null){
1709 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1710 }
1711 luceneSearch.setSortFields(sortFields);
1712
1713 // ---- search criteria
1714 luceneSearch.setCdmTypRestriction(clazz);
1715
1716 textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1717 textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1718
1719 finalQuery.add(textQuery, Occur.MUST);
1720
1721 if(classification != null){
1722 finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1723 }
1724 luceneSearch.setQuery(finalQuery);
1725
1726 if(highlightFragments){
1727 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1728 }
1729 return luceneSearch;
1730 }
1731
1732 /**
1733 * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1734 * the BlockJoinQuery could be used. The latter might be more memory save but has the
1735 * drawback of requiring to do the join an indexing time.
1736 * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1737 *
1738 * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1739 * <ul>
1740 * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1741 * <li>inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1742 * <ul>
1743 * @param queryString
1744 * @param classification
1745 * @param languages
1746 * @param highlightFragments
1747 * @param sortFields TODO
1748 *
1749 * @return
1750 * @throws IOException
1751 */
1752 protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1753 boolean highlightFragments, SortField[] sortFields) throws IOException {
1754
1755 String fromField;
1756 String queryTermField;
1757 String toField = "id"; // TaxonBase.uuid
1758
1759 if(edge.isBidirectional()){
1760 throw new RuntimeException("Bidirectional joining not supported!");
1761 }
1762 if(edge.isEvers()){
1763 fromField = "relatedFrom.id";
1764 queryTermField = "relatedFrom.titleCache";
1765 } else if(edge.isInvers()) {
1766 fromField = "relatedTo.id";
1767 queryTermField = "relatedTo.titleCache";
1768 } else {
1769 throw new RuntimeException("Invalid direction: " + edge.getDirections());
1770 }
1771
1772 BooleanQuery finalQuery = new BooleanQuery();
1773
1774 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1775 QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1776
1777 BooleanQuery joinFromQuery = new BooleanQuery();
1778 joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1779 joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1780 Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1781
1782 if(sortFields == null){
1783 sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
1784 }
1785 luceneSearch.setSortFields(sortFields);
1786
1787 finalQuery.add(joinQuery, Occur.MUST);
1788
1789 if(classification != null){
1790 finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1791 }
1792 luceneSearch.setQuery(finalQuery);
1793
1794 if(highlightFragments){
1795 luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1796 }
1797 return luceneSearch;
1798 }
1799
1800
1801
1802
1803 /* (non-Javadoc)
1804 * @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)
1805 */
1806 @Override
1807 public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1808 EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1809 Set<NamedArea> namedAreas, Set<PresenceAbsenceTermBase<?>> distributionStatus, List<Language> languages,
1810 boolean highlightFragments, Integer pageSize,
1811 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1812 throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1813
1814 // FIXME: allow taxonomic ordering
1815 // 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";
1816 // this require building a special sort column by a special classBridge
1817 if(highlightFragments){
1818 logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1819 "currently not fully supported by this method and thus " +
1820 "may not work with common names and misapplied names.");
1821 }
1822
1823 // convert sets to lists
1824 List<NamedArea> namedAreaList = null;
1825 List<PresenceAbsenceTermBase<?>>distributionStatusList = null;
1826 if(namedAreas != null){
1827 namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1828 namedAreaList.addAll(namedAreas);
1829 }
1830 if(distributionStatus != null){
1831 distributionStatusList = new ArrayList<PresenceAbsenceTermBase<?>>(distributionStatus.size());
1832 distributionStatusList.addAll(distributionStatus);
1833 }
1834
1835 // set default if parameter is null
1836 if(searchModes == null){
1837 searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1838 }
1839
1840 // set sort order and thus override any sort orders which may have been
1841 // defindes by prepare*Search methods
1842 if(orderHints == null){
1843 orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER;
1844 }
1845 SortField[] sortFields = new SortField[orderHints.size()];
1846 int i = 0;
1847 for(OrderHint oh : orderHints){
1848 sortFields[i++] = oh.toSortField();
1849 }
1850 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1851 // SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1852
1853
1854 boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1855
1856 List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1857 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1858
1859 /*
1860 ======== filtering by distribution , HOWTO ========
1861
1862 - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1863 - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1864 add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1865 which will be put into a FilteredQuersy in the end ?
1866
1867
1868 3. how does it work in spatial?
1869 see
1870 - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1871 - http://www.infoq.com/articles/LuceneSpatialSupport
1872 - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1873 ------------------------------------------------------------------------
1874
1875 filter strategies:
1876 A) use a separate distribution filter per index sub-query/search:
1877 - byTaxonSyonym (query TaxaonBase):
1878 use a join area filter (Distribution -> TaxonBase)
1879 - byCommonName (query DescriptionElementBase): use an area filter on
1880 DescriptionElementBase !!! PROBLEM !!!
1881 This cannot work since the distributions are different entities than the
1882 common names and thus these are different lucene documents.
1883 - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1884 use a join area filter (Distribution -> TaxonBase)
1885
1886 B) use a common distribution filter for all index sub-query/searches:
1887 - use a common join area filter (Distribution -> TaxonBase)
1888 - also implement the byCommonName as join query (CommonName -> TaxonBase)
1889 PROBLEM in this case: we are losing the fragment highlighting for the
1890 common names, since the returned documents are always TaxonBases
1891 */
1892
1893 /* The QueryFactory for creating filter queries on Distributions should
1894 * The query factory used for the common names query cannot be reused
1895 * for this case, since we want to only record the text fields which are
1896 * actually used in the primary query
1897 */
1898 QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1899
1900 BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1901
1902
1903 // search for taxa or synonyms
1904 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1905 Class taxonBaseSubclass = TaxonBase.class;
1906 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1907 taxonBaseSubclass = Taxon.class;
1908 } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1909 taxonBaseSubclass = Synonym.class;
1910 }
1911 luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1912 idFieldMap.put(CdmBaseType.TAXON, "id");
1913 /* A) does not work!!!!
1914 if(addDistributionFilter){
1915 // in this case we need a filter which uses a join query
1916 // to get the TaxonBase documents for the DescriptionElementBase documents
1917 // which are matching the areas in question
1918 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1919 namedAreaList,
1920 distributionStatusList,
1921 distributionFilterQueryFactory
1922 );
1923 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1924 }
1925 */
1926 if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1927 // add additional area filter for synonyms
1928 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1929 String toField = "accTaxon.id"; // id in TaxonBase index
1930
1931 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1932
1933 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1934 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1935
1936 }
1937 }
1938
1939 // search by CommonTaxonName
1940 if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1941 // B)
1942 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1943 Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1944 "inDescription.taxon.id",
1945 "id",
1946 QueryFactory.addTypeRestriction(
1947 createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
1948 , CommonTaxonName.class
1949 ),
1950 CommonTaxonName.class);
1951 logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
1952 LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
1953 byCommonNameSearch.setCdmTypRestriction(Taxon.class);
1954 byCommonNameSearch.setQuery(byCommonNameJoinQuery);
1955 byCommonNameSearch.setSortFields(sortFields);
1956 idFieldMap.put(CdmBaseType.TAXON, "id");
1957
1958 luceneSearches.add(byCommonNameSearch);
1959
1960 /* A) does not work!!!!
1961 luceneSearches.add(
1962 prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1963 queryString, classification, null, languages, highlightFragments)
1964 );
1965 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1966 if(addDistributionFilter){
1967 // in this case we are able to use DescriptionElementBase documents
1968 // which are matching the areas in question directly
1969 BooleanQuery byDistributionQuery = createByDistributionQuery(
1970 namedAreaList,
1971 distributionStatusList,
1972 distributionFilterQueryFactory
1973 );
1974 multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1975 } */
1976 }
1977
1978 // search by misapplied names
1979 if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
1980 // NOTE:
1981 // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1982 // which allows doing query time joins
1983 // finds the misapplied name (Taxon B) which is an misapplication for
1984 // a related Taxon A.
1985 //
1986 luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
1987 new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
1988 queryString, classification, languages, highlightFragments, sortFields));
1989 idFieldMap.put(CdmBaseType.TAXON, "id");
1990
1991 if(addDistributionFilter){
1992 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1993
1994 /*
1995 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
1996 * Maybe this is a but in java itself java.
1997 *
1998 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1999 * directly:
2000 *
2001 * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2002 *
2003 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2004 * will execute as expected:
2005 *
2006 * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2007 * String toField = "relation." + misappliedNameForUuid +".to.id";
2008 *
2009 * Comparing both strings by the String.equals method returns true, so both String are identical.
2010 *
2011 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2012 * 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)
2013 * The bug is persistent after a reboot of the development computer.
2014 */
2015 // String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2016 // String toField = "relation." + misappliedNameForUuid +".to.id";
2017 String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2018 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2019 // System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2020
2021 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2022 Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2023 QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery);
2024
2025 // debug code for bug described above
2026 DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2027 // System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2028
2029 multiIndexByAreaFilter.add(filter, Occur.SHOULD);
2030 }
2031 }
2032
2033 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
2034 luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
2035
2036
2037 if(addDistributionFilter){
2038
2039 // B)
2040 // in this case we need a filter which uses a join query
2041 // to get the TaxonBase documents for the DescriptionElementBase documents
2042 // which are matching the areas in question
2043 //
2044 // for toTaxa, doByCommonName
2045 Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2046 namedAreaList,
2047 distributionStatusList,
2048 distributionFilterQueryFactory
2049 );
2050 multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
2051 }
2052
2053 if (addDistributionFilter){
2054 multiSearch.setFilter(multiIndexByAreaFilter);
2055 }
2056
2057
2058 // --- execute search
2059 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2060
2061 // --- initialize taxa, highlight matches ....
2062 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2063
2064
2065 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2066 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2067
2068 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2069 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2070 }
2071
2072 /**
2073 * @param namedAreaList at least one area must be in the list
2074 * @param distributionStatusList optional
2075 * @return
2076 * @throws IOException
2077 */
2078 protected Query createByDistributionJoinQuery(
2079 List<NamedArea> namedAreaList,
2080 List<PresenceAbsenceTermBase<?>> distributionStatusList,
2081 QueryFactory queryFactory
2082 ) throws IOException {
2083
2084 String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2085 String toField = "id"; // id in TaxonBase index
2086
2087 BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2088
2089 Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2090
2091 return taxonAreaJoinQuery;
2092 }
2093
2094 /**
2095 * @param namedAreaList
2096 * @param distributionStatusList
2097 * @param queryFactory
2098 * @return
2099 */
2100 private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2101 List<PresenceAbsenceTermBase<?>> distributionStatusList, QueryFactory queryFactory) {
2102 BooleanQuery areaQuery = new BooleanQuery();
2103 // area field from Distribution
2104 areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2105
2106 // status field from Distribution
2107 if(distributionStatusList != null && distributionStatusList.size() > 0){
2108 areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2109 }
2110
2111 logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2112 return areaQuery;
2113 }
2114
2115 /**
2116 * This method has been primarily created for testing the area join query but might
2117 * also be useful in other situations
2118 *
2119 * @param namedAreaList
2120 * @param distributionStatusList
2121 * @param classification
2122 * @param highlightFragments
2123 * @return
2124 * @throws IOException
2125 */
2126 protected LuceneSearch prepareByDistributionSearch(
2127 List<NamedArea> namedAreaList, List<PresenceAbsenceTermBase<?>> distributionStatusList,
2128 Classification classification) throws IOException {
2129
2130 BooleanQuery finalQuery = new BooleanQuery();
2131
2132 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2133
2134 // FIXME is this query factory using the wrong type?
2135 QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2136
2137 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
2138 luceneSearch.setSortFields(sortFields);
2139
2140
2141 Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory);
2142
2143 finalQuery.add(byAreaQuery, Occur.MUST);
2144
2145 if(classification != null){
2146 finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2147 }
2148
2149 logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2150 luceneSearch.setQuery(finalQuery);
2151
2152 return luceneSearch;
2153 }
2154
2155
2156
2157 /* (non-Javadoc)
2158 * @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)
2159 */
2160 @Override
2161 public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2162 Class<? extends DescriptionElementBase> clazz, String queryString,
2163 Classification classification, List<Feature> features, List<Language> languages,
2164 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2165
2166
2167 LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2168
2169 // --- execute search
2170 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2171
2172 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2173 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2174
2175 // --- initialize taxa, highlight matches ....
2176 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2177 @SuppressWarnings("rawtypes")
2178 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2179 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2180
2181 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2182 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2183
2184 }
2185
2186
2187 @Override
2188 public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2189 Classification classification, List<Language> languages, boolean highlightFragments,
2190 Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2191
2192 LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2193 LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2194
2195 LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2196
2197 // --- execute search
2198 TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2199
2200 // --- initialize taxa, highlight matches ....
2201 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2202
2203 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2204 idFieldMap.put(CdmBaseType.TAXON, "id");
2205 idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2206
2207 List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2208 topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2209
2210 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2211 return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2212
2213 }
2214
2215
2216 /**
2217 * @param clazz
2218 * @param queryString
2219 * @param classification
2220 * @param features
2221 * @param languages
2222 * @param highlightFragments
2223 * @param directorySelectClass
2224 * @return
2225 */
2226 protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2227 String queryString, Classification classification, List<Feature> features,
2228 List<Language> languages, boolean highlightFragments) {
2229
2230 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2231 QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2232
2233 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.STRING, false)};
2234
2235 BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2236 languages, descriptionElementQueryFactory);
2237
2238 luceneSearch.setSortFields(sortFields);
2239 luceneSearch.setCdmTypRestriction(clazz);
2240 luceneSearch.setQuery(finalQuery);
2241 if(highlightFragments){
2242 luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2243 }
2244
2245 return luceneSearch;
2246 }
2247
2248 /**
2249 * @param queryString
2250 * @param classification
2251 * @param features
2252 * @param languages
2253 * @param descriptionElementQueryFactory
2254 * @return
2255 */
2256 private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2257 List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2258 BooleanQuery finalQuery = new BooleanQuery();
2259 BooleanQuery textQuery = new BooleanQuery();
2260 textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2261
2262 // common name
2263 Query nameQuery;
2264 if(languages == null || languages.size() == 0){
2265 nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString);
2266 } else {
2267 nameQuery = new BooleanQuery();
2268 BooleanQuery languageSubQuery = new BooleanQuery();
2269 for(Language lang : languages){
2270 languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD);
2271 }
2272 ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2273 ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
2274 }
2275 textQuery.add(nameQuery, Occur.SHOULD);
2276
2277
2278 // text field from TextData
2279 textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2280
2281 // --- TermBase fields - by representation ----
2282 // state field from CategoricalData
2283 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2284
2285 // state field from CategoricalData
2286 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2287
2288 // area field from Distribution
2289 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2290
2291 // status field from Distribution
2292 textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2293
2294 finalQuery.add(textQuery, Occur.MUST);
2295 // --- classification ----
2296
2297 if(classification != null){
2298 finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2299 }
2300
2301 // --- IdentifieableEntity fields - by uuid
2302 if(features != null && features.size() > 0 ){
2303 finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2304 }
2305
2306 // the description must be associated with a taxon
2307 finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2308
2309 logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2310 return finalQuery;
2311 }
2312
2313 /**
2314 * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2315 * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2316 * This method is a convenient means to retrieve a Lucene query string for such the fields.
2317 *
2318 * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2319 * or {@link MultilanguageTextFieldBridge }
2320 * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2321 * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2322 * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2323 *
2324 * TODO move to utiliy class !!!!!!!!
2325 */
2326 private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2327
2328 if(stringBuilder == null){
2329 stringBuilder = new StringBuilder();
2330 }
2331 if(languages == null || languages.size() == 0){
2332 stringBuilder.append(name + ".ALL:(%1$s) ");
2333 } else {
2334 for(Language lang : languages){
2335 stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2336 }
2337 }
2338 return stringBuilder;
2339 }
2340
2341 @Override
2342 public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2343 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2344 List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2345
2346 HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2347
2348
2349 UUID nameUuid= taxon.getName().getUuid();
2350 ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2351 String epithetOfTaxon = null;
2352 String infragenericEpithetOfTaxon = null;
2353 String infraspecificEpithetOfTaxon = null;
2354 if (taxonName.isSpecies()){
2355 epithetOfTaxon= taxonName.getSpecificEpithet();
2356 } else if (taxonName.isInfraGeneric()){
2357 infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2358 } else if (taxonName.isInfraSpecific()){
2359 infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2360 }
2361 String genusOfTaxon = taxonName.getGenusOrUninomial();
2362 Set<TaxonNode> nodes = taxon.getTaxonNodes();
2363 List<String> taxonNames = new ArrayList<String>();
2364
2365 for (TaxonNode node: nodes){
2366 // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2367 // List<String> synonymsEpithet = new ArrayList<String>();
2368
2369 if (node.getClassification().equals(classification)){
2370 if (!node.isTopmostNode()){
2371 TaxonNode parent = node.getParent();
2372 parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
2373 TaxonNameBase<?,?> parentName = parent.getTaxon().getName();
2374 ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2375 Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
2376 Rank rankOfTaxon = taxonName.getRank();
2377
2378
2379 //create inferred synonyms for species, subspecies
2380 if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2381
2382 Synonym inferredEpithet = null;
2383 Synonym inferredGenus = null;
2384 Synonym potentialCombination = null;
2385
2386 List<String> propertyPaths = new ArrayList<String>();
2387 propertyPaths.add("synonym");
2388 propertyPaths.add("synonym.name");
2389 List<OrderHint> orderHints = new ArrayList<OrderHint>();
2390 orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2391
2392 List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2393 List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2394
2395 List<TaxonRelationship> taxonRelListParent = null;
2396 List<TaxonRelationship> taxonRelListTaxon = null;
2397 if (doWithMisappliedNames){
2398 taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2399 taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2400 }
2401
2402
2403 if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2404
2405
2406 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2407 Synonym syn = synonymRelationOfParent.getSynonym();
2408
2409 inferredEpithet = createInferredEpithets(taxon,
2410 zooHashMap, taxonName, epithetOfTaxon,
2411 infragenericEpithetOfTaxon,
2412 infraspecificEpithetOfTaxon,
2413 taxonNames, parentName,
2414 syn);
2415
2416
2417 inferredSynonyms.add(inferredEpithet);
2418 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2419 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2420 }
2421
2422 if (doWithMisappliedNames){
2423
2424 for (TaxonRelationship taxonRelationship: taxonRelListParent){
2425 Taxon misappliedName = taxonRelationship.getFromTaxon();
2426
2427 inferredEpithet = createInferredEpithets(taxon,
2428 zooHashMap, taxonName, epithetOfTaxon,
2429 infragenericEpithetOfTaxon,
2430 infraspecificEpithetOfTaxon,
2431 taxonNames, parentName,
2432 misappliedName);
2433
2434 inferredSynonyms.add(inferredEpithet);
2435 zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2436 taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2437 }
2438 }
2439
2440 if (!taxonNames.isEmpty()){
2441 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2442 ZoologicalName name;
2443 if (!synNotInCDM.isEmpty()){
2444 inferredSynonymsToBeRemoved.clear();
2445
2446 for (Synonym syn :inferredSynonyms){
2447 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2448 if (!synNotInCDM.contains(name.getNameCache())){
2449 inferredSynonymsToBeRemoved.add(syn);
2450 }
2451 }
2452
2453 // Remove identified Synonyms from inferredSynonyms
2454 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2455 inferredSynonyms.remove(synonym);
2456 }
2457 }
2458 }
2459
2460 }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2461
2462
2463 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2464 TaxonNameBase synName;
2465 ZoologicalName inferredSynName;
2466
2467 Synonym syn = synonymRelationOfTaxon.getSynonym();
2468 inferredGenus = createInferredGenus(taxon,
2469 zooHashMap, taxonName, epithetOfTaxon,
2470 genusOfTaxon, taxonNames, zooParentName, syn);
2471
2472 inferredSynonyms.add(inferredGenus);
2473 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2474 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2475
2476
2477 }
2478
2479 if (doWithMisappliedNames){
2480
2481 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2482 Taxon misappliedName = taxonRelationship.getFromTaxon();
2483 inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName, misappliedName);
2484
2485 inferredSynonyms.add(inferredGenus);
2486 zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2487 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2488 }
2489 }
2490
2491
2492 if (!taxonNames.isEmpty()){
2493 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2494 ZoologicalName name;
2495 if (!synNotInCDM.isEmpty()){
2496 inferredSynonymsToBeRemoved.clear();
2497
2498 for (Synonym syn :inferredSynonyms){
2499 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2500 if (!synNotInCDM.contains(name.getNameCache())){
2501 inferredSynonymsToBeRemoved.add(syn);
2502 }
2503 }
2504
2505 // Remove identified Synonyms from inferredSynonyms
2506 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2507 inferredSynonyms.remove(synonym);
2508 }
2509 }
2510 }
2511
2512 }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2513
2514 Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2515 ZoologicalName inferredSynName;
2516 //for all synonyms of the parent...
2517 for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2518 TaxonNameBase synName;
2519 Synonym synParent = synonymRelationOfParent.getSynonym();
2520 synName = synParent.getName();
2521
2522 HibernateProxyHelper.deproxy(synParent);
2523
2524 // Set the sourceReference
2525 sourceReference = synParent.getSec();
2526
2527 // Determine the idInSource
2528 String idInSourceParent = getIdInSource(synParent);
2529
2530 ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2531 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2532 String synParentInfragenericName = null;
2533 String synParentSpecificEpithet = null;
2534
2535 if (parentSynZooName.isInfraGeneric()){
2536 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2537 }
2538 if (parentSynZooName.isSpecies()){
2539 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2540 }
2541
2542 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2543 synonymsGenus.put(synGenusName, idInSource);
2544 }*/
2545
2546 //for all synonyms of the taxon
2547
2548 for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2549
2550 Synonym syn = synonymRelationOfTaxon.getSynonym();
2551 ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2552 potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2553 synParentGenus,
2554 synParentInfragenericName,
2555 synParentSpecificEpithet, syn, zooHashMap);
2556
2557 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2558 inferredSynonyms.add(potentialCombination);
2559 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2560 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2561
2562 }
2563
2564
2565 }
2566
2567 if (doWithMisappliedNames){
2568
2569 for (TaxonRelationship parentRelationship: taxonRelListParent){
2570
2571 TaxonNameBase misappliedParentName;
2572
2573 Taxon misappliedParent = parentRelationship.getFromTaxon();
2574 misappliedParentName = misappliedParent.getName();
2575
2576 HibernateProxyHelper.deproxy(misappliedParent);
2577
2578 // Set the sourceReference
2579 sourceReference = misappliedParent.getSec();
2580
2581 // Determine the idInSource
2582 String idInSourceParent = getIdInSource(misappliedParent);
2583
2584 ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2585 String synParentGenus = parentSynZooName.getGenusOrUninomial();
2586 String synParentInfragenericName = null;
2587 String synParentSpecificEpithet = null;
2588
2589 if (parentSynZooName.isInfraGeneric()){
2590 synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2591 }
2592 if (parentSynZooName.isSpecies()){
2593 synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2594 }
2595
2596
2597 for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2598 Taxon misappliedName = taxonRelationship.getFromTaxon();
2599 ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2600 potentialCombination = createPotentialCombination(
2601 idInSourceParent, parentSynZooName, zooMisappliedName,
2602 synParentGenus,
2603 synParentInfragenericName,
2604 synParentSpecificEpithet, misappliedName, zooHashMap);
2605
2606
2607 taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2608 inferredSynonyms.add(potentialCombination);
2609 zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2610 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2611 }
2612 }
2613 }
2614
2615 if (!taxonNames.isEmpty()){
2616 List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2617 ZoologicalName name;
2618 if (!synNotInCDM.isEmpty()){
2619 inferredSynonymsToBeRemoved.clear();
2620 for (Synonym syn :inferredSynonyms){
2621 try{
2622 name = (ZoologicalName) syn.getName();
2623 }catch (ClassCastException e){
2624 name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2625 }
2626 if (!synNotInCDM.contains(name.getNameCache())){
2627 inferredSynonymsToBeRemoved.add(syn);
2628 }
2629 }
2630 // Remove identified Synonyms from inferredSynonyms
2631 for (Synonym synonym : inferredSynonymsToBeRemoved) {
2632 inferredSynonyms.remove(synonym);
2633 }
2634 }
2635 }
2636 }
2637 }else {
2638 logger.info("The synonymrelationship type is not defined.");
2639 return inferredSynonyms;
2640 }
2641 }
2642 }
2643
2644 }
2645
2646 return inferredSynonyms;
2647 }
2648
2649 private Synonym createPotentialCombination(String idInSourceParent,
2650 ZoologicalName parentSynZooName, ZoologicalName zooSynName, String synParentGenus,
2651 String synParentInfragenericName, String synParentSpecificEpithet,
2652 TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2653 Synonym potentialCombination;
2654 Reference sourceReference;
2655 ZoologicalName inferredSynName;
2656 HibernateProxyHelper.deproxy(syn);
2657
2658 // Set sourceReference
2659 sourceReference = syn.getSec();
2660 if (sourceReference == null){
2661 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2662 //TODO:Remove
2663 if (!parentSynZooName.getTaxa().isEmpty()){
2664 TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2665
2666 sourceReference = taxon.getSec();
2667 }
2668 }
2669 String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2670
2671 String synTaxonInfraSpecificName= null;
2672
2673 if (parentSynZooName.isSpecies()){
2674 synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2675 }
2676
2677 /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2678 synonymsEpithet.add(epithetName);
2679 }*/
2680
2681 //create potential combinations...
2682 inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2683
2684 inferredSynName.setGenusOrUninomial(synParentGenus);
2685 if (zooSynName.isSpecies()){
2686 inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2687 if (parentSynZooName.isInfraGeneric()){
2688 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2689 }
2690 }
2691 if (zooSynName.isInfraSpecific()){
2692 inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2693 inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2694 }
2695 if (parentSynZooName.isInfraGeneric()){
2696 inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2697 }
2698
2699
2700 potentialCombination = Synonym.NewInstance(inferredSynName, null);
2701
2702 // Set the sourceReference
2703 potentialCombination.setSec(sourceReference);
2704
2705
2706 // Determine the idInSource
2707 String idInSourceSyn= getIdInSource(syn);
2708
2709 if (idInSourceParent != null && idInSourceSyn != null) {
2710 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2711 inferredSynName.addSource(originalSource);
2712 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2713 potentialCombination.addSource(originalSource);
2714 }
2715
2716 return potentialCombination;
2717 }
2718
2719 private Synonym createInferredGenus(Taxon taxon,
2720 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2721 String epithetOfTaxon, String genusOfTaxon,
2722 List<String> taxonNames, ZoologicalName zooParentName,
2723 TaxonBase syn) {
2724
2725 Synonym inferredGenus;
2726 TaxonNameBase synName;
2727 ZoologicalName inferredSynName;
2728 synName =syn.getName();
2729 HibernateProxyHelper.deproxy(syn);
2730
2731 // Determine the idInSource
2732 String idInSourceSyn = getIdInSource(syn);
2733 String idInSourceTaxon = getIdInSource(taxon);
2734 // Determine the sourceReference
2735 Reference sourceReference = syn.getSec();
2736
2737 //logger.warn(sourceReference.getTitleCache());
2738
2739 synName = syn.getName();
2740 ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2741 String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2742 /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2743 synonymsEpithet.add(synSpeciesEpithetName);
2744 }*/
2745
2746 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2747 //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...
2748
2749
2750 inferredSynName.setGenusOrUninomial(genusOfTaxon);
2751 if (zooParentName.isInfraGeneric()){
2752 inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2753 }
2754
2755 if (taxonName.isSpecies()){
2756 inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2757 }
2758 if (taxonName.isInfraSpecific()){
2759 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2760 inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2761 }
2762
2763
2764 inferredGenus = Synonym.NewInstance(inferredSynName, null);
2765
2766 // Set the sourceReference
2767 inferredGenus.setSec(sourceReference);
2768
2769 // Add the original source
2770 if (idInSourceSyn != null && idInSourceTaxon != null) {
2771 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2772 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2773 inferredGenus.addSource(originalSource);
2774
2775 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2776 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2777 inferredSynName.addSource(originalSource);
2778 originalSource = null;
2779
2780 }else{
2781 logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2782 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2783 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2784 inferredGenus.addSource(originalSource);
2785
2786 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2787 idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2788 inferredSynName.addSource(originalSource);
2789 originalSource = null;
2790 }
2791
2792 taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2793
2794 return inferredGenus;
2795 }
2796
2797 private Synonym createInferredEpithets(Taxon taxon,
2798 HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2799 String epithetOfTaxon, String infragenericEpithetOfTaxon,
2800 String infraspecificEpithetOfTaxon, List<String> taxonNames,
2801 TaxonNameBase parentName, TaxonBase syn) {
2802
2803 Synonym inferredEpithet;
2804 TaxonNameBase<?,?> synName;
2805 ZoologicalName inferredSynName;
2806 HibernateProxyHelper.deproxy(syn);
2807
2808 // Determine the idInSource
2809 String idInSourceSyn = getIdInSource(syn);
2810 String idInSourceTaxon = getIdInSource(taxon);
2811 // Determine the sourceReference
2812 Reference<?> sourceReference = syn.getSec();
2813
2814 if (sourceReference == null){
2815 logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2816 sourceReference = taxon.getSec();
2817 }
2818
2819 synName = syn.getName();
2820 ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2821 String synGenusName = zooSynName.getGenusOrUninomial();
2822 String synInfraGenericEpithet = null;
2823 String synSpecificEpithet = null;
2824
2825 if (zooSynName.getInfraGenericEpithet() != null){
2826 synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2827 }
2828
2829 if (zooSynName.isInfraSpecific()){
2830 synSpecificEpithet = zooSynName.getSpecificEpithet();
2831 }
2832
2833 /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2834 synonymsGenus.put(synGenusName, idInSource);
2835 }*/
2836
2837 inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2838
2839 // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2840 if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2841 logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2842 }
2843 inferredSynName.setGenusOrUninomial(synGenusName);
2844
2845 if (parentName.isInfraGeneric()){
2846 inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2847 }
2848 if (taxonName.isSpecies()){
2849 inferredSynName.setSpecificEpithet(epithetOfTaxon);
2850 }else if (taxonName.isInfraSpecific()){
2851 inferredSynName.setSpecificEpithet(synSpecificEpithet);
2852 inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2853 }
2854
2855 inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2856
2857 // Set the sourceReference
2858 inferredEpithet.setSec(sourceReference);
2859
2860 /* Add the original source
2861 if (idInSource != null) {
2862 IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2863
2864 // Add the citation
2865 Reference citation = getCitation(syn);
2866 if (citation != null) {
2867 originalSource.setCitation(citation);
2868 inferredEpithet.addSource(originalSource);
2869 }
2870 }*/
2871 String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2872
2873
2874 IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2875 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2876
2877 inferredEpithet.addSource(originalSource);
2878
2879 originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2880 taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2881
2882 inferredSynName.addSource(originalSource);
2883
2884
2885
2886 taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2887
2888 return inferredEpithet;
2889 }
2890
2891 /**
2892 * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2893 * Very likely only useful for createInferredSynonyms().
2894 * @param uuid
2895 * @param zooHashMap
2896 * @return
2897 */
2898 private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2899 ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2900 if (taxonName == null) {
2901 taxonName = zooHashMap.get(uuid);
2902 }
2903 return taxonName;
2904 }
2905
2906 /**
2907 * Returns the idInSource for a given Synonym.
2908 * @param syn
2909 */
2910 private String getIdInSource(TaxonBase taxonBase) {
2911 String idInSource = null;
2912 Set<IdentifiableSource> sources = taxonBase.getSources();
2913 if (sources.size() == 1) {
2914 IdentifiableSource source = sources.iterator().next();
2915 if (source != null) {
2916 idInSource = source.getIdInSource();
2917 }
2918 } else if (sources.size() > 1) {
2919 int count = 1;
2920 idInSource = "";
2921 for (IdentifiableSource source : sources) {
2922 idInSource += source.getIdInSource();
2923 if (count < sources.size()) {
2924 idInSource += "; ";
2925 }
2926 count++;
2927 }
2928 } else if (sources.size() == 0){
2929 logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2930 }
2931
2932
2933 return idInSource;
2934 }
2935
2936
2937 /**
2938 * Returns the citation for a given Synonym.
2939 * @param syn
2940 */
2941 private Reference getCitation(Synonym syn) {
2942 Reference citation = null;
2943 Set<IdentifiableSource> sources = syn.getSources();
2944 if (sources.size() == 1) {
2945 IdentifiableSource source = sources.iterator().next();
2946 if (source != null) {
2947 citation = source.getCitation();
2948 }
2949 } else if (sources.size() > 1) {
2950 logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2951 }
2952
2953 return citation;
2954 }
2955
2956 @Override
2957 public List<Synonym> createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2958 List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2959
2960 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2961 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2962 inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2963
2964 return inferredSynonyms;
2965 }
2966
2967 @Override
2968 public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2969
2970 // TODO quickly implemented, create according dao !!!!
2971 Set<TaxonNode> nodes = new HashSet<TaxonNode>();
2972 Set<Classification> classifications = new HashSet<Classification>();
2973 List<Classification> list = new ArrayList<Classification>();
2974
2975 if (taxonBase == null) {
2976 return list;
2977 }
2978
2979 taxonBase = load(taxonBase.getUuid());
2980
2981 if (taxonBase instanceof Taxon) {
2982 nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
2983 } else {
2984 for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
2985 nodes.addAll(taxon.getTaxonNodes());
2986 }
2987 }
2988 for (TaxonNode node : nodes) {
2989 classifications.add(node.getClassification());
2990 }
2991 list.addAll(classifications);
2992 return list;
2993 }
2994
2995 @Override
2996 public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
2997 SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
2998 // Create new synonym using concept name
2999 TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
3000 Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
3001
3002 // Remove concept relation from taxon
3003 toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
3004
3005
3006
3007
3008 // Create a new synonym for the taxon
3009 SynonymRelationship synonymRelationship;
3010 if (synonymRelationshipType != null
3011 && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
3012 synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
3013 } else{
3014 synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
3015 }
3016
3017 this.saveOrUpdate(toTaxon);
3018 //TODO: configurator and classification
3019 this.deleteTaxon(fromTaxon, null, null);
3020 return synonymRelationship.getSynonym();
3021
3022 }
3023 @Override
3024 public List<String> isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
3025 List<String> result = new ArrayList<String>();
3026 Set<CdmBase> references = commonService.getReferencingObjects(taxonBase);
3027 if (taxonBase instanceof Taxon){
3028 TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3029 result = isDeletableForTaxon(references, taxonConfig);
3030 }else{
3031 SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3032 result = isDeletableForSynonym(references, synonymConfig);
3033 }
3034 return result;
3035 }
3036
3037 private List<String> isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3038 String message;
3039 List<String> result = new ArrayList<String>();
3040 for (CdmBase ref: references){
3041 if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase)){
3042 message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3043 result.add(message);
3044 }
3045 }
3046
3047 return result;
3048 }
3049 private List<String> isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3050 String message;
3051 List<String> result = new ArrayList<String>();
3052 for (CdmBase ref: references){
3053 if (!(ref instanceof TaxonNameBase)){
3054 if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){
3055 message = "The Taxon can't be deleted as long as it has synonyms.";
3056 result.add(message);
3057 }
3058 if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3059 message = "The Taxon can't be deleted as long as it has factual data.";
3060 result.add(message);
3061 }
3062
3063 if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
3064 message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3065 result.add(message);
3066 }
3067 if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonNode)){
3068 if (!config.isDeleteMisappliedNamesAndInvalidDesignations() && (((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())|| ((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR()))){
3069 message = "The Taxon can't be deleted as long as it has misapplied names or invalid designations.";
3070 result.add(message);
3071 } else{
3072 message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3073 result.add(message);
3074 }
3075 }
3076 if (ref instanceof PolytomousKeyNode){
3077 message = "The Taxon can't be deleted as long as it is referenced by a polytomous key node.";
3078 result.add(message);
3079 }
3080
3081 if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){
3082 message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
3083 result.add(message);
3084
3085 }
3086
3087
3088 /* //PolytomousKeyNode
3089 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3090 String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3091 return message;
3092 }*/
3093
3094 //TaxonInteraction
3095 if (ref.isInstanceOf(TaxonInteraction.class)){
3096 message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3097 result.add(message);
3098 }
3099
3100 //TaxonInteraction
3101 if (ref.isInstanceOf(DeterminationEvent.class)){
3102 message = "Taxon can't be deleted as it is used in a determination event";
3103 result.add(message);
3104 }
3105
3106 }
3107
3108 }
3109
3110 return result;
3111 }
3112
3113 @Override
3114 public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3115 IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3116
3117 //preliminary implementation
3118
3119 Set<Taxon> taxa = new HashSet<Taxon>();
3120 TaxonBase taxonBase = find(taxonUuid);
3121 if (taxonBase == null){
3122 return new IncludedTaxaDTO();
3123 }else if (taxonBase.isInstanceOf(Taxon.class)){
3124 Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3125 taxa.add(taxon);
3126 }else if (taxonBase.isInstanceOf(Synonym.class)){
3127 //TODO partial synonyms ??
3128 //TODO synonyms in general
3129 Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3130 taxa.addAll(syn.getAcceptedTaxa());
3131 }else{
3132 throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3133 }
3134
3135 Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3136 int i = 0;
3137 while((! related.isEmpty()) && i++ < 100){ //to avoid
3138 related = makeRelatedIncluded(related, result, config);
3139 }
3140
3141 return result;
3142 }
3143
3144 /**
3145 * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3146 * data structure.
3147 * @return the set of conceptually related taxa for further use
3148 */
3149 /**
3150 * @param uncheckedTaxa
3151 * @param existingTaxa
3152 * @param config
3153 * @return
3154 */
3155 private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3156
3157 //children
3158 Set<TaxonNode> taxonNodes = new HashSet<TaxonNode>();
3159 for (Taxon taxon: uncheckedTaxa){
3160 taxonNodes.addAll(taxon.getTaxonNodes());
3161 }
3162
3163 Set<Taxon> children = new HashSet<Taxon>();
3164 if (! config.onlyCongruent){
3165 for (TaxonNode node: taxonNodes){
3166 List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, false);
3167 for (TaxonNode child : childNodes){
3168 children.add(child.getTaxon());
3169 }
3170 }
3171 children.remove(null); // just to be on the save side
3172 }
3173
3174 Iterator<Taxon> it = children.iterator();
3175 while(it.hasNext()){
3176 UUID uuid = it.next().getUuid();
3177 if (existingTaxa.contains(uuid)){
3178 it.remove();
3179 }else{
3180 existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3181 }
3182 }
3183
3184 //concept relations
3185 Set<Taxon> uncheckedAndChildren = new HashSet<Taxon>(uncheckedTaxa);
3186 uncheckedAndChildren.addAll(children);
3187
3188 Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3189
3190
3191 Set<Taxon> result = new HashSet<Taxon>(relatedTaxa);
3192 return result;
3193 }
3194
3195 /**
3196 * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3197 * @return the set of these computed taxa
3198 */
3199 private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3200 Set<Taxon> result = new HashSet<Taxon>();
3201
3202 for (Taxon taxon : unchecked){
3203 Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3204 Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3205
3206 for (TaxonRelationship fromRel : fromRelations){
3207 if (config.includeDoubtful == false && fromRel.isDoubtful()){
3208 continue;
3209 }
3210 if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3211 !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
3212 !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3213 ){
3214 result.add(fromRel.getToTaxon());
3215 }
3216 }
3217
3218 for (TaxonRelationship toRel : toRelations){
3219 if (config.includeDoubtful == false && toRel.isDoubtful()){
3220 continue;
3221 }
3222 if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
3223 result.add(toRel.getFromTaxon());
3224 }
3225 }
3226 }
3227
3228 Iterator<Taxon> it = result.iterator();
3229 while(it.hasNext()){
3230 UUID uuid = it.next().getUuid();
3231 if (existingTaxa.contains(uuid)){
3232 it.remove();
3233 }else{
3234 existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3235 }
3236 }
3237 return result;
3238 }
3239
3240
3241
3242 }