Project

General

Profile

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

    
131

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

    
142
    public static final String POTENTIAL_COMBINATION_NAMESPACE = "Potential combination";
143

    
144
    public static final String INFERRED_EPITHET_NAMESPACE = "Inferred epithet";
145

    
146
    public static final String INFERRED_GENUS_NAMESPACE = "Inferred genus";
147

    
148

    
149
    @Autowired
150
    private ITaxonNameDao nameDao;
151

    
152
    @Autowired
153
    private INameService nameService;
154

    
155
    @Autowired
156
    private ITaxonNodeService nodeService;
157

    
158
    @Autowired
159
    private ICdmGenericDao genericDao;
160

    
161
    @Autowired
162
    private IDescriptionService descriptionService;
163

    
164
    @Autowired
165
    private IOrderedTermVocabularyDao orderedVocabularyDao;
166

    
167
    @Autowired
168
    private IOccurrenceDao occurrenceDao;
169

    
170
    @Autowired
171
    private IClassificationDao classificationDao;
172

    
173
    @Autowired
174
    private AbstractBeanInitializer beanInitializer;
175

    
176
    @Autowired
177
    private ILuceneIndexToolProvider luceneIndexToolProvider;
178

    
179
    /**
180
     * Constructor
181
     */
182
    public TaxonServiceImpl(){
183
        if (logger.isDebugEnabled()) { logger.debug("Load TaxonService Bean"); }
184
    }
185

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

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

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

    
214
    @Override
215
    public List<RelationshipBase> getAllRelationships(int limit, int start){
216
        return dao.getAllRelationships(limit, start);
217
    }
218

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

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

    
234

    
235

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

    
244
        TaxonNameBase<?,?> synonymName = synonym.getName();
245
        synonymName.removeTaxonBase(synonym);
246
        TaxonNameBase<?,?> taxonName = acceptedTaxon.getName();
247
        taxonName.removeTaxonBase(acceptedTaxon);
248

    
249
        synonym.setName(taxonName);
250
        acceptedTaxon.setName(synonymName);
251

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

    
257

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

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

    
266
        TaxonNameBase<?,?> acceptedName = acceptedTaxon.getName();
267
        TaxonNameBase<?,?> synonymName = synonym.getName();
268
        HomotypicalGroup synonymHomotypicGroup = synonymName.getHomotypicalGroup();
269

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

    
276
        Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, acceptedTaxon.getSec());
277

    
278
        SynonymRelationshipType relTypeForGroup = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
279
        List<Synonym> heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup);
280
        Set<NameRelationship> basionymsAndReplacedSynonyms = synonymHomotypicGroup.getBasionymAndReplacedSynonymRelations();
281

    
282
        for (Synonym heteroSynonym : heteroSynonyms){
283
            if (synonym.equals(heteroSynonym)){
284
                acceptedTaxon.removeSynonym(heteroSynonym, false);
285

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

    
292
        //synonym.getName().removeTaxonBase(synonym);
293

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

    
302
            } catch (Exception e) {
303
                logger.info("Can't delete old synonym from database");
304
            }
305
        }
306

    
307
        return newAcceptedTaxon;
308
    }
309

    
310

    
311
    @Override
312
    public Taxon changeSynonymToRelatedTaxon(Synonym synonym, Taxon toTaxon, TaxonRelationshipType taxonRelationshipType, Reference citation, String microcitation){
313

    
314
        // Get name from synonym
315
        TaxonNameBase<?, ?> synonymName = synonym.getName();
316

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

    
323
        // Add taxon relation
324
        fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
325

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

    
331
        return fromTaxon;
332
    }
333

    
334

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

    
346

    
347
        // Switch groups
348
        oldHomotypicalGroup.removeTypifiedName(synonymName);
349
        newHomotypicalGroup.addTypifiedName(synonymName);
350

    
351
        //remove existing basionym relationships
352
        synonymName.removeBasionyms();
353

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

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

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

    
396
    }
397

    
398

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

    
411
    @Override
412
    @Autowired
413
    protected void setDao(ITaxonDao dao) {
414
        this.dao = dao;
415
    }
416

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

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

    
429
        return new DefaultPagerImpl<TaxonBase>(pageNumber, numberOfResults, pageSize, results);
430
    }
431

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

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

    
444
        return results;
445
    }
446

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

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

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

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

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

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

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

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

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

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

    
513
        List<Taxon> list = new ArrayList<Taxon>();
514
        Long count = 0l;
515

    
516
        Synonym synonym = null;
517

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

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

    
535
            }
536
        }
537

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

    
543
        return new DefaultPagerImpl<Taxon>(pageNumber, count.intValue(), pageSize, list);
544
    }
545

    
546

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

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

    
566

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

    
579
        if(taxa.isEmpty()) {
580
            taxa.add(taxon);
581
        }
582

    
583
        if(includeRelationships.isEmpty()){
584
            return taxa;
585
        }
586

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

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

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

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

    
639
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
640
    }
641

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

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

    
654
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
655
    }
656

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

    
665
        //homotypic
666
        result.add(t.getHomotypicSynonymsByHomotypicGroup());
667

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

    
674
        return result;
675

    
676
    }
677

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

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

    
701
    @Override
702
    public List<UuidAndTitleCache<IdentifiableEntity>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator){
703

    
704
        List<UuidAndTitleCache<IdentifiableEntity>> results = new ArrayList<UuidAndTitleCache<IdentifiableEntity>>();
705

    
706

    
707
        if (configurator.isDoSynonyms() || configurator.isDoTaxa() || configurator.isDoNamesWithoutTaxa()){
708
        	results = dao.getTaxaByNameForEditor(configurator.isDoTaxa(), configurator.isDoSynonyms(), configurator.isDoNamesWithoutTaxa(), configurator.isDoMisappliedNames(),configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
709
        }
710
        if (configurator.isDoTaxaByCommonNames()) {
711
            
712
            if(configurator.getPageSize() == null ){
713
                List<UuidAndTitleCache<IdentifiableEntity>> commonNameResults = dao.getTaxaByCommonNameForEditor(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
714
                if(commonNameResults != null){
715
                    results.addAll(commonNameResults);
716
                }
717
            }
718
        }
719
        return results;
720
    }
721

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

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

    
732
        // Taxa and synonyms
733
        long numberTaxaResults = 0L;
734

    
735

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

    
741

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

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

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

    
760
        if(taxa != null){
761
            results.addAll(taxa);
762
        }
763

    
764
        numberOfResults += numberTaxaResults;
765

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

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

    
786
        // Taxa from common names
787

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

    
803
        }
804

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

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

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

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

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

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

    
843

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

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

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

    
858
        if (limitToGalleries == null) {
859
            limitToGalleries = false;
860
        }
861

    
862
        // --- resolve related taxa
863
        if (includeRelationships != null && ! includeRelationships.isEmpty()) {
864
            logger.trace("listMedia() - resolve related taxa");
865
            taxa = listRelatedTaxa(taxon, includeRelationships, null, null, null, null);
866
        }
867

    
868
        taxa.add((Taxon) dao.load(taxon.getUuid()));
869

    
870
        if(includeTaxonDescriptions != null && includeTaxonDescriptions){
871
            logger.trace("listMedia() - includeTaxonDescriptions");
872
            List<TaxonDescription> taxonDescriptions = new ArrayList<TaxonDescription>();
873
            // --- TaxonDescriptions
874
            for (Taxon t : taxa) {
875
                taxonDescriptions.addAll(descriptionService.listTaxonDescriptions(t, null, null, null, null, propertyPath));
876
            }
877
            for (TaxonDescription taxonDescription : taxonDescriptions) {
878
                if (!limitToGalleries || taxonDescription.isImageGallery()) {
879
                    for (DescriptionElementBase element : taxonDescription.getElements()) {
880
                        for (Media media : element.getMedia()) {
881
                            if(taxonDescription.isImageGallery()){
882
                                taxonMedia.add(media);
883
                            }
884
                            else{
885
                                nonImageGalleryImages.add(media);
886
                            }
887
                        }
888
                    }
889
                }
890
            }
891
            //put images from image gallery first (#3242)
892
            taxonMedia.addAll(nonImageGalleryImages);
893
        }
894

    
895

    
896
        if(includeOccurrences != null && includeOccurrences) {
897
            logger.trace("listMedia() - includeOccurrences");
898
            Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<SpecimenOrObservationBase>();
899
            // --- Specimens
900
            for (Taxon t : taxa) {
901
                specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
902
            }
903
            for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
904

    
905
//            	direct media removed from specimen #3597
906
//              taxonMedia.addAll(occurrence.getMedia());
907

    
908
                // SpecimenDescriptions
909
                Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
910
                for (DescriptionBase specimenDescription : specimenDescriptions) {
911
                    if (!limitToGalleries || specimenDescription.isImageGallery()) {
912
                        Set<DescriptionElementBase> elements = specimenDescription.getElements();
913
                        for (DescriptionElementBase element : elements) {
914
                            for (Media media : element.getMedia()) {
915
                                taxonMedia.add(media);
916
                            }
917
                        }
918
                    }
919
                }
920

    
921
                // Collection
922
                //TODO why may collections have media attached? #
923
                if (occurrence.isInstanceOf(DerivedUnit.class)) {
924
                    DerivedUnit derivedUnit = CdmBase.deproxy(occurrence, DerivedUnit.class);
925
                    if (derivedUnit.getCollection() != null){
926
                        taxonMedia.addAll(derivedUnit.getCollection().getMedia());
927
                    }
928
                }
929

    
930
                // pherograms & gelPhotos
931
                if (occurrence.isInstanceOf(DnaSample.class)) {
932
                    DnaSample dnaSample = CdmBase.deproxy(occurrence, DnaSample.class);
933
                    Set<Sequence> sequences = dnaSample.getSequences();
934
                    //we do show only those gelPhotos which lead to a consensus sequence
935
                    for (Sequence sequence : sequences) {
936
                        Set<Media> dnaRelatedMedia = new HashSet<Media>();
937
                        for (SingleRead singleRead : sequence.getSingleReads()){
938
                            AmplificationResult amplification = singleRead.getAmplificationResult();
939
                            dnaRelatedMedia.add(amplification.getGelPhoto());
940
                            dnaRelatedMedia.add(singleRead.getPherogram());
941
                            dnaRelatedMedia.remove(null);
942
                        }
943
                        taxonMedia.addAll(dnaRelatedMedia);
944
                    }
945
                }
946

    
947
            }
948
        }
