Project

General

Profile

Download (149 KB) Statistics
| Branch: | Tag: | Revision:
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.MatchingTaxonConfigurator;
43
import eu.etaxonomy.cdm.api.service.config.SynonymDeletionConfigurator;
44
import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
45
import eu.etaxonomy.cdm.api.service.config.TaxonNodeDeletionConfigurator.ChildHandling;
46
import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
47
import eu.etaxonomy.cdm.api.service.exception.HomotypicalGroupChangeException;
48
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
49
import eu.etaxonomy.cdm.api.service.pager.Pager;
50
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
51
import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
52
import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
53
import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearch;
54
import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearchException;
55
import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
56
import eu.etaxonomy.cdm.api.service.search.LuceneSearch.TopGroupsWithMaxScore;
57
import eu.etaxonomy.cdm.api.service.search.QueryFactory;
58
import eu.etaxonomy.cdm.api.service.search.SearchResult;
59
import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
60
import eu.etaxonomy.cdm.api.service.util.TaxonRelationshipEdge;
61
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
62
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
63
import eu.etaxonomy.cdm.hibernate.search.DefinedTermBaseClassBridge;
64
import eu.etaxonomy.cdm.hibernate.search.GroupByTaxonClassBridge;
65
import eu.etaxonomy.cdm.hibernate.search.MultilanguageTextFieldBridge;
66
import eu.etaxonomy.cdm.model.CdmBaseType;
67
import eu.etaxonomy.cdm.model.common.CdmBase;
68
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
69
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
70
import eu.etaxonomy.cdm.model.common.Language;
71
import eu.etaxonomy.cdm.model.common.OrderedTermVocabulary;
72
import eu.etaxonomy.cdm.model.common.OriginalSourceType;
73
import eu.etaxonomy.cdm.model.common.RelationshipBase;
74
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
75
import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
76
import eu.etaxonomy.cdm.model.description.CommonTaxonName;
77
import eu.etaxonomy.cdm.model.description.DescriptionBase;
78
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
79
import eu.etaxonomy.cdm.model.description.Distribution;
80
import eu.etaxonomy.cdm.model.description.Feature;
81
import eu.etaxonomy.cdm.model.description.IIdentificationKey;
82
import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
83
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase;
84
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
85
import eu.etaxonomy.cdm.model.description.TaxonDescription;
86
import eu.etaxonomy.cdm.model.description.TaxonInteraction;
87
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
88
import eu.etaxonomy.cdm.model.location.NamedArea;
89
import eu.etaxonomy.cdm.model.media.Media;
90
import eu.etaxonomy.cdm.model.media.MediaRepresentation;
91
import eu.etaxonomy.cdm.model.media.MediaUtils;
92
import eu.etaxonomy.cdm.model.molecular.Amplification;
93
import eu.etaxonomy.cdm.model.molecular.DnaSample;
94
import eu.etaxonomy.cdm.model.molecular.Sequence;
95
import eu.etaxonomy.cdm.model.molecular.SingleRead;
96
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
97
import eu.etaxonomy.cdm.model.name.NameRelationship;
98
import eu.etaxonomy.cdm.model.name.Rank;
99
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
100
import eu.etaxonomy.cdm.model.name.ZoologicalName;
101
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
102
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
103
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
104
import eu.etaxonomy.cdm.model.reference.Reference;
105
import eu.etaxonomy.cdm.model.taxon.Classification;
106
import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
107
import eu.etaxonomy.cdm.model.taxon.Synonym;
108
import eu.etaxonomy.cdm.model.taxon.SynonymRelationship;
109
import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;
110
import eu.etaxonomy.cdm.model.taxon.Taxon;
111
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
112
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
113
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
114
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
115
import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
116
import eu.etaxonomy.cdm.persistence.dao.common.IOrderedTermVocabularyDao;
117
import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
118
import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
119
import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
120
import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
121
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
122
import eu.etaxonomy.cdm.persistence.fetch.CdmFetch;
123
import eu.etaxonomy.cdm.persistence.query.MatchMode;
124
import eu.etaxonomy.cdm.persistence.query.OrderHint;
125
import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
126
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
127

    
128

    
129
/**
130
 * @author a.kohlbecker
131
 * @date 10.09.2010
132
 *
133
 */
134
@Service
135
@Transactional(readOnly = true)
136
public class TaxonServiceImpl extends IdentifiableServiceBase<TaxonBase,ITaxonDao> implements ITaxonService{
137
    private static final Logger logger = Logger.getLogger(TaxonServiceImpl.class);
138

    
139
    public static final String POTENTIAL_COMBINATION_NAMESPACE = "Potential combination";
140

    
141
    public static final String INFERRED_EPITHET_NAMESPACE = "Inferred epithet";
142

    
143
    public static final String INFERRED_GENUS_NAMESPACE = "Inferred genus";
144

    
145

    
146
    @Autowired
147
    private ITaxonNameDao nameDao;
148

    
149
    @Autowired
150
    private INameService nameService;
151

    
152
    @Autowired
153
    private ITaxonNodeService nodeService;
154

    
155
    @Autowired
156
    private ICdmGenericDao genericDao;
157

    
158
    @Autowired
159
    private IDescriptionService descriptionService;
160

    
161
    @Autowired
162
    private IOrderedTermVocabularyDao orderedVocabularyDao;
163

    
164
    @Autowired
165
    private IOccurrenceDao occurrenceDao;
166

    
167
    @Autowired
168
    private IClassificationDao classificationDao;
169

    
170
    @Autowired
171
    private AbstractBeanInitializer beanInitializer;
172

    
173
    @Autowired
174
    private ILuceneIndexToolProvider luceneIndexToolProvider;
175

    
176
    /**
177
     * Constructor
178
     */
179
    public TaxonServiceImpl(){
180
        if (logger.isDebugEnabled()) { logger.debug("Load TaxonService Bean"); }
181
    }
182

    
183
    /**
184
     * FIXME Candidate for harmonization
185
     * rename searchByName ?
186
     */
187
    @Override
188
    public List<TaxonBase> searchTaxaByName(String name, Reference sec) {
189
        return dao.getTaxaByName(name, sec);
190
    }
191

    
192
    /**
193
     * FIXME Candidate for harmonization
194
     * merge with getRootTaxa(Reference sec, ..., ...)
195
     *  (non-Javadoc)
196
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getRootTaxa(eu.etaxonomy.cdm.model.reference.Reference, boolean)
197
     */
198
    @Override
199
    public List<Taxon> getRootTaxa(Reference sec, CdmFetch cdmFetch, boolean onlyWithChildren) {
200
        if (cdmFetch == null){
201
            cdmFetch = CdmFetch.NO_FETCH();
202
        }
203
        return dao.getRootTaxa(sec, cdmFetch, onlyWithChildren, false);
204
    }
205

    
206
    @Override
207
    public List<Taxon> getRootTaxa(Rank rank, Reference sec, boolean onlyWithChildren,boolean withMisapplications, List<String> propertyPaths) {
208
        return dao.getRootTaxa(rank, sec, null, onlyWithChildren, withMisapplications, propertyPaths);
209
    }
210

    
211
    @Override
212
    public List<RelationshipBase> getAllRelationships(int limit, int start){
213
        return dao.getAllRelationships(limit, start);
214
    }
215

    
216
    /**
217
     * FIXME Candidate for harmonization
218
     * is this the same as termService.getVocabulary(VocabularyEnum.TaxonRelationshipType) ?
219
     */
220
    @Override
221
    @Deprecated
222
    public OrderedTermVocabulary<TaxonRelationshipType> getTaxonRelationshipTypeVocabulary() {
223

    
224
        String taxonRelTypeVocabularyId = "15db0cf7-7afc-4a86-a7d4-221c73b0c9ac";
225
        UUID uuid = UUID.fromString(taxonRelTypeVocabularyId);
226
        OrderedTermVocabulary<TaxonRelationshipType> taxonRelTypeVocabulary =
227
            (OrderedTermVocabulary)orderedVocabularyDao.findByUuid(uuid);
228
        return taxonRelTypeVocabulary;
229
    }
230

    
231

    
232

    
233
    /*
234
     * (non-Javadoc)
235
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#swapSynonymWithAcceptedTaxon(eu.etaxonomy.cdm.model.taxon.Synonym)
236
     */
237
    @Override
238
    @Transactional(readOnly = false)
239
    public void swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon){
240

    
241
        TaxonNameBase<?,?> synonymName = synonym.getName();
242
        synonymName.removeTaxonBase(synonym);
243
        TaxonNameBase<?,?> taxonName = acceptedTaxon.getName();
244
        taxonName.removeTaxonBase(acceptedTaxon);
245

    
246
        synonym.setName(taxonName);
247
        acceptedTaxon.setName(synonymName);
248

    
249
        // the accepted taxon needs a new uuid because the concept has changed
250
        // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
251
        //acceptedTaxon.setUuid(UUID.randomUUID());
252
    }
253

    
254

    
255
    /* (non-Javadoc)
256
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#changeSynonymToAcceptedTaxon(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
257
     */
258

    
259
    @Override
260
    @Transactional(readOnly = false)
261
    public Taxon changeSynonymToAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean deleteSynonym, boolean copyCitationInfo, Reference citation, String microCitation) throws HomotypicalGroupChangeException{
262

    
263
        TaxonNameBase<?,?> acceptedName = acceptedTaxon.getName();
264
        TaxonNameBase<?,?> synonymName = synonym.getName();
265
        HomotypicalGroup synonymHomotypicGroup = synonymName.getHomotypicalGroup();
266

    
267
        //check synonym is not homotypic
268
        if (acceptedName.getHomotypicalGroup().equals(synonymHomotypicGroup)){
269
            String message = "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
270
            throw new HomotypicalGroupChangeException(message);
271
        }
272

    
273
        Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, acceptedTaxon.getSec());
274

    
275
        SynonymRelationshipType relTypeForGroup = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
276
        List<Synonym> heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup);
277
        Set<NameRelationship> basionymsAndReplacedSynonyms = synonymHomotypicGroup.getBasionymAndReplacedSynonymRelations();
278

    
279
        for (Synonym heteroSynonym : heteroSynonyms){
280
            if (synonym.equals(heteroSynonym)){
281
                acceptedTaxon.removeSynonym(heteroSynonym, false);
282

    
283
            }else{
284
                //move synonyms in same homotypic group to new accepted taxon
285
                heteroSynonym.replaceAcceptedTaxon(newAcceptedTaxon, relTypeForGroup, copyCitationInfo, citation, microCitation);
286
            }
287
        }
288

    
289
        //synonym.getName().removeTaxonBase(synonym);
290

    
291
        if (deleteSynonym){
292
//			deleteSynonym(synonym, taxon, false);
293
            try {
294
                this.dao.flush();
295
                SynonymDeletionConfigurator config = new SynonymDeletionConfigurator();
296
                config.setDeleteNameIfPossible(false);
297
                this.deleteSynonym(synonym, acceptedTaxon, config);
298

    
299
            } catch (Exception e) {
300
                logger.info("Can't delete old synonym from database");
301
            }
302
        }
303

    
304
        return newAcceptedTaxon;
305
    }
306

    
307

    
308
    @Override
309
    public Taxon changeSynonymToRelatedTaxon(Synonym synonym, Taxon toTaxon, TaxonRelationshipType taxonRelationshipType, Reference citation, String microcitation){
310

    
311
        // Get name from synonym
312
        TaxonNameBase<?, ?> synonymName = synonym.getName();
313

    
314
      /*  // remove synonym from taxon
315
        toTaxon.removeSynonym(synonym);
316
*/
317
        // Create a taxon with synonym name
318
        Taxon fromTaxon = Taxon.NewInstance(synonymName, null);
319

    
320
        // Add taxon relation
321
        fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
322

    
323
        // since we are swapping names, we have to detach the name from the synonym completely.
324
        // Otherwise the synonym will still be in the list of typified names.
325
       // synonym.getName().removeTaxonBase(synonym);
326
        this.deleteSynonym(synonym, null);
327

    
328
        return fromTaxon;
329
    }
330

    
331

    
332
    /* (non-Javadoc)
333
     * @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)
334
     */
335
    @Transactional(readOnly = false)
336
    @Override
