Project

General

Profile

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

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

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

    
24
import javax.persistence.EntityNotFoundException;
25

    
26
import org.apache.commons.lang.StringUtils;
27
import org.apache.log4j.Logger;
28
import org.apache.lucene.index.CorruptIndexException;
29
import org.apache.lucene.queryparser.classic.ParseException;
30
import org.apache.lucene.search.BooleanClause.Occur;
31
import org.apache.lucene.search.BooleanQuery;
32
import org.apache.lucene.search.BooleanQuery.Builder;
33
import org.apache.lucene.search.Query;
34
import org.apache.lucene.search.SortField;
35
import org.apache.lucene.search.grouping.TopGroups;
36
import org.apache.lucene.search.join.ScoreMode;
37
import org.apache.lucene.util.BytesRef;
38
import org.springframework.beans.factory.annotation.Autowired;
39
import org.springframework.stereotype.Service;
40
import org.springframework.transaction.annotation.Transactional;
41

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

    
206
    @Override
207
    @Transactional(readOnly = false)
208
    public UpdateResult swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon){
209
    	UpdateResult result = new UpdateResult();
210
        TaxonNameBase<?,?> synonymName = synonym.getName();
211
        synonymName.removeTaxonBase(synonym);
212
        TaxonNameBase<?,?> taxonName = acceptedTaxon.getName();
213
        taxonName.removeTaxonBase(acceptedTaxon);
214

    
215
        synonym.setName(taxonName);
216
        acceptedTaxon.setName(synonymName);
217
        saveOrUpdate(synonym);
218
        saveOrUpdate(acceptedTaxon);
219
        result.addUpdatedObject(acceptedTaxon);
220
        result.addUpdatedObject(synonym);
221
		return result;
222

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

    
228

    
229
    @Override
230
    @Transactional(readOnly = false)
231
    public Taxon changeSynonymToAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean deleteSynonym, boolean copyCitationInfo, Reference citation, String microCitation) throws HomotypicalGroupChangeException{
232

    
233
        TaxonNameBase<?,?> acceptedName = acceptedTaxon.getName();
234
        TaxonNameBase<?,?> synonymName = synonym.getName();
235
        HomotypicalGroup synonymHomotypicGroup = synonymName.getHomotypicalGroup();
236

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

    
243
        Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, acceptedTaxon.getSec());
244
        dao.save(newAcceptedTaxon);
245
        SynonymRelationshipType relTypeForGroup = SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF();
246
        List<Synonym> heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup);
247
        Set<NameRelationship> basionymsAndReplacedSynonyms = synonymHomotypicGroup.getBasionymAndReplacedSynonymRelations();
248

    
249
        for (Synonym heteroSynonym : heteroSynonyms){
250
            if (synonym.equals(heteroSynonym)){
251
                acceptedTaxon.removeSynonym(heteroSynonym, false);
252

    
253
            }else{
254
                //move synonyms in same homotypic group to new accepted taxon
255
                heteroSynonym.replaceAcceptedTaxon(newAcceptedTaxon, relTypeForGroup, copyCitationInfo, citation, microCitation);
256
            }
257
        }
258
        dao.saveOrUpdate(acceptedTaxon);
259
        //synonym.getName().removeTaxonBase(synonym);
260

    
261
        if (deleteSynonym){
262
//			deleteSynonym(synonym, taxon, false);
263
            try {
264
                this.dao.flush();
265
                SynonymDeletionConfigurator config = new SynonymDeletionConfigurator();
266
                config.setDeleteNameIfPossible(false);
267
                this.deleteSynonym(synonym, acceptedTaxon, config);
268

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

    
274
        return newAcceptedTaxon;
275
    }
276

    
277
    @Override
278
    @Transactional(readOnly = false)
279
    public UpdateResult changeSynonymToAcceptedTaxon(UUID synonymUuid,
280
            UUID acceptedTaxonUuid,
281
            UUID newParentNodeUuid,
282
            boolean deleteSynonym,
283
            boolean copyCitationInfo,
284
            Reference citation,
285
            String microCitation) throws HomotypicalGroupChangeException {
286
        UpdateResult result = new UpdateResult();
287
        Synonym synonym = CdmBase.deproxy(dao.load(synonymUuid), Synonym.class);
288
        Taxon acceptedTaxon = CdmBase.deproxy(dao.load(acceptedTaxonUuid), Taxon.class);
289
        Taxon taxon =  changeSynonymToAcceptedTaxon(synonym, acceptedTaxon, deleteSynonym, copyCitationInfo, citation, microCitation);
290
        TaxonNode newParentNode = taxonNodeDao.load(newParentNodeUuid);
291
        TaxonNode newNode = newParentNode.addChildTaxon(taxon, null, null);
292
        taxonNodeDao.save(newNode);
293
        result.addUpdatedObject(taxon);
294
        result.addUpdatedObject(acceptedTaxon);
295
        result.setCdmEntity(newNode);
296
        return result;
297
    }
298

    
299
    @Override
300
    @Transactional(readOnly = false)
301
    public UpdateResult changeSynonymToRelatedTaxon(UUID synonymUuid,
302
            UUID toTaxonUuid,
303
            TaxonRelationshipType taxonRelationshipType,
304
            Reference citation,
305
            String microcitation){
306

    
307
        UpdateResult result = new UpdateResult();
308
        Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
309
        Synonym synonym = (Synonym) dao.load(synonymUuid);
310
        Taxon relatedTaxon = changeSynonymToRelatedTaxon(synonym, toTaxon, taxonRelationshipType, citation, microcitation);
311
        result.setCdmEntity(relatedTaxon);
312
        result.addUpdatedObject(relatedTaxon);
313
        result.addUpdatedObject(toTaxon);
314
        return result;
315
    }
316

    
317
    @Override
318
    @Transactional(readOnly = false)
319
    public Taxon changeSynonymToRelatedTaxon(Synonym synonym, Taxon toTaxon, TaxonRelationshipType taxonRelationshipType, Reference citation, String microcitation){
320

    
321
        // Get name from synonym
322
        if (synonym == null){
323
            return null;
324
        }
325
        TaxonNameBase<?, ?> synonymName = synonym.getName();
326

    
327
      /*  // remove synonym from taxon
328
        toTaxon.removeSynonym(synonym);
329
*/
330
        // Create a taxon with synonym name
331
        Taxon fromTaxon = Taxon.NewInstance(synonymName, null);
332

    
333
        // Add taxon relation
334
        fromTaxon.addTaxonRelation(toTaxon, taxonRelationshipType, citation, microcitation);
335

    
336
        // since we are swapping names, we have to detach the name from the synonym completely.
337
        // Otherwise the synonym will still be in the list of typified names.
338
       // synonym.getName().removeTaxonBase(synonym);
339
        this.deleteSynonym(synonym, null);
340

    
341
        return fromTaxon;
342
    }
343

    
344
    @Transactional(readOnly = false)
345
    @Override
346
    public void changeHomotypicalGroupOfSynonym(Synonym synonym, HomotypicalGroup newHomotypicalGroup, Taxon targetTaxon,
347
                        boolean removeFromOtherTaxa, boolean setBasionymRelationIfApplicable){
348
        // Get synonym name
349
        TaxonNameBase synonymName = synonym.getName();
350
        HomotypicalGroup oldHomotypicalGroup = synonymName.getHomotypicalGroup();
351

    
352

    
353
        // Switch groups
354
        oldHomotypicalGroup.removeTypifiedName(synonymName, false);
355
        newHomotypicalGroup.addTypifiedName(synonymName);
356

    
357
        //remove existing basionym relationships
358
        synonymName.removeBasionyms();
359

    
360
        //add basionym relationship
361
        if (setBasionymRelationIfApplicable){
362
            Set<TaxonNameBase> basionyms = newHomotypicalGroup.getBasionyms();
363
            for (TaxonNameBase basionym : basionyms){
364
                synonymName.addBasionym(basionym);
365
            }
366
        }
367

    
368
        //set synonym relationship correctly
369
//			SynonymRelationship relToTaxon = null;
370
        boolean relToTargetTaxonExists = false;
371
        Set<SynonymRelationship> existingRelations = synonym.getSynonymRelations();
372
        for (SynonymRelationship rel : existingRelations){
373
            Taxon acceptedTaxon = rel.getAcceptedTaxon();
374
            boolean isTargetTaxon = acceptedTaxon != null && acceptedTaxon.equals(targetTaxon);
375
            HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
376
            boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
377
            SynonymRelationshipType newRelationType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
378
            rel.setType(newRelationType);
379
            //TODO handle citation and microCitation
380

    
381
            if (isTargetTaxon){
382
                relToTargetTaxonExists = true;
383
            }else{
384
                if (removeFromOtherTaxa){
385
                    acceptedTaxon.removeSynonym(synonym, false);
386
                }else{
387
                    //do nothing
388
                }
389
            }
390
        }
391
        if (targetTaxon != null &&  ! relToTargetTaxonExists ){
392
            Taxon acceptedTaxon = targetTaxon;
393
            HomotypicalGroup acceptedGroup = acceptedTaxon.getHomotypicGroup();
394
            boolean isHomotypicToTaxon = acceptedGroup.equals(newHomotypicalGroup);
395
            SynonymRelationshipType relType = isHomotypicToTaxon? SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF() : SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
396
            //TODO handle citation and microCitation
397
            Reference citation = null;
398
            String microCitation = null;
399
            acceptedTaxon.addSynonym(synonym, relType, citation, microCitation);
400
        }
401

    
402
    }
403

    
404
    @Override
405
    @Transactional(readOnly = false)
406
    public void updateTitleCache(Class<? extends TaxonBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonBase> cacheStrategy, IProgressMonitor monitor) {
407
        if (clazz == null){
408
            clazz = TaxonBase.class;
409
        }
410
        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
411
    }
412

    
413
    @Override
414
    @Autowired
415
    protected void setDao(ITaxonDao dao) {
416
        this.dao = dao;
417
    }
418

    
419
    @Override
420
    public Pager<TaxonBase> findTaxaByName(Class<? extends TaxonBase> clazz, String uninomial,	String infragenericEpithet, String specificEpithet,	String infraspecificEpithet, String authorship, Rank rank, Integer pageSize,Integer pageNumber) {
421
        if (clazz == null){
422
            clazz = TaxonBase.class;
423
        }
424
        Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
425

    
426
        List<TaxonBase> results = new ArrayList<TaxonBase>();
427
        if(numberOfResults > 0) { // no point checking again
428
            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, authorship, rank, pageSize, pageNumber);
429
        }
430

    
431
        return new DefaultPagerImpl<TaxonBase>(pageNumber, numberOfResults, pageSize, results);
432
    }
433

    
434
    @Override
435
    public List<TaxonBase> listTaxaByName(Class<? extends TaxonBase> clazz, String uninomial,	String infragenericEpithet, String specificEpithet,	String infraspecificEpithet, String authorship, Rank rank, Integer pageSize,Integer pageNumber) {
436
        if (clazz == null){
437
            clazz = TaxonBase.class;
438
        }
439
        Integer numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
440

    
441
        List<TaxonBase> results = new ArrayList<TaxonBase>();
442
        if(numberOfResults > 0) { // no point checking again
443
            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, authorship, rank, pageSize, pageNumber);
444
        }
445

    
446
        return results;
447
    }
448

    
449
    @Override
450
    public List<TaxonRelationship> listToTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
451
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedTo);
452

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

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

    
464
        List<TaxonRelationship> results = new ArrayList<TaxonRelationship>();
465
        if(numberOfResults > 0) { // no point checking again
466
            results = dao.getTaxonRelationships(taxon, type, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedTo);
467
        }
468
        return new DefaultPagerImpl<TaxonRelationship>(pageNumber, numberOfResults, pageSize, results);
469
    }
470

    
471
    @Override
472
    public List<TaxonRelationship> listFromTaxonRelationships(Taxon taxon, TaxonRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths){
473
        Integer numberOfResults = dao.countTaxonRelationships(taxon, type, TaxonRelationship.Direction.relatedFrom);
474

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

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

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

    
493
    @Override
494
    public List<Taxon> listAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber,
495
            List<OrderHint> orderHints, List<String> propertyPaths){
496
        return pageAcceptedTaxaFor(synonymUuid, classificationUuid, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
497
    }
498

    
499
    @Override
500
    public Pager<Taxon> pageAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber,
501
            List<OrderHint> orderHints, List<String> propertyPaths){
502

    
503
        List<Taxon> list = new ArrayList<Taxon>();
504
        Long count = 0l;
505

    
506
        Synonym synonym = null;
507

    
508
        try {
509
            synonym = (Synonym) dao.load(synonymUuid);
510
        } catch (ClassCastException e){
511
            throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid + " is not a Synonmy");
512
        } catch (NullPointerException e){
513
            throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid);
514
        }
515

    
516
        Classification classificationFilter = null;
517
        if(classificationUuid != null){
518
            try {
519
            classificationFilter = classificationDao.load(classificationUuid);
520
            } catch (NullPointerException e){
521
                throw new EntityNotFoundException("No Classification entity found for " + classificationUuid);
522
            }
523
            if(classificationFilter == null){
524

    
525
            }
526
        }
527

    
528
        count = dao.countAcceptedTaxaFor(synonym, classificationFilter) ;
529
        if(count > (pageSize * pageNumber)){
530
            list = dao.listAcceptedTaxaFor(synonym, classificationFilter, pageSize, pageNumber, orderHints, propertyPaths);
531
        }
532

    
533
        return new DefaultPagerImpl<Taxon>(pageNumber, count.intValue(), pageSize, list);
534
    }
535

    
536

    
537
    @Override
538
    public Set<Taxon> listRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Integer maxDepth,
539
            Integer limit, Integer start, List<String> propertyPaths) {
540

    
541
        Set<Taxon> relatedTaxa = collectRelatedTaxa(taxon, includeRelationships, new HashSet<Taxon>(), maxDepth);
542
        relatedTaxa.remove(taxon);
543
        beanInitializer.initializeAll(relatedTaxa, propertyPaths);
544
        return relatedTaxa;
545
    }
546

    
547

    
548
    /**
549
     * recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
550
     *  <code>taxon</code> supplied as parameter.
551
     *
552
     * @param taxon
553
     * @param includeRelationships
554
     * @param taxa
555
     * @param maxDepth can be <code>null</code> for infinite depth
556
     * @return
557
     */