949

    
950
        if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
951
            logger.trace("listMedia() - includeTaxonNameDescriptions");
952
            // --- TaxonNameDescription
953
            Set<TaxonNameDescription> nameDescriptions = new HashSet<TaxonNameDescription>();
954
            for (Taxon t : taxa) {
955
                nameDescriptions .addAll(t.getName().getDescriptions());
956
            }
957
            for(TaxonNameDescription nameDescription: nameDescriptions){
958
                if (!limitToGalleries || nameDescription.isImageGallery()) {
959
                    Set<DescriptionElementBase> elements = nameDescription.getElements();
960
                    for (DescriptionElementBase element : elements) {
961
                        for (Media media : element.getMedia()) {
962
                            taxonMedia.add(media);
963
                        }
964
                    }
965
                }
966
            }
967
        }
968

    
969

    
970
        logger.trace("listMedia() - initialize");
971
        beanInitializer.initializeAll(taxonMedia, propertyPath);
972

    
973
        logger.trace("listMedia() - END");
974

    
975
        return taxonMedia;
976
    }
977

    
978
    /* (non-Javadoc)
979
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaByID(java.util.Set)
980
     */
981
    @Override
982
    public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
983
        return this.dao.listByIds(listOfIDs, null, null, null, null);
984
    }
985

    
986
    /* (non-Javadoc)
987
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxonByUuid(UUID uuid, List<String> propertyPaths)
988
     */
989
    @Override
990
    public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
991
        return this.dao.findByUuid(uuid, null ,propertyPaths);
992
    }
993

    
994
    /* (non-Javadoc)
995
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#countAllRelationships()
996
     */
997
    @Override
998
    public int countAllRelationships() {
999
        return this.dao.countAllRelationships();
1000
    }
1001

    
1002

    
1003

    
1004

    
1005
    /* (non-Javadoc)
1006
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNames(java.util.List)
1007
     */
1008
    @Override
1009
    public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
1010
        return this.dao.findIdenticalTaxonNames(propertyPath);
1011
    }
1012

    
1013

    
1014
    /* (non-Javadoc)
1015
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator)
1016
     */
1017
    @Override
1018
    public DeleteResult deleteTaxon(Taxon taxon, TaxonDeletionConfigurator config, Classification classification)  {
1019
    	
1020
    	if (config == null){
1021
            config = new TaxonDeletionConfigurator();
1022
        }
1023

    
1024
        DeleteResult result = isDeletable(taxon, config);
1025

    
1026
        if (result.isOk()){
1027
            // --- DeleteSynonymRelations
1028
            if (config.isDeleteSynonymRelations()){
1029
                boolean removeSynonymNameFromHomotypicalGroup = false;
1030
                // use tmp Set to avoid concurrent modification
1031
                Set<SynonymRelationship> synRelsToDelete = new HashSet<SynonymRelationship>();
1032
                synRelsToDelete.addAll(taxon.getSynonymRelations());
1033
                for (SynonymRelationship synRel : synRelsToDelete){
1034
                    Synonym synonym = synRel.getSynonym();
1035
                    // taxon.removeSynonymRelation will set the accepted taxon and the synonym to NULL
1036
                    // this will cause hibernate to delete the relationship since
1037
                    // the SynonymRelationship field on both is annotated with removeOrphan
1038
                    // so no further explicit deleting of the relationship should be done here
1039
                    taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
1040

    
1041
                    // --- DeleteSynonymsIfPossible
1042
                    if (config.isDeleteSynonymsIfPossible()){
1043
                        //TODO which value
1044
                        boolean newHomotypicGroupIfNeeded = true;
1045
                        SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
1046
                        deleteSynonym(synonym, taxon, synConfig);
1047
                    }
1048
                    // relationship will be deleted by hibernate automatically,
1049
                    // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797
1050
                    // else{
1051
                    //     deleteSynonymRelationships(synonym, taxon);
1052
                    // }
1053
                }
1054
            }
1055

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

    
1086
                    }*/
1087
                }
1088
            }
1089

    
1090
            //    	TaxonDescription
1091
            if (config.isDeleteDescriptions()){
1092
                Set<TaxonDescription> descriptions = taxon.getDescriptions();
1093
                List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1094
                for (TaxonDescription desc: descriptions){
1095
                    //TODO use description delete configurator ?
1096
                    //FIXME check if description is ALWAYS deletable
1097
                    if (desc.getDescribedSpecimenOrObservation() != null){
1098
                        String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1099
                                " which also describes specimens or abservations";
1100
                        //throw new ReferencedObjectUndeletableException(message);
1101
                    }
1102
                    removeDescriptions.add(desc);
1103
                    descriptionService.delete(desc);
1104

    
1105
                }
1106
                for (TaxonDescription desc: removeDescriptions){
1107
                    taxon.removeDescription(desc);
1108
                }
1109
            }
1110

    
1111

    
1112
         /*   //check references with only reverse mapping
1113
        String message = checkForReferences(taxon);
1114
        if (message != null){
1115
            //throw new ReferencedObjectUndeletableException(message.toString());
1116
        }*/
1117

    
1118
         if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null )){
1119
                //if (taxon.getTaxonNodes().size() > 0){
1120
                   // message = "Taxon can't be deleted as it is used in a classification node. Remove taxon from all classifications prior to deletion or define a classification where it should be deleted or adapt the taxon deletion configurator.";
1121
                   // throw new ReferencedObjectUndeletableException(message);
1122
                //}
1123
            }else{
1124
                if (taxon.getTaxonNodes().size() != 0){
1125
                    Set<TaxonNode> nodes = taxon.getTaxonNodes();
1126
                    Iterator<TaxonNode> iterator = nodes.iterator();
1127
                    TaxonNode node = null;
1128
                    boolean deleteChildren;
1129
                    if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1130
                        deleteChildren = true;
1131
                    }else {
1132
                        deleteChildren = false;
1133
                    }
1134
                    boolean success = true;
1135
                    if (!config.isDeleteInAllClassifications() && !(classification == null)){
1136
                        while (iterator.hasNext()){
1137
                            node = iterator.next();
1138
                            if (node.getClassification().equals(classification)){
1139
                                break;
1140
                            }
1141
                            node = null;
1142
                        }
1143
                        if (node != null){
1144
                            success =taxon.removeTaxonNode(node, deleteChildren);
1145
                            nodeService.delete(node);
1146
                        } else {
1147
                        	result.setError();
1148
                        	result.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1149
                        }
1150
                    } else if (config.isDeleteInAllClassifications()){
1151
                        Set<ITaxonTreeNode> nodesList = new HashSet<ITaxonTreeNode>();
1152
                        nodesList.addAll(taxon.getTaxonNodes());
1153

    
1154
                            for (ITaxonTreeNode treeNode: nodesList){
1155
                                TaxonNode taxonNode = (TaxonNode) treeNode;
1156
                                if(!deleteChildren){
1157
                                   /* Object[] childNodes = taxonNode.getChildNodes().toArray();
1158
                                    //nodesList.addAll(taxonNode.getChildNodes());
1159
                                    for (Object childNode: childNodes){
1160
                                        TaxonNode childNodeCast = (TaxonNode) childNode;
1161
                                        deleteTaxon(childNodeCast.getTaxon(), config, classification);
1162

    
1163
                                    }
1164

    
1165
                                    /*for (TaxonNode childNode: taxonNode.getChildNodes()){
1166
                                        deleteTaxon(childNode.getTaxon(), config, classification);
1167

    
1168
                                    }
1169
                                   // taxon.removeTaxonNode(taxonNode);
1170
                                    //nodeService.delete(taxonNode);
1171
                                } else{
1172
                                    */
1173
                                    Object[] childNodes = taxonNode.getChildNodes().toArray();
1174
                                    for (Object childNode: childNodes){
1175
                                        TaxonNode childNodeCast = (TaxonNode) childNode;
1176
                                        taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1177
                                    }
1178

    
1179
                                    //taxon.removeTaxonNode(taxonNode);
1180
                                }
1181
                            }
1182
                        config.getTaxonNodeConfig().setDeleteTaxon(false);
1183
                        DeleteResult resultNodes = nodeService.deleteTaxonNodes(nodesList, config);
1184
                        if (!resultNodes.isOk()){
1185
                        	result.addExceptions(resultNodes.getExceptions());
1186
                        	result.setStatus(resultNodes.getStatus());
1187
                        }
1188
                    }
1189
                    if (!success){
1190
                        result.setError();
1191
                        result.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1192
                    }
1193
                }
1194
            }
1195

    
1196

    
1197
             //PolytomousKey TODO
1198

    
1199

    
1200
            //TaxonNameBase
1201
            if (config.isDeleteNameIfPossible()){
1202

    
1203

    
1204
                    //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1205
                    TaxonNameBase name = (TaxonNameBase)HibernateProxyHelper.deproxy(taxon.getName());
1206
                    //check whether taxon will be deleted or not
1207
                    if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){
1208
                        taxon = (Taxon) HibernateProxyHelper.deproxy(taxon);
1209
                        name.removeTaxonBase(taxon);
1210
                        nameService.saveOrUpdate(name);
1211
                        DeleteResult nameResult = new DeleteResult();
1212

    
1213
                        nameResult = nameService.delete(name, config.getNameDeletionConfig());
1214

    
1215
                        if (nameResult.isError()){
1216
                        	//result.setError();
1217
                        	result.addRelatedObject(name);
1218
                        	result.addExceptions(nameResult.getExceptions());
1219
                        }
1220

    
1221
                    }
1222

    
1223
            }
1224

    
1225
//        	TaxonDescription
1226
           /* Set<TaxonDescription> descriptions = taxon.getDescriptions();
1227

    
1228
            for (TaxonDescription desc: descriptions){
1229
                if (config.isDeleteDescriptions()){
1230
                    //TODO use description delete configurator ?
1231
                    //FIXME check if description is ALWAYS deletable
1232
                    taxon.removeDescription(desc);
1233
                    descriptionService.delete(desc);
1234
                }else{
1235
                    if (desc.getDescribedSpecimenOrObservations().size()>0){
1236
                        String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1237
                                " which also describes specimens or observations";
1238
                            throw new ReferencedObjectUndeletableException(message);
1239
    }
1240
                    }
1241
                }*/
