Project

General

Profile

Download (89.9 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.HashMap;
16
import java.util.HashSet;
17
import java.util.List;
18
import java.util.Set;
19
import java.util.UUID;
20

    
21
import org.apache.log4j.Logger;
22
import org.apache.lucene.index.CorruptIndexException;
23
import org.apache.lucene.queryParser.ParseException;
24
import org.apache.lucene.search.Query;
25
import org.apache.lucene.search.SortField;
26
import org.apache.lucene.search.TopDocs;
27
import org.hibernate.criterion.Criterion;
28
import org.springframework.beans.factory.annotation.Autowired;
29
import org.springframework.stereotype.Service;
30
import org.springframework.transaction.annotation.Propagation;
31
import org.springframework.transaction.annotation.Transactional;
32

    
33
import eu.etaxonomy.cdm.api.service.config.IFindTaxaAndNamesConfigurator;
34
import eu.etaxonomy.cdm.api.service.config.MatchingTaxonConfigurator;
35
import eu.etaxonomy.cdm.api.service.config.NameDeletionConfigurator;
36
import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
37
import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
38
import eu.etaxonomy.cdm.api.service.exception.HomotypicalGroupChangeException;
39
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
40
import eu.etaxonomy.cdm.api.service.pager.Pager;
41
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
42
import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
43
import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
44
import eu.etaxonomy.cdm.api.service.search.SearchResult;
45
import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
46
import eu.etaxonomy.cdm.api.service.search.SearchResultHighligther;
47
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
48
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
49
import eu.etaxonomy.cdm.hibernate.search.DefinedTermBaseClassBridge;
50
import eu.etaxonomy.cdm.hibernate.search.PaddedIntegerBridge;
51
import eu.etaxonomy.cdm.model.common.CdmBase;
52
import eu.etaxonomy.cdm.model.common.DefinedTermBase;
53
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
54
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
55
import eu.etaxonomy.cdm.model.common.Language;
56
import eu.etaxonomy.cdm.model.common.OrderedTermVocabulary;
57
import eu.etaxonomy.cdm.model.common.RelationshipBase;
58
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
59
import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
60
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
61
import eu.etaxonomy.cdm.model.description.Feature;
62
import eu.etaxonomy.cdm.model.description.IIdentificationKey;
63
import eu.etaxonomy.cdm.model.description.PolytomousKeyNode;
64
import eu.etaxonomy.cdm.model.description.TaxonDescription;
65
import eu.etaxonomy.cdm.model.description.TaxonInteraction;
66
import eu.etaxonomy.cdm.model.media.Media;
67
import eu.etaxonomy.cdm.model.media.MediaRepresentation;
68
import eu.etaxonomy.cdm.model.media.MediaUtils;
69
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
70
import eu.etaxonomy.cdm.model.name.NonViralName;
71
import eu.etaxonomy.cdm.model.name.Rank;
72
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
73
import eu.etaxonomy.cdm.model.name.ZoologicalName;
74
import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;
75
import eu.etaxonomy.cdm.model.reference.Reference;
76
import eu.etaxonomy.cdm.model.taxon.Classification;
77
import eu.etaxonomy.cdm.model.taxon.Synonym;
78
import eu.etaxonomy.cdm.model.taxon.SynonymRelationship;
79
import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;
80
import eu.etaxonomy.cdm.model.taxon.Taxon;
81
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
82
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
83
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
84
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
85
import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
86
import eu.etaxonomy.cdm.persistence.dao.common.IOrderedTermVocabularyDao;
87
import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
88
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
89
import eu.etaxonomy.cdm.persistence.fetch.CdmFetch;
90
import eu.etaxonomy.cdm.persistence.query.MatchMode;
91
import eu.etaxonomy.cdm.persistence.query.OrderHint;
92
import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
93
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
94

    
95

    
96
/**
97
 * @author a.kohlbecker
98
 * @date 10.09.2010
99
 *
100
 */
101
@Service
102
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
103
public class TaxonServiceImpl extends IdentifiableServiceBase<TaxonBase,ITaxonDao> implements ITaxonService{
104
    private static final Logger logger = Logger.getLogger(TaxonServiceImpl.class);
105

    
106
    public static final String POTENTIAL_COMBINATION_NAMESPACE = "Potential combination";
107

    
108
    public static final String INFERRED_EPITHET_NAMESPACE = "Inferred epithet";
109

    
110
    public static final String INFERRED_GENUS_NAMESPACE = "Inferred genus";
111

    
112

    
113
    @Autowired
114
    private ITaxonNameDao nameDao;
115

    
116
    @Autowired
117
    private INameService nameService;
118

    
119
    @Autowired
120
    private ICdmGenericDao genericDao;
121

    
122
    @Autowired
123
    private IDescriptionService descriptionService;
124

    
125
    @Autowired
126
    private IOrderedTermVocabularyDao orderedVocabularyDao;
127

    
128
    /**
129
     * Constructor
130
     */
131
    public TaxonServiceImpl(){
132
        if (logger.isDebugEnabled()) { logger.debug("Load TaxonService Bean"); }
133
    }
134

    
135
    /**
136
     * FIXME Candidate for harmonization
137
     * rename searchByName ?
138
     */
139
    public List<TaxonBase> searchTaxaByName(String name, Reference sec) {
140
        return dao.getTaxaByName(name, sec);
141
    }
142

    
143
    /**
144
     * FIXME Candidate for harmonization
145
     * list(Synonym.class, ...)
146
     *  (non-Javadoc)
147
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllSynonyms(int, int)
148
     */
149
    public List<Synonym> getAllSynonyms(int limit, int start) {
150
        return dao.getAllSynonyms(limit, start);
151
    }
152

    
153
    /**
154
     * FIXME Candidate for harmonization
155
     * list(Taxon.class, ...)
156
     *  (non-Javadoc)
157
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllTaxa(int, int)
158
     */
159
    public List<Taxon> getAllTaxa(int limit, int start) {
160
        return dao.getAllTaxa(limit, start);
161
    }
162

    
163
    /**
164
     * FIXME Candidate for harmonization
165
     * merge with getRootTaxa(Reference sec, ..., ...)
166
     *  (non-Javadoc)
167
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getRootTaxa(eu.etaxonomy.cdm.model.reference.Reference, boolean)
168
     */
169
    public List<Taxon> getRootTaxa(Reference sec, CdmFetch cdmFetch, boolean onlyWithChildren) {
170
        if (cdmFetch == null){
171
            cdmFetch = CdmFetch.NO_FETCH();
172
        }
173
        return dao.getRootTaxa(sec, cdmFetch, onlyWithChildren, false);
174
    }
175

    
176

    
177
    /* (non-Javadoc)
178
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getRootTaxa(eu.etaxonomy.cdm.model.name.Rank, eu.etaxonomy.cdm.model.reference.Reference, boolean, boolean)
179
     */
180
    public List<Taxon> getRootTaxa(Rank rank, Reference sec, boolean onlyWithChildren,boolean withMisapplications, List<String> propertyPaths) {
181
        return dao.getRootTaxa(rank, sec, null, onlyWithChildren, withMisapplications, propertyPaths);
182
    }
183

    
184
    /* (non-Javadoc)
185
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllRelationships(int, int)
186
     */
187
    public List<RelationshipBase> getAllRelationships(int limit, int start){
188
        return dao.getAllRelationships(limit, start);
189
    }
190

    
191
    /**
192
     * FIXME Candidate for harmonization
193
     * is this the same as termService.getVocabulary(VocabularyEnum.TaxonRelationshipType) ?
194
     */
195
    @Deprecated
196
    public OrderedTermVocabulary<TaxonRelationshipType> getTaxonRelationshipTypeVocabulary() {
197

    
198
        String taxonRelTypeVocabularyId = "15db0cf7-7afc-4a86-a7d4-221c73b0c9ac";
199
        UUID uuid = UUID.fromString(taxonRelTypeVocabularyId);
200
        OrderedTermVocabulary<TaxonRelationshipType> taxonRelTypeVocabulary =
201
            (OrderedTermVocabulary)orderedVocabularyDao.findByUuid(uuid);
202
        return taxonRelTypeVocabulary;
203
    }
204

    
205

    
206

    
207
    /*
208
     * (non-Javadoc)
209
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#swapSynonymWithAcceptedTaxon(eu.etaxonomy.cdm.model.taxon.Synonym)
210
     */
211
    @Transactional(readOnly = false)
212
    public void swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon){
213

    
214
        TaxonNameBase<?,?> synonymName = synonym.getName();
215
        synonymName.removeTaxonBase(synonym);
216
        TaxonNameBase<?,?> taxonName = acceptedTaxon.getName();
217
        taxonName.removeTaxonBase(acceptedTaxon);
218

    
219
        synonym.setName(taxonName);
220
        acceptedTaxon.setName(synonymName);
221

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

    
227

    
228
    /* (non-Javadoc)
229
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#changeSynonymToAcceptedTaxon(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
230
     */
231
    //TODO correct delete handling still needs to be implemented / checked
232
    @Override
233
    @Transactional(readOnly = false)
234
    public Taxon changeSynonymToAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean deleteSynonym, boolean copyCitationInfo, Reference citation, String microCitation) throws HomotypicalGroupChangeException{
235

    
236
        TaxonNameBase<?,?> acceptedName = acceptedTaxon.getName();
237
        TaxonNameBase<?,?> synonymName = synonym.getName();
238
        HomotypicalGroup synonymHomotypicGroup = synonymName.getHomotypicalGroup();
239

    
240
        //check synonym is not homotypic
241
        if (acceptedName.getHomotypicalGroup().equals(synonymHomotypicGroup)){
242
            String message = "The accepted taxon and the synonym are part of the same homotypical group and therefore can not be both accepted.";
243
            throw new HomotypicalGroupChangeException(message);
244
        }
245

    
246
        Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, acceptedTaxon.getSec());
247

    
248
        SynonymRelationshipType relTypeForGroup = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
249
        List<Synonym> heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup);