558
    private Set<Taxon> collectRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Set<Taxon> taxa, Integer maxDepth) {
559

    
560
        if(taxa.isEmpty()) {
561
            taxa.add(taxon);
562
        }
563

    
564
        if(includeRelationships.isEmpty()){
565
            return taxa;
566
        }
567

    
568
        if(maxDepth != null) {
569
            maxDepth--;
570
        }
571
        if(logger.isDebugEnabled()){
572
            logger.debug("collecting related taxa for " + taxon + " with maxDepth=" + maxDepth);
573
        }
574
        List<TaxonRelationship> taxonRelationships = dao.getTaxonRelationships(taxon, null, null, null, null, null, null);
575
        for (TaxonRelationship taxRel : taxonRelationships) {
576

    
577
            // skip invalid data
578
            if (taxRel.getToTaxon() == null || taxRel.getFromTaxon() == null || taxRel.getType() == null) {
579
                continue;
580
            }
581
            // filter by includeRelationships
582
            for (TaxonRelationshipEdge relationshipEdgeFilter : includeRelationships) {
583
                if ( relationshipEdgeFilter.getTaxonRelationshipType().equals(taxRel.getType()) ) {
584
                    if (relationshipEdgeFilter.getDirections().contains(Direction.relatedTo) && !taxa.contains(taxRel.getToTaxon())) {
585
                        if(logger.isDebugEnabled()){
586
                            logger.debug(maxDepth + ": " + taxon.getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxRel.getToTaxon().getTitleCache());
587
                        }
588
                        taxa.add(taxRel.getToTaxon());
589
                        if(maxDepth == null || maxDepth > 0) {
590
                            taxa.addAll(collectRelatedTaxa(taxRel.getToTaxon(), includeRelationships, taxa, maxDepth));
591
                        }
592
                    }
593
                    if(relationshipEdgeFilter.getDirections().contains(Direction.relatedFrom) && !taxa.contains(taxRel.getFromTaxon())) {
594
                        taxa.add(taxRel.getFromTaxon());
595
                        if(logger.isDebugEnabled()){
596
                            logger.debug(maxDepth + ": " +taxRel.getFromTaxon().getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxon.getTitleCache() );
597
                        }
598
                        if(maxDepth == null || maxDepth > 0) {
599
                            taxa.addAll(collectRelatedTaxa(taxRel.getFromTaxon(), includeRelationships, taxa, maxDepth));
600
                        }
601
                    }
602
                }
603
            }
604
        }
605
        return taxa;
606
    }
607

    
608
    @Override
609
    public Pager<SynonymRelationship> getSynonyms(Taxon taxon,	SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
610
        Integer numberOfResults = dao.countSynonyms(taxon, type);
611

    
612
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
613
        if(numberOfResults > 0) { // no point checking again
614
            results = dao.getSynonyms(taxon, type, pageSize, pageNumber, orderHints, propertyPaths);
615
        }
616

    
617
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
618
    }
619

    
620
    @Override
621
    public Pager<SynonymRelationship> getSynonyms(Synonym synonym,	SynonymRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
622
        Integer numberOfResults = dao.countSynonyms(synonym, type);
623

    
624
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
625
        if(numberOfResults > 0) { // no point checking again
626
            results = dao.getSynonyms(synonym, type, pageSize, pageNumber, orderHints, propertyPaths);
627
        }
628

    
629
        return new DefaultPagerImpl<SynonymRelationship>(pageNumber, numberOfResults, pageSize, results);
630
    }
631

    
632
    @Override
633
    public List<List<Synonym>> getSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
634
         List<List<Synonym>> result = new ArrayList<List<Synonym>>();
635
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
636

    
637
        //homotypic
638
        result.add(t.getHomotypicSynonymsByHomotypicGroup());
639

    
640
        //heterotypic
641
        List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
642
        for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
643
            result.add(t.getSynonymsInGroup(homotypicalGroup));
644
        }
645

    
646
        return result;
647

    
648
    }
649

    
650
    @Override
651
    public List<Synonym> getHomotypicSynonymsByHomotypicGroup(Taxon taxon, List<String> propertyPaths){
652
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
653
        return t.getHomotypicSynonymsByHomotypicGroup();
654
    }
655

    
656
    @Override
657
    public List<List<Synonym>> getHeterotypicSynonymyGroups(Taxon taxon, List<String> propertyPaths){
658
        Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
659
        List<HomotypicalGroup> homotypicalGroups = t.getHeterotypicSynonymyGroups();
660
        List<List<Synonym>> heterotypicSynonymyGroups = new ArrayList<List<Synonym>>(homotypicalGroups.size());
661
        for(HomotypicalGroup homotypicalGroup : homotypicalGroups){
662
            heterotypicSynonymyGroups.add(t.getSynonymsInGroup(homotypicalGroup));
663
        }
664
        return heterotypicSynonymyGroups;
665
    }
666

    
667
    @Override
668
    public List<UuidAndTitleCache<IdentifiableEntity>> findTaxaAndNamesForEditor(IFindTaxaAndNamesConfigurator configurator){
669

    
670
        List<UuidAndTitleCache<IdentifiableEntity>> results = new ArrayList<UuidAndTitleCache<IdentifiableEntity>>();
671

    
672

    
673
        if (configurator.isDoSynonyms() || configurator.isDoTaxa() || configurator.isDoNamesWithoutTaxa()){
674
        	results = dao.getTaxaByNameForEditor(configurator.isDoTaxa(), configurator.isDoSynonyms(), configurator.isDoNamesWithoutTaxa(), configurator.isDoMisappliedNames(),configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
675
        }
676
        if (configurator.isDoTaxaByCommonNames()) {
677
            //if(configurator.getPageSize() == null ){
678
                List<UuidAndTitleCache<IdentifiableEntity>> commonNameResults = dao.getTaxaByCommonNameForEditor(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
679
                if(commonNameResults != null){
680
                    results.addAll(commonNameResults);
681
                }
682
           // }
683
        }
684
        return results;
685
    }
686

    
687
    @Override
688
    public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
689

    
690
        List<IdentifiableEntity> results = new ArrayList<IdentifiableEntity>();
691
        int numberOfResults = 0; // overall number of results (as opposed to number of results per page)
692
        List<TaxonBase> taxa = null;
693

    
694
        // Taxa and synonyms
695
        long numberTaxaResults = 0L;
696

    
697

    
698
        List<String> propertyPath = new ArrayList<String>();
699
        if(configurator.getTaxonPropertyPath() != null){
700
            propertyPath.addAll(configurator.getTaxonPropertyPath());
701
        }
702

    
703

    
704
       if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa()){
705
            if(configurator.getPageSize() != null){ // no point counting if we need all anyway
706
                numberTaxaResults =
707
                    dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
708
                        configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(),
709
                        configurator.getNamedAreas());
710
            }
711

    
712
            if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
713
                taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
714
                    configurator.isDoMisappliedNames(), configurator.getTitleSearchStringSqlized(), configurator.getClassification(),
715
                    configurator.getMatchMode(), configurator.getNamedAreas(),
716
                    configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
717
            }
718
       }
719

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

    
722
        if(taxa != null){
723
            results.addAll(taxa);
724
        }
725

    
726
        numberOfResults += numberTaxaResults;
727

    
728
        // Names without taxa
729
        if (configurator.isDoNamesWithoutTaxa()) {
730
            int numberNameResults = 0;
731

    
732
            List<? extends TaxonNameBase<?,?>> names =
733
                nameDao.findByName(configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
734
                        configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
735
            if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
736
            if (names.size() > 0) {
737
                for (TaxonNameBase<?,?> taxonName : names) {
738
                    if (taxonName.getTaxonBases().size() == 0) {
739
                        results.add(taxonName);
740
                        numberNameResults++;
741
                    }
742
                }
743
                if (logger.isDebugEnabled()) { logger.debug(numberNameResults + " matching name(s) without taxa found"); }
744
                numberOfResults += numberNameResults;
745
            }
746
        }
747

    
748
        // Taxa from common names
749

    
750
        if (configurator.isDoTaxaByCommonNames()) {
751
            taxa = new ArrayList<TaxonBase>();
752
            numberTaxaResults = 0;
753
            if(configurator.getPageSize() != null){// no point counting if we need all anyway
754
                numberTaxaResults = dao.countTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas());
755
            }
756
            if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){
757
                List<Taxon> commonNameResults = dao.getTaxaByCommonName(configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getMatchMode(), configurator.getNamedAreas(), configurator.getPageSize(), configurator.getPageNumber(), configurator.getTaxonPropertyPath());
758
                taxa.addAll(commonNameResults);
759
            }
760
            if(taxa != null){
761
                results.addAll(taxa);
762
            }
763
            numberOfResults += numberTaxaResults;
764

    
765
        }
766

    
767
       return new DefaultPagerImpl<IdentifiableEntity>
768
            (configurator.getPageNumber(), numberOfResults, configurator.getPageSize(), results);
769
    }
770

    
771
    public List<UuidAndTitleCache<TaxonBase>> getTaxonUuidAndTitleCache(Integer limit, String pattern){
772
        return dao.getUuidAndTitleCache(limit, pattern);
773
    }
774

    
775
    @Override
776
    public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
777
        List<MediaRepresentation> medRep = new ArrayList<MediaRepresentation>();
778
        taxon = (Taxon)dao.load(taxon.getUuid());
779
        Set<TaxonDescription> descriptions = taxon.getDescriptions();
780
        for (TaxonDescription taxDesc: descriptions){
781
            Set<DescriptionElementBase> elements = taxDesc.getElements();
782
            for (DescriptionElementBase descElem: elements){
783
                for(Media media : descElem.getMedia()){
784

    
785
                    //find the best matching representation
786
                    medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
787

    
788
                }
789
            }
790
        }
791
        return medRep;
792
    }
793

    
794
    @Override
795
    public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
796
        return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
797
    }
798

    
799
    @Override
800
    public List<Media> listMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships,
801
            Boolean limitToGalleries, Boolean includeTaxonDescriptions, Boolean includeOccurrences,
802
            Boolean includeTaxonNameDescriptions, List<String> propertyPath) {
803

    
804
    //    logger.setLevel(Level.TRACE);
805
//        Logger.getLogger("org.hibernate.SQL").setLevel(Level.TRACE);
806

    
807
        logger.trace("listMedia() - START");
808

    
809
        Set<Taxon> taxa = new HashSet<Taxon>();
810
        List<Media> taxonMedia = new ArrayList<Media>();
811
        List<Media> nonImageGalleryImages = new ArrayList<Media>();
812

    
813
        if (limitToGalleries == null) {
814
            limitToGalleries = false;
815
        }
816

    
817
        // --- resolve related taxa
818
        if (includeRelationships != null && ! includeRelationships.isEmpty()) {
819
            logger.trace("listMedia() - resolve related taxa");
820
            taxa = listRelatedTaxa(taxon, includeRelationships, null, null, null, null);
821
        }
822

    
823
        taxa.add((Taxon) dao.load(taxon.getUuid()));
824

    
825
        if(includeTaxonDescriptions != null && includeTaxonDescriptions){
826
            logger.trace("listMedia() - includeTaxonDescriptions");
827
            List<TaxonDescription> taxonDescriptions = new ArrayList<TaxonDescription>();
828
            // --- TaxonDescriptions
829
            for (Taxon t : taxa) {
830
                taxonDescriptions.addAll(descriptionService.listTaxonDescriptions(t, null, null, null, null, propertyPath));
831
            }
832
            for (TaxonDescription taxonDescription : taxonDescriptions) {
833
                if (!limitToGalleries || taxonDescription.isImageGallery()) {
834
                    for (DescriptionElementBase element : taxonDescription.getElements()) {
835
                        for (Media media : element.getMedia()) {
836
                            if(taxonDescription.isImageGallery()){
837
                                taxonMedia.add(media);
838
                            }
839
                            else{
840
                                nonImageGalleryImages.add(media);
841
                            }
842
                        }
843
                    }
844
                }
845
            }
846
            //put images from image gallery first (#3242)
847
            taxonMedia.addAll(nonImageGalleryImages);
848
        }
849

    
850

    
851
        if(includeOccurrences != null && includeOccurrences) {
852
            logger.trace("listMedia() - includeOccurrences");
853
            Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<SpecimenOrObservationBase>();
854
            // --- Specimens
855
            for (Taxon t : taxa) {
856
                specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
857
            }
858
            for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
859

    
860
//            	direct media removed from specimen #3597
861
//              taxonMedia.addAll(occurrence.getMedia());
862

    
863
                // SpecimenDescriptions
864
                Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
865
                for (DescriptionBase specimenDescription : specimenDescriptions) {
866
                    if (!limitToGalleries || specimenDescription.isImageGallery()) {
867
                        Set<DescriptionElementBase> elements = specimenDescription.getElements();
868
                        for (DescriptionElementBase element : elements) {
869
                            for (Media media : element.getMedia()) {
870
                                taxonMedia.add(media);
871
                            }
872
                        }
873
                    }
874
                }
875

    
876
                if (occurrence.isInstanceOf(DerivedUnit.class)) {
877
                    DerivedUnit derivedUnit = CdmBase.deproxy(occurrence, DerivedUnit.class);
878
                    // Collection
879
                    //TODO why may collections have media attached? #
880
                    if (derivedUnit.getCollection() != null){
881
                        taxonMedia.addAll(derivedUnit.getCollection().getMedia());
882
                    }
883
                }
884
                //media in hierarchy
885
                taxonMedia.addAll(occurrenceService.getMediainHierarchy(occurrence, null, null, propertyPath).getRecords());
886
            }
887
        }
888

    
889
        if(includeTaxonNameDescriptions != null && includeTaxonNameDescriptions) {
890
            logger.trace("listMedia() - includeTaxonNameDescriptions");
891
            // --- TaxonNameDescription
892
            Set<TaxonNameDescription> nameDescriptions = new HashSet<TaxonNameDescription>();
893
            for (Taxon t : taxa) {
894
                nameDescriptions .addAll(t.getName().getDescriptions());
895
            }
896
            for(TaxonNameDescription nameDescription: nameDescriptions){
897
                if (!limitToGalleries || nameDescription.isImageGallery()) {
898
                    Set<DescriptionElementBase> elements = nameDescription.getElements();
899
                    for (DescriptionElementBase element : elements) {
900
                        for (Media media : element.getMedia()) {
901
                            taxonMedia.add(media);
902
                        }
903
                    }
904
                }
905
            }
906
        }
907

    
908

    
909
        logger.trace("listMedia() - initialize");
