Project

General

Profile

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

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

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

    
24
import javax.persistence.EntityNotFoundException;
25

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

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

    
132

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

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

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

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

    
149
    @Autowired
150
    private ITaxonNodeDao taxonNodeDao;
151

    
152
    @Autowired
153
    private ITaxonNameDao nameDao;
154

    
155
    @Autowired
156
    private INameService nameService;
157

    
158
    @Autowired
159
    private IOccurrenceService occurrenceService;
160

    
161
    @Autowired
162
    private ITaxonNodeService nodeService;
163

    
164
    @Autowired
165
    private ICdmGenericDao genericDao;
166

    
167
    @Autowired
168
    private IDescriptionService descriptionService;
169

    
170
    @Autowired
171
    private IOrderedTermVocabularyDao orderedVocabularyDao;
172

    
173
    @Autowired
174
    private IOccurrenceDao occurrenceDao;
175

    
176
    @Autowired
177
    private IClassificationDao classificationDao;
178

    
179
    @Autowired
180
    private AbstractBeanInitializer beanInitializer;
181

    
182
    @Autowired
183
    private ILuceneIndexToolProvider luceneIndexToolProvider;
184

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

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

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

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

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

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

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

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

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

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

    
262

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

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

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

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

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

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

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

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

    
308
        return newAcceptedTaxon;
309
    }
310

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

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

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

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

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

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

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

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

    
372
        return fromTaxon;
373
    }
374

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

    
383

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

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

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

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

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

    
433
    }
434

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

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

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

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

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

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

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

    
471
        return results;
472
    }
473

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

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

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

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

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

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

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

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

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

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

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

    
531
        Synonym synonym = null;
532

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

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

    
550
            }
551
        }
552

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

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

    
561

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

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

    
572

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
671
        return result;
672

    
673
    }
674

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

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

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

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

    
697

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

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

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

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

    
722

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

    
728

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

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

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

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

    
751
        numberOfResults += numberTaxaResults;
752

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

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

    
773
        // Taxa from common names
774

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

    
790
        }
791

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
875

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

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

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

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

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

    
933

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

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

    
939
        return taxonMedia;
940
    }
941

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

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

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

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

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

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

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

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

    
1003
            // --- DeleteTaxonRelationships
1004
            if (! config.isDeleteTaxonRelationships()){
1005
                if (taxon.getTaxonRelations().size() > 0){
1006
                    String message = "Taxon can't be deleted as it is related to another taxon. " +
1007
                            "Remove taxon from all relations to other taxa prior to deletion.";
1008
                   // throw new ReferencedObjectUndeletableException(message);
1009
                }
1010
            } else{
1011
                for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
1012
                    if (config.isDeleteMisappliedNamesAndInvalidDesignations()){
1013
                        if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){
1014
                            if (taxon.equals(taxRel.getToTaxon())){
1015

    
1016
                                this.deleteTaxon(taxRel.getFromTaxon().getUuid(), config, classificationUuid);
1017
                            }
1018
                        }
1019
                    }
1020
                    taxon.removeTaxonRelation(taxRel);
1021
                    /*if (taxFrom.equals(taxon)){
1022
                        try{
1023
                            this.deleteTaxon(taxTo, taxConf, classification);
1024
                        } catch(DataChangeNoRollbackException e){
1025
                            logger.debug("A related taxon will not be deleted." + e.getMessage());
1026
                        }
1027
                    } else {
1028
                        try{
1029
                            this.deleteTaxon(taxFrom, taxConf, classification);
1030
                        } catch(DataChangeNoRollbackException e){
1031
                            logger.debug("A related taxon will not be deleted." + e.getMessage());
1032
                        }
1033

    
1034
                    }*/
1035
                }
1036
            }
1037

    
1038
            //    	TaxonDescription
1039
            if (config.isDeleteDescriptions()){
1040
                Set<TaxonDescription> descriptions = taxon.getDescriptions();
1041
                List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1042
                for (TaxonDescription desc: descriptions){
1043
                    //TODO use description delete configurator ?
1044
                    //FIXME check if description is ALWAYS deletable
1045
                    if (desc.getDescribedSpecimenOrObservation() != null){
1046
                        String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1047
                                " which also describes specimens or abservations";
1048
                        //throw new ReferencedObjectUndeletableException(message);
1049
                    }
1050
                    removeDescriptions.add(desc);
1051

    
1052

    
1053
                }
1054
                for (TaxonDescription desc: removeDescriptions){
1055
                    taxon.removeDescription(desc);
1056
                    descriptionService.delete(desc);
1057
                }
1058
            }
1059

    
1060

    
1061
         /*   //check references with only reverse mapping
1062
        String message = checkForReferences(taxon);
1063
        if (message != null){
1064
            //throw new ReferencedObjectUndeletableException(message.toString());
1065
        }*/
1066

    
1067
         if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null )){
1068
                //if (taxon.getTaxonNodes().size() > 0){
1069
                   // message = "Taxon can't be deleted as it is used in a classification node. Remove taxon from all classifications prior to deletion or define a classification where it should be deleted or adapt the taxon deletion configurator.";
1070
                   // throw new ReferencedObjectUndeletableException(message);
1071
                //}
1072
         }else{
1073
                if (taxon.getTaxonNodes().size() != 0){
1074
                    Set<TaxonNode> nodes = taxon.getTaxonNodes();
1075
                    Iterator<TaxonNode> iterator = nodes.iterator();
1076
                    TaxonNode node = null;
1077
                    boolean deleteChildren;
1078
                    if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
1079
                        deleteChildren = true;
1080
                    }else {
1081
                        deleteChildren = false;
1082
                    }
1083
                    boolean success = true;
1084
                    if (!config.isDeleteInAllClassifications() && !(classification == null)){
1085
                        while (iterator.hasNext()){
1086
                            node = iterator.next();
1087
                            if (node.getClassification().equals(classification)){
1088
                                break;
1089
                            }
1090
                            node = null;
1091
                        }
1092
                        if (node != null){
1093
                            success =taxon.removeTaxonNode(node, deleteChildren);
1094
                            nodeService.delete(node);
1095
                        } else {
1096
                        	result.setError();
1097
                        	result.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
1098
                        }
1099
                    } else if (config.isDeleteInAllClassifications()){
1100
                        Set<ITaxonTreeNode> nodesList = new HashSet<ITaxonTreeNode>();
1101
                        nodesList.addAll(taxon.getTaxonNodes());
1102

    
1103
                            for (ITaxonTreeNode treeNode: nodesList){
1104
                                TaxonNode taxonNode = (TaxonNode) treeNode;
1105
                                if(!deleteChildren){
1106
                                   /* Object[] childNodes = taxonNode.getChildNodes().toArray();
1107
                                    //nodesList.addAll(taxonNode.getChildNodes());
1108
                                    for (Object childNode: childNodes){
1109
                                        TaxonNode childNodeCast = (TaxonNode) childNode;
1110
                                        deleteTaxon(childNodeCast.getTaxon(), config, classification);
1111

    
1112
                                    }
1113

    
1114
                                    /*for (TaxonNode childNode: taxonNode.getChildNodes()){
1115
                                        deleteTaxon(childNode.getTaxon(), config, classification);
1116

    
1117
                                    }
1118
                                   // taxon.removeTaxonNode(taxonNode);
1119
                                    //nodeService.delete(taxonNode);
1120
                                } else{
1121
                                    */
1122
                                    Object[] childNodes = taxonNode.getChildNodes().toArray();
1123
                                    for (Object childNode: childNodes){
1124
                                        TaxonNode childNodeCast = (TaxonNode) childNode;
1125
                                        taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
1126
                                    }
1127

    
1128
                                    //taxon.removeTaxonNode(taxonNode);
1129
                                }
1130
                            }
1131
                        config.getTaxonNodeConfig().setDeleteTaxon(false);
1132
                        DeleteResult resultNodes = nodeService.deleteTaxonNodes(nodesList, config);
1133
                        if (!resultNodes.isOk()){
1134
                        	result.addExceptions(resultNodes.getExceptions());
1135
                        	result.setStatus(resultNodes.getStatus());
1136
                        } else {
1137
                            result.addUpdatedObjects(resultNodes.getUpdatedObjects());
1138
                        }
1139
                    }
1140
                    if (!success){
1141
                        result.setError();
1142
                        result.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
1143
                    }
1144
                }
1145
            }
1146

    
1147

    
1148
             //PolytomousKey TODO
1149

    
1150

    
1151
            //TaxonNameBase
1152
            if (config.isDeleteNameIfPossible()){
1153

    
1154

    
1155
                    //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1156
                    TaxonNameBase name = (TaxonNameBase)HibernateProxyHelper.deproxy(taxon.getName());
1157
                    //check whether taxon will be deleted or not
1158
                    if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){
1159
                        taxon = (Taxon) HibernateProxyHelper.deproxy(taxon);
1160
                        //name.removeTaxonBase(taxon);
1161
                        //nameService.saveOrUpdate(name);
1162
                        taxon.setName(null);
1163
                        //dao.delete(taxon);
1164
                        DeleteResult nameResult = new DeleteResult();
1165

    
1166
                        //remove name if possible (and required)
1167
                        if (name != null && config.isDeleteNameIfPossible()){
1168
                        	nameResult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
1169
                        }
1170

    
1171
                        if (nameResult.isError() || nameResult.isAbort()){
1172
                        	//result.setError();
1173
                        	result.addRelatedObject(name);
1174
                        	result.addExceptions(nameResult.getExceptions());
1175
                        }
1176

    
1177
                    }
1178

    
1179
            }else {
1180
                taxon.setName(null);
1181
            }
