Project

General

Profile

Download (155 KB) Statistics
| Branch: | Tag: | Revision:
1
// $Id$
2
/**
3
* Copyright (C) 2007 EDIT
4
* European Distributed Institute of Taxonomy
5
* http://www.e-taxonomy.eu
6
*
7
* The contents of this file are subject to the Mozilla Public License Version 1.1
8
* See LICENSE.TXT at the top of this package for the full license terms.
9
*/
10

    
11
package eu.etaxonomy.cdm.api.service;
12

    
13
import java.io.IOException;
14
import java.util.ArrayList;
15
import java.util.EnumSet;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.Iterator;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22
import java.util.UUID;
23

    
24
import javax.persistence.EntityNotFoundException;
25

    
26
import org.apache.commons.lang.StringUtils;
27
import org.apache.log4j.Logger;
28
import org.apache.lucene.index.CorruptIndexException;
29
import org.apache.lucene.queryParser.ParseException;
30
import org.apache.lucene.search.BooleanClause.Occur;
31
import org.apache.lucene.search.BooleanFilter;
32
import org.apache.lucene.search.BooleanQuery;
33
import org.apache.lucene.search.DocIdSet;
34
import org.apache.lucene.search.Query;
35
import org.apache.lucene.search.QueryWrapperFilter;
36
import org.apache.lucene.search.SortField;
37
import org.springframework.beans.factory.annotation.Autowired;
38
import org.springframework.stereotype.Service;
39
import org.springframework.transaction.annotation.Transactional;
40

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

    
132

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

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

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

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

    
149
    @Autowired
150
    private ITaxonNodeDao taxonNodeDao;
151

    
152
    @Autowired
153
    private ITaxonNameDao nameDao;
154

    
155
    @Autowired
156
    private INameService nameService;
157

    
158
    @Autowired
159
    private IOccurrenceService occurrenceService;
160

    
161
    @Autowired
162
    private ITaxonNodeService nodeService;
163

    
164
    @Autowired
165
    private ICdmGenericDao genericDao;
166

    
167
    @Autowired
168
    private IDescriptionService descriptionService;
169

    
170
    @Autowired
171
    private IOrderedTermVocabularyDao orderedVocabularyDao;
172

    
173
    @Autowired
174
    private IOccurrenceDao occurrenceDao;
175

    
176
    @Autowired
177
    private IClassificationDao classificationDao;
178

    
179
    @Autowired
180
    private AbstractBeanInitializer beanInitializer;
181

    
182
    @Autowired
183
    private ILuceneIndexToolProvider luceneIndexToolProvider;
184

    
185
    /**
186
     * Constructor
187
     */
188
    public TaxonServiceImpl(){
189
        if (logger.isDebugEnabled()) { logger.debug("Load TaxonService Bean"); }
190
    }
191

    
192
    /**
193
     * FIXME Candidate for harmonization
194
     * rename searchByName ?
195
     */
196
    @Override
197
    public List<TaxonBase> searchTaxaByName(String name, Reference sec) {
198
        return dao.getTaxaByName(name, sec);
199
    }
200

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

    
215
    @Override
216
    public List<Taxon> getRootTaxa(Rank rank, Reference sec, boolean onlyWithChildren,boolean withMisapplications, List<String> propertyPaths) {
217
        return dao.getRootTaxa(rank, sec, null, onlyWithChildren, withMisapplications, propertyPaths);
218
    }
219

    
220
    @Override
221
    public List<RelationshipBase> getAllRelationships(int limit, int start){
222
        return dao.getAllRelationships(limit, start);
223
    }
224

    
225
    /**
226
     * FIXME Candidate for harmonization
227
     * is this the same as termService.getVocabulary(VocabularyEnum.TaxonRelationshipType) ?
228
     */
229
    @Override
230
    @Deprecated
231
    public OrderedTermVocabulary<TaxonRelationshipType> getTaxonRelationshipTypeVocabulary() {
232

    
233
        String taxonRelTypeVocabularyId = "15db0cf7-7afc-4a86-a7d4-221c73b0c9ac";
234
        UUID uuid = UUID.fromString(taxonRelTypeVocabularyId);
235
        OrderedTermVocabulary<TaxonRelationshipType> taxonRelTypeVocabulary =
236
            (OrderedTermVocabulary)orderedVocabularyDao.findByUuid(uuid);
237
        return taxonRelTypeVocabulary;
238
    }
239

    
240
    @Override
241
    @Transactional(readOnly = false)
242
    public UpdateResult swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon){
243
    	UpdateResult result = new UpdateResult();
244
        TaxonNameBase<?,?> synonymName = synonym.getName();
245
        synonymName.removeTaxonBase(synonym);
246
        TaxonNameBase<?,?> taxonName = acceptedTaxon.getName();
247
        taxonName.removeTaxonBase(acceptedTaxon);
248

    
249
        synonym.setName(taxonName);
250
        acceptedTaxon.setName(synonymName);
251
        saveOrUpdate(synonym);
252
        saveOrUpdate(acceptedTaxon);
253
        result.addUpdatedObject(acceptedTaxon);
254
        result.addUpdatedObject(synonym);
255
		return result;
256

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

    
262

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

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

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

    
277
        Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, acceptedTaxon.getSec());
278
        dao.save(newAcceptedTaxon);
279
        SynonymRelationshipType relTypeForGroup = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
280
        List<Synonym> heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup);
281
        Set<NameRelationship> basionymsAndReplacedSynonyms = synonymHomotypicGroup.getBasionymAndReplacedSynonymRelations();
282

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

    
287
            }else{
288
                //move synonyms in same homotypic group to new accepted taxon
289
                heteroSynonym.replaceAcceptedTaxon(newAcceptedTaxon, relTypeForGroup, copyCitationInfo, citation, microCitation);
290
            }
291
        }
292
        dao.saveOrUpdate(acceptedTaxon);
293
        //synonym.getName().removeTaxonBase(synonym);
294

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

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

    
308
        return newAcceptedTaxon;
309
    }
310

    
311
    @Override
312
    @Transactional(readOnly = false)
313
    public UpdateResult changeSynonymToAcceptedTaxon(UUID synonymUuid,
314
            UUID acceptedTaxonUuid,
315
            UUID newParentNodeUuid,
316
            boolean deleteSynonym,
317
            boolean copyCitationInfo,
318
            Reference citation,
319
            String microCitation) throws HomotypicalGroupChangeException {
320
        UpdateResult result = new UpdateResult();
321
        Synonym synonym = CdmBase.deproxy(dao.load(synonymUuid), Synonym.class);
322
        Taxon acceptedTaxon = CdmBase.deproxy(dao.load(acceptedTaxonUuid), Taxon.class);
323
        Taxon taxon =  changeSynonymToAcceptedTaxon(synonym, acceptedTaxon, deleteSynonym, copyCitationInfo, citation, microCitation);
324
        TaxonNode newParentNode = taxonNodeDao.load(newParentNodeUuid);
325
        TaxonNode newNode = newParentNode.addChildTaxon(taxon, null, null);
326
        taxonNodeDao.save(newNode);
327
        result.addUpdatedObject(taxon);
328
        result.addUpdatedObject(acceptedTaxon);
329
        result.setCdmEntity(newNode);
330
        return result;
331
    }
332

    
333
    @Override
334
    @Transactional(readOnly = false)
335
    public UpdateResult changeSynonymToRelatedTaxon(UUID synonymUuid,
336
            UUID toTaxonUuid,
337
            TaxonRelationshipType taxonRelationshipType,
338
            Reference citation,
339
            String microcitation){
340

    
341
        UpdateResult result = new UpdateResult();
342
        Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
343
        Synonym synonym = (Synonym) dao.load(synonymUuid);
344
        Taxon relatedTaxon = changeSynonymToRelatedTaxon(synonym, toTaxon, taxonRelationshipType, citation, microcitation);
345
        result.setCdmEntity(relatedTaxon);
346
        result.addUpdatedObject(relatedTaxon);
347
        result.addUpdatedObject(toTaxon);
348
        return result;
349
    }
350

    
351
    @Override
352
    @Transactional(readOnly = false)
353
    public Taxon changeSynonymToRelatedTaxon(Synonym synonym, Taxon toTaxon, TaxonRelationshipType taxonRelationshipType, Reference citation, String microcitation){
354

    
355
        // Get name from synonym
356
        TaxonNameBase<?, ?> synonymName = synonym.getName();
357

    
358
      /*  // remove synonym from taxon
359
        toTaxon.removeSynonym(synonym);
360
*/
361
        // Create a taxon with synonym name
362
        Taxon fromTaxon = Taxon.NewInstance(synonymName, null);
363

    
364
        // Add taxon relation
365
        fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
366

    
367
        // since we are swapping names, we have to detach the name from the synonym completely.
368
        // Otherwise the synonym will still be in the list of typified names.
369
       // synonym.getName().removeTaxonBase(synonym);
370
        this.deleteSynonym(synonym, null);
371

    
372
        return fromTaxon;
373
    }
374

    
375
    @Transactional(readOnly = false)
376
    @Override
377
    public void changeHomotypicalGroupOfSynonym(Synonym synonym, HomotypicalGroup newHomotypicalGroup, Taxon targetTaxon,
378
                        boolean removeFromOtherTaxa, boolean setBasionymRelationIfApplicable){
379
        // Get synonym name
380
        TaxonNameBase synonymName = synonym.getName();
381
        HomotypicalGroup oldHomotypicalGroup = synonymName.getHomotypicalGroup();
382

    
383

    
384
        // Switch groups
385
        oldHomotypicalGroup.removeTypifiedName(synonymName, false);
386
        newHomotypicalGroup.addTypifiedName(synonymName);
387

    
388
        //remove existing basionym relationships
389
        synonymName.removeBasionyms();
390

    
391
        //add basionym relationship
392
        if (setBasionymRelationIfApplicable){
393
            Set<TaxonNameBase> basionyms = newHomotypicalGroup.getBasionyms();
394
            for (TaxonNameBase basionym : basionyms){
395
                synonymName.addBasionym(basionym);
396
            }
397
        }
398

    
399
        //set synonym relationship correctly
400
//			SynonymRelationship relToTaxon = null;
401
        boolean relToTargetTaxonExists = false;
402
        Set<SynonymRelationship> existingRelations = synonym.getSynonymRelations();
403
        for (SynonymRelationship rel : existingRelations){
404
            Taxon acceptedTaxon = rel.getAcceptedTaxon();
405
            boolean isTargetTaxon = acceptedTaxon != null && acceptedTaxon.equals(targetTaxon);
406
            HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
407
            boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
408
            SynonymRelationshipType newRelationType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
409
            rel.setType(newRelationType);
410
            //TODO handle citation and microCitation
411

    
412
            if (isTargetTaxon){
413
                relToTargetTaxonExists = true;
414
            }else{
415
                if (removeFromOtherTaxa){
416
                    acceptedTaxon.removeSynonym(synonym, false);
417
                }else{
418
                    //do nothing
419
                }
420
            }
421
        }
422
        if (targetTaxon != null &&  ! relToTargetTaxonExists ){
423
            Taxon acceptedTaxon = targetTaxon;
424
            HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
425
            boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
426
            SynonymRelationshipType relType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
427
            //TODO handle citation and microCitation
428
            Reference citation = null;
429
            String microCitation = null;
430
            acceptedTaxon.addSynonym(synonym, relType, citation, microCitation);
431
        }
432

    
433
    }
434

    
435
    @Override
436
    @Transactional(readOnly = false)
437
    public void updateTitleCache(Class<? extends TaxonBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonBase> cacheStrategy, IProgressMonitor monitor) {
438
        if (clazz == null){
439
            clazz = TaxonBase.class;
440
        }
441
        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
442
    }
443

    
444
    @Override
445
    @Autowired
446
    protected void setDao(ITaxonDao dao) {
447
        this.dao = dao;
448
    }
449

    
450
    @Override
451
    public Pager<TaxonBase> findTaxaByName(Class<? extends TaxonBase> clazz, String uninomial,	String infragenericEpithet, String specificEpithet,	String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
452
        Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
453

    
454
        List<TaxonBase> results = new ArrayList<TaxonBase>();
455
        if(numberOfResults > 0) { // no point checking again
456
            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
457
        }
458

    
459
        return new DefaultPagerImpl<TaxonBase>(pageNumber, numberOfResults, pageSize, results);
460
    }
461

    
462
    @Override
463
    public List<TaxonBase> listTaxaByName(Class<? extends TaxonBase> clazz, String uninomial,	String infragenericEpithet, String specificEpithet,	String infraspecificEpithet, Rank rank, Integer pageSize,Integer pageNumber) {
464
        Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
465

    
466
        List<TaxonBase> results = new ArrayList<TaxonBase>();
467
        if(numberOfResults > 0) { // no point checking again
468
            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber);
469
        }