910
        beanInitializer.initializeAll(taxonMedia, propertyPath);
911

    
912
        logger.trace("listMedia() - END");
913

    
914
        return taxonMedia;
915
    }
916

    
917
    @Override
918
    public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
919
        return this.dao.loadList(listOfIDs, null);
920
    }
921

    
922
    @Override
923
    public TaxonBase findTaxonByUuid(UUID uuid, List<String> propertyPaths){
924
        return this.dao.findByUuid(uuid, null ,propertyPaths);
925
    }
926

    
927
    @Override
928
    public int countAllRelationships() {
929
        return this.dao.countAllRelationships();
930
    }
931

    
932
    @Override
933
    public List<TaxonNameBase> findIdenticalTaxonNames(List<String> propertyPath) {
934
        return this.dao.findIdenticalTaxonNames(propertyPath);
935
    }
936

    
937
    @Override
938
    @Transactional(readOnly = false)
939
    public DeleteResult deleteTaxon(UUID taxonUUID, TaxonDeletionConfigurator config, UUID classificationUuid)  {
940

    
941
    	if (config == null){
942
            config = new TaxonDeletionConfigurator();
943
        }
944
    	Taxon taxon = (Taxon)dao.load(taxonUUID);
945
    	DeleteResult result = new DeleteResult();
946
    	if (taxon == null){
947
    	    result.setAbort();
948
    	    result.addException(new Exception ("The taxon was already deleted."));
949
    	    return result;
950
    	}
951
    	taxon = HibernateProxyHelper.deproxy(taxon);
952
    	Classification classification = HibernateProxyHelper.deproxy(classificationDao.load(classificationUuid), Classification.class);
953
        result = isDeletable(taxon, config);
954

    
955
        if (result.isOk()){
956
            // --- DeleteSynonymRelations
957
            if (config.isDeleteSynonymRelations()){
958
                boolean removeSynonymNameFromHomotypicalGroup = false;
959
                // use tmp Set to avoid concurrent modification
960
                Set<SynonymRelationship> synRelsToDelete = new HashSet<SynonymRelationship>();
961
                synRelsToDelete.addAll(taxon.getSynonymRelations());
962
                for (SynonymRelationship synRel : synRelsToDelete){
963
                    Synonym synonym = synRel.getSynonym();
964
                    // taxon.removeSynonymRelation will set the accepted taxon and the synonym to NULL
965
                    // this will cause hibernate to delete the relationship since
966
                    // the SynonymRelationship field on both is annotated with removeOrphan
967
                    // so no further explicit deleting of the relationship should be done here
968
                    taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup);
969

    
970
                    // --- DeleteSynonymsIfPossible
971
                    if (config.isDeleteSynonymsIfPossible()){
972
                        //TODO which value
973
                        boolean newHomotypicGroupIfNeeded = true;
974
                        SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator();
975
                        deleteSynonym(synonym, taxon, synConfig);
976
                    }
977
                    // relationship will be deleted by hibernate automatically,
978
                    // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797
979

    
980
                }
981
            }
982

    
983
            // --- DeleteTaxonRelationships
984
            if (! config.isDeleteTaxonRelationships()){
985
                if (taxon.getTaxonRelations().size() > 0){
986
                    result.setAbort();
987
                    result.addException(new Exception("Taxon can't be deleted as it is related to another taxon. " +
988
                            "Remove taxon from all relations to other taxa prior to deletion."));
989

    
990
                }
991
            } else{
992
                TaxonDeletionConfigurator configRelTaxon = new TaxonDeletionConfigurator();
993
                configRelTaxon.setDeleteTaxonNodes(false);
994
                configRelTaxon.setDeleteConceptRelationships(true);
995

    
996
                for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
997
                    if (config.isDeleteMisappliedNamesAndInvalidDesignations()){
998
                        if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){
999
                            if (taxon.equals(taxRel.getToTaxon())){
1000
                                this.deleteTaxon(taxRel.getFromTaxon().getUuid(), config, classificationUuid);
1001
                            }
1002
                        }
1003
                    } else if (config.isDeleteConceptRelationships() && taxRel.getType().isConceptRelationship()){
1004

    
1005
                        if (taxon.equals(taxRel.getToTaxon()) && isDeletable(taxRel.getFromTaxon(), configRelTaxon).isOk()){
1006
                            this.deleteTaxon(taxRel.getFromTaxon().getUuid(), configRelTaxon, classificationUuid);
1007
                        }else if (isDeletable(taxRel.getToTaxon(), configRelTaxon).isOk()){
1008
                            this.deleteTaxon(taxRel.getToTaxon().getUuid(), configRelTaxon, classificationUuid);
1009
                        }
1010
                    }
1011
                    taxon.removeTaxonRelation(taxRel);
1012

    
1013
                }
1014
            }
1015

    
1016
            //    	TaxonDescription
1017
            if (config.isDeleteDescriptions()){
1018
                Set<TaxonDescription> descriptions = taxon.getDescriptions();
1019
                List<TaxonDescription> removeDescriptions = new ArrayList<TaxonDescription>();
1020
                for (TaxonDescription desc: descriptions){
1021
                    //TODO use description delete configurator ?
1022
                    //FIXME check if description is ALWAYS deletable
1023
                    if (desc.getDescribedSpecimenOrObservation() != null){
1024
                        result.setAbort();
1025
                        result.addException(new Exception("Taxon can't be deleted as it is used in a TaxonDescription" +
1026
                                " which also describes specimens or observations"));
1027
                        break;
1028
                    }
1029
                    removeDescriptions.add(desc);
1030

    
1031

    
1032
                }
1033
                if (result.isOk()){
1034
                    for (TaxonDescription desc: removeDescriptions){
1035
                        taxon.removeDescription(desc);
1036
                        descriptionService.delete(desc);
1037
                    }
1038
                } else {
1039
                    return result;
1040
                }
1041
            }
1042

    
1043

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

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

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

    
1109
            //TaxonNameBase
1110
            if (config.isDeleteNameIfPossible() && result.isOk()){
1111

    
1112

    
1113
                    //TaxonNameBase name = nameService.find(taxon.getName().getUuid());
1114
                    TaxonNameBase name = HibernateProxyHelper.deproxy(taxon.getName());
1115
                    //check whether taxon will be deleted or not
1116

    
1117
                    if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){
1118

    
1119
                        //name.removeTaxonBase(taxon);
1120
                        //nameService.saveOrUpdate(name);
1121
                        taxon.setName(null);
1122
                        //dao.delete(taxon);
1123
                        DeleteResult nameResult = new DeleteResult();
1124

    
1125
                        //remove name if possible (and required)
1126
                        if (name != null && config.isDeleteNameIfPossible()){
1127
                        	nameResult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
1128
                        }
1129

    
1130
                        if (nameResult.isError() || nameResult.isAbort()){
1131
                        	//result.setError();
1132
                        	result.addRelatedObject(name);
1133
                        	result.addExceptions(nameResult.getExceptions());
1134
                        }
1135

    
1136
                    }
1137

    
1138
            }else {
1139
                taxon.setName(null);
1140
            }
1141

    
1142

    
1143
            if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0)  && result.isOk()){
1144
            	try{
1145
            		UUID uuid = dao.delete(taxon);
1146

    
1147
            	}catch(Exception e){
1148
            		result.addException(e);
1149
            		result.setError();
1150

    
1151
            	}
1152
            } else {
1153
            	result.setError();
1154
            	result.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
1155

    
1156
            }
1157
        }
1158

    
1159
        return result;
1160

    
1161
    }
1162

    
1163
    private String checkForReferences(Taxon taxon){
1164
        Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
1165
        for (CdmBase referencingObject : referencingObjects){
1166
            //IIdentificationKeys (Media, Polytomous, MultiAccess)
1167
            if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
1168
                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";
1169

    
1170
                return message;
1171
            }
1172

    
1173

    
1174
           /* //PolytomousKeyNode
1175
            if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
1176
                String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
1177
                return message;
1178
            }*/
1179

    
1180
            //TaxonInteraction
1181
            if (referencingObject.isInstanceOf(TaxonInteraction.class)){
1182
                String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
1183
                return message;
1184
            }
1185

    
1186
          //TaxonInteraction
1187
            if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1188
                String message = "Taxon can't be deleted as it is used in a determination event";
1189
                return message;
1190
            }
1191

    
1192
        }
1193

    
1194
        referencingObjects = null;
1195
        return null;
1196
    }
1197

    
1198
    private boolean checkForPolytomousKeys(Taxon taxon){
1199
        boolean result = false;
1200
        List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon);
1201
        if (!list.isEmpty()) {
1202
            result = true;
1203
        }
1204
        return result;
1205
    }
1206

    
1207
    @Override
1208
    @Transactional(readOnly = false)
1209
    public DeleteResult delete(UUID synUUID){
1210
    	DeleteResult result = new DeleteResult();
1211
    	Synonym syn = (Synonym)dao.load(synUUID);
1212

    
1213
        return this.deleteSynonym(syn, null);
1214
    }
1215

    
1216

    
1217
    @Override
1218
    @Transactional(readOnly = false)
1219
    public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
1220
        return deleteSynonym(synonym, null, config);
1221

    
1222
    }
1223

    
1224
    @Override
1225
    @Transactional(readOnly = false)
1226
    public DeleteResult deleteSynonym(UUID synonymUuid, SynonymDeletionConfigurator config) {
1227
        return deleteSynonym((Synonym)dao.load(synonymUuid), config);
1228

    
1229
    }
1230

    
1231
    @Transactional(readOnly = false)
1232
    @Override
1233
    public DeleteResult deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) {
1234
        DeleteResult result = new DeleteResult();
1235
    	if (synonym == null){
1236
    		result.setAbort();
1237
    		result.addException(new Exception("The synonym was already deleted."));
1238
    		return result;
1239
        }
1240

    
1241
        if (config == null){
1242
            config = new SynonymDeletionConfigurator();
1243
        }
1244

    
1245
        result = isDeletable(synonym, config);
1246

    
1247

    
1248
        if (result.isOk()){
1249

    
1250
            synonym = HibernateProxyHelper.deproxy(this.load(synonym.getUuid()), Synonym.class);
1251

    
1252
            //remove synonymRelationship
1253
            Set<Taxon> taxonSet = new HashSet<Taxon>();
1254
            if (taxon != null){
1255
                taxonSet.add(taxon);
1256
            }else{
1257
                taxonSet.addAll(synonym.getAcceptedTaxa());
1258
            }
1259
            for (Taxon relatedTaxon : taxonSet){
1260
            	relatedTaxon = HibernateProxyHelper.deproxy(relatedTaxon, Taxon.class);
1261
                relatedTaxon.removeSynonym(synonym, false);
1262
                this.saveOrUpdate(relatedTaxon);
1263
            }
1264
            this.saveOrUpdate(synonym);
1265

    
1266
            //TODO remove name from homotypical group?
1267

    
1268
            //remove synonym (if necessary)
1269

    
1270
            result.addUpdatedObject(taxon);
1271
            if (synonym.getSynonymRelations().isEmpty()){
1272
                TaxonNameBase<?,?> name = synonym.getName();
1273
                synonym.setName(null);
1274
                dao.delete(synonym);
1275

    
1276
                //remove name if possible (and required)
1277
                if (name != null && config.isDeleteNameIfPossible()){
1278

    
1279
                        DeleteResult nameDeleteresult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
1280
                        if (nameDeleteresult.isAbort()){
1281
                        	result.addExceptions(nameDeleteresult.getExceptions());
1282
                        	result.addRelatedObject(name);
1283
                        }
1284

    
1285
                }
1286

    
1287
            }else {
1288
            	result.setError();
1289
            	result.addException(new ReferencedObjectUndeletableException("Synonym can not be deleted it is used in an other synonymRelationship."));
1290
                return result;
1291
            }
1292

    
1293

    
1294
        }
1295
        return result;
1296
//        else{
1297
//        	List<Exception> exceptions = new ArrayList<Exception>();
1298
//        	for (String message :messages){
1299
//        		exceptions.add(new ReferencedObjectUndeletableException(message));
1300
//        	}
1301
//        	result.setError();
1302
//        	result.addExceptions(exceptions);
1303
//            return result;
1304
//        }
1305

    
1306

    
1307
    }
1308

    
1309
    @Override
1310
    public List<TaxonNameBase> findIdenticalTaxonNameIds(List<String> propertyPath) {
1311

    
1312
        return this.dao.findIdenticalNamesNew(propertyPath);
1313
    }
1314

    
1315
    @Override
1316
    public String getPhylumName(TaxonNameBase name){
1317
        return this.dao.getPhylumName(name);
1318
    }
1319

    
1320
    @Override
1321
    public long deleteSynonymRelationships(Synonym syn) {
1322
        return dao.deleteSynonymRelationships(syn, null);
1323
    }
1324

    
1325
    @Override
1326
    public List<SynonymRelationship> listSynonymRelationships(
1327
            TaxonBase taxonBase, SynonymRelationshipType type, Integer pageSize, Integer pageNumber,
1328
            List<OrderHint> orderHints, List<String> propertyPaths, Direction direction) {
1329
        Integer numberOfResults = dao.countSynonymRelationships(taxonBase, type, direction);
1330

    
1331
        List<SynonymRelationship> results = new ArrayList<SynonymRelationship>();
1332
        if(numberOfResults > 0) { // no point checking again
1333
            results = dao.getSynonymRelationships(taxonBase, type, pageSize, pageNumber, orderHints, propertyPaths, direction);
1334
        }
1335
        return results;
1336
    }
1337

    
1338
    @Override
1339
    public Taxon findBestMatchingTaxon(String taxonName) {
1340
        MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
1341
        config.setTaxonNameTitle(taxonName);
1342
        return findBestMatchingTaxon(config);
1343
    }
1344

    
1345
    @Override