250

    
251
        for (Synonym heteroSynonym : heteroSynonyms){
252
            if (synonym.equals(heteroSynonym)){
253
                acceptedTaxon.removeSynonym(heteroSynonym, false);
254
            }else{
255
                //move synonyms in same homotypic group to new accepted taxon
256
                heteroSynonym.replaceAcceptedTaxon(newAcceptedTaxon, relTypeForGroup, copyCitationInfo, citation, microCitation);
257
            }
258
        }
259

    
260
        //synonym.getName().removeTaxonBase(synonym);
261
        //TODO correct delete handling still needs to be implemented / checked
262
        if (deleteSynonym){
263
//			deleteSynonym(synonym, taxon, false);
264
            try {
265
                this.dao.flush();
266
                this.delete(synonym);
267

    
268
            } catch (Exception e) {
269
                logger.info("Can't delete old synonym from database");
270
            }
271
        }
272

    
273
        return newAcceptedTaxon;
274
    }
275

    
276

    
277
    public Taxon changeSynonymToRelatedTaxon(Synonym synonym, Taxon toTaxon, TaxonRelationshipType taxonRelationshipType, Reference citation, String microcitation){
278

    
279
        // Get name from synonym
280
        TaxonNameBase<?, ?> synonymName = synonym.getName();
281

    
282
        // remove synonym from taxon
283
        toTaxon.removeSynonym(synonym);
284

    
285
        // Create a taxon with synonym name
286
        Taxon fromTaxon = Taxon.NewInstance(synonymName, null);
287

    
288
        // Add taxon relation
289
        fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
290

    
291
        // since we are swapping names, we have to detach the name from the synonym completely.
292
        // Otherwise the synonym will still be in the list of typified names.
293
        synonym.getName().removeTaxonBase(synonym);
294

    
295
        return fromTaxon;
296
    }
297

    
298

    
299
    /* (non-Javadoc)
300
     * @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)
301
     */
302
    @Transactional(readOnly = false)
303
    @Override
304
    public void changeHomotypicalGroupOfSynonym(Synonym synonym, HomotypicalGroup newHomotypicalGroup, Taxon targetTaxon,
305
                        boolean removeFromOtherTaxa, boolean setBasionymRelationIfApplicable){
306
        // Get synonym name
307
        TaxonNameBase synonymName = synonym.getName();
308
        HomotypicalGroup oldHomotypicalGroup = synonymName.getHomotypicalGroup();
309

    
310

    
311
        // Switch groups
312
        oldHomotypicalGroup.removeTypifiedName(synonymName);
313
        newHomotypicalGroup.addTypifiedName(synonymName);
314

    
315
        //remove existing basionym relationships
316
        synonymName.removeBasionyms();
317

    
318
        //add basionym relationship
319
        if (setBasionymRelationIfApplicable){
320
            Set<TaxonNameBase> basionyms = newHomotypicalGroup.getBasionyms();
321
            for (TaxonNameBase basionym : basionyms){
322
                synonymName.addBasionym(basionym);
323
            }
324
        }
325

    
326
        //set synonym relationship correctly
327
//			SynonymRelationship relToTaxon = null;
328
        boolean relToTargetTaxonExists = false;
329
        Set<SynonymRelationship> existingRelations = synonym.getSynonymRelations();
330
        for (SynonymRelationship rel : existingRelations){
331
            Taxon acceptedTaxon = rel.getAcceptedTaxon();
332
            boolean isTargetTaxon = acceptedTaxon != null && acceptedTaxon.equals(targetTaxon);
333
            HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
334
            boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
335
            SynonymRelationshipType newRelationType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
336
            rel.setType(newRelationType);
337
            //TODO handle citation and microCitation
338

    
339
            if (isTargetTaxon){
340
                relToTargetTaxonExists = true;
341
            }else{
342
                if (removeFromOtherTaxa){
343
                    acceptedTaxon.removeSynonym(synonym, false);
344
                }else{
345
                    //do nothing
346
                }
347
            }
348
        }
349
        if (targetTaxon != null &&  ! relToTargetTaxonExists ){
350
            Taxon acceptedTaxon = targetTaxon;
351
            HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
352
            boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
353
            SynonymRelationshipType relType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
354
            //TODO handle citation and microCitation
355
            Reference citation = null;
356
            String microCitation = null;
357
            acceptedTaxon.addSynonym(synonym, relType, citation, microCitation);
358
        }
359

    
360
    }
361

    
362

    
363
    /* (non-Javadoc)
364
     * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache(java.lang.Integer, eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy)
365
     */
366
    @Override
367
    @Transactional(readOnly = false)
368
    public void updateTitleCache(Class<? extends TaxonBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonBase> cacheStrategy, IProgressMonitor monitor) {
369
        if (clazz == null){
370
            clazz = TaxonBase.class;
371
        }
372
        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
373
    }
374

    
375
    @Autowired
376
    protected void setDao(ITaxonDao dao) {
377
        this.dao = dao;
378
    }
379

    
380
    /* (non-Javadoc)
381
     * @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)
382
     */
383
    public Pager<TaxonBase> findTaxaByName(Class<? extends TaxonBase> clazz, String uninomial,	String infragenericEpithet, String specificEpithet,	String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
384
        Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
385

    
386
        List<TaxonBase> results = new ArrayList<TaxonBase>();
387
        if(numberOfResults > 0) { // no point checking again
388
            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
389
        }
390

    
391
        return new DefaultPagerImpl<TaxonBase>(pageNumber, numberOfResults, pageSize, results);
392
    }
393

    
394
    /* (non-Javadoc)
395
     * @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)
396
     */
397
    public List<TaxonBase> listTaxaByName(Class<? extends TaxonBase> clazz, String uninomial,	String infragenericEpithet, String specificEpithet,	String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
398
        Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
399

    
400
        List<TaxonBase> results = new ArrayList<TaxonBase>();
401
        if(numberOfResults > 0) { // no point checking again
402
            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
403
        }
404

    
405
        return results;
406
    }
407

    
408
    /* (non-Javadoc)
409
     * @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)
410
     */
411
    public List<TaxonRelationship> listToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
412
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
413

    
414
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
415
        if(numberOfResults > 0) { // no point checking again
416
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
417
        }
418
        return results;
419
    }
420

    
421
    /* (non-Javadoc)
422
     * @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)
423
     */
424
    public Pager<TaxonRelationship> pageToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