1182

    
1183

    
1184
//        	TaxonDescription
1185
           /* Set<TaxonDescription> descriptions = taxon.getDescriptions();
1186

    
1187
            for (TaxonDescription desc: descriptions){
1188
                if (config.isDeleteDescriptions()){
1189
                    //TODO use description delete configurator ?
1190
                    //FIXME check if description is ALWAYS deletable
1191
                    taxon.removeDescription(desc);
1192
                    descriptionService.delete(desc);
1193
                }else{
1194
                    if (desc.getDescribedSpecimenOrObservations().size()>0){
1195
                        String message = "Taxon can't be deleted as it is used in a TaxonDescription" +
1196
                                " which also describes specimens or observations";
1197
                            throw new ReferencedObjectUndeletableException(message);
1198
    }
1199
                    }
1200
                }*/
1201

    
1202
            if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0)  ){
1203
            	try{
1204
            		UUID uuid = dao.delete(taxon);
1205

    
1206
            	}catch(Exception e){
1207
            		result.addException(e);
1208
            		result.setError();
1209

    
1210
            	}
1211
            } else {
1212
            	result.setError();
1213
            	result.addException(new Exception("The Taxon can't be deleted."));
1214

    
1215
            }
1216
        }
1217
//        }else {
1218
//        	List<Exception> exceptions = new ArrayList<Exception>();
1219
//        	for (String message: referencedObjects){
1220
//        		ReferencedObjectUndeletableException exception = new ReferencedObjectUndeletableException(message);
1221
//        		exceptions.add(exception);
1222
//        	}
1223
//        	result.addExceptions(exceptions);
1224
//        	result.setError();
1225
//
1226
//        }
1227
        return result;
1228

    
1229
    }
1230

    
1231
    private String checkForReferences(Taxon taxon){
1232
        Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1233
        for (CdmBase referencingObject : referencingObjects){
1234
            //IIdentificationKeys (Media, Polytomous, MultiAccess)
1235
            if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1236
                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";
1237

    
1238
                return message;
1239
            }
1240

    
1241

    
1242
           /* //PolytomousKeyNode
1243
            if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1244
                String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1245
                return message;
1246
            }*/
1247

    
1248
            //TaxonInteraction
1249
            if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1250
                String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1251
                return message;
1252
            }
1253

    
1254
          //TaxonInteraction
1255
            if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1256
                String message = "Taxon can't be deleted as it is used in a determination event";
1257
                return message;
1258
            }
1259

    
1260
        }
1261

    
1262
        referencingObjects = null;
1263
        return null;
1264
    }
1265

    
1266
    private boolean checkForPolytomousKeys(Taxon taxon){
1267
        boolean result = false;
1268
        List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1269
        if (!list.isEmpty()) {
1270
            result = true;
1271
        }
1272
        return result;
1273
    }
1274

    
1275
    @Override
1276
    @Transactional(readOnly = false)
1277
    public DeleteResult delete(UUID synUUID){
1278
    	DeleteResult result = new DeleteResult();
1279
    	Synonym syn = (Synonym)dao.load(synUUID);
1280

    
1281
        return this.deleteSynonym(syn, null);
1282
    }
1283

    
1284

    
1285
    @Override
1286
    @Transactional(readOnly = false)
1287
    public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1288
        return deleteSynonym(synonym, null, config);
1289

    
1290
    }
1291

    
1292
    @Override
1293
    @Transactional(readOnly = false)
1294
    public DeleteResult deleteSynonym(UUID synonymUuid, SynonymDeletionConfigurator config) {
1295
        return deleteSynonym((Synonym)dao.load(synonymUuid), config);
1296

    
1297
    }
1298

    
1299
    @Transactional(readOnly = false)
1300
    @Override
1301
    public DeleteResult deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1302
        DeleteResult result = new DeleteResult();
1303
    	if (synonym == null){
1304
    		result.setAbort();
1305
    		return result;
1306
        }
1307

    
1308
        if (config == null){
1309
            config = new SynonymDeletionConfigurator();
1310
        }
1311
        result = isDeletable(synonym, config);
1312

    
1313

    
1314
        if (result.isOk()){
1315

    
1316
            synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class);
1317

    
1318
            //remove synonymRelationship
1319
            Set<Taxon> taxonSet = new HashSet<Taxon>();
1320
            if (taxon != null){
1321
                taxonSet.add(taxon);
1322
            }else{
1323
                taxonSet.addAll(synonym.getAcceptedTaxa());
1324
            }
1325
            for (Taxon relatedTaxon : taxonSet){
1326
            	relatedTaxon = HibernateProxyHelper.deproxy(relatedTaxon, Taxon.class);
1327
                relatedTaxon.removeSynonym(synonym, false);
1328
                this.saveOrUpdate(relatedTaxon);
1329
            }
1330
            this.saveOrUpdate(synonym);
1331

    
1332
            //TODO remove name from homotypical group?
1333

    
1334
            //remove synonym (if necessary)
1335

    
1336
            result.addUpdatedObject(taxon);
1337
            if (synonym.getSynonymRelations().isEmpty()){
1338
                TaxonNameBase<?,?> name = synonym.getName();
1339
                synonym.setName(null);
1340
                dao.delete(synonym);
1341

    
1342
                //remove name if possible (and required)
1343
                if (name != null && config.isDeleteNameIfPossible()){
1344

    
1345
                        DeleteResult nameDeleteresult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
1346
                        if (nameDeleteresult.isAbort()){
1347
                        	result.addExceptions(nameDeleteresult.getExceptions());
1348
                        	result.addUpdatedObject(name);
1349
                        }
1350

    
1351
                }
1352

    
1353
            }else {
1354
            	result.setError();
1355
            	result.addException(new ReferencedObjectUndeletableException("Synonym can not be deleted it is used in a synonymRelationship."));
1356
                return result;
1357
            }
1358

    
1359

    
1360
        }
1361
        return result;
1362
//        else{
1363
//        	List<Exception> exceptions = new ArrayList<Exception>();
1364
//        	for (String message :messages){
1365
//        		exceptions.add(new ReferencedObjectUndeletableException(message));
1366
//        	}
1367
//        	result.setError();
1368
//        	result.addExceptions(exceptions);
1369
//            return result;
1370
//        }
1371

    
1372

    
1373
    }
1374

    
1375
    @Override
1376
    public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1377

    
1378
        return this.dao.findIdenticalNamesNew(propertyPath);
1379
    }
1380

    
1381
    @Override
1382
    public String getPhylumName(TaxonNameBase name){
1383
        return this.dao.getPhylumName(name);
1384
    }
1385

    
1386
    @Override
1387
    public long deleteSynonymRelationships(Synonym syn, Taxon taxon) {
1388
        return dao.deleteSynonymRelationships(syn, taxon);
1389
    }
1390

    
1391
    @Override
1392
    public long deleteSynonymRelationships(Synonym syn) {
1393
        return dao.deleteSynonymRelationships(syn, null);
1394
    }
1395

    
1396
    @Override
1397
    public List<SynonymRelationship> listSynonymRelationships(
1398
            TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1399
            List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1400
        Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1401

    
1402
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1403
        if(numberOfResults > 0) { // no point checking again
1404
            results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1405
        }
1406
        return results;
1407
    }
1408

    
1409
    @Override
1410
    public Taxon findBestMatchingTaxon(String taxonName) {
1411
        MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1412
        config.setTaxonNameTitle(taxonName);
1413
        return findBestMatchingTaxon(config);
1414
    }
1415

    
1416
    @Override
1417
    public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1418

    
1419
        Taxon bestCandidate = null;
1420
        try{
1421
            // 1. search for acceptet taxa
1422
            List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1423
            boolean bestCandidateMatchesSecUuid = false;
1424
            boolean bestCandidateIsInClassification = false;
1425
            int countEqualCandidates = 0;
1426
            for(TaxonBase taxonBaseCandidate : taxonList){
1427
                if(taxonBaseCandidate instanceof Taxon){
1428
                    Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1429
                    boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1430
                    if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1431
                        continue;
1432
                    }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1433
                        bestCandidate = newCanditate;
1434
                        countEqualCandidates = 1;
1435
                        bestCandidateMatchesSecUuid = true;
1436
                        continue;
1437
                    }
1438

    
1439
                    boolean newCandidateInClassification = isInClassification(newCanditate, config);
1440
                    if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1441
                        continue;
1442
                    }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1443
                        bestCandidate = newCanditate;
1444
                        countEqualCandidates = 1;
1445
                        bestCandidateIsInClassification = true;
1446
                        continue;
1447
                    }
1448
                    if (bestCandidate == null){
1449
                        bestCandidate = newCanditate;
1450
                        countEqualCandidates = 1;
1451
                        continue;
1452
                    }
1453

    
1454
                }else{  //not Taxon.class
1455
                    continue;
1456
                }
1457
                countEqualCandidates++;
1458

    
1459
            }
1460
            if (bestCandidate != null){
1461
                if(countEqualCandidates > 1){
1462
                    logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1463
                    return bestCandidate;
1464
                } else {
1465
                    logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1466
                    return bestCandidate;
1467
                }
1468
            }
1469

    
1470

    
1471
            // 2. search for synonyms
1472
            if (config.isIncludeSynonyms()){
1473
                List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1474
                for(TaxonBase taxonBase : synonymList){
1475
                    if(taxonBase instanceof Synonym){
1476
                        Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1477
                        Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1478
                        if(!acceptetdCandidates.isEmpty()){
1479
                            bestCandidate = acceptetdCandidates.iterator().next();
1480
                            if(acceptetdCandidates.size() == 1){
1481
                                logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1482
                                return bestCandidate;
1483
                            } else {
1484
                                logger.info("using accepted Taxon " +  bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1485
                                return bestCandidate;
1486
                            }
1487
                            //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1488
                        }
1489
                    }
1490
                }
1491
            }
1492

    
1493
        } catch (Exception e){
1494
            logger.error(e);
1495
            e.printStackTrace();
1496
        }