1346
    public Taxon findBestMatchingTaxon(MatchingTaxonConfigurator config) {
1347

    
1348
        Taxon bestCandidate = null;
1349
        try{
1350
            // 1. search for acceptet taxa
1351
            List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1352
            boolean bestCandidateMatchesSecUuid = false;
1353
            boolean bestCandidateIsInClassification = false;
1354
            int countEqualCandidates = 0;
1355
            for(TaxonBase taxonBaseCandidate : taxonList){
1356
                if(taxonBaseCandidate instanceof Taxon){
1357
                    Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
1358
                    boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
1359
                    if (! newCandidateMatchesSecUuid && config.isOnlyMatchingSecUuid() ){
1360
                        continue;
1361
                    }else if(newCandidateMatchesSecUuid && ! bestCandidateMatchesSecUuid){
1362
                        bestCandidate = newCanditate;
1363
                        countEqualCandidates = 1;
1364
                        bestCandidateMatchesSecUuid = true;
1365
                        continue;
1366
                    }
1367

    
1368
                    boolean newCandidateInClassification = isInClassification(newCanditate, config);
1369
                    if (! newCandidateInClassification && config.isOnlyMatchingClassificationUuid()){
1370
                        continue;
1371
                    }else if (newCandidateInClassification && ! bestCandidateIsInClassification){
1372
                        bestCandidate = newCanditate;
1373
                        countEqualCandidates = 1;
1374
                        bestCandidateIsInClassification = true;
1375
                        continue;
1376
                    }
1377
                    if (bestCandidate == null){
1378
                        bestCandidate = newCanditate;
1379
                        countEqualCandidates = 1;
1380
                        continue;
1381
                    }
1382

    
1383
                }else{  //not Taxon.class
1384
                    continue;
1385
                }
1386
                countEqualCandidates++;
1387

    
1388
            }
1389
            if (bestCandidate != null){
1390
                if(countEqualCandidates > 1){
1391
                    logger.info(countEqualCandidates + " equally matching TaxonBases found, using first accepted Taxon: " + bestCandidate.getTitleCache());
1392
                    return bestCandidate;
1393
                } else {
1394
                    logger.info("using accepted Taxon: " + bestCandidate.getTitleCache());
1395
                    return bestCandidate;
1396
                }
1397
            }
1398

    
1399

    
1400
            // 2. search for synonyms
1401
            if (config.isIncludeSynonyms()){
1402
                List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, null, null);
1403
                for(TaxonBase taxonBase : synonymList){
1404
                    if(taxonBase instanceof Synonym){
1405
                        Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
1406
                        Set<Taxon> acceptetdCandidates = synonym.getAcceptedTaxa();
1407
                        if(!acceptetdCandidates.isEmpty()){
1408
                            bestCandidate = acceptetdCandidates.iterator().next();
1409
                            if(acceptetdCandidates.size() == 1){
1410
                                logger.info(acceptetdCandidates.size() + " Accepted taxa found for synonym " + taxonBase.getTitleCache() + ", using first one: " + bestCandidate.getTitleCache());
1411
                                return bestCandidate;
1412
                            } else {
1413
                                logger.info("using accepted Taxon " +  bestCandidate.getTitleCache() + "for synonym " + taxonBase.getTitleCache());
1414
                                return bestCandidate;
1415
                            }
1416
                            //TODO extend method: search using treeUUID, using SecUUID, first find accepted then include synonyms until a matching taxon is found
1417
                        }
1418
                    }
1419
                }
1420
            }
1421

    
1422
        } catch (Exception e){
1423
            logger.error(e);
1424
            e.printStackTrace();
1425
        }
1426

    
1427
        return bestCandidate;
1428
    }
1429

    
1430
    private boolean isInClassification(Taxon taxon, MatchingTaxonConfigurator config) {
1431
        UUID configClassificationUuid = config.getClassificationUuid();
1432
        if (configClassificationUuid == null){
1433
            return false;
1434
        }
1435
        for (TaxonNode node : taxon.getTaxonNodes()){
1436
            UUID classUuid = node.getClassification().getUuid();
1437
            if (configClassificationUuid.equals(classUuid)){
1438
                return true;
1439
            }
1440
        }
1441
        return false;
1442
    }
1443

    
1444
    private boolean isMatchesSecUuid(Taxon taxon, MatchingTaxonConfigurator config) {
1445
        UUID configSecUuid = config.getSecUuid();
1446
        if (configSecUuid == null){
1447
            return false;
1448
        }
1449
        UUID taxonSecUuid = (taxon.getSec() == null)? null : taxon.getSec().getUuid();
1450
        return configSecUuid.equals(taxonSecUuid);
1451
    }
1452

    
1453
    @Override
1454
    public Synonym findBestMatchingSynonym(String taxonName) {
1455
        List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, taxonName, null, MatchMode.EXACT, null, 0, null, null);
1456
        if(! synonymList.isEmpty()){
1457
            Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
1458
            if(synonymList.size() == 1){
1459
                logger.info(synonymList.size() + " Synonym found " + result.getTitleCache() );
1460
                return result;
1461
            } else {
1462
                logger.info("Several matching synonyms found. Using first: " +  result.getTitleCache());
1463
                return result;
1464
            }
1465
        }
1466
        return null;
1467
    }
1468

    
1469
    @Override
1470
    @Transactional(readOnly = false)
1471
    public UpdateResult moveSynonymToAnotherTaxon(SynonymRelationship oldSynonymRelation,
1472
            Taxon newTaxon,
1473
            boolean moveHomotypicGroup,
1474
            SynonymRelationshipType newSynonymRelationshipType,
1475
            Reference reference,
1476
            String referenceDetail,
1477
            boolean keepReference) throws HomotypicalGroupChangeException {
1478

    
1479
        Synonym synonym = (Synonym) dao.load(oldSynonymRelation.getSynonym().getUuid());
1480
        Taxon fromTaxon = (Taxon) dao.load(oldSynonymRelation.getAcceptedTaxon().getUuid());
1481
        //TODO what if there is no name ?? Concepts may be cached (e.g. via TCS import)
1482
        TaxonNameBase<?,?> synonymName = synonym.getName();
1483
        TaxonNameBase<?,?> fromTaxonName = fromTaxon.getName();
1484
        //set default relationship type
1485
        if (newSynonymRelationshipType == null){
1486
            newSynonymRelationshipType = SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF();
1487
        }
1488
        boolean newRelTypeIsHomotypic = newSynonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF());
1489

    
1490
        HomotypicalGroup homotypicGroup = synonymName.getHomotypicalGroup();
1491
        int hgSize = homotypicGroup.getTypifiedNames().size();
1492
        boolean isSingleInGroup = !(hgSize > 1);
1493

    
1494
        if (! isSingleInGroup){
1495
            boolean isHomotypicToAccepted = synonymName.isHomotypic(fromTaxonName);
1496
            boolean hasHomotypicSynonymRelatives = isHomotypicToAccepted ? hgSize > 2 : hgSize > 1;
1497
            if (isHomotypicToAccepted){
1498
                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.";
1499
                String homotypicRelatives = hasHomotypicSynonymRelatives ? " and other synonym(s)":"";
1500
                message = String.format(message, homotypicRelatives);
1501
                throw new HomotypicalGroupChangeException(message);
1502
            }
1503
            if (! moveHomotypicGroup){
1504
                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.";
1505
                throw new HomotypicalGroupChangeException(message);
1506
            }
1507
        }else{
1508
            moveHomotypicGroup = true;  //single synonym always allows to moveCompleteGroup
1509
        }
1510
//        Assert.assertTrue("Synonym can only be moved with complete homotypic group", moveHomotypicGroup);
1511

    
1512
        UpdateResult result = new UpdateResult();
1513
        //move all synonyms to new taxon
1514
        List<Synonym> homotypicSynonyms = fromTaxon.getSynonymsInGroup(homotypicGroup);
1515
        for (Synonym syn: homotypicSynonyms){
1516
            Set<SynonymRelationship> synRelations = syn.getSynonymRelations();
1517
            for (SynonymRelationship synRelation : synRelations){
1518
                if (fromTaxon.equals(synRelation.getAcceptedTaxon())){
1519
                    Reference newReference = reference;
1520
                    if (newReference == null && keepReference){
1521
                        newReference = synRelation.getCitation();
1522
                    }
1523
                    String newRefDetail = referenceDetail;
1524
                    if (newRefDetail == null && keepReference){
1525
                        newRefDetail = synRelation.getCitationMicroReference();
1526
                    }
1527
                    newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
1528
                    fromTaxon = HibernateProxyHelper.deproxy(fromTaxon, Taxon.class);
1529
                    SynonymRelationship newSynRelation = newTaxon.addSynonym(syn, newSynonymRelationshipType, newReference, newRefDetail);
1530
                    fromTaxon.removeSynonymRelation(synRelation, false);
1531
//
1532
                    //change homotypic group of synonym if relType is 'homotypic'
1533
//                	if (newRelTypeIsHomotypic){
1534
//                		newTaxon.getName().getHomotypicalGroup().addTypifiedName(syn.getName());
1535
//                	}
1536
                    //set result
1537
                    if (!synRelation.equals(oldSynonymRelation)){
1538
                        result.setError();
1539
                    }
1540
                }
1541
            }
1542

    
1543
        }
1544
        result.addUpdatedObject(fromTaxon);
1545
        result.addUpdatedObject(newTaxon);
1546
        saveOrUpdate(fromTaxon);
1547
        saveOrUpdate(newTaxon);
1548
        //Assert that there is a result
1549
        if (result == null){
1550
            String message = "Old synonym relation could not be transformed into new relation. This should not happen.";
1551
            throw new IllegalStateException(message);
1552
        }
1553
        return result;
1554
    }
1555

    
1556
    @Override
1557
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheTaxon(Integer limit, String pattern) {
1558
        return dao.getUuidAndTitleCacheTaxon(limit, pattern);
1559
    }
1560

    
1561
    @Override
1562
    public List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCacheSynonym(Integer limit, String pattern) {
1563
        return dao.getUuidAndTitleCacheSynonym(limit, pattern);
1564
    }
1565

    
1566
    @Override
1567
    public Pager<SearchResult<TaxonBase>> findByFullText(
1568
            Class<? extends TaxonBase> clazz, String queryString,
1569
            Classification classification, List<Language> languages,
1570
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
1571

    
1572

    
1573
        LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null);
1574

    
1575
        // --- execute search
1576
        TopGroups<BytesRef> topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1577

    
1578
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1579
        idFieldMap.put(CdmBaseType.TAXON, "id");
1580

    
1581
        // ---  initialize taxa, thighlight matches ....
1582
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1583
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1584
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1585

    
1586
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
1587
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1588
    }
1589

    
1590
    @Override
1591
    public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
1592
            Classification classification,
1593
            Integer pageSize, Integer pageNumber,
1594
            List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, ParseException {
1595

    
1596
        LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
1597

    
1598
        // --- execute search
1599
        TopGroups<BytesRef> topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
1600

    
1601
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1602
        idFieldMap.put(CdmBaseType.TAXON, "id");
1603

    
1604
        // ---  initialize taxa, thighlight matches ....
1605
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
1606
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1607
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1608

    
1609
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
1610
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1611
    }
1612

    
1613
    /**
1614
     * @param clazz
1615
     * @param queryString
1616
     * @param classification
1617
     * @param languages
1618
     * @param highlightFragments
1619
     * @param sortFields TODO
1620
     * @param directorySelectClass
1621
     * @return
1622
     */
1623
    protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString, Classification classification, List<Language> languages,
1624
            boolean highlightFragments, SortField[] sortFields) {
1625
        Builder finalQueryBuilder = new Builder();
1626
        Builder textQueryBuilder = new Builder();
1627

    
1628
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1629
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1630

    
1631
        if(sortFields == null){
1632
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING,  false)};
1633
        }
1634
        luceneSearch.setSortFields(sortFields);
1635

    
1636
        // ---- search criteria
1637
        luceneSearch.setCdmTypRestriction(clazz);
1638

    
1639
        if(!queryString.isEmpty() && !queryString.equals("*") && !queryString.equals("?") ) {
1640
            textQueryBuilder.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
1641
            textQueryBuilder.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
1642
        }
1643

    
1644
        BooleanQuery textQuery = textQueryBuilder.build();
1645
        if(textQuery.clauses().size() > 0) {
1646
            finalQueryBuilder.add(textQuery, Occur.MUST);
1647
        }
1648

    
1649

    
1650
        if(classification != null){
1651
            finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
1652
        }
1653
        luceneSearch.setQuery(finalQueryBuilder.build());
1654

    
1655
        if(highlightFragments){
1656
            luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray());
1657
        }
1658
        return luceneSearch;
1659
    }
1660

    
1661
    /**
1662
     * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively
1663
     * the BlockJoinQuery could be used. The latter might be more memory save but has the
1664
     * drawback of requiring to do the join an indexing time.
1665
     * see  http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this.
1666
     *
1667
     * Joins TaxonRelationShip with Taxon depending on the direction of the given edge:
1668
     * <ul>
1669
     * <li>direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --&gt; Taxon.id </li>
1670
     * <li>inverse: {@link Direction.relatedFrom}:  TaxonRelationShip.relatedFrom.id --&gt; Taxon.id </li>
1671
     * <ul>
1672
     * @param queryString
1673
     * @param classification
1674
     * @param languages
1675
     * @param highlightFragments
1676
     * @param sortFields TODO
1677
     *
1678
     * @return
1679
     * @throws IOException
1680
     */
1681
    protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List<Language> languages,
1682
            boolean highlightFragments, SortField[] sortFields) throws IOException {
1683

    
1684
        String fromField;
1685
        String queryTermField;
1686
        String toField = "id"; // TaxonBase.uuid
1687

    
1688
        if(edge.isBidirectional()){
1689
            throw new RuntimeException("Bidirectional joining not supported!");
1690
        }
1691
        if(edge.isEvers()){
1692
            fromField = "relatedFrom.id";
1693
            queryTermField = "relatedFrom.titleCache";
1694
        } else if(edge.isInvers()) {
1695
            fromField = "relatedTo.id";
1696
            queryTermField = "relatedTo.titleCache";
1697
        } else {
1698
            throw new RuntimeException("Invalid direction: " + edge.getDirections());
1699
        }
1700

    
1701
        Builder finalQueryBuilder = new Builder();
1702

    
1703
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class);
1704
        QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
1705

    
1706
        Builder joinFromQueryBuilder = new Builder();
1707
        joinFromQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
1708
        joinFromQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST);
1709
        Query joinQuery = taxonBaseQueryFactory.newJoinQuery(TaxonRelationship.class, fromField, false, joinFromQueryBuilder.build(), toField, null, ScoreMode.Max);
1710

    
1711
        if(sortFields == null){
1712
            sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING,  false)};
1713
        }
1714
        luceneSearch.setSortFields(sortFields);
1715

    
1716
        finalQueryBuilder.add(joinQuery, Occur.MUST);
1717

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

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

    
1729
    @Override
1730
    public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
1731
            EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
1732
            Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