470

    
471
        return results;
472
    }
473

    
474
    @Override
475
    public List<TaxonRelationship> listToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
476
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
477

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

    
485
    @Override
486
    public Pager<TaxonRelationship> pageToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
487
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
488

    
489
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
490
        if(numberOfResults > 0) { // no point checking again
491
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
492
        }
493
        return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
494
    }
495

    
496
    @Override
497
    public List<TaxonRelationship> listFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
498
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
499

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

    
507
    @Override
508
    public Pager<TaxonRelationship> pageFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
509
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
510

    
511
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
512
        if(numberOfResults > 0) { // no point checking again
513
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
514
        }
515
        return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
516
    }
517

    
518
    @Override
519
    public List<Taxon> listAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber,
520
            List<OrderHint> orderHints, List<String> propertyPaths){
521
        return pageAcceptedTaxaFor(synonymUuid, classificationUuid, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
522
    }
523

    
524
    @Override
525
    public Pager<Taxon> pageAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber,
526
            List<OrderHint> orderHints, List<String> propertyPaths){
527

    
528
        List<Taxon> list = new ArrayList<Taxon>();
529
        Long count = 0l;
530

    
531
        Synonym synonym = null;
532

    
533
        try {
534
            synonym = (Synonym) dao.load(synonymUuid);
535
        } catch (ClassCastException e){
536
            throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid + " is not a Synonmy");
537
        } catch (NullPointerException e){
538
            throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid);
539
        }
540

    
541
        Classification classificationFilter = null;
542
        if(classificationUuid != null){
543
            try {
544
            classificationFilter = classificationDao.load(classificationUuid);
545
            } catch (NullPointerException e){
546
                throw new EntityNotFoundException("No Classification entity found for " + classificationUuid);
547
            }
548
            if(classificationFilter == null){
549

    
550
            }
551
        }
552

    
553
        count = dao.countAcceptedTaxaFor(synonym, classificationFilter) ;
554
        if(count > (pageSize * pageNumber)){
555
            list = dao.listAcceptedTaxaFor(synonym, classificationFilter, pageSize, pageNumber, orderHints, propertyPaths);
556
        }
557

    
558
        return new DefaultPagerImpl<Taxon>(pageNumber, count.intValue(), pageSize, list);
559
    }
560

    
561

    
562
    @Override
563
    public Set<Taxon> listRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Integer maxDepth,
564
            Integer limit, Integer start, List<String> propertyPaths) {
565

    
566
        Set<Taxon> relatedTaxa = collectRelatedTaxa(taxon, includeRelationships, new HashSet<Taxon>(), maxDepth);
567
        relatedTaxa.remove(taxon);
568
        beanInitializer.initializeAll(relatedTaxa, propertyPaths);
569
        return relatedTaxa;
570
    }
571

    
572

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

    
585
        if(taxa.isEmpty()) {
586
            taxa.add(taxon);
587
        }
588

    
589
        if(includeRelationships.isEmpty()){
590
            return taxa;
591
        }
592

    
593
        if(maxDepth != null) {
594
            maxDepth--;
595
        }
596
        if(logger.isDebugEnabled()){
597
            logger.debug("collecting related taxa for " + taxon + " with maxDepth=" + maxDepth);
598
        }
599
        List<TaxonRelationship> taxonRelationships = dao.getTaxonRelationships(taxon, null, null, null, null, null, null);
600
        for (TaxonRelationship taxRel : taxonRelationships) {
601

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

    
633
    @Override
634
    public Pager<SynonymRelationship> getSynonyms(Taxon taxon,	SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
635
        Integer numberOfResults = dao.countSynonyms(taxon, type);
636

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

    
642
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
643
    }
644

    
645
    @Override
646
    public Pager<SynonymRelationship> getSynonyms(Synonym synonym,	SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
647
        Integer numberOfResults = dao.countSynonyms(synonym, type);
648

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

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

    
657
    @Override
658
    public List<List<Synonym>> getSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
659
         List<List<Synonym>> result = new ArrayList<List<Synonym>>();
660
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
661

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

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

    
671
        return result;
672

    
673
    }
674

    
675
    @Override
676
    public List<Synonym> getHomotypicSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
677
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
678
        return t.getHomotypicSynonymsByHomotypicGroup();
679
    }
680

    
681
    @Override
682
    public List<List<Synonym>> getHeterotypicSynonymyGroups(Taxon taxon, List<String> propertyPaths){
683
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
684
        List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
685
        List<List<Synonym>> heterotypicSynonymyGroups = new ArrayList<List<Synonym>>(homotypicalGroups.size());
686
        for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
687
            heterotypicSynonymyGroups.add(t.getSynonymsInGroup(homotypicalGroup));
688
        }
689
        return heterotypicSynonymyGroups;
690
    }
691

    
692
    @Override
693
    public List<UuidAndTitleCache<IdentifiableEntity>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator){
694

    
695
        List<UuidAndTitleCache<IdentifiableEntity>> results = new ArrayList<UuidAndTitleCache<IdentifiableEntity>>();
696

    
697

    
698
        if (configurator.isDoSynonyms() || configurator.isDoTaxa() || configurator.isDoNamesWithoutTaxa()){
699
        	results = dao.getTaxaByNameForEditor(configurator.isDoTaxa(), configurator.isDoSynonyms(), configurator.isDoNamesWithoutTaxa(), configurator.isDoMisappliedNames(),configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
700
        }
701
        if (configurator.isDoTaxaByCommonNames()) {
702
            //if(configurator.getPageSize() == null ){
703
                List<UuidAndTitleCache<IdentifiableEntity>> commonNameResults = dao.getTaxaByCommonNameForEditor(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
704
                if(commonNameResults != null){
705
                    results.addAll(commonNameResults);
706
                }
707
           // }
708
        }
709
        return results;
710
    }
711

    
712
    @Override
713
    public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
714

    
715
        List<IdentifiableEntity> results = new ArrayList<IdentifiableEntity>();
716
        int numberOfResults = 0; // overall number of results (as opposed to number of results per page)
717
        List<TaxonBase> taxa = null;
718

    
719
        // Taxa and synonyms
720
        long numberTaxaResults = 0L;
721

    
722

    
723
        List<String> propertyPath = new ArrayList<String>();
724
        if(configurator.getTaxonPropertyPath() != null){
725
            propertyPath.addAll(configurator.getTaxonPropertyPath());
726
        }
727

    
728

    
729
       if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa()){
730
            if(configurator.getPageSize() != null){ // no point counting if we need all anyway
731
                numberTaxaResults =
732
                    dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
733
                        configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(),
734
                        configurator.getNamedAreas());
735
            }
736

    
737
            if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
738
                taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
739
                    configurator.isDoMisappliedNames(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(),
740
                    configurator.getMatchMode(), configurator.getNamedAreas(),
741
                    configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
742
            }
743
       }
744

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

    
747
        if(taxa != null){
748
            results.addAll(taxa);
749
        }
750

    
751
        numberOfResults += numberTaxaResults;
752

    
753
        // Names without taxa
754
        if (configurator.isDoNamesWithoutTaxa()) {
755
            int numberNameResults = 0;
756

    
757
            List<? extends TaxonNameBase<?,?>> names =
758
                nameDao.findByName(configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
759
                        configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
760
            if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
761
            if (names.size() > 0) {
762
                for (TaxonNameBase<?,?> taxonName : names) {
763
                    if (taxonName.getTaxonBases().size() == 0) {
764
                        results.add(taxonName);
765
                        numberNameResults++;
766
                    }
767
                }
768
                if (logger.isDebugEnabled()) { logger.debug(numberNameResults + " matching name(s) without taxa found"); }
769
                numberOfResults += numberNameResults;
770
            }
771
        }
772

    
773
        // Taxa from common names
774

    
775
        if (configurator.isDoTaxaByCommonNames()) {
776
            taxa = new ArrayList<TaxonBase>();
777
            numberTaxaResults = 0;
778
            if(configurator.getPageSize() != null){// no point counting if we need all anyway
779
                numberTaxaResults = dao.countTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
780
            }
781
            if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){
782
                List<Taxon> commonNameResults = dao.getTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas(), configurator.getPageSize(), configurator.getPageNumber(), configurator.getTaxonPropertyPath());
783
                taxa.addAll(commonNameResults);
784
            }
785
            if(taxa != null){
786
                results.addAll(taxa);
787
            }
788
            numberOfResults += numberTaxaResults;
789

    
790
        }
791

    
792
       return new DefaultPagerImpl<IdentifiableEntity>
793
            (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
794
    }
795

    
796
    public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(){
797
        return dao.getUuidAndTitleCache();
798
    }
799

    
800
    @Override
801
    public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
802
        List<MediaRepresentation> medRep = new ArrayList<MediaRepresentation>();
803
        taxon = (Taxon)dao.load(taxon.getUuid());
804
        Set<TaxonDescription> descriptions = taxon.getDescriptions();
805
        for (TaxonDescription taxDesc: descriptions){
806
            Set<DescriptionElementBase> elements = taxDesc.getElements();
807
            for (DescriptionElementBase descElem: elements){
808
                for(Media media : descElem.getMedia()){
809

    
810
                    //find the best matching representation
811
                    medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
812

    
813
                }
814
            }
815
        }
816
        return medRep;
817
    }
818

    
819
    @Override
820
    public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
821
        return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
822
    }
823

    
824
    @Override
825
    public List<Media> listMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships,
826
            Boolean limitToGalleries, Boolean includeTaxonDescriptions, Boolean includeOccurrences,
827
            Boolean includeTaxonNameDescriptions, List<String> propertyPath) {
828

    
829
    //    logger.setLevel(Level.TRACE);
830
//        Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
831

    
832
        logger.trace("listMedia() - START");
833

    
834
        Set<Taxon> taxa = new HashSet<Taxon>();
835
        List<Media> taxonMedia = new ArrayList<Media>();
836
        List<Media> nonImageGalleryImages = new ArrayList<Media>();
837

    
838
        if (limitToGalleries == null) {
839
            limitToGalleries = false;
840
        }
841

    
842
        // --- resolve related taxa
843
        if (includeRelationships != null && ! includeRelationships.isEmpty()) {
844
            logger.trace("listMedia() - resolve related taxa");
845
            taxa = listRelatedTaxa(taxon, includeRelationships, null, null, null, null);
846
        }
847

    
848
        taxa.add((Taxon) dao.load(taxon.getUuid()));
849

    
850
        if(includeTaxonDescriptions != null && includeTaxonDescriptions){
851
            logger.trace("listMedia() - includeTaxonDescriptions");
852
            List<TaxonDescription> taxonDescriptions = new ArrayList<TaxonDescription>();
853
            // --- TaxonDescriptions
854
            for (Taxon t : taxa) {
855
                taxonDescriptions.addAll(descriptionService.listTaxonDescriptions(t, null, null, null, null, propertyPath));
856
            }
857
            for (TaxonDescription taxonDescription : taxonDescriptions) {
858
                if (!limitToGalleries || taxonDescription.isImageGallery()) {
859
                    for (DescriptionElementBase element : taxonDescription.getElements()) {
860
                        for (Media media : element.getMedia()) {
861
                            if(taxonDescription.isImageGallery()){
862
                                taxonMedia.add(media);
863
                            }
864
                            else{
865
                                nonImageGalleryImages.add(media);
866
                            }
867
                        }
868
                    }
869
                }
870
            }
871
            //put images from image gallery first (#3242)
872
            taxonMedia.addAll(nonImageGalleryImages);
873
        }
874

    
875

    
876
        if(includeOccurrences != null && includeOccurrences) {
877
            logger.trace("listMedia() - includeOccurrences");
878
            Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<SpecimenOrObservationBase>();
879
            // --- Specimens
880
            for (Taxon t : taxa) {
881
                specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
882
            }
883
            for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
884

    
885
//            	direct media removed from specimen #3597
886
//              taxonMedia.addAll(occurrence.getMedia());
887

    
888
                // SpecimenDescriptions
889
                Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
890
                for (DescriptionBase specimenDescription : specimenDescriptions) {
891
                    if (!limitToGalleries || specimenDescription.isImageGallery()) {
892
                        Set<DescriptionElementBase> elements = specimenDescription.getElements();
893
                        for (DescriptionElementBase element : elements) {
894
                            for (Media media : element.getMedia()) {
895
                                taxonMedia.add(media);
896
                            }
897
                        }
898
                    }
899
                }
900

    
901
                if (occurrence.isInstanceOf(DerivedUnit.class)) {
902
                    DerivedUnit derivedUnit = CdmBase.deproxy(occurrence, DerivedUnit.class);
903
                    // Collection
904
                    //TODO why may collections have media attached? #
905
                    if (derivedUnit.getCollection() != null){
906
                        taxonMedia.addAll(derivedUnit.getCollection().getMedia());
907
                    }
908
                }
909
                //media in hierarchy
910
                taxonMedia.addAll(occurrenceService.getMediainHierarchy(occurrence, null, null, propertyPath).getRecords());
911
            }
912
        }