425
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
426

    
427
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
428
        if(numberOfResults > 0) { // no point checking again
429
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
430
        }
431
        return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
432
    }
433

    
434
    /* (non-Javadoc)
435
     * @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)
436
     */
437
    public List<TaxonRelationship> listFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
438
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
439

    
440
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
441
        if(numberOfResults > 0) { // no point checking again
442
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
443
        }
444
        return results;
445
    }
446

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

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

    
460
    /* (non-Javadoc)
461
     * @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)
462
     */
463
    public Pager<SynonymRelationship> getSynonyms(Taxon taxon,	SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
464
        Integer numberOfResults = dao.countSynonyms(taxon, type);
465

    
466
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
467
        if(numberOfResults > 0) { // no point checking again
468
            results = dao.getSynonyms(taxon, type, pageSize, pageNumber, orderHints, propertyPaths);
469
        }
470

    
471
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
472
    }
473

    
474
    /* (non-Javadoc)
475
     * @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)
476
     */
477
    public Pager<SynonymRelationship> getSynonyms(Synonym synonym,	SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
478
        Integer numberOfResults = dao.countSynonyms(synonym, type);
479

    
480
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
481
        if(numberOfResults > 0) { // no point checking again
482
            results = dao.getSynonyms(synonym, type, pageSize, pageNumber, orderHints, propertyPaths);
483
        }
484

    
485
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
486
    }
487

    
488
    /* (non-Javadoc)
489
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHomotypicSynonymsByHomotypicGroup(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
490
     */
491
    public List<Synonym> getHomotypicSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
492
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
493
        return t.getHomotypicSynonymsByHomotypicGroup();
494
    }
495

    
496
    /* (non-Javadoc)
497
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHeterotypicSynonymyGroups(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List)
498
     */
499
    public List<List<Synonym>> getHeterotypicSynonymyGroups(Taxon taxon, List<String> propertyPaths){
500
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
501
        List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
502
        List<List<Synonym>> heterotypicSynonymyGroups = new ArrayList<List<Synonym>>(homotypicalGroups.size());
503
        for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
504
            heterotypicSynonymyGroups.add(t.getSynonymsInGroup(homotypicalGroup));
505
        }
506
        return heterotypicSynonymyGroups;
507
    }
508

    
509
    public List<UuidAndTitleCache<TaxonBase>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator){
510

    
511
        List<UuidAndTitleCache<TaxonBase>> result = new ArrayList<UuidAndTitleCache<TaxonBase>>();
512
//        Class<? extends TaxonBase> clazz = null;
513
//        if ((configurator.isDoTaxa() && configurator.isDoSynonyms())) {
514
//            clazz = TaxonBase.class;
515
//            //propertyPath.addAll(configurator.getTaxonPropertyPath());
516
//            //propertyPath.addAll(configurator.getSynonymPropertyPath());
517
//        } else if(configurator.isDoTaxa()) {
518
//            clazz = Taxon.class;
519
//            //propertyPath = configurator.getTaxonPropertyPath();
520
//        } else if (configurator.isDoSynonyms()) {
521
//            clazz = Synonym.class;
522
//            //propertyPath = configurator.getSynonymPropertyPath();
523
//        }
524

    
525

    
526
        result = dao.getTaxaByNameForEditor(configurator.isDoTaxa(), configurator.isDoSynonyms(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
527
        return result;
528
    }
529

    
530
    /* (non-Javadoc)
531
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaAndNames(eu.etaxonomy.cdm.api.service.config.ITaxonServiceConfigurator)
532
     */
533
    public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
534

    
535
        List<IdentifiableEntity> results = new ArrayList<IdentifiableEntity>();
536
        int numberOfResults = 0; // overall number of results (as opposed to number of results per page)
537
        List<TaxonBase> taxa = null;
538

    
539
        // Taxa and synonyms
540
        long numberTaxaResults = 0L;
541

    
542

    
543
        List<String> propertyPath = new ArrayList<String>();
544
        if(configurator.getTaxonPropertyPath() != null){
545
            propertyPath.addAll(configurator.getTaxonPropertyPath());
546
        }
547

    
548

    
549
       if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa()){
550
            if(configurator.getPageSize() != null){ // no point counting if we need all anyway
551
                numberTaxaResults =
552
                    dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
553
                        configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(),
554
                        configurator.getNamedAreas());
555
            }
556

    
557
            if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
558
                taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
559
                    configurator.isDoMisappliedNames(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(),
560
                    configurator.getMatchMode(), configurator.getNamedAreas(),
561
                    configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
562
            }
563
       }
564

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

    
567
        if(taxa != null){
568
            results.addAll(taxa);
569
        }
570

    
571
        numberOfResults += numberTaxaResults;
572

    
573
        // Names without taxa
574
        if (configurator.isDoNamesWithoutTaxa()) {
575
            int numberNameResults = 0;
576

    
577
            List<? extends TaxonNameBase<?,?>> names =
578
                nameDao.findByName(configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
579
                        configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
580
            if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
581
            if (names.size() > 0) {
582
                for (TaxonNameBase<?,?> taxonName : names) {
583
                    if (taxonName.getTaxonBases().size() == 0) {
584
                        results.add(taxonName);
585
                        numberNameResults++;
586
                    }
587
                }
588
                if (logger.isDebugEnabled()) { logger.debug(numberNameResults + " matching name(s) without taxa found"); }
589
                numberOfResults += numberNameResults;
590
            }
591
        }
592

    
593
        // Taxa from common names
594

    
595
        if (configurator.isDoTaxaByCommonNames()) {
596
            taxa = new ArrayList<TaxonBase>();
597
            numberTaxaResults = 0;
598
            if(configurator.getPageSize() != null){// no point counting if we need all anyway
599
                numberTaxaResults = dao.countTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
600
            }
601
            if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){
602
                List<Object[]> commonNameResults = dao.getTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas(), configurator.getPageSize(), configurator.getPageNumber(), configurator.getTaxonPropertyPath());
603
                for( Object[] entry : commonNameResults ) {
604
                    taxa.add((TaxonBase) entry[0]);
605
                }
606
            }
607
            if(taxa != null){
608
                results.addAll(taxa);
609
            }
610
            numberOfResults += numberTaxaResults;
611

    
612
        }
613

    
614
       return new DefaultPagerImpl<IdentifiableEntity>
615
            (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
616
    }
617

    
618
    public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(){
619
        return dao.getUuidAndTitleCache();
620
    }
621

    
622
    /* (non-Javadoc)
623
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllMedia(eu.etaxonomy.cdm.model.taxon.Taxon, int, int, int, java.lang.String[])
624
     */
625
    public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
626
        List<MediaRepresentation> medRep = new ArrayList<MediaRepresentation>();
627
        taxon = (Taxon)dao.load(taxon.getUuid());
628
        Set<TaxonDescription> descriptions = taxon.getDescriptions();
629
        for (TaxonDescription taxDesc: descriptions){
630
            Set<DescriptionElementBase> elements = taxDesc.getElements();
631
            for (DescriptionElementBase descElem: elements){
632
                for(Media media : descElem.getMedia()){
633

    
634
                    //find the best matching representation
635
                    medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
636

    
637
                }
638
            }
639
        }
640
        return medRep;
641
    }
642

    
643
    /* (non-Javadoc)
644
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#listTaxonDescriptionMedia(eu.etaxonomy.cdm.model.taxon.Taxon, boolean)
645
     */
646
    public List<Media> listTaxonDescriptionMedia(Taxon taxon, boolean limitToGalleries, List<String> propertyPath){
647

    
648
        Pager<TaxonDescription> p =
649
                    descriptionService.getTaxonDescriptions(taxon, null, null, null, null, propertyPath);
650

    
651
        // pars the media and quality parameters
652

    
653

    
654
        // collect all media of the given taxon
655
        List<Media> taxonMedia = new ArrayList<Media>();
656
        List<Media> taxonGalleryMedia = new ArrayList<Media>();
657
        for(TaxonDescription desc : p.getRecords()){
658

    
659
            if(desc.isImageGallery()){
660
                for(DescriptionElementBase element : desc.getElements()){
661
                    for(Media media : element.getMedia()){
662
                        taxonGalleryMedia.add(media);
663
                    }
664
                }
665
            } else if(!limitToGalleries){
666
                for(DescriptionElementBase element : desc.getElements()){
667
                    for(Media media : element.getMedia()){
668
                        taxonMedia.add(media);
669
                    }
670
                }
671
            }
672

    
673
        }
674

    
675
        taxonGalleryMedia.addAll(taxonMedia);
676
        return taxonGalleryMedia;
677
    }
