Project

General

Profile

Download (153 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.classic.ParseException;
30
import org.apache.lucene.search.BooleanClause.Occur;
31
import org.apache.lucene.search.BooleanQuery;
32
import org.apache.lucene.search.BooleanQuery.Builder;
33
import org.apache.lucene.search.Query;
34
import org.apache.lucene.search.SortField;
35
import org.apache.lucene.search.grouping.TopGroups;
36
import org.apache.lucene.search.join.ScoreMode;
37
import org.apache.lucene.util.BytesRef;
38
import org.springframework.beans.factory.annotation.Autowired;
39
import org.springframework.stereotype.Service;
40
import org.springframework.transaction.annotation.Transactional;
41

    
42
import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
43
import eu.etaxonomy.cdm.api.service.config.IFindTaxaAndNamesConfigurator;
44
import eu.etaxonomy.cdm.api.service.config.IncludedTaxonConfiguration;
45
import eu.etaxonomy.cdm.api.service.config.MatchingTaxonConfigurator;
46
import eu.etaxonomy.cdm.api.service.config.NodeDeletionConfigurator.ChildHandling;
47
import eu.etaxonomy.cdm.api.service.config.SynonymDeletionConfigurator;
48
import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
49
import eu.etaxonomy.cdm.api.service.dto.FindByIdentifierDTO;
50
import eu.etaxonomy.cdm.api.service.dto.IncludedTaxaDTO;
51
import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
52
import eu.etaxonomy.cdm.api.service.exception.HomotypicalGroupChangeException;
53
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
54
import eu.etaxonomy.cdm.api.service.pager.Pager;
55
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
56
import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
57
import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
58
import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearch;
59
import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearchException;
60
import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
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
        if (synonym == null){
357
            return null;
358
        }
359
        TaxonNameBase<?, ?> synonymName = synonym.getName();
360

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

    
367
        // Add taxon relation
368
        fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
369

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

    
375
        return fromTaxon;
376
    }
377

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

    
386

    
387
        // Switch groups
388
        oldHomotypicalGroup.removeTypifiedName(synonymName, false);
389
        newHomotypicalGroup.addTypifiedName(synonymName);
390

    
391
        //remove existing basionym relationships
392
        synonymName.removeBasionyms();
393

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

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

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

    
436
    }
437

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

    
447
    @Override
448
    @Autowired
449
    protected void setDao(ITaxonDao dao) {
450
        this.dao = dao;
451
    }
452

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

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

    
462
        return new DefaultPagerImpl<TaxonBase>(pageNumber, numberOfResults, pageSize, results);
463
    }
464

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

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

    
474
        return results;
475
    }
476

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

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

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

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

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

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

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

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

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

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

    
531
        List<Taxon> list = new ArrayList<Taxon>();
532
        Long count = 0l;
533

    
534
        Synonym synonym = null;
535

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

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

    
553
            }
554
        }
555

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

    
561
        return new DefaultPagerImpl<Taxon>(pageNumber, count.intValue(), pageSize, list);
562
    }
563

    
564

    
565
    @Override
566
    public Set<Taxon> listRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Integer maxDepth,
567
            Integer limit, Integer start, List<String> propertyPaths) {
568

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

    
575

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

    
588
        if(taxa.isEmpty()) {
589
            taxa.add(taxon);
590
        }
591

    
592
        if(includeRelationships.isEmpty()){
593
            return taxa;
594
        }
595

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

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

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

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

    
645
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
646
    }
647

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

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

    
657
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
658
    }
659

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

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

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

    
674
        return result;
675

    
676
    }
677

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

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

    
695
    @Override
696
    public List<UuidAndTitleCache<IdentifiableEntity>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator){
697

    
698
        List<UuidAndTitleCache<IdentifiableEntity>> results = new ArrayList<UuidAndTitleCache<IdentifiableEntity>>();
699

    
700

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

    
715
    @Override
716
    public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
717

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

    
722
        // Taxa and synonyms
723
        long numberTaxaResults = 0L;
724

    
725

    
726
        List<String> propertyPath = new ArrayList<String>();
727
        if(configurator.getTaxonPropertyPath() != null){
728
            propertyPath.addAll(configurator.getTaxonPropertyPath());
729
        }
730

    
731

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

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

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

    
750
        if(taxa != null){
751
            results.addAll(taxa);
752
        }
753

    
754
        numberOfResults += numberTaxaResults;
755

    
756
        // Names without taxa
757
        if (configurator.isDoNamesWithoutTaxa()) {
758
            int numberNameResults = 0;
759

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

    
776
        // Taxa from common names
777

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

    
793
        }
794

    
795
       return new DefaultPagerImpl<IdentifiableEntity>
796
            (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
797
    }
798

    
799
    public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(Integer limit, String pattern){
800
        return dao.getUuidAndTitleCache(limit, pattern);
801
    }
802

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

    
813
                    //find the best matching representation
814
                    medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
815

    
816
                }
817
            }
818
        }
819
        return medRep;
820
    }
821

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

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

    
832
    //    logger.setLevel(Level.TRACE);
833
//        Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
834

    
835
        logger.trace("listMedia() - START");
836

    
837
        Set<Taxon> taxa = new HashSet<Taxon>();
838
        List<Media> taxonMedia = new ArrayList<Media>();
839
        List<Media> nonImageGalleryImages = new ArrayList<Media>();
840

    
841
        if (limitToGalleries == null) {
842
            limitToGalleries = false;
843
        }
844

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

    
851
        taxa.add((Taxon) dao.load(taxon.getUuid()));
852

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

    
878

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

    
888
//            	direct media removed from specimen #3597
889
//              taxonMedia.addAll(occurrence.getMedia());
890

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

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

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

    
936

    
937
        logger.trace("listMedia() - initialize");
938
        beanInitializer.initializeAll(taxonMedia, propertyPath);
939

    
940
        logger.trace("listMedia() - END");
941

    
942
        return taxonMedia;
943
    }
944

    
945
    @Override
946
    public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
947
        return this.dao.loadList(listOfIDs, null);
948
    }
949

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

    
955
    @Override
956
    public int countAllRelationships() {
957
        return this.dao.countAllRelationships();
958
    }
959

    
960
    @Override
961
    public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
962
        return this.dao.findIdenticalTaxonNames(propertyPath);
963
    }
964

    
965
    @Override
966
    @Transactional(readOnly = false)