913

    
914
        if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
915
            logger.trace("listMedia() - includeTaxonNameDescriptions");
916
            // --- TaxonNameDescription
917
            Set<TaxonNameDescription> nameDescriptions = new HashSet<TaxonNameDescription>();
918
            for (Taxon t : taxa) {
919
                nameDescriptions .addAll(t.getName().getDescriptions());
920
            }
921
            for(TaxonNameDescription nameDescription: nameDescriptions){
922
                if (!limitToGalleries || nameDescription.isImageGallery()) {
923
                    Set<DescriptionElementBase> elements = nameDescription.getElements();
924
                    for (DescriptionElementBase element : elements) {
925
                        for (Media media : element.getMedia()) {
926
                            taxonMedia.add(media);
927
                        }
928
                    }
929
                }
930
            }
931
        }
932

    
933

    
934
        logger.trace("listMedia() - initialize");
935
        beanInitializer.initializeAll(taxonMedia, propertyPath);
936

    
937
        logger.trace("listMedia() - END");
938

    
939
        return taxonMedia;
940
    }
941

    
942
    @Override
943
    public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
944
        return this.dao.listByIds(listOfIDs, null, null, null, null);
945
    }
946

    
947
    @Override
948
    public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
949
        return this.dao.findByUuid(uuid, null ,propertyPaths);
950
    }
951

    
952
    @Override
953
    public int countAllRelationships() {
954
        return this.dao.countAllRelationships();
955
    }
956

    
957
    @Override
958
    public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
959
        return this.dao.findIdenticalTaxonNames(propertyPath);
960
    }
961

    
962
    @Override
963
    @Transactional(readOnly = false)
964
    public DeleteResult deleteTaxon(UUID taxonUUID, TaxonDeletionConfigurator config, UUID classificationUuid)  {
965

    
966
    	if (config == null){
967
            config = new TaxonDeletionConfigurator();
968
        }
969
    	Taxon taxon = (Taxon)dao.load(taxonUUID);
970
    	taxon = (Taxon) HibernateProxyHelper.deproxy(taxon);
971
    	Classification classification = HibernateProxyHelper.deproxy(classificationDao.load(classificationUuid), Classification.class);
972
        DeleteResult result = isDeletable(taxon, config);
973

    
974
        if (result.isOk()){
975
            // --- DeleteSynonymRelations
976
            if (config.isDeleteSynonymRelations()){
977
                boolean removeSynonymNameFromHomotypicalGroup = false;
978
                // use tmp Set to avoid concurrent modification
979
                Set<SynonymRelationship> synRelsToDelete = new HashSet<SynonymRelationship>();
980
                synRelsToDelete.addAll(taxon.getSynonymRelations());
981
                for (SynonymRelationship synRel : synRelsToDelete){
982
                    Synonym synonym = synRel.getSynonym();
983
                    // taxon.removeSynonymRelation will set the accepted taxon and the synonym to NULL
984
                    // this will cause hibernate to delete the relationship since
985
                    // the SynonymRelationship field on both is annotated with removeOrphan
986
                    // so no further explicit deleting of the relationship should be done here
987
                    taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
988

    
989
                    // --- DeleteSynonymsIfPossible
990
                    if (config.isDeleteSynonymsIfPossible()){
991
                        //TODO which value
992
                        boolean newHomotypicGroupIfNeeded = true;
993
                        SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
994
                        deleteSynonym(synonym, taxon, synConfig);
995
                    }
996
                    // relationship will be deleted by hibernate automatically,
997
                    // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797
998

    
999
                }
1000
            }
1001

    
1002
            // --- DeleteTaxonRelationships
1003
            if (! config.isDeleteTaxonRelationships()){
1004
                if (taxon.getTaxonRelations().size() > 0){
1005
                    String message = "Taxon can't be deleted as it is related to another taxon. " +
1006
                            "Remove taxon from all relations to other taxa prior to deletion.";
1007

    
1008
                }
1009
            } else{
1010
                for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
1011
                    if (config.isDeleteMisappliedNamesAndInvalidDesignations()){
1012
                        if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){
1013
                            if (taxon.equals(taxRel.getToTaxon())){
1014

    
1015
                                this.deleteTaxon(taxRel.getFromTaxon().getUuid(), config, classificationUuid);
1016
                            }
1017
                        }
1018
                    }
1019
                    taxon.removeTaxonRelation(taxRel);
1020

    
1021
                }
1022
            }
1023

    
1024
            //    	TaxonDescription
1025
            if (config.isDeleteDescriptions()){
1026
                Set<TaxonDescription> descriptions = taxon.getDescriptions();
1027
                List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1028
                for (TaxonDescription desc: descriptions){
1029
                    //TODO use description delete configurator ?
1030
                    //FIXME check if description is ALWAYS deletable
1031
                    if (desc.getDescribedSpecimenOrObservation() != null){
1032
                        String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1033
                                " which also describes specimens or abservations";
1034
                    }
1035
                    removeDescriptions.add(desc);
1036

    
1037

    
1038
                }
1039
                for (TaxonDescription desc: removeDescriptions){
1040
                    taxon.removeDescription(desc);
1041
                    descriptionService.delete(desc);
1042
                }
1043
            }
1044

    
1045

    
1046
         if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null )){
1047
                //if (taxon.getTaxonNodes().size() > 0){
1048
                   // message = "Taxon can't be deleted as it is used in a classification node. Remove taxon from all classifications prior to deletion or define a classification where it should be deleted or adapt the taxon deletion configurator.";
1049
                   // throw new ReferencedObjectUndeletableException(message);
1050
                //}
1051
         }else{
1052
                if (taxon.getTaxonNodes().size() != 0){
1053
                    Set<TaxonNode> nodes = taxon.getTaxonNodes();
1054
                    Iterator<TaxonNode> iterator = nodes.iterator();
1055
                    TaxonNode node = null;
1056
                    boolean deleteChildren;
1057
                    if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1058
                        deleteChildren = true;
1059
                    }else {
1060
                        deleteChildren = false;
1061
                    }
1062
                    boolean success = true;
1063
                    if (!config.isDeleteInAllClassifications() && !(classification == null)){
1064
                        while (iterator.hasNext()){
1065
                            node = iterator.next();
1066
                            if (node.getClassification().equals(classification)){
1067
                                break;
1068
                            }
1069
                            node = null;
1070
                        }
1071
                        if (node != null){
1072
                            HibernateProxyHelper.deproxy(node, TaxonNode.class);
1073
                            success =taxon.removeTaxonNode(node, deleteChildren);
1074
                            nodeService.delete(node);
1075
                        } else {
1076
                        	result.setError();
1077
                        	result.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1078
                        }
1079
                    } else if (config.isDeleteInAllClassifications()){
1080
                        List<TaxonNode> nodesList = new ArrayList<TaxonNode>();
1081
                        nodesList.addAll(taxon.getTaxonNodes());
1082

    
1083
                            for (ITaxonTreeNode treeNode: nodesList){
1084
                                TaxonNode taxonNode = (TaxonNode) treeNode;
1085
                                if(!deleteChildren){
1086
                                    Object[] childNodes = taxonNode.getChildNodes().toArray();
1087
                                    for (Object childNode: childNodes){
1088
                                        TaxonNode childNodeCast = (TaxonNode) childNode;
1089
                                        taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1090
                                    }
1091

    
1092
                                    //taxon.removeTaxonNode(taxonNode);
1093
                                }
1094
                            }
1095
                        config.getTaxonNodeConfig().setDeleteTaxon(false);
1096
                        DeleteResult resultNodes = nodeService.deleteTaxonNodes(nodesList, config);
1097
                        if (!resultNodes.isOk()){
1098
                        	result.addExceptions(resultNodes.getExceptions());
1099
                        	result.setStatus(resultNodes.getStatus());
1100
                        } else {
1101
                            result.addUpdatedObjects(resultNodes.getUpdatedObjects());
1102
                        }
1103
                    }
1104
                    if (!success){
1105
                        result.setError();
1106
                        result.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1107
                    }
1108
                }
1109
            }
1110

    
1111

    
1112
             //PolytomousKey TODO
1113

    
1114

    
1115
            //TaxonNameBase
1116
            if (config.isDeleteNameIfPossible()){
1117

    
1118

    
1119
                    //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1120
                    TaxonNameBase name = (TaxonNameBase)HibernateProxyHelper.deproxy(taxon.getName());
1121
                    //check whether taxon will be deleted or not
1122

    
1123
                    if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){
1124

    
1125
                        //name.removeTaxonBase(taxon);
1126
                        //nameService.saveOrUpdate(name);
1127
                        taxon.setName(null);
1128
                        //dao.delete(taxon);
1129
                        DeleteResult nameResult = new DeleteResult();
1130

    
1131
                        //remove name if possible (and required)
1132
                        if (name != null && config.isDeleteNameIfPossible()){
1133
                        	nameResult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
1134
                        }
1135

    
1136
                        if (nameResult.isError() || nameResult.isAbort()){
1137
                        	//result.setError();
1138
                        	result.addRelatedObject(name);
1139
                        	result.addExceptions(nameResult.getExceptions());
1140
                        }
1141

    
1142
                    }
1143

    
1144
            }else {
1145
                taxon.setName(null);
1146
            }
1147

    
1148

    
1149
//        	TaxonDescription
1150
           /* Set<TaxonDescription> descriptions = taxon.getDescriptions();
1151

    
1152
            for (TaxonDescription desc: descriptions){
1153
                if (config.isDeleteDescriptions()){
1154
                    //TODO use description delete configurator ?
1155
                    //FIXME check if description is ALWAYS deletable
1156
                    taxon.removeDescription(desc);
1157
                    descriptionService.delete(desc);
1158
                }else{
1159
                    if (desc.getDescribedSpecimenOrObservations().size()>0){
1160
                        String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1161
                                " which also describes specimens or observations";
1162
                            throw new ReferencedObjectUndeletableException(message);
1163
    }
1164
                    }
1165
                }*/
1166

    
1167
            if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0)  ){
1168
            	try{
1169
            		UUID uuid = dao.delete(taxon);
1170

    
1171
            	}catch(Exception e){
1172
            		result.addException(e);
1173
            		result.setError();
1174

    
1175
            	}
1176
            } else {
1177
            	result.setError();
1178
            	result.addException(new Exception("The Taxon can't be deleted."));
1179

    
1180
            }
1181
        }
1182
//        }else {
1183
//        	List<Exception> exceptions = new ArrayList<Exception>();
1184
//        	for (String message: referencedObjects){
1185
//        		ReferencedObjectUndeletableException exception = new ReferencedObjectUndeletableException(message);
1186
//        		exceptions.add(exception);
1187
//        	}
1188
//        	result.addExceptions(exceptions);
1189
//        	result.setError();
1190
//
1191
//        }
1192
        return result;
1193

    
1194
    }
1195

    
1196
    private String checkForReferences(Taxon taxon){
1197
        Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1198
        for (CdmBase referencingObject : referencingObjects){
1199
            //IIdentificationKeys (Media, Polytomous, MultiAccess)
1200
            if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1201
                String message = "Taxon" + taxon.getTitleCache() + "can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
1202

    
1203
                return message;
1204
            }
1205

    
1206

    
1207
           /* //PolytomousKeyNode
1208
            if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1209
                String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1210
                return message;
1211
            }*/
1212

    
1213
            //TaxonInteraction
1214
            if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1215
                String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1216
                return message;
1217
            }
1218

    
1219
          //TaxonInteraction
1220
            if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1221
                String message = "Taxon can't be deleted as it is used in a determination event";
1222
                return message;
1223
            }
1224

    
1225
        }
1226

    
1227
        referencingObjects = null;
1228
        return null;
1229
    }
1230

    
1231
    private boolean checkForPolytomousKeys(Taxon taxon){
1232
        boolean result = false;
1233
        List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1234
        if (!list.isEmpty()) {
1235
            result = true;
1236
        }
1237
        return result;
1238
    }
1239

    
1240
    @Override
1241
    @Transactional(readOnly = false)
1242
    public DeleteResult delete(UUID synUUID){
1243
    	DeleteResult result = new DeleteResult();
1244
    	Synonym syn = (Synonym)dao.load(synUUID);
1245

    
1246
        return this.deleteSynonym(syn, null);
1247
    }
1248

    
1249

    
1250
    @Override
1251
    @Transactional(readOnly = false)