1242

    
1243
            if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0)  ){
1244
            	try{
1245
            		UUID uuid = dao.delete(taxon);
1246

    
1247
            	}catch(Exception e){
1248
            		result.addException(e);
1249
            		result.setError();
1250

    
1251
            	}
1252
            } else {
1253
            	result.setError();
1254
            	result.addException(new Exception("The Taxon can't be deleted."));
1255

    
1256
            }
1257
        }
1258
//        }else {
1259
//        	List<Exception> exceptions = new ArrayList<Exception>();
1260
//        	for (String message: referencedObjects){
1261
//        		ReferencedObjectUndeletableException exception = new ReferencedObjectUndeletableException(message);
1262
//        		exceptions.add(exception);
1263
//        	}
1264
//        	result.addExceptions(exceptions);
1265
//        	result.setError();
1266
//
1267
//        }
1268
        return result;
1269

    
1270
    }
1271

    
1272
    private String checkForReferences(Taxon taxon){
1273
        Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1274
        for (CdmBase referencingObject : referencingObjects){
1275
            //IIdentificationKeys (Media, Polytomous, MultiAccess)
1276
            if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1277
                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";
1278

    
1279
                return message;
1280
            }
1281

    
1282

    
1283
           /* //PolytomousKeyNode
1284
            if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1285
                String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1286
                return message;
1287
            }*/
1288

    
1289
            //TaxonInteraction
1290
            if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1291
                String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1292
                return message;
1293
            }
1294

    
1295
          //TaxonInteraction
1296
            if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1297
                String message = "Taxon can't be deleted as it is used in a determination event";
1298
                return message;
1299
            }
1300

    
1301
        }
1302

    
1303
        referencingObjects = null;
1304
        return null;
1305
    }
1306

    
1307
    private boolean checkForPolytomousKeys(Taxon taxon){
1308
        boolean result = false;
1309
        List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1310
        if (!list.isEmpty()) {
1311
            result = true;
1312
        }
1313
        return result;
1314
    }
1315

    
1316
    @Transactional(readOnly = false)
1317
    public UUID delete(Synonym syn){
1318
        UUID result = syn.getUuid();
1319
        this.deleteSynonym(syn, null);
1320
        return result;
1321
    }
1322

    
1323

    
1324

    
1325
    /* (non-Javadoc)
1326
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1327
     */
1328
    @Transactional(readOnly = false)
1329
    @Override
1330
    public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1331
        return deleteSynonym(synonym, null, config);
1332

    
1333
    }
1334

    
1335

    
1336
    /* (non-Javadoc)
1337
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
1338
     */
1339
    @Transactional(readOnly = false)
1340
    @Override
1341
    public DeleteResult deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1342
        DeleteResult result = new DeleteResult();
1343
    	if (synonym == null){
1344
    		result.setAbort();
1345
    		return result;
1346
        }
1347

    
1348
        if (config == null){
1349
            config = new SynonymDeletionConfigurator();
1350
        }
1351
        result = isDeletable(synonym, config);
1352

    
1353

    
1354
        if (result.isOk()){
1355
            synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
1356

    
1357
            //remove synonymRelationship
1358
            Set<Taxon> taxonSet = new HashSet<Taxon>();
1359
            if (taxon != null){
1360
                taxonSet.add(taxon);
1361
            }else{
1362
                taxonSet.addAll(synonym.getAcceptedTaxa());
1363
            }
1364
            for (Taxon relatedTaxon : taxonSet){
1365
    //			dao.deleteSynonymRelationships(synonym, relatedTaxon);
1366
                relatedTaxon.removeSynonym(synonym, config.isNewHomotypicGroupIfNeeded());
1367
            }
1368
            this.saveOrUpdate(synonym);
1369

    
1370
            //TODO remove name from homotypical group?
1371

    
1372
            //remove synonym (if necessary)
1373

    
1374
            UUID uuid = null;
1375
            if (synonym.getSynonymRelations().isEmpty()){
1376
                TaxonNameBase<?,?> name = synonym.getName();
1377
                synonym.setName(null);
1378
                uuid = dao.delete(synonym);
1379

    
1380
                //remove name if possible (and required)
1381
                if (name != null && config.isDeleteNameIfPossible()){
1382

    
1383
                        nameService.delete(name, config.getNameDeletionConfig());
1384

    
1385
                }
1386

    
1387
            }else {
1388
            	result.setError();
1389
            	result.addException(new ReferencedObjectUndeletableException("Synonym can not be deleted it is used in a synonymRelationship."));
1390
                return result;
1391
            }
1392

    
1393
        
1394
        }
1395
        return result;
1396
//        else{
1397
//        	List<Exception> exceptions = new ArrayList<Exception>();
1398
//        	for (String message :messages){
1399
//        		exceptions.add(new ReferencedObjectUndeletableException(message));
1400
//        	}
1401
//        	result.setError();
1402
//        	result.addExceptions(exceptions);
1403
//            return result;
1404
//        }
1405

    
1406

    
1407
    }
1408

    
1409

    
1410
    /* (non-Javadoc)
1411
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNameIds(java.util.List)
1412
     */
1413
    @Override
1414
    public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1415

    
1416
        return this.dao.findIdenticalNamesNew(propertyPath);
1417
    }
1418

    
1419
    /* (non-Javadoc)
1420
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getPhylumName(eu.etaxonomy.cdm.model.name.TaxonNameBase)
1421
     */
1422
    @Override
1423
    public String getPhylumName(TaxonNameBase name){
1424
        return this.dao.getPhylumName(name);
1425
    }
1426

    
1427
    /* (non-Javadoc)
1428
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
1429
     */
1430
    @Override
1431
    public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1432
        return dao.deleteSynonymRelationships(syn, taxon);
1433
    }
1434

    
1435
/* (non-Javadoc)
1436
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym)
1437
     */
1438
    @Override
1439
    public long deleteSynonymRelationships(Synonym syn) {
1440
        return dao.deleteSynonymRelationships(syn, null);
1441
    }
1442

    
1443

    
1444
    /* (non-Javadoc)
1445
     * @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)
1446
     */
1447
    @Override
1448
    public List<SynonymRelationship> listSynonymRelationships(
1449
            TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1450
            List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1451
        Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1452

    
1453
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1454
        if(numberOfResults > 0) { // no point checking again
1455
            results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1456
        }
1457
        return results;
1458
    }
1459

    
1460
    /* (non-Javadoc)
1461
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingTaxon(java.lang.String)
1462
     */
1463
    @Override
1464
    public Taxon findBestMatchingTaxon(String taxonName) {
1465
        MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1466
        config.setTaxonNameTitle(taxonName);
1467
        return findBestMatchingTaxon(config);
1468
    }
1469

    
1470

    
1471

    
1472
    @Override
1473
    public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1474

    
1475
        Taxon bestCandidate = null;
1476
        try{
1477
            // 1. search for acceptet taxa
1478
            List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1479
            boolean bestCandidateMatchesSecUuid = false;
1480
            boolean bestCandidateIsInClassification = false;
1481
            int countEqualCandidates = 0;
1482
            for(TaxonBase taxonBaseCandidate : taxonList){
1483
                if(taxonBaseCandidate instanceof Taxon){
1484
                    Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1485
                    boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1486
                    if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1487
                        continue;
1488
                    }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1489
                        bestCandidate = newCanditate;
1490
                        countEqualCandidates = 1;
1491
                        bestCandidateMatchesSecUuid = true;
1492
                        continue;
1493
                    }
1494

    
1495
                    boolean newCandidateInClassification = isInClassification(newCanditate, config);
1496
                    if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1497
                        continue;
1498
                    }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1499
                        bestCandidate = newCanditate;
1500
                        countEqualCandidates = 1;
1501
                        bestCandidateIsInClassification = true;
1502
                        continue;
1503
                    }
1504
                    if (bestCandidate == null){
1505
                        bestCandidate = newCanditate;
1506
                        countEqualCandidates = 1;
1507
                        continue;
1508
                    }
1509

    
1510
                }else{  //not Taxon.class
1511
                    continue;
1512
                }
1513
                countEqualCandidates++;
1514

    
1515
            }
1516
            if (bestCandidate != null){
1517
                if(countEqualCandidates > 1){
1518
                    logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1519
                    return bestCandidate;
1520
                } else {
1521
                    logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1522
                    return bestCandidate;
1523
                }
1524
            }
1525

    
1526

    
1527
            // 2. search for synonyms
1528
            if (config.isIncludeSynonyms()){
1529
                List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1530
                for(TaxonBase taxonBase : synonymList){
1531
                    if(taxonBase instanceof Synonym){
1532
                        Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1533
                        Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1534
                        if(!acceptetdCandidates.isEmpty()){
1535
                            bestCandidate = acceptetdCandidates.iterator().next();
1536
                            if(acceptetdCandidates.size() == 1){
1537
                                logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1538
                                return bestCandidate;
1539
                            } else {
1540
                                logger.info("using accepted Taxon " +  bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1541
                                return bestCandidate;
1542
                            }
1543
                            //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1544
                        }
1545
                    }
1546
                }
1547
            }
1548

    
1549
        } catch (Exception e){
1550
            logger.error(e);
1551
            e.printStackTrace();
1552
        }
1553

    
1554
        return bestCandidate;
1555
    }
1556

    
1557
    private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1558
        UUID configClassificationUuid = config.getClassificationUuid();
1559
        if (configClassificationUuid == null){
1560
            return false;
1561
        }
1562
        for (TaxonNode node : taxon.getTaxonNodes()){
1563
            UUID classUuid = node.getClassification().getUuid();
1564
            if (configClassificationUuid.equals(classUuid)){
1565
                return true;
1566
            }
1567
        }
1568
        return false;
1569
    }
1570

    
1571
    private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1572
        UUID configSecUuid = config.getSecUuid();
1573
        if (configSecUuid == null){
1574
            return false;
1575
        }
1576
        UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1577
        return configSecUuid.equals(taxonSecUuid);
1578
    }
1579

    
1580
    /* (non-Javadoc)
1581
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingSynonym(java.lang.String)
1582
     */