967
    public DeleteResult deleteTaxon(UUID taxonUUID, TaxonDeletionConfigurator config, UUID classificationUuid)  {
968

    
969
    	if (config == null){
970
            config = new TaxonDeletionConfigurator();
971
        }
972
    	Taxon taxon = (Taxon)dao.load(taxonUUID);
973
    	DeleteResult result = new DeleteResult();
974
    	if (taxon == null){
975
    	    result.setAbort();
976
    	    result.addException(new Exception ("The taxon was already deleted."));
977
    	    return result;
978
    	}
979
    	taxon = HibernateProxyHelper.deproxy(taxon);
980
    	Classification classification = HibernateProxyHelper.deproxy(classificationDao.load(classificationUuid), Classification.class);
981
        result = isDeletable(taxon, config);
982

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

    
998
                    // --- DeleteSynonymsIfPossible
999
                    if (config.isDeleteSynonymsIfPossible()){
1000
                        //TODO which value
1001
                        boolean newHomotypicGroupIfNeeded = true;
1002
                        SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
1003
                        deleteSynonym(synonym, taxon, synConfig);
1004
                    }
1005
                    // relationship will be deleted by hibernate automatically,
1006
                    // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797
1007

    
1008
                }
1009
            }
1010

    
1011
            // --- DeleteTaxonRelationships
1012
            if (! config.isDeleteTaxonRelationships()){
1013
                if (taxon.getTaxonRelations().size() > 0){
1014
                    result.setAbort();
1015
                    result.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
1016
                            "Remove taxon from all relations to other taxa prior to deletion."));
1017

    
1018
                }
1019
            } else{
1020
                TaxonDeletionConfigurator configRelTaxon = new TaxonDeletionConfigurator();
1021
                configRelTaxon.setDeleteTaxonNodes(false);
1022
                configRelTaxon.setDeleteConceptRelationships(true);
1023

    
1024
                for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
1025
                    if (config.isDeleteMisappliedNamesAndInvalidDesignations()){
1026
                        if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){
1027
                            if (taxon.equals(taxRel.getToTaxon())){
1028
                                this.deleteTaxon(taxRel.getFromTaxon().getUuid(), config, classificationUuid);
1029
                            }
1030
                        }
1031
                    } else if (config.isDeleteConceptRelationships() && taxRel.getType().isConceptRelationship()){
1032

    
1033
                        if (taxon.equals(taxRel.getToTaxon()) && isDeletable(taxRel.getFromTaxon(), configRelTaxon).isOk()){
1034
                            this.deleteTaxon(taxRel.getFromTaxon().getUuid(), configRelTaxon, classificationUuid);
1035
                        }else if (isDeletable(taxRel.getToTaxon(), configRelTaxon).isOk()){
1036
                            this.deleteTaxon(taxRel.getToTaxon().getUuid(), configRelTaxon, classificationUuid);
1037
                        }
1038
                    }
1039
                    taxon.removeTaxonRelation(taxRel);
1040

    
1041
                }
1042
            }
1043

    
1044
            //    	TaxonDescription
1045
            if (config.isDeleteDescriptions()){
1046
                Set<TaxonDescription> descriptions = taxon.getDescriptions();
1047
                List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1048
                for (TaxonDescription desc: descriptions){
1049
                    //TODO use description delete configurator ?
1050
                    //FIXME check if description is ALWAYS deletable
1051
                    if (desc.getDescribedSpecimenOrObservation() != null){
1052
                        result.setAbort();
1053
                        result.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1054
                                " which also describes specimens or observations"));
1055
                        break;
1056
                    }
1057
                    removeDescriptions.add(desc);
1058

    
1059

    
1060
                }
1061
                if (result.isOk()){
1062
                    for (TaxonDescription desc: removeDescriptions){
1063
                        taxon.removeDescription(desc);
1064
                        descriptionService.delete(desc);
1065
                    }
1066
                } else {
1067
                    return result;
1068
                }
1069
            }
1070

    
1071

    
1072
         if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null && taxon.getTaxonNodes().size() > 1)){
1073
                //if (taxon.getTaxonNodes().size() > 0){
1074
                 result.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
1075
                   // throw new ReferencedObjectUndeletableException(message);
1076
                //}
1077
         }else{
1078
                if (taxon.getTaxonNodes().size() != 0){
1079
                    Set<TaxonNode> nodes = taxon.getTaxonNodes();
1080
                    Iterator<TaxonNode> iterator = nodes.iterator();
1081
                    TaxonNode node = null;
1082
                    boolean deleteChildren;
1083
                    if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1084
                        deleteChildren = true;
1085
                    }else {
1086
                        deleteChildren = false;
1087
                    }
1088
                    boolean success = true;
1089
                    if (!config.isDeleteInAllClassifications() && !(classification == null)){
1090
                        while (iterator.hasNext()){
1091
                            node = iterator.next();
1092
                            if (node.getClassification().equals(classification)){
1093
                                break;
1094
                            }
1095
                            node = null;
1096
                        }
1097
                        if (node != null){
1098
                            HibernateProxyHelper.deproxy(node, TaxonNode.class);
1099
                            success =taxon.removeTaxonNode(node, deleteChildren);
1100
                            nodeService.delete(node);
1101
                        } else {
1102
                        	result.setError();
1103
                        	result.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1104
                        }
1105
                    } else if (config.isDeleteInAllClassifications()){
1106
                        List<TaxonNode> nodesList = new ArrayList<TaxonNode>();
1107
                        nodesList.addAll(taxon.getTaxonNodes());
1108

    
1109
                            for (ITaxonTreeNode treeNode: nodesList){
1110
                                TaxonNode taxonNode = (TaxonNode) treeNode;
1111
                                if(!deleteChildren){
1112
                                    Object[] childNodes = taxonNode.getChildNodes().toArray();
1113
                                    for (Object childNode: childNodes){
1114
                                        TaxonNode childNodeCast = (TaxonNode) childNode;
1115
                                        taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1116
                                    }
1117

    
1118
                                    //taxon.removeTaxonNode(taxonNode);
1119
                                }
1120
                            }
1121
                        config.getTaxonNodeConfig().setDeleteElement(false);
1122
                        DeleteResult resultNodes = nodeService.deleteTaxonNodes(nodesList, config);
1123
                        if (!resultNodes.isOk()){
1124
                        	result.addExceptions(resultNodes.getExceptions());
1125
                        	result.setStatus(resultNodes.getStatus());
1126
                        } else {
1127
                            result.addUpdatedObjects(resultNodes.getUpdatedObjects());
1128
                        }
1129
                    }
1130
                    if (!success){
1131
                        result.setError();
1132
                        result.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1133
                    }
1134
                }
1135
            }
1136

    
1137
            //TaxonNameBase
1138
            if (config.isDeleteNameIfPossible() && result.isOk()){
1139

    
1140

    
1141
                    //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1142
                    TaxonNameBase name = HibernateProxyHelper.deproxy(taxon.getName());
1143
                    //check whether taxon will be deleted or not
1144

    
1145
                    if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){
1146

    
1147
                        //name.removeTaxonBase(taxon);
1148
                        //nameService.saveOrUpdate(name);
1149
                        taxon.setName(null);
1150
                        //dao.delete(taxon);
1151
                        DeleteResult nameResult = new DeleteResult();
1152

    
1153
                        //remove name if possible (and required)
1154
                        if (name != null && config.isDeleteNameIfPossible()){
1155
                        	nameResult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
1156
                        }
1157

    
1158
                        if (nameResult.isError() || nameResult.isAbort()){
1159
                        	//result.setError();
1160
                        	result.addRelatedObject(name);
1161
                        	result.addExceptions(nameResult.getExceptions());
1162
                        }
1163

    
1164
                    }