337
    public void changeHomotypicalGroupOfSynonym(Synonym synonym, HomotypicalGroup newHomotypicalGroup, Taxon targetTaxon,
338
                        boolean removeFromOtherTaxa, boolean setBasionymRelationIfApplicable){
339
        // Get synonym name
340
        TaxonNameBase synonymName = synonym.getName();
341
        HomotypicalGroup oldHomotypicalGroup = synonymName.getHomotypicalGroup();
342

    
343

    
344
        // Switch groups
345
        oldHomotypicalGroup.removeTypifiedName(synonymName);
346
        newHomotypicalGroup.addTypifiedName(synonymName);
347

    
348
        //remove existing basionym relationships
349
        synonymName.removeBasionyms();
350

    
351
        //add basionym relationship
352
        if (setBasionymRelationIfApplicable){
353
            Set<TaxonNameBase> basionyms = newHomotypicalGroup.getBasionyms();
354
            for (TaxonNameBase basionym : basionyms){
355
                synonymName.addBasionym(basionym);
356
            }
357
        }
358

    
359
        //set synonym relationship correctly
360
//			SynonymRelationship relToTaxon = null;
361
        boolean relToTargetTaxonExists = false;
362
        Set<SynonymRelationship> existingRelations = synonym.getSynonymRelations();
363
        for (SynonymRelationship rel : existingRelations){
364
            Taxon acceptedTaxon = rel.getAcceptedTaxon();
365
            boolean isTargetTaxon = acceptedTaxon != null && acceptedTaxon.equals(targetTaxon);
366
            HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
367
            boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
368
            SynonymRelationshipType newRelationType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
369
            rel.setType(newRelationType);
370
            //TODO handle citation and microCitation
371

    
372
            if (isTargetTaxon){
373
                relToTargetTaxonExists = true;
374
            }else{
375
                if (removeFromOtherTaxa){
376
                    acceptedTaxon.removeSynonym(synonym, false);
377
                }else{
378
                    //do nothing
379
                }
380
            }
381
        }
382
        if (targetTaxon != null &&  ! relToTargetTaxonExists ){
383
            Taxon acceptedTaxon = targetTaxon;
384
            HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
385
            boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
386
            SynonymRelationshipType relType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
387
            //TODO handle citation and microCitation
388
            Reference citation = null;
389
            String microCitation = null;
390
            acceptedTaxon.addSynonym(synonym, relType, citation, microCitation);
391
        }
392

    
393
    }
394

    
395

    
396
    /* (non-Javadoc)
397
     * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache(java.lang.Integer, eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy)
398
     */
399
    @Override
400
    @Transactional(readOnly = false)
401
    public void updateTitleCache(Class<? extends TaxonBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonBase> cacheStrategy, IProgressMonitor monitor) {
402
        if (clazz == null){
403
            clazz = TaxonBase.class;
404
        }
405
        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
406
    }
407

    
408
    @Override
409
    @Autowired
410
    protected void setDao(ITaxonDao dao) {
411
        this.dao = dao;
412
    }
413

    
414
    /* (non-Javadoc)
415
     * @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)
416
     */
417
    @Override
418
    public Pager<TaxonBase> findTaxaByName(Class<? extends TaxonBase> clazz, String uninomial,	String infragenericEpithet, String specificEpithet,	String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
419
        Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
420

    
421
        List<TaxonBase> results = new ArrayList<TaxonBase>();
422
        if(numberOfResults > 0) { // no point checking again
423
            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
424
        }
425

    
426
        return new DefaultPagerImpl<TaxonBase>(pageNumber, numberOfResults, pageSize, results);
427
    }
428

    
429
    /* (non-Javadoc)
430
     * @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)
431
     */
432
    @Override
433
    public List<TaxonBase> listTaxaByName(Class<? extends TaxonBase> clazz, String uninomial,	String infragenericEpithet, String specificEpithet,	String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
434
        Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
435

    
436
        List<TaxonBase> results = new ArrayList<TaxonBase>();
437
        if(numberOfResults > 0) { // no point checking again
438
            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
439
        }
440

    
441
        return results;
442
    }
443

    
444
    /* (non-Javadoc)
445
     * @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)
446
     */
447
    @Override
448
    public List<TaxonRelationship> listToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
449
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
450

    
451
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
452
        if(numberOfResults > 0) { // no point checking again
453
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
454
        }
455
        return results;
456
    }
457

    
458
    /* (non-Javadoc)
459
     * @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)
460
     */
461
    @Override
462
    public Pager<TaxonRelationship> pageToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
463
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
464

    
465
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
466
        if(numberOfResults > 0) { // no point checking again
467
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
468
        }
469
        return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
470
    }
471

    
472
    /* (non-Javadoc)
473
     * @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)
474
     */
475
    @Override
476
    public List<TaxonRelationship> listFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
477
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
478

    
479
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
480
        if(numberOfResults > 0) { // no point checking again
481
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
482
        }
483
        return results;
484
    }
485

    
486
    /* (non-Javadoc)
487
     * @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)
488
     */
489
    @Override
490
    public Pager<TaxonRelationship> pageFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
491
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
492

    
493
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
494
        if(numberOfResults > 0) { // no point checking again
495
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
496
        }
497
        return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
498
    }
499

    
500
    @Override
501
    public List<Taxon> listAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber,
502
            List<OrderHint> orderHints, List<String> propertyPaths){
503
        return pageAcceptedTaxaFor(synonymUuid, classificationUuid, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
504
    }
505

    
506
    @Override
507
    public Pager<Taxon> pageAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber,
508
            List<OrderHint> orderHints, List<String> propertyPaths){
509

    
510
        List<Taxon> list = new ArrayList<Taxon>();
511
        Long count = 0l;
512

    
513
        Synonym synonym = null;
514

    
515
        try {
516
            synonym = (Synonym) dao.load(synonymUuid);
517
        } catch (ClassCastException e){
518
            throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid + " is not a Synonmy");
519
        } catch (NullPointerException e){
520
            throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid);
521
        }
522

    
523
        Classification classificationFilter = null;
524
        if(classificationUuid != null){
525
            try {
526
            classificationFilter = classificationDao.load(classificationUuid);
527
            } catch (NullPointerException e){
528
                throw new EntityNotFoundException("No Classification entity found for " + classificationUuid);
529
            }
530
            if(classificationFilter == null){
531

    
532
            }
533
        }
534

    
535
        count = dao.countAcceptedTaxaFor(synonym, classificationFilter) ;
536
        if(count > (pageSize * pageNumber)){
537
            list = dao.listAcceptedTaxaFor(synonym, classificationFilter, pageSize, pageNumber, orderHints, propertyPaths);
538
        }
539

    
540
        return new DefaultPagerImpl<Taxon>(pageNumber, count.intValue(), pageSize, list);
541
    }
542

    
543

    
544
    /**
545
     * @param taxon
546
     * @param includeRelationships
547
     * @param maxDepth
548
     * @param limit
549
     * @param starts
550
     * @param propertyPaths
551
     * @return an List which is not specifically ordered
552
     */
553
    @Override
554
    public Set<Taxon> listRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Integer maxDepth,
555
            Integer limit, Integer start, List<String> propertyPaths) {
556

    
557
        Set<Taxon> relatedTaxa = collectRelatedTaxa(taxon, includeRelationships, new HashSet<Taxon>(), maxDepth);
558
        relatedTaxa.remove(taxon);
559
        beanInitializer.initializeAll(relatedTaxa, propertyPaths);
560
        return relatedTaxa;
561
    }
562

    
563

    
564
    /**
565
     * recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
566
     *  <code>taxon</code> supplied as parameter.
567
     *
568
     * @param taxon
569
     * @param includeRelationships
570
     * @param taxa
571
     * @param maxDepth can be <code>null</code> for infinite depth
572
     * @return
573
     */
574
    private Set<Taxon> collectRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Set<Taxon> taxa, Integer maxDepth) {
575

    
576
        if(taxa.isEmpty()) {
577
            taxa.add(taxon);
578
        }
579

    
580
        if(includeRelationships.isEmpty()){
581
            return taxa;
582
        }
583

    
584
        if(maxDepth != null) {
585
            maxDepth--;
586
        }
587
        if(logger.isDebugEnabled()){
588
            logger.debug("collecting related taxa for " + taxon + " with maxDepth=" + maxDepth);
589
        }
590
        List<TaxonRelationship> taxonRelationships = dao.getTaxonRelationships(taxon, null, null, null, null, null, null);
591
        for (TaxonRelationship taxRel : taxonRelationships) {
592

    
593
            // skip invalid data
594
            if (taxRel.getToTaxon() == null || taxRel.getFromTaxon() == null || taxRel.getType() == null) {
595
                continue;
596
            }
597
            // filter by includeRelationships
598
            for (TaxonRelationshipEdge relationshipEdgeFilter : includeRelationships) {
599
                if ( relationshipEdgeFilter.getTaxonRelationshipType().equals(taxRel.getType()) ) {
600
                    if (relationshipEdgeFilter.getDirections().contains(Direction.relatedTo) && !taxa.contains(taxRel.getToTaxon())) {
601
                        if(logger.isDebugEnabled()){
602
                            logger.debug(maxDepth + ": " + taxon.getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxRel.getToTaxon().getTitleCache());
603
                        }
604
                        taxa.add(taxRel.getToTaxon());
605
                        if(maxDepth == null || maxDepth > 0) {
606
                            taxa.addAll(collectRelatedTaxa(taxRel.getToTaxon(), includeRelationships, taxa, maxDepth));
607
                        }
608
                    }
609
                    if(relationshipEdgeFilter.getDirections().contains(Direction.relatedFrom) && !taxa.contains(taxRel.getFromTaxon())) {
610
                        taxa.add(taxRel.getFromTaxon());
611
                        if(logger.isDebugEnabled()){
612
                            logger.debug(maxDepth + ": " +taxRel.getFromTaxon().getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxon.getTitleCache() );
613
                        }
614
                        if(maxDepth == null || maxDepth > 0) {
615
                            taxa.addAll(collectRelatedTaxa(taxRel.getFromTaxon(), includeRelationships, taxa, maxDepth));
616
                        }
617
                    }
618
                }
619
            }
620
        }
621
        return taxa;
622
    }
623

    
624
    /* (non-Javadoc)
625
     * @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)
626
     */
627
    @Override
628
    public Pager<SynonymRelationship> getSynonyms(Taxon taxon,	SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
629
        Integer numberOfResults = dao.countSynonyms(taxon, type);
630

    
631
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
632
        if(numberOfResults > 0) { // no point checking again
633
            results = dao.getSynonyms(taxon, type, pageSize, pageNumber, orderHints, propertyPaths);
634
        }
635

    
636
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
637
    }
638

    
639
    /* (non-Javadoc)
640
     * @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)
641
     */
642
    @Override
643
    public Pager<SynonymRelationship> getSynonyms(Synonym synonym,	SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
644
        Integer numberOfResults = dao.countSynonyms(synonym, type);
645

    
646
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
647
        if(numberOfResults > 0) { // no point checking again
648
            results = dao.getSynonyms(synonym, type, pageSize, pageNumber, orderHints, propertyPaths);
649
        }
650

    
651
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
652
    }
653

    
654
    /* (non-Javadoc)
655
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHomotypicSynonymsByHomotypicGroup(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
656
     */
657
    @Override
658
    public List<List<Synonym>> getSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
659
         List<List<Synonym>> result = new ArrayList<List<Synonym>>();
660
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
661

    
662
        //homotypic
663
        result.add(t.getHomotypicSynonymsByHomotypicGroup());
664

    
665
        //heterotypic
666
        List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
667
        for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
668
            result.add(t.getSynonymsInGroup(homotypicalGroup));
669
        }
670

    
671
        return result;
672

    
673
    }
674

    
675
    /* (non-Javadoc)
676
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHomotypicSynonymsByHomotypicGroup(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
677
     */
678
    @Override
679
    public List<Synonym> getHomotypicSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
680
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
681
        return t.getHomotypicSynonymsByHomotypicGroup();
682
    }
683

    
684
    /* (non-Javadoc)
685
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHeterotypicSynonymyGroups(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
686
     */
687
    @Override
688
    public List<List<Synonym>> getHeterotypicSynonymyGroups(Taxon taxon, List<String> propertyPaths){
689
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
690
        List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
691
        List<List<Synonym>> heterotypicSynonymyGroups = new ArrayList<List<Synonym>>(homotypicalGroups.size());
692
        for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
693
            heterotypicSynonymyGroups.add(t.getSynonymsInGroup(homotypicalGroup));
694
        }
695
        return heterotypicSynonymyGroups;
696
    }
697

    
698
    @Override