1583
    @Override
1584
    public Synonym findBestMatchingSynonym(String taxonName) {
1585
        List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1586
        if(! synonymList.isEmpty()){
1587
            Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1588
            if(synonymList.size() == 1){
1589
                logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1590
                return result;
1591
            } else {
1592
                logger.info("Several matching synonyms found. Using first: " +  result.getTitleCache());
1593
                return result;
1594
            }
1595
        }
1596
        return null;
1597
    }
1598

    
1599

    
1600
    /* (non-Javadoc)
1601
     * @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)
1602
     */
1603
    @Override
1604
    public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, Taxon newTaxon, boolean moveHomotypicGroup,
1605
            SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
1606

    
1607
        Synonym synonym = oldSynonymRelation.getSynonym();
1608
        Taxon fromTaxon = oldSynonymRelation.getAcceptedTaxon();
1609
        //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1610
        TaxonNameBase<?,?> synonymName = synonym.getName();
1611
        TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1612
        //set default relationship type
1613
        if (newSynonymRelationshipType == null){
1614
            newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1615
        }
1616
        boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1617

    
1618
        HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1619
        int hgSize = homotypicGroup.getTypifiedNames().size();
1620
        boolean isSingleInGroup = !(hgSize > 1);
1621

    
1622
        if (! isSingleInGroup){
1623
            boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1624
            boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1625
            if (isHomotypicToAccepted){
1626
                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.";
1627
                String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1628
                message = String.format(message, homotypicRelatives);
1629
                throw new HomotypicalGroupChangeException(message);
1630
            }
1631
            if (! moveHomotypicGroup){
1632
                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.";
1633
                throw new HomotypicalGroupChangeException(message);
1634
            }
1635
        }else{
1636
            moveHomotypicGroup = true;  //single synonym always allows to moveCompleteGroup
1637
        }
1638
//        Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1639

    
1640
        SynonymRelationship result = null;
1641
        //move all synonyms to new taxon
1642
        List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1643
        for (Synonym syn: homotypicSynonyms){
1644
            Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1645
            for (SynonymRelationship synRelation : synRelations){
1646
                if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1647
                    Reference<?> newReference = reference;
1648
                    if (newReference == null && keepReference){
1649
                        newReference = synRelation.getCitation();
1650
                    }
1651
                    String newRefDetail = referenceDetail;
1652
                    if (newRefDetail == null && keepReference){
1653
                        newRefDetail = synRelation.getCitationMicroReference();
1654
                    }
1655
                    newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
1656
                    fromTaxon = HibernateProxyHelper.deproxy(fromTaxon, Taxon.class);
1657
                    SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1658
                    fromTaxon.removeSynonymRelation(synRelation, false);
1659
//
1660
                    //change homotypic group of synonym if relType is 'homotypic'
1661
//                	if (newRelTypeIsHomotypic){
1662
//                		newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1663
//                	}
1664
                    //set result
1665
                    if (synRelation.equals(oldSynonymRelation)){
1666
                        result = newSynRelation;
1667
                    }
1668
                }
1669
            }
1670

    
1671
        }
1672
        saveOrUpdate(fromTaxon);
1673
        saveOrUpdate(newTaxon);
1674
        //Assert that there is a result
1675
        if (result == null){
1676
            String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1677
            throw new IllegalStateException(message);
1678
        }
1679
        return result;
1680
    }
1681

    
1682
    /* (non-Javadoc)
1683
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheTaxon()
1684
     */
1685
    @Override
1686
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1687
        return dao.getUuidAndTitleCacheTaxon();
1688
    }
1689

    
1690
    /* (non-Javadoc)
1691
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheSynonym()
1692
     */
1693
    @Override
1694
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1695
        return dao.getUuidAndTitleCacheSynonym();
1696
    }
1697

    
1698
    /* (non-Javadoc)
1699
     * @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)
1700
     */
1701
    @Override
1702
    public Pager<SearchResult<TaxonBase>> findByFullText(
1703
            Class<? extends TaxonBase> clazz, String queryString,
1704
            Classification classification, List<Language> languages,
1705
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1706

    
1707

    
1708
        LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1709

    
1710
        // --- execute search
1711
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1712

    
1713
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1714
        idFieldMap.put(CdmBaseType.TAXON, "id");
1715

    
1716
        // ---  initialize taxa, thighlight matches ....
1717
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1718
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1719
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1720

    
1721
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1722
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1723
    }
1724

    
1725
    @Override
1726
    public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
1727
            Classification classification,
1728
            Integer pageSize, Integer pageNumber,
1729
            List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1730

    
1731
        LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1732

    
1733
        // --- execute search
1734
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1735

    
1736
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1737
        idFieldMap.put(CdmBaseType.TAXON, "id");
1738

    
1739
        // ---  initialize taxa, thighlight matches ....
1740
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1741
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1742
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1743

    
1744
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1745
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1746
    }
1747

    
1748
    /**
1749
     * @param clazz
1750
     * @param queryString
1751
     * @param classification
1752
     * @param languages
1753
     * @param highlightFragments
1754
     * @param sortFields TODO
1755
     * @param directorySelectClass
1756
     * @return
1757
     */
1758
    protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1759
            boolean highlightFragments, SortField[] sortFields) {
1760
        BooleanQuery finalQuery = new BooleanQuery();
1761
        BooleanQuery textQuery = new BooleanQuery();
1762

    
1763
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1764
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1765

    
1766
        if(sortFields == null){
1767
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
1768
        }
1769
        luceneSearch.setSortFields(sortFields);
1770

    
1771
        // ---- search criteria
1772
        luceneSearch.setCdmTypRestriction(clazz);
1773

    
1774
        textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1775
        textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1776

    
1777
        finalQuery.add(textQuery, Occur.MUST);
1778

    
1779
        if(classification != null){
1780
            finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1781
        }
1782
        luceneSearch.setQuery(finalQuery);
1783

    
1784
        if(highlightFragments){
1785
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1786
        }
1787
        return luceneSearch;
1788
    }
1789

    
1790
    /**
1791
     * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1792
     * the BlockJoinQuery could be used. The latter might be more memory save but has the
1793
     * drawback of requiring to do the join an indexing time.
1794
     * see  http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1795
     *
1796
     * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1797
     * <ul>
1798
     * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1799
     * <li>inverse: {@link Direction.relatedFrom}:  TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1800
     * <ul>
1801
     * @param queryString
1802
     * @param classification
1803
     * @param languages
1804
     * @param highlightFragments
1805
     * @param sortFields TODO
1806
     *
1807
     * @return
1808
     * @throws IOException
1809
     */
1810
    protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1811
            boolean highlightFragments, SortField[] sortFields) throws IOException {
1812

    
1813
        String fromField;
1814
        String queryTermField;
1815
        String toField = "id"; // TaxonBase.uuid
1816

    
1817
        if(edge.isBidirectional()){
1818
            throw new RuntimeException("Bidirectional joining not supported!");
1819
        }
1820
        if(edge.isEvers()){
1821
            fromField = "relatedFrom.id";
1822
            queryTermField = "relatedFrom.titleCache";
1823
        } else if(edge.isInvers()) {
1824
            fromField = "relatedTo.id";
1825
            queryTermField = "relatedTo.titleCache";
1826
        } else {
1827
            throw new RuntimeException("Invalid direction: " + edge.getDirections());
1828
        }
1829

    
1830
        BooleanQuery finalQuery = new BooleanQuery();
1831

    
1832
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1833
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1834

    
1835
        BooleanQuery joinFromQuery = new BooleanQuery();
1836
        joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1837
        joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1838
        Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1839

    
1840
        if(sortFields == null){
1841
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
1842
        }
1843
        luceneSearch.setSortFields(sortFields);
1844

    
1845
        finalQuery.add(joinQuery, Occur.MUST);
1846

    
1847
        if(classification != null){
1848
            finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1849
        }
1850
        luceneSearch.setQuery(finalQuery);
1851

    
1852
        if(highlightFragments){
1853
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1854
        }
1855
        return luceneSearch;
1856
    }
1857

    
1858

    
1859

    
1860

    
1861
    /* (non-Javadoc)
1862
     * @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)
1863
     */
1864
    @Override
1865
    public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1866
            EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1867
            Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
1868
            boolean highlightFragments, Integer pageSize,
1869
            Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1870
            throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1871

    
1872
        // FIXME: allow taxonomic ordering
1873
        //  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";
1874
        // this require building a special sort column by a special classBridge
1875
        if(highlightFragments){
1876
            logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1877
                    "currently not fully supported by this method and thus " +
1878
                    "may not work with common names and misapplied names.");
1879
        }
1880

    
1881
        // convert sets to lists
1882
        List<NamedArea> namedAreaList = null;
1883
        List<PresenceAbsenceTerm>distributionStatusList = null;
1884
        if(namedAreas != null){
1885
            namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1886
            namedAreaList.addAll(namedAreas);
1887
        }
1888
        if(distributionStatus != null){
1889
            distributionStatusList = new ArrayList<PresenceAbsenceTerm>(distributionStatus.size());
1890
            distributionStatusList.addAll(distributionStatus);
1891
        }
1892

    
1893
        // set default if parameter is null
1894
        if(searchModes == null){
1895
            searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1896
        }
1897

    
1898
        // set sort order and thus override any sort orders which may have been
1899
        // defindes by prepare*Search methods
1900
        if(orderHints == null){
1901
            orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER;
1902
        }
1903
        SortField[] sortFields = new SortField[orderHints.size()];
1904
        int i = 0;
1905
        for(OrderHint oh : orderHints){
1906
            sortFields[i++] = oh.toSortField();
1907
        }
1908
//        SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1909
//        SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1910

    
1911

    
1912
        boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1913

    