1252
    public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1253
        return deleteSynonym(synonym, null, config);
1254

    
1255
    }
1256

    
1257
    @Override
1258
    @Transactional(readOnly = false)
1259
    public DeleteResult deleteSynonym(UUID synonymUuid, SynonymDeletionConfigurator config) {
1260
        return deleteSynonym((Synonym)dao.load(synonymUuid), config);
1261

    
1262
    }
1263

    
1264
    @Transactional(readOnly = false)
1265
    @Override
1266
    public DeleteResult deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1267
        DeleteResult result = new DeleteResult();
1268
    	if (synonym == null){
1269
    		result.setAbort();
1270
    		return result;
1271
        }
1272

    
1273
        if (config == null){
1274
            config = new SynonymDeletionConfigurator();
1275
        }
1276
        result = isDeletable(synonym, config);
1277

    
1278

    
1279
        if (result.isOk()){
1280

    
1281
            synonym = HibernateProxyHelper.deproxy(dao.merge(synonym), Synonym.class);
1282

    
1283
            //remove synonymRelationship
1284
            Set<Taxon> taxonSet = new HashSet<Taxon>();
1285
            if (taxon != null){
1286
                taxonSet.add(taxon);
1287
            }else{
1288
                taxonSet.addAll(synonym.getAcceptedTaxa());
1289
            }
1290
            for (Taxon relatedTaxon : taxonSet){
1291
            	relatedTaxon = HibernateProxyHelper.deproxy(relatedTaxon, Taxon.class);
1292
                relatedTaxon.removeSynonym(synonym, false);
1293
                this.saveOrUpdate(relatedTaxon);
1294
            }
1295
            this.saveOrUpdate(synonym);
1296

    
1297
            //TODO remove name from homotypical group?
1298

    
1299
            //remove synonym (if necessary)
1300

    
1301
            result.addUpdatedObject(taxon);
1302
            if (synonym.getSynonymRelations().isEmpty()){
1303
                TaxonNameBase<?,?> name = synonym.getName();
1304
                synonym.setName(null);
1305
                dao.delete(synonym);
1306

    
1307
                //remove name if possible (and required)
1308
                if (name != null && config.isDeleteNameIfPossible()){
1309

    
1310
                        DeleteResult nameDeleteresult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
1311
                        if (nameDeleteresult.isAbort()){
1312
                        	result.addExceptions(nameDeleteresult.getExceptions());
1313
                        	result.addRelatedObject(name);
1314
                        }
1315

    
1316
                }
1317

    
1318
            }else {
1319
            	result.setError();
1320
            	result.addException(new ReferencedObjectUndeletableException("Synonym can not be deleted it is used in a synonymRelationship."));
1321
                return result;
1322
            }
1323

    
1324

    
1325
        }
1326
        return result;
1327
//        else{
1328
//        	List<Exception> exceptions = new ArrayList<Exception>();
1329
//        	for (String message :messages){
1330
//        		exceptions.add(new ReferencedObjectUndeletableException(message));
1331
//        	}
1332
//        	result.setError();
1333
//        	result.addExceptions(exceptions);
1334
//            return result;
1335
//        }
1336

    
1337

    
1338
    }
1339

    
1340
    @Override
1341
    public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1342

    
1343
        return this.dao.findIdenticalNamesNew(propertyPath);
1344
    }
1345

    
1346
    @Override
1347
    public String getPhylumName(TaxonNameBase name){
1348
        return this.dao.getPhylumName(name);
1349
    }
1350

    
1351
    @Override
1352
    public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1353
        return dao.deleteSynonymRelationships(syn, taxon);
1354
    }
1355

    
1356
    @Override
1357
    public long deleteSynonymRelationships(Synonym syn) {
1358
        return dao.deleteSynonymRelationships(syn, null);
1359
    }
1360

    
1361
    @Override
1362
    public List<SynonymRelationship> listSynonymRelationships(
1363
            TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1364
            List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1365
        Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1366

    
1367
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1368
        if(numberOfResults > 0) { // no point checking again
1369
            results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1370
        }
1371
        return results;
1372
    }
1373

    
1374
    @Override
1375
    public Taxon findBestMatchingTaxon(String taxonName) {
1376
        MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1377
        config.setTaxonNameTitle(taxonName);
1378
        return findBestMatchingTaxon(config);
1379
    }
1380

    
1381
    @Override
1382
    public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1383

    
1384
        Taxon bestCandidate = null;
1385
        try{
1386
            // 1. search for acceptet taxa
1387
            List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1388
            boolean bestCandidateMatchesSecUuid = false;
1389
            boolean bestCandidateIsInClassification = false;
1390
            int countEqualCandidates = 0;
1391
            for(TaxonBase taxonBaseCandidate : taxonList){
1392
                if(taxonBaseCandidate instanceof Taxon){
1393
                    Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1394
                    boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1395
                    if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1396
                        continue;
1397
                    }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1398
                        bestCandidate = newCanditate;
1399
                        countEqualCandidates = 1;
1400
                        bestCandidateMatchesSecUuid = true;
1401
                        continue;
1402
                    }
1403

    
1404
                    boolean newCandidateInClassification = isInClassification(newCanditate, config);
1405
                    if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1406
                        continue;
1407
                    }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1408
                        bestCandidate = newCanditate;
1409
                        countEqualCandidates = 1;
1410
                        bestCandidateIsInClassification = true;
1411
                        continue;
1412
                    }
1413
                    if (bestCandidate == null){
1414
                        bestCandidate = newCanditate;
1415
                        countEqualCandidates = 1;
1416
                        continue;
1417
                    }
1418

    
1419
                }else{  //not Taxon.class
1420
                    continue;
1421
                }
1422
                countEqualCandidates++;
1423

    
1424
            }
1425
            if (bestCandidate != null){
1426
                if(countEqualCandidates > 1){
1427
                    logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1428
                    return bestCandidate;
1429
                } else {
1430
                    logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1431
                    return bestCandidate;
1432
                }
1433
            }
1434

    
1435

    
1436
            // 2. search for synonyms
1437
            if (config.isIncludeSynonyms()){
1438
                List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1439
                for(TaxonBase taxonBase : synonymList){
1440
                    if(taxonBase instanceof Synonym){
1441
                        Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1442
                        Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1443
                        if(!acceptetdCandidates.isEmpty()){
1444
                            bestCandidate = acceptetdCandidates.iterator().next();
1445
                            if(acceptetdCandidates.size() == 1){
1446
                                logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1447
                                return bestCandidate;
1448
                            } else {
1449
                                logger.info("using accepted Taxon " +  bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1450
                                return bestCandidate;
1451
                            }
1452
                            //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1453
                        }
1454
                    }
1455
                }
1456
            }
1457

    
1458
        } catch (Exception e){
1459
            logger.error(e);
1460
            e.printStackTrace();
1461
        }
1462

    
1463
        return bestCandidate;
1464
    }
1465

    
1466
    private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1467
        UUID configClassificationUuid = config.getClassificationUuid();
1468
        if (configClassificationUuid == null){
1469
            return false;
1470
        }
1471
        for (TaxonNode node : taxon.getTaxonNodes()){
1472
            UUID classUuid = node.getClassification().getUuid();
1473
            if (configClassificationUuid.equals(classUuid)){
1474
                return true;
1475
            }
1476
        }
1477
        return false;
1478
    }
1479

    
1480
    private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1481
        UUID configSecUuid = config.getSecUuid();
1482
        if (configSecUuid == null){
1483
            return false;
1484
        }
1485
        UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1486
        return configSecUuid.equals(taxonSecUuid);
1487
    }
1488

    
1489
    @Override
1490
    public Synonym findBestMatchingSynonym(String taxonName) {
1491
        List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1492
        if(! synonymList.isEmpty()){
1493
            Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1494
            if(synonymList.size() == 1){
1495
                logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1496
                return result;
1497
            } else {
1498
                logger.info("Several matching synonyms found. Using first: " +  result.getTitleCache());
1499
                return result;
1500
            }
1501
        }
1502
        return null;
1503
    }
1504

    
1505
    @Override
1506
    @Transactional(readOnly = false)
1507
    public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation,
1508
            Taxon newTaxon,
1509
            boolean moveHomotypicGroup,
1510
            SynonymRelationshipType newSynonymRelationshipType,
1511
            Reference reference,
1512
            String referenceDetail,
1513
            boolean keepReference) throws HomotypicalGroupChangeException {
1514

    
1515
        Synonym synonym = (Synonym) dao.load(oldSynonymRelation.getSynonym().getUuid());
1516
        Taxon fromTaxon = (Taxon) dao.load(oldSynonymRelation.getAcceptedTaxon().getUuid());
1517
        //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1518
        TaxonNameBase<?,?> synonymName = synonym.getName();
1519
        TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1520
        //set default relationship type
1521
        if (newSynonymRelationshipType == null){
1522
            newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1523
        }
1524
        boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1525

    
1526
        HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1527
        int hgSize = homotypicGroup.getTypifiedNames().size();
1528
        boolean isSingleInGroup = !(hgSize > 1);
1529

    
1530
        if (! isSingleInGroup){
1531
            boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1532
            boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1533
            if (isHomotypicToAccepted){
1534
                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.";
1535
                String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1536
                message = String.format(message, homotypicRelatives);
1537
                throw new HomotypicalGroupChangeException(message);
1538
            }
1539
            if (! moveHomotypicGroup){
1540
                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.";
1541
                throw new HomotypicalGroupChangeException(message);
1542
            }
1543
        }else{
1544
            moveHomotypicGroup = true;  //single synonym always allows to moveCompleteGroup
1545
        }
1546
//        Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1547

    
1548
        SynonymRelationship result = null;
1549
        //move all synonyms to new taxon
1550
        List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1551
        for (Synonym syn: homotypicSynonyms){
1552
            Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1553
            for (SynonymRelationship synRelation : synRelations){
1554
                if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1555
                    Reference<?> newReference = reference;
1556
                    if (newReference == null && keepReference){
1557
                        newReference = synRelation.getCitation();
1558
                    }
1559
                    String newRefDetail = referenceDetail;
1560
                    if (newRefDetail == null && keepReference){
1561
                        newRefDetail = synRelation.getCitationMicroReference();
1562
                    }
1563
                    newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
1564
                    fromTaxon = HibernateProxyHelper.deproxy(fromTaxon, Taxon.class);
1565
                    SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1566
                    fromTaxon.removeSynonymRelation(synRelation, false);
1567
//
1568
                    //change homotypic group of synonym if relType is 'homotypic'
1569
//                	if (newRelTypeIsHomotypic){
1570
//                		newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1571
//                	}
1572
                    //set result
1573
                    if (synRelation.equals(oldSynonymRelation)){
1574
                        result = newSynRelation;
1575
                    }
1576
                }
1577
            }
1578

    
1579
        }
1580
        saveOrUpdate(fromTaxon);
1581
        saveOrUpdate(newTaxon);
1582
        //Assert that there is a result
1583
        if (result == null){
1584
            String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1585
            throw new IllegalStateException(message);
1586
        }
1587
        return result;
1588
    }
1589

    
1590
    @Override
1591
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1592
        return dao.getUuidAndTitleCacheTaxon();
1593
    }
1594

    
1595
    @Override
1596
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1597
        return dao.getUuidAndTitleCacheSynonym();
1598
    }
1599

    
1600
    @Override
1601
    public Pager<SearchResult<TaxonBase>> findByFullText(
1602
            Class<? extends TaxonBase> clazz, String queryString,
1603
            Classification classification, List<Language> languages,
1604
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1605

    
1606

    
1607
        LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1608

    
1609
        // --- execute search
1610
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1611

    
1612
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1613
        idFieldMap.put(CdmBaseType.TAXON, "id");
1614

    
1615
        // ---  initialize taxa, thighlight matches ....
1616
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1617
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1618
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1619

    
1620
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1621
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1622
    }
1623

    
1624
    @Override
1625
    public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
1626
            Classification classification,
1627
            Integer pageSize, Integer pageNumber,
1628
            List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1629

    
1630
        LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1631

    
1632
        // --- execute search
1633
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1634

    
1635
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1636
        idFieldMap.put(CdmBaseType.TAXON, "id");
1637

    
1638
        // ---  initialize taxa, thighlight matches ....
1639
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1640
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1641
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1642

    
1643
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1644
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1645
    }
1646

    
1647
    /**
1648
     * @param clazz
1649
     * @param queryString
1650
     * @param classification
1651
     * @param languages
1652
     * @param highlightFragments
1653
     * @param sortFields TODO
1654
     * @param directorySelectClass
1655
     * @return
1656
     */