699
    public List<UuidAndTitleCache<TaxonBase>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator){
700

    
701
        List<UuidAndTitleCache<TaxonBase>> result = new ArrayList<UuidAndTitleCache<TaxonBase>>();
702
//        Class<? extends TaxonBase> clazz = null;
703
//        if ((configurator.isDoTaxa() && configurator.isDoSynonyms())) {
704
//            clazz = TaxonBase.class;
705
//            //propertyPath.addAll(configurator.getTaxonPropertyPath());
706
//            //propertyPath.addAll(configurator.getSynonymPropertyPath());
707
//        } else if(configurator.isDoTaxa()) {
708
//            clazz = Taxon.class;
709
//            //propertyPath = configurator.getTaxonPropertyPath();
710
//        } else if (configurator.isDoSynonyms()) {
711
//            clazz = Synonym.class;
712
//            //propertyPath = configurator.getSynonymPropertyPath();
713
//        }
714

    
715

    
716
        result = dao.getTaxaByNameForEditor(configurator.isDoTaxa(), configurator.isDoSynonyms(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
717
        return result;
718
    }
719

    
720
    /* (non-Javadoc)
721
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaAndNames(eu.etaxonomy.cdm.api.service.config.ITaxonServiceConfigurator)
722
     */
723
    @Override
724
    public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
725

    
726
        List<IdentifiableEntity> results = new ArrayList<IdentifiableEntity>();
727
        int numberOfResults = 0; // overall number of results (as opposed to number of results per page)
728
        List<TaxonBase> taxa = null;
729

    
730
        // Taxa and synonyms
731
        long numberTaxaResults = 0L;
732

    
733

    
734
        List<String> propertyPath = new ArrayList<String>();
735
        if(configurator.getTaxonPropertyPath() != null){
736
            propertyPath.addAll(configurator.getTaxonPropertyPath());
737
        }
738

    
739

    
740
       if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa()){
741
            if(configurator.getPageSize() != null){ // no point counting if we need all anyway
742
                numberTaxaResults =
743
                    dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
744
                        configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(),
745
                        configurator.getNamedAreas());
746
            }
747

    
748
            if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
749
                taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
750
                    configurator.isDoMisappliedNames(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(),
751
                    configurator.getMatchMode(), configurator.getNamedAreas(),
752
                    configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
753
            }
754
       }
755

    
756
        if (logger.isDebugEnabled()) { logger.debug(numberTaxaResults + " matching taxa counted"); }
757

    
758
        if(taxa != null){
759
            results.addAll(taxa);
760
        }
761

    
762
        numberOfResults += numberTaxaResults;
763

    
764
        // Names without taxa
765
        if (configurator.isDoNamesWithoutTaxa()) {
766
            int numberNameResults = 0;
767

    
768
            List<? extends TaxonNameBase<?,?>> names =
769
                nameDao.findByName(configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
770
                        configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
771
            if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
772
            if (names.size() > 0) {
773
                for (TaxonNameBase<?,?> taxonName : names) {
774
                    if (taxonName.getTaxonBases().size() == 0) {
775
                        results.add(taxonName);
776
                        numberNameResults++;
777
                    }
778
                }
779
                if (logger.isDebugEnabled()) { logger.debug(numberNameResults + " matching name(s) without taxa found"); }
780
                numberOfResults += numberNameResults;
781
            }
782
        }
783

    
784
        // Taxa from common names
785

    
786
        if (configurator.isDoTaxaByCommonNames()) {
787
            taxa = new ArrayList<TaxonBase>();
788
            numberTaxaResults = 0;
789
            if(configurator.getPageSize() != null){// no point counting if we need all anyway
790
                numberTaxaResults = dao.countTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
791
            }
792
            if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){
793
                List<Object[]> commonNameResults = dao.getTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas(), configurator.getPageSize(), configurator.getPageNumber(), configurator.getTaxonPropertyPath());
794
                for( Object[] entry : commonNameResults ) {
795
                    taxa.add((TaxonBase) entry[0]);
796
                }
797
            }
798
            if(taxa != null){
799
                results.addAll(taxa);
800
            }
801
            numberOfResults += numberTaxaResults;
802

    
803
        }
804

    
805
       return new DefaultPagerImpl<IdentifiableEntity>
806
            (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
807
    }
808

    
809
    public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(){
810
        return dao.getUuidAndTitleCache();
811
    }
812

    
813
    /* (non-Javadoc)
814
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllMedia(eu.etaxonomy.cdm.model.taxon.Taxon, int, int, int, java.lang.String[])
815
     */
816
    @Override
817
    public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
818
        List<MediaRepresentation> medRep = new ArrayList<MediaRepresentation>();
819
        taxon = (Taxon)dao.load(taxon.getUuid());
820
        Set<TaxonDescription> descriptions = taxon.getDescriptions();
821
        for (TaxonDescription taxDesc: descriptions){
822
            Set<DescriptionElementBase> elements = taxDesc.getElements();
823
            for (DescriptionElementBase descElem: elements){
824
                for(Media media : descElem.getMedia()){
825

    
826
                    //find the best matching representation
827
                    medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
828

    
829
                }
830
            }
831
        }
832
        return medRep;
833
    }
834

    
835
    /* (non-Javadoc)
836
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#listTaxonDescriptionMedia(eu.etaxonomy.cdm.model.taxon.Taxon, boolean)
837
     */
838
    @Override
839
    public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
840
        return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
841
    }
842

    
843

    
844
    /* (non-Javadoc)
845
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#listMedia(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.Set, boolean, java.util.List)
846
     */
847
    @Override
848
    public List<Media> listMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships,
849
            Boolean limitToGalleries, Boolean includeTaxonDescriptions, Boolean includeOccurrences,
850
            Boolean includeTaxonNameDescriptions, List<String> propertyPath) {
851

    
852
        logger.trace("listMedia() - START");
853

    
854
        Set<Taxon> taxa = new HashSet<Taxon>();
855
        List<Media> taxonMedia = new ArrayList<Media>();
856

    
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
                            taxonMedia.add(media);
881
                        }
882
                    }
883
                }
884
            }
885
        }
886

    
887

    
888
        if(includeOccurrences != null && includeOccurrences) {
889
            logger.trace("listMedia() - includeOccurrences");
890
            Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<SpecimenOrObservationBase>();
891
            // --- Specimens
892
            for (Taxon t : taxa) {
893
                specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
894
            }
895
            for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
896

    
897
//            	direct media removed from specimen #3597
898
//              taxonMedia.addAll(occurrence.getMedia());
899

    
900
                // SpecimenDescriptions
901
                Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
902
                for (DescriptionBase specimenDescription : specimenDescriptions) {
903
                    if (!limitToGalleries || specimenDescription.isImageGallery()) {
904
                        Set<DescriptionElementBase> elements = specimenDescription.getElements();
905
                        for (DescriptionElementBase element : elements) {
906
                            for (Media media : element.getMedia()) {
907
                                taxonMedia.add(media);
908
                            }
909
                        }
910
                    }
911
                }
912

    
913
                // Collection
914
                //TODO why may collections have media attached? #
915
                if (occurrence.isInstanceOf(DerivedUnit.class)) {
916
                    DerivedUnit derivedUnit = CdmBase.deproxy(occurrence, DerivedUnit.class);
917
                    if (derivedUnit.getCollection() != null){
918
                        taxonMedia.addAll(derivedUnit.getCollection().getMedia());
919
                    }
920
                }
921

    
922
                // pherograms & gelPhotos
923
                if (occurrence.isInstanceOf(DnaSample.class)) {
924
                    DnaSample dnaSample = CdmBase.deproxy(occurrence, DnaSample.class);
925
                    Set<Sequence> sequences = dnaSample.getSequences();
926
                    //we do show only those gelPhotos which lead to a consensus sequence
927
                    for (Sequence sequence : sequences) {
928
                        Set<Media> dnaRelatedMedia = new HashSet<Media>();
929
                        for (SingleRead singleRead : sequence.getSingleReads()){
930
                            Amplification amplification = singleRead.getAmplification();
931
                            dnaRelatedMedia.add(amplification.getGelPhoto());
932
                            dnaRelatedMedia.add(singleRead.getPherogram());
933
                            dnaRelatedMedia.remove(null);
934
                        }
935
                        taxonMedia.addAll(dnaRelatedMedia);
936
                    }
937
                }
938

    
939
            }
940
        }
941

    
942
        if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
943
            logger.trace("listMedia() - includeTaxonNameDescriptions");
944
            // --- TaxonNameDescription
945
            Set<TaxonNameDescription> nameDescriptions = new HashSet<TaxonNameDescription>();
946
            for (Taxon t : taxa) {
947
                nameDescriptions .addAll(t.getName().getDescriptions());
948
            }
949
            for(TaxonNameDescription nameDescription: nameDescriptions){
950
                if (!limitToGalleries || nameDescription.isImageGallery()) {
951
                    Set<DescriptionElementBase> elements = nameDescription.getElements();
952
                    for (DescriptionElementBase element : elements) {
953
                        for (Media media : element.getMedia()) {
954
                            taxonMedia.add(media);
955
                        }
956
                    }
957
                }
958
            }
959
        }
960

    
961

    
962
        logger.trace("listMedia() - initialize");
963
        beanInitializer.initializeAll(taxonMedia, propertyPath);
964

    
965
        logger.trace("listMedia() - END");
966

    
967
        return taxonMedia;
968
    }
969

    
970
    /* (non-Javadoc)
971
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaByID(java.util.Set)
972
     */
973
    @Override
974
    public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
975
        return this.dao.listByIds(listOfIDs, null, null, null, null);
976
    }
977

    
978
    /* (non-Javadoc)
979
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxonByUuid(UUID uuid, List<String> propertyPaths)
980
     */
981
    @Override
982
    public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
983
        return this.dao.findByUuid(uuid, null ,propertyPaths);
984
    }
985

    
986
    /* (non-Javadoc)
987
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#countAllRelationships()
988
     */
989
    @Override
990
    public int countAllRelationships() {
991
        return this.dao.countAllRelationships();
992
    }
993

    
994

    
995

    
996

    
997
    /* (non-Javadoc)
998
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNames(java.util.List)
999
     */
1000
    @Override
1001
    public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
1002
        return this.dao.findIdenticalTaxonNames(propertyPath);
1003
    }
1004

    
1005

    
1006
    /* (non-Javadoc)
1007
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator)
1008
     */
1009
    @Override
1010
    public String deleteTaxon(Taxon taxon, TaxonDeletionConfigurator config, Classification classification)  {
1011
        if (config == null){
1012
            config = new TaxonDeletionConfigurator();
1013
        }
1014
        
1015
        List<String> referencedObjects = isDeletable(taxon, config);
1016
        
1017
        if (referencedObjects.isEmpty()){
1018
            // --- DeleteSynonymRelations
1019
            if (config.isDeleteSynonymRelations()){
1020
                boolean removeSynonymNameFromHomotypicalGroup = false;
1021
                // use tmp Set to avoid concurrent modification
1022
                Set<SynonymRelationship> synRelsToDelete = new HashSet<SynonymRelationship>();
1023
                synRelsToDelete.addAll(taxon.getSynonymRelations());
1024
                for (SynonymRelationship synRel : synRelsToDelete){
1025
                    Synonym synonym = synRel.getSynonym();
1026
                    // taxon.removeSynonymRelation will set the accepted taxon and the synonym to NULL
1027
                    // this will cause hibernate to delete the relationship since
1028
                    // the SynonymRelationship field on both is annotated with removeOrphan
1029
                    // so no further explicit deleting of the relationship should be done here
1030
                    taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
1031

    
1032
                    // --- DeleteSynonymsIfPossible
1033
                    if (config.isDeleteSynonymsIfPossible()){
1034
                        //TODO which value
1035
                        boolean newHomotypicGroupIfNeeded = true;
1036
                        SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
1037
                        deleteSynonym(synonym, taxon, synConfig);
1038
                    }
1039
                    // relationship will be deleted by hibernate automatically,
1040
                    // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797
1041
                    // else{
1042
                    //     deleteSynonymRelationships(synonym, taxon);
1043
                    // }
1044
                }
1045
            }
1046

    
1047
            // --- DeleteTaxonRelationships
1048
            if (! config.isDeleteTaxonRelationships()){
1049
                if (taxon.getTaxonRelations().size() > 0){
1050
                    String message = "Taxon can't be deleted as it is related to another taxon. " +
1051
                            "Remove taxon from all relations to other taxa prior to deletion.";
1052
                   // throw new ReferencedObjectUndeletableException(message);
1053
                }
1054
            } else{
1055
                for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
1056
                    if (config.isDeleteMisappliedNamesAndInvalidDesignations()){
1057
                        if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){
1058
                            if (taxon.equals(taxRel.getToTaxon())){
1059
                                this.deleteTaxon(taxRel.getFromTaxon(), config, classification);
1060
                            }
1061
                        }
1062
                    }
1063
                    taxon.removeTaxonRelation(taxRel);
1064
                    /*if (taxFrom.equals(taxon)){
1065
                        try{
1066
                            this.deleteTaxon(taxTo, taxConf, classification);
1067
                        } catch(DataChangeNoRollbackException e){
1068
                            logger.debug("A related taxon will not be deleted." + e.getMessage());
1069
                        }
1070
                    } else {
1071
                        try{
1072
                            this.deleteTaxon(taxFrom, taxConf, classification);
1073
                        } catch(DataChangeNoRollbackException e){
1074
                            logger.debug("A related taxon will not be deleted." + e.getMessage());
1075
                        }
1076

    
1077
                    }*/
1078
                }
1079
            }