1733
            boolean highlightFragments, Integer pageSize,
1734
            Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
1735
            throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
1736

    
1737
        // FIXME: allow taxonomic ordering
1738
        //  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";
1739
        // this require building a special sort column by a special classBridge
1740
        if(highlightFragments){
1741
            logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " +
1742
                    "currently not fully supported by this method and thus " +
1743
                    "may not work with common names and misapplied names.");
1744
        }
1745

    
1746
        // convert sets to lists
1747
        List<NamedArea> namedAreaList = null;
1748
        List<PresenceAbsenceTerm>distributionStatusList = null;
1749
        if(namedAreas != null){
1750
            namedAreaList = new ArrayList<NamedArea>(namedAreas.size());
1751
            namedAreaList.addAll(namedAreas);
1752
        }
1753
        if(distributionStatus != null){
1754
            distributionStatusList = new ArrayList<PresenceAbsenceTerm>(distributionStatus.size());
1755
            distributionStatusList.addAll(distributionStatus);
1756
        }
1757

    
1758
        // set default if parameter is null
1759
        if(searchModes == null){
1760
            searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa);
1761
        }
1762

    
1763
        // set sort order and thus override any sort orders which may have been
1764
        // defined by prepare*Search methods
1765
        if(orderHints == null){
1766
            orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER.asList();
1767
        }
1768
        SortField[] sortFields = new SortField[orderHints.size()];
1769
        int i = 0;
1770
        for(OrderHint oh : orderHints){
1771
            sortFields[i++] = oh.toSortField();
1772
        }
1773
//        SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
1774
//        SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
1775

    
1776

    
1777
        boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
1778

    
1779
        List<LuceneSearch> luceneSearches = new ArrayList<LuceneSearch>();
1780
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
1781

    
1782
        /*
1783
          ======== filtering by distribution , HOWTO ========
1784

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

    
1790

    
1791
          3. how does it work in spatial?
1792
          see
1793
           - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html
1794
           - http://www.infoq.com/articles/LuceneSpatialSupport
1795
           - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html
1796
          ------------------------------------------------------------------------
1797

    
1798
          filter strategies:
1799
          A) use a separate distribution filter per index sub-query/search:
1800
           - byTaxonSyonym (query TaxaonBase):
1801
               use a join area filter (Distribution -> TaxonBase)
1802
           - byCommonName (query DescriptionElementBase): use an area filter on
1803
               DescriptionElementBase !!! PROBLEM !!!
1804
               This cannot work since the distributions are different entities than the
1805
               common names and thus these are different lucene documents.
1806
           - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase):
1807
               use a join area filter (Distribution -> TaxonBase)
1808

    
1809
          B) use a common distribution filter for all index sub-query/searches:
1810
           - use a common join area filter (Distribution -> TaxonBase)
1811
           - also implement the byCommonName as join query (CommonName -> TaxonBase)
1812
           PROBLEM in this case: we are losing the fragment highlighting for the
1813
           common names, since the returned documents are always TaxonBases
1814
        */
1815

    
1816
        /* The QueryFactory for creating filter queries on Distributions should
1817
         * The query factory used for the common names query cannot be reused
1818
         * for this case, since we want to only record the text fields which are
1819
         * actually used in the primary query
1820
         */
1821
        QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class);
1822

    
1823
        Builder multiIndexByAreaFilterBuilder = new Builder();
1824

    
1825
        // search for taxa or synonyms
1826
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1827
            Class taxonBaseSubclass = TaxonBase.class;
1828
            if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1829
                taxonBaseSubclass = Taxon.class;
1830
            } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
1831
                taxonBaseSubclass = Synonym.class;
1832
            }
1833
            luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields));
1834
            idFieldMap.put(CdmBaseType.TAXON, "id");
1835
            /* A) does not work!!!!
1836
            if(addDistributionFilter){
1837
                // in this case we need a filter which uses a join query
1838
                // to get the TaxonBase documents for the DescriptionElementBase documents
1839
                // which are matching the areas in question
1840
                Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1841
                        namedAreaList,
1842
                        distributionStatusList,
1843
                        distributionFilterQueryFactory
1844
                        );
1845
                multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD);
1846
            }
1847
            */
1848
            if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){
1849
                // add additional area filter for synonyms
1850
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1851
                String toField = "accTaxon.id"; // id in TaxonBase index (is multivalued)
1852

    
1853
                //TODO replace by createByDistributionJoinQuery
1854
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1855
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class, fromField, true, byDistributionQuery, toField, Taxon.class, ScoreMode.None);
1856
                multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
1857

    
1858
            }
1859
        }
1860

    
1861
        // search by CommonTaxonName
1862
        if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) {
1863
            // B)
1864
            QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
1865
            Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery(
1866
                    CommonTaxonName.class,
1867
                    "inDescription.taxon.id",
1868
                    true,
1869
                    QueryFactory.addTypeRestriction(
1870
                                createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
1871
                                , CommonTaxonName.class
1872
                                ).build(), "id", null, ScoreMode.Max);
1873
            logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());
1874
            LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
1875
            byCommonNameSearch.setCdmTypRestriction(Taxon.class);
1876
            byCommonNameSearch.setQuery(byCommonNameJoinQuery);
1877
            byCommonNameSearch.setSortFields(sortFields);
1878
            idFieldMap.put(CdmBaseType.TAXON, "id");
1879

    
1880
            luceneSearches.add(byCommonNameSearch);
1881

    
1882
            /* A) does not work!!!!
1883
            luceneSearches.add(
1884
                    prepareByDescriptionElementFullTextSearch(CommonTaxonName.class,
1885
                            queryString, classification, null, languages, highlightFragments)
1886
                        );
1887
            idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
1888
            if(addDistributionFilter){
1889
                // in this case we are able to use DescriptionElementBase documents
1890
                // which are matching the areas in question directly
1891
                BooleanQuery byDistributionQuery = createByDistributionQuery(
1892
                        namedAreaList,
1893
                        distributionStatusList,
1894
                        distributionFilterQueryFactory
1895
                        );
1896
                multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD);
1897
            } */
1898
        }
1899

    
1900
        // search by misapplied names
1901
        if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) {
1902
            // NOTE:
1903
            // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery()
1904
            // which allows doing query time joins
1905
            // finds the misapplied name (Taxon B) which is an misapplication for
1906
            // a related Taxon A.
1907
            //
1908
            luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
1909
                    new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo),
1910
                    queryString, classification, languages, highlightFragments, sortFields));
1911
            idFieldMap.put(CdmBaseType.TAXON, "id");
1912

    
1913
            if(addDistributionFilter){
1914
                String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
1915

    
1916
                /*
1917
                 * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution.
1918
                 * Maybe this is a bug in java itself java.
1919
                 *
1920
                 * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString()
1921
                 * directly:
1922
                 *
1923
                 *    String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id";
1924
                 *
1925
                 * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query
1926
                 * will execute as expected:
1927
                 *
1928
                 *    String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1929
                 *    String toField = "relation." + misappliedNameForUuid +".to.id";
1930
                 *
1931
                 * Comparing both strings by the String.equals method returns true, so both String are identical.
1932
                 *
1933
                 * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be
1934
                 * 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)
1935
                 * The bug is persistent after a reboot of the development computer.
1936
                 */
1937
//                String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString();
1938
//                String toField = "relation." + misappliedNameForUuid +".to.id";
1939
                String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id";
1940
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different");
1941
//                System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different");
1942

    
1943
                //TODO replace by createByDistributionJoinQuery
1944
                BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory);
1945
                Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(Distribution.class, fromField, true, byDistributionQuery, toField, null, ScoreMode.None);
1946

    
1947
//                debug code for bug described above
1948
                //does not compile anymore since changing from lucene 3.6.2 to lucene 4.10+
1949
//                DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class));
1950
//                System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100));
1951

    
1952
                multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
1953
            }
1954
        }
1955

    
1956
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
1957
                luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
1958

    
1959

    
1960
        if(addDistributionFilter){
1961

    
1962
            // B)
1963
            // in this case we need a filter which uses a join query
1964
            // to get the TaxonBase documents for the DescriptionElementBase documents
1965
            // which are matching the areas in question
1966
            //
1967
            // for toTaxa, doByCommonName
1968
            Query taxonAreaJoinQuery = createByDistributionJoinQuery(
1969
                    namedAreaList,
1970
                    distributionStatusList,
1971
                    distributionFilterQueryFactory,
1972
                    Taxon.class, true
1973
                    );
1974
            multiIndexByAreaFilterBuilder.add(taxonAreaJoinQuery, Occur.SHOULD);
1975
        }
1976

    
1977
        if (addDistributionFilter){
1978
            multiSearch.setFilter(multiIndexByAreaFilterBuilder.build());
1979
        }
1980

    
1981

    
1982
        // --- execute search
1983
        TopGroups<BytesRef> topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
1984

    
1985
        // --- initialize taxa, highlight matches ....
1986
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
1987

    
1988

    
1989
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
1990
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
1991

    
1992
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
1993
        return new DefaultPagerImpl<SearchResult<TaxonBase>>(pageNumber, totalHits, pageSize, searchResults);
1994
    }
1995

    
1996
    /**
1997
     * @param namedAreaList at least one area must be in the list
1998
     * @param distributionStatusList optional
1999
     * @param toType toType
2000
     *      Optional parameter. Only used for debugging to print the toType documents
2001
     * @param asFilter TODO
2002
     * @return
2003
     * @throws IOException
2004
     */
2005
    protected Query createByDistributionJoinQuery(
2006
            List<NamedArea> namedAreaList,
2007
            List<PresenceAbsenceTerm> distributionStatusList,
2008
            QueryFactory queryFactory, Class<? extends CdmBase> toType, boolean asFilter
2009
            ) throws IOException {
2010

    
2011
        String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index
2012
        String toField = "id"; // id in toType usually this is the TaxonBase index
2013

    
2014
        BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory);
2015

    
2016
        ScoreMode scoreMode = asFilter ?  ScoreMode.None : ScoreMode.Max;
2017

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

    
2020
        return taxonAreaJoinQuery;
2021
    }
2022

    
2023
    /**
2024
     * @param namedAreaList
2025
     * @param distributionStatusList
2026
     * @param queryFactory
2027
     * @return
2028
     */
2029
    private BooleanQuery createByDistributionQuery(List<NamedArea> namedAreaList,
2030
            List<PresenceAbsenceTerm> distributionStatusList, QueryFactory queryFactory) {
2031
        Builder areaQueryBuilder = new Builder();
2032
        // area field from Distribution
2033
        areaQueryBuilder.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST);
2034

    
2035
        // status field from Distribution
2036
        if(distributionStatusList != null && distributionStatusList.size() > 0){
2037
            areaQueryBuilder.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST);
2038
        }
2039

    
2040
        BooleanQuery areaQuery = areaQueryBuilder.build();
2041
        logger.debug("createByDistributionQuery() query: " + areaQuery.toString());
2042
        return areaQuery;
2043
    }
2044

    
2045
    /**
2046
     * This method has been primarily created for testing the area join query but might
2047
     * also be useful in other situations
2048
     *
2049
     * @param namedAreaList
2050
     * @param distributionStatusList
2051
     * @param classification
2052
     * @param highlightFragments
2053
     * @return
2054
     * @throws IOException
2055
     */
2056
    protected LuceneSearch prepareByDistributionSearch(
2057
            List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
2058
            Classification classification) throws IOException {
2059

    
2060
        Builder finalQueryBuilder = new Builder();
2061

    
2062
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class);
2063

    
2064
        // FIXME is this query factory using the wrong type?
2065
        QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class);
2066

    
2067
        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false)};
2068
        luceneSearch.setSortFields(sortFields);
2069

    
2070

    
2071
        Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory, null, false);
2072

    
2073
        finalQueryBuilder.add(byAreaQuery, Occur.MUST);
2074

    
2075
        if(classification != null){
2076
            finalQueryBuilder.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
2077
        }
2078
        BooleanQuery finalQuery = finalQueryBuilder.build();
2079
        logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
2080
        luceneSearch.setQuery(finalQuery);
2081

    
2082
        return luceneSearch;
2083
    }
2084

    
2085
    @Override
2086
    public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
2087
            Class<? extends DescriptionElementBase> clazz, String queryString,
2088
            Classification classification, List<Feature> features, List<Language> languages,
2089
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
2090

    
2091

    
2092
        LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
2093

    
2094
        // --- execute search
2095
        TopGroups<BytesRef> topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
2096

    
2097
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
2098
        idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id");
2099

    
2100
        // --- initialize taxa, highlight matches ....
2101
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
2102
        @SuppressWarnings("rawtypes")
2103
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2104
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2105

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

    
2109
    }
2110

    
2111

    
2112
    @Override
2113
    public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
2114
            Classification classification, List<Language> languages, boolean highlightFragments,
2115
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException {
2116

    
2117
        LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments);
2118
        LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null);
2119

    
2120
        LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
2121

    
2122
        // --- execute search
2123
        TopGroups<BytesRef> topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber);
2124

    
2125
        // --- initialize taxa, highlight matches ....
2126
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery());
2127

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

    
2132
        List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
2133
                topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
2134

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

    
2138
    }
2139

    
2140

    
2141
    /**
2142
     * @param clazz
2143
     * @param queryString
2144
     * @param classification
2145
     * @param features
2146
     * @param languages
2147
     * @param highlightFragments
2148
     * @param directorySelectClass
2149
     * @return
2150
     */
2151
    protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
2152
            String queryString, Classification classification, List<Feature> features,
2153
            List<Language> languages, boolean highlightFragments) {
2154

    
2155
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
2156
        QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class);
2157

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

    
2160
        BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
2161
                languages, descriptionElementQueryFactory);
2162

    
2163
        luceneSearch.setSortFields(sortFields);
2164
        luceneSearch.setCdmTypRestriction(clazz);
2165
        luceneSearch.setQuery(finalQuery);
2166
        if(highlightFragments){
2167
            luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray());
2168
        }
2169

    
2170
        return luceneSearch;
2171
    }
2172

    
2173
    /**
2174
     * @param queryString
2175
     * @param classification
2176
     * @param features
2177
     * @param languages
2178
     * @param descriptionElementQueryFactory
2179
     * @return
2180
     */
2181
    private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
2182
            List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
2183
        Builder finalQueryBuilder = new Builder();
2184
        Builder textQueryBuilder = new Builder();