1657
    protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1658
            boolean highlightFragments, SortField[] sortFields) {
1659
        BooleanQuery finalQuery = new BooleanQuery();
1660
        BooleanQuery textQuery = new BooleanQuery();
1661

    
1662
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1663
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1664

    
1665
        if(sortFields == null){
1666
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
1667
        }
1668
        luceneSearch.setSortFields(sortFields);
1669

    
1670
        // ---- search criteria
1671
        luceneSearch.setCdmTypRestriction(clazz);
1672

    
1673
        if(!queryString.isEmpty() && !queryString.equals("*") && !queryString.equals("?") ) {
1674
            textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1675
            textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1676
        }
1677

    
1678
        if(textQuery.getClauses().length > 0) {
1679
            finalQuery.add(textQuery, Occur.MUST);
1680
        }
1681

    
1682

    
1683
        if(classification != null){
1684
            finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1685
        }
1686
        luceneSearch.setQuery(finalQuery);
1687

    
1688
        if(highlightFragments){
1689
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1690
        }
1691
        return luceneSearch;
1692
    }
1693

    
1694
    /**
1695
     * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1696
     * the BlockJoinQuery could be used. The latter might be more memory save but has the
1697
     * drawback of requiring to do the join an indexing time.
1698
     * see  http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1699
     *
1700
     * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1701
     * <ul>
1702
     * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1703
     * <li>inverse: {@link Direction.relatedFrom}:  TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1704
     * <ul>
1705
     * @param queryString
1706
     * @param classification
1707
     * @param languages
1708
     * @param highlightFragments
1709
     * @param sortFields TODO
1710
     *
1711
     * @return
1712
     * @throws IOException
1713
     */
1714
    protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1715
            boolean highlightFragments, SortField[] sortFields) throws IOException {
1716

    
1717
        String fromField;
1718
        String queryTermField;
1719
        String toField = "id"; // TaxonBase.uuid
1720

    
1721
        if(edge.isBidirectional()){
1722
            throw new RuntimeException("Bidirectional joining not supported!");
1723
        }
1724
        if(edge.isEvers()){
1725
            fromField = "relatedFrom.id";
1726
            queryTermField = "relatedFrom.titleCache";
1727
        } else if(edge.isInvers()) {
1728
            fromField = "relatedTo.id";
1729
            queryTermField = "relatedTo.titleCache";
1730
        } else {
1731
            throw new RuntimeException("Invalid direction: " + edge.getDirections());
1732
        }
1733

    
1734
        BooleanQuery finalQuery = new BooleanQuery();
1735

    
1736
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1737
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1738

    
1739
        BooleanQuery joinFromQuery = new BooleanQuery();
1740
        joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1741
        joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1742
        Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1743

    
1744
        if(sortFields == null){
1745
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
1746
        }
1747
        luceneSearch.setSortFields(sortFields);
1748

    
1749
        finalQuery.add(joinQuery, Occur.MUST);
1750

    
1751
        if(classification != null){
1752
            finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1753
        }
1754
        luceneSearch.setQuery(finalQuery);
1755

    
1756
        if(highlightFragments){
1757
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1758
        }
1759
        return luceneSearch;
1760
    }
1761

    
1762
    @Override
1763
    public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1764
            EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1765
            Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
1766
            boolean highlightFragments, Integer pageSize,
1767
            Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1768
            throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1769

    
1770
        // FIXME: allow taxonomic ordering
1771
        //  hql equivalent:  order by t.name.genusOrUninomial, case when t.name.specificEpithet like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache";
1772
        // this require building a special sort column by a special classBridge
1773
        if(highlightFragments){
1774
            logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1775
                    "currently not fully supported by this method and thus " +
1776
                    "may not work with common names and misapplied names.");
1777
        }
1778

    
1779
        // convert sets to lists
1780
        List<NamedArea> namedAreaList = null;
1781
        List<PresenceAbsenceTerm>distributionStatusList = null;
1782
        if(namedAreas != null){
1783
            namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1784
            namedAreaList.addAll(namedAreas);
1785
        }
1786
        if(distributionStatus != null){
1787
            distributionStatusList = new ArrayList<PresenceAbsenceTerm>(distributionStatus.size());
1788
            distributionStatusList.addAll(distributionStatus);
1789
        }
1790

    
1791
        // set default if parameter is null
1792
        if(searchModes == null){
1793
            searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1794
        }
1795

    
1796
        // set sort order and thus override any sort orders which may have been
1797
        // defindes by prepare*Search methods
1798
        if(orderHints == null){
1799
            orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER;
1800
        }
1801
        SortField[] sortFields = new SortField[orderHints.size()];
1802
        int i = 0;
1803
        for(OrderHint oh : orderHints){
1804
            sortFields[i++] = oh.toSortField();
1805
        }
1806
//        SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1807
//        SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1808

    
1809

    
1810
        boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1811

    
1812
        List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1813
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1814

    
1815
        /*
1816
          ======== filtering by distribution , HOWTO ========
1817

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

    
1823

    
1824
          3. how does it work in spatial?
1825
          see
1826
           - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1827
           - http://www.infoq.com/articles/LuceneSpatialSupport
1828
           - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1829
          ------------------------------------------------------------------------
1830

    
1831
          filter strategies:
1832
          A) use a separate distribution filter per index sub-query/search:
1833
           - byTaxonSyonym (query TaxaonBase):
1834
               use a join area filter (Distribution -> TaxonBase)
1835
           - byCommonName (query DescriptionElementBase): use an area filter on
1836
               DescriptionElementBase !!! PROBLEM !!!
1837
               This cannot work since the distributions are different entities than the
1838
               common names and thus these are different lucene documents.
1839
           - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1840
               use a join area filter (Distribution -> TaxonBase)
1841

    
1842
          B) use a common distribution filter for all index sub-query/searches:
1843
           - use a common join area filter (Distribution -> TaxonBase)
1844
           - also implement the byCommonName as join query (CommonName -> TaxonBase)
1845
           PROBLEM in this case: we are losing the fragment highlighting for the
1846
           common names, since the returned documents are always TaxonBases
1847
        */
1848

    
1849
        /* The QueryFactory for creating filter queries on Distributions should
1850
         * The query factory used for the common names query cannot be reused
1851
         * for this case, since we want to only record the text fields which are
1852
         * actually used in the primary query
1853
         */
1854
        QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1855

    
1856
        BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1857

    
1858

    
1859
        // search for taxa or synonyms
1860
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1861
            Class taxonBaseSubclass = TaxonBase.class;
1862
            if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1863
                taxonBaseSubclass = Taxon.class;
1864
            } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1865
                taxonBaseSubclass = Synonym.class;
1866
            }
1867
            luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1868
            idFieldMap.put(CdmBaseType.TAXON, "id");
1869
            /* A) does not work!!!!
1870
            if(addDistributionFilter){
1871
                // in this case we need a filter which uses a join query
1872
                // to get the TaxonBase documents for the DescriptionElementBase documents
1873
                // which are matching the areas in question
1874
                Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1875
                        namedAreaList,
1876
                        distributionStatusList,
1877
                        distributionFilterQueryFactory
1878
                        );
1879
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1880
            }
1881
            */
1882
            if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1883
                // add additional area filter for synonyms
1884
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1885
                String toField = "accTaxon.id"; // id in TaxonBase index
1886

    
1887
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1888

    
1889
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1890
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1891

    
1892
            }
1893
        }
1894

    
1895
        // search by CommonTaxonName
1896
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1897
            // B)
1898
            QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1899
            Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1900
                    "inDescription.taxon.id",
1901
                    "id",
1902
                    QueryFactory.addTypeRestriction(
1903
                                createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
1904
                                , CommonTaxonName.class
1905
                                ),
1906
                    CommonTaxonName.class);
1907
            logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
1908
            LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
1909
            byCommonNameSearch.setCdmTypRestriction(Taxon.class);
1910
            byCommonNameSearch.setQuery(byCommonNameJoinQuery);
1911
            byCommonNameSearch.setSortFields(sortFields);
1912
            idFieldMap.put(CdmBaseType.TAXON, "id");
1913

    
1914
            luceneSearches.add(byCommonNameSearch);
1915

    
1916
            /* A) does not work!!!!
1917
            luceneSearches.add(
1918
                    prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1919
                            queryString, classification, null, languages, highlightFragments)
1920
                        );
1921
            idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1922
            if(addDistributionFilter){
1923
                // in this case we are able to use DescriptionElementBase documents
1924
                // which are matching the areas in question directly
1925
                BooleanQuery byDistributionQuery = createByDistributionQuery(
1926
                        namedAreaList,
1927
                        distributionStatusList,
1928
                        distributionFilterQueryFactory
1929
                        );
1930
                multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1931
            } */
1932
        }
1933

    
1934
        // search by misapplied names
1935
        if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
1936
            // NOTE:
1937
            // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1938
            // which allows doing query time joins
1939
            // finds the misapplied name (Taxon B) which is an misapplication for
1940
            // a related Taxon A.
1941
            //
1942
            luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
1943
                    new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
1944
                    queryString, classification, languages, highlightFragments, sortFields));
1945
            idFieldMap.put(CdmBaseType.TAXON, "id");
1946

    
1947
            if(addDistributionFilter){
1948
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1949

    
1950
                /*
1951
                 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
1952
                 * Maybe this is a but in java itself java.
1953
                 *
1954
                 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1955
                 * directly:
1956
                 *
1957
                 *    String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1958
                 *
1959
                 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1960
                 * will execute as expected:
1961
                 *
1962
                 *    String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1963
                 *    String toField = "relation." + misappliedNameForUuid +".to.id";
1964
                 *
1965
                 * Comparing both strings by the String.equals method returns true, so both String are identical.
1966
                 *
1967
                 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1968
                 * dependent from a specific jvm (openjdk6  6b27-1.12.6-1ubuntu0.13.04.2, openjdk7 7u25-2.3.10-1ubuntu0.13.04.2,  oracle jdk1.7.0_25 tested)
1969
                 * The bug is persistent after a reboot of the development computer.
1970
                 */
1971
//                String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1972
//                String toField = "relation." + misappliedNameForUuid +".to.id";
1973
                String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1974
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1975
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1976

    
1977
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1978
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1979
                QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery);
1980

    
1981
//                debug code for bug described above
1982
                DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1983
//                System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1984

    
1985
                multiIndexByAreaFilter.add(filter, Occur.SHOULD);
1986
            }
1987
        }
1988

    
1989
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
1990
                luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
1991

    
1992

    
1993
        if(addDistributionFilter){
1994

    
1995
            // B)
1996
            // in this case we need a filter which uses a join query
1997
            // to get the TaxonBase documents for the DescriptionElementBase documents
1998
            // which are matching the areas in question
1999
            //
2000
            // for toTaxa, doByCommonName
2001
            Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2002
                    namedAreaList,
2003
                    distributionStatusList,
2004
                    distributionFilterQueryFactory
2005
                    );
2006
            multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
2007
        }
2008

    
2009
        if (addDistributionFilter){
2010
            multiSearch.setFilter(multiIndexByAreaFilter);
2011
        }
2012

    
2013

    
2014
        // --- execute search
2015
        TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2016

    
2017
        // --- initialize taxa, highlight matches ....
2018
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2019

    
2020

    
2021
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2022
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2023

    
2024
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2025
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2026
    }
2027

    
2028
    /**
2029
     * @param namedAreaList at least one area must be in the list
2030
     * @param distributionStatusList optional
2031
     * @return
2032
     * @throws IOException
2033
     */
2034
    protected Query createByDistributionJoinQuery(
2035
            List<NamedArea> namedAreaList,
2036
            List<PresenceAbsenceTerm> distributionStatusList,
2037
            QueryFactory queryFactory
2038
            ) throws IOException {
2039

    
2040
        String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2041
        String toField = "id"; // id in TaxonBase index
2042

    
2043
        BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2044

    
2045
        Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2046

    
2047
        return taxonAreaJoinQuery;
2048
    }
2049

    
2050
    /**
2051
     * @param namedAreaList
2052
     * @param distributionStatusList
2053
     * @param queryFactory
2054
     * @return
2055
     */
2056
    private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2057
            List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2058
        BooleanQuery areaQuery = new BooleanQuery();
2059
        // area field from Distribution
2060
        areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2061

    
2062
        // status field from Distribution
2063
        if(distributionStatusList != null && distributionStatusList.size() > 0){
2064
            areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2065
        }
2066

    
2067
        logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2068
        return areaQuery;
2069
    }
2070

    
2071
    /**
2072
     * This method has been primarily created for testing the area join query but might
2073
     * also be useful in other situations
2074
     *
2075
     * @param namedAreaList
2076
     * @param distributionStatusList
2077
     * @param classification
2078
     * @param highlightFragments
2079
     * @return
2080
     * @throws IOException
2081
     */