1497

    
1498
        return bestCandidate;
1499
    }
1500

    
1501
    private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1502
        UUID configClassificationUuid = config.getClassificationUuid();
1503
        if (configClassificationUuid == null){
1504
            return false;
1505
        }
1506
        for (TaxonNode node : taxon.getTaxonNodes()){
1507
            UUID classUuid = node.getClassification().getUuid();
1508
            if (configClassificationUuid.equals(classUuid)){
1509
                return true;
1510
            }
1511
        }
1512
        return false;
1513
    }
1514

    
1515
    private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1516
        UUID configSecUuid = config.getSecUuid();
1517
        if (configSecUuid == null){
1518
            return false;
1519
        }
1520
        UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1521
        return configSecUuid.equals(taxonSecUuid);
1522
    }
1523

    
1524
    @Override
1525
    public Synonym findBestMatchingSynonym(String taxonName) {
1526
        List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1527
        if(! synonymList.isEmpty()){
1528
            Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1529
            if(synonymList.size() == 1){
1530
                logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1531
                return result;
1532
            } else {
1533
                logger.info("Several matching synonyms found. Using first: " +  result.getTitleCache());
1534
                return result;
1535
            }
1536
        }
1537
        return null;
1538
    }
1539

    
1540
    @Override
1541
    @Transactional(readOnly = false)
1542
    public SynonymRelationship moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation,
1543
            Taxon newTaxon,
1544
            boolean moveHomotypicGroup,
1545
            SynonymRelationshipType newSynonymRelationshipType,
1546
            Reference reference,
1547
            String referenceDetail,
1548
            boolean keepReference) throws HomotypicalGroupChangeException {
1549

    
1550
        Synonym synonym = (Synonym) dao.load(oldSynonymRelation.getSynonym().getUuid());
1551
        Taxon fromTaxon = (Taxon) dao.load(oldSynonymRelation.getAcceptedTaxon().getUuid());
1552
        //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1553
        TaxonNameBase<?,?> synonymName = synonym.getName();
1554
        TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1555
        //set default relationship type
1556
        if (newSynonymRelationshipType == null){
1557
            newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1558
        }
1559
        boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1560

    
1561
        HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1562
        int hgSize = homotypicGroup.getTypifiedNames().size();
1563
        boolean isSingleInGroup = !(hgSize > 1);
1564

    
1565
        if (! isSingleInGroup){
1566
            boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1567
            boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1568
            if (isHomotypicToAccepted){
1569
                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.";
1570
                String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1571
                message = String.format(message, homotypicRelatives);
1572
                throw new HomotypicalGroupChangeException(message);
1573
            }
1574
            if (! moveHomotypicGroup){
1575
                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.";
1576
                throw new HomotypicalGroupChangeException(message);
1577
            }
1578
        }else{
1579
            moveHomotypicGroup = true;  //single synonym always allows to moveCompleteGroup
1580
        }
1581
//        Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1582

    
1583
        SynonymRelationship result = null;
1584
        //move all synonyms to new taxon
1585
        List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1586
        for (Synonym syn: homotypicSynonyms){
1587
            Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1588
            for (SynonymRelationship synRelation : synRelations){
1589
                if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1590
                    Reference<?> newReference = reference;
1591
                    if (newReference == null && keepReference){
1592
                        newReference = synRelation.getCitation();
1593
                    }
1594
                    String newRefDetail = referenceDetail;
1595
                    if (newRefDetail == null && keepReference){
1596
                        newRefDetail = synRelation.getCitationMicroReference();
1597
                    }
1598
                    newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
1599
                    fromTaxon = HibernateProxyHelper.deproxy(fromTaxon, Taxon.class);
1600
                    SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1601
                    fromTaxon.removeSynonymRelation(synRelation, false);
1602
//
1603
                    //change homotypic group of synonym if relType is 'homotypic'
1604
//                	if (newRelTypeIsHomotypic){
1605
//                		newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1606
//                	}
1607
                    //set result
1608
                    if (synRelation.equals(oldSynonymRelation)){
1609
                        result = newSynRelation;
1610
                    }
1611
                }
1612
            }
1613

    
1614
        }
1615
        saveOrUpdate(fromTaxon);
1616
        saveOrUpdate(newTaxon);
1617
        //Assert that there is a result
1618
        if (result == null){
1619
            String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1620
            throw new IllegalStateException(message);
1621
        }
1622
        return result;
1623
    }
1624

    
1625
    @Override
1626
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon() {
1627
        return dao.getUuidAndTitleCacheTaxon();
1628
    }
1629

    
1630
    @Override
1631
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym() {
1632
        return dao.getUuidAndTitleCacheSynonym();
1633
    }
1634

    
1635
    @Override
1636
    public Pager<SearchResult<TaxonBase>> findByFullText(
1637
            Class<? extends TaxonBase> clazz, String queryString,
1638
            Classification classification, List<Language> languages,
1639
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1640

    
1641

    
1642
        LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1643

    
1644
        // --- execute search
1645
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1646

    
1647
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1648
        idFieldMap.put(CdmBaseType.TAXON, "id");
1649

    
1650
        // ---  initialize taxa, thighlight matches ....
1651
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1652
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1653
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1654

    
1655
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1656
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1657
    }
1658

    
1659
    @Override
1660
    public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
1661
            Classification classification,
1662
            Integer pageSize, Integer pageNumber,
1663
            List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1664

    
1665
        LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1666

    
1667
        // --- execute search
1668
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1669

    
1670
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1671
        idFieldMap.put(CdmBaseType.TAXON, "id");
1672

    
1673
        // ---  initialize taxa, thighlight matches ....
1674
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1675
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1676
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1677

    
1678
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
1679
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1680
    }
1681

    
1682
    /**
1683
     * @param clazz
1684
     * @param queryString
1685
     * @param classification
1686
     * @param languages
1687
     * @param highlightFragments
1688
     * @param sortFields TODO
1689
     * @param directorySelectClass
1690
     * @return
1691
     */
1692
    protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1693
            boolean highlightFragments, SortField[] sortFields) {
1694
        BooleanQuery finalQuery = new BooleanQuery();
1695
        BooleanQuery textQuery = new BooleanQuery();
1696

    
1697
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1698
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1699

    
1700
        if(sortFields == null){
1701
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
1702
        }
1703
        luceneSearch.setSortFields(sortFields);
1704

    
1705
        // ---- search criteria
1706
        luceneSearch.setCdmTypRestriction(clazz);
1707

    
1708
        if(!queryString.isEmpty() && !queryString.equals("*") && !queryString.equals("?") ) {
1709
            textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1710
            textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1711
        }
1712

    
1713
        if(textQuery.getClauses().length > 0) {
1714
            finalQuery.add(textQuery, Occur.MUST);
1715
        }
1716

    
1717

    
1718
        if(classification != null){
1719
            finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1720
        }
1721
        luceneSearch.setQuery(finalQuery);
1722

    
1723
        if(highlightFragments){
1724
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1725
        }
1726
        return luceneSearch;
1727
    }
1728

    
1729
    /**
1730
     * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1731
     * the BlockJoinQuery could be used. The latter might be more memory save but has the
1732
     * drawback of requiring to do the join an indexing time.
1733
     * see  http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1734
     *
1735
     * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1736
     * <ul>
1737
     * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1738
     * <li>inverse: {@link Direction.relatedFrom}:  TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1739
     * <ul>
1740
     * @param queryString
1741
     * @param classification
1742
     * @param languages
1743
     * @param highlightFragments
1744
     * @param sortFields TODO
1745
     *
1746
     * @return
1747
     * @throws IOException
1748
     */
1749
    protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1750
            boolean highlightFragments, SortField[] sortFields) throws IOException {
1751

    
1752
        String fromField;
1753
        String queryTermField;
1754
        String toField = "id"; // TaxonBase.uuid
1755

    
1756
        if(edge.isBidirectional()){
1757
            throw new RuntimeException("Bidirectional joining not supported!");
1758
        }
1759
        if(edge.isEvers()){
1760
            fromField = "relatedFrom.id";
1761
            queryTermField = "relatedFrom.titleCache";
1762
        } else if(edge.isInvers()) {
1763
            fromField = "relatedTo.id";
1764
            queryTermField = "relatedTo.titleCache";
1765
        } else {
1766
            throw new RuntimeException("Invalid direction: " + edge.getDirections());
1767
        }
1768

    
1769
        BooleanQuery finalQuery = new BooleanQuery();
1770

    
1771
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1772
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1773

    
1774
        BooleanQuery joinFromQuery = new BooleanQuery();
1775
        joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1776
        joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1777
        Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class);
1778

    
1779
        if(sortFields == null){
1780
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
1781
        }
1782
        luceneSearch.setSortFields(sortFields);
1783

    
1784
        finalQuery.add(joinQuery, Occur.MUST);
1785

    
1786
        if(classification != null){
1787
            finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1788
        }
1789
        luceneSearch.setQuery(finalQuery);
1790

    
1791
        if(highlightFragments){
1792
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1793
        }
1794
        return luceneSearch;
1795
    }
1796

    
1797
    @Override
1798
    public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1799
            EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1800
            Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
1801
            boolean highlightFragments, Integer pageSize,
1802
            Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1803
            throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1804

    
1805
        // FIXME: allow taxonomic ordering
1806
        //  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";
1807
        // this require building a special sort column by a special classBridge
1808
        if(highlightFragments){
1809
            logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1810
                    "currently not fully supported by this method and thus " +
1811
                    "may not work with common names and misapplied names.");
1812
        }
1813

    
1814
        // convert sets to lists
1815
        List<NamedArea> namedAreaList = null;
1816
        List<PresenceAbsenceTerm>distributionStatusList = null;