678

    
679
    /* (non-Javadoc)
680
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaByID(java.util.Set)
681
     */
682
    public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
683
        return this.dao.findById(listOfIDs);
684
    }
685

    
686
    /* (non-Javadoc)
687
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxonByUuid(UUID uuid, List<String> propertyPaths)
688
     */
689
    public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
690
        return this.dao.findByUuid(uuid, null ,propertyPaths);
691
    }
692

    
693
    /* (non-Javadoc)
694
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#countAllRelationships()
695
     */
696
    public int countAllRelationships() {
697
        return this.dao.countAllRelationships();
698
    }
699

    
700

    
701

    
702

    
703
    /* (non-Javadoc)
704
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNames(java.util.List)
705
     */
706
    public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
707
        return this.dao.findIdenticalTaxonNames(propertyPath);
708
    }
709

    
710

    
711
    /* (non-Javadoc)
712
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteTaxon(eu.etaxonomy.cdm.model.taxon.Taxon, eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator)
713
     */
714
    @Override
715
    public void deleteTaxon(Taxon taxon, TaxonDeletionConfigurator config) throws ReferencedObjectUndeletableException {
716
        if (config == null){
717
            config = new TaxonDeletionConfigurator();
718
        }
719

    
720
            //    	TaxonNode
721
            if (! config.isDeleteTaxonNodes()){
722
                if (taxon.getTaxonNodes().size() > 0){
723
                    String message = "Taxon can't be deleted as it is used in a classification node. Remove taxon from all classifications prior to deletion.";
724
                    throw new ReferencedObjectUndeletableException(message);
725
                }
726
            }
727

    
728

    
729
            //    	SynonymRelationShip
730
            if (config.isDeleteSynonymRelations()){
731
                boolean removeSynonymNameFromHomotypicalGroup = false;
732
                for (SynonymRelationship synRel : taxon.getSynonymRelations()){
733
                    Synonym synonym = synRel.getSynonym();
734
                    taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
735
                    if (config.isDeleteSynonymsIfPossible()){
736
                        //TODO which value
737
                        boolean newHomotypicGroupIfNeeded = true;
738
                        deleteSynonym(synonym, taxon, config.isDeleteNameIfPossible(), newHomotypicGroupIfNeeded);
739
                    }else{
740
                        deleteSynonymRelationships(synonym, taxon);
741
                    }
742
                }
743
            }
744

    
745
            //    	TaxonRelationship
746
            if (! config.isDeleteTaxonRelationships()){
747
                if (taxon.getTaxonRelations().size() > 0){
748
                    String message = "Taxon can't be deleted as it is related to another taxon. Remove taxon from all relations to other taxa prior to deletion.";
749
                    throw new ReferencedObjectUndeletableException(message);
750
                }
751
            }
752

    
753

    
754
            //    	TaxonDescription
755
                    Set<TaxonDescription> descriptions = taxon.getDescriptions();
756

    
757
                    for (TaxonDescription desc: descriptions){
758
                        if (config.isDeleteDescriptions()){
759
                            //TODO use description delete configurator ?
760
                            //FIXME check if description is ALWAYS deletable
761
                            descriptionService.delete(desc);
762
                        }else{
763
                            if (desc.getDescribedSpecimenOrObservations().size()>0){
764
                                String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
765
                                        " which also describes specimens or abservations";
766
                                    throw new ReferencedObjectUndeletableException(message);
767
                                }
768
                            }
769
                        }
770

    
771

    
772
                //check references with only reverse mapping
773
            Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
774
            for (CdmBase referencingObject : referencingObjects){
775
                //IIdentificationKeys (Media, Polytomous, MultiAccess)
776
                if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
777
                    String message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
778
                    message = String.format(message, CdmBase.deproxy(referencingObject, DerivedUnitBase.class).getTitleCache());
779
                    throw new ReferencedObjectUndeletableException(message);
780
                }
781

    
782

    
783
                //PolytomousKeyNode
784
                if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
785
                    String message = "Taxon can't be deleted as it is used in polytomous key node";
786
                    throw new ReferencedObjectUndeletableException(message);
787
                }
788

    
789
                //TaxonInteraction
790
                if (referencingObject.isInstanceOf(TaxonInteraction.class)){
791
                    String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
792
                    throw new ReferencedObjectUndeletableException(message);
793
                }
794
            }
795

    
796

    
797
            //TaxonNameBase
798
            if (config.isDeleteNameIfPossible()){
799
                try {
800
                    nameService.delete(taxon.getName(), config.getNameDeletionConfig());
801
                } catch (ReferencedObjectUndeletableException e) {
802
                    //do nothing
803
                    if (logger.isDebugEnabled()){logger.debug("Name could not be deleted");}
804
                }
805
            }
806

    
807
    }
808

    
809
    /* (non-Javadoc)
810
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean)
811
     */
812
    @Transactional(readOnly = false)
813
    @Override
814
    public void deleteSynonym(Synonym synonym, Taxon taxon, boolean removeNameIfPossible,boolean newHomotypicGroupIfNeeded) {
815
        if (synonym == null){
816
            return;
817
        }
818
        synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
819

    
820
        //remove synonymRelationship
821
        Set<Taxon> taxonSet = new HashSet<Taxon>();
822
        if (taxon != null){
823
            taxonSet.add(taxon);
824
        }else{
825
            taxonSet.addAll(synonym.getAcceptedTaxa());
826
        }
827
        for (Taxon relatedTaxon : taxonSet){
828
//			dao.deleteSynonymRelationships(synonym, relatedTaxon);
829
            relatedTaxon.removeSynonym(synonym, newHomotypicGroupIfNeeded);
830
        }
831
        this.saveOrUpdate(synonym);
832

    
833
        //TODO remove name from homotypical group?
834

    
835
        //remove synonym (if necessary)
836
        if (synonym.getSynonymRelations().isEmpty()){
837
            TaxonNameBase<?,?> name = synonym.getName();
838
            synonym.setName(null);
839
            dao.delete(synonym);
840

    
841
            //remove name if possible (and required)
842
            if (name != null && removeNameIfPossible){
843
                try{
844
                    nameService.delete(name, new NameDeletionConfigurator());
845
                }catch (DataChangeNoRollbackException ex){
846
                    if (logger.isDebugEnabled())logger.debug("Name wasn't deleted as it is referenced");
847
                }
848
            }
849
        }
850
    }
851

    
852

    
853
    /* (non-Javadoc)
854
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findIdenticalTaxonNameIds(java.util.List)
855
     */
856
    public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
857

    
858
        return this.dao.findIdenticalNamesNew(propertyPath);
859
    }
860

    
861
    /* (non-Javadoc)
862
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getPhylumName(eu.etaxonomy.cdm.model.name.TaxonNameBase)
863
     */
864
    public String getPhylumName(TaxonNameBase name){
865
        return this.dao.getPhylumName(name);
866
    }
867

    
868
    /* (non-Javadoc)
869
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon)
870
     */
871
    public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
872
        return dao.deleteSynonymRelationships(syn, taxon);
873
    }
874

    
875
/* (non-Javadoc)
876
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonymRelationships(eu.etaxonomy.cdm.model.taxon.Synonym)
877
     */
878
    public long deleteSynonymRelationships(Synonym syn) {
879
        return dao.deleteSynonymRelationships(syn, null);
880
    }
881

    
882

    
883
    /* (non-Javadoc)
884
     * @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)
885
     */
886
    public List<SynonymRelationship> listSynonymRelationships(
887
            TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
888
            List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
889
        Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
890

    
891
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
892
        if(numberOfResults > 0) { // no point checking again
893
            results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
894
        }
895
        return results;
896
    }
897

    
898
    /* (non-Javadoc)
899
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingTaxon(java.lang.String)
900
     */
901
    @Override