1080

    
1081
            //    	TaxonDescription
1082
            if (config.isDeleteDescriptions()){
1083
                Set<TaxonDescription> descriptions = taxon.getDescriptions();
1084
                List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1085
                for (TaxonDescription desc: descriptions){
1086
                    //TODO use description delete configurator ?
1087
                    //FIXME check if description is ALWAYS deletable
1088
                    if (desc.getDescribedSpecimenOrObservation() != null){
1089
                        String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1090
                                " which also describes specimens or abservations";
1091
                        //throw new ReferencedObjectUndeletableException(message);
1092
                    }
1093
                    removeDescriptions.add(desc);
1094
                    descriptionService.delete(desc);
1095

    
1096
                }
1097
                for (TaxonDescription desc: removeDescriptions){
1098
                    taxon.removeDescription(desc);
1099
                }
1100
            }
1101

    
1102

    
1103
         /*   //check references with only reverse mapping
1104
        String message = checkForReferences(taxon);
1105
        if (message != null){
1106
            //throw new ReferencedObjectUndeletableException(message.toString());
1107
        }*/
1108

    
1109
         if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null )){
1110
                //if (taxon.getTaxonNodes().size() > 0){
1111
                   // 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.";
1112
                   // throw new ReferencedObjectUndeletableException(message);
1113
                //}
1114
            }else{
1115
                if (taxon.getTaxonNodes().size() != 0){
1116
                    Set<TaxonNode> nodes = taxon.getTaxonNodes();
1117
                    Iterator<TaxonNode> iterator = nodes.iterator();
1118
                    TaxonNode node = null;
1119
                    boolean deleteChildren;
1120
                    if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1121
                        deleteChildren = true;
1122
                    }else {
1123
                        deleteChildren = false;
1124
                    }
1125
                    boolean success = true;
1126
                    if (!config.isDeleteInAllClassifications() && !(classification == null)){
1127
                        while (iterator.hasNext()){
1128
                            node = iterator.next();
1129
                            if (node.getClassification().equals(classification)){
1130
                                break;
1131
                            }
1132
                            node = null;
1133
                        }
1134
                        if (node != null){
1135
                            success =taxon.removeTaxonNode(node, deleteChildren);
1136
                            nodeService.delete(node);
1137
                        } else {
1138
                           // message = "Taxon is not used in defined classification";
1139
                           // throw new DataChangeNoRollbackException(message);
1140
                        }
1141
                    } else if (config.isDeleteInAllClassifications()){
1142
                        Set<ITaxonTreeNode> nodesList = new HashSet<ITaxonTreeNode>();
1143
                        nodesList.addAll(taxon.getTaxonNodes());
1144

    
1145
                            for (ITaxonTreeNode treeNode: nodesList){
1146
                                TaxonNode taxonNode = (TaxonNode) treeNode;
1147
                                if(!deleteChildren){
1148
                                   /* Object[] childNodes = taxonNode.getChildNodes().toArray();
1149
                                    //nodesList.addAll(taxonNode.getChildNodes());
1150
                                    for (Object childNode: childNodes){
1151
                                        TaxonNode childNodeCast = (TaxonNode) childNode;
1152
                                        deleteTaxon(childNodeCast.getTaxon(), config, classification);
1153

    
1154
                                    }
1155

    
1156
                                    /*for (TaxonNode childNode: taxonNode.getChildNodes()){
1157
                                        deleteTaxon(childNode.getTaxon(), config, classification);
1158

    
1159
                                    }
1160
                                   // taxon.removeTaxonNode(taxonNode);
1161
                                    //nodeService.delete(taxonNode);
1162
                                } else{
1163
                                    */
1164
                                    Object[] childNodes = taxonNode.getChildNodes().toArray();
1165
                                    for (Object childNode: childNodes){
1166
                                        TaxonNode childNodeCast = (TaxonNode) childNode;
1167
                                        taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1168
                                    }
1169

    
1170
                                    //taxon.removeTaxonNode(taxonNode);
1171
                                }
1172
                            }
1173
                        config.getTaxonNodeConfig().setDeleteTaxon(false);
1174
                        nodeService.deleteTaxonNodes(nodesList, config);
1175
                    }
1176
                    if (!success){
1177
                        // message = "The taxon node could not be deleted.";
1178
                        //throw new DataChangeNoRollbackException(message);
1179
                    }
1180
                }
1181
            }
1182

    
1183

    
1184
             //PolytomousKey TODO
1185

    
1186
             boolean usedInPolytomousKey = checkForPolytomousKeys(taxon);
1187
            //TaxonNameBase
1188
            if (config.isDeleteNameIfPossible()){
1189
                
1190

    
1191
                    //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1192
                    TaxonNameBase name = (TaxonNameBase)HibernateProxyHelper.deproxy(taxon.getName());
1193
                    //check whether taxon will be deleted or not
1194
                    if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){
1195
                        taxon = (Taxon) HibernateProxyHelper.deproxy(taxon);
1196
                        name.removeTaxonBase(taxon);
1197
                        nameService.save(name);
1198
                        String uuidString = nameService.delete(name, config.getNameDeletionConfig());
1199
                        logger.debug(uuidString);
1200
                    }
1201
                
1202
            }
1203

    
1204
//        	TaxonDescription
1205
           /* Set<TaxonDescription> descriptions = taxon.getDescriptions();
1206

    
1207
            for (TaxonDescription desc: descriptions){
1208
                if (config.isDeleteDescriptions()){
1209
                    //TODO use description delete configurator ?
1210
                    //FIXME check if description is ALWAYS deletable
1211
                    taxon.removeDescription(desc);
1212
                    descriptionService.delete(desc);
1213
                }else{
1214
                    if (desc.getDescribedSpecimenOrObservations().size()>0){
1215
                        String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1216
                                " which also describes specimens or observations";
1217
                            throw new ReferencedObjectUndeletableException(message);
1218
    }
1219
                    }
1220
                }*/
1221
        
1222
            if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0)  ){
1223
                UUID uuid = dao.delete(taxon);
1224
                return uuid.toString();
1225
            } else {
1226
            	return "The Taxon can't be deleted.";
1227
            }
1228
        }else {
1229
        	 return referencedObjects.toString();
1230
        }
1231

    
1232

    
1233
    }
1234

    
1235
    private String checkForReferences(Taxon taxon){
1236
        Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1237
        for (CdmBase referencingObject : referencingObjects){
1238
            //IIdentificationKeys (Media, Polytomous, MultiAccess)
1239
            if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1240
                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";
1241

    
1242
                return message;
1243
            }
1244

    
1245

    
1246
           /* //PolytomousKeyNode
1247
            if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1248
                String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1249
                return message;
1250
            }*/
1251

    
1252
            //TaxonInteraction
1253
            if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1254
                String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1255
                return message;
1256
            }
1257

    
1258
          //TaxonInteraction
1259
            if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1260
                String message = "Taxon can't be deleted as it is used in a determination event";
1261
                return message;
1262
            }
1263

    
1264
        }
1265

    
1266
        referencingObjects = null;
1267
        return null;
1268
    }
1269

    
1270
    private boolean checkForPolytomousKeys(Taxon taxon){
1271
        boolean result = false;
1272
        List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1273
        if (!list.isEmpty()) {
1274
            result = true;
1275
        }
1276
        return result;
1277
    }
1278

    
1279
    @Transactional(readOnly = false)
1280
    public UUID delete(Synonym syn){
1281
        UUID result = syn.getUuid();
1282
        this.deleteSynonym(syn, null);
1283
        return result;
1284
    }
1285

    
1286
    /* (non-Javadoc)
1287
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1288
     */
1289
    @Transactional(readOnly = false)
1290
    @Override
1291
    public String deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1292
        return deleteSynonym(synonym, null, config);
1293

    
1294
    }
1295

    
1296

    
1297
    /* (non-Javadoc)
1298
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1299
     */
1300
    @Transactional(readOnly = false)
1301
    @Override
1302
    public String deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1303
        if (synonym == null){
1304
            return null;
1305
        }
1306
        
1307
        if (config == null){
1308
            config = new SynonymDeletionConfigurator();
1309
        }
1310
        List<String> messages = isDeletable(synonym, config);
1311
        if (messages.isEmpty()){
1312
	        synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
1313
	
1314
	        //remove synonymRelationship
1315
	        Set<Taxon> taxonSet = new HashSet<Taxon>();
1316
	        if (taxon != null){
1317
	            taxonSet.add(taxon);
1318
	        }else{
1319
	            taxonSet.addAll(synonym.getAcceptedTaxa());
1320
	        }
1321
	        for (Taxon relatedTaxon : taxonSet){
1322
	//			dao.deleteSynonymRelationships(synonym, relatedTaxon);
1323
	            relatedTaxon.removeSynonym(synonym, config.isNewHomotypicGroupIfNeeded());
1324
	        }
1325
	        this.saveOrUpdate(synonym);
1326
	
1327
	        //TODO remove name from homotypical group?
1328
	
1329
	        //remove synonym (if necessary)
1330
	
1331
	        UUID uuid = null;
1332
	        if (synonym.getSynonymRelations().isEmpty()){
1333
	            TaxonNameBase<?,?> name = synonym.getName();
1334
	            synonym.setName(null);
1335
	            uuid = dao.delete(synonym);
1336
	
1337
	            //remove name if possible (and required)
1338
	            if (name != null && config.isDeleteNameIfPossible()){
1339
	                
1340
	                    nameService.delete(name, config.getNameDeletionConfig());
1341
	                
1342
	            }
1343
	            
1344
	        }else {
1345
	        	return null;
1346
	        }
1347
	        return uuid.toString();
1348
        }else{
1349
        	return messages.toString();
1350
        }
1351
        
1352
     
1353
    }
1354

    
1355

    
1356
    /* (non-Javadoc)
1357
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNameIds(java.util.List)
1358
     */
1359
    @Override
1360
    public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1361

    
1362
        return this.dao.findIdenticalNamesNew(propertyPath);
1363
    }
1364

    
1365
    /* (non-Javadoc)
1366
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getPhylumName(eu.etaxonomy.cdm.model.name.TaxonNameBase)
1367
     */
1368
    @Override
1369
    public String getPhylumName(TaxonNameBase name){
1370
        return this.dao.getPhylumName(name);
1371
    }
1372

    
1373
    /* (non-Javadoc)
1374
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
1375
     */
1376
    @Override
1377
    public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1378
        return dao.deleteSynonymRelationships(syn, taxon);
1379
    }
1380

    
1381
/* (non-Javadoc)
1382
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym)
1383
     */
1384
    @Override
1385
    public long deleteSynonymRelationships(Synonym syn) {
1386
        return dao.deleteSynonymRelationships(syn, null);
1387
    }
1388

    
1389

    
1390
    /* (non-Javadoc)
1391
     * @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)
1392
     */
1393
    @Override
1394
    public List<SynonymRelationship> listSynonymRelationships(
1395
            TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1396
            List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1397
        Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1398

    
1399
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1400
        if(numberOfResults > 0) { // no point checking again
1401
            results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1402
        }
1403
        return results;
1404
    }
1405

    
1406
    /* (non-Javadoc)
1407
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingTaxon(java.lang.String)
1408
     */
1409
    @Override
1410
    public Taxon findBestMatchingTaxon(String taxonName) {
1411
        MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1412
        config.setTaxonNameTitle(taxonName);
1413
        return findBestMatchingTaxon(config);
1414
    }
1415

    
1416

    
1417

    
1418
    @Override
1419
    public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1420

    
1421
        Taxon bestCandidate = null;