2082
    protected LuceneSearch prepareByDistributionSearch(
2083
            List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
2084
            Classification classification) throws IOException {
2085

    
2086
        BooleanQuery finalQuery = new BooleanQuery();
2087

    
2088
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2089

    
2090
        // FIXME is this query factory using the wrong type?
2091
        QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2092

    
2093
        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
2094
        luceneSearch.setSortFields(sortFields);
2095

    
2096

    
2097
        Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory);
2098

    
2099
        finalQuery.add(byAreaQuery, Occur.MUST);
2100

    
2101
        if(classification != null){
2102
            finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2103
        }
2104

    
2105
        logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2106
        luceneSearch.setQuery(finalQuery);
2107

    
2108
        return luceneSearch;
2109
    }
2110

    
2111
    @Override
2112
    public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2113
            Class<? extends DescriptionElementBase> clazz, String queryString,
2114
            Classification classification, List<Feature> features, List<Language> languages,
2115
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2116

    
2117

    
2118
        LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2119

    
2120
        // --- execute search
2121
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2122

    
2123
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2124
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2125

    
2126
        // --- initialize taxa, highlight matches ....
2127
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2128
        @SuppressWarnings("rawtypes")
2129
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2130
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2131

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

    
2135
    }
2136

    
2137

    
2138
    @Override
2139
    public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2140
            Classification classification, List<Language> languages, boolean highlightFragments,
2141
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2142

    
2143
        LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2144
        LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2145

    
2146
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2147

    
2148
        // --- execute search
2149
        TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2150

    
2151
        // --- initialize taxa, highlight matches ....
2152
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2153

    
2154
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2155
        idFieldMap.put(CdmBaseType.TAXON, "id");
2156
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2157

    
2158
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2159
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2160

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

    
2164
    }
2165

    
2166

    
2167
    /**
2168
     * @param clazz
2169
     * @param queryString
2170
     * @param classification
2171
     * @param features
2172
     * @param languages
2173
     * @param highlightFragments
2174
     * @param directorySelectClass
2175
     * @return
2176
     */
2177
    protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2178
            String queryString, Classification classification, List<Feature> features,
2179
            List<Language> languages, boolean highlightFragments) {
2180

    
2181
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2182
        QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2183

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

    
2186
        BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2187
                languages, descriptionElementQueryFactory);
2188

    
2189
        luceneSearch.setSortFields(sortFields);
2190
        luceneSearch.setCdmTypRestriction(clazz);
2191
        luceneSearch.setQuery(finalQuery);
2192
        if(highlightFragments){
2193
            luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2194
        }
2195

    
2196
        return luceneSearch;
2197
    }
2198

    
2199
    /**
2200
     * @param queryString
2201
     * @param classification
2202
     * @param features
2203
     * @param languages
2204
     * @param descriptionElementQueryFactory
2205
     * @return
2206
     */
2207
    private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2208
            List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2209
        BooleanQuery finalQuery = new BooleanQuery();
2210
        BooleanQuery textQuery = new BooleanQuery();
2211
        textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2212

    
2213
        // common name
2214
        Query nameQuery;
2215
        if(languages == null || languages.size() == 0){
2216
            nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString);
2217
        } else {
2218
            nameQuery = new BooleanQuery();
2219
            BooleanQuery languageSubQuery = new BooleanQuery();
2220
            for(Language lang : languages){
2221
                languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid",  lang.getUuid().toString(), false), Occur.SHOULD);
2222
            }
2223
            ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2224
            ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
2225
        }
2226
        textQuery.add(nameQuery, Occur.SHOULD);
2227

    
2228

    
2229
        // text field from TextData
2230
        textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2231

    
2232
        // --- TermBase fields - by representation ----
2233
        // state field from CategoricalData
2234
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2235

    
2236
        // state field from CategoricalData
2237
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2238

    
2239
        // area field from Distribution
2240
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2241

    
2242
        // status field from Distribution
2243
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2244

    
2245
        finalQuery.add(textQuery, Occur.MUST);
2246
        // --- classification ----
2247

    
2248
        if(classification != null){
2249
            finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2250
        }
2251

    
2252
        // --- IdentifieableEntity fields - by uuid
2253
        if(features != null && features.size() > 0 ){
2254
            finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2255
        }
2256

    
2257
        // the description must be associated with a taxon
2258
        finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2259

    
2260
        logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2261
        return finalQuery;
2262
    }
2263

    
2264
    /**
2265
     * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2266
     * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2267
     * This method is a convenient means to retrieve a Lucene query string for such the fields.
2268
     *
2269
     * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2270
     * or {@link MultilanguageTextFieldBridge }
2271
     * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2272
     * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2273
     * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2274
     *
2275
     * TODO move to utiliy class !!!!!!!!
2276
     */
2277
    private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2278

    
2279
        if(stringBuilder == null){
2280
            stringBuilder = new StringBuilder();
2281
        }
2282
        if(languages == null || languages.size() == 0){
2283
            stringBuilder.append(name + ".ALL:(%1$s) ");
2284
        } else {
2285
            for(Language lang : languages){
2286
                stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2287
            }
2288
        }
2289
        return stringBuilder;
2290
    }
2291

    
2292
    @Override
2293
    public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2294
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2295
        List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2296

    
2297
        HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2298

    
2299

    
2300
        UUID nameUuid= taxon.getName().getUuid();
2301
        ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2302
        String epithetOfTaxon = null;
2303
        String infragenericEpithetOfTaxon = null;
2304
        String infraspecificEpithetOfTaxon = null;
2305
        if (taxonName.isSpecies()){
2306
             epithetOfTaxon= taxonName.getSpecificEpithet();
2307
        } else if (taxonName.isInfraGeneric()){
2308
            infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2309
        } else if (taxonName.isInfraSpecific()){
2310
            infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2311
        }
2312
        String genusOfTaxon = taxonName.getGenusOrUninomial();
2313
        Set<TaxonNode> nodes = taxon.getTaxonNodes();
2314
        List<String> taxonNames = new ArrayList<String>();
2315

    
2316
        for (TaxonNode node: nodes){
2317
           // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2318
           // List<String> synonymsEpithet = new ArrayList<String>();
2319

    
2320
            if (node.getClassification().equals(classification)){
2321
                if (!node.isTopmostNode()){
2322
                    TaxonNode parent = node.getParent();
2323
                    parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
2324
                    TaxonNameBase<?,?> parentName =  parent.getTaxon().getName();
2325
                    ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2326
                    Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
2327
                    Rank rankOfTaxon = taxonName.getRank();
2328

    
2329

    
2330
                    //create inferred synonyms for species, subspecies
2331
                    if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2332

    
2333
                        Synonym inferredEpithet = null;
2334
                        Synonym inferredGenus = null;
2335
                        Synonym potentialCombination = null;
2336

    
2337
                        List<String> propertyPaths = new ArrayList<String>();
2338
                        propertyPaths.add("synonym");
2339
                        propertyPaths.add("synonym.name");
2340
                        List<OrderHint> orderHints = new ArrayList<OrderHint>();
2341
                        orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2342

    
2343
                        List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2344
                        List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2345

    
2346
                        List<TaxonRelationship> taxonRelListParent = null;
2347
                        List<TaxonRelationship> taxonRelListTaxon = null;
2348
                        if (doWithMisappliedNames){
2349
                            taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2350
                            taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2351
                        }
2352

    
2353

    
2354
                        if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2355

    
2356

    
2357
                            for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2358
                                Synonym syn = synonymRelationOfParent.getSynonym();
2359

    
2360
                                inferredEpithet = createInferredEpithets(taxon,
2361
                                        zooHashMap, taxonName, epithetOfTaxon,
2362
                                        infragenericEpithetOfTaxon,
2363
                                        infraspecificEpithetOfTaxon,
2364
                                        taxonNames, parentName,
2365
                                        syn);
2366

    
2367

    
2368
                                inferredSynonyms.add(inferredEpithet);
2369
                                zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2370
                                taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2371
                            }
2372

    
2373
                            if (doWithMisappliedNames){
2374

    
2375
                                for (TaxonRelationship taxonRelationship: taxonRelListParent){
2376
                                     Taxon misappliedName = taxonRelationship.getFromTaxon();
2377

    
2378
                                     inferredEpithet = createInferredEpithets(taxon,
2379
                                             zooHashMap, taxonName, epithetOfTaxon,
2380
                                             infragenericEpithetOfTaxon,
2381
                                             infraspecificEpithetOfTaxon,
2382
                                             taxonNames, parentName,
2383
                                             misappliedName);
2384

    
2385
                                    inferredSynonyms.add(inferredEpithet);
2386
                                    zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2387
                                     taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2388
                                }
2389
                            }
2390

    
2391
                            if (!taxonNames.isEmpty()){
2392
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2393
                            ZoologicalName name;
2394
                            if (!synNotInCDM.isEmpty()){
2395
                                inferredSynonymsToBeRemoved.clear();
2396

    
2397
                                for (Synonym syn :inferredSynonyms){
2398
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2399
                                    if (!synNotInCDM.contains(name.getNameCache())){
2400
                                        inferredSynonymsToBeRemoved.add(syn);
2401
                                    }
2402
                                }
2403

    
2404
                                // Remove identified Synonyms from inferredSynonyms
2405
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2406
                                    inferredSynonyms.remove(synonym);
2407
                                }
2408
                            }
2409
                        }
2410

    
2411
                    }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2412

    
2413

    
2414
                        for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2415
                            TaxonNameBase synName;
2416
                            ZoologicalName inferredSynName;
2417

    
2418
                            Synonym syn = synonymRelationOfTaxon.getSynonym();
2419
                            inferredGenus = createInferredGenus(taxon,
2420
                                    zooHashMap, taxonName, epithetOfTaxon,
2421
                                    genusOfTaxon, taxonNames, zooParentName, syn);
2422

    
2423
                            inferredSynonyms.add(inferredGenus);
2424
                            zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2425
                            taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2426

    
2427

    
2428
                        }
2429

    
2430
                        if (doWithMisappliedNames){
2431

    
2432
                            for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2433
                                Taxon misappliedName = taxonRelationship.getFromTaxon();
2434
                                inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
2435

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

    
2442

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

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

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

    
2463
                    }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2464

    
2465
                        Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2466
                        ZoologicalName inferredSynName;
2467
                        //for all synonyms of the parent...
2468
                        for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2469
                            TaxonNameBase synName;
2470
                            Synonym synParent = synonymRelationOfParent.getSynonym();
2471
                            synName = synParent.getName();
2472

    
2473
                            HibernateProxyHelper.deproxy(synParent);
2474

    
2475
                            // Set the sourceReference
2476
                            sourceReference = synParent.getSec();
2477

    
2478
                            // Determine the idInSource
2479
                            String idInSourceParent = getIdInSource(synParent);
2480

    
2481
                            ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2482
                            String synParentGenus = parentSynZooName.getGenusOrUninomial();
2483
                            String synParentInfragenericName = null;
2484
                            String synParentSpecificEpithet = null;
2485

    
2486
                            if (parentSynZooName.isInfraGeneric()){
2487
                                synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2488
                            }
2489
                            if (parentSynZooName.isSpecies()){
2490
                                synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2491
                            }
2492

    
2493
                           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2494
                                synonymsGenus.put(synGenusName, idInSource);
2495
                            }*/
2496

    
2497
                            //for all synonyms of the taxon
2498

    
2499
                            for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2500

    
2501
                                Synonym syn = synonymRelationOfTaxon.getSynonym();
2502
                                ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2503
                                potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2504
                                        synParentGenus,
2505
                                        synParentInfragenericName,
2506
                                        synParentSpecificEpithet, syn, zooHashMap);
2507

    
2508
                                taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2509
                                inferredSynonyms.add(potentialCombination);
2510
                                zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2511
                                 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2512

    
2513
                            }
2514

    
2515

    
2516
                        }