902
    public Taxon findBestMatchingTaxon(String taxonName) {
903
        MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
904
        config.setTaxonNameTitle(taxonName);
905
        return findBestMatchingTaxon(config);
906
    }
907

    
908

    
909

    
910
    @Override
911
    public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
912

    
913
        Taxon bestCandidate = null;
914
        try{
915
            // 1. search for acceptet taxa
916
            List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
917
            boolean bestCandidateMatchesSecUuid = false;
918
            boolean bestCandidateIsInClassification = false;
919
            int countEqualCandidates = 0;
920
            for(TaxonBase taxonBaseCandidate : taxonList){
921
                if(taxonBaseCandidate instanceof Taxon){
922
                    Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
923
                    boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
924
                    if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
925
                        continue;
926
                    }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
927
                        bestCandidate = newCanditate;
928
                        countEqualCandidates = 1;
929
                        bestCandidateMatchesSecUuid = true;
930
                        continue;
931
                    }
932

    
933
                    boolean newCandidateInClassification = isInClassification(newCanditate, config);
934
                    if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
935
                        continue;
936
                    }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
937
                        bestCandidate = newCanditate;
938
                        countEqualCandidates = 1;
939
                        bestCandidateIsInClassification = true;
940
                        continue;
941
                    }
942
                    if (bestCandidate == null){
943
                        bestCandidate = newCanditate;
944
                        countEqualCandidates = 1;
945
                        continue;
946
                    }
947

    
948
                }else{  //not Taxon.class
949
                    continue;
950
                }
951
                countEqualCandidates++;
952

    
953
            }
954
            if (bestCandidate != null){
955
                if(countEqualCandidates > 1){
956
                    logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
957
                    return bestCandidate;
958
                } else {
959
                    logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
960
                    return bestCandidate;
961
                }
962
            }
963

    
964

    
965
            // 2. search for synonyms
966
            if (config.isIncludeSynonyms()){
967
                List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
968
                for(TaxonBase taxonBase : synonymList){
969
                    if(taxonBase instanceof Synonym){
970
                        Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
971
                        Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
972
                        if(!acceptetdCandidates.isEmpty()){
973
                            bestCandidate = acceptetdCandidates.iterator().next();
974
                            if(acceptetdCandidates.size() == 1){
975
                                logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
976
                                return bestCandidate;
977
                            } else {
978
                                logger.info("using accepted Taxon " +  bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
979
                                return bestCandidate;
980
                            }
981
                            //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
982
                        }
983
                    }
984
                }
985
            }
986

    
987
        } catch (Exception e){
988
            logger.error(e);
989
        }
990

    
991
        return bestCandidate;
992
    }
993

    
994
    private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
995
        UUID configClassificationUuid = config.getClassificationUuid();
996
        if (configClassificationUuid == null){
997
            return false;
998
        }
999
        for (TaxonNode node : taxon.getTaxonNodes()){
1000
            UUID classUuid = node.getClassification().getUuid();
1001
            if (configClassificationUuid.equals(classUuid)){
1002
                return true;
1003
            }
1004
        }
1005
        return false;
1006
    }
1007

    
1008
    private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1009
        UUID configSecUuid = config.getSecUuid();
1010
        if (configSecUuid == null){
1011
            return false;
1012
        }
1013
        UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1014
        return configSecUuid.equals(taxonSecUuid);
1015
    }
1016

    
1017
    /* (non-Javadoc)
1018
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#findBestMatchingSynonym(java.lang.String)
1019
     */
1020
    @Override
1021
    public Synonym findBestMatchingSynonym(String taxonName) {
1022
        List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1023
        if(! synonymList.isEmpty()){
1024
            Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1025
            if(synonymList.size() == 1){
1026
                logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1027
                return result;
1028
            } else {
1029
                logger.info("Several matching synonyms found. Using first: " +  result.getTitleCache());
1030
                return result;
1031
            }
1032
        }
1033
        return null;
1034
    }
1035

    
1036

    
1037
    /* (non-Javadoc)
1038
     * @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)
1039
     */
1040
    @Override
1041
    public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, Taxon newTaxon, boolean moveHomotypicGroup,
1042
            SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
1043

    
1044
        Synonym synonym = oldSynonymRelation.getSynonym();
1045
        Taxon fromTaxon = oldSynonymRelation.getAcceptedTaxon();
1046
        //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1047
        TaxonNameBase<?,?> synonymName = synonym.getName();
1048
        TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1049
        //set default relationship type
1050
        if (newSynonymRelationshipType == null){
1051
            newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1052
        }
1053
        boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1054

    
1055
        HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1056
        int hgSize = homotypicGroup.getTypifiedNames().size();
1057
        boolean isSingleInGroup = !(hgSize > 1);
1058

    
1059
        if (! isSingleInGroup){
1060
            boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1061
            boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1062
            if (isHomotypicToAccepted){
1063
                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.";
1064
                String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1065
                message = String.format(message, homotypicRelatives);
1066
                throw new HomotypicalGroupChangeException(message);
1067
            }
1068
            if (! moveHomotypicGroup){
1069
                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.";
1070
                throw new HomotypicalGroupChangeException(message);
1071
            }
1072
        }else{
1073
            moveHomotypicGroup = true;  //single synonym always allows to moveCompleteGroup
1074
        }
1075
//        Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1076

    
1077
        SynonymRelationship result = null;
1078
        //move all synonyms to new taxon
1079
        List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1080
        for (Synonym syn: homotypicSynonyms){
1081
            Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1082
            for (SynonymRelationship synRelation : synRelations){
1083
                if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1084
                    Reference<?> newReference = reference;
1085
                    if (newReference == null && keepReference){
1086
                        newReference = synRelation.getCitation();
1087
                    }
1088
                    String newRefDetail = referenceDetail;
1089
                    if (newRefDetail == null && keepReference){
1090
                        newRefDetail = synRelation.getCitationMicroReference();
1091
                    }
1092
                    SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1093
                    fromTaxon.removeSynonymRelation(synRelation, false);
1094
//
1095
                    //change homotypic group of synonym if relType is 'homotypic'
1096
//                	if (newRelTypeIsHomotypic){
1097
//                		newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1098
//                	}
1099
                    //set result
1100
                    if (synRelation.equals(oldSynonymRelation)){
1101
                        result = newSynRelation;
1102
                    }
1103
                }
1104
            }
1105

    
1106
        }
1107
        saveOrUpdate(newTaxon);
1108
        //Assert that there is a result
1109
        if (result == null){
1110
            String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1111
            throw new IllegalStateException(message);
1112
        }
1113
        return result;
1114
    }
1115

    
1116
    /* (non-Javadoc)
1117
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheTaxon()
1118
     */
1119
    @Override
1120
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1121
        return dao.getUuidAndTitleCacheTaxon();
1122
    }
1123

    
1124
    /* (non-Javadoc)
1125
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#getUuidAndTitleCacheSynonym()
1126
     */
1127
    @Override
1128
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1129
        return dao.getUuidAndTitleCacheSynonym();
1130
    }
1131

    
1132
    @Override
1133
    public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
1134
            Class<? extends DescriptionElementBase> clazz, String queryString,
1135
            Classification classification, List<Feature> features, List<Language> languages,
1136
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1137

    
1138
        Class<? extends DescriptionElementBase> directorySelectClass = DescriptionElementBase.class;
1139
        if(clazz != null){
1140
            directorySelectClass = clazz;
1141
        }
1142

    
1143
        Set<String> freetextFields = new HashSet<String>();
1144
        // ---- search criteria
1145
        freetextFields.add("titleCache");
1146
        StringBuilder luceneQueryTemplate = new StringBuilder();
1147
        luceneQueryTemplate.append("+(");
1148
        luceneQueryTemplate.append("titleCache:(%1$s) ");
1149
        // common name
1150
        freetextFields.add("name");
1151
        if(languages == null || languages.size() == 0){
1152
            luceneQueryTemplate.append("name:(%1$s) ");
1153
        } else {
1154
            luceneQueryTemplate.append("(+name:(%1$s) ");
1155
            for(Language lang : languages){
1156
                luceneQueryTemplate.append(" +language.uuid:" + lang.getUuid().toString());
1157
            }
1158
            luceneQueryTemplate.append(")");
1159
        }