1914
        List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1915
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1916

    
1917
        /*
1918
          ======== filtering by distribution , HOWTO ========
1919

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

    
1925

    
1926
          3. how does it work in spatial?
1927
          see
1928
           - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1929
           - http://www.infoq.com/articles/LuceneSpatialSupport
1930
           - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1931
          ------------------------------------------------------------------------
1932

    
1933
          filter strategies:
1934
          A) use a separate distribution filter per index sub-query/search:
1935
           - byTaxonSyonym (query TaxaonBase):
1936
               use a join area filter (Distribution -> TaxonBase)
1937
           - byCommonName (query DescriptionElementBase): use an area filter on
1938
               DescriptionElementBase !!! PROBLEM !!!
1939
               This cannot work since the distributions are different entities than the
1940
               common names and thus these are different lucene documents.
1941
           - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1942
               use a join area filter (Distribution -> TaxonBase)
1943

    
1944
          B) use a common distribution filter for all index sub-query/searches:
1945
           - use a common join area filter (Distribution -> TaxonBase)
1946
           - also implement the byCommonName as join query (CommonName -> TaxonBase)
1947
           PROBLEM in this case: we are losing the fragment highlighting for the
1948
           common names, since the returned documents are always TaxonBases
1949
        */
1950

    
1951
        /* The QueryFactory for creating filter queries on Distributions should
1952
         * The query factory used for the common names query cannot be reused
1953
         * for this case, since we want to only record the text fields which are
1954
         * actually used in the primary query
1955
         */
1956
        QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1957

    
1958
        BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1959

    
1960

    
1961
        // search for taxa or synonyms
1962
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1963
            Class taxonBaseSubclass = TaxonBase.class;
1964
            if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1965
                taxonBaseSubclass = Taxon.class;
1966
            } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1967
                taxonBaseSubclass = Synonym.class;
1968
            }
1969
            luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1970
            idFieldMap.put(CdmBaseType.TAXON, "id");
1971
            /* A) does not work!!!!
1972
            if(addDistributionFilter){
1973
                // in this case we need a filter which uses a join query
1974
                // to get the TaxonBase documents for the DescriptionElementBase documents
1975
                // which are matching the areas in question
1976
                Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1977
                        namedAreaList,
1978
                        distributionStatusList,
1979
                        distributionFilterQueryFactory
1980
                        );
1981
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1982
            }
1983
            */
1984
            if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1985
                // add additional area filter for synonyms
1986
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1987
                String toField = "accTaxon.id"; // id in TaxonBase index
1988

    
1989
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1990

    
1991
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1992
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1993

    
1994
            }
1995
        }
1996

    
1997
        // search by CommonTaxonName
1998
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1999
            // B)
2000
            QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2001
            Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
2002
                    "inDescription.taxon.id",
2003
                    "id",
2004
                    QueryFactory.addTypeRestriction(
2005
                                createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
2006
                                , CommonTaxonName.class
2007
                                ),
2008
                    CommonTaxonName.class);
2009
            logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
2010
            LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2011
            byCommonNameSearch.setCdmTypRestriction(Taxon.class);
2012
            byCommonNameSearch.setQuery(byCommonNameJoinQuery);
2013
            byCommonNameSearch.setSortFields(sortFields);
2014
            idFieldMap.put(CdmBaseType.TAXON, "id");
2015

    
2016
            luceneSearches.add(byCommonNameSearch);
2017

    
2018
            /* A) does not work!!!!
2019
            luceneSearches.add(
2020
                    prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
2021
                            queryString, classification, null, languages, highlightFragments)
2022
                        );
2023
            idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2024
            if(addDistributionFilter){
2025
                // in this case we are able to use DescriptionElementBase documents
2026
                // which are matching the areas in question directly
2027
                BooleanQuery byDistributionQuery = createByDistributionQuery(
2028
                        namedAreaList,
2029
                        distributionStatusList,
2030
                        distributionFilterQueryFactory
2031
                        );
2032
                multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
2033
            } */
2034
        }
2035

    
2036
        // search by misapplied names
2037
        if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
2038
            // NOTE:
2039
            // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
2040
            // which allows doing query time joins
2041
            // finds the misapplied name (Taxon B) which is an misapplication for
2042
            // a related Taxon A.
2043
            //
2044
            luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
2045
                    new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
2046
                    queryString, classification, languages, highlightFragments, sortFields));
2047
            idFieldMap.put(CdmBaseType.TAXON, "id");
2048

    
2049
            if(addDistributionFilter){
2050
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2051

    
2052
                /*
2053
                 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
2054
                 * Maybe this is a but in java itself java.
2055
                 *
2056
                 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
2057
                 * directly:
2058
                 *
2059
                 *    String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
2060
                 *
2061
                 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
2062
                 * will execute as expected:
2063
                 *
2064
                 *    String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2065
                 *    String toField = "relation." + misappliedNameForUuid +".to.id";
2066
                 *
2067
                 * Comparing both strings by the String.equals method returns true, so both String are identical.
2068
                 *
2069
                 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2070
                 * 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)
2071
                 * The bug is persistent after a reboot of the development computer.
2072
                 */
2073
//                String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2074
//                String toField = "relation." + misappliedNameForUuid +".to.id";
2075
                String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2076
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2077
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2078

    
2079
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2080
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2081
                QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery);
2082

    
2083
//                debug code for bug described above
2084
                DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2085
//                System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2086

    
2087
                multiIndexByAreaFilter.add(filter, Occur.SHOULD);
2088
            }
2089
        }
2090

    
2091
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
2092
                luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
2093

    
2094

    
2095
        if(addDistributionFilter){
2096

    
2097
            // B)
2098
            // in this case we need a filter which uses a join query
2099
            // to get the TaxonBase documents for the DescriptionElementBase documents
2100
            // which are matching the areas in question
2101
            //
2102
            // for toTaxa, doByCommonName
2103
            Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2104
                    namedAreaList,
2105
                    distributionStatusList,
2106
                    distributionFilterQueryFactory
2107
                    );
2108
            multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
2109
        }
2110

    
2111
        if (addDistributionFilter){
2112
            multiSearch.setFilter(multiIndexByAreaFilter);
2113
        }
2114

    
2115

    
2116
        // --- execute search
2117
        TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2118

    
2119
        // --- initialize taxa, highlight matches ....
2120
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2121

    
2122

    
2123
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2124
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2125

    
2126
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2127
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2128
    }
2129

    
2130
    /**
2131
     * @param namedAreaList at least one area must be in the list
2132
     * @param distributionStatusList optional
2133
     * @return
2134
     * @throws IOException
2135
     */
2136
    protected Query createByDistributionJoinQuery(
2137
            List<NamedArea> namedAreaList,
2138
            List<PresenceAbsenceTerm> distributionStatusList,
2139
            QueryFactory queryFactory
2140
            ) throws IOException {
2141

    
2142
        String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2143
        String toField = "id"; // id in TaxonBase index
2144

    
2145
        BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2146

    
2147
        Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2148

    
2149
        return taxonAreaJoinQuery;
2150
    }
2151

    
2152
    /**
2153
     * @param namedAreaList
2154
     * @param distributionStatusList
2155
     * @param queryFactory
2156
     * @return
2157
     */
2158
    private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2159
            List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2160
        BooleanQuery areaQuery = new BooleanQuery();
2161
        // area field from Distribution
2162
        areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2163

    
2164
        // status field from Distribution
2165
        if(distributionStatusList != null && distributionStatusList.size() > 0){
2166
            areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2167
        }
2168

    
2169
        logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2170
        return areaQuery;
2171
    }
2172

    
2173
    /**
2174
     * This method has been primarily created for testing the area join query but might
2175
     * also be useful in other situations
2176
     *
2177
     * @param namedAreaList
2178
     * @param distributionStatusList
2179
     * @param classification
2180
     * @param highlightFragments
2181
     * @return
2182
     * @throws IOException
2183
     */
2184
    protected LuceneSearch prepareByDistributionSearch(
2185
            List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
2186
            Classification classification) throws IOException {
2187

    
2188
        BooleanQuery finalQuery = new BooleanQuery();
2189

    
2190
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2191

    
2192
        // FIXME is this query factory using the wrong type?
2193
        QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2194

    
2195
        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
2196
        luceneSearch.setSortFields(sortFields);
2197

    
2198

    
2199
        Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory);
2200

    
2201
        finalQuery.add(byAreaQuery, Occur.MUST);
2202

    
2203
        if(classification != null){
2204
            finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2205
        }
2206

    
2207
        logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2208
        luceneSearch.setQuery(finalQuery);
2209

    
2210
        return luceneSearch;
2211
    }
2212

    
2213

    
2214

    
2215
    /* (non-Javadoc)
2216
     * @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)
2217
     */
2218
    @Override
2219
    public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2220
            Class<? extends DescriptionElementBase> clazz, String queryString,
2221
            Classification classification, List<Feature> features, List<Language> languages,
2222
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2223

    
2224

    
2225
        LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2226

    
2227
        // --- execute search
2228
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2229

    
2230
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2231
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2232

    
2233
        // --- initialize taxa, highlight matches ....
2234
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2235
        @SuppressWarnings("rawtypes")
2236
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2237
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2238

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

    
2242
    }
2243

    
2244

    
2245
    @Override
2246
    public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2247
            Classification classification, List<Language> languages, boolean highlightFragments,
2248
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2249

    
2250
        LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2251
        LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2252

    
2253
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2254

    
2255
        // --- execute search
2256
        TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2257

    
2258
        // --- initialize taxa, highlight matches ....
2259
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2260

    
2261
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2262
        idFieldMap.put(CdmBaseType.TAXON, "id");
2263
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2264

    
2265
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2266
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2267

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

    
2271
    }
2272

    
2273

    
2274
    /**
2275
     * @param clazz
2276
     * @param queryString
2277
     * @param classification
2278
     * @param features
2279
     * @param languages
2280
     * @param highlightFragments
2281
     * @param directorySelectClass
2282
     * @return
2283
     */
2284
    protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2285
            String queryString, Classification classification, List<Feature> features,
2286
            List<Language> languages, boolean highlightFragments) {
2287

    
2288
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2289
        QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2290

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

    
2293
        BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2294
                languages, descriptionElementQueryFactory);
2295

    
2296
        luceneSearch.setSortFields(sortFields);
2297
        luceneSearch.setCdmTypRestriction(clazz);
2298
        luceneSearch.setQuery(finalQuery);
2299
        if(highlightFragments){
2300
            luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2301
        }
2302

    
2303
        return luceneSearch;
2304
    }
2305

    
2306
    /**
2307
     * @param queryString
2308
     * @param classification
2309
     * @param features
2310
     * @param languages
2311
     * @param descriptionElementQueryFactory
2312
     * @return
2313
     */