1817
        if(namedAreas != null){
1818
            namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1819
            namedAreaList.addAll(namedAreas);
1820
        }
1821
        if(distributionStatus != null){
1822
            distributionStatusList = new ArrayList<PresenceAbsenceTerm>(distributionStatus.size());
1823
            distributionStatusList.addAll(distributionStatus);
1824
        }
1825

    
1826
        // set default if parameter is null
1827
        if(searchModes == null){
1828
            searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1829
        }
1830

    
1831
        // set sort order and thus override any sort orders which may have been
1832
        // defindes by prepare*Search methods
1833
        if(orderHints == null){
1834
            orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER;
1835
        }
1836
        SortField[] sortFields = new SortField[orderHints.size()];
1837
        int i = 0;
1838
        for(OrderHint oh : orderHints){
1839
            sortFields[i++] = oh.toSortField();
1840
        }
1841
//        SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1842
//        SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1843

    
1844

    
1845
        boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1846

    
1847
        List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1848
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1849

    
1850
        /*
1851
          ======== filtering by distribution , HOWTO ========
1852

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

    
1858

    
1859
          3. how does it work in spatial?
1860
          see
1861
           - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1862
           - http://www.infoq.com/articles/LuceneSpatialSupport
1863
           - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1864
          ------------------------------------------------------------------------
1865

    
1866
          filter strategies:
1867
          A) use a separate distribution filter per index sub-query/search:
1868
           - byTaxonSyonym (query TaxaonBase):
1869
               use a join area filter (Distribution -> TaxonBase)
1870
           - byCommonName (query DescriptionElementBase): use an area filter on
1871
               DescriptionElementBase !!! PROBLEM !!!
1872
               This cannot work since the distributions are different entities than the
1873
               common names and thus these are different lucene documents.
1874
           - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1875
               use a join area filter (Distribution -> TaxonBase)
1876

    
1877
          B) use a common distribution filter for all index sub-query/searches:
1878
           - use a common join area filter (Distribution -> TaxonBase)
1879
           - also implement the byCommonName as join query (CommonName -> TaxonBase)
1880
           PROBLEM in this case: we are losing the fragment highlighting for the
1881
           common names, since the returned documents are always TaxonBases
1882
        */
1883

    
1884
        /* The QueryFactory for creating filter queries on Distributions should
1885
         * The query factory used for the common names query cannot be reused
1886
         * for this case, since we want to only record the text fields which are
1887
         * actually used in the primary query
1888
         */
1889
        QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1890

    
1891
        BooleanFilter multiIndexByAreaFilter = new BooleanFilter();
1892

    
1893

    
1894
        // search for taxa or synonyms
1895
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1896
            Class taxonBaseSubclass = TaxonBase.class;
1897
            if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1898
                taxonBaseSubclass = Taxon.class;
1899
            } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1900
                taxonBaseSubclass = Synonym.class;
1901
            }
1902
            luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1903
            idFieldMap.put(CdmBaseType.TAXON, "id");
1904
            /* A) does not work!!!!
1905
            if(addDistributionFilter){
1906
                // in this case we need a filter which uses a join query
1907
                // to get the TaxonBase documents for the DescriptionElementBase documents
1908
                // which are matching the areas in question
1909
                Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1910
                        namedAreaList,
1911
                        distributionStatusList,
1912
                        distributionFilterQueryFactory
1913
                        );
1914
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1915
            }
1916
            */
1917
            if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1918
                // add additional area filter for synonyms
1919
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1920
                String toField = "accTaxon.id"; // id in TaxonBase index
1921

    
1922
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1923

    
1924
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
1925
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1926

    
1927
            }
1928
        }
1929

    
1930
        // search by CommonTaxonName
1931
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1932
            // B)
1933
            QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1934
            Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1935
                    "inDescription.taxon.id",
1936
                    "id",
1937
                    QueryFactory.addTypeRestriction(
1938
                                createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
1939
                                , CommonTaxonName.class
1940
                                ),
1941
                    CommonTaxonName.class);
1942
            logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
1943
            LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
1944
            byCommonNameSearch.setCdmTypRestriction(Taxon.class);
1945
            byCommonNameSearch.setQuery(byCommonNameJoinQuery);
1946
            byCommonNameSearch.setSortFields(sortFields);
1947
            idFieldMap.put(CdmBaseType.TAXON, "id");
1948

    
1949
            luceneSearches.add(byCommonNameSearch);
1950

    
1951
            /* A) does not work!!!!
1952
            luceneSearches.add(
1953
                    prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1954
                            queryString, classification, null, languages, highlightFragments)
1955
                        );
1956
            idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1957
            if(addDistributionFilter){
1958
                // in this case we are able to use DescriptionElementBase documents
1959
                // which are matching the areas in question directly
1960
                BooleanQuery byDistributionQuery = createByDistributionQuery(
1961
                        namedAreaList,
1962
                        distributionStatusList,
1963
                        distributionFilterQueryFactory
1964
                        );
1965
                multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1966
            } */
1967
        }
1968

    
1969
        // search by misapplied names
1970
        if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
1971
            // NOTE:
1972
            // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1973
            // which allows doing query time joins
1974
            // finds the misapplied name (Taxon B) which is an misapplication for
1975
            // a related Taxon A.
1976
            //
1977
            luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
1978
                    new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
1979
                    queryString, classification, languages, highlightFragments, sortFields));
1980
            idFieldMap.put(CdmBaseType.TAXON, "id");
1981

    
1982
            if(addDistributionFilter){
1983
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1984

    
1985
                /*
1986
                 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
1987
                 * Maybe this is a but in java itself java.
1988
                 *
1989
                 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1990
                 * directly:
1991
                 *
1992
                 *    String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1993
                 *
1994
                 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1995
                 * will execute as expected:
1996
                 *
1997
                 *    String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1998
                 *    String toField = "relation." + misappliedNameForUuid +".to.id";
1999
                 *
2000
                 * Comparing both strings by the String.equals method returns true, so both String are identical.
2001
                 *
2002
                 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
2003
                 * 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)
2004
                 * The bug is persistent after a reboot of the development computer.
2005
                 */
2006
//                String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
2007
//                String toField = "relation." + misappliedNameForUuid +".to.id";
2008
                String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
2009
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
2010
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
2011

    
2012
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
2013
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2014
                QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery);
2015

    
2016
//                debug code for bug described above
2017
                DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
2018
//                System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
2019

    
2020
                multiIndexByAreaFilter.add(filter, Occur.SHOULD);
2021
            }
2022
        }
2023

    
2024
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
2025
                luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
2026

    
2027

    
2028
        if(addDistributionFilter){
2029

    
2030
            // B)
2031
            // in this case we need a filter which uses a join query
2032
            // to get the TaxonBase documents for the DescriptionElementBase documents
2033
            // which are matching the areas in question
2034
            //
2035
            // for toTaxa, doByCommonName
2036
            Query taxonAreaJoinQuery = createByDistributionJoinQuery(
2037
                    namedAreaList,
2038
                    distributionStatusList,
2039
                    distributionFilterQueryFactory
2040
                    );
2041
            multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
2042
        }
2043

    
2044
        if (addDistributionFilter){
2045
            multiSearch.setFilter(multiIndexByAreaFilter);
2046
        }
2047

    
2048

    
2049
        // --- execute search
2050
        TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2051

    
2052
        // --- initialize taxa, highlight matches ....
2053
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2054

    
2055

    
2056
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2057
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2058

    
2059
        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
2060
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
2061
    }
2062

    
2063
    /**
2064
     * @param namedAreaList at least one area must be in the list
2065
     * @param distributionStatusList optional
2066
     * @return
2067
     * @throws IOException
2068
     */
2069
    protected Query createByDistributionJoinQuery(
2070
            List<NamedArea> namedAreaList,
2071
            List<PresenceAbsenceTerm> distributionStatusList,
2072
            QueryFactory queryFactory
2073
            ) throws IOException {
2074

    
2075
        String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2076
        String toField = "id"; // id in TaxonBase index
2077

    
2078
        BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2079

    
2080
        Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class);
2081

    
2082
        return taxonAreaJoinQuery;
2083
    }
2084

    
2085
    /**
2086
     * @param namedAreaList
2087
     * @param distributionStatusList
2088
     * @param queryFactory
2089
     * @return
2090
     */
2091
    private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2092
            List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2093
        BooleanQuery areaQuery = new BooleanQuery();
2094
        // area field from Distribution
2095
        areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2096

    
2097
        // status field from Distribution
2098
        if(distributionStatusList != null && distributionStatusList.size() > 0){
2099
            areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2100
        }
2101

    
2102
        logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2103
        return areaQuery;
2104
    }
2105

    
2106
    /**
2107
     * This method has been primarily created for testing the area join query but might
2108
     * also be useful in other situations
2109
     *
2110
     * @param namedAreaList
2111
     * @param distributionStatusList
2112
     * @param classification
2113
     * @param highlightFragments
2114
     * @return
2115
     * @throws IOException
2116
     */
2117
    protected LuceneSearch prepareByDistributionSearch(
2118
            List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
2119
            Classification classification) throws IOException {
2120

    
2121
        BooleanQuery finalQuery = new BooleanQuery();
2122

    
2123
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2124

    
2125
        // FIXME is this query factory using the wrong type?
2126
        QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2127

    
2128
        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
2129
        luceneSearch.setSortFields(sortFields);
2130

    
2131

    
2132
        Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory);
2133

    
2134
        finalQuery.add(byAreaQuery, Occur.MUST);
2135

    
2136
        if(classification != null){
2137
            finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2138
        }
2139

    
2140
        logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2141
        luceneSearch.setQuery(finalQuery);
2142

    
2143
        return luceneSearch;
2144
    }