1422
        try{
1423
            // 1. search for acceptet taxa
1424
            List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1425
            boolean bestCandidateMatchesSecUuid = false;
1426
            boolean bestCandidateIsInClassification = false;
1427
            int countEqualCandidates = 0;
1428
            for(TaxonBase taxonBaseCandidate : taxonList){
1429
                if(taxonBaseCandidate instanceof Taxon){
1430
                    Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1431
                    boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1432
                    if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1433
                        continue;
1434
                    }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1435
                        bestCandidate = newCanditate;
1436
                        countEqualCandidates = 1;
1437
                        bestCandidateMatchesSecUuid = true;
1438
                        continue;
1439
                    }
1440

    
1441
                    boolean newCandidateInClassification = isInClassification(newCanditate, config);
1442
                    if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1443
                        continue;
1444
                    }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1445
                        bestCandidate = newCanditate;
1446
                        countEqualCandidates = 1;
1447
                        bestCandidateIsInClassification = true;
1448
                        continue;
1449
                    }
1450
                    if (bestCandidate == null){
1451
                        bestCandidate = newCanditate;
1452
                        countEqualCandidates = 1;
1453
                        continue;
1454
                    }
1455

    
1456
                }else{  //not Taxon.class
1457
                    continue;
1458
                }
1459
                countEqualCandidates++;
1460

    
1461
            }
1462
            if (bestCandidate != null){
1463
                if(countEqualCandidates > 1){
1464
                    logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1465
                    return bestCandidate;
1466
                } else {
1467
                    logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1468
                    return bestCandidate;
1469
                }
1470
            }
1471

    
1472

    
1473
            // 2. search for synonyms
1474
            if (config.isIncludeSynonyms()){
1475
                List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1476
                for(TaxonBase taxonBase : synonymList){
1477
                    if(taxonBase instanceof Synonym){
1478
                        Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1479
                        Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1480
                        if(!acceptetdCandidates.isEmpty()){
1481
                            bestCandidate = acceptetdCandidates.iterator().next();
1482
                            if(acceptetdCandidates.size() == 1){
1483
                                logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1484
                                return bestCandidate;
1485
                            } else {
1486
                                logger.info("using accepted Taxon " +  bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1487
                                return bestCandidate;
1488
                            }
1489
                            //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1490
                        }
1491
                    }
1492
                }
1493
            }
1494

    
1495
        } catch (Exception e){
1496
            logger.error(e);
1497
            e.printStackTrace();
1498
        }
1499

    
1500
        return bestCandidate;
1501
    }
1502

    
1503
    private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1504
        UUID configClassificationUuid = config.getClassificationUuid();
1505
        if (configClassificationUuid == null){
1506
            return false;
1507
        }
1508
        for (TaxonNode node : taxon.getTaxonNodes()){
1509
            UUID classUuid = node.getClassification().getUuid();
1510
            if (configClassificationUuid.equals(classUuid)){
1511
                return true;
1512
            }
1513
        }
1514
        return false;
1515
    }
1516

    
1517
    private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1518
        UUID configSecUuid = config.getSecUuid();
1519
        if (configSecUuid == null){
1520
            return false;
1521
        }
1522
        UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1523
        return configSecUuid.equals(taxonSecUuid);
1524
    }
1525

    
1526
    /* (non-Javadoc)
1527
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingSynonym(java.lang.String)
1528
     */
1529
    @Override
1530
    public Synonym findBestMatchingSynonym(String taxonName) {
1531
        List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1532
        if(! synonymList.isEmpty()){
1533
            Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1534
            if(synonymList.size() == 1){
1535
                logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1536
                return result;
1537
            } else {
1538
                logger.info("Several matching synonyms found. Using first: " +  result.getTitleCache());
1539
                return result;
1540
            }
1541
        }
1542
        return null;
1543
    }
1544

    
1545

    
1546
    /* (non-Javadoc)
1547
     * @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)
1548
     */
1549
    @Override
1550
    public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, Taxon newTaxon, boolean moveHomotypicGroup,
1551
            SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
1552

    
1553
        Synonym synonym = oldSynonymRelation.getSynonym();
1554
        Taxon fromTaxon = oldSynonymRelation.getAcceptedTaxon();
1555
        //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1556
        TaxonNameBase<?,?> synonymName = synonym.getName();
1557
        TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1558
        //set default relationship type
1559
        if (newSynonymRelationshipType == null){
1560
            newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1561
        }
1562
        boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1563

    
1564
        HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1565
        int hgSize = homotypicGroup.getTypifiedNames().size();
1566
        boolean isSingleInGroup = !(hgSize > 1);
1567

    
1568
        if (! isSingleInGroup){
1569
            boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1570
            boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1571
            if (isHomotypicToAccepted){
1572
                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.";
1573
                String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1574
                message = String.format(message, homotypicRelatives);
1575
                throw new HomotypicalGroupChangeException(message);
1576
            }
1577
            if (! moveHomotypicGroup){
1578
                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.";
1579
                throw new HomotypicalGroupChangeException(message);
1580
            }
1581
        }else{
1582
            moveHomotypicGroup = true;  //single synonym always allows to moveCompleteGroup
1583
        }
1584
//        Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1585

    
1586
        SynonymRelationship result = null;
1587
        //move all synonyms to new taxon
1588
        List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1589
        for (Synonym syn: homotypicSynonyms){
1590
            Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1591
            for (SynonymRelationship synRelation : synRelations){
1592
                if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1593
                    Reference<?> newReference = reference;
1594
                    if (newReference == null && keepReference){
1595
                        newReference = synRelation.getCitation();
1596
                    }
1597
                    String newRefDetail = referenceDetail;
1598
                    if (newRefDetail == null && keepReference){
1599
                        newRefDetail = synRelation.getCitationMicroReference();
1600
                    }
1601
                    SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1602
                    fromTaxon.removeSynonymRelation(synRelation, false);
1603
//
1604
                    //change homotypic group of synonym if relType is 'homotypic'
1605
//                	if (newRelTypeIsHomotypic){
1606
//                		newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1607
//                	}
1608
                    //set result
1609
                    if (synRelation.equals(oldSynonymRelation)){
1610
                        result = newSynRelation;
1611
                    }
1612
                }
1613
            }
1614

    
1615
        }
1616
        saveOrUpdate(newTaxon);
1617
        //Assert that there is a result
1618
        if (result == null){
1619
            String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1620
            throw new IllegalStateException(message);
1621
        }
1622
        return result;
1623
    }
1624

    
1625
    /* (non-Javadoc)
1626
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheTaxon()
1627
     */
1628
    @Override
1629
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1630
        return dao.getUuidAndTitleCacheTaxon();
1631
    }
1632

    
1633
    /* (non-Javadoc)
1634
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheSynonym()
1635
     */
1636
    @Override
1637
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1638
        return dao.getUuidAndTitleCacheSynonym();
1639
    }
1640

    
1641
    /* (non-Javadoc)
1642
     * @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)
1643
     */
1644
    @Override
1645
    public Pager<SearchResult<TaxonBase>> findByFullText(
1646
            Class<? extends TaxonBase> clazz, String queryString,
1647
            Classification classification, List<Language> languages,
1648
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1649

    
1650

    
1651
        LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1652

    
1653
        // --- execute search
1654
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1655

    
1656
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1657
        idFieldMap.put(CdmBaseType.TAXON, "id");
1658

    
1659
        // ---  initialize taxa, thighlight matches ....
1660
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1661
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1662
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1663

    
1664
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1665
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1666
    }
1667

    
1668
    @Override
1669
    public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTermBase<?>> statusFilter,
1670
            Classification classification,
1671
            Integer pageSize, Integer pageNumber,
1672
            List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1673

    
1674
        LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1675

    
1676
        // --- execute search
1677
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1678

    
1679
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1680
        idFieldMap.put(CdmBaseType.TAXON, "id");
1681

    
1682
        // ---  initialize taxa, thighlight matches ....
1683
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1684
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1685
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1686

    
1687
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1688
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1689
    }
1690

    
1691
    /**
1692
     * @param clazz
1693
     * @param queryString
1694
     * @param classification
1695
     * @param languages
1696
     * @param highlightFragments
1697
     * @param sortFields TODO
1698
     * @param directorySelectClass
1699
     * @return
1700
     */
1701
    protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1702
            boolean highlightFragments, SortField[] sortFields) {
1703
        BooleanQuery finalQuery = new BooleanQuery();
1704
        BooleanQuery textQuery = new BooleanQuery();
1705

    
1706
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1707
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1708

    
1709
        if(sortFields == null){
1710
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
1711
        }
1712
        luceneSearch.setSortFields(sortFields);
1713

    
1714
        // ---- search criteria
1715
        luceneSearch.setCdmTypRestriction(clazz);
1716

    
1717
        textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1718
        textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1719

    
1720
        finalQuery.add(textQuery, Occur.MUST);
1721

    
1722
        if(classification != null){
1723
            finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1724
        }
1725
        luceneSearch.setQuery(finalQuery);
1726

    
1727
        if(highlightFragments){
1728
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1729
        }
1730
        return luceneSearch;
1731
    }
1732

    
1733
    /**
1734
     * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1735
     * the BlockJoinQuery could be used. The latter might be more memory save but has the
1736
     * drawback of requiring to do the join an indexing time.
1737
     * see  http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1738
     *
1739
     * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1740
     * <ul>
1741
     * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1742
     * <li>inverse: {@link Direction.relatedFrom}:  TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1743
     * <ul>
1744
     * @param queryString
1745
     * @param classification
1746
     * @param languages
1747
     * @param highlightFragments
1748
     * @param sortFields TODO
1749
     *
1750
     * @return
1751
     * @throws IOException
1752
     */
1753
    protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1754
            boolean highlightFragments, SortField[] sortFields) throws IOException {
1755

    
1756
        String fromField;
1757
        String queryTermField;
1758
        String toField = "id"; // TaxonBase.uuid
1759

    
1760
        if(edge.isBidirectional()){
1761
            throw new RuntimeException("Bidirectional joining not supported!");
1762
        }
1763
        if(edge.isEvers()){
1764
            fromField = "relatedFrom.id";
1765
            queryTermField = "relatedFrom.titleCache";
1766
        } else if(edge.isInvers()) {
1767
            fromField = "relatedTo.id";
1768
            queryTermField = "relatedTo.titleCache";
1769
        } else {
1770
            throw new RuntimeException("Invalid direction: " + edge.getDirections());
1771
        }
1772

    
1773
        BooleanQuery finalQuery = new BooleanQuery();
1774

    
1775
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1776
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1777

    
1778
        BooleanQuery joinFromQuery = new BooleanQuery();
1779
        joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1780
        joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1781
        Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1782

    
1783
        if(sortFields == null){
1784
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
1785
        }
1786
        luceneSearch.setSortFields(sortFields);
1787

    
1788
        finalQuery.add(joinQuery, Occur.MUST);
1789

    
1790
        if(classification != null){
1791
            finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1792
        }
1793
        luceneSearch.setQuery(finalQuery);
1794

    
1795
        if(highlightFragments){
1796
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1797
        }
1798
        return luceneSearch;
1799
    }
1800

    
1801

    
1802

    
1803

    
1804
    /* (non-Javadoc)
1805
     * @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)
1806
     */
1807
    @Override
1808
    public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1809
            EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1810
            Set<NamedArea> namedAreas, Set<PresenceAbsenceTermBase<?>> distributionStatus, List<Language> languages,
1811
            boolean highlightFragments, Integer pageSize,
1812
            Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1813
            throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1814

    
1815
        // FIXME: allow taxonomic ordering
1816
        //  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";
1817
        // this require building a special sort column by a special classBridge
1818
        if(highlightFragments){
1819
            logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1820
                    "currently not fully supported by this method and thus " +
1821
                    "may not work with common names and misapplied names.");
1822
        }
1823

    
1824
        // convert sets to lists
1825
        List<NamedArea> namedAreaList = null;
1826
        List<PresenceAbsenceTermBase<?>>distributionStatusList = null;
1827
        if(namedAreas != null){
1828
            namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1829
            namedAreaList.addAll(namedAreas);
1830
        }
1831
        if(distributionStatus != null){
1832
            distributionStatusList = new ArrayList<PresenceAbsenceTermBase<?>>(distributionStatus.size());
1833
            distributionStatusList.addAll(distributionStatus);
1834
        }
1835

    
1836
        // set default if parameter is null
1837
        if(searchModes == null){
1838
            searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1839
        }
1840

    
1841
        // set sort order and thus override any sort orders which may have been
1842
        // defindes by prepare*Search methods
1843
        if(orderHints == null){
1844
            orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER;
1845
        }
1846
        SortField[] sortFields = new SortField[orderHints.size()];
1847
        int i = 0;
1848
        for(OrderHint oh : orderHints){
1849
            sortFields[i++] = oh.toSortField();
1850
        }
1851
//        SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1852
//        SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1853

    
1854

    
1855
        boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1856

    