1165

    
1166
            }else {
1167
                taxon.setName(null);
1168
            }
1169

    
1170

    
1171
            if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0)  && result.isOk()){
1172
            	try{
1173
            		UUID uuid = dao.delete(taxon);
1174

    
1175
            	}catch(Exception e){
1176
            		result.addException(e);
1177
            		result.setError();
1178

    
1179
            	}
1180
            } else {
1181
            	result.setError();
1182
            	result.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1183

    
1184
            }
1185
        }
1186

    
1187
        return result;
1188

    
1189
    }
1190

    
1191
    private String checkForReferences(Taxon taxon){
1192
        Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1193
        for (CdmBase referencingObject : referencingObjects){
1194
            //IIdentificationKeys (Media, Polytomous, MultiAccess)
1195
            if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1196
                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";
1197

    
1198
                return message;
1199
            }
1200

    
1201

    
1202
           /* //PolytomousKeyNode
1203
            if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1204
                String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1205
                return message;
1206
            }*/
1207

    
1208
            //TaxonInteraction
1209
            if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1210
                String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1211
                return message;
1212
            }
1213

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

    
1220
        }
1221

    
1222
        referencingObjects = null;
1223
        return null;
1224
    }
1225

    
1226
    private boolean checkForPolytomousKeys(Taxon taxon){
1227
        boolean result = false;
1228
        List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1229
        if (!list.isEmpty()) {
1230
            result = true;
1231
        }
1232
        return result;
1233
    }
1234

    
1235
    @Override
1236
    @Transactional(readOnly = false)
1237
    public DeleteResult delete(UUID synUUID){
1238
    	DeleteResult result = new DeleteResult();
1239
    	Synonym syn = (Synonym)dao.load(synUUID);
1240

    
1241
        return this.deleteSynonym(syn, null);
1242
    }
1243

    
1244

    
1245
    @Override
1246
    @Transactional(readOnly = false)
1247
    public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1248
        return deleteSynonym(synonym, null, config);
1249

    
1250
    }
1251

    
1252
    @Override
1253
    @Transactional(readOnly = false)
1254
    public DeleteResult deleteSynonym(UUID synonymUuid, SynonymDeletionConfigurator config) {
1255
        return deleteSynonym((Synonym)dao.load(synonymUuid), config);
1256

    
1257
    }
1258

    
1259
    @Transactional(readOnly = false)
1260
    @Override
1261
    public DeleteResult deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1262
        DeleteResult result = new DeleteResult();
1263
    	if (synonym == null){
1264
    		result.setAbort();
1265
    		result.addException(new Exception("The synonym was already deleted."));
1266
    		return result;
1267
        }
1268

    
1269
        if (config == null){
1270
            config = new SynonymDeletionConfigurator();
1271
        }
1272

    
1273
        result = isDeletable(synonym, config);
1274

    
1275

    
1276
        if (result.isOk()){
1277

    
1278
            synonym = HibernateProxyHelper.deproxy(this.load(synonym.getUuid()), Synonym.class);
1279

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

    
1294
            //TODO remove name from homotypical group?
1295

    
1296
            //remove synonym (if necessary)
1297

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

    
1304
                //remove name if possible (and required)
1305
                if (name != null && config.isDeleteNameIfPossible()){
1306

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

    
1313
                }
1314

    
1315
            }else {
1316
            	result.setError();
1317
            	result.addException(new ReferencedObjectUndeletableException("Synonym can not be deleted it is used in an other synonymRelationship."));
1318
                return result;
1319
            }
1320

    
1321

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

    
1334

    
1335
    }
1336

    
1337
    @Override
1338
    public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1339

    
1340
        return this.dao.findIdenticalNamesNew(propertyPath);
1341
    }
1342

    
1343
    @Override
1344
    public String getPhylumName(TaxonNameBase name){
1345
        return this.dao.getPhylumName(name);
1346
    }
1347

    
1348
    @Override
1349
    public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1350
        return dao.deleteSynonymRelationships(syn, taxon);
1351
    }
1352

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

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

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

    
1371
    @Override
1372
    public Taxon findBestMatchingTaxon(String taxonName) {
1373
        MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1374
        config.setTaxonNameTitle(taxonName);
1375
        return findBestMatchingTaxon(config);
1376
    }
1377

    
1378
    @Override
1379
    public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1380

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

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

    
1416
                }else{  //not Taxon.class
1417
                    continue;
1418
                }
1419
                countEqualCandidates++;
1420

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

    
1432

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

    
1455
        } catch (Exception e){
1456
            logger.error(e);
1457
            e.printStackTrace();
1458
        }
1459

    
1460
        return bestCandidate;
1461
    }
1462

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

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

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

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

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

    
1523
        HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1524
        int hgSize = homotypicGroup.getTypifiedNames().size();
1525
        boolean isSingleInGroup = !(hgSize > 1);
1526

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

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

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

    
1589
    @Override
1590
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon(Integer limit, String pattern) {
1591
        return dao.getUuidAndTitleCacheTaxon(limit, pattern);
1592
    }
1593

    
1594
    @Override
1595
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym(Integer limit, String pattern) {
1596
        return dao.getUuidAndTitleCacheSynonym(limit, pattern);
1597
    }
1598

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

    
1605

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

    
1608
        // --- execute search
1609
        TopGroups<BytesRef> topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1610

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

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

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

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

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

    
1631
        // --- execute search
1632
        TopGroups<BytesRef> topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1633

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

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

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

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

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

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

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

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

    
1677
        BooleanQuery textQuery = textQueryBuilder.build();
1678
        if(textQuery.clauses().size() > 0) {
1679
            finalQueryBuilder.add(textQuery, Occur.MUST);
1680
        }
1681

    
1682

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

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

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

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

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

    
1734
        Builder finalQueryBuilder = new Builder();
1735

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

    
1739
        Builder joinFromQueryBuilder = new Builder();
1740
        joinFromQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1741
        joinFromQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1742
        Query joinQuery = taxonBaseQueryFactory.newJoinQuery(TaxonRelationship.class, fromField, false, joinFromQueryBuilder.build(), toField, null, ScoreMode.Max);
1743

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

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

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

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

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

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

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

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

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

    
1809

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

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

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

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

    
1823

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

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

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

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

    
1856
        Builder multiIndexByAreaFilterBuilder = new Builder();
1857

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

    
1886
                //TODO replace by createByDistributionJoinQuery
1887
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1888
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class, fromField, true, byDistributionQuery, toField, Taxon.class, ScoreMode.None);
1889
                multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