2145

    
2146
    @Override
2147
    public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2148
            Class<? extends DescriptionElementBase> clazz, String queryString,
2149
            Classification classification, List<Feature> features, List<Language> languages,
2150
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2151

    
2152

    
2153
        LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2154

    
2155
        // --- execute search
2156
        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2157

    
2158
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2159
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2160

    
2161
        // --- initialize taxa, highlight matches ....
2162
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2163
        @SuppressWarnings("rawtypes")
2164
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2165
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2166

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

    
2170
    }
2171

    
2172

    
2173
    @Override
2174
    public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2175
            Classification classification, List<Language> languages, boolean highlightFragments,
2176
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2177

    
2178
        LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2179
        LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2180

    
2181
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2182

    
2183
        // --- execute search
2184
        TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2185

    
2186
        // --- initialize taxa, highlight matches ....
2187
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2188

    
2189
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2190
        idFieldMap.put(CdmBaseType.TAXON, "id");
2191
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2192

    
2193
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2194
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2195

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

    
2199
    }
2200

    
2201

    
2202
    /**
2203
     * @param clazz
2204
     * @param queryString
2205
     * @param classification
2206
     * @param features
2207
     * @param languages
2208
     * @param highlightFragments
2209
     * @param directorySelectClass
2210
     * @return
2211
     */
2212
    protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2213
            String queryString, Classification classification, List<Feature> features,
2214
            List<Language> languages, boolean highlightFragments) {
2215

    
2216
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2217
        QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2218

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

    
2221
        BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2222
                languages, descriptionElementQueryFactory);
2223

    
2224
        luceneSearch.setSortFields(sortFields);
2225
        luceneSearch.setCdmTypRestriction(clazz);
2226
        luceneSearch.setQuery(finalQuery);
2227
        if(highlightFragments){
2228
            luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2229
        }
2230

    
2231
        return luceneSearch;
2232
    }
2233

    
2234
    /**
2235
     * @param queryString
2236
     * @param classification
2237
     * @param features
2238
     * @param languages
2239
     * @param descriptionElementQueryFactory
2240
     * @return
2241
     */
2242
    private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2243
            List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2244
        BooleanQuery finalQuery = new BooleanQuery();
2245
        BooleanQuery textQuery = new BooleanQuery();
2246
        textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2247

    
2248
        // common name
2249
        Query nameQuery;
2250
        if(languages == null || languages.size() == 0){
2251
            nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString);
2252
        } else {
2253
            nameQuery = new BooleanQuery();
2254
            BooleanQuery languageSubQuery = new BooleanQuery();
2255
            for(Language lang : languages){
2256
                languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid",  lang.getUuid().toString(), false), Occur.SHOULD);
2257
            }
2258
            ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2259
            ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST);
2260
        }
2261
        textQuery.add(nameQuery, Occur.SHOULD);
2262

    
2263

    
2264
        // text field from TextData
2265
        textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2266

    
2267
        // --- TermBase fields - by representation ----
2268
        // state field from CategoricalData
2269
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2270

    
2271
        // state field from CategoricalData
2272
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2273

    
2274
        // area field from Distribution
2275
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2276

    
2277
        // status field from Distribution
2278
        textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2279

    
2280
        finalQuery.add(textQuery, Occur.MUST);
2281
        // --- classification ----
2282

    
2283
        if(classification != null){
2284
            finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2285
        }
2286

    
2287
        // --- IdentifieableEntity fields - by uuid
2288
        if(features != null && features.size() > 0 ){
2289
            finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2290
        }
2291

    
2292
        // the description must be associated with a taxon
2293
        finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2294

    
2295
        logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2296
        return finalQuery;
2297
    }
2298

    
2299
    /**
2300
     * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2301
     * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2302
     * This method is a convenient means to retrieve a Lucene query string for such the fields.
2303
     *
2304
     * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2305
     * or {@link MultilanguageTextFieldBridge }
2306
     * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2307
     * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2308
     * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2309
     *
2310
     * TODO move to utiliy class !!!!!!!!
2311
     */
2312
    private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2313

    
2314
        if(stringBuilder == null){
2315
            stringBuilder = new StringBuilder();
2316
        }
2317
        if(languages == null || languages.size() == 0){
2318
            stringBuilder.append(name + ".ALL:(%1$s) ");
2319
        } else {
2320
            for(Language lang : languages){
2321
                stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2322
            }
2323
        }
2324
        return stringBuilder;
2325
    }
2326

    
2327
    @Override
2328
    public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2329
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2330
        List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2331

    
2332
        HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2333

    
2334

    
2335
        UUID nameUuid= taxon.getName().getUuid();
2336
        ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2337
        String epithetOfTaxon = null;
2338
        String infragenericEpithetOfTaxon = null;
2339
        String infraspecificEpithetOfTaxon = null;
2340
        if (taxonName.isSpecies()){
2341
             epithetOfTaxon= taxonName.getSpecificEpithet();
2342
        } else if (taxonName.isInfraGeneric()){
2343
            infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2344
        } else if (taxonName.isInfraSpecific()){
2345
            infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2346
        }
2347
        String genusOfTaxon = taxonName.getGenusOrUninomial();
2348
        Set<TaxonNode> nodes = taxon.getTaxonNodes();
2349
        List<String> taxonNames = new ArrayList<String>();
2350

    
2351
        for (TaxonNode node: nodes){
2352
           // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2353
           // List<String> synonymsEpithet = new ArrayList<String>();
2354

    
2355
            if (node.getClassification().equals(classification)){
2356
                if (!node.isTopmostNode()){
2357
                    TaxonNode parent = node.getParent();
2358
                    parent = (TaxonNode)HibernateProxyHelper.deproxy(parent);
2359
                    TaxonNameBase<?,?> parentName =  parent.getTaxon().getName();
2360
                    ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2361
                    Taxon parentTaxon = (Taxon)HibernateProxyHelper.deproxy(parent.getTaxon());
2362
                    Rank rankOfTaxon = taxonName.getRank();
2363

    
2364

    
2365
                    //create inferred synonyms for species, subspecies
2366
                    if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2367

    
2368
                        Synonym inferredEpithet = null;
2369
                        Synonym inferredGenus = null;
2370
                        Synonym potentialCombination = null;
2371

    
2372
                        List<String> propertyPaths = new ArrayList<String>();
2373
                        propertyPaths.add("synonym");
2374
                        propertyPaths.add("synonym.name");
2375
                        List<OrderHint> orderHints = new ArrayList<OrderHint>();
2376
                        orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2377

    
2378
                        List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2379
                        List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2380

    
2381
                        List<TaxonRelationship> taxonRelListParent = null;
2382
                        List<TaxonRelationship> taxonRelListTaxon = null;
2383
                        if (doWithMisappliedNames){
2384
                            taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2385
                            taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2386
                        }
2387

    
2388

    
2389
                        if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2390

    
2391

    
2392
                            for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2393
                                Synonym syn = synonymRelationOfParent.getSynonym();
2394

    
2395
                                inferredEpithet = createInferredEpithets(taxon,
2396
                                        zooHashMap, taxonName, epithetOfTaxon,
2397
                                        infragenericEpithetOfTaxon,
2398
                                        infraspecificEpithetOfTaxon,
2399
                                        taxonNames, parentName,
2400
                                        syn);
2401

    
2402

    
2403
                                inferredSynonyms.add(inferredEpithet);
2404
                                zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2405
                                taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2406
                            }
2407

    
2408
                            if (doWithMisappliedNames){
2409

    
2410
                                for (TaxonRelationship taxonRelationship: taxonRelListParent){
2411
                                     Taxon misappliedName = taxonRelationship.getFromTaxon();
2412

    
2413
                                     inferredEpithet = createInferredEpithets(taxon,
2414
                                             zooHashMap, taxonName, epithetOfTaxon,
2415
                                             infragenericEpithetOfTaxon,
2416
                                             infraspecificEpithetOfTaxon,
2417
                                             taxonNames, parentName,
2418
                                             misappliedName);
2419

    
2420
                                    inferredSynonyms.add(inferredEpithet);
2421
                                    zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2422
                                     taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2423
                                }
2424
                            }
2425

    
2426
                            if (!taxonNames.isEmpty()){
2427
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2428
                            ZoologicalName name;
2429
                            if (!synNotInCDM.isEmpty()){
2430
                                inferredSynonymsToBeRemoved.clear();
2431

    
2432
                                for (Synonym syn :inferredSynonyms){
2433
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2434
                                    if (!synNotInCDM.contains(name.getNameCache())){
2435
                                        inferredSynonymsToBeRemoved.add(syn);
2436
                                    }
2437
                                }
2438

    
2439
                                // Remove identified Synonyms from inferredSynonyms
2440
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2441
                                    inferredSynonyms.remove(synonym);
2442
                                }
2443
                            }
2444
                        }
2445

    
2446
                    }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2447

    
2448

    
2449
                        for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2450
                            TaxonNameBase synName;
2451
                            ZoologicalName inferredSynName;
2452

    
2453
                            Synonym syn = synonymRelationOfTaxon.getSynonym();
2454
                            inferredGenus = createInferredGenus(taxon,
2455
                                    zooHashMap, taxonName, epithetOfTaxon,
2456
                                    genusOfTaxon, taxonNames, zooParentName, syn);
2457

    
2458
                            inferredSynonyms.add(inferredGenus);
2459
                            zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2460
                            taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2461

    
2462

    
2463
                        }
2464

    
2465
                        if (doWithMisappliedNames){
2466

    
2467
                            for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2468
                                Taxon misappliedName = taxonRelationship.getFromTaxon();
2469
                                inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
2470

    
2471
                                inferredSynonyms.add(inferredGenus);
2472
                                zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2473
                                 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2474
                            }