2185
        textQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
2186

    
2187
        // common name
2188
        Builder nameQueryBuilder = new Builder();
2189
        if(languages == null || languages.size() == 0){
2190
            nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2191
        } else {
2192
            Builder languageSubQueryBuilder = new Builder();
2193
            for(Language lang : languages){
2194
                languageSubQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("language.uuid",  lang.getUuid().toString(), false), Occur.SHOULD);
2195
            }
2196
            nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
2197
            nameQueryBuilder.add(languageSubQueryBuilder.build(), Occur.MUST);
2198
        }
2199
        textQueryBuilder.add(nameQueryBuilder.build(), Occur.SHOULD);
2200

    
2201

    
2202
        // text field from TextData
2203
        textQueryBuilder.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
2204

    
2205
        // --- TermBase fields - by representation ----
2206
        // state field from CategoricalData
2207
        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
2208

    
2209
        // state field from CategoricalData
2210
        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
2211

    
2212
        // area field from Distribution
2213
        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
2214

    
2215
        // status field from Distribution
2216
        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
2217

    
2218
        finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
2219
        // --- classification ----
2220

    
2221
        if(classification != null){
2222
            finalQueryBuilder.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
2223
        }
2224

    
2225
        // --- IdentifieableEntity fields - by uuid
2226
        if(features != null && features.size() > 0 ){
2227
            finalQueryBuilder.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST);
2228
        }
2229

    
2230
        // the description must be associated with a taxon
2231
        finalQueryBuilder.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST);
2232

    
2233
        BooleanQuery finalQuery = finalQueryBuilder.build();
2234
        logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString());
2235
        return finalQuery;
2236
    }
2237

    
2238
    /**
2239
     * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
2240
     * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
2241
     * This method is a convenient means to retrieve a Lucene query string for such the fields.
2242
     *
2243
     * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
2244
     * or {@link MultilanguageTextFieldBridge }
2245
     * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
2246
     * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
2247
     * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
2248
     *
2249
     * TODO move to utiliy class !!!!!!!!
2250
     */
2251
    private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
2252

    
2253
        if(stringBuilder == null){
2254
            stringBuilder = new StringBuilder();
2255
        }
2256
        if(languages == null || languages.size() == 0){
2257
            stringBuilder.append(name + ".ALL:(%1$s) ");
2258
        } else {
2259
            for(Language lang : languages){
2260
                stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
2261
            }
2262
        }
2263
        return stringBuilder;
2264
    }
2265

    
2266
    @Override
2267
    public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymRelationshipType type, boolean doWithMisappliedNames){
2268
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2269
        List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<Synonym>();
2270

    
2271
        HashMap <UUID, ZoologicalName> zooHashMap = new HashMap<UUID, ZoologicalName>();
2272

    
2273

    
2274
        UUID nameUuid= taxon.getName().getUuid();
2275
        ZoologicalName taxonName = getZoologicalName(nameUuid, zooHashMap);
2276
        String epithetOfTaxon = null;
2277
        String infragenericEpithetOfTaxon = null;
2278
        String infraspecificEpithetOfTaxon = null;
2279
        if (taxonName.isSpecies()){
2280
             epithetOfTaxon= taxonName.getSpecificEpithet();
2281
        } else if (taxonName.isInfraGeneric()){
2282
            infragenericEpithetOfTaxon = taxonName.getInfraGenericEpithet();
2283
        } else if (taxonName.isInfraSpecific()){
2284
            infraspecificEpithetOfTaxon = taxonName.getInfraSpecificEpithet();
2285
        }
2286
        String genusOfTaxon = taxonName.getGenusOrUninomial();
2287
        Set<TaxonNode> nodes = taxon.getTaxonNodes();
2288
        List<String> taxonNames = new ArrayList<String>();
2289

    
2290
        for (TaxonNode node: nodes){
2291
           // HashMap<String, String> synonymsGenus = new HashMap<String, String>(); // Changed this to be able to store the idInSource to a genusName
2292
           // List<String> synonymsEpithet = new ArrayList<String>();
2293

    
2294
            if (node.getClassification().equals(classification)){
2295
                if (!node.isTopmostNode()){
2296
                    TaxonNode parent = node.getParent();
2297
                    parent = HibernateProxyHelper.deproxy(parent);
2298
                    TaxonNameBase<?,?> parentName =  parent.getTaxon().getName();
2299
                    ZoologicalName zooParentName = HibernateProxyHelper.deproxy(parentName, ZoologicalName.class);
2300
                    Taxon parentTaxon = HibernateProxyHelper.deproxy(parent.getTaxon());
2301
                    Rank rankOfTaxon = taxonName.getRank();
2302

    
2303

    
2304
                    //create inferred synonyms for species, subspecies
2305
                    if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
2306

    
2307
                        Synonym inferredEpithet = null;
2308
                        Synonym inferredGenus = null;
2309
                        Synonym potentialCombination = null;
2310

    
2311
                        List<String> propertyPaths = new ArrayList<String>();
2312
                        propertyPaths.add("synonym");
2313
                        propertyPaths.add("synonym.name");
2314
                        List<OrderHint> orderHints = new ArrayList<OrderHint>();
2315
                        orderHints.add(new OrderHint("relatedFrom.titleCache", SortOrder.ASCENDING));
2316

    
2317
                        List<SynonymRelationship> synonymRelationshipsOfParent = dao.getSynonyms(parentTaxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2318
                        List<SynonymRelationship> synonymRelationshipsOfTaxon= dao.getSynonyms(taxon, SynonymRelationshipType.HETEROTYPIC_SYNONYM_OF(), null, null,orderHints,propertyPaths);
2319

    
2320
                        List<TaxonRelationship> taxonRelListParent = null;
2321
                        List<TaxonRelationship> taxonRelListTaxon = null;
2322
                        if (doWithMisappliedNames){
2323
                            taxonRelListParent = dao.getTaxonRelationships(parentTaxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2324
                            taxonRelListTaxon = dao.getTaxonRelationships(taxon, TaxonRelationshipType.MISAPPLIED_NAME_FOR(), null, null, orderHints, propertyPaths, Direction.relatedTo);
2325
                        }
2326

    
2327

    
2328
                        if (type.equals(SynonymRelationshipType.INFERRED_EPITHET_OF())){
2329

    
2330

    
2331
                            for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2332
                                Synonym syn = synonymRelationOfParent.getSynonym();
2333

    
2334
                                inferredEpithet = createInferredEpithets(taxon,
2335
                                        zooHashMap, taxonName, epithetOfTaxon,
2336
                                        infragenericEpithetOfTaxon,
2337
                                        infraspecificEpithetOfTaxon,
2338
                                        taxonNames, parentName,
2339
                                        syn);
2340

    
2341

    
2342
                                inferredSynonyms.add(inferredEpithet);
2343
                                zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2344
                                taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2345
                            }
2346

    
2347
                            if (doWithMisappliedNames){
2348

    
2349
                                for (TaxonRelationship taxonRelationship: taxonRelListParent){
2350
                                     Taxon misappliedName = taxonRelationship.getFromTaxon();
2351

    
2352
                                     inferredEpithet = createInferredEpithets(taxon,
2353
                                             zooHashMap, taxonName, epithetOfTaxon,
2354
                                             infragenericEpithetOfTaxon,
2355
                                             infraspecificEpithetOfTaxon,
2356
                                             taxonNames, parentName,
2357
                                             misappliedName);
2358

    
2359
                                    inferredSynonyms.add(inferredEpithet);
2360
                                    zooHashMap.put(inferredEpithet.getName().getUuid(), (ZoologicalName)inferredEpithet.getName());
2361
                                     taxonNames.add(((ZoologicalName)inferredEpithet.getName()).getNameCache());
2362
                                }
2363
                            }
2364

    
2365
                            if (!taxonNames.isEmpty()){
2366
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2367
                            ZoologicalName name;
2368
                            if (!synNotInCDM.isEmpty()){
2369
                                inferredSynonymsToBeRemoved.clear();
2370

    
2371
                                for (Synonym syn :inferredSynonyms){
2372
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2373
                                    if (!synNotInCDM.contains(name.getNameCache())){
2374
                                        inferredSynonymsToBeRemoved.add(syn);
2375
                                    }
2376
                                }
2377

    
2378
                                // Remove identified Synonyms from inferredSynonyms
2379
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2380
                                    inferredSynonyms.remove(synonym);
2381
                                }
2382
                            }
2383
                        }
2384

    
2385
                    }else if (type.equals(SynonymRelationshipType.INFERRED_GENUS_OF())){
2386

    
2387

    
2388
                        for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2389
                            TaxonNameBase synName;
2390
                            ZoologicalName inferredSynName;
2391

    
2392
                            Synonym syn = synonymRelationOfTaxon.getSynonym();
2393
                            inferredGenus = createInferredGenus(taxon,
2394
                                    zooHashMap, taxonName, epithetOfTaxon,
2395
                                    genusOfTaxon, taxonNames, zooParentName, syn);
2396

    
2397
                            inferredSynonyms.add(inferredGenus);
2398
                            zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2399
                            taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2400

    
2401

    
2402
                        }
2403

    
2404
                        if (doWithMisappliedNames){
2405

    
2406
                            for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2407
                                Taxon misappliedName = taxonRelationship.getFromTaxon();
2408
                                inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
2409

    
2410
                                inferredSynonyms.add(inferredGenus);
2411
                                zooHashMap.put(inferredGenus.getName().getUuid(), (ZoologicalName)inferredGenus.getName());
2412
                                 taxonNames.add(( (ZoologicalName)inferredGenus.getName()).getNameCache());
2413
                            }
2414
                        }
2415

    
2416

    
2417
                        if (!taxonNames.isEmpty()){
2418
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2419
                            ZoologicalName name;
2420
                            if (!synNotInCDM.isEmpty()){
2421
                                inferredSynonymsToBeRemoved.clear();
2422

    
2423
                                for (Synonym syn :inferredSynonyms){
2424
                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2425
                                    if (!synNotInCDM.contains(name.getNameCache())){
2426
                                        inferredSynonymsToBeRemoved.add(syn);
2427
                                    }
2428
                                }
2429

    
2430
                                // Remove identified Synonyms from inferredSynonyms
2431
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2432
                                    inferredSynonyms.remove(synonym);
2433
                                }
2434
                            }
2435
                        }
2436

    
2437
                    }else if (type.equals(SynonymRelationshipType.POTENTIAL_COMBINATION_OF())){
2438

    
2439
                        Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
2440
                        ZoologicalName inferredSynName;
2441
                        //for all synonyms of the parent...
2442
                        for (SynonymRelationship synonymRelationOfParent:synonymRelationshipsOfParent){
2443
                            TaxonNameBase synName;
2444
                            Synonym synParent = synonymRelationOfParent.getSynonym();
2445
                            synName = synParent.getName();
2446

    
2447
                            HibernateProxyHelper.deproxy(synParent);
2448

    
2449
                            // Set the sourceReference
2450
                            sourceReference = synParent.getSec();
2451

    
2452
                            // Determine the idInSource
2453
                            String idInSourceParent = getIdInSource(synParent);
2454

    
2455
                            ZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2456
                            String synParentGenus = parentSynZooName.getGenusOrUninomial();
2457
                            String synParentInfragenericName = null;
2458
                            String synParentSpecificEpithet = null;
2459

    
2460
                            if (parentSynZooName.isInfraGeneric()){
2461
                                synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2462
                            }
2463
                            if (parentSynZooName.isSpecies()){
2464
                                synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2465
                            }
2466

    
2467
                           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2468
                                synonymsGenus.put(synGenusName, idInSource);
2469
                            }*/
2470

    
2471
                            //for all synonyms of the taxon
2472

    
2473
                            for (SynonymRelationship synonymRelationOfTaxon:synonymRelationshipsOfTaxon){
2474

    
2475
                                Synonym syn = synonymRelationOfTaxon.getSynonym();
2476
                                ZoologicalName zooSynName = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2477
                                potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
2478
                                        synParentGenus,
2479
                                        synParentInfragenericName,
2480
                                        synParentSpecificEpithet, syn, zooHashMap);
2481

    
2482
                                taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2483
                                inferredSynonyms.add(potentialCombination);
2484
                                zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2485
                                 taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2486

    
2487
                            }
2488

    
2489

    
2490
                        }
2491

    
2492
                        if (doWithMisappliedNames){
2493

    
2494
                            for (TaxonRelationship parentRelationship: taxonRelListParent){
2495

    
2496
                                TaxonNameBase misappliedParentName;
2497

    
2498
                                Taxon misappliedParent = parentRelationship.getFromTaxon();
2499
                                misappliedParentName = misappliedParent.getName();
2500

    
2501
                                HibernateProxyHelper.deproxy(misappliedParent);
2502

    
2503
                                // Set the sourceReference
2504
                                sourceReference = misappliedParent.getSec();
2505

    
2506
                                // Determine the idInSource
2507
                                String idInSourceParent = getIdInSource(misappliedParent);
2508

    
2509
                                ZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
2510
                                String synParentGenus = parentSynZooName.getGenusOrUninomial();
2511
                                String synParentInfragenericName = null;
2512
                                String synParentSpecificEpithet = null;
2513

    
2514
                                if (parentSynZooName.isInfraGeneric()){
2515
                                    synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
2516
                                }
2517
                                if (parentSynZooName.isSpecies()){
2518
                                    synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
2519
                                }
2520

    
2521

    
2522
                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
2523
                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
2524
                                    ZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
2525
                                    potentialCombination = createPotentialCombination(
2526
                                            idInSourceParent, parentSynZooName, zooMisappliedName,
2527
                                            synParentGenus,
2528
                                            synParentInfragenericName,
2529
                                            synParentSpecificEpithet, misappliedName, zooHashMap);
2530

    
2531

    
2532
                                    taxon.addSynonym(potentialCombination, SynonymRelationshipType.POTENTIAL_COMBINATION_OF());
2533
                                    inferredSynonyms.add(potentialCombination);
2534
                                    zooHashMap.put(potentialCombination.getName().getUuid(), (ZoologicalName)potentialCombination.getName());
2535
                                     taxonNames.add(( (ZoologicalName)potentialCombination.getName()).getNameCache());
2536
                                }
2537
                            }
2538
                        }