1890

    
1891
            }
1892
        }
1893

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

    
1913
            luceneSearches.add(byCommonNameSearch);
1914

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

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

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

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

    
1976
                //TODO replace by createByDistributionJoinQuery
1977
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1978
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class, fromField, true, byDistributionQuery, toField, null, ScoreMode.None);
1979

    
1980
//                debug code for bug described above
1981
                //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1982
//                DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1983
//                System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1984

    
1985
                multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
1986
            }
1987
        }
1988

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

    
1992

    
1993
        if(addDistributionFilter){
1994

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

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

    
2014

    
2015
        // --- execute search
2016
        TopGroups<BytesRef> 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.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
     * @param toType toType
2033
     *      Optional parameter. Only used for debugging to print the toType documents
2034
     * @param asFilter TODO
2035
     * @return
2036
     * @throws IOException
2037
     */
2038
    protected Query createByDistributionJoinQuery(
2039
            List<NamedArea> namedAreaList,
2040
            List<PresenceAbsenceTerm> distributionStatusList,
2041
            QueryFactory queryFactory, Class<? extends CdmBase> toType, boolean asFilter
2042
            ) throws IOException {
2043

    
2044
        String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2045
        String toField = "id"; // id in toType usually this is the TaxonBase index
2046

    
2047
        BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2048

    
2049
        ScoreMode scoreMode = asFilter ?  ScoreMode.None : ScoreMode.Max;
2050

    
2051
        Query taxonAreaJoinQuery = queryFactory.newJoinQuery(Distribution.class, fromField, false, byDistributionQuery, toField, toType, scoreMode);
2052

    
2053
        return taxonAreaJoinQuery;
2054
    }
2055

    
2056
    /**
2057
     * @param namedAreaList
2058
     * @param distributionStatusList
2059
     * @param queryFactory
2060
     * @return
2061
     */
2062
    private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2063
            List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2064
        Builder areaQueryBuilder = new Builder();
2065
        // area field from Distribution
2066
        areaQueryBuilder.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2067

    
2068
        // status field from Distribution
2069
        if(distributionStatusList != null && distributionStatusList.size() > 0){
2070
            areaQueryBuilder.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2071
        }
2072

    
2073
        BooleanQuery areaQuery = areaQueryBuilder.build();
2074
        logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2075
        return areaQuery;
2076
    }
2077

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

    
2093
        Builder finalQueryBuilder = new Builder();
2094

    
2095
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2096

    
2097
        // FIXME is this query factory using the wrong type?
2098
        QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2099

    
2100
        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false)};
2101
        luceneSearch.setSortFields(sortFields);
2102

    
2103

    
2104
        Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory, null, false);
2105

    
2106
        finalQueryBuilder.add(byAreaQuery, Occur.MUST);
2107

    
2108
        if(classification != null){
2109
            finalQueryBuilder.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2110
        }
2111
        BooleanQuery finalQuery = finalQueryBuilder.build();
2112
        logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2113
        luceneSearch.setQuery(finalQuery);
2114

    
2115
        return luceneSearch;
2116
    }
2117

    
2118
    @Override
2119
    public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2120
            Class<? extends DescriptionElementBase> clazz, String queryString,
2121
            Classification classification, List<Feature> features, List<Language> languages,
2122
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2123

    
2124

    
2125
        LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2126

    
2127
        // --- execute search
2128
        TopGroups<BytesRef> topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2129

    
2130
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2131
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2132

    
2133
        // --- initialize taxa, highlight matches ....
2134
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2135
        @SuppressWarnings("rawtypes")
2136
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2137
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2138

    
2139
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
2140
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2141

    
2142
    }
2143

    
2144

    
2145
    @Override
2146
    public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2147
            Classification classification, List<Language> languages, boolean highlightFragments,
2148
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2149

    
2150
        LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2151
        LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2152

    
2153
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2154

    
2155
        // --- execute search
2156
        TopGroups<BytesRef> topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2157

    
2158
        // --- initialize taxa, highlight matches ....
2159
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2160

    
2161
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2162
        idFieldMap.put(CdmBaseType.TAXON, "id");
2163
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2164

    
2165
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2166
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2167

    
2168
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
2169
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2170

    
2171
    }
2172

    
2173

    
2174
    /**
2175
     * @param clazz
2176
     * @param queryString
2177
     * @param classification
2178
     * @param features
2179
     * @param languages
2180
     * @param highlightFragments
2181
     * @param directorySelectClass
2182
     * @return
2183
     */
2184
    protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2185
            String queryString, Classification classification, List<Feature> features,
2186
            List<Language> languages, boolean highlightFragments) {
2187

    
2188
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2189
        QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2190

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

    
2193
        BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2194
                languages, descriptionElementQueryFactory);
2195

    
2196
        luceneSearch.setSortFields(sortFields);
2197
        luceneSearch.setCdmTypRestriction(clazz);
2198
        luceneSearch.setQuery(finalQuery);
2199
        if(highlightFragments){
2200
            luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2201
        }
2202

    
2203
        return luceneSearch;
2204
    }
2205

    
2206
    /**
2207
     * @param queryString
2208
     * @param classification
2209
     * @param features
2210
     * @param languages
2211
     * @param descriptionElementQueryFactory
2212
     * @return
2213
     */
2214
    private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2215
            List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2216
        Builder finalQueryBuilder = new Builder();
2217
        Builder textQueryBuilder = new Builder();
2218
        textQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2219

    
2220
        // common name
2221
        Builder nameQueryBuilder = new Builder();
2222
        if(languages == null || languages.size() == 0){
2223
            nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2224
        } else {
2225
            Builder languageSubQueryBuilder = new Builder();
2226
            for(Language lang : languages){
2227
                languageSubQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("language.uuid",  lang.getUuid().toString(), false), Occur.SHOULD);
2228
            }
2229
            nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2230
            nameQueryBuilder.add(languageSubQueryBuilder.build(), Occur.MUST);
2231
        }
2232
        textQueryBuilder.add(nameQueryBuilder.build(), Occur.SHOULD);
2233

    
2234

    
2235
        // text field from TextData
2236
        textQueryBuilder.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2237

    
2238
        // --- TermBase fields - by representation ----
2239
        // state field from CategoricalData
2240
        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2241

    
2242
        // state field from CategoricalData
2243
        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2244

    
2245
        // area field from Distribution
2246
        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2247

    
2248
        // status field from Distribution
2249
        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2250

    
2251
        finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
2252
        // --- classification ----
2253

    
2254
        if(classification != null){
2255
            finalQueryBuilder.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2256
        }
2257

    
2258
        // --- IdentifieableEntity fields - by uuid
2259
        if(features != null && features.size() > 0 ){
2260
            finalQueryBuilder.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2261
        }
2262

    
2263
        // the description must be associated with a taxon
