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