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