2264
        finalQueryBuilder.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2265

    
2266
        BooleanQuery finalQuery = finalQueryBuilder.build();
2267
        logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2268
        return finalQuery;
2269
    }
2270

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

    
2286
        if(stringBuilder == null){
2287
            stringBuilder = new StringBuilder();
2288
        }
2289
        if(languages == null || languages.size() == 0){
2290
            stringBuilder.append(name + ".ALL:(%1$s) ");
2291
        } else {
2292
            for(Language lang : languages){
2293
                stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2294
            }
2295
        }
2296
        return stringBuilder;
2297
    }
2298

    
2299
    @Override
2300
    public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2301
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2302
        List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2303

    
2304
        HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2305

    
2306

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

    
2323
        for (TaxonNode node: nodes){
2324
           // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2325
           // List<String> synonymsEpithet = new ArrayList<String>();
2326

    
2327
            if (node.getClassification().equals(classification)){
2328
                if (!node.isTopmostNode()){
2329
                    TaxonNode parent = node.getParent();
2330
                    parent = HibernateProxyHelper.deproxy(parent);
2331
                    TaxonNameBase<?,?> parentName =  parent.getTaxon().getName();
2332
                    ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2333
                    Taxon parentTaxon = HibernateProxyHelper.deproxy(parent.getTaxon());
2334
                    Rank rankOfTaxon = taxonName.getRank();
2335

    
2336

    
2337
                    //create inferred synonyms for species, subspecies
2338
                    if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2339

    
2340
                        Synonym inferredEpithet = null;
2341
                        Synonym inferredGenus = null;
2342
                        Synonym potentialCombination = null;
2343

    
2344
                        List<String> propertyPaths = new ArrayList<String>();
2345
                        propertyPaths.add("synonym");
2346
                        propertyPaths.add("synonym.name");
2347
                        List<OrderHint> orderHints = new ArrayList<OrderHint>();
2348
                        orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2349

    
2350
                        List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2351
                        List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2352

    
2353
                        List<TaxonRelationship> taxonRelListParent = null;
2354
                        List<TaxonRelationship> taxonRelListTaxon = null;
2355
                        if (doWithMisappliedNames){
2356
                            taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2357
                            taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2358
                        }
2359

    
2360

    
2361
                        if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2362

    
2363

    
2364
                            for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2365
                                Synonym syn = synonymRelationOfParent.getSynonym();
2366

    
2367
                                inferredEpithet = createInferredEpithets(taxon,
2368
                                        zooHashMap, taxonName, epithetOfTaxon,
2369
                                        infragenericEpithetOfTaxon,
2370
                                        infraspecificEpithetOfTaxon,
2371
                                        taxonNames, parentName,
2372
                                        syn);
2373

    
2374

    
2375
                                inferredSynonyms.add(inferredEpithet);
2376
                                zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2377
                                taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2378
                            }
2379

    
2380
                            if (doWithMisappliedNames){
2381

    
2382
                                for (TaxonRelationship taxonRelationship: taxonRelListParent){
2383
                                     Taxon misappliedName = taxonRelationship.getFromTaxon();
2384

    
2385
                                     inferredEpithet = createInferredEpithets(taxon,
2386
                                             zooHashMap, taxonName, epithetOfTaxon,
2387
                                             infragenericEpithetOfTaxon,
2388
                                             infraspecificEpithetOfTaxon,
2389
                                             taxonNames, parentName,
2390
                                             misappliedName);
2391

    
2392
                                    inferredSynonyms.add(inferredEpithet);
2393
                                    zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2394
                                     taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2395
                                }
2396
                            }
2397

    
2398
                            if (!taxonNames.isEmpty()){
2399
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2400
                            ZoologicalName name;
2401
                            if (!synNotInCDM.isEmpty()){
2402
                                inferredSynonymsToBeRemoved.clear();
2403

    
2404
                                for (Synonym syn :inferredSynonyms){
2405
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2406
                                    if (!synNotInCDM.contains(name.getNameCache())){
2407
                                        inferredSynonymsToBeRemoved.add(syn);
2408
                                    }
2409
                                }
2410

    
2411
                                // Remove identified Synonyms from inferredSynonyms
2412
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2413
                                    inferredSynonyms.remove(synonym);
2414
                                }
2415
                            }
2416
                        }
2417

    
2418
                    }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2419

    
2420

    
2421
                        for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2422
                            TaxonNameBase synName;
2423
                            ZoologicalName inferredSynName;
2424

    
2425
                            Synonym syn = synonymRelationOfTaxon.getSynonym();
2426
                            inferredGenus = createInferredGenus(taxon,
2427
                                    zooHashMap, taxonName, epithetOfTaxon,
2428
                                    genusOfTaxon, taxonNames, zooParentName, syn);
2429

    
2430
                            inferredSynonyms.add(inferredGenus);
2431
                            zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2432
                            taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2433

    
2434

    
2435
                        }
2436

    
2437
                        if (doWithMisappliedNames){
2438

    
2439
                            for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2440
                                Taxon misappliedName = taxonRelationship.getFromTaxon();
2441
                                inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
2442

    
2443
                                inferredSynonyms.add(inferredGenus);
2444
                                zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2445
                                 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2446
                            }
2447
                        }
2448

    
2449

    
2450
                        if (!taxonNames.isEmpty()){
2451
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2452
                            ZoologicalName name;
2453
                            if (!synNotInCDM.isEmpty()){
2454
                                inferredSynonymsToBeRemoved.clear();
2455

    
2456
                                for (Synonym syn :inferredSynonyms){
2457
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2458
                                    if (!synNotInCDM.contains(name.getNameCache())){
2459
                                        inferredSynonymsToBeRemoved.add(syn);
2460
                                    }
2461
                                }
2462

    
2463
                                // Remove identified Synonyms from inferredSynonyms
2464
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2465
                                    inferredSynonyms.remove(synonym);
2466
                                }
2467
                            }
2468
                        }
2469

    
2470
                    }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2471

    
2472
                        Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2473
                        ZoologicalName inferredSynName;
2474
                        //for all synonyms of the parent...
2475
                        for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2476
                            TaxonNameBase synName;
2477
                            Synonym synParent = synonymRelationOfParent.getSynonym();
2478
                            synName = synParent.getName();
2479

    
2480
                            HibernateProxyHelper.deproxy(synParent);
2481

    
2482
                            // Set the sourceReference
2483
                            sourceReference = synParent.getSec();
2484

    
2485
                            // Determine the idInSource
2486
                            String idInSourceParent = getIdInSource(synParent);
2487

    
2488
                            ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2489
                            String synParentGenus = parentSynZooName.getGenusOrUninomial();
2490
                            String synParentInfragenericName = null;
2491
                            String synParentSpecificEpithet = null;
2492

    
2493
                            if (parentSynZooName.isInfraGeneric()){
2494
                                synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2495
                            }