2314
    private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2315
            List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2316
        BooleanQuery finalQuery = new BooleanQuery();
2317
        BooleanQuery textQuery = new BooleanQuery();
2318
        textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2319

    
2320
        // common name
2321
        Query nameQuery;
2322
        if(languages == null || languages.size() == 0){
2323
            nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString);
2324
        } else {
2325
            nameQuery = new BooleanQuery();
2326
            BooleanQuery languageSubQuery = new BooleanQuery();
2327
            for(Language lang : languages){
2328
                languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid",  lang.getUuid().toString(), false), Occur.SHOULD);
2329
            }
2330
            ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2331
            ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
2332
        }
2333
        textQuery.add(nameQuery, Occur.SHOULD);
2334

    
2335

    
2336
        // text field from TextData
2337
        textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2338

    
2339
        // --- TermBase fields - by representation ----
2340
        // state field from CategoricalData
2341
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2342

    
2343
        // state field from CategoricalData
2344
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2345

    
2346
        // area field from Distribution
2347
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2348

    
2349
        // status field from Distribution
2350
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2351

    
2352
        finalQuery.add(textQuery, Occur.MUST);
2353
        // --- classification ----
2354

    
2355
        if(classification != null){
2356
            finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2357
        }
2358

    
2359
        // --- IdentifieableEntity fields - by uuid
2360
        if(features != null && features.size() > 0 ){
2361
            finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2362
        }
2363

    
2364
        // the description must be associated with a taxon
2365
        finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2366

    
2367
        logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2368
        return finalQuery;
2369
    }
2370

    
2371
    /**
2372
     * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2373
     * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2374
     * This method is a convenient means to retrieve a Lucene query string for such the fields.
2375
     *
2376
     * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2377
     * or {@link MultilanguageTextFieldBridge }
2378
     * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2379
     * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2380
     * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2381
     *
2382
     * TODO move to utiliy class !!!!!!!!
2383
     */
2384
    private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2385

    
2386
        if(stringBuilder == null){
2387
            stringBuilder = new StringBuilder();
2388
        }
2389
        if(languages == null || languages.size() == 0){
2390
            stringBuilder.append(name + ".ALL:(%1$s) ");
2391
        } else {
2392
            for(Language lang : languages){
2393
                stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2394
            }
2395
        }
2396
        return stringBuilder;
2397
    }
2398

    
2399
    @Override
2400
    public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2401
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2402
        List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2403

    
2404
        HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2405

    
2406

    
2407
        UUID nameUuid= taxon.getName().getUuid();
2408
        ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2409
        String epithetOfTaxon = null;
2410
        String infragenericEpithetOfTaxon = null;
2411
        String infraspecificEpithetOfTaxon = null;
2412
        if (taxonName.isSpecies()){
2413
             epithetOfTaxon= taxonName.getSpecificEpithet();
2414
        } else if (taxonName.isInfraGeneric()){
2415
            infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2416
        } else if (taxonName.isInfraSpecific()){
2417
            infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2418
        }
2419
        String genusOfTaxon = taxonName.getGenusOrUninomial();
2420
        Set<TaxonNode> nodes = taxon.getTaxonNodes();
2421
        List<String> taxonNames = new ArrayList<String>();
2422

    
2423
        for (TaxonNode node: nodes){
2424
           // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2425
           // List<String> synonymsEpithet = new ArrayList<String>();
2426

    
2427
            if (node.getClassification().equals(classification)){
2428
                if (!node.isTopmostNode()){
2429
                    TaxonNode parent = node.getParent();
2430
                    parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
2431
                    TaxonNameBase<?,?> parentName =  parent.getTaxon().getName();
2432
                    ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2433
                    Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
2434
                    Rank rankOfTaxon = taxonName.getRank();
2435

    
2436

    
2437
                    //create inferred synonyms for species, subspecies
2438
                    if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2439

    
2440
                        Synonym inferredEpithet = null;
2441
                        Synonym inferredGenus = null;
2442
                        Synonym potentialCombination = null;
2443

    
2444
                        List<String> propertyPaths = new ArrayList<String>();
2445
                        propertyPaths.add("synonym");
2446
                        propertyPaths.add("synonym.name");
2447
                        List<OrderHint> orderHints = new ArrayList<OrderHint>();
2448
                        orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2449

    
2450
                        List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2451
                        List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2452

    
2453
                        List<TaxonRelationship> taxonRelListParent = null;
2454
                        List<TaxonRelationship> taxonRelListTaxon = null;
2455
                        if (doWithMisappliedNames){
2456
                            taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2457
                            taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2458
                        }
2459

    
2460

    
2461
                        if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2462

    
2463

    
2464
                            for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2465
                                Synonym syn = synonymRelationOfParent.getSynonym();
2466

    
2467
                                inferredEpithet = createInferredEpithets(taxon,
2468
                                        zooHashMap, taxonName, epithetOfTaxon,
2469
                                        infragenericEpithetOfTaxon,
2470
                                        infraspecificEpithetOfTaxon,
2471
                                        taxonNames, parentName,
2472
                                        syn);
2473

    
2474

    
2475
                                inferredSynonyms.add(inferredEpithet);
2476
                                zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2477
                                taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2478
                            }
2479

    
2480
                            if (doWithMisappliedNames){
2481

    
2482
                                for (TaxonRelationship taxonRelationship: taxonRelListParent){
2483
                                     Taxon misappliedName = taxonRelationship.getFromTaxon();
2484

    
2485
                                     inferredEpithet = createInferredEpithets(taxon,
2486
                                             zooHashMap, taxonName, epithetOfTaxon,
2487
                                             infragenericEpithetOfTaxon,
2488
                                             infraspecificEpithetOfTaxon,
2489
                                             taxonNames, parentName,
2490
                                             misappliedName);
2491

    
2492
                                    inferredSynonyms.add(inferredEpithet);
2493
                                    zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2494
                                     taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2495
                                }
2496
                            }
2497

    
2498
                            if (!taxonNames.isEmpty()){
2499
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2500
                            ZoologicalName name;
2501
                            if (!synNotInCDM.isEmpty()){
2502
                                inferredSynonymsToBeRemoved.clear();
2503

    
2504
                                for (Synonym syn :inferredSynonyms){
2505
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2506
                                    if (!synNotInCDM.contains(name.getNameCache())){
2507
                                        inferredSynonymsToBeRemoved.add(syn);
2508
                                    }
2509
                                }
2510

    
2511
                                // Remove identified Synonyms from inferredSynonyms
2512
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2513
                                    inferredSynonyms.remove(synonym);
2514
                                }
2515
                            }
2516
                        }
2517

    
2518
                    }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2519

    
2520

    
2521
                        for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2522
                            TaxonNameBase synName;
2523
                            ZoologicalName inferredSynName;
2524

    
2525
                            Synonym syn = synonymRelationOfTaxon.getSynonym();
2526
                            inferredGenus = createInferredGenus(taxon,
2527
                                    zooHashMap, taxonName, epithetOfTaxon,
2528
                                    genusOfTaxon, taxonNames, zooParentName, syn);
2529

    
2530
                            inferredSynonyms.add(inferredGenus);
2531
                            zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2532
                            taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2533

    
2534

    
2535
                        }
2536

    
2537
                        if (doWithMisappliedNames){
2538

    
2539
                            for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2540
                                Taxon misappliedName = taxonRelationship.getFromTaxon();
2541
                                inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
2542

    
2543
                                inferredSynonyms.add(inferredGenus);
2544
                                zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2545
                                 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2546
                            }
2547
                        }
2548

    
2549

    
2550
                        if (!taxonNames.isEmpty()){
2551
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2552
                            ZoologicalName name;
2553
                            if (!synNotInCDM.isEmpty()){
2554
                                inferredSynonymsToBeRemoved.clear();
2555

    
2556
                                for (Synonym syn :inferredSynonyms){
2557
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2558
                                    if (!synNotInCDM.contains(name.getNameCache())){
2559
                                        inferredSynonymsToBeRemoved.add(syn);
2560
                                    }
2561
                                }
2562

    
2563
                                // Remove identified Synonyms from inferredSynonyms
2564
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2565
                                    inferredSynonyms.remove(synonym);
2566
                                }
2567
                            }
2568
                        }
2569

    
2570
                    }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2571

    
2572
                        Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2573
                        ZoologicalName inferredSynName;
2574
                        //for all synonyms of the parent...
2575
                        for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2576
                            TaxonNameBase synName;
2577
                            Synonym synParent = synonymRelationOfParent.getSynonym();
2578
                            synName = synParent.getName();
2579

    
2580
                            HibernateProxyHelper.deproxy(synParent);
2581

    
2582
                            // Set the sourceReference
2583
                            sourceReference = synParent.getSec();
2584

    
2585
                            // Determine the idInSource
2586
                            String idInSourceParent = getIdInSource(synParent);
2587

    
2588
                            ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2589
                            String synParentGenus = parentSynZooName.getGenusOrUninomial();
2590
                            String synParentInfragenericName = null;
2591
                            String synParentSpecificEpithet = null;
2592

    
2593
                            if (parentSynZooName.isInfraGeneric()){
2594
                                synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2595
                            }
2596
                            if (parentSynZooName.isSpecies()){
2597
                                synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2598
                            }
2599

    
2600
                           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2601
                                synonymsGenus.put(synGenusName, idInSource);
2602
                            }*/
2603

    
2604
                            //for all synonyms of the taxon
2605

    
2606
                            for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2607

    
2608
                                Synonym syn = synonymRelationOfTaxon.getSynonym();
2609
                                ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2610
                                potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2611
                                        synParentGenus,
2612
                                        synParentInfragenericName,
2613
                                        synParentSpecificEpithet, syn, zooHashMap);
2614

    
2615
                                taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2616
                                inferredSynonyms.add(potentialCombination);
2617
                                zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2618
                                 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2619

    
2620
                            }
2621

    
2622

    
2623
                        }