1160
        // text field from TextData
1161
        freetextFields.add("text.ALL");
1162
        appendLocalizedFieldQuery("text", languages, luceneQueryTemplate).append(" ");
1163
        // state field from CategoricalData
1164
        freetextFields.add("states.state.representation.ALL");
1165
        appendLocalizedFieldQuery("states.state.representation", languages, luceneQueryTemplate).append(" ");
1166
        // state field from CategoricalData
1167
        freetextFields.add("states.modifyingText.ALL");
1168
        appendLocalizedFieldQuery("states.modifyingText", languages, luceneQueryTemplate).append(" ");
1169
        luceneQueryTemplate.append(") ");
1170

    
1171
        if(classification != null){
1172
            luceneQueryTemplate.append("+inDescription.taxon.taxonNodes.classification.id:").append(PaddedIntegerBridge.paddInteger(classification.getId())).append(" ");
1173
        }
1174

    
1175
        if(features != null && features.size() > 0 ){
1176
            luceneQueryTemplate.append("+feature.uuid:(");
1177
            for(Feature feature : features){
1178
                luceneQueryTemplate.append(feature.getUuid()).append(" ");
1179
            }
1180
            luceneQueryTemplate.append(") ");
1181
        }
1182

    
1183
        // the description must be associated with a taxon
1184
        // TODO open range queries [0 TO *] not working in the current version of lucene (https://issues.apache.org/jira/browse/LUCENE-995)
1185
        //       so we are using integer maximum as workaround
1186
        luceneQueryTemplate.append("+inDescription.taxon.id:[ " + PaddedIntegerBridge.paddInteger(0) + " TO " + PaddedIntegerBridge.paddInteger(Integer.MAX_VALUE) + "] ");
1187
        //luceneQueryTemplate.append("-inDescription.taxon.id:" + PaddedIntegerBridge.NULL_STRING);
1188

    
1189
        String luceneQueryStr = String.format(luceneQueryTemplate.toString(), queryString);
1190

    
1191
        // --- sort fields
1192
        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", false)};
1193

    
1194
        // ---- execute criteria
1195
        LuceneSearch luceneSearch = new LuceneSearch(getSession(), directorySelectClass);
1196

    
1197
        Query luceneQuery = luceneSearch.parse(luceneQueryStr);
1198
        TopDocs topDocsResultSet = luceneSearch.executeSearch(luceneQuery, clazz, pageSize, pageNumber, sortFields);
1199

    
1200
        String[] highlightFields = null;
1201
        if(highlightFragments){
1202
            highlightFields = freetextFields.toArray(new String[freetextFields.size()]);
1203
        }
1204

    
1205
        // initialize taxa, thighlight matches ....
1206
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneQuery);
1207
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1208
                topDocsResultSet, highlightFields, dao, "inDescription.taxon.id", propertyPaths);
1209

    
1210
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, searchResults.size(), pageSize, searchResults);
1211

    
1212
    }
1213

    
1214
    /**
1215
     * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
1216
     * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
1217
     * This method is a convenient means to retrieve a Lucene query string for such the fields.
1218
     *
1219
     * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
1220
     * or {@link MultilanguageTextFieldBridge }
1221
     * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
1222
     * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
1223
     * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
1224
     *
1225
     * TODO move to utiliy class !!!!!!!!
1226
     */
1227
    private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
1228

    
1229
        if(stringBuilder == null){
1230
            stringBuilder = new StringBuilder();
1231
        }
1232
        if(languages == null || languages.size() == 0){
1233
            stringBuilder.append(name + ".ALL:(%1$s) ");
1234
        } else {
1235
            for(Language lang : languages){
1236
                stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
1237
            }
1238
        }
1239
        return stringBuilder;
1240
    }
1241

    
1242
    public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
1243
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
1244
        List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
1245

    
1246
        HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
1247

    
1248

    
1249
        UUID uuid= taxon.getName().getUuid();
1250
        ZoologicalName taxonName = getZoologicalName(uuid, zooHashMap);
1251
        String epithetOfTaxon = null;
1252
        String infragenericEpithetOfTaxon = null;
1253
        String infraspecificEpithetOfTaxon = null;
1254
        if (taxonName.isSpecies()){
1255
             epithetOfTaxon= taxonName.getSpecificEpithet();
1256
        } else if (taxonName.isInfraGeneric()){
1257
            infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
1258
        } else if (taxonName.isInfraSpecific()){
1259
            infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
1260
        }
1261
        String genusOfTaxon = taxonName.getGenusOrUninomial();
1262
        Set<TaxonNode> nodes = taxon.getTaxonNodes();
1263
         List<String> taxonNames = new ArrayList<String>();
1264

    
1265
        for (TaxonNode node: nodes){
1266
           // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
1267
           // List<String> synonymsEpithet = new ArrayList<String>();
1268

    
1269
            if (node.getClassification().equals(classification)){
1270
                if (!node.isTopmostNode()){
1271
                    TaxonNode parent = (TaxonNode)node.getParent();
1272
                    parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
1273
                    TaxonNameBase parentName =  parent.getTaxon().getName();
1274
                    ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
1275
                    Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
1276
                    Rank rankOfTaxon = taxonName.getRank();
1277

    
1278

    
1279
                    //create inferred synonyms for species, subspecies
1280
                    if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
1281

    
1282
                        Synonym inferredEpithet = null;
1283
                        Synonym inferredGenus = null;
1284
                        Synonym potentialCombination = null;
1285

    
1286
                        List<String> propertyPaths = new ArrayList<String>();
1287
                        propertyPaths.add("synonym");
1288
                        propertyPaths.add("synonym.name");
1289
                        List<OrderHint> orderHints = new ArrayList<OrderHint>();
1290
                        orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
1291

    
1292
                        List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
1293
                        List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
1294

    
1295
                        List<TaxonRelationship> taxonRelListParent = null;
1296
                        List<TaxonRelationship> taxonRelListTaxon = null;
1297
                        if (doWithMisappliedNames){
1298
                            taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
1299
                            taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
1300
                        }
1301

    
1302

    
1303
                        if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
1304
                            Set<String> genusNames = new HashSet<String>();
1305

    
1306
                            for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
1307
                                Synonym syn = synonymRelationOfParent.getSynonym();
1308

    
1309
                                inferredEpithet = createInferredEpithets(taxon,
1310
                                        zooHashMap, taxonName, epithetOfTaxon,
1311
                                        infragenericEpithetOfTaxon,
1312
                                        infraspecificEpithetOfTaxon,
1313
                                        taxonNames, parentName,
1314
                                        syn);
1315

    
1316

    
1317
                                inferredSynonyms.add(inferredEpithet);
1318
                                zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
1319
                                taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
1320
                            }
1321

    
1322
                            if (doWithMisappliedNames){
1323

    
1324
                                for (TaxonRelationship taxonRelationship: taxonRelListParent){
1325
                                     Taxon misappliedName = taxonRelationship.getFromTaxon();
1326

    
1327
                                     inferredEpithet = createInferredEpithets(taxon,
1328
                                             zooHashMap, taxonName, epithetOfTaxon,
1329
                                             infragenericEpithetOfTaxon,
1330
                                             infraspecificEpithetOfTaxon,
1331
                                             taxonNames, parentName,
1332
                                             misappliedName);
1333

    
1334
                                    inferredSynonyms.add(inferredEpithet);
1335
                                    zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
1336
                                     taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
1337
                                }
1338
                            }
1339

    
1340
                            if (!taxonNames.isEmpty()){
1341
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
1342
                            ZoologicalName name;
1343
                            if (!synNotInCDM.isEmpty()){
1344
                                inferredSynonymsToBeRemoved.clear();
1345

    
1346
                                for (Synonym syn :inferredSynonyms){
1347
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1348
                                    if (!synNotInCDM.contains(name.getNameCache())){
1349
                                        inferredSynonymsToBeRemoved.add(syn);
1350
                                    }
1351
                                }
1352

    
1353
                                // Remove identified Synonyms from inferredSynonyms
1354
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
1355
                                    inferredSynonyms.remove(synonym);
1356
                                }
1357
                            }
1358
                        }
1359

    
1360
                    }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