2496
                            if (parentSynZooName.isSpecies()){
2497
                                synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2498
                            }
2499

    
2500
                           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2501
                                synonymsGenus.put(synGenusName, idInSource);
2502
                            }*/
2503

    
2504
                            //for all synonyms of the taxon
2505

    
2506
                            for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2507

    
2508
                                Synonym syn = synonymRelationOfTaxon.getSynonym();
2509
                                ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2510
                                potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2511
                                        synParentGenus,
2512
                                        synParentInfragenericName,
2513
                                        synParentSpecificEpithet, syn, zooHashMap);
2514

    
2515
                                taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2516
                                inferredSynonyms.add(potentialCombination);
2517
                                zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2518
                                 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2519

    
2520
                            }
2521

    
2522

    
2523
                        }
2524

    
2525
                        if (doWithMisappliedNames){
2526

    
2527
                            for (TaxonRelationship parentRelationship: taxonRelListParent){
2528

    
2529
                                TaxonNameBase misappliedParentName;
2530

    
2531
                                Taxon misappliedParent = parentRelationship.getFromTaxon();
2532
                                misappliedParentName = misappliedParent.getName();
2533

    
2534
                                HibernateProxyHelper.deproxy(misappliedParent);
2535

    
2536
                                // Set the sourceReference
2537
                                sourceReference = misappliedParent.getSec();
2538

    
2539
                                // Determine the idInSource
2540
                                String idInSourceParent = getIdInSource(misappliedParent);
2541

    
2542
                                ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2543
                                String synParentGenus = parentSynZooName.getGenusOrUninomial();
2544
                                String synParentInfragenericName = null;
2545
                                String synParentSpecificEpithet = null;
2546

    
2547
                                if (parentSynZooName.isInfraGeneric()){
2548
                                    synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2549
                                }
2550
                                if (parentSynZooName.isSpecies()){
2551
                                    synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2552
                                }
2553

    
2554

    
2555
                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2556
                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
2557
                                    ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2558
                                    potentialCombination = createPotentialCombination(
2559
                                            idInSourceParent, parentSynZooName, zooMisappliedName,
2560
                                            synParentGenus,
2561
                                            synParentInfragenericName,
2562
                                            synParentSpecificEpithet, misappliedName, zooHashMap);
2563

    
2564

    
2565
                                    taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2566
                                    inferredSynonyms.add(potentialCombination);
2567
                                    zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2568
                                     taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2569
                                }
2570
                            }
2571
                        }
2572

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

    
2602
        }
2603

    
2604
        return inferredSynonyms;
2605
    }
2606

    
2607
    private Synonym createPotentialCombination(String idInSourceParent,
2608
            ZoologicalName parentSynZooName, 	ZoologicalName zooSynName, String synParentGenus,
2609
            String synParentInfragenericName, String synParentSpecificEpithet,
2610
            TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2611
        Synonym potentialCombination;
2612
        Reference sourceReference;
2613
        ZoologicalName inferredSynName;
2614
        HibernateProxyHelper.deproxy(syn);
2615

    
2616
        // Set sourceReference
2617
        sourceReference = syn.getSec();
2618
        if (sourceReference == null){
2619
            logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2620
            //TODO:Remove
2621
            if (!parentSynZooName.getTaxa().isEmpty()){
2622
                TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2623

    
2624
                sourceReference = taxon.getSec();
2625
            }
2626
        }
2627
        String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2628

    
2629
        String synTaxonInfraSpecificName= null;
2630

    
2631
        if (parentSynZooName.isSpecies()){
2632
            synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2633
        }
2634

    
2635
        /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2636
            synonymsEpithet.add(epithetName);
2637
        }*/
2638

    
2639
        //create potential combinations...
2640
        inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2641

    
2642
        inferredSynName.setGenusOrUninomial(synParentGenus);
2643
        if (zooSynName.isSpecies()){
2644
              inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2645
              if (parentSynZooName.isInfraGeneric()){
2646
                  inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2647
              }
2648
        }
2649
        if (zooSynName.isInfraSpecific()){
2650
            inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2651
            inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2652
        }
2653
        if (parentSynZooName.isInfraGeneric()){
2654
            inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2655
        }
2656

    
2657

    
2658
        potentialCombination = Synonym.NewInstance(inferredSynName, null);
2659

    
2660
        // Set the sourceReference
2661
        potentialCombination.setSec(sourceReference);
2662

    
2663

    
2664
        // Determine the idInSource
2665
        String idInSourceSyn= getIdInSource(syn);
2666

    
2667
        if (idInSourceParent != null && idInSourceSyn != null) {
2668
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2669
            inferredSynName.addSource(originalSource);
2670
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2671
            potentialCombination.addSource(originalSource);
2672
        }
2673

    
2674
        return potentialCombination;
2675
    }
2676

    
2677
    private Synonym createInferredGenus(Taxon taxon,
2678
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2679
            String epithetOfTaxon, String genusOfTaxon,
2680
            List<String> taxonNames, ZoologicalName zooParentName,
2681
            TaxonBase syn) {
2682

    
2683
        Synonym inferredGenus;
2684
        TaxonNameBase synName;
2685
        ZoologicalName inferredSynName;
2686
        synName =syn.getName();
2687
        HibernateProxyHelper.deproxy(syn);
2688

    
2689
        // Determine the idInSource
2690
        String idInSourceSyn = getIdInSource(syn);
2691
        String idInSourceTaxon = getIdInSource(taxon);
2692
        // Determine the sourceReference
2693
        Reference sourceReference = syn.getSec();
2694

    
2695
        //logger.warn(sourceReference.getTitleCache());
2696

    
2697
        synName = syn.getName();
2698
        ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2699
        String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2700
                     /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2701
            synonymsEpithet.add(synSpeciesEpithetName);
2702
        }*/
2703

    
2704
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2705
        //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...
2706

    
2707

    
2708
        inferredSynName.setGenusOrUninomial(genusOfTaxon);
2709
        if (zooParentName.isInfraGeneric()){
2710
            inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2711
        }
2712

    
2713
        if (taxonName.isSpecies()){
2714
            inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2715
        }
2716
        if (taxonName.isInfraSpecific()){
2717
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2718
            inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2719
        }
2720

    
2721

    
2722
        inferredGenus = Synonym.NewInstance(inferredSynName, null);
2723

    
2724
        // Set the sourceReference
2725
        inferredGenus.setSec(sourceReference);
2726

    
2727
        // Add the original source
2728
        if (idInSourceSyn != null && idInSourceTaxon != null) {
2729
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2730
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2731
            inferredGenus.addSource(originalSource);
2732

    
2733
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2734
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2735
            inferredSynName.addSource(originalSource);
2736
            originalSource = null;
2737

    
2738
        }else{
2739
            logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2740
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2741
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2742
            inferredGenus.addSource(originalSource);
2743

    
2744
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2745
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2746
            inferredSynName.addSource(originalSource);
2747
            originalSource = null;
2748
        }