2539

    
2540
                        if (!taxonNames.isEmpty()){
2541
                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
2542
                            ZoologicalName name;
2543
                            if (!synNotInCDM.isEmpty()){
2544
                                inferredSynonymsToBeRemoved.clear();
2545
                                for (Synonym syn :inferredSynonyms){
2546
                                    try{
2547
                                        name = (ZoologicalName) syn.getName();
2548
                                    }catch (ClassCastException e){
2549
                                        name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
2550
                                    }
2551
                                    if (!synNotInCDM.contains(name.getNameCache())){
2552
                                        inferredSynonymsToBeRemoved.add(syn);
2553
                                    }
2554
                                 }
2555
                                // Remove identified Synonyms from inferredSynonyms
2556
                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
2557
                                    inferredSynonyms.remove(synonym);
2558
                                }
2559
                            }
2560
                         }
2561
                        }
2562
                    }else {
2563
                        logger.info("The synonymrelationship type is not defined.");
2564
                        return inferredSynonyms;
2565
                    }
2566
                }
2567
            }
2568

    
2569
        }
2570

    
2571
        return inferredSynonyms;
2572
    }
2573

    
2574
    private Synonym createPotentialCombination(String idInSourceParent,
2575
            ZoologicalName parentSynZooName, 	ZoologicalName zooSynName, String synParentGenus,
2576
            String synParentInfragenericName, String synParentSpecificEpithet,
2577
            TaxonBase syn, HashMap<UUID, ZoologicalName> zooHashMap) {
2578
        Synonym potentialCombination;
2579
        Reference sourceReference;
2580
        ZoologicalName inferredSynName;
2581
        HibernateProxyHelper.deproxy(syn);
2582

    
2583
        // Set sourceReference
2584
        sourceReference = syn.getSec();
2585
        if (sourceReference == null){
2586
            logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon");
2587
            //TODO:Remove
2588
            if (!parentSynZooName.getTaxa().isEmpty()){
2589
                TaxonBase taxon = parentSynZooName.getTaxa().iterator().next();
2590

    
2591
                sourceReference = taxon.getSec();
2592
            }
2593
        }
2594
        String synTaxonSpecificEpithet = zooSynName.getSpecificEpithet();
2595

    
2596
        String synTaxonInfraSpecificName= null;
2597

    
2598
        if (parentSynZooName.isSpecies()){
2599
            synTaxonInfraSpecificName = zooSynName.getInfraSpecificEpithet();
2600
        }
2601

    
2602
        /*if (epithetName != null && !synonymsEpithet.contains(epithetName)){
2603
            synonymsEpithet.add(epithetName);
2604
        }*/
2605

    
2606
        //create potential combinations...
2607
        inferredSynName = ZoologicalName.NewInstance(syn.getName().getRank());
2608

    
2609
        inferredSynName.setGenusOrUninomial(synParentGenus);
2610
        if (zooSynName.isSpecies()){
2611
              inferredSynName.setSpecificEpithet(synTaxonSpecificEpithet);
2612
              if (parentSynZooName.isInfraGeneric()){
2613
                  inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2614
              }
2615
        }
2616
        if (zooSynName.isInfraSpecific()){
2617
            inferredSynName.setSpecificEpithet(synParentSpecificEpithet);
2618
            inferredSynName.setInfraSpecificEpithet(synTaxonInfraSpecificName);
2619
        }
2620
        if (parentSynZooName.isInfraGeneric()){
2621
            inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
2622
        }
2623

    
2624

    
2625
        potentialCombination = Synonym.NewInstance(inferredSynName, null);
2626

    
2627
        // Set the sourceReference
2628
        potentialCombination.setSec(sourceReference);
2629

    
2630

    
2631
        // Determine the idInSource
2632
        String idInSourceSyn= getIdInSource(syn);
2633

    
2634
        if (idInSourceParent != null && idInSourceSyn != null) {
2635
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2636
            inferredSynName.addSource(originalSource);
2637
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation, idInSourceSyn + "; " + idInSourceParent, POTENTIAL_COMBINATION_NAMESPACE, sourceReference, null);
2638
            potentialCombination.addSource(originalSource);
2639
        }
2640

    
2641
        return potentialCombination;
2642
    }
2643

    
2644
    private Synonym createInferredGenus(Taxon taxon,
2645
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2646
            String epithetOfTaxon, String genusOfTaxon,
2647
            List<String> taxonNames, ZoologicalName zooParentName,
2648
            TaxonBase syn) {
2649

    
2650
        Synonym inferredGenus;
2651
        TaxonNameBase synName;
2652
        ZoologicalName inferredSynName;
2653
        synName =syn.getName();
2654
        HibernateProxyHelper.deproxy(syn);
2655

    
2656
        // Determine the idInSource
2657
        String idInSourceSyn = getIdInSource(syn);
2658
        String idInSourceTaxon = getIdInSource(taxon);
2659
        // Determine the sourceReference
2660
        Reference sourceReference = syn.getSec();
2661

    
2662
        //logger.warn(sourceReference.getTitleCache());
2663

    
2664
        synName = syn.getName();
2665
        ZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
2666
        String synSpeciesEpithetName = synZooName.getSpecificEpithet();
2667
                     /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
2668
            synonymsEpithet.add(synSpeciesEpithetName);
2669
        }*/
2670

    
2671
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2672
        //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...
2673

    
2674

    
2675
        inferredSynName.setGenusOrUninomial(genusOfTaxon);
2676
        if (zooParentName.isInfraGeneric()){
2677
            inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
2678
        }
2679

    
2680
        if (taxonName.isSpecies()){
2681
            inferredSynName.setSpecificEpithet(synSpeciesEpithetName);
2682
        }
2683
        if (taxonName.isInfraSpecific()){
2684
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2685
            inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
2686
        }
2687

    
2688

    
2689
        inferredGenus = Synonym.NewInstance(inferredSynName, null);
2690

    
2691
        // Set the sourceReference
2692
        inferredGenus.setSec(sourceReference);
2693

    
2694
        // Add the original source
2695
        if (idInSourceSyn != null && idInSourceTaxon != null) {
2696
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2697
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2698
            inferredGenus.addSource(originalSource);
2699

    
2700
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2701
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2702
            inferredSynName.addSource(originalSource);
2703
            originalSource = null;
2704

    
2705
        }else{
2706
            logger.error("There is an idInSource missing: " + idInSourceSyn + " of Synonym or " + idInSourceTaxon + " of Taxon");
2707
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2708
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2709
            inferredGenus.addSource(originalSource);
2710

    
2711
            originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2712
                    idInSourceSyn + "; " + idInSourceTaxon, INFERRED_GENUS_NAMESPACE, sourceReference, null);
2713
            inferredSynName.addSource(originalSource);
2714
            originalSource = null;
2715
        }
2716

    
2717
        taxon.addSynonym(inferredGenus, SynonymRelationshipType.INFERRED_GENUS_OF());
2718

    
2719
        return inferredGenus;
2720
    }
2721

    
2722
    private Synonym createInferredEpithets(Taxon taxon,
2723
            HashMap<UUID, ZoologicalName> zooHashMap, ZoologicalName taxonName,
2724
            String epithetOfTaxon, String infragenericEpithetOfTaxon,
2725
            String infraspecificEpithetOfTaxon, List<String> taxonNames,
2726
            TaxonNameBase parentName, TaxonBase syn) {
2727

    
2728
        Synonym inferredEpithet;
2729
        TaxonNameBase<?,?> synName;
2730
        ZoologicalName inferredSynName;
2731
        HibernateProxyHelper.deproxy(syn);
2732

    
2733
        // Determine the idInSource
2734
        String idInSourceSyn = getIdInSource(syn);
2735
        String idInSourceTaxon =  getIdInSource(taxon);
2736
        // Determine the sourceReference
2737
        Reference sourceReference = syn.getSec();
2738

    
2739
        if (sourceReference == null){
2740
             logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec());
2741
             sourceReference = taxon.getSec();
2742
        }
2743

    
2744
        synName = syn.getName();
2745
        ZoologicalName zooSynName = getZoologicalName(synName.getUuid(), zooHashMap);
2746
        String synGenusName = zooSynName.getGenusOrUninomial();
2747
        String synInfraGenericEpithet = null;
2748
        String synSpecificEpithet = null;
2749

    
2750
        if (zooSynName.getInfraGenericEpithet() != null){
2751
            synInfraGenericEpithet = zooSynName.getInfraGenericEpithet();
2752
        }
2753

    
2754
        if (zooSynName.isInfraSpecific()){
2755
            synSpecificEpithet = zooSynName.getSpecificEpithet();
2756
        }
2757

    
2758
                     /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
2759
            synonymsGenus.put(synGenusName, idInSource);
2760
        }*/
2761

    
2762
        inferredSynName = ZoologicalName.NewInstance(taxon.getName().getRank());
2763

    
2764
        // DEBUG TODO: for subgenus or subspecies the infrageneric or infraspecific epithet should be used!!!
2765
        if (epithetOfTaxon == null && infragenericEpithetOfTaxon == null && infraspecificEpithetOfTaxon == null) {
2766
            logger.error("This specificEpithet is NULL" + taxon.getTitleCache());
2767
        }
2768
        inferredSynName.setGenusOrUninomial(synGenusName);
2769

    
2770
        if (parentName.isInfraGeneric()){
2771
            inferredSynName.setInfraGenericEpithet(synInfraGenericEpithet);
2772
        }
2773
        if (taxonName.isSpecies()){
2774
            inferredSynName.setSpecificEpithet(epithetOfTaxon);
2775
        }else if (taxonName.isInfraSpecific()){
2776
            inferredSynName.setSpecificEpithet(synSpecificEpithet);
2777
            inferredSynName.setInfraSpecificEpithet(infraspecificEpithetOfTaxon);
2778
        }
2779

    
2780
        inferredEpithet = Synonym.NewInstance(inferredSynName, null);
2781

    
2782
        // Set the sourceReference
2783
        inferredEpithet.setSec(sourceReference);
2784

    
2785
        /* Add the original source
2786
        if (idInSource != null) {
2787
            IdentifiableSource originalSource = IdentifiableSource.NewInstance(idInSource, "InferredEpithetOf", syn.getSec(), null);
2788

    
2789
            // Add the citation
2790
            Reference citation = getCitation(syn);
2791
            if (citation != null) {
2792
                originalSource.setCitation(citation);
2793
                inferredEpithet.addSource(originalSource);
2794
            }
2795
        }*/
2796
        String taxonId = idInSourceTaxon+ "; " + idInSourceSyn;
2797

    
2798

    
2799
        IdentifiableSource originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2800
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2801

    
2802
        inferredEpithet.addSource(originalSource);
2803

    
2804
        originalSource = IdentifiableSource.NewInstance(OriginalSourceType.Transformation,
2805
                taxonId, INFERRED_EPITHET_NAMESPACE, sourceReference, null);
2806

    
2807
        inferredSynName.addSource(originalSource);
2808

    
2809

    
2810

    
2811
        taxon.addSynonym(inferredEpithet, SynonymRelationshipType.INFERRED_EPITHET_OF());
2812

    
2813
        return inferredEpithet;
2814
    }
2815

    
2816
    /**
2817
     * Returns an existing ZoologicalName or extends an internal hashmap if it does not exist.
2818
     * Very likely only useful for createInferredSynonyms().
2819
     * @param uuid
2820
     * @param zooHashMap
2821
     * @return
2822
     */
2823
    private ZoologicalName getZoologicalName(UUID uuid, HashMap <UUID, ZoologicalName> zooHashMap) {
2824
        ZoologicalName taxonName =nameDao.findZoologicalNameByUUID(uuid);
2825
        if (taxonName == null) {
2826
            taxonName = zooHashMap.get(uuid);
2827
        }
2828
        return taxonName;
2829
    }
2830

    
2831
    /**
2832
     * Returns the idInSource for a given Synonym.
2833
     * @param syn
2834
     */
2835
    private String getIdInSource(TaxonBase taxonBase) {
2836
        String idInSource = null;
2837
        Set<IdentifiableSource> sources = taxonBase.getSources();
2838
        if (sources.size() == 1) {
2839
            IdentifiableSource source = sources.iterator().next();
2840
            if (source != null) {
2841
                idInSource  = source.getIdInSource();
2842
            }
2843
        } else if (sources.size() > 1) {
2844
            int count = 1;
2845
            idInSource = "";
2846
            for (IdentifiableSource source : sources) {
2847
                idInSource += source.getIdInSource();
2848
                if (count < sources.size()) {
2849
                    idInSource += "; ";
2850
                }
2851
                count++;
2852
            }
2853
        } else if (sources.size() == 0){
2854
            logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
2855
        }
2856

    
2857

    
2858
        return idInSource;
2859
    }
2860

    
2861

    
2862
    /**
2863
     * Returns the citation for a given Synonym.
2864
     * @param syn
2865
     */
2866
    private Reference getCitation(Synonym syn) {
2867
        Reference citation = null;
2868
        Set<IdentifiableSource> sources = syn.getSources();
2869
        if (sources.size() == 1) {
2870
            IdentifiableSource source = sources.iterator().next();
2871
            if (source != null) {
2872
                citation = source.getCitation();
2873
            }
2874
        } else if (sources.size() > 1) {
2875
            logger.warn("This Synonym has more than one source: " + syn.getUuid() + " (" + syn.getTitleCache() +")");
2876
        }
2877

    
2878
        return citation;
2879
    }
2880

    
2881
    @Override
2882
    public List<Synonym>  createAllInferredSynonyms(Taxon taxon, Classification tree, boolean doWithMisappliedNames){
2883
        List <Synonym> inferredSynonyms = new ArrayList<Synonym>();
2884

    
2885
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_EPITHET_OF(), doWithMisappliedNames));
2886
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.INFERRED_GENUS_OF(), doWithMisappliedNames));
2887
        inferredSynonyms.addAll(createInferredSynonyms(taxon, tree, SynonymRelationshipType.POTENTIAL_COMBINATION_OF(), doWithMisappliedNames));
2888

    
2889
        return inferredSynonyms;
2890
    }
2891

    
2892
    @Override