2517

    
2518
                        if (doWithMisappliedNames){
2519

    
2520
                            for (TaxonRelationship parentRelationship: taxonRelListParent){
2521

    
2522
                                TaxonNameBase misappliedParentName;
2523

    
2524
                                Taxon misappliedParent = parentRelationship.getFromTaxon();
2525
                                misappliedParentName = misappliedParent.getName();
2526

    
2527
                                HibernateProxyHelper.deproxy(misappliedParent);
2528

    
2529
                                // Set the sourceReference
2530
                                sourceReference = misappliedParent.getSec();
2531

    
2532
                                // Determine the idInSource
2533
                                String idInSourceParent = getIdInSource(misappliedParent);
2534

    
2535
                                ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2536
                                String synParentGenus = parentSynZooName.getGenusOrUninomial();
2537
                                String synParentInfragenericName = null;
2538
                                String synParentSpecificEpithet = null;
2539

    
2540
                                if (parentSynZooName.isInfraGeneric()){
2541
                                    synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2542
                                }
2543
                                if (parentSynZooName.isSpecies()){
2544
                                    synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2545
                                }
2546

    
2547

    
2548
                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2549
                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
2550
                                    ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2551
                                    potentialCombination = createPotentialCombination(
2552
                                            idInSourceParent, parentSynZooName, zooMisappliedName,
2553
                                            synParentGenus,
2554
                                            synParentInfragenericName,
2555
                                            synParentSpecificEpithet, misappliedName, zooHashMap);
2556

    
2557

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

    
2566
                        if (!taxonNames.isEmpty()){
2567
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2568
                            ZoologicalName name;
2569
                            if (!synNotInCDM.isEmpty()){
2570
                                inferredSynonymsToBeRemoved.clear();
2571
                                for (Synonym syn :inferredSynonyms){
2572
                                    try{
2573
                                        name = (ZoologicalName) syn.getName();
2574
                                    }catch (ClassCastException e){
2575
                                        name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2576
                                    }
2577
                                    if (!synNotInCDM.contains(name.getNameCache())){
2578
                                        inferredSynonymsToBeRemoved.add(syn);
2579
                                    }
2580
                                 }
2581
                                // Remove identified Synonyms from inferredSynonyms
2582
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2583
                                    inferredSynonyms.remove(synonym);
2584
                                }
2585
                            }
2586
                         }
2587
                        }
2588
                    }else {
2589
                        logger.info("The synonymrelationship type is not defined.");
2590
                        return inferredSynonyms;
2591
                    }
2592
                }
2593
            }
2594

    
2595
        }
2596

    
2597
        return inferredSynonyms;
2598
    }
2599

    
2600
    private Synonym createPotentialCombination(String idInSourceParent,
2601
            ZoologicalName parentSynZooName, 	ZoologicalName zooSynName, String synParentGenus,
2602
            String synParentInfragenericName, String synParentSpecificEpithet,
2603
            TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2604
        Synonym potentialCombination;
2605
        Reference sourceReference;
2606
        ZoologicalName inferredSynName;
2607
        HibernateProxyHelper.deproxy(syn);
2608

    
2609
        // Set sourceReference
2610
        sourceReference = syn.getSec();
2611
        if (sourceReference == null){
2612
            logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2613
            //TODO:Remove
2614
            if (!parentSynZooName.getTaxa().isEmpty()){
2615
                TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2616

    
2617
                sourceReference = taxon.getSec();
2618
            }
2619
        }
2620
        String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2621

    
2622
        String synTaxonInfraSpecificName= null;
2623

    
2624
        if (parentSynZooName.isSpecies()){
2625
            synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2626
        }
2627

    
2628
        /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2629
            synonymsEpithet.add(epithetName);
2630
        }*/
2631

    
2632
        //create potential combinations...
2633
        inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2634

    
2635
        inferredSynName.setGenusOrUninomial(synParentGenus);
2636
        if (zooSynName.isSpecies()){
2637
              inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2638
              if (parentSynZooName.isInfraGeneric()){
2639
                  inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2640
              }
2641
        }
2642
        if (zooSynName.isInfraSpecific()){
2643
            inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2644
            inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2645
        }
2646
        if (parentSynZooName.isInfraGeneric()){
2647
            inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2648
        }
2649

    
2650

    
2651
        potentialCombination = Synonym.NewInstance(inferredSynName, null);
2652

    
2653
        // Set the sourceReference
2654
        potentialCombination.setSec(sourceReference);
2655

    
2656

    
2657
        // Determine the idInSource
2658
        String idInSourceSyn= getIdInSource(syn);
2659

    
2660
        if (idInSourceParent != null && idInSourceSyn != null) {
2661
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2662
            inferredSynName.addSource(originalSource);
2663
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2664
            potentialCombination.addSource(originalSource);
2665
        }
2666

    
2667
        return potentialCombination;
2668
    }
2669

    
2670
    private Synonym createInferredGenus(Taxon taxon,
2671
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2672
            String epithetOfTaxon, String genusOfTaxon,
2673
            List<String> taxonNames, ZoologicalName zooParentName,
2674
            TaxonBase syn) {
2675

    
2676
        Synonym inferredGenus;
2677
        TaxonNameBase synName;
2678
        ZoologicalName inferredSynName;
2679
        synName =syn.getName();
2680
        HibernateProxyHelper.deproxy(syn);
2681

    
2682
        // Determine the idInSource
2683
        String idInSourceSyn = getIdInSource(syn);
2684
        String idInSourceTaxon = getIdInSource(taxon);
2685
        // Determine the sourceReference
2686
        Reference sourceReference = syn.getSec();
2687

    
2688
        //logger.warn(sourceReference.getTitleCache());
2689

    
2690
        synName = syn.getName();
2691
        ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2692
        String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2693
                     /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2694
            synonymsEpithet.add(synSpeciesEpithetName);
2695
        }*/
2696

    
2697
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2698
        //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...
2699

    
2700

    
2701
        inferredSynName.setGenusOrUninomial(genusOfTaxon);
2702
        if (zooParentName.isInfraGeneric()){
2703
            inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2704
        }
2705

    
2706
        if (taxonName.isSpecies()){
2707
            inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2708
        }
2709
        if (taxonName.isInfraSpecific()){
2710
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2711
            inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2712
        }
2713

    
2714

    
2715
        inferredGenus = Synonym.NewInstance(inferredSynName, null);
2716

    
2717
        // Set the sourceReference
2718
        inferredGenus.setSec(sourceReference);
2719

    
2720
        // Add the original source
2721
        if (idInSourceSyn != null && idInSourceTaxon != null) {
2722
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2723
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2724
            inferredGenus.addSource(originalSource);
2725

    
2726
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2727
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2728
            inferredSynName.addSource(originalSource);
2729
            originalSource = null;
2730

    
2731
        }else{
2732
            logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2733
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2734
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2735
            inferredGenus.addSource(originalSource);
2736

    
2737
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2738
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2739
            inferredSynName.addSource(originalSource);
2740
            originalSource = null;
2741
        }
2742

    
2743
        taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2744

    
2745
        return inferredGenus;
2746
    }
2747

    
2748
    private Synonym createInferredEpithets(Taxon taxon,
2749
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2750
            String epithetOfTaxon, String infragenericEpithetOfTaxon,
2751
            String infraspecificEpithetOfTaxon, List<String> taxonNames,
2752
            TaxonNameBase parentName, TaxonBase syn) {
2753

    
2754
        Synonym inferredEpithet;
2755
        TaxonNameBase<?,?> synName;
2756
        ZoologicalName inferredSynName;
2757
        HibernateProxyHelper.deproxy(syn);
2758

    
2759
        // Determine the idInSource
2760
        String idInSourceSyn = getIdInSource(syn);
2761
        String idInSourceTaxon =  getIdInSource(taxon);
2762
        // Determine the sourceReference
2763
        Reference<?> sourceReference = syn.getSec();
2764

    
2765
        if (sourceReference == null){
2766
             logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2767
             sourceReference = taxon.getSec();
2768
        }
2769

    
2770
        synName = syn.getName();
2771
        ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2772
        String synGenusName = zooSynName.getGenusOrUninomial();
2773
        String synInfraGenericEpithet = null;
2774
        String synSpecificEpithet = null;
2775

    
2776
        if (zooSynName.getInfraGenericEpithet() != null){
2777
            synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2778
        }
2779

    
2780
        if (zooSynName.isInfraSpecific()){
2781
            synSpecificEpithet = zooSynName.getSpecificEpithet();
2782
        }
2783

    
2784
                     /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2785
            synonymsGenus.put(synGenusName, idInSource);
2786
        }*/
2787

    
2788
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2789

    
2790
        // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2791
        if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2792
            logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2793
        }
2794
        inferredSynName.setGenusOrUninomial(synGenusName);
2795

    
2796
        if (parentName.isInfraGeneric()){
2797
            inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2798
        }
2799
        if (taxonName.isSpecies()){
2800
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2801
        }else if (taxonName.isInfraSpecific()){
2802
            inferredSynName.setSpecificEpithet(synSpecificEpithet);
2803
            inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2804
        }
2805

    
2806
        inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2807

    
2808
        // Set the sourceReference
2809
        inferredEpithet.setSec(sourceReference);
2810

    
2811
        /* Add the original source
2812
        if (idInSource != null) {
2813
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2814

    
2815
            // Add the citation
2816
            Reference citation = getCitation(syn);
2817
            if (citation != null) {
2818
                originalSource.setCitation(citation);
2819
                inferredEpithet.addSource(originalSource);
2820
            }
2821
        }*/
2822
        String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2823

    
2824

    
2825
        IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2826
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2827

    
2828
        inferredEpithet.addSource(originalSource);
2829

    
2830
        originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2831
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2832

    
2833
        inferredSynName.addSource(originalSource);
2834

    
2835

    
2836

    
2837
        taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2838

    
2839
        return inferredEpithet;
2840
    }
2841

    
2842
    /**
2843
     * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2844
     * Very likely only useful for createInferredSynonyms().
2845
     * @param uuid
2846
     * @param zooHashMap
2847
     * @return
2848
     */
2849
    private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2850
        ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2851
        if (taxonName == null) {
2852
            taxonName = zooHashMap.get(uuid);
2853
        }
2854
        return taxonName;
2855
    }
2856

    
2857
    /**
2858
     * Returns the idInSource for a given Synonym.
2859
     * @param syn
2860
     */
2861
    private String getIdInSource(TaxonBase taxonBase) {
2862
        String idInSource = null;
2863
        Set<IdentifiableSource> sources = taxonBase.getSources();
2864
        if (sources.size() == 1) {
2865
            IdentifiableSource source = sources.iterator().next();
2866
            if (source != null) {
2867
                idInSource  = source.getIdInSource();
2868
            }
2869
        } else if (sources.size() > 1) {
2870
            int count = 1;
2871
            idInSource = "";
2872
            for (IdentifiableSource source : sources) {
2873
                idInSource += source.getIdInSource();
2874
                if (count < sources.size()) {
2875
                    idInSource += "; ";
2876
                }
2877
                count++;
2878
            }
2879
        } else if (sources.size() == 0){
2880
            logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2881
        }
2882

    
2883

    
2884
        return idInSource;
2885
    }
2886

    
2887

    
2888
    /**
2889
     * Returns the citation for a given Synonym.
2890
     * @param syn
2891
     */
2892
    private Reference getCitation(Synonym syn) {
2893
        Reference citation = null;
2894
        Set<IdentifiableSource> sources = syn.getSources();
2895
        if (sources.size() == 1) {
2896
            IdentifiableSource source = sources.iterator().next();
2897
            if (source != null) {
2898
                citation = source.getCitation();
2899
            }
2900
        } else if (sources.size() > 1) {
2901
            logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2902
        }
2903

    
2904
        return citation;
2905
    }
2906

    
2907
    @Override
2908
    public List<Synonym>  createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2909
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2910

    
2911
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2912
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2913
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2914

    
2915
        return inferredSynonyms;
2916
    }
2917

    
2918
    @Override
2919
    public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2920

    
2921
        // TODO quickly implemented, create according dao !!!!
2922
        Set<TaxonNode> nodes = new HashSet<TaxonNode>();
2923
        Set<Classification> classifications = new HashSet<Classification>();
2924
        List<Classification> list = new ArrayList<Classification>();
2925

    
2926
        if (taxonBase == null) {
2927
            return list;
2928
        }
2929

    
2930
        taxonBase = load(taxonBase.getUuid());
2931

    
2932
        if (taxonBase instanceof Taxon) {
2933
            nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
2934
        } else {
2935
            for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
2936
                nodes.addAll(taxon.getTaxonNodes());
2937
            }
2938
        }
2939
        for (TaxonNode node : nodes) {
2940
            classifications.add(node.getClassification());
2941
        }
2942
        list.addAll(classifications);
2943
        return list;
2944
    }
2945

    
2946
    @Override
2947
    @Transactional(readOnly = false)
2948
    public UpdateResult changeRelatedTaxonToSynonym(UUID fromTaxonUuid,
2949
            UUID toTaxonUuid,
2950
            TaxonRelationshipType oldRelationshipType,
2951
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
2952
        UpdateResult result = new UpdateResult();
2953
        Taxon fromTaxon = (Taxon) dao.load(fromTaxonUuid);
2954
        Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
2955
        Synonym synonym = changeRelatedTaxonToSynonym(fromTaxon, toTaxon, oldRelationshipType, synonymRelationshipType);
2956
        result.setCdmEntity(synonym);
2957
        result.addUpdatedObject(fromTaxon);
2958
        result.addUpdatedObject(toTaxon);
2959
        result.addUpdatedObject(synonym);
2960

    
2961
        return result;
2962
    }