2475
                        }
2476

    
2477

    
2478
                        if (!taxonNames.isEmpty()){
2479
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2480
                            ZoologicalName name;
2481
                            if (!synNotInCDM.isEmpty()){
2482
                                inferredSynonymsToBeRemoved.clear();
2483

    
2484
                                for (Synonym syn :inferredSynonyms){
2485
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2486
                                    if (!synNotInCDM.contains(name.getNameCache())){
2487
                                        inferredSynonymsToBeRemoved.add(syn);
2488
                                    }
2489
                                }
2490

    
2491
                                // Remove identified Synonyms from inferredSynonyms
2492
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2493
                                    inferredSynonyms.remove(synonym);
2494
                                }
2495
                            }
2496
                        }
2497

    
2498
                    }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2499

    
2500
                        Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2501
                        ZoologicalName inferredSynName;
2502
                        //for all synonyms of the parent...
2503
                        for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2504
                            TaxonNameBase synName;
2505
                            Synonym synParent = synonymRelationOfParent.getSynonym();
2506
                            synName = synParent.getName();
2507

    
2508
                            HibernateProxyHelper.deproxy(synParent);
2509

    
2510
                            // Set the sourceReference
2511
                            sourceReference = synParent.getSec();
2512

    
2513
                            // Determine the idInSource
2514
                            String idInSourceParent = getIdInSource(synParent);
2515

    
2516
                            ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2517
                            String synParentGenus = parentSynZooName.getGenusOrUninomial();
2518
                            String synParentInfragenericName = null;
2519
                            String synParentSpecificEpithet = null;
2520

    
2521
                            if (parentSynZooName.isInfraGeneric()){
2522
                                synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2523
                            }
2524
                            if (parentSynZooName.isSpecies()){
2525
                                synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2526
                            }
2527

    
2528
                           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2529
                                synonymsGenus.put(synGenusName, idInSource);
2530
                            }*/
2531

    
2532
                            //for all synonyms of the taxon
2533

    
2534
                            for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2535

    
2536
                                Synonym syn = synonymRelationOfTaxon.getSynonym();
2537
                                ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2538
                                potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2539
                                        synParentGenus,
2540
                                        synParentInfragenericName,
2541
                                        synParentSpecificEpithet, syn, zooHashMap);
2542

    
2543
                                taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2544
                                inferredSynonyms.add(potentialCombination);
2545
                                zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2546
                                 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2547

    
2548
                            }
2549

    
2550

    
2551
                        }
2552

    
2553
                        if (doWithMisappliedNames){
2554

    
2555
                            for (TaxonRelationship parentRelationship: taxonRelListParent){
2556

    
2557
                                TaxonNameBase misappliedParentName;
2558

    
2559
                                Taxon misappliedParent = parentRelationship.getFromTaxon();
2560
                                misappliedParentName = misappliedParent.getName();
2561

    
2562
                                HibernateProxyHelper.deproxy(misappliedParent);
2563

    
2564
                                // Set the sourceReference
2565
                                sourceReference = misappliedParent.getSec();
2566

    
2567
                                // Determine the idInSource
2568
                                String idInSourceParent = getIdInSource(misappliedParent);
2569

    
2570
                                ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2571
                                String synParentGenus = parentSynZooName.getGenusOrUninomial();
2572
                                String synParentInfragenericName = null;
2573
                                String synParentSpecificEpithet = null;
2574

    
2575
                                if (parentSynZooName.isInfraGeneric()){
2576
                                    synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2577
                                }
2578
                                if (parentSynZooName.isSpecies()){
2579
                                    synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2580
                                }
2581

    
2582

    
2583
                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2584
                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
2585
                                    ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2586
                                    potentialCombination = createPotentialCombination(
2587
                                            idInSourceParent, parentSynZooName, zooMisappliedName,
2588
                                            synParentGenus,
2589
                                            synParentInfragenericName,
2590
                                            synParentSpecificEpithet, misappliedName, zooHashMap);
2591

    
2592

    
2593
                                    taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2594
                                    inferredSynonyms.add(potentialCombination);
2595
                                    zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2596
                                     taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2597
                                }
2598
                            }
2599
                        }
2600

    
2601
                        if (!taxonNames.isEmpty()){
2602
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2603
                            ZoologicalName name;
2604
                            if (!synNotInCDM.isEmpty()){
2605
                                inferredSynonymsToBeRemoved.clear();
2606
                                for (Synonym syn :inferredSynonyms){
2607
                                    try{
2608
                                        name = (ZoologicalName) syn.getName();
2609
                                    }catch (ClassCastException e){
2610
                                        name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2611
                                    }
2612
                                    if (!synNotInCDM.contains(name.getNameCache())){
2613
                                        inferredSynonymsToBeRemoved.add(syn);
2614
                                    }
2615
                                 }
2616
                                // Remove identified Synonyms from inferredSynonyms
2617
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2618
                                    inferredSynonyms.remove(synonym);
2619
                                }
2620
                            }
2621
                         }
2622
                        }
2623
                    }else {
2624
                        logger.info("The synonymrelationship type is not defined.");
2625
                        return inferredSynonyms;
2626
                    }
2627
                }
2628
            }
2629

    
2630
        }
2631

    
2632
        return inferredSynonyms;
2633
    }
2634

    
2635
    private Synonym createPotentialCombination(String idInSourceParent,
2636
            ZoologicalName parentSynZooName, 	ZoologicalName zooSynName, String synParentGenus,
2637
            String synParentInfragenericName, String synParentSpecificEpithet,
2638
            TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2639
        Synonym potentialCombination;
2640
        Reference sourceReference;
2641
        ZoologicalName inferredSynName;
2642
        HibernateProxyHelper.deproxy(syn);
2643

    
2644
        // Set sourceReference
2645
        sourceReference = syn.getSec();
2646
        if (sourceReference == null){
2647
            logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2648
            //TODO:Remove
2649
            if (!parentSynZooName.getTaxa().isEmpty()){
2650
                TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2651

    
2652
                sourceReference = taxon.getSec();
2653
            }
2654
        }
2655
        String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2656

    
2657
        String synTaxonInfraSpecificName= null;
2658

    
2659
        if (parentSynZooName.isSpecies()){
2660
            synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2661
        }
2662

    
2663
        /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2664
            synonymsEpithet.add(epithetName);
2665
        }*/
2666

    
2667
        //create potential combinations...
2668
        inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2669

    
2670
        inferredSynName.setGenusOrUninomial(synParentGenus);
2671
        if (zooSynName.isSpecies()){
2672
              inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2673
              if (parentSynZooName.isInfraGeneric()){
2674
                  inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2675
              }
2676
        }
2677
        if (zooSynName.isInfraSpecific()){
2678
            inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2679
            inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2680
        }
2681
        if (parentSynZooName.isInfraGeneric()){
2682
            inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2683
        }
2684

    
2685

    
2686
        potentialCombination = Synonym.NewInstance(inferredSynName, null);
2687

    
2688
        // Set the sourceReference
2689
        potentialCombination.setSec(sourceReference);
2690

    
2691

    
2692
        // Determine the idInSource
2693
        String idInSourceSyn= getIdInSource(syn);
2694

    
2695
        if (idInSourceParent != null && idInSourceSyn != null) {
2696
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2697
            inferredSynName.addSource(originalSource);
2698
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2699
            potentialCombination.addSource(originalSource);
2700
        }
2701

    
2702
        return potentialCombination;
2703
    }
2704

    
2705
    private Synonym createInferredGenus(Taxon taxon,
2706
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2707
            String epithetOfTaxon, String genusOfTaxon,
2708
            List<String> taxonNames, ZoologicalName zooParentName,
2709
            TaxonBase syn) {
2710

    
2711
        Synonym inferredGenus;
2712
        TaxonNameBase synName;
2713
        ZoologicalName inferredSynName;
2714
        synName =syn.getName();
2715
        HibernateProxyHelper.deproxy(syn);
2716

    
2717
        // Determine the idInSource
2718
        String idInSourceSyn = getIdInSource(syn);
2719
        String idInSourceTaxon = getIdInSource(taxon);
2720
        // Determine the sourceReference
2721
        Reference sourceReference = syn.getSec();
2722

    
2723
        //logger.warn(sourceReference.getTitleCache());
2724

    
2725
        synName = syn.getName();
2726
        ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2727
        String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2728
                     /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2729
            synonymsEpithet.add(synSpeciesEpithetName);
2730
        }*/
2731

    
2732
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2733
        //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...
2734

    
2735

    
2736
        inferredSynName.setGenusOrUninomial(genusOfTaxon);
2737
        if (zooParentName.isInfraGeneric()){
2738
            inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2739
        }
2740

    
2741
        if (taxonName.isSpecies()){
2742
            inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2743
        }
2744
        if (taxonName.isInfraSpecific()){
2745
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2746
            inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2747
        }
2748

    
2749

    
2750
        inferredGenus = Synonym.NewInstance(inferredSynName, null);
2751

    
2752
        // Set the sourceReference
2753
        inferredGenus.setSec(sourceReference);
2754

    
2755
        // Add the original source
2756
        if (idInSourceSyn != null && idInSourceTaxon != null) {
2757
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2758
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2759
            inferredGenus.addSource(originalSource);
2760

    
2761
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2762
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2763
            inferredSynName.addSource(originalSource);
2764
            originalSource = null;
2765

    
2766
        }else{
2767
            logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2768
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2769
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2770
            inferredGenus.addSource(originalSource);
2771

    
2772
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2773
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2774
            inferredSynName.addSource(originalSource);
2775
            originalSource = null;
2776
        }
2777

    
2778
        taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2779

    
2780
        return inferredGenus;
2781
    }
