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 =  HibernateProxyHelper.deproxy(dao.load(taxonUUID), Taxon.class);
970

    
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
            this.saveOrUpdate(taxon);
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
            	    //taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
1170
            		UUID uuid = dao.delete(taxon);
1171

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

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

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

    
1195
    }
1196

    
1197
    private String checkForReferences(Taxon taxon){
1198
        Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1199
        for (CdmBase referencingObject : referencingObjects){
1200
            //IIdentificationKeys (Media, Polytomous, MultiAccess)
1201
            if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1202
                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";
1203

    
1204
                return message;
1205
            }
1206

    
1207

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

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

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

    
1226
        }
1227

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

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

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

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

    
1250

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

    
1256
    }
1257

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

    
1263
    }
1264

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

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

    
1279

    
1280
        if (result.isOk()){
1281

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

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

    
1298
            //TODO remove name from homotypical group?
1299

    
1300
            //remove synonym (if necessary)
1301

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

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

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

    
1317
                }
1318

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

    
1325

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

    
1338

    
1339
    }
1340

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1436

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

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

    
1464
        return bestCandidate;
1465
    }
1466

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

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

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

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

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

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

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

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

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

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

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

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

    
1607

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1683

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

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

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

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

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

    
1735
        BooleanQuery finalQuery = new BooleanQuery();
1736

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

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

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

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

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

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

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

    
1771
        // FIXME: allow taxonomic ordering
1772
        //  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";
1773
        // this require building a special sort column by a special classBridge
1774
        if(highlightFragments){
1775
            logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1776
                    "currently not fully supported by this method and thus " +
1777
                    "may not work with common names and misapplied names.");
1778
        }
1779

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

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

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

    
1810

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

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

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

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

    
1824

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

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

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

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

    
1857
        BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1858

    
1859

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

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

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

    
1893
            }
1894
        }
1895

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

    
1915
            luceneSearches.add(byCommonNameSearch);
1916

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

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

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

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

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

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

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

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

    
1993

    
1994
        if(addDistributionFilter){
1995

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

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

    
2014

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

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

    
2021

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

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

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

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

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

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

    
2048
        return taxonAreaJoinQuery;
2049
    }
2050

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

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

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

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

    
2087
        BooleanQuery finalQuery = new BooleanQuery();
2088

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

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

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

    
2097

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

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

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

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

    
2109
        return luceneSearch;
2110
    }
2111

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

    
2118

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

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

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

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

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

    
2136
    }
2137

    
2138

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

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

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

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

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

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

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

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

    
2165
    }
2166

    
2167

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

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

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

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

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

    
2197
        return luceneSearch;
2198
    }
2199

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

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

    
2229

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2300

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

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

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

    
2330

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

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

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

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

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

    
2354

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

    
2357

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

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

    
2368

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

    
2374
                            if (doWithMisappliedNames){
2375

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

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

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

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

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

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

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

    
2414

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

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

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

    
2428

    
2429
                        }
2430

    
2431
                        if (doWithMisappliedNames){
2432

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

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

    
2443

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

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

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

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

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

    
2474
                            HibernateProxyHelper.deproxy(synParent);
2475

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

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

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

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

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

    
2498
                            //for all synonyms of the taxon
2499

    
2500
                            for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2501

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

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

    
2514
                            }
2515

    
2516

    
2517
                        }
2518

    
2519
                        if (doWithMisappliedNames){
2520

    
2521
                            for (TaxonRelationship parentRelationship: taxonRelListParent){
2522

    
2523
                                TaxonNameBase misappliedParentName;
2524

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

    
2528
                                HibernateProxyHelper.deproxy(misappliedParent);
2529

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

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

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

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

    
2548

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

    
2558

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

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

    
2596
        }
2597

    
2598
        return inferredSynonyms;
2599
    }
2600

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

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

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

    
2623
        String synTaxonInfraSpecificName= null;
2624

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

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

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

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

    
2651

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

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

    
2657

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

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

    
2668
        return potentialCombination;
2669
    }
2670

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

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

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

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

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

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

    
2701

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

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

    
2715

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

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

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

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

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

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

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

    
2746
        return inferredGenus;
2747
    }
2748

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2825

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

    
2829
        inferredEpithet.addSource(originalSource);
2830

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

    
2834
        inferredSynName.addSource(originalSource);
2835

    
2836

    
2837

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

    
2840
        return inferredEpithet;
2841
    }
2842

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

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

    
2884

    
2885
        return idInSource;
2886
    }
2887

    
2888

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

    
2905
        return citation;
2906
    }
2907

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

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

    
2916
        return inferredSynonyms;
2917
    }
2918

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

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

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

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

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

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

    
2962
        return result;
2963
    }
2964

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

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

    
2976

    
2977

    
2978

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

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

    
2995
    }
2996

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

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

    
3023
        return result;
3024
    }
3025

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

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

    
3039
                }
3040

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

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

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

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

    
3057
                }
3058

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

    
3062

    
3063
                }
3064

    
3065

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

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

    
3076
                }
3077

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

    
3082
                }
3083

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

    
3092
        return result;
3093
    }
3094

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

    
3099
        //preliminary implementation
3100

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

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

    
3123
        return result;
3124
    }
3125

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

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

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

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

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

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

    
3172

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3297
          }
3298

    
3299

    
3300
		return result;
3301
	}
3302

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

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

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

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

    
3326

    
3327

    
3328
}
(85-85/92)