1361

    
1362

    
1363
                        for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
1364
                            TaxonNameBase synName;
1365
                            ZoologicalName inferredSynName;
1366

    
1367
                            Synonym syn = synonymRelationOfTaxon.getSynonym();
1368
                            inferredGenus = createInferredGenus(taxon,
1369
                                    zooHashMap, taxonName, epithetOfTaxon,
1370
                                    genusOfTaxon, taxonNames, zooParentName, syn);
1371

    
1372
                            inferredSynonyms.add(inferredGenus);
1373
                            zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
1374
                            taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
1375

    
1376

    
1377
                        }
1378

    
1379
                        if (doWithMisappliedNames){
1380

    
1381
                            for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
1382
                                Taxon misappliedName = taxonRelationship.getFromTaxon();
1383
                                inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
1384

    
1385
                                inferredSynonyms.add(inferredGenus);
1386
                                zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
1387
                                 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
1388
                            }
1389
                        }
1390

    
1391

    
1392
                        if (!taxonNames.isEmpty()){
1393
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
1394
                            ZoologicalName name;
1395
                            if (!synNotInCDM.isEmpty()){
1396
                                inferredSynonymsToBeRemoved.clear();
1397

    
1398
                                for (Synonym syn :inferredSynonyms){
1399
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1400
                                    if (!synNotInCDM.contains(name.getNameCache())){
1401
                                        inferredSynonymsToBeRemoved.add(syn);
1402
                                    }
1403
                                }
1404

    
1405
                                // Remove identified Synonyms from inferredSynonyms
1406
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
1407
                                    inferredSynonyms.remove(synonym);
1408
                                }
1409
                            }
1410
                        }
1411

    
1412
                    }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
1413

    
1414
                        Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
1415
                        ZoologicalName inferredSynName;
1416
                        //for all synonyms of the parent...
1417
                        for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
1418
                            TaxonNameBase synName;
1419
                            Synonym synParent = synonymRelationOfParent.getSynonym();
1420
                            synName = synParent.getName();
1421

    
1422
                            HibernateProxyHelper.deproxy(synParent);
1423

    
1424
                            // Set the sourceReference
1425
                            sourceReference = synParent.getSec();
1426

    
1427
                            // Determine the idInSource
1428
                            String idInSourceParent = getIdInSource(synParent);
1429

    
1430
                            ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
1431
                            String synParentGenus = parentSynZooName.getGenusOrUninomial();
1432
                            String synParentInfragenericName = null;
1433
                            String synParentSpecificEpithet = null;
1434

    
1435
                            if (parentSynZooName.isInfraGeneric()){
1436
                                synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
1437
                            }
1438
                            if (parentSynZooName.isSpecies()){
1439
                                synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
1440
                            }
1441

    
1442
                           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
1443
                                synonymsGenus.put(synGenusName, idInSource);
1444
                            }*/
1445

    
1446
                            //for all synonyms of the taxon
1447

    
1448
                            for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
1449

    
1450
                                Synonym syn = synonymRelationOfTaxon.getSynonym();
1451
                                ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1452
                                potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
1453
                                        synParentGenus,
1454
                                        synParentInfragenericName,
1455
                                        synParentSpecificEpithet, syn, zooHashMap);
1456

    
1457
                                taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
1458
                                inferredSynonyms.add(potentialCombination);
1459
                                zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
1460
                                 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
1461

    
1462
                            }
1463

    
1464

    
1465
                        }
1466

    
1467
                        if (doWithMisappliedNames){
1468

    
1469
                            for (TaxonRelationship parentRelationship: taxonRelListParent){
1470

    
1471
                                TaxonNameBase misappliedParentName;
1472

    
1473
                                Taxon misappliedParent = parentRelationship.getFromTaxon();
1474
                                misappliedParentName = misappliedParent.getName();
1475

    
1476
                                HibernateProxyHelper.deproxy(misappliedParent);
1477

    
1478
                                // Set the sourceReference
1479
                                sourceReference = misappliedParent.getSec();
1480

    
1481
                                // Determine the idInSource
1482
                                String idInSourceParent = getIdInSource(misappliedParent);
1483

    
1484
                                ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
1485
                                String synParentGenus = parentSynZooName.getGenusOrUninomial();
1486
                                String synParentInfragenericName = null;
1487
                                String synParentSpecificEpithet = null;
1488

    
1489
                                if (parentSynZooName.isInfraGeneric()){
1490
                                    synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
1491
                                }
1492
                                if (parentSynZooName.isSpecies()){
1493
                                    synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
1494
                                }
1495

    
1496

    
1497
                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
1498
                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
1499
                                    ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
1500
                                    potentialCombination = createPotentialCombination(
1501
                                            idInSourceParent, parentSynZooName, zooMisappliedName,
1502
                                            synParentGenus,
1503
                                            synParentInfragenericName,
1504
                                            synParentSpecificEpithet, misappliedName, zooHashMap);
1505

    
1506

    
1507
                                    taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
1508
                                    inferredSynonyms.add(potentialCombination);
1509
                                    zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
1510
                                     taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
1511
                                }
1512
                            }
1513
                        }
1514

    
1515
                        if (!taxonNames.isEmpty()){
1516
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
1517
                            ZoologicalName name;
1518
                            if (!synNotInCDM.isEmpty()){
1519
                                inferredSynonymsToBeRemoved.clear();
1520
                                for (Synonym syn :inferredSynonyms){
1521
                                    try{
1522
                                        name = (ZoologicalName) syn.getName();
1523
                                    }catch (ClassCastException e){
1524
                                        name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
1525
                                    }
1526
                                    if (!synNotInCDM.contains(name.getNameCache())){
1527
                                        inferredSynonymsToBeRemoved.add(syn);
1528
                                    }
1529
                                 }
1530
                                // Remove identified Synonyms from inferredSynonyms
1531
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
1532
                                    inferredSynonyms.remove(synonym);
1533
                                }
1534
                            }
1535
                         }
1536
                        }
1537
                    }else {
1538
                        logger.info("The synonymrelationship type is not defined.");
1539
                        return inferredSynonyms;
1540
                    }
1541
                }
1542
            }
1543

    
1544
        }
1545

    
1546
        return inferredSynonyms;
1547
    }
1548

    
1549
    private Synonym createPotentialCombination(String idInSourceParent,
1550
            ZoologicalName parentSynZooName, 	ZoologicalName zooSynName, String synParentGenus,
1551
            String synParentInfragenericName, String synParentSpecificEpithet,
1552
            TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
1553
        Synonym potentialCombination;
1554
        Reference sourceReference;
1555
        ZoologicalName inferredSynName;
1556
        HibernateProxyHelper.deproxy(syn);
1557

    
1558
        // Set sourceReference
1559
        sourceReference = syn.getSec();
1560
        if (sourceReference == null){
1561
            logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
1562
            //TODO:Remove
1563
            if (!parentSynZooName.getTaxa().isEmpty()){
1564
                TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
1565

    
1566
                sourceReference = taxon.getSec();
1567
            }
1568
        }
1569
        String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
1570

    
1571
        String synTaxonInfraSpecificName= null;
1572

    
1573
        if (parentSynZooName.isSpecies()){
1574
            synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
1575
        }
1576

    
1577
        /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
1578
            synonymsEpithet.add(epithetName);
1579
        }*/
1580

    
1581
        //create potential combinations...
1582
        inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
1583

    
1584
        inferredSynName.setGenusOrUninomial(synParentGenus);
1585
        if (zooSynName.isSpecies()){
1586
              inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
1587
              if (parentSynZooName.isInfraGeneric()){
1588
                  inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
1589
              }
1590
        }
1591
        if (zooSynName.isInfraSpecific()){
1592
            inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
1593
            inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
1594
        }
1595
        if (parentSynZooName.isInfraGeneric()){
1596
            inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
1597
        }
1598

    
1599

    
1600
        potentialCombination = Synonym.NewInstance(inferredSynName, null);
1601

    
1602
        // Set the sourceReference
1603
        potentialCombination.setSec(sourceReference);
1604

    
1605

    
1606
        // Determine the idInSource
1607
        String idInSourceSyn= getIdInSource(syn);