2963

    
2964
    @Override
2965
    @Transactional(readOnly = false)
2966
    public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
2967
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
2968
        // Create new synonym using concept name
2969
                TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
2970
                Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
2971

    
2972
                // Remove concept relation from taxon
2973
                toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
2974

    
2975

    
2976

    
2977

    
2978
                // Create a new synonym for the taxon
2979
                SynonymRelationship synonymRelationship;
2980
                if (synonymRelationshipType != null
2981
                        && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
2982
                    synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
2983
                } else{
2984
                    synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
2985
                }
2986

    
2987
                this.saveOrUpdate(toTaxon);
2988
                //TODO: configurator and classification
2989
                TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
2990
                config.setDeleteNameIfPossible(false);
2991
                this.deleteTaxon(fromTaxon.getUuid(), config, null);
2992
                return synonymRelationship.getSynonym();
2993

    
2994
    }
2995

    
2996
    @Override
2997
    public DeleteResult isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
2998
        DeleteResult result = new DeleteResult();
2999
        Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
3000
        if (taxonBase instanceof Taxon){
3001
            TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3002
            result = isDeletableForTaxon(references, taxonConfig);
3003
        }else{
3004
            SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3005
            result = isDeletableForSynonym(references, synonymConfig);
3006
        }
3007
        return result;
3008
    }
3009

    
3010
    private DeleteResult isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3011
        String message;
3012
        DeleteResult result = new DeleteResult();
3013
        for (CdmBase ref: references){
3014
            if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase )){
3015
                message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3016
                result.addException(new ReferencedObjectUndeletableException(message));
3017
                result.addRelatedObject(ref);
3018
                result.setAbort();
3019
            }
3020
        }
3021

    
3022
        return result;
3023
    }
3024

    
3025
    private DeleteResult isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3026
        String message = null;
3027
        DeleteResult result = new DeleteResult();
3028
        for (CdmBase ref: references){
3029
            if (!(ref instanceof TaxonNameBase)){
3030
            	message = null;
3031
                if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){
3032
                    message = "The taxon can't be deleted as long as it has synonyms.";
3033

    
3034
                }
3035
                if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3036
                    message = "The taxon can't be deleted as long as it has factual data.";
3037

    
3038
                }
3039

    
3040
                if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
3041
                    message = "The taxon can't be deleted as long as it belongs to a taxon node.";
3042

    
3043
                }
3044
                if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonNode)){
3045
                    if (!config.isDeleteMisappliedNamesAndInvalidDesignations() && (((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())|| ((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR()))){
3046
                        message = "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
3047

    
3048
                    } else{
3049
                        message = "The taxon can't be deleted as long as it belongs to a taxon node.";
3050

    
3051
                    }
3052
                }
3053
                if (ref instanceof PolytomousKeyNode){
3054
                    message = "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3055

    
3056
                }
3057

    
3058
                if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){
3059
                   message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
3060

    
3061

    
3062
                }
3063

    
3064

    
3065
               /* //PolytomousKeyNode
3066
                if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3067
                    String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3068
                    return message;
3069
                }*/
3070

    
3071
                //TaxonInteraction
3072
                if (ref.isInstanceOf(TaxonInteraction.class)){
3073
                    message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3074

    
3075
                }
3076

    
3077
              //TaxonInteraction
3078
                if (ref.isInstanceOf(DeterminationEvent.class)){
3079
                    message = "Taxon can't be deleted as it is used in a determination event";
3080

    
3081
                }
3082

    
3083
            }
3084
            if (message != null){
3085
	            result.addException(new ReferencedObjectUndeletableException(message));
3086
	            result.addRelatedObject(ref);
3087
	            result.setAbort();
3088
            }
3089
        }
3090

    
3091
        return result;
3092
    }
3093

    
3094
    @Override
3095
    public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3096
        IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3097

    
3098
        //preliminary implementation
3099

    
3100
        Set<Taxon> taxa = new HashSet<Taxon>();
3101
        TaxonBase taxonBase = find(taxonUuid);
3102
        if (taxonBase == null){
3103
            return new IncludedTaxaDTO();
3104
        }else if (taxonBase.isInstanceOf(Taxon.class)){
3105
            Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3106
            taxa.add(taxon);
3107
        }else if (taxonBase.isInstanceOf(Synonym.class)){
3108
            //TODO partial synonyms ??
3109
            //TODO synonyms in general
3110
            Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3111
            taxa.addAll(syn.getAcceptedTaxa());
3112
        }else{
3113
            throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3114
        }
3115

    
3116
        Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3117
        int i = 0;
3118
        while((! related.isEmpty()) && i++ < 100){  //to avoid
3119
             related = makeRelatedIncluded(related, result, config);
3120
        }
3121

    
3122
        return result;
3123
    }
3124

    
3125
    /**
3126
     * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3127
     * data structure.
3128
     * @return the set of conceptually related taxa for further use
3129
     */
3130
    /**
3131
     * @param uncheckedTaxa
3132
     * @param existingTaxa
3133
     * @param config
3134
     * @return
3135
     */
3136
    private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3137

    
3138
        //children
3139
        Set<TaxonNode> taxonNodes = new HashSet<TaxonNode>();
3140
        for (Taxon taxon: uncheckedTaxa){
3141
            taxonNodes.addAll(taxon.getTaxonNodes());
3142
        }
3143

    
3144
        Set<Taxon> children = new HashSet<Taxon>();
3145
        if (! config.onlyCongruent){
3146
            for (TaxonNode node: taxonNodes){
3147
                List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, null);
3148
                for (TaxonNode child : childNodes){
3149
                    children.add(child.getTaxon());
3150
                }
3151
            }
3152
            children.remove(null);  // just to be on the save side
3153
        }
3154

    
3155
        Iterator<Taxon> it = children.iterator();
3156
        while(it.hasNext()){
3157
            UUID uuid = it.next().getUuid();
3158
            if (existingTaxa.contains(uuid)){
3159
                it.remove();
3160
            }else{
3161
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3162
            }
3163
        }
3164

    
3165
        //concept relations
3166
        Set<Taxon> uncheckedAndChildren = new HashSet<Taxon>(uncheckedTaxa);
3167
        uncheckedAndChildren.addAll(children);
3168

    
3169
        Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3170

    
3171

    
3172
        Set<Taxon> result = new HashSet<Taxon>(relatedTaxa);
3173
        return result;
3174
    }
3175

    
3176
    /**
3177
     * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3178
     * @return the set of these computed taxa
3179
     */
3180
    private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3181
        Set<Taxon> result = new HashSet<Taxon>();
3182

    
3183
        for (Taxon taxon : unchecked){
3184
            Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3185
            Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3186

    
3187
            for (TaxonRelationship fromRel : fromRelations){
3188
                if (config.includeDoubtful == false && fromRel.isDoubtful()){
3189
                    continue;
3190
                }
3191
                if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3192
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
3193
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3194
                        ){
3195
                    result.add(fromRel.getToTaxon());
3196
                }
3197
            }
3198

    
3199
            for (TaxonRelationship toRel : toRelations){
3200
                if (config.includeDoubtful == false && toRel.isDoubtful()){
3201
                    continue;
3202
                }
3203
                if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
3204
                    result.add(toRel.getFromTaxon());
3205
                }
3206
            }
3207
        }
3208

    
3209
        Iterator<Taxon> it = result.iterator();
3210
        while(it.hasNext()){
3211
            UUID uuid = it.next().getUuid();
3212
            if (existingTaxa.contains(uuid)){
3213
                it.remove();
3214
            }else{
3215
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3216
            }
3217
        }
3218
        return result;
3219
    }
3220

    
3221
    @Override
3222
    public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3223
        List<TaxonBase> taxonList = dao.getTaxaByName(true, false, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, 0, config.getPropertyPath());
3224
        return taxonList;
3225
    }
3226

    
3227
	@Override
3228
	@Transactional(readOnly = true)
3229
	public <S extends TaxonBase> Pager<FindByIdentifierDTO<S>> findByIdentifier(
3230
			Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
3231
			MatchMode matchmode, boolean includeEntity, Integer pageSize,
3232
			Integer pageNumber,	List<String> propertyPaths) {
3233
		if (subtreeFilter == null){
3234
			return findByIdentifier(clazz, identifier, identifierType, matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3235
		}
3236

    
3237
		Integer numberOfResults = dao.countByIdentifier(clazz, identifier, identifierType, subtreeFilter, matchmode);
3238
        List<Object[]> daoResults = new ArrayList<Object[]>();
3239
        if(numberOfResults > 0) { // no point checking again
3240
        	daoResults = dao.findByIdentifier(clazz, identifier, identifierType, subtreeFilter,
3241
    				matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3242
        }
3243

    
3244
        List<FindByIdentifierDTO<S>> result = new ArrayList<FindByIdentifierDTO<S>>();
3245
        for (Object[] daoObj : daoResults){
3246
        	if (includeEntity){
3247
        		result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (S)daoObj[2]));
3248
        	}else{
3249
        		result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (UUID)daoObj[2], (String)daoObj[3]));
3250
        	}
3251
        }
3252
		return new DefaultPagerImpl<FindByIdentifierDTO<S>>(pageNumber, numberOfResults, pageSize, result);
3253
	}
3254

    
3255
	@Override
3256
	@Transactional(readOnly = false)
3257
	public UpdateResult moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, UUID newTaxonUUID, boolean moveHomotypicGroup,
3258
            SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
3259

    
3260
	    UpdateResult result = new UpdateResult();
3261
		Taxon newTaxon = (Taxon) dao.load(newTaxonUUID);
3262
		SynonymRelationship sr = moveSynonymToAnotherTaxon(oldSynonymRelation, newTaxon, moveHomotypicGroup, newSynonymRelationshipType, reference, referenceDetail, keepReference);
3263
		result.setCdmEntity(sr);
3264
		result.addUpdatedObject(sr);
3265
		result.addUpdatedObject(newTaxon);
3266
		return result;
3267
	}
3268

    
3269
	@Override
3270
	public UpdateResult moveFactualDateToAnotherTaxon(UUID fromTaxonUuid, UUID toTaxonUuid){
3271
		UpdateResult result = new UpdateResult();
3272

    
3273
		Taxon fromTaxon = (Taxon)dao.load(fromTaxonUuid);
3274
		Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
3275
		  for(TaxonDescription description : fromTaxon.getDescriptions()){
3276
              //reload to avoid session conflicts
3277
              description = HibernateProxyHelper.deproxy(description, TaxonDescription.class);
3278

    
3279
              String moveMessage = String.format("Description moved from %s", fromTaxon);
3280
              if(description.isProtectedTitleCache()){
3281
                  String separator = "";
3282
                  if(!StringUtils.isBlank(description.getTitleCache())){
3283
                      separator = " - ";
3284
                  }
3285
                  description.setTitleCache(description.getTitleCache() + separator + moveMessage, true);
3286
              }
3287
              Annotation annotation = Annotation.NewInstance(moveMessage, Language.getDefaultLanguage());
3288
              annotation.setAnnotationType(AnnotationType.TECHNICAL());
3289
              description.addAnnotation(annotation);
3290
              toTaxon.addDescription(description);
3291
              dao.saveOrUpdate(toTaxon);
3292
              dao.saveOrUpdate(fromTaxon);
3293
              result.addUpdatedObject(toTaxon);
3294
              result.addUpdatedObject(fromTaxon);
3295

    
3296
          }
3297

    
3298

    
3299
		return result;
3300
	}
3301

    
3302
	@Override
3303
	public DeleteResult deleteSynonym(UUID synonymUuid, UUID taxonUuid,
3304
			SynonymDeletionConfigurator config) {
3305
		TaxonBase base = this.load(synonymUuid);
3306
		Synonym syn = HibernateProxyHelper.deproxy(base, Synonym.class);
3307
		base = this.load(taxonUuid);
3308
		Taxon taxon = HibernateProxyHelper.deproxy(base, Taxon.class);
3309

    
3310
		return this.deleteSynonym(syn, taxon, config);
3311
	}
3312

    
3313
	@Override
3314
	@Transactional(readOnly = false)
3315
	public UpdateResult swapSynonymAndAcceptedTaxon(UUID synonymUUid,
3316
			UUID acceptedTaxonUuid) {
3317
		TaxonBase base = this.load(synonymUUid);
3318
		Synonym syn = HibernateProxyHelper.deproxy(base, Synonym.class);
3319
		base = this.load(acceptedTaxonUuid);
3320
		Taxon taxon = HibernateProxyHelper.deproxy(base, Taxon.class);
3321

    
3322
		return this.swapSynonymAndAcceptedTaxon(syn, taxon);
3323
	}
3324

    
3325

    
3326

    
3327
}
(85-85/92)