2782

    
2783
    private Synonym createInferredEpithets(Taxon taxon,
2784
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2785
            String epithetOfTaxon, String infragenericEpithetOfTaxon,
2786
            String infraspecificEpithetOfTaxon, List<String> taxonNames,
2787
            TaxonNameBase parentName, TaxonBase syn) {
2788

    
2789
        Synonym inferredEpithet;
2790
        TaxonNameBase<?,?> synName;
2791
        ZoologicalName inferredSynName;
2792
        HibernateProxyHelper.deproxy(syn);
2793

    
2794
        // Determine the idInSource
2795
        String idInSourceSyn = getIdInSource(syn);
2796
        String idInSourceTaxon =  getIdInSource(taxon);
2797
        // Determine the sourceReference
2798
        Reference<?> sourceReference = syn.getSec();
2799

    
2800
        if (sourceReference == null){
2801
             logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2802
             sourceReference = taxon.getSec();
2803
        }
2804

    
2805
        synName = syn.getName();
2806
        ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2807
        String synGenusName = zooSynName.getGenusOrUninomial();
2808
        String synInfraGenericEpithet = null;
2809
        String synSpecificEpithet = null;
2810

    
2811
        if (zooSynName.getInfraGenericEpithet() != null){
2812
            synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2813
        }
2814

    
2815
        if (zooSynName.isInfraSpecific()){
2816
            synSpecificEpithet = zooSynName.getSpecificEpithet();
2817
        }
2818

    
2819
                     /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2820
            synonymsGenus.put(synGenusName, idInSource);
2821
        }*/
2822

    
2823
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2824

    
2825
        // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2826
        if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2827
            logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2828
        }
2829
        inferredSynName.setGenusOrUninomial(synGenusName);
2830

    
2831
        if (parentName.isInfraGeneric()){
2832
            inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2833
        }
2834
        if (taxonName.isSpecies()){
2835
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2836
        }else if (taxonName.isInfraSpecific()){
2837
            inferredSynName.setSpecificEpithet(synSpecificEpithet);
2838
            inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2839
        }
2840

    
2841
        inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2842

    
2843
        // Set the sourceReference
2844
        inferredEpithet.setSec(sourceReference);
2845

    
2846
        /* Add the original source
2847
        if (idInSource != null) {
2848
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2849

    
2850
            // Add the citation
2851
            Reference citation = getCitation(syn);
2852
            if (citation != null) {
2853
                originalSource.setCitation(citation);
2854
                inferredEpithet.addSource(originalSource);
2855
            }
2856
        }*/
2857
        String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2858

    
2859

    
2860
        IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2861
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2862

    
2863
        inferredEpithet.addSource(originalSource);
2864

    
2865
        originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2866
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2867

    
2868
        inferredSynName.addSource(originalSource);
2869

    
2870

    
2871

    
2872
        taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2873

    
2874
        return inferredEpithet;
2875
    }
2876

    
2877
    /**
2878
     * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2879
     * Very likely only useful for createInferredSynonyms().
2880
     * @param uuid
2881
     * @param zooHashMap
2882
     * @return
2883
     */
2884
    private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2885
        ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2886
        if (taxonName == null) {
2887
            taxonName = zooHashMap.get(uuid);
2888
        }
2889
        return taxonName;
2890
    }
2891

    
2892
    /**
2893
     * Returns the idInSource for a given Synonym.
2894
     * @param syn
2895
     */
2896
    private String getIdInSource(TaxonBase taxonBase) {
2897
        String idInSource = null;
2898
        Set<IdentifiableSource> sources = taxonBase.getSources();
2899
        if (sources.size() == 1) {
2900
            IdentifiableSource source = sources.iterator().next();
2901
            if (source != null) {
2902
                idInSource  = source.getIdInSource();
2903
            }
2904
        } else if (sources.size() > 1) {
2905
            int count = 1;
2906
            idInSource = "";
2907
            for (IdentifiableSource source : sources) {
2908
                idInSource += source.getIdInSource();
2909
                if (count < sources.size()) {
2910
                    idInSource += "; ";
2911
                }
2912
                count++;
2913
            }
2914
        } else if (sources.size() == 0){
2915
            logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2916
        }
2917

    
2918

    
2919
        return idInSource;
2920
    }
2921

    
2922

    
2923
    /**
2924
     * Returns the citation for a given Synonym.
2925
     * @param syn
2926
     */
2927
    private Reference getCitation(Synonym syn) {
2928
        Reference citation = null;
2929
        Set<IdentifiableSource> sources = syn.getSources();
2930
        if (sources.size() == 1) {
2931
            IdentifiableSource source = sources.iterator().next();
2932
            if (source != null) {
2933
                citation = source.getCitation();
2934
            }
2935
        } else if (sources.size() > 1) {
2936
            logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2937
        }
2938

    
2939
        return citation;
2940
    }
2941

    
2942
    @Override
2943
    public List<Synonym>  createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2944
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2945

    
2946
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2947
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2948
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2949

    
2950
        return inferredSynonyms;
2951
    }
2952

    
2953
    @Override
2954
    public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2955

    
2956
        // TODO quickly implemented, create according dao !!!!
2957
        Set<TaxonNode> nodes = new HashSet<TaxonNode>();
2958
        Set<Classification> classifications = new HashSet<Classification>();
2959
        List<Classification> list = new ArrayList<Classification>();
2960

    
2961
        if (taxonBase == null) {
2962
            return list;
2963
        }
2964

    
2965
        taxonBase = load(taxonBase.getUuid());
2966

    
2967
        if (taxonBase instanceof Taxon) {
2968
            nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
2969
        } else {
2970
            for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
2971
                nodes.addAll(taxon.getTaxonNodes());
2972
            }
2973
        }
2974
        for (TaxonNode node : nodes) {
2975
            classifications.add(node.getClassification());
2976
        }
2977
        list.addAll(classifications);
2978
        return list;
2979
    }
2980

    
2981
    @Override
2982
    @Transactional(readOnly = false)
2983
    public UpdateResult changeRelatedTaxonToSynonym(UUID fromTaxonUuid,
2984
            UUID toTaxonUuid,
2985
            TaxonRelationshipType oldRelationshipType,
2986
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
2987
        UpdateResult result = new UpdateResult();
2988
        Taxon fromTaxon = (Taxon) dao.load(fromTaxonUuid);
2989
        Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
2990
        Synonym synonym = changeRelatedTaxonToSynonym(fromTaxon, toTaxon, oldRelationshipType, synonymRelationshipType);
2991
        result.setCdmEntity(synonym);
2992
        result.addUpdatedObject(fromTaxon);
2993
        result.addUpdatedObject(toTaxon);
2994
        result.addUpdatedObject(synonym);
2995

    
2996
        return result;
2997
    }
2998

    
2999
    @Override
3000
    @Transactional(readOnly = false)
3001
    public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
3002
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
3003
        // Create new synonym using concept name
3004
                TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
3005
                Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
3006

    
3007
                // Remove concept relation from taxon
3008
                toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
3009

    
3010

    
3011

    
3012

    
3013
                // Create a new synonym for the taxon
3014
                SynonymRelationship synonymRelationship;
3015
                if (synonymRelationshipType != null
3016
                        && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
3017
                    synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
3018
                } else{
3019
                    synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
3020
                }
3021

    
3022
                this.saveOrUpdate(toTaxon);
3023
                //TODO: configurator and classification
3024
                TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
3025
                config.setDeleteNameIfPossible(false);
3026
                this.deleteTaxon(fromTaxon.getUuid(), config, null);
3027
                return synonymRelationship.getSynonym();
3028

    
3029
    }
3030

    
3031
    @Override
3032
    public DeleteResult isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
3033
        DeleteResult result = new DeleteResult();
3034
        Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
3035
        if (taxonBase instanceof Taxon){
3036
            TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
3037
            result = isDeletableForTaxon(references, taxonConfig);
3038
        }else{
3039
            SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
3040
            result = isDeletableForSynonym(references, synonymConfig);
3041
        }
3042
        return result;
3043
    }
3044

    
3045
    private DeleteResult isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
3046
        String message;
3047
        DeleteResult result = new DeleteResult();
3048
        for (CdmBase ref: references){
3049
            if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase )){
3050
                message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
3051
                result.addException(new ReferencedObjectUndeletableException(message));
3052
                result.addRelatedObject(ref);
3053
                result.setAbort();
3054
            }
3055
        }
3056

    
3057
        return result;
3058
    }
3059

    
3060
    private DeleteResult isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3061
        String message = null;
3062
        DeleteResult result = new DeleteResult();
3063
        for (CdmBase ref: references){
3064
            if (!(ref instanceof TaxonNameBase)){
3065
            	message = null;
3066
                if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){
3067
                    message = "The Taxon can't be deleted as long as it has synonyms.";
3068

    
3069
                }
3070
                if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3071
                    message = "The Taxon can't be deleted as long as it has factual data.";
3072

    
3073
                }
3074

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

    
3078
                }
3079
                if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonNode)){
3080
                    if (!config.isDeleteMisappliedNamesAndInvalidDesignations() && (((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())|| ((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR()))){
3081
                        message = "The Taxon can't be deleted as long as it has misapplied names or invalid designations.";
3082

    
3083
                    } else{
3084
                        message = "The Taxon can't be deleted as long as it belongs to a taxon node.";
3085

    
3086
                    }
3087
                }
3088
                if (ref instanceof PolytomousKeyNode){
3089
                    message = "The Taxon can't be deleted as long as it is referenced by a polytomous key node.";
3090

    
3091
                }
3092

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

    
3096

    
3097
                }
3098

    
3099

    
3100
               /* //PolytomousKeyNode
3101
                if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3102
                    String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3103
                    return message;
3104
                }*/
3105

    
3106
                //TaxonInteraction
3107
                if (ref.isInstanceOf(TaxonInteraction.class)){
3108
                    message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3109

    
3110
                }
3111

    
3112
              //TaxonInteraction
3113
                if (ref.isInstanceOf(DeterminationEvent.class)){
3114
                    message = "Taxon can't be deleted as it is used in a determination event";
3115

    
3116
                }
3117

    
3118
            }