2749

    
2750
        taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2751

    
2752
        return inferredGenus;
2753
    }
2754

    
2755
    private Synonym createInferredEpithets(Taxon taxon,
2756
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2757
            String epithetOfTaxon, String infragenericEpithetOfTaxon,
2758
            String infraspecificEpithetOfTaxon, List<String> taxonNames,
2759
            TaxonNameBase parentName, TaxonBase syn) {
2760

    
2761
        Synonym inferredEpithet;
2762
        TaxonNameBase<?,?> synName;
2763
        ZoologicalName inferredSynName;
2764
        HibernateProxyHelper.deproxy(syn);
2765

    
2766
        // Determine the idInSource
2767
        String idInSourceSyn = getIdInSource(syn);
2768
        String idInSourceTaxon =  getIdInSource(taxon);
2769
        // Determine the sourceReference
2770
        Reference sourceReference = syn.getSec();
2771

    
2772
        if (sourceReference == null){
2773
             logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2774
             sourceReference = taxon.getSec();
2775
        }
2776

    
2777
        synName = syn.getName();
2778
        ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2779
        String synGenusName = zooSynName.getGenusOrUninomial();
2780
        String synInfraGenericEpithet = null;
2781
        String synSpecificEpithet = null;
2782

    
2783
        if (zooSynName.getInfraGenericEpithet() != null){
2784
            synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2785
        }
2786

    
2787
        if (zooSynName.isInfraSpecific()){
2788
            synSpecificEpithet = zooSynName.getSpecificEpithet();
2789
        }
2790

    
2791
                     /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2792
            synonymsGenus.put(synGenusName, idInSource);
2793
        }*/
2794

    
2795
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2796

    
2797
        // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2798
        if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2799
            logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2800
        }
2801
        inferredSynName.setGenusOrUninomial(synGenusName);
2802

    
2803
        if (parentName.isInfraGeneric()){
2804
            inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2805
        }
2806
        if (taxonName.isSpecies()){
2807
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2808
        }else if (taxonName.isInfraSpecific()){
2809
            inferredSynName.setSpecificEpithet(synSpecificEpithet);
2810
            inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2811
        }
2812

    
2813
        inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2814

    
2815
        // Set the sourceReference
2816
        inferredEpithet.setSec(sourceReference);
2817

    
2818
        /* Add the original source
2819
        if (idInSource != null) {
2820
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2821

    
2822
            // Add the citation
2823
            Reference citation = getCitation(syn);
2824
            if (citation != null) {
2825
                originalSource.setCitation(citation);
2826
                inferredEpithet.addSource(originalSource);
2827
            }
2828
        }*/
2829
        String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2830

    
2831

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

    
2835
        inferredEpithet.addSource(originalSource);
2836

    
2837
        originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2838
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2839

    
2840
        inferredSynName.addSource(originalSource);
2841

    
2842

    
2843

    
2844
        taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2845

    
2846
        return inferredEpithet;
2847
    }
2848

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

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

    
2890

    
2891
        return idInSource;
2892
    }
2893

    
2894

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

    
2911
        return citation;
2912
    }
2913

    
2914
    @Override
2915
    public List<Synonym>  createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2916
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2917

    
2918
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2919
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2920
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2921

    
2922
        return inferredSynonyms;
2923
    }
2924

    
2925
    @Override
2926
    public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2927

    
2928
        // TODO quickly implemented, create according dao !!!!
2929
        Set<TaxonNode> nodes = new HashSet<TaxonNode>();
2930
        Set<Classification> classifications = new HashSet<Classification>();
2931
        List<Classification> list = new ArrayList<Classification>();
2932

    
2933
        if (taxonBase == null) {
2934
            return list;
2935
        }
2936

    
2937
        taxonBase = load(taxonBase.getUuid());
2938

    
2939
        if (taxonBase instanceof Taxon) {
2940
            nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
2941
        } else {
2942
            for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
2943
                nodes.addAll(taxon.getTaxonNodes());
2944
            }
2945
        }
2946
        for (TaxonNode node : nodes) {
2947
            classifications.add(node.getClassification());
2948
        }
2949
        list.addAll(classifications);
2950
        return list;
2951
    }
2952

    
2953
    @Override
2954
    @Transactional(readOnly = false)
2955
    public UpdateResult changeRelatedTaxonToSynonym(UUID fromTaxonUuid,
2956
            UUID toTaxonUuid,
2957
            TaxonRelationshipType oldRelationshipType,
2958
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
2959
        UpdateResult result = new UpdateResult();
2960
        Taxon fromTaxon = (Taxon) dao.load(fromTaxonUuid);
2961
        Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
2962
        Synonym synonym = changeRelatedTaxonToSynonym(fromTaxon, toTaxon, oldRelationshipType, synonymRelationshipType);
2963
        result.setCdmEntity(synonym);
2964
        result.addUpdatedObject(fromTaxon);
2965
        result.addUpdatedObject(toTaxon);
2966
        result.addUpdatedObject(synonym);
2967

    
2968
        return result;
2969
    }
2970

    
2971
    @Override
2972
    @Transactional(readOnly = false)
2973
    public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
2974
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
2975
        // Create new synonym using concept name
2976
                TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
2977
                Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
2978

    
2979
                // Remove concept relation from taxon
2980
                toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
2981

    
2982

    
2983

    
2984

    
2985
                // Create a new synonym for the taxon
2986
                SynonymRelationship synonymRelationship;
2987
                if (synonymRelationshipType != null
2988
                        && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
2989
                    synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
2990
                } else{
2991
                    synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
2992
                }
2993

    
2994
                this.saveOrUpdate(toTaxon);
2995
                //TODO: configurator and classification
2996
                TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
2997
                config.setDeleteNameIfPossible(false);
2998
                this.deleteTaxon(fromTaxon.getUuid(), config, null);
2999
                return synonymRelationship.getSynonym();
3000

    
3001
    }
3002

    
3003
    @Override
3004
    public DeleteResult isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
3005
        DeleteResult result = new DeleteResult();
3006
        Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
3007
        if (taxonBase instanceof Taxon){
3008
            TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3009
            result = isDeletableForTaxon(references, taxonConfig);
3010
        }else{
3011
            SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3012
            result = isDeletableForSynonym(references, synonymConfig);
3013
        }
3014
        return result;
3015
    }
3016

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

    
3029
        return result;
3030
    }
3031

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

    
3041
                }
3042
                if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3043
                    message = "The taxon can't be deleted as long as it has factual data.";
3044

    
3045
                }
3046

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

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

    
3055
                    } else{
3056
                        message = "The taxon can't be deleted as long as it belongs to a taxon node.";
3057

    
3058
                    }
3059
                }
3060
                if (ref instanceof PolytomousKeyNode){
3061
                    message = "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3062

    
3063
                }