2624

    
2625
                        if (doWithMisappliedNames){
2626

    
2627
                            for (TaxonRelationship parentRelationship: taxonRelListParent){
2628

    
2629
                                TaxonNameBase misappliedParentName;
2630

    
2631
                                Taxon misappliedParent = parentRelationship.getFromTaxon();
2632
                                misappliedParentName = misappliedParent.getName();
2633

    
2634
                                HibernateProxyHelper.deproxy(misappliedParent);
2635

    
2636
                                // Set the sourceReference
2637
                                sourceReference = misappliedParent.getSec();
2638

    
2639
                                // Determine the idInSource
2640
                                String idInSourceParent = getIdInSource(misappliedParent);
2641

    
2642
                                ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2643
                                String synParentGenus = parentSynZooName.getGenusOrUninomial();
2644
                                String synParentInfragenericName = null;
2645
                                String synParentSpecificEpithet = null;
2646

    
2647
                                if (parentSynZooName.isInfraGeneric()){
2648
                                    synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2649
                                }
2650
                                if (parentSynZooName.isSpecies()){
2651
                                    synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2652
                                }
2653

    
2654

    
2655
                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2656
                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
2657
                                    ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2658
                                    potentialCombination = createPotentialCombination(
2659
                                            idInSourceParent, parentSynZooName, zooMisappliedName,
2660
                                            synParentGenus,
2661
                                            synParentInfragenericName,
2662
                                            synParentSpecificEpithet, misappliedName, zooHashMap);
2663

    
2664

    
2665
                                    taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2666
                                    inferredSynonyms.add(potentialCombination);
2667
                                    zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2668
                                     taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2669
                                }
2670
                            }
2671
                        }
2672

    
2673
                        if (!taxonNames.isEmpty()){
2674
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2675
                            ZoologicalName name;
2676
                            if (!synNotInCDM.isEmpty()){
2677
                                inferredSynonymsToBeRemoved.clear();
2678
                                for (Synonym syn :inferredSynonyms){
2679
                                    try{
2680
                                        name = (ZoologicalName) syn.getName();
2681
                                    }catch (ClassCastException e){
2682
                                        name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2683
                                    }
2684
                                    if (!synNotInCDM.contains(name.getNameCache())){
2685
                                        inferredSynonymsToBeRemoved.add(syn);
2686
                                    }
2687
                                 }
2688
                                // Remove identified Synonyms from inferredSynonyms
2689
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2690
                                    inferredSynonyms.remove(synonym);
2691
                                }
2692
                            }
2693
                         }
2694
                        }
2695
                    }else {
2696
                        logger.info("The synonymrelationship type is not defined.");
2697
                        return inferredSynonyms;
2698
                    }
2699
                }
2700
            }
2701

    
2702
        }
2703

    
2704
        return inferredSynonyms;
2705
    }
2706

    
2707
    private Synonym createPotentialCombination(String idInSourceParent,
2708
            ZoologicalName parentSynZooName, 	ZoologicalName zooSynName, String synParentGenus,
2709
            String synParentInfragenericName, String synParentSpecificEpithet,
2710
            TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2711
        Synonym potentialCombination;
2712
        Reference sourceReference;
2713
        ZoologicalName inferredSynName;
2714
        HibernateProxyHelper.deproxy(syn);
2715

    
2716
        // Set sourceReference
2717
        sourceReference = syn.getSec();
2718
        if (sourceReference == null){
2719
            logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2720
            //TODO:Remove
2721
            if (!parentSynZooName.getTaxa().isEmpty()){
2722
                TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2723

    
2724
                sourceReference = taxon.getSec();
2725
            }
2726
        }
2727
        String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2728

    
2729
        String synTaxonInfraSpecificName= null;
2730

    
2731
        if (parentSynZooName.isSpecies()){
2732
            synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2733
        }
2734

    
2735
        /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2736
            synonymsEpithet.add(epithetName);
2737
        }*/
2738

    
2739
        //create potential combinations...
2740
        inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2741

    
2742
        inferredSynName.setGenusOrUninomial(synParentGenus);
2743
        if (zooSynName.isSpecies()){
2744
              inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2745
              if (parentSynZooName.isInfraGeneric()){
2746
                  inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2747
              }
2748
        }
2749
        if (zooSynName.isInfraSpecific()){
2750
            inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2751
            inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2752
        }
2753
        if (parentSynZooName.isInfraGeneric()){
2754
            inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2755
        }
2756

    
2757

    
2758
        potentialCombination = Synonym.NewInstance(inferredSynName, null);
2759

    
2760
        // Set the sourceReference
2761
        potentialCombination.setSec(sourceReference);
2762

    
2763

    
2764
        // Determine the idInSource
2765
        String idInSourceSyn= getIdInSource(syn);
2766

    
2767
        if (idInSourceParent != null && idInSourceSyn != null) {
2768
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2769
            inferredSynName.addSource(originalSource);
2770
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2771
            potentialCombination.addSource(originalSource);
2772
        }
2773

    
2774
        return potentialCombination;
2775
    }
2776

    
2777
    private Synonym createInferredGenus(Taxon taxon,
2778
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2779
            String epithetOfTaxon, String genusOfTaxon,
2780
            List<String> taxonNames, ZoologicalName zooParentName,
2781
            TaxonBase syn) {
2782

    
2783
        Synonym inferredGenus;
2784
        TaxonNameBase synName;
2785
        ZoologicalName inferredSynName;
2786
        synName =syn.getName();
2787
        HibernateProxyHelper.deproxy(syn);
2788

    
2789
        // Determine the idInSource
2790
        String idInSourceSyn = getIdInSource(syn);
2791
        String idInSourceTaxon = getIdInSource(taxon);
2792
        // Determine the sourceReference
2793
        Reference sourceReference = syn.getSec();
2794

    
2795
        //logger.warn(sourceReference.getTitleCache());
2796

    
2797
        synName = syn.getName();
2798
        ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2799
        String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2800
                     /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2801
            synonymsEpithet.add(synSpeciesEpithetName);
2802
        }*/
2803

    
2804
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2805
        //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...
2806

    
2807

    
2808
        inferredSynName.setGenusOrUninomial(genusOfTaxon);
2809
        if (zooParentName.isInfraGeneric()){
2810
            inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2811
        }
2812

    
2813
        if (taxonName.isSpecies()){
2814
            inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2815
        }
2816
        if (taxonName.isInfraSpecific()){
2817
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2818
            inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2819
        }
2820

    
2821

    
2822
        inferredGenus = Synonym.NewInstance(inferredSynName, null);
2823

    
2824
        // Set the sourceReference
2825
        inferredGenus.setSec(sourceReference);
2826

    
2827
        // Add the original source
2828
        if (idInSourceSyn != null && idInSourceTaxon != null) {
2829
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2830
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2831
            inferredGenus.addSource(originalSource);
2832

    
2833
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2834
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2835
            inferredSynName.addSource(originalSource);
2836
            originalSource = null;
2837

    
2838
        }else{
2839
            logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2840
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2841
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2842
            inferredGenus.addSource(originalSource);
2843

    
2844
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2845
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2846
            inferredSynName.addSource(originalSource);
2847
            originalSource = null;
2848
        }
2849

    
2850
        taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2851

    
2852
        return inferredGenus;
2853
    }
2854

    
2855
    private Synonym createInferredEpithets(Taxon taxon,
2856
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2857
            String epithetOfTaxon, String infragenericEpithetOfTaxon,
2858
            String infraspecificEpithetOfTaxon, List<String> taxonNames,
2859
            TaxonNameBase parentName, TaxonBase syn) {
2860

    
2861
        Synonym inferredEpithet;
2862
        TaxonNameBase<?,?> synName;
2863
        ZoologicalName inferredSynName;
2864
        HibernateProxyHelper.deproxy(syn);
2865

    
2866
        // Determine the idInSource
2867
        String idInSourceSyn = getIdInSource(syn);
2868
        String idInSourceTaxon =  getIdInSource(taxon);
2869
        // Determine the sourceReference
2870
        Reference<?> sourceReference = syn.getSec();
2871

    
2872
        if (sourceReference == null){
2873
             logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2874
             sourceReference = taxon.getSec();
2875
        }
2876

    
2877
        synName = syn.getName();
2878
        ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2879
        String synGenusName = zooSynName.getGenusOrUninomial();
2880
        String synInfraGenericEpithet = null;
2881
        String synSpecificEpithet = null;
2882

    
2883
        if (zooSynName.getInfraGenericEpithet() != null){
2884
            synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2885
        }
2886

    
2887
        if (zooSynName.isInfraSpecific()){
2888
            synSpecificEpithet = zooSynName.getSpecificEpithet();
2889
        }
2890

    
2891
                     /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2892
            synonymsGenus.put(synGenusName, idInSource);
2893
        }*/
2894

    
2895
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2896

    
2897
        // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2898
        if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2899
            logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2900
        }
2901
        inferredSynName.setGenusOrUninomial(synGenusName);
2902

    
2903
        if (parentName.isInfraGeneric()){
2904
            inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2905
        }
2906
        if (taxonName.isSpecies()){
2907
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2908
        }else if (taxonName.isInfraSpecific()){
2909
            inferredSynName.setSpecificEpithet(synSpecificEpithet);
2910
            inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2911
        }
2912

    
2913
        inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2914

    
2915
        // Set the sourceReference
2916
        inferredEpithet.setSec(sourceReference);
2917

    
2918
        /* Add the original source
2919
        if (idInSource != null) {
2920
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2921

    
2922
            // Add the citation
2923
            Reference citation = getCitation(syn);
2924
            if (citation != null) {
2925
                originalSource.setCitation(citation);
2926
                inferredEpithet.addSource(originalSource);
2927
            }
2928
        }*/
2929
        String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2930

    
2931

    
2932
        IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2933
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2934

    
2935
        inferredEpithet.addSource(originalSource);
2936

    
2937
        originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2938
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2939

    
2940
        inferredSynName.addSource(originalSource);
2941

    
2942

    
2943

    
2944
        taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2945

    
2946
        return inferredEpithet;
2947
    }
2948

    
2949
    /**
2950
     * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2951
     * Very likely only useful for createInferredSynonyms().
2952
     * @param uuid
2953
     * @param zooHashMap
2954
     * @return
2955
     */
2956
    private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2957
        ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2958
        if (taxonName == null) {
2959
            taxonName = zooHashMap.get(uuid);
2960
        }
2961
        return taxonName;
2962
    }
2963

    
2964
    /**
2965
     * Returns the idInSource for a given Synonym.
2966
     * @param syn
2967
     */