1857
        List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1858
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1859

    
1860
        /*
1861
          ======== filtering by distribution , HOWTO ========
1862

    
1863
           - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html
1864
           - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter
1865
          add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html
1866
          which will be put into a FilteredQuersy  in the end ?
1867

    
1868

    
1869
          3. how does it work in spatial?
1870
          see
1871
           - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1872
           - http://www.infoq.com/articles/LuceneSpatialSupport
1873
           - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1874
          ------------------------------------------------------------------------
1875

    
1876
          filter strategies:
1877
          A) use a separate distribution filter per index sub-query/search:
1878
           - byTaxonSyonym (query TaxaonBase):
1879
               use a join area filter (Distribution -> TaxonBase)
1880
           - byCommonName (query DescriptionElementBase): use an area filter on
1881
               DescriptionElementBase !!! PROBLEM !!!
1882
               This cannot work since the distributions are different entities than the
1883
               common names and thus these are different lucene documents.
1884
           - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1885
               use a join area filter (Distribution -> TaxonBase)
1886

    
1887
          B) use a common distribution filter for all index sub-query/searches:
1888
           - use a common join area filter (Distribution -> TaxonBase)
1889
           - also implement the byCommonName as join query (CommonName -> TaxonBase)
1890
           PROBLEM in this case: we are losing the fragment highlighting for the
1891
           common names, since the returned documents are always TaxonBases
1892
        */
1893

    
1894
        /* The QueryFactory for creating filter queries on Distributions should
1895
         * The query factory used for the common names query cannot be reused
1896
         * for this case, since we want to only record the text fields which are
1897
         * actually used in the primary query
1898
         */
1899
        QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1900

    
1901
        BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1902

    
1903

    
1904
        // search for taxa or synonyms
1905
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1906
            Class taxonBaseSubclass = TaxonBase.class;
1907
            if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1908
                taxonBaseSubclass = Taxon.class;
1909
            } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1910
                taxonBaseSubclass = Synonym.class;
1911
            }
1912
            luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1913
            idFieldMap.put(CdmBaseType.TAXON, "id");
1914
            /* A) does not work!!!!
1915
            if(addDistributionFilter){
1916
                // in this case we need a filter which uses a join query
1917
                // to get the TaxonBase documents for the DescriptionElementBase documents
1918
                // which are matching the areas in question
1919
                Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1920
                        namedAreaList,
1921
                        distributionStatusList,
1922
                        distributionFilterQueryFactory
1923
                        );
1924
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1925
            }
1926
            */
1927
            if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1928
                // add additional area filter for synonyms
1929
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1930
                String toField = "accTaxon.id"; // id in TaxonBase index
1931

    
1932
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1933

    
1934
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1935
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1936

    
1937
            }
1938
        }
1939

    
1940
        // search by CommonTaxonName
1941
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1942
            // B)
1943
            QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1944
            Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1945
                    "inDescription.taxon.id",
1946
                    "id",
1947
                    QueryFactory.addTypeRestriction(
1948
                                createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
1949
                                , CommonTaxonName.class
1950
                                ),
1951
                    CommonTaxonName.class);
1952
            logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
1953
            LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
1954
            byCommonNameSearch.setCdmTypRestriction(Taxon.class);
1955
            byCommonNameSearch.setQuery(byCommonNameJoinQuery);
1956
            byCommonNameSearch.setSortFields(sortFields);
1957
            idFieldMap.put(CdmBaseType.TAXON, "id");
1958

    
1959
            luceneSearches.add(byCommonNameSearch);
1960

    
1961
            /* A) does not work!!!!
1962
            luceneSearches.add(
1963
                    prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1964
                            queryString, classification, null, languages, highlightFragments)
1965
                        );
1966
            idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1967
            if(addDistributionFilter){
1968
                // in this case we are able to use DescriptionElementBase documents
1969
                // which are matching the areas in question directly
1970
                BooleanQuery byDistributionQuery = createByDistributionQuery(
1971
                        namedAreaList,
1972
                        distributionStatusList,
1973
                        distributionFilterQueryFactory
1974
                        );
1975
                multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1976
            } */
1977
        }
1978

    
1979
        // search by misapplied names
1980
        if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
1981
            // NOTE:
1982
            // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1983
            // which allows doing query time joins
1984
            // finds the misapplied name (Taxon B) which is an misapplication for
1985
            // a related Taxon A.
1986
            //
1987
            luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
1988
                    new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
1989
                    queryString, classification, languages, highlightFragments, sortFields));
1990
            idFieldMap.put(CdmBaseType.TAXON, "id");
1991

    
1992
            if(addDistributionFilter){
1993
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1994

    
1995
                /*
1996
                 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
1997
                 * Maybe this is a but in java itself java.
1998
                 *
1999
                 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2000
                 * directly:
2001
                 *
2002
                 *    String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2003
                 *
2004
                 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2005
                 * will execute as expected:
2006
                 *
2007
                 *    String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2008
                 *    String toField = "relation." + misappliedNameForUuid +".to.id";
2009
                 *
2010
                 * Comparing both strings by the String.equals method returns true, so both String are identical.
2011
                 *
2012
                 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2013
                 * 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)
2014
                 * The bug is persistent after a reboot of the development computer.
2015
                 */
2016
//                String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2017
//                String toField = "relation." + misappliedNameForUuid +".to.id";
2018
                String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2019
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2020
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2021

    
2022
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2023
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2024
                QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery);
2025

    
2026
//                debug code for bug described above
2027
                DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2028
//                System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2029

    
2030
                multiIndexByAreaFilter.add(filter, Occur.SHOULD);
2031
            }
2032
        }
2033

    
2034
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
2035
                luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
2036

    
2037

    
2038
        if(addDistributionFilter){
2039

    
2040
            // B)
2041
            // in this case we need a filter which uses a join query
2042
            // to get the TaxonBase documents for the DescriptionElementBase documents
2043
            // which are matching the areas in question
2044
            //
2045
            // for toTaxa, doByCommonName
2046
            Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2047
                    namedAreaList,
2048
                    distributionStatusList,
2049
                    distributionFilterQueryFactory
2050
                    );
2051
            multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
2052
        }
2053

    
2054
        if (addDistributionFilter){
2055
            multiSearch.setFilter(multiIndexByAreaFilter);
2056
        }
2057

    
2058

    
2059
        // --- execute search
2060
        TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2061

    
2062
        // --- initialize taxa, highlight matches ....
2063
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2064

    
2065

    
2066
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2067
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2068

    
2069
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2070
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2071
    }
2072

    
2073
    /**
2074
     * @param namedAreaList at least one area must be in the list
2075
     * @param distributionStatusList optional
2076
     * @return
2077
     * @throws IOException
2078
     */
2079
    protected Query createByDistributionJoinQuery(
2080
            List<NamedArea> namedAreaList,
2081
            List<PresenceAbsenceTermBase<?>> distributionStatusList,
2082
            QueryFactory queryFactory
2083
            ) throws IOException {
2084

    
2085
        String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2086
        String toField = "id"; // id in TaxonBase index
2087

    
2088
        BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2089

    
2090
        Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2091

    
2092
        return taxonAreaJoinQuery;
2093
    }
2094

    
2095
    /**
2096
     * @param namedAreaList
2097
     * @param distributionStatusList
2098
     * @param queryFactory
2099
     * @return
2100
     */
2101
    private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2102
            List<PresenceAbsenceTermBase<?>> distributionStatusList, QueryFactory queryFactory) {
2103
        BooleanQuery areaQuery = new BooleanQuery();
2104
        // area field from Distribution
2105
        areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2106

    
2107
        // status field from Distribution
2108
        if(distributionStatusList != null && distributionStatusList.size() > 0){
2109
            areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2110
        }
2111

    
2112
        logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2113
        return areaQuery;
2114
    }
2115

    
2116
    /**
2117
     * This method has been primarily created for testing the area join query but might
2118
     * also be useful in other situations
2119
     *
2120
     * @param namedAreaList
2121
     * @param distributionStatusList
2122
     * @param classification
2123
     * @param highlightFragments
2124
     * @return
2125
     * @throws IOException
2126
     */
2127
    protected LuceneSearch prepareByDistributionSearch(
2128
            List<NamedArea> namedAreaList, List<PresenceAbsenceTermBase<?>> distributionStatusList,
2129
            Classification classification) throws IOException {
2130

    
2131
        BooleanQuery finalQuery = new BooleanQuery();
2132

    
2133
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2134

    
2135
        // FIXME is this query factory using the wrong type?
2136
        QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2137

    
2138
        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
2139
        luceneSearch.setSortFields(sortFields);
2140

    
2141

    
2142
        Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory);
2143

    
2144
        finalQuery.add(byAreaQuery, Occur.MUST);
2145

    
2146
        if(classification != null){
2147
            finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2148
        }
2149

    
2150
        logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2151
        luceneSearch.setQuery(finalQuery);
2152

    
2153
        return luceneSearch;
2154
    }
2155

    
2156

    
2157

    
2158
    /* (non-Javadoc)
2159
     * @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)
2160
     */
2161
    @Override
2162
    public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2163
            Class<? extends DescriptionElementBase> clazz, String queryString,
2164
            Classification classification, List<Feature> features, List<Language> languages,
2165
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2166

    
2167

    
2168
        LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2169

    
2170
        // --- execute search
2171
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2172

    
2173
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2174
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2175

    
2176
        // --- initialize taxa, highlight matches ....
2177
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2178
        @SuppressWarnings("rawtypes")
2179
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2180
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2181

    
2182
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2183
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2184

    
2185
    }
2186

    
2187

    
2188
    @Override
2189
    public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2190
            Classification classification, List<Language> languages, boolean highlightFragments,
2191
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2192

    
2193
        LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2194
        LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2195

    
2196
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2197

    
2198
        // --- execute search
2199
        TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2200

    
2201
        // --- initialize taxa, highlight matches ....
2202
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2203

    
2204
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2205
        idFieldMap.put(CdmBaseType.TAXON, "id");
2206
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2207

    
2208
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2209
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2210

    
2211
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2212
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2213

    
2214
    }
2215

    
2216

    
2217
    /**
2218
     * @param clazz
2219
     * @param queryString
2220
     * @param classification
2221
     * @param features
2222
     * @param languages
2223
     * @param highlightFragments
2224
     * @param directorySelectClass
2225
     * @return
2226
     */
2227
    protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2228
            String queryString, Classification classification, List<Feature> features,
2229
            List<Language> languages, boolean highlightFragments) {
2230

    
2231
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2232
        QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2233

    
2234
        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.STRING, false)};
2235

    
2236
        BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2237
                languages, descriptionElementQueryFactory);
2238

    
2239
        luceneSearch.setSortFields(sortFields);
2240
        luceneSearch.setCdmTypRestriction(clazz);
2241
        luceneSearch.setQuery(finalQuery);
2242
        if(highlightFragments){
2243
            luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2244
        }
2245

    
2246
        return luceneSearch;
2247
    }
2248

    
2249
    /**
2250
     * @param queryString
2251
     * @param classification
2252
     * @param features
2253
     * @param languages
2254
     * @param descriptionElementQueryFactory
2255
     * @return
2256
     */
2257
    private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2258
            List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2259
        BooleanQuery finalQuery = new BooleanQuery();
2260
        BooleanQuery textQuery = new BooleanQuery();
2261
        textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2262

    
2263
        // common name
2264
        Query nameQuery;
2265
        if(languages == null || languages.size() == 0){
2266
            nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString);
2267
        } else {
2268
            nameQuery = new BooleanQuery();
2269
            BooleanQuery languageSubQuery = new BooleanQuery();
2270
            for(Language lang : languages){
2271
                languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid",  lang.getUuid().toString(), false), Occur.SHOULD);
2272
            }
2273
            ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2274
            ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
2275
        }
2276
        textQuery.add(nameQuery, Occur.SHOULD);
2277

    
2278

    
2279
        // text field from TextData
2280
        textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2281

    
2282
        // --- TermBase fields - by representation ----
2283
        // state field from CategoricalData
2284
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2285

    
2286
        // state field from CategoricalData
2287
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2288

    
2289
        // area field from Distribution
2290
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2291

    
2292
        // status field from Distribution
2293
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2294

    
2295
        finalQuery.add(textQuery, Occur.MUST);
2296
        // --- classification ----
2297

    
2298
        if(classification != null){
2299
            finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2300
        }
2301

    
2302
        // --- IdentifieableEntity fields - by uuid
2303
        if(features != null && features.size() > 0 ){
2304
            finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2305
        }
2306

    
2307
        // the description must be associated with a taxon
2308
        finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2309

    