2893
    public List<Classification> listClassifications(TaxonBase taxonBase, Integer limit, Integer start, List<String> propertyPaths) {
2894

    
2895
        // TODO quickly implemented, create according dao !!!!
2896
        Set<TaxonNode> nodes = new HashSet<TaxonNode>();
2897
        Set<Classification> classifications = new HashSet<Classification>();
2898
        List<Classification> list = new ArrayList<Classification>();
2899

    
2900
        if (taxonBase == null) {
2901
            return list;
2902
        }
2903

    
2904
        taxonBase = load(taxonBase.getUuid());
2905

    
2906
        if (taxonBase instanceof Taxon) {
2907
            nodes.addAll(((Taxon)taxonBase).getTaxonNodes());
2908
        } else {
2909
            for (Taxon taxon : ((Synonym)taxonBase).getAcceptedTaxa() ) {
2910
                nodes.addAll(taxon.getTaxonNodes());
2911
            }
2912
        }
2913
        for (TaxonNode node : nodes) {
2914
            classifications.add(node.getClassification());
2915
        }
2916
        list.addAll(classifications);
2917
        return list;
2918
    }
2919

    
2920
    @Override
2921
    @Transactional(readOnly = false)
2922
    public UpdateResult changeRelatedTaxonToSynonym(UUID fromTaxonUuid,
2923
            UUID toTaxonUuid,
2924
            TaxonRelationshipType oldRelationshipType,
2925
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
2926
        UpdateResult result = new UpdateResult();
2927
        Taxon fromTaxon = (Taxon) dao.load(fromTaxonUuid);
2928
        Taxon toTaxon = (Taxon) dao.load(toTaxonUuid);
2929
        Synonym synonym = changeRelatedTaxonToSynonym(fromTaxon, toTaxon, oldRelationshipType, synonymRelationshipType);
2930
        result.setCdmEntity(synonym);
2931
        result.addUpdatedObject(fromTaxon);
2932
        result.addUpdatedObject(toTaxon);
2933
        result.addUpdatedObject(synonym);
2934

    
2935
        return result;
2936
    }
2937

    
2938
    @Override
2939
    @Transactional(readOnly = false)
2940
    public Synonym changeRelatedTaxonToSynonym(Taxon fromTaxon, Taxon toTaxon, TaxonRelationshipType oldRelationshipType,
2941
            SynonymRelationshipType synonymRelationshipType) throws DataChangeNoRollbackException {
2942
        // Create new synonym using concept name
2943
                TaxonNameBase<?, ?> synonymName = fromTaxon.getName();
2944
                Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec());
2945

    
2946
                // Remove concept relation from taxon
2947
                toTaxon.removeTaxon(fromTaxon, oldRelationshipType);
2948

    
2949

    
2950

    
2951

    
2952
                // Create a new synonym for the taxon
2953
                SynonymRelationship synonymRelationship;
2954
                if (synonymRelationshipType != null
2955
                        && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){
2956
                    synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null);
2957
                } else{
2958
                    synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName);
2959
                }
2960

    
2961
                this.saveOrUpdate(toTaxon);
2962
                //TODO: configurator and classification
2963
                TaxonDeletionConfigurator config = new TaxonDeletionConfigurator();
2964
                config.setDeleteNameIfPossible(false);
2965
                this.deleteTaxon(fromTaxon.getUuid(), config, null);
2966
                return synonymRelationship.getSynonym();
2967

    
2968
    }
2969

    
2970
    @Override
2971
    public DeleteResult isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){
2972
        DeleteResult result = new DeleteResult();
2973
        Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
2974
        if (taxonBase instanceof Taxon){
2975
            TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
2976
            result = isDeletableForTaxon(references, taxonConfig);
2977
        }else{
2978
            SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
2979
            result = isDeletableForSynonym(references, synonymConfig);
2980
        }
2981
        return result;
2982
    }
2983

    
2984
    private DeleteResult isDeletableForSynonym(Set<CdmBase> references, SynonymDeletionConfigurator config){
2985
        String message;
2986
        DeleteResult result = new DeleteResult();
2987
        for (CdmBase ref: references){
2988
            if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase )){
2989
                message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId();
2990
                result.addException(new ReferencedObjectUndeletableException(message));
2991
                result.addRelatedObject(ref);
2992
                result.setAbort();
2993
            }
2994
        }
2995

    
2996
        return result;
2997
    }
2998

    
2999
    private DeleteResult isDeletableForTaxon(Set<CdmBase> references, TaxonDeletionConfigurator config){
3000
        String message = null;
3001
        DeleteResult result = new DeleteResult();
3002
        for (CdmBase ref: references){
3003
            if (!(ref instanceof TaxonNameBase)){
3004
            	message = null;
3005
                if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){
3006
                    message = "The taxon can't be deleted as long as it has synonyms.";
3007

    
3008
                }
3009
                if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){
3010
                    message = "The taxon can't be deleted as long as it has factual data.";
3011

    
3012
                }
3013

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

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

    
3022
                    } else{
3023
                        message = "The taxon can't be deleted as long as it belongs to a taxon node.";
3024

    
3025
                    }
3026
                }
3027
                if (ref instanceof PolytomousKeyNode){
3028
                    message = "The taxon can't be deleted as long as it is referenced by a polytomous key node.";
3029

    
3030
                }
3031

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

    
3035

    
3036
                }
3037

    
3038

    
3039
               /* //PolytomousKeyNode
3040
                if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
3041
                    String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
3042
                    return message;
3043
                }*/
3044

    
3045
                //TaxonInteraction
3046
                if (ref.isInstanceOf(TaxonInteraction.class)){
3047
                    message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
3048

    
3049
                }
3050

    
3051
              //TaxonInteraction
3052
                if (ref.isInstanceOf(DeterminationEvent.class)){
3053
                    message = "Taxon can't be deleted as it is used in a determination event";
3054

    
3055
                }
3056

    
3057
            }
3058
            if (message != null){
3059
	            result.addException(new ReferencedObjectUndeletableException(message));
3060
	            result.addRelatedObject(ref);
3061
	            result.setAbort();
3062
            }
3063
        }
3064

    
3065
        return result;
3066
    }
3067

    
3068
    @Override
3069
    public IncludedTaxaDTO listIncludedTaxa(UUID taxonUuid, IncludedTaxonConfiguration config) {
3070
        IncludedTaxaDTO result = new IncludedTaxaDTO(taxonUuid);
3071

    
3072
        //preliminary implementation
3073

    
3074
        Set<Taxon> taxa = new HashSet<Taxon>();
3075
        TaxonBase taxonBase = find(taxonUuid);
3076
        if (taxonBase == null){
3077
            return new IncludedTaxaDTO();
3078
        }else if (taxonBase.isInstanceOf(Taxon.class)){
3079
            Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
3080
            taxa.add(taxon);
3081
        }else if (taxonBase.isInstanceOf(Synonym.class)){
3082
            //TODO partial synonyms ??
3083
            //TODO synonyms in general
3084
            Synonym syn = CdmBase.deproxy(taxonBase, Synonym.class);
3085
            taxa.addAll(syn.getAcceptedTaxa());
3086
        }else{
3087
            throw new IllegalArgumentException("Unhandled class " + taxonBase.getClass().getSimpleName());
3088
        }
3089

    
3090
        Set<Taxon> related = makeRelatedIncluded(taxa, result, config);
3091
        int i = 0;
3092
        while((! related.isEmpty()) && i++ < 100){  //to avoid
3093
             related = makeRelatedIncluded(related, result, config);
3094
        }
3095

    
3096
        return result;
3097
    }
3098

    
3099
    /**
3100
     * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
3101
     * data structure.
3102
     * @return the set of conceptually related taxa for further use
3103
     */
3104
    /**
3105
     * @param uncheckedTaxa
3106
     * @param existingTaxa
3107
     * @param config
3108
     * @return
3109
     */
3110
    private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3111

    
3112
        //children
3113
        Set<TaxonNode> taxonNodes = new HashSet<TaxonNode>();
3114
        for (Taxon taxon: uncheckedTaxa){
3115
            taxonNodes.addAll(taxon.getTaxonNodes());
3116
        }
3117

    
3118
        Set<Taxon> children = new HashSet<Taxon>();
3119
        if (! config.onlyCongruent){
3120
            for (TaxonNode node: taxonNodes){
3121
                List<TaxonNode> childNodes = nodeService.loadChildNodesOfTaxonNode(node, null, true, null);
3122
                for (TaxonNode child : childNodes){
3123
                    children.add(child.getTaxon());
3124
                }
3125
            }
3126
            children.remove(null);  // just to be on the save side
3127
        }
3128

    
3129
        Iterator<Taxon> it = children.iterator();
3130
        while(it.hasNext()){
3131
            UUID uuid = it.next().getUuid();
3132
            if (existingTaxa.contains(uuid)){
3133
                it.remove();
3134
            }else{
3135
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3136
            }
3137
        }
3138

    
3139
        //concept relations
3140
        Set<Taxon> uncheckedAndChildren = new HashSet<Taxon>(uncheckedTaxa);
3141
        uncheckedAndChildren.addAll(children);
3142

    
3143
        Set<Taxon> relatedTaxa = makeConceptIncludedTaxa(uncheckedAndChildren, existingTaxa, config);
3144

    
3145

    
3146
        Set<Taxon> result = new HashSet<Taxon>(relatedTaxa);
3147
        return result;
3148
    }
3149

    
3150
    /**
3151
     * Computes all conceptually congruent or included taxa and adds them to the existingTaxa data structure.
3152
     * @return the set of these computed taxa
3153
     */
3154
    private Set<Taxon> makeConceptIncludedTaxa(Set<Taxon> unchecked, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
3155
        Set<Taxon> result = new HashSet<Taxon>();
3156

    
3157
        for (Taxon taxon : unchecked){
3158
            Set<TaxonRelationship> fromRelations = taxon.getRelationsFromThisTaxon();
3159
            Set<TaxonRelationship> toRelations = taxon.getRelationsToThisTaxon();
3160

    
3161
            for (TaxonRelationship fromRel : fromRelations){
3162
                if (config.includeDoubtful == false && fromRel.isDoubtful()){
3163
                    continue;
3164
                }
3165
                if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
3166
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
3167
                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
3168
                        ){
3169
                    result.add(fromRel.getToTaxon());
3170
                }
3171
            }
3172

    
3173
            for (TaxonRelationship toRel : toRelations){
3174
                if (config.includeDoubtful == false && toRel.isDoubtful()){
3175
                    continue;
3176
                }
3177
                if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
3178
                    result.add(toRel.getFromTaxon());
3179
                }
3180
            }
3181
        }
3182

    
3183
        Iterator<Taxon> it = result.iterator();
3184
        while(it.hasNext()){
3185
            UUID uuid = it.next().getUuid();
3186
            if (existingTaxa.contains(uuid)){
3187
                it.remove();
3188
            }else{
3189
                existingTaxa.addIncludedTaxon(uuid, new ArrayList<UUID>(), false);
3190
            }
3191
        }
3192
        return result;
3193
    }
3194

    
3195
    @Override
3196
    public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
3197
        List<TaxonBase> taxonList = dao.getTaxaByName(true, false, false, config.getTaxonNameTitle(), null, MatchMode.EXACT, null, 0, 0, config.getPropertyPath());
3198
        return taxonList;
3199
    }
3200

    
3201
	@Override
3202
	@Transactional(readOnly = true)
3203
	public <S extends TaxonBase> Pager<FindByIdentifierDTO<S>> findByIdentifier(
3204
			Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
3205
			MatchMode matchmode, boolean includeEntity, Integer pageSize,
3206
			Integer pageNumber,	List<String> propertyPaths) {
3207
		if (subtreeFilter == null){
3208
			return findByIdentifier(clazz, identifier, identifierType, matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3209
		}
3210

    
3211
		Integer numberOfResults = dao.countByIdentifier(clazz, identifier, identifierType, subtreeFilter, matchmode);
3212
        List<Object[]> daoResults = new ArrayList<Object[]>();
3213
        if(numberOfResults > 0) { // no point checking again
3214
        	daoResults = dao.findByIdentifier(clazz, identifier, identifierType, subtreeFilter,
3215
    				matchmode, includeEntity, pageSize, pageNumber, propertyPaths);
3216
        }
3217

    
3218
        List<FindByIdentifierDTO<S>> result = new ArrayList<FindByIdentifierDTO<S>>();
3219
        for (Object[] daoObj : daoResults){
3220
        	if (includeEntity){
3221
        		result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (S)daoObj[2]));
3222
        	}else{
3223
        		result.add(new FindByIdentifierDTO<S>((DefinedTerm)daoObj[0], (String)daoObj[1], (UUID)daoObj[2], (String)daoObj[3]));
3224
        	}
3225
        }
3226
		return new DefaultPagerImpl<FindByIdentifierDTO<S>>(pageNumber, numberOfResults, pageSize, result);
3227
	}
3228

    
3229
	@Override
3230
    @Transactional(readOnly = true)
3231
    public <S extends TaxonBase> Pager<FindByMarkerDTO<S>> findByMarker(
3232
            Class<S> clazz, MarkerType markerType, Boolean markerValue,
3233
            TaxonNode subtreeFilter, boolean includeEntity, Integer pageSize,
3234
            Integer pageNumber, List<String> propertyPaths) {
3235
        if (subtreeFilter == null){
3236
            return super.findByMarker (clazz, markerType, markerValue, includeEntity, pageSize, pageNumber, propertyPaths);
3237
        }
3238

    
3239
        Long numberOfResults = dao.countByMarker(clazz, markerType, markerValue, subtreeFilter);
3240
        List<Object[]> daoResults = new ArrayList<Object[]>();
3241
        if(numberOfResults > 0) { // no point checking again
3242
            daoResults = dao.findByMarker(clazz, markerType, markerValue, subtreeFilter,
3243
                    includeEntity, pageSize, pageNumber, propertyPaths);
3244
        }
3245

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

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

    
3262
	    UpdateResult result = new UpdateResult();
3263
		Taxon newTaxon = (Taxon) dao.load(newTaxonUUID);
3264
		result = moveSynonymToAnotherTaxon(oldSynonymRelation, newTaxon, moveHomotypicGroup, newSynonymRelationshipType, reference, referenceDetail, keepReference);
3265

    
3266
		return result;
3267
	}
3268

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

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

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

    
3296
          }
3297

    
3298

    
3299
		return result;
3300
	}
3301

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

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

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

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

    
3325
	@Override
3326
	public UUID saveOrUpdate(TaxonBase taxonbase){
3327
	    if (taxonbase.getName()!= null && taxonbase.getName().getId() > 0){
3328
	        TaxonNameBase name = taxonbase.getName();
3329
	        name = nameService.load(name.getUuid());
3330
	        taxonbase.setName(name);
3331
	    }
3332
	    return super.saveOrUpdate(taxonbase);
3333
	}
3334

    
3335

    
3336

    
3337
}
(90-90/98)