1608

    
1609
        if (idInSourceParent != null && idInSourceSyn != null) {
1610
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
1611
            inferredSynName.addSource(originalSource);
1612
            originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
1613
            potentialCombination.addSource(originalSource);
1614
        }
1615

    
1616
        inferredSynName.generateTitle();
1617

    
1618
        return potentialCombination;
1619
    }
1620

    
1621
    private Synonym createInferredGenus(Taxon taxon,
1622
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
1623
            String epithetOfTaxon, String genusOfTaxon,
1624
            List<String> taxonNames, ZoologicalName zooParentName,
1625
            TaxonBase syn) {
1626

    
1627
        Synonym inferredGenus;
1628
        TaxonNameBase synName;
1629
        ZoologicalName inferredSynName;
1630
        synName =syn.getName();
1631
        HibernateProxyHelper.deproxy(syn);
1632

    
1633
        // Determine the idInSource
1634
        String idInSourceSyn = getIdInSource(syn);
1635
        String idInSourceTaxon = getIdInSource(taxon);
1636
        // Determine the sourceReference
1637
        Reference sourceReference = syn.getSec();
1638

    
1639
        //logger.warn(sourceReference.getTitleCache());
1640

    
1641
        synName = syn.getName();
1642
        ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
1643
        String synSpeciesEpithetName = synZooName.getSpecificEpithet();
1644
                     /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
1645
            synonymsEpithet.add(synSpeciesEpithetName);
1646
        }*/
1647

    
1648
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
1649
        //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...
1650

    
1651

    
1652
        inferredSynName.setGenusOrUninomial(genusOfTaxon);
1653
        if (zooParentName.isInfraGeneric()){
1654
            inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
1655
        }
1656

    
1657
        if (taxonName.isSpecies()){
1658
            inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
1659
        }
1660
        if (taxonName.isInfraSpecific()){
1661
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
1662
            inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
1663
        }
1664

    
1665

    
1666
        inferredGenus = Synonym.NewInstance(inferredSynName, null);
1667

    
1668
        // Set the sourceReference
1669
        inferredGenus.setSec(sourceReference);
1670

    
1671
        // Add the original source
1672
        if (idInSourceSyn != null && idInSourceTaxon != null) {
1673
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
1674
            inferredGenus.addSource(originalSource);
1675

    
1676
            originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
1677
            inferredSynName.addSource(originalSource);
1678
            originalSource = null;
1679

    
1680
        }else{
1681
            logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
1682
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
1683
            inferredGenus.addSource(originalSource);
1684

    
1685
            originalSource = IdentifiableSource.NewInstance(idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
1686
            inferredSynName.addSource(originalSource);
1687
            originalSource = null;
1688
        }
1689

    
1690
        taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
1691

    
1692
        inferredSynName.generateTitle();
1693

    
1694

    
1695
        return inferredGenus;
1696
    }
1697

    
1698
    private Synonym createInferredEpithets(Taxon taxon,
1699
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
1700
            String epithetOfTaxon, String infragenericEpithetOfTaxon,
1701
            String infraspecificEpithetOfTaxon, List<String> taxonNames,
1702
            TaxonNameBase parentName, TaxonBase syn) {
1703

    
1704
        Synonym inferredEpithet;
1705
        TaxonNameBase synName;
1706
        ZoologicalName inferredSynName;
1707
        HibernateProxyHelper.deproxy(syn);
1708

    
1709
        // Determine the idInSource
1710
        String idInSourceSyn = getIdInSource(syn);
1711
        String idInSourceTaxon =  getIdInSource(taxon);
1712
        // Determine the sourceReference
1713
        Reference sourceReference = syn.getSec();
1714

    
1715
        if (sourceReference == null){
1716
            logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
1717
            //TODO:Remove
1718
            System.out.println("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
1719
            sourceReference = taxon.getSec();
1720
        }
1721

    
1722
        synName = syn.getName();
1723
        ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
1724
        String synGenusName = zooSynName.getGenusOrUninomial();
1725
        String synInfraGenericEpithet = null;
1726
        String synSpecificEpithet = null;
1727

    
1728
        if (zooSynName.getInfraGenericEpithet() != null){
1729
            synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
1730
        }
1731

    
1732
        if (zooSynName.isInfraSpecific()){
1733
            synSpecificEpithet = zooSynName.getSpecificEpithet();
1734
        }
1735

    
1736
                     /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
1737
            synonymsGenus.put(synGenusName, idInSource);
1738
        }*/
1739

    
1740
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
1741

    
1742
        // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
1743
        if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
1744
            logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
1745
        }
1746
        inferredSynName.setGenusOrUninomial(synGenusName);
1747

    
1748
        if (parentName.isInfraGeneric()){
1749
            inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
1750
        }
1751
        if (taxonName.isSpecies()){
1752
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
1753
        }else if (taxonName.isInfraSpecific()){
1754
            inferredSynName.setSpecificEpithet(synSpecificEpithet);
1755
            inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
1756
        }
1757

    
1758
        inferredEpithet = Synonym.NewInstance(inferredSynName, null);
1759

    
1760
        // Set the sourceReference
1761
        inferredEpithet.setSec(sourceReference);
1762

    
1763
        /* Add the original source
1764
        if (idInSource != null) {
1765
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
1766

    
1767
            // Add the citation
1768
            Reference citation = getCitation(syn);
1769
            if (citation != null) {
1770
                originalSource.setCitation(citation);
1771
                inferredEpithet.addSource(originalSource);
1772
            }
1773
        }*/
1774
        String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
1775

    
1776
        IdentifiableSource originalSource;
1777
        originalSource = IdentifiableSource.NewInstance(taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
1778

    
1779
        inferredEpithet.addSource(originalSource);
1780

    
1781
        originalSource = IdentifiableSource.NewInstance(taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
1782

    
1783
        inferredSynName.addSource(originalSource);
1784

    
1785

    
1786

    
1787
        taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
1788

    
1789
        inferredSynName.generateTitle();
1790
        return inferredEpithet;
1791
    }
1792

    
1793
    /**
1794
     * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
1795
     * Very likely only useful for createInferredSynonyms().
1796
     * @param uuid
1797
     * @param zooHashMap
1798
     * @return
1799
     */
1800
    private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
1801
        ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
1802
        if (taxonName == null) {
1803
            taxonName = zooHashMap.get(uuid);
1804
        }
1805
        return taxonName;
1806
    }
1807

    
1808
    /**
1809
     * Returns the idInSource for a given Synonym.
1810
     * @param syn
1811
     */
1812
    private String getIdInSource(TaxonBase taxonBase) {
1813
        String idInSource = null;
1814
        Set<IdentifiableSource> sources = taxonBase.getSources();
1815
        if (sources.size() == 1) {
1816
            IdentifiableSource source = sources.iterator().next();
1817
            if (source != null) {
1818
                idInSource  = source.getIdInSource();
1819
            }
1820
        } else if (sources.size() > 1) {
1821
            int count = 1;
1822
            idInSource = "";
1823
            for (IdentifiableSource source : sources) {
1824
                idInSource += source.getIdInSource();
1825
                if (count < sources.size()) {
1826
                    idInSource += "; ";
1827
                }
1828
                count++;
1829
            }
1830
        } else if (sources.size() == 0){
1831
            logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
1832
        }
1833

    
1834

    
1835
        return idInSource;
1836
    }
1837

    
1838

    
1839
    /**
1840
     * Returns the citation for a given Synonym.
1841
     * @param syn
1842
     */
1843
    private Reference getCitation(Synonym syn) {
1844
        Reference citation = null;
1845
        Set<IdentifiableSource> sources = syn.getSources();
1846
        if (sources.size() == 1) {
1847
            IdentifiableSource source = sources.iterator().next();
1848
            if (source != null) {
1849
                citation = source.getCitation();
1850
            }
1851
        } else if (sources.size() > 1) {
1852
            logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
1853
        }
1854

    
1855
        return citation;
1856
    }
1857

    
1858
    public List<Synonym>  createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
1859
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
1860

    
1861
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
1862
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
1863
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
1864

    
1865
        return inferredSynonyms;
1866
    }
1867

    
1868

    
1869

    
1870

    
1871
}
(74-74/79)