2310
        logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2311
        return finalQuery;
2312
    }
2313

    
2314
    /**
2315
     * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2316
     * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2317
     * This method is a convenient means to retrieve a Lucene query string for such the fields.
2318
     *
2319
     * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2320
     * or {@link MultilanguageTextFieldBridge }
2321
     * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2322
     * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2323
     * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2324
     *
2325
     * TODO move to utiliy class !!!!!!!!
2326
     */
2327
    private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2328

    
2329
        if(stringBuilder == null){
2330
            stringBuilder = new StringBuilder();
2331
        }
2332
        if(languages == null || languages.size() == 0){
2333
            stringBuilder.append(name + ".ALL:(%1$s) ");
2334
        } else {
2335
            for(Language lang : languages){
2336
                stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2337
            }
2338
        }
2339
        return stringBuilder;
2340
    }
2341

    
2342
    @Override
2343
    public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2344
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2345
        List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2346

    
2347
        HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2348

    
2349

    
2350
        UUID nameUuid= taxon.getName().getUuid();
2351
        ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2352
        String epithetOfTaxon = null;
2353
        String infragenericEpithetOfTaxon = null;
2354
        String infraspecificEpithetOfTaxon = null;
2355
        if (taxonName.isSpecies()){
2356
             epithetOfTaxon= taxonName.getSpecificEpithet();
2357
        } else if (taxonName.isInfraGeneric()){
2358
            infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2359
        } else if (taxonName.isInfraSpecific()){
2360
            infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2361
        }
2362
        String genusOfTaxon = taxonName.getGenusOrUninomial();
2363
        Set<TaxonNode> nodes = taxon.getTaxonNodes();
2364
        List<String> taxonNames = new ArrayList<String>();
2365

    
2366
        for (TaxonNode node: nodes){
2367
           // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2368
           // List<String> synonymsEpithet = new ArrayList<String>();
2369

    
2370
            if (node.getClassification().equals(classification)){
2371
                if (!node.isTopmostNode()){
2372
                    TaxonNode parent = node.getParent();
2373
                    parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
2374
                    TaxonNameBase<?,?> parentName =  parent.getTaxon().getName();
2375
                    ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2376
                    Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
2377
                    Rank rankOfTaxon = taxonName.getRank();
2378

    
2379

    
2380
                    //create inferred synonyms for species, subspecies
2381
                    if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2382

    
2383
                        Synonym inferredEpithet = null;
2384
                        Synonym inferredGenus = null;
2385
                        Synonym potentialCombination = null;
2386

    
2387
                        List<String> propertyPaths = new ArrayList<String>();
2388
                        propertyPaths.add("synonym");
2389
                        propertyPaths.add("synonym.name");
2390
                        List<OrderHint> orderHints = new ArrayList<OrderHint>();
2391
                        orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2392

    
2393
                        List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2394
                        List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2395

    
2396
                        List<TaxonRelationship> taxonRelListParent = null;
2397
                        List<TaxonRelationship> taxonRelListTaxon = null;
2398
                        if (doWithMisappliedNames){
2399
                            taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2400
                            taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2401
                        }
2402

    
2403

    
2404
                        if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2405

    
2406

    
2407
                            for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2408
                                Synonym syn = synonymRelationOfParent.getSynonym();
2409

    
2410
                                inferredEpithet = createInferredEpithets(taxon,
2411
                                        zooHashMap, taxonName, epithetOfTaxon,
2412
                                        infragenericEpithetOfTaxon,
2413
                                        infraspecificEpithetOfTaxon,
2414
                                        taxonNames, parentName,
2415
                                        syn);
2416

    
2417

    
2418
                                inferredSynonyms.add(inferredEpithet);
2419
                                zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2420
                                taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2421
                            }
2422

    
2423
                            if (doWithMisappliedNames){
2424

    
2425
                                for (TaxonRelationship taxonRelationship: taxonRelListParent){
2426
                                     Taxon misappliedName = taxonRelationship.getFromTaxon();
2427

    
2428
                                     inferredEpithet = createInferredEpithets(taxon,
2429
                                             zooHashMap, taxonName, epithetOfTaxon,
2430
                                             infragenericEpithetOfTaxon,
2431
                                             infraspecificEpithetOfTaxon,
2432
                                             taxonNames, parentName,
2433
                                             misappliedName);
2434

    
2435
                                    inferredSynonyms.add(inferredEpithet);
2436
                                    zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2437
                                     taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2438
                                }
2439
                            }
2440

    
2441
                            if (!taxonNames.isEmpty()){
2442
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2443
                            ZoologicalName name;
2444
                            if (!synNotInCDM.isEmpty()){
2445
                                inferredSynonymsToBeRemoved.clear();
2446

    
2447
                                for (Synonym syn :inferredSynonyms){
2448
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2449
                                    if (!synNotInCDM.contains(name.getNameCache())){
2450
                                        inferredSynonymsToBeRemoved.add(syn);
2451
                                    }
2452
                                }
2453

    
2454
                                // Remove identified Synonyms from inferredSynonyms
2455
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2456
                                    inferredSynonyms.remove(synonym);
2457
                                }
2458
                            }
2459
                        }
2460

    
2461
                    }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2462

    
2463

    
2464
                        for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2465
                            TaxonNameBase synName;
2466
                            ZoologicalName inferredSynName;
2467

    
2468
                            Synonym syn = synonymRelationOfTaxon.getSynonym();
2469
                            inferredGenus = createInferredGenus(taxon,
2470
                                    zooHashMap, taxonName, epithetOfTaxon,
2471
                                    genusOfTaxon, taxonNames, zooParentName, syn);
2472

    
2473
                            inferredSynonyms.add(inferredGenus);
2474
                            zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2475
                            taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2476

    
2477

    
2478
                        }
2479

    
2480
                        if (doWithMisappliedNames){
2481

    
2482
                            for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2483
                                Taxon misappliedName = taxonRelationship.getFromTaxon();
2484
                                inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
2485

    
2486
                                inferredSynonyms.add(inferredGenus);
2487
                                zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2488
                                 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2489
                            }
2490
                        }
2491

    
2492

    
2493
                        if (!taxonNames.isEmpty()){
2494
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2495
                            ZoologicalName name;
2496
                            if (!synNotInCDM.isEmpty()){
2497
                                inferredSynonymsToBeRemoved.clear();
2498

    
2499
                                for (Synonym syn :inferredSynonyms){
2500
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2501
                                    if (!synNotInCDM.contains(name.getNameCache())){
2502
                                        inferredSynonymsToBeRemoved.add(syn);
2503
                                    }
2504
                                }
2505

    
2506
                                // Remove identified Synonyms from inferredSynonyms
2507
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2508
                                    inferredSynonyms.remove(synonym);
2509
                                }
2510
                            }
2511
                        }
2512

    
2513
                    }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2514

    
2515
                        Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2516
                        ZoologicalName inferredSynName;
2517
                        //for all synonyms of the parent...
2518
                        for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2519
                            TaxonNameBase synName;
2520
                            Synonym synParent = synonymRelationOfParent.getSynonym();
2521
                            synName = synParent.getName();
2522

    
2523
                            HibernateProxyHelper.deproxy(synParent);
2524

    
2525
                            // Set the sourceReference
2526
                            sourceReference = synParent.getSec();
2527

    
2528
                            // Determine the idInSource
2529
                            String idInSourceParent = getIdInSource(synParent);
2530

    
2531
                            ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2532
                            String synParentGenus = parentSynZooName.getGenusOrUninomial();
2533
                            String synParentInfragenericName = null;
2534
                            String synParentSpecificEpithet = null;
2535

    
2536
                            if (parentSynZooName.isInfraGeneric()){
2537
                                synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2538
                            }
2539
                            if (parentSynZooName.isSpecies()){
2540
                                synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2541
                            }
2542

    
2543
                           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2544
                                synonymsGenus.put(synGenusName, idInSource);
2545
                            }*/
2546

    
2547
                            //for all synonyms of the taxon
2548

    
2549
                            for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2550

    
2551
                                Synonym syn = synonymRelationOfTaxon.getSynonym();
2552
                                ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2553
                                potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2554
                                        synParentGenus,
2555
                                        synParentInfragenericName,
2556
                                        synParentSpecificEpithet, syn, zooHashMap);
2557

    
2558
                                taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2559
                                inferredSynonyms.add(potentialCombination);
2560
                                zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2561
                                 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2562

    
2563
                            }
2564

    
2565

    
2566
                        }
2567

    
2568
                        if (doWithMisappliedNames){
2569

    
2570
                            for (TaxonRelationship parentRelationship: taxonRelListParent){
2571

    
2572
                                TaxonNameBase misappliedParentName;
2573

    
2574
                                Taxon misappliedParent = parentRelationship.getFromTaxon();
2575
                                misappliedParentName = misappliedParent.getName();
2576

    
2577
                                HibernateProxyHelper.deproxy(misappliedParent);
2578

    
2579
                                // Set the sourceReference
2580
                                sourceReference = misappliedParent.getSec();
2581

    
2582
                                // Determine the idInSource
2583
                                String idInSourceParent = getIdInSource(misappliedParent);
2584

    
2585
                                ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2586
                                String synParentGenus = parentSynZooName.getGenusOrUninomial();
2587
                                String synParentInfragenericName = null;
2588
                                String synParentSpecificEpithet = null;
2589

    
2590
                                if (parentSynZooName.isInfraGeneric()){
2591
                                    synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2592
                                }
2593
                                if (parentSynZooName.isSpecies()){
2594
                                    synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2595
                                }
2596

    
2597

    
2598
                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2599
                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
2600
                                    ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2601
                                    potentialCombination = createPotentialCombination(
2602
                                            idInSourceParent, parentSynZooName, zooMisappliedName,
2603
                                            synParentGenus,
2604
                                            synParentInfragenericName,
2605
                                            synParentSpecificEpithet, misappliedName, zooHashMap);
2606

    
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
                        if (!taxonNames.isEmpty()){
2617
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2618
                            ZoologicalName name;
2619
                            if (!synNotInCDM.isEmpty()){
2620
                                inferredSynonymsToBeRemoved.clear();
2621
                                for (Synonym syn :inferredSynonyms){
2622
                                    try{
2623
                                        name = (ZoologicalName) syn.getName();
2624
                                    }catch (ClassCastException e){
2625
                                        name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2626
                                    }
2627
                                    if (!synNotInCDM.contains(name.getNameCache())){
2628
                                        inferredSynonymsToBeRemoved.add(syn);
2629
                                    }
2630
                                 }
2631
                                // Remove identified Synonyms from inferredSynonyms
2632
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2633
                                    inferredSynonyms.remove(synonym);
2634
                                }
2635
                            }
2636
                         }
2637
                        }
2638
                    }else {
2639
                        logger.info("The synonymrelationship type is not defined.");
2640
                        return inferredSynonyms;
2641
                    }
2642
                }
2643
            }
2644

    
2645
        }
2646

    
2647
        return inferredSynonyms;
2648
    }
2649

    
2650
    private Synonym createPotentialCombination(String idInSourceParent,
2651
            ZoologicalName parentSynZooName, 	ZoologicalName zooSynName, String synParentGenus,
2652
            String synParentInfragenericName, String synParentSpecificEpithet,
2653
            TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2654
        Synonym potentialCombination;
2655
        Reference sourceReference;
2656
        ZoologicalName inferredSynName;
2657
        HibernateProxyHelper.deproxy(syn);
2658

    
2659
        // Set sourceReference
2660
        sourceReference = syn.getSec();
2661
        if (sourceReference == null){
2662
            logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2663
            //TODO:Remove
2664
            if (!parentSynZooName.getTaxa().isEmpty()){
2665
                TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2666

    
2667
                sourceReference = taxon.getSec();
2668
            }
2669
        }
2670
        String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2671

    
2672
        String synTaxonInfraSpecificName= null;
2673

    
2674
        if (parentSynZooName.isSpecies()){
2675
            synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2676
        }
2677

    
2678
        /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2679
            synonymsEpithet.add(epithetName);
2680
        }*/
2681

    
2682
        //create potential combinations...
2683
        inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2684

    
2685
        inferredSynName.setGenusOrUninomial(synParentGenus);
2686
        if (zooSynName.isSpecies()){
2687
              inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2688
              if (parentSynZooName.isInfraGeneric()){
2689
                  inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2690
              }
2691
        }
2692
        if (zooSynName.isInfraSpecific()){
2693
            inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2694
            inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2695
        }
2696
        if (parentSynZooName.isInfraGeneric()){
2697
            inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2698
        }
2699

    
2700

    
2701
        potentialCombination = Synonym.NewInstance(inferredSynName, null);