3119
            if (message != null){
3120
	            result.addException(new ReferencedObjectUndeletableException(message));
3121
	            result.addRelatedObject(ref);
3122
	            result.setAbort();
3123
            }
3124
        }
3125

    
3126
        return result;
3127
    }
3128

    
3129
    @Override
3130
    public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3131
        IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3132

    
3133
        //preliminary implementation
3134

    
3135
        Set<Taxon> taxa = new HashSet<Taxon>();
3136
        TaxonBase taxonBase = find(taxonUuid);
3137
        if (taxonBase == null){
3138
            return new IncludedTaxaDTO();
3139
        }else if (taxonBase.isInstanceOf(Taxon.class)){
3140
            Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3141
            taxa.add(taxon);
3142
        }else if (taxonBase.isInstanceOf(Synonym.class)){
3143
            //TODO partial synonyms ??
3144
            //TODO synonyms in general
3145
            Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3146
            taxa.addAll(syn.getAcceptedTaxa());
3147
        }else{
3148
            throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3149
        }
3150

    
3151
        Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3152
        int i = 0;
3153
        while((! related.isEmpty()) && i++ < 100){  //to avoid
3154
             related = makeRelatedIncluded(related, result, config);
3155
        }
3156

    
3157
        return result;
3158
    }
3159

    
3160
    /**
3161
     * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3162
     * data structure.
3163
     * @return the set of conceptually related taxa for further use
3164
     */
3165
    /**
3166
     * @param uncheckedTaxa
3167
     * @param existingTaxa
3168
     * @param config
3169
     * @return
3170
     */
3171
    private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3172

    
3173
        //children
3174
        Set<TaxonNode> taxonNodes = new HashSet<TaxonNode>();
3175
        for (Taxon taxon: uncheckedTaxa){
3176
            taxonNodes.addAll(taxon.getTaxonNodes());
3177
        }
3178

    
3179
        Set<Taxon> children = new HashSet<Taxon>();
3180
        if (! config.onlyCongruent){
3181
            for (TaxonNode node: taxonNodes){
3182
                List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, null);
3183
                for (TaxonNode child : childNodes){
3184
                    children.add(child.getTaxon());
3185
                }
3186
            }
3187
            children.remove(null);  // just to be on the save side
3188
        }
3189

    
3190
        Iterator<Taxon> it = children.iterator();
3191
        while(it.hasNext()){
3192
            UUID uuid = it.next().getUuid();
3193
            if (existingTaxa.contains(uuid)){
3194
                it.remove();
3195
            }else{
3196
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3197
            }
3198
        }
3199

    
3200
        //concept relations
3201
        Set<Taxon> uncheckedAndChildren = new HashSet<Taxon>(uncheckedTaxa);
3202
        uncheckedAndChildren.addAll(children);
3203

    
3204
        Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3205

    
3206

    
3207
        Set<Taxon> result = new HashSet<Taxon>(relatedTaxa);
3208
        return result;
3209
    }
3210

    
3211
    /**
3212
     * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3213
     * @return the set of these computed taxa
3214
     */
3215
    private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3216
        Set<Taxon> result = new HashSet<Taxon>();
3217

    
3218
        for (Taxon taxon : unchecked){
3219
            Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3220
            Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3221

    
3222
            for (TaxonRelationship fromRel : fromRelations){
3223
                if (config.includeDoubtful == false && fromRel.isDoubtful()){
3224
                    continue;
3225
                }
3226
                if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3227
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
3228
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3229
                        ){
3230
                    result.add(fromRel.getToTaxon());
3231
                }
3232
            }
3233

    
3234
            for (TaxonRelationship toRel : toRelations){
3235
                if (config.includeDoubtful == false && toRel.isDoubtful()){
3236
                    continue;
3237
                }
3238
                if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
3239
                    result.add(toRel.getFromTaxon());
3240
                }
3241
            }
3242
        }
3243

    
3244
        Iterator<Taxon> it = result.iterator();
3245
        while(it.hasNext()){
3246
            UUID uuid = it.next().getUuid();
3247
            if (existingTaxa.contains(uuid)){
3248
                it.remove();
3249
            }else{
3250
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3251
            }
3252
        }
3253
        return result;
3254
    }
3255

    
3256
    @Override
3257
    public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3258
        List<TaxonBase> taxonList = dao.getTaxaByName(true, false, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, 0, config.getPropertyPath());
3259
        return taxonList;
3260
    }
3261

    
3262
	@Override
3263
	@Transactional(readOnly = true)
3264
	public <S extends TaxonBase> Pager<FindByIdentifierDTO<S>> findByIdentifier(
3265
			Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
3266
			MatchMode matchmode, boolean includeEntity, Integer pageSize,
3267
			Integer pageNumber,	List<String> propertyPaths) {
3268
		if (subtreeFilter == null){
3269
			return findByIdentifier(clazz, identifier, identifierType, matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3270
		}
3271

    
3272
		Integer numberOfResults = dao.countByIdentifier(clazz, identifier, identifierType, subtreeFilter, matchmode);
3273
        List<Object[]> daoResults = new ArrayList<Object[]>();
3274
        if(numberOfResults > 0) { // no point checking again
3275
        	daoResults = dao.findByIdentifier(clazz, identifier, identifierType, subtreeFilter,
3276
    				matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3277
        }
3278

    
3279
        List<FindByIdentifierDTO<S>> result = new ArrayList<FindByIdentifierDTO<S>>();
3280
        for (Object[] daoObj : daoResults){
3281
        	if (includeEntity){
3282
        		result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (S)daoObj[2]));
3283
        	}else{
3284
        		result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (UUID)daoObj[2], (String)daoObj[3]));
3285
        	}
3286
        }
3287
		return new DefaultPagerImpl<FindByIdentifierDTO<S>>(pageNumber, numberOfResults, pageSize, result);
3288
	}
3289

    
3290
	@Override
3291
	@Transactional(readOnly = false)
3292
	public UpdateResult moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation, UUID newTaxonUUID, boolean moveHomotypicGroup,
3293
            SynonymRelationshipType newSynonymRelationshipType, Reference reference, String referenceDetail, boolean keepReference) throws HomotypicalGroupChangeException {
3294

    
3295
	    UpdateResult result = new UpdateResult();
3296
		Taxon newTaxon = (Taxon) dao.load(newTaxonUUID);
3297
		SynonymRelationship sr = moveSynonymToAnotherTaxon(oldSynonymRelation, newTaxon, moveHomotypicGroup, newSynonymRelationshipType, reference, referenceDetail, keepReference);
3298
		result.setCdmEntity(sr);
3299
		result.addUpdatedObject(sr);
3300
		result.addUpdatedObject(newTaxon);
3301
		return result;
3302
	}
3303

    
3304
	@Override
3305
	public UpdateResult moveFactualDateToAnotherTaxon(UUID fromTaxonUuid, UUID toTaxonUuid){
3306
		UpdateResult result = new UpdateResult();
3307

    
3308
		Taxon fromTaxon = (Taxon)dao.load(fromTaxonUuid);
3309
		Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
3310
		  for(TaxonDescription description : fromTaxon.getDescriptions()){
3311
              //reload to avoid session conflicts
3312
              description = HibernateProxyHelper.deproxy(description, TaxonDescription.class);
3313

    
3314
              String moveMessage = String.format("Description moved from %s", fromTaxon);
3315
              if(description.isProtectedTitleCache()){
3316
                  String separator = "";
3317
                  if(!StringUtils.isBlank(description.getTitleCache())){
3318
                      separator = " - ";
3319
                  }
3320
                  description.setTitleCache(description.getTitleCache() + separator + moveMessage, true);
3321
              }
3322
              Annotation annotation = Annotation.NewInstance(moveMessage, Language.getDefaultLanguage());
3323
              annotation.setAnnotationType(AnnotationType.TECHNICAL());
3324
              description.addAnnotation(annotation);
3325
              toTaxon.addDescription(description);
3326
              dao.saveOrUpdate(toTaxon);
3327
              dao.saveOrUpdate(fromTaxon);
3328
              result.addUpdatedObject(toTaxon);
3329
              result.addUpdatedObject(fromTaxon);
3330

    
3331
          }
3332

    
3333

    
3334
		return result;
3335
	}
3336

    
3337
	@Override
3338
	public DeleteResult deleteSynonym(UUID synonymUuid, UUID taxonUuid,
3339
			SynonymDeletionConfigurator config) {
3340
		TaxonBase base = this.load(synonymUuid);
3341
		Synonym syn = HibernateProxyHelper.deproxy(base, Synonym.class);
3342
		base = this.load(taxonUuid);
3343
		Taxon taxon = HibernateProxyHelper.deproxy(base, Taxon.class);
3344

    
3345
		return this.deleteSynonym(syn, taxon, config);
3346
	}
3347

    
3348
	@Override
3349
	@Transactional(readOnly = false)
3350
	public UpdateResult swapSynonymAndAcceptedTaxon(UUID synonymUUid,
3351
			UUID acceptedTaxonUuid) {
3352
		TaxonBase base = this.load(synonymUUid);
3353
		Synonym syn = HibernateProxyHelper.deproxy(base, Synonym.class);
3354
		base = this.load(acceptedTaxonUuid);
3355
		Taxon taxon = HibernateProxyHelper.deproxy(base, Taxon.class);
3356

    
3357
		return this.swapSynonymAndAcceptedTaxon(syn, taxon);
3358
	}
3359

    
3360

    
3361

    
3362
}
(85-85/92)