2968
    private String getIdInSource(TaxonBase taxonBase) {
2969
        String idInSource = null;
2970
        Set<IdentifiableSource> sources = taxonBase.getSources();
2971
        if (sources.size() == 1) {
2972
            IdentifiableSource source = sources.iterator().next();
2973
            if (source != null) {
2974
                idInSource  = source.getIdInSource();
2975
            }
2976
        } else if (sources.size() > 1) {
2977
            int count = 1;
2978
            idInSource = "";
2979
            for (IdentifiableSource source : sources) {
2980
                idInSource += source.getIdInSource();
2981
                if (count < sources.size()) {
2982
                    idInSource += "; ";
2983
                }
2984
                count++;
2985
            }
2986
        } else if (sources.size() == 0){
2987
            logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2988
        }
2989

    
2990

    
2991
        return idInSource;
2992
    }
2993

    
2994

    
2995
    /**
2996
     * Returns the citation for a given Synonym.
2997
     * @param syn
2998
     */
2999
    private Reference getCitation(Synonym syn) {
3000
        Reference citation = null;
3001
        Set<IdentifiableSource> sources = syn.getSources();
3002
        if (sources.size() == 1) {
3003
            IdentifiableSource source = sources.iterator().next();
3004
            if (source != null) {
3005
                citation = source.getCitation();
3006
            }
3007
        } else if (sources.size() > 1) {
3008
            logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
3009
        }
3010

    
3011
        return citation;
3012
    }
3013

    
3014
    @Override
3015
    public List<Synonym>  createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
3016
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
3017

    
3018
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
3019
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
3020
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
3021

    
3022
        return inferredSynonyms;
3023
    }
3024

    
3025
    @Override
3026
    public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
3027

    
3028
        // TODO quickly implemented, create according dao !!!!
3029
        Set<TaxonNode> nodes = new HashSet<TaxonNode>();
3030
        Set<Classification> classifications = new HashSet<Classification>();
3031
        List<Classification> list = new ArrayList<Classification>();
3032

    
3033
        if (taxonBase == null) {
3034
            return list;
3035
        }
3036

    
3037
        taxonBase = load(taxonBase.getUuid());
3038

    
3039
        if (taxonBase instanceof Taxon) {
3040
            nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
3041
        } else {
3042
            for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
3043
                nodes.addAll(taxon.getTaxonNodes());
3044
            }
3045
        }
3046
        for (TaxonNode node : nodes) {
3047
            classifications.add(node.getClassification());
3048
        }
3049
        list.addAll(classifications);
3050
        return list;
3051
    }
3052

    
3053
    @Override
3054
    public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
3055
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
3056
        // Create new synonym using concept name
3057
                TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
3058
                Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
3059

    
3060
                // Remove concept relation from taxon
3061
                toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
3062

    
3063

    
3064

    
3065

    
3066
                // Create a new synonym for the taxon
3067
                SynonymRelationship synonymRelationship;
3068
                if (synonymRelationshipType != null
3069
                        && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
3070
                    synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
3071
                } else{
3072
                    synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
3073
                }
3074

    
3075
                this.saveOrUpdate(toTaxon);
3076
                //TODO: configurator and classification
3077
                TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
3078
                config.setDeleteNameIfPossible(false);
3079
                this.deleteTaxon(fromTaxon, config, null);
3080
                return synonymRelationship.getSynonym();
3081

    
3082
    }
3083
    @Override
3084
    public DeleteResult isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
3085
        DeleteResult result = new DeleteResult();
3086
        Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
3087
        if (taxonBase instanceof Taxon){
3088
            TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3089
            result = isDeletableForTaxon(references, taxonConfig);
3090
        }else{
3091
            SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3092
            result = isDeletableForSynonym(references, synonymConfig);
3093
        }
3094
        return result;
3095
    }
3096

    
3097
    private DeleteResult isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3098
        String message;
3099
        DeleteResult result = new DeleteResult();
3100
        for (CdmBase ref: references){
3101
            if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase )){
3102
                message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3103
                result.addException(new ReferencedObjectUndeletableException(message));
3104
                result.addRelatedObject(ref);
3105
                result.setAbort();
3106
            }
3107
        }
3108

    
3109
        return result;
3110
    }
3111
    private DeleteResult isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3112
        String message = null;
3113
        DeleteResult result = new DeleteResult();
3114
        for (CdmBase ref: references){
3115
            if (!(ref instanceof TaxonNameBase)){
3116
                if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){
3117
                    message = "The Taxon can't be deleted as long as it has synonyms.";
3118
                    
3119
                }
3120
                if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3121
                    message = "The Taxon can't be deleted as long as it has factual data.";
3122
                   
3123
                }
3124

    
3125
                if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
3126
                    message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3127
                    
3128
                }
3129
                if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonNode)){
3130
                    if (!config.isDeleteMisappliedNamesAndInvalidDesignations() && (((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())|| ((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR()))){
3131
                        message = "The Taxon can't be deleted as long as it has misapplied names or invalid designations.";
3132
                        
3133
                    } else{
3134
                        message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3135
                        
3136
                    }
3137
                }
3138
                if (ref instanceof PolytomousKeyNode){
3139
                    message = "The Taxon can't be deleted as long as it is referenced by a polytomous key node.";
3140
                    
3141
                }
3142

    
3143
                if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){
3144
                   message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
3145
                   
3146

    
3147
                }
3148

    
3149

    
3150
               /* //PolytomousKeyNode
3151
                if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3152
                    String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3153
                    return message;
3154
                }*/
3155

    
3156
                //TaxonInteraction
3157
                if (ref.isInstanceOf(TaxonInteraction.class)){
3158
                    message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3159
                    
3160
                }
3161

    
3162
              //TaxonInteraction
3163
                if (ref.isInstanceOf(DeterminationEvent.class)){
3164
                    message = "Taxon can't be deleted as it is used in a determination event";
3165
                    
3166
                }
3167

    
3168
            }
3169
            if (message != null){
3170
	            result.addException(new ReferencedObjectUndeletableException(message));
3171
	            result.addRelatedObject(ref);
3172
	            result.setAbort();
3173
            }
3174
        }
3175

    
3176
        return result;
3177
    }
3178

    
3179
    @Override
3180
    public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3181
        IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3182

    
3183
        //preliminary implementation
3184

    
3185
        Set<Taxon> taxa = new HashSet<Taxon>();
3186
        TaxonBase taxonBase = find(taxonUuid);
3187
        if (taxonBase == null){
3188
            return new IncludedTaxaDTO();
3189
        }else if (taxonBase.isInstanceOf(Taxon.class)){
3190
            Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3191
            taxa.add(taxon);
3192
        }else if (taxonBase.isInstanceOf(Synonym.class)){
3193
            //TODO partial synonyms ??
3194
            //TODO synonyms in general
3195
            Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3196
            taxa.addAll(syn.getAcceptedTaxa());
3197
        }else{
3198
            throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3199
        }
3200

    
3201
        Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3202
        int i = 0;
3203
        while((! related.isEmpty()) && i++ < 100){  //to avoid
3204
             related = makeRelatedIncluded(related, result, config);
3205
        }
3206

    
3207
        return result;
3208
    }
3209

    
3210
    /**
3211
     * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3212
     * data structure.
3213
     * @return the set of conceptually related taxa for further use
3214
     */
3215
    /**
3216
     * @param uncheckedTaxa
3217
     * @param existingTaxa
3218
     * @param config
3219
     * @return
3220
     */
3221
    private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3222

    
3223
        //children
3224
        Set<TaxonNode> taxonNodes = new HashSet<TaxonNode>();
3225
        for (Taxon taxon: uncheckedTaxa){
3226
            taxonNodes.addAll(taxon.getTaxonNodes());
3227
        }
3228

    
3229
        Set<Taxon> children = new HashSet<Taxon>();
3230
        if (! config.onlyCongruent){
3231
            for (TaxonNode node: taxonNodes){
3232
                List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, false);
3233
                for (TaxonNode child : childNodes){
3234
                    children.add(child.getTaxon());
3235
                }
3236
            }
3237
            children.remove(null);  // just to be on the save side
3238
        }
3239

    
3240
        Iterator<Taxon> it = children.iterator();
3241
        while(it.hasNext()){
3242
            UUID uuid = it.next().getUuid();
3243
            if (existingTaxa.contains(uuid)){
3244
                it.remove();
3245
            }else{
3246
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3247
            }
3248
        }
3249

    
3250
        //concept relations
3251
        Set<Taxon> uncheckedAndChildren = new HashSet<Taxon>(uncheckedTaxa);
3252
        uncheckedAndChildren.addAll(children);
3253

    
3254
        Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3255

    
3256

    
3257
        Set<Taxon> result = new HashSet<Taxon>(relatedTaxa);
3258
        return result;
3259
    }
3260

    
3261
    /**
3262
     * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3263
     * @return the set of these computed taxa
3264
     */
3265
    private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3266
        Set<Taxon> result = new HashSet<Taxon>();
3267

    
3268
        for (Taxon taxon : unchecked){
3269
            Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3270
            Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3271

    
3272
            for (TaxonRelationship fromRel : fromRelations){
3273
                if (config.includeDoubtful == false && fromRel.isDoubtful()){
3274
                    continue;
3275
                }
3276
                if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3277
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
3278
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3279
                        ){
3280
                    result.add(fromRel.getToTaxon());
3281
                }
3282
            }
3283

    
3284
            for (TaxonRelationship toRel : toRelations){
3285
                if (config.includeDoubtful == false && toRel.isDoubtful()){
3286
                    continue;
3287
                }
3288
                if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
3289
                    result.add(toRel.getFromTaxon());
3290
                }
3291
            }
3292
        }
3293

    
3294
        Iterator<Taxon> it = result.iterator();
3295
        while(it.hasNext()){
3296
            UUID uuid = it.next().getUuid();
3297
            if (existingTaxa.contains(uuid)){
3298
                it.remove();
3299
            }else{
3300
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3301
            }
3302
        }
3303
        return result;
3304
    }
3305
    @Override
3306
    public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3307
        List<TaxonBase> taxonList = dao.getTaxaByName(true, false, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, 0, config.getPropertyPath());
3308
        return taxonList;
3309
    }
3310

    
3311

    
3312
}
(79-79/84)