3064

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

    
3068

    
3069
                }
3070

    
3071

    
3072
               /* //PolytomousKeyNode
3073
                if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3074
                    String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3075
                    return message;
3076
                }*/
3077

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

    
3082
                }
3083

    
3084
              //TaxonInteraction
3085
                if (ref.isInstanceOf(DeterminationEvent.class)){
3086
                    message = "Taxon can't be deleted as it is used in a determination event";
3087

    
3088
                }
3089

    
3090
            }
3091
            if (message != null){
3092
	            result.addException(new ReferencedObjectUndeletableException(message));
3093
	            result.addRelatedObject(ref);
3094
	            result.setAbort();
3095
            }
3096
        }
3097

    
3098
        return result;
3099
    }
3100

    
3101
    @Override
3102
    public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3103
        IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3104

    
3105
        //preliminary implementation
3106

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

    
3123
        Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3124
        int i = 0;
3125
        while((! related.isEmpty()) && i++ < 100){  //to avoid
3126
             related = makeRelatedIncluded(related, result, config);
3127
        }
3128

    
3129
        return result;
3130
    }
3131

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

    
3145
        //children
3146
        Set<TaxonNode> taxonNodes = new HashSet<TaxonNode>();
3147
        for (Taxon taxon: uncheckedTaxa){
3148
            taxonNodes.addAll(taxon.getTaxonNodes());
3149
        }
3150

    
3151
        Set<Taxon> children = new HashSet<Taxon>();
3152
        if (! config.onlyCongruent){
3153
            for (TaxonNode node: taxonNodes){
3154
                List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, null);
3155
                for (TaxonNode child : childNodes){
3156
                    children.add(child.getTaxon());
3157
                }
3158
            }
3159
            children.remove(null);  // just to be on the save side
3160
        }
3161

    
3162
        Iterator<Taxon> it = children.iterator();
3163
        while(it.hasNext()){
3164
            UUID uuid = it.next().getUuid();
3165
            if (existingTaxa.contains(uuid)){
3166
                it.remove();
3167
            }else{
3168
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3169
            }
3170
        }
3171

    
3172
        //concept relations
3173
        Set<Taxon> uncheckedAndChildren = new HashSet<Taxon>(uncheckedTaxa);
3174
        uncheckedAndChildren.addAll(children);
3175

    
3176
        Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3177

    
3178

    
3179
        Set<Taxon> result = new HashSet<Taxon>(relatedTaxa);
3180
        return result;
3181
    }
3182

    
3183
    /**
3184
     * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3185
     * @return the set of these computed taxa
3186
     */
3187
    private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3188
        Set<Taxon> result = new HashSet<Taxon>();
3189

    
3190
        for (Taxon taxon : unchecked){
3191
            Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3192
            Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3193

    
3194
            for (TaxonRelationship fromRel : fromRelations){
3195
                if (config.includeDoubtful == false && fromRel.isDoubtful()){
3196
                    continue;
3197
                }
3198
                if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3199
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
3200
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3201
                        ){
3202
                    result.add(fromRel.getToTaxon());
3203
                }
3204
            }
3205

    
3206
            for (TaxonRelationship toRel : toRelations){
3207
                if (config.includeDoubtful == false && toRel.isDoubtful()){
3208
                    continue;
3209
                }
3210
                if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
3211
                    result.add(toRel.getFromTaxon());
3212
                }
3213
            }
3214
        }
3215

    
3216
        Iterator<Taxon> it = result.iterator();
3217
        while(it.hasNext()){
3218
            UUID uuid = it.next().getUuid();
3219
            if (existingTaxa.contains(uuid)){
3220
                it.remove();
3221
            }else{
3222
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3223
            }
3224
        }
3225
        return result;
3226
    }
3227

    
3228
    @Override
3229
    public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3230
        List<TaxonBase> taxonList = dao.getTaxaByName(true, false, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, 0, config.getPropertyPath());
3231
        return taxonList;
3232
    }
3233

    
3234
	@Override
3235
	@Transactional(readOnly = true)
3236
	public <S extends TaxonBase> Pager<FindByIdentifierDTO<S>> findByIdentifier(
3237
			Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
3238
			MatchMode matchmode, boolean includeEntity, Integer pageSize,
3239
			Integer pageNumber,	List<String> propertyPaths) {
3240
		if (subtreeFilter == null){
3241
			return findByIdentifier(clazz, identifier, identifierType, matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3242
		}
3243

    
3244
		Integer numberOfResults = dao.countByIdentifier(clazz, identifier, identifierType, subtreeFilter, matchmode);
3245
        List<Object[]> daoResults = new ArrayList<Object[]>();
3246
        if(numberOfResults > 0) { // no point checking again
3247
        	daoResults = dao.findByIdentifier(clazz, identifier, identifierType, subtreeFilter,
3248
    				matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3249
        }
3250

    
3251
        List<FindByIdentifierDTO<S>> result = new ArrayList<FindByIdentifierDTO<S>>();
3252
        for (Object[] daoObj : daoResults){
3253
        	if (includeEntity){
3254
        		result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (S)daoObj[2]));
3255
        	}else{
3256
        		result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (UUID)daoObj[2], (String)daoObj[3]));
3257
        	}
3258
        }
3259
		return new DefaultPagerImpl<FindByIdentifierDTO<S>>(pageNumber, numberOfResults, pageSize, result);
3260
	}
3261

    
3262
	@Override
3263
	@Transactional(readOnly = false)
3264
	public UpdateResult moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, UUID newTaxonUUID, boolean moveHomotypicGroup,
3265
            SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
3266

    
3267
	    UpdateResult result = new UpdateResult();
3268
		Taxon newTaxon = (Taxon) dao.load(newTaxonUUID);
3269
		result = moveSynonymToAnotherTaxon(oldSynonymRelation, newTaxon, moveHomotypicGroup, newSynonymRelationshipType, reference, referenceDetail, keepReference);
3270

    
3271
		return result;
3272
	}
3273

    
3274
	@Override
3275
	public UpdateResult moveFactualDateToAnotherTaxon(UUID fromTaxonUuid, UUID toTaxonUuid){
3276
		UpdateResult result = new UpdateResult();
3277

    
3278
		Taxon fromTaxon = (Taxon)dao.load(fromTaxonUuid);
3279
		Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
3280
		  for(TaxonDescription description : fromTaxon.getDescriptions()){
3281
              //reload to avoid session conflicts
3282
              description = HibernateProxyHelper.deproxy(description, TaxonDescription.class);
3283

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

    
3301
          }
3302

    
3303

    
3304
		return result;
3305
	}
3306

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

    
3315
		return this.deleteSynonym(syn, taxon, config);
3316
	}
3317

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

    
3327
		return this.swapSynonymAndAcceptedTaxon(syn, taxon);
3328
	}
3329

    
3330

    
3331

    
3332
}
(90-90/97)