2702

    
2703
        // Set the sourceReference
2704
        potentialCombination.setSec(sourceReference);
2705

    
2706

    
2707
        // Determine the idInSource
2708
        String idInSourceSyn= getIdInSource(syn);
2709

    
2710
        if (idInSourceParent != null && idInSourceSyn != null) {
2711
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2712
            inferredSynName.addSource(originalSource);
2713
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2714
            potentialCombination.addSource(originalSource);
2715
        }
2716

    
2717
        inferredSynName.generateTitle();
2718

    
2719
        return potentialCombination;
2720
    }
2721

    
2722
    private Synonym createInferredGenus(Taxon taxon,
2723
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2724
            String epithetOfTaxon, String genusOfTaxon,
2725
            List<String> taxonNames, ZoologicalName zooParentName,
2726
            TaxonBase syn) {
2727

    
2728
        Synonym inferredGenus;
2729
        TaxonNameBase synName;
2730
        ZoologicalName inferredSynName;
2731
        synName =syn.getName();
2732
        HibernateProxyHelper.deproxy(syn);
2733

    
2734
        // Determine the idInSource
2735
        String idInSourceSyn = getIdInSource(syn);
2736
        String idInSourceTaxon = getIdInSource(taxon);
2737
        // Determine the sourceReference
2738
        Reference sourceReference = syn.getSec();
2739

    
2740
        //logger.warn(sourceReference.getTitleCache());
2741

    
2742
        synName = syn.getName();
2743
        ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2744
        String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2745
                     /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2746
            synonymsEpithet.add(synSpeciesEpithetName);
2747
        }*/
2748

    
2749
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2750
        //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...
2751

    
2752

    
2753
        inferredSynName.setGenusOrUninomial(genusOfTaxon);
2754
        if (zooParentName.isInfraGeneric()){
2755
            inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2756
        }
2757

    
2758
        if (taxonName.isSpecies()){
2759
            inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2760
        }
2761
        if (taxonName.isInfraSpecific()){
2762
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2763
            inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2764
        }
2765

    
2766

    
2767
        inferredGenus = Synonym.NewInstance(inferredSynName, null);
2768

    
2769
        // Set the sourceReference
2770
        inferredGenus.setSec(sourceReference);
2771

    
2772
        // Add the original source
2773
        if (idInSourceSyn != null && idInSourceTaxon != null) {
2774
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2775
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2776
            inferredGenus.addSource(originalSource);
2777

    
2778
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2779
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2780
            inferredSynName.addSource(originalSource);
2781
            originalSource = null;
2782

    
2783
        }else{
2784
            logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2785
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2786
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2787
            inferredGenus.addSource(originalSource);
2788

    
2789
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2790
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2791
            inferredSynName.addSource(originalSource);
2792
            originalSource = null;
2793
        }
2794

    
2795
        taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2796

    
2797
        inferredSynName.generateTitle();
2798

    
2799

    
2800
        return inferredGenus;
2801
    }
2802

    
2803
    private Synonym createInferredEpithets(Taxon taxon,
2804
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2805
            String epithetOfTaxon, String infragenericEpithetOfTaxon,
2806
            String infraspecificEpithetOfTaxon, List<String> taxonNames,
2807
            TaxonNameBase parentName, TaxonBase syn) {
2808

    
2809
        Synonym inferredEpithet;
2810
        TaxonNameBase<?,?> synName;
2811
        ZoologicalName inferredSynName;
2812
        HibernateProxyHelper.deproxy(syn);
2813

    
2814
        // Determine the idInSource
2815
        String idInSourceSyn = getIdInSource(syn);
2816
        String idInSourceTaxon =  getIdInSource(taxon);
2817
        // Determine the sourceReference
2818
        Reference<?> sourceReference = syn.getSec();
2819

    
2820
        if (sourceReference == null){
2821
             logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2822
             sourceReference = taxon.getSec();
2823
        }
2824

    
2825
        synName = syn.getName();
2826
        ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2827
        String synGenusName = zooSynName.getGenusOrUninomial();
2828
        String synInfraGenericEpithet = null;
2829
        String synSpecificEpithet = null;
2830

    
2831
        if (zooSynName.getInfraGenericEpithet() != null){
2832
            synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2833
        }
2834

    
2835
        if (zooSynName.isInfraSpecific()){
2836
            synSpecificEpithet = zooSynName.getSpecificEpithet();
2837
        }
2838

    
2839
                     /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2840
            synonymsGenus.put(synGenusName, idInSource);
2841
        }*/
2842

    
2843
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2844

    
2845
        // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2846
        if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2847
            logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2848
        }
2849
        inferredSynName.setGenusOrUninomial(synGenusName);
2850

    
2851
        if (parentName.isInfraGeneric()){
2852
            inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2853
        }
2854
        if (taxonName.isSpecies()){
2855
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2856
        }else if (taxonName.isInfraSpecific()){
2857
            inferredSynName.setSpecificEpithet(synSpecificEpithet);
2858
            inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2859
        }
2860

    
2861
        inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2862

    
2863
        // Set the sourceReference
2864
        inferredEpithet.setSec(sourceReference);
2865

    
2866
        /* Add the original source
2867
        if (idInSource != null) {
2868
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2869

    
2870
            // Add the citation
2871
            Reference citation = getCitation(syn);
2872
            if (citation != null) {
2873
                originalSource.setCitation(citation);
2874
                inferredEpithet.addSource(originalSource);
2875
            }
2876
        }*/
2877
        String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2878

    
2879

    
2880
        IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2881
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2882

    
2883
        inferredEpithet.addSource(originalSource);
2884

    
2885
        originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2886
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2887

    
2888
        inferredSynName.addSource(originalSource);
2889

    
2890

    
2891

    
2892
        taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2893

    
2894
        inferredSynName.generateTitle();
2895
        return inferredEpithet;
2896
    }
2897

    
2898
    /**
2899
     * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2900
     * Very likely only useful for createInferredSynonyms().
2901
     * @param uuid
2902
     * @param zooHashMap
2903
     * @return
2904
     */
2905
    private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2906
        ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2907
        if (taxonName == null) {
2908
            taxonName = zooHashMap.get(uuid);
2909
        }
2910
        return taxonName;
2911
    }
2912

    
2913
    /**
2914
     * Returns the idInSource for a given Synonym.
2915
     * @param syn
2916
     */
2917
    private String getIdInSource(TaxonBase taxonBase) {
2918
        String idInSource = null;
2919
        Set<IdentifiableSource> sources = taxonBase.getSources();
2920
        if (sources.size() == 1) {
2921
            IdentifiableSource source = sources.iterator().next();
2922
            if (source != null) {
2923
                idInSource  = source.getIdInSource();
2924
            }
2925
        } else if (sources.size() > 1) {
2926
            int count = 1;
2927
            idInSource = "";
2928
            for (IdentifiableSource source : sources) {
2929
                idInSource += source.getIdInSource();
2930
                if (count < sources.size()) {
2931
                    idInSource += "; ";
2932
                }
2933
                count++;
2934
            }
2935
        } else if (sources.size() == 0){
2936
            logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2937
        }
2938

    
2939

    
2940
        return idInSource;
2941
    }
2942

    
2943

    
2944
    /**
2945
     * Returns the citation for a given Synonym.
2946
     * @param syn
2947
     */
2948
    private Reference getCitation(Synonym syn) {
2949
        Reference citation = null;
2950
        Set<IdentifiableSource> sources = syn.getSources();
2951
        if (sources.size() == 1) {
2952
            IdentifiableSource source = sources.iterator().next();
2953
            if (source != null) {
2954
                citation = source.getCitation();
2955
            }
2956
        } else if (sources.size() > 1) {
2957
            logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2958
        }
2959

    
2960
        return citation;
2961
    }
2962

    
2963
    @Override
2964
    public List<Synonym>  createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2965
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2966

    
2967
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2968
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2969
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2970

    
2971
        return inferredSynonyms;
2972
    }
2973

    
2974
    /* (non-Javadoc)
2975
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#listClassifications(eu.etaxonomy.cdm.model.taxon.TaxonBase, java.lang.Integer, java.lang.Integer, java.util.List)
2976
     */
2977
    @Override
2978
    public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2979

    
2980
        // TODO quickly implemented, create according dao !!!!
2981
        Set<TaxonNode> nodes = new HashSet<TaxonNode>();
2982
        Set<Classification> classifications = new HashSet<Classification>();
2983
        List<Classification> list = new ArrayList<Classification>();
2984

    
2985
        if (taxonBase == null) {
2986
            return list;
2987
        }
2988

    
2989
        taxonBase = load(taxonBase.getUuid());
2990

    
2991
        if (taxonBase instanceof Taxon) {
2992
            nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
2993
        } else {
2994
            for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
2995
                nodes.addAll(taxon.getTaxonNodes());
2996
            }
2997
        }
2998
        for (TaxonNode node : nodes) {
2999
            classifications.add(node.getClassification());
3000
        }
3001
        list.addAll(classifications);
3002
        return list;
3003
    }
3004

    
3005
    @Override
3006
    public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
3007
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
3008
        // Create new synonym using concept name
3009
                TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
3010
                Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
3011

    
3012
                // Remove concept relation from taxon
3013
                toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
3014

    
3015

    
3016

    
3017

    
3018
                // Create a new synonym for the taxon
3019
                SynonymRelationship synonymRelationship;
3020
                if (synonymRelationshipType != null
3021
                        && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
3022
                    synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
3023
                } else{
3024
                    synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
3025
                }
3026

    
3027
                this.saveOrUpdate(toTaxon);
3028
                //TODO: configurator and classification
3029
                this.deleteTaxon(fromTaxon, null, null);
3030
                return synonymRelationship.getSynonym();
3031

    
3032
    }
3033
    @Override
3034
    public List<String> isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
3035
    	List<String> result = new ArrayList<String>();
3036
    	Set<CdmBase> references = commonService.getReferencingObjects(taxonBase);
3037
    	if (taxonBase instanceof Taxon){
3038
    		TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3039
    		result = isDeletableForTaxon(references, taxonConfig);
3040
    	}else{
3041
    		SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3042
    		result = isDeletableForSynonym(references, synonymConfig);
3043
    	}
3044
    	return result;
3045
    }
3046
    
3047
    private List<String> isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3048
    	String message;
3049
    	List<String> result = new ArrayList<String>();
3050
    	for (CdmBase ref: references){
3051
    		if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase)){
3052
    			message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3053
    			result.add(message);
3054
    		}
3055
    	}
3056
    	
3057
    	return result;
3058
    }
3059
    private List<String> isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3060
    	String message;
3061
    	List<String> result = new ArrayList<String>();
3062
    	for (CdmBase ref: references){
3063
    		if (!(ref instanceof TaxonNameBase)){
3064
    			if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){
3065
    				message = "The Taxon can't be deleted as long as it has synonyms.";
3066
    				result.add(message);
3067
    			}
3068
    			if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3069
    				message = "The Taxon can't be deleted as long as it has factual data.";
3070
    				result.add(message);
3071
    			}
3072
    			
3073
    			if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
3074
    				message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3075
    				result.add(message);
3076
    			}
3077
    			if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonNode)){
3078
    				if (!config.isDeleteMisappliedNamesAndInvalidDesignations() && (((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())|| ((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR()))){
3079
        				message = "The Taxon can't be deleted as long as it has misapplied names or invalid designations.";
3080
        				result.add(message);
3081
        			} else{
3082
        				message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3083
        				result.add(message);
3084
        			}
3085
    			}
3086
    			if (ref instanceof PolytomousKeyNode){
3087
    				message = "The Taxon can't be deleted as long as it is referenced by a polytomous key node.";
3088
    				result.add(message);
3089
    			}
3090
    			
3091
    			if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){
3092
                   message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
3093
                   result.add(message);
3094
                   
3095
                }
3096

    
3097

    
3098
               /* //PolytomousKeyNode
3099
                if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3100
                    String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3101
                    return message;
3102
                }*/
3103

    
3104
                //TaxonInteraction
3105
                if (ref.isInstanceOf(TaxonInteraction.class)){
3106
                    message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3107
                    result.add(message);
3108
                }
3109

    
3110
              //TaxonInteraction
3111
                if (ref.isInstanceOf(DeterminationEvent.class)){
3112
                    message = "Taxon can't be deleted as it is used in a determination event";
3113
                    result.add(message);
3114
                }
3115

    
3116
            }
3117

    
3118
 	}
3119
    	
3120
    	return result;
3121
    }
3122

    
3123

    
3124

    
3125

    
3126

    
3127
    }
(79-79/84)