Project

General

Profile

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

    
11
import java.io.IOException;
12
import java.util.ArrayList;
13
import java.util.Arrays;
14
import java.util.Collection;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Optional;
20
import java.util.Set;
21
import java.util.UUID;
22
import java.util.stream.Collectors;
23

    
24
import org.apache.logging.log4j.LogManager;
25
import org.apache.logging.log4j.Logger;
26
import org.apache.lucene.index.Term;
27
import org.apache.lucene.sandbox.queries.FuzzyLikeThisQuery;
28
import org.apache.lucene.search.BooleanClause.Occur;
29
import org.apache.lucene.search.BooleanQuery;
30
import org.apache.lucene.search.BooleanQuery.Builder;
31
import org.apache.lucene.search.TopDocs;
32
import org.apache.lucene.search.WildcardQuery;
33
import org.hibernate.criterion.Criterion;
34
import org.springframework.beans.factory.annotation.Autowired;
35
import org.springframework.stereotype.Service;
36
import org.springframework.transaction.annotation.Transactional;
37

    
38
import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
39
import eu.etaxonomy.cdm.api.service.config.NameDeletionConfigurator;
40
import eu.etaxonomy.cdm.api.service.dto.TypeDesignationStatusFilter;
41
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
42
import eu.etaxonomy.cdm.api.service.pager.Pager;
43
import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
44
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
45
import eu.etaxonomy.cdm.api.service.search.DocumentSearchResult;
46
import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
47
import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
48
import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
49
import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
50
import eu.etaxonomy.cdm.api.service.search.QueryFactory;
51
import eu.etaxonomy.cdm.api.service.search.SearchResult;
52
import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
53
import eu.etaxonomy.cdm.api.util.TaxonNamePartsFilter;
54
import eu.etaxonomy.cdm.common.URI;
55
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
56
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
57
import eu.etaxonomy.cdm.model.CdmBaseType;
58
import eu.etaxonomy.cdm.model.agent.Person;
59
import eu.etaxonomy.cdm.model.agent.Team;
60
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
61
import eu.etaxonomy.cdm.model.common.CdmBase;
62
import eu.etaxonomy.cdm.model.common.Language;
63
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
64
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
65
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
66
import eu.etaxonomy.cdm.model.name.HybridRelationship;
67
import eu.etaxonomy.cdm.model.name.HybridRelationshipType;
68
import eu.etaxonomy.cdm.model.name.INonViralName;
69
import eu.etaxonomy.cdm.model.name.NameRelationship;
70
import eu.etaxonomy.cdm.model.name.NameRelationshipType;
71
import eu.etaxonomy.cdm.model.name.NameTypeDesignation;
72
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
73
import eu.etaxonomy.cdm.model.name.NomenclaturalSource;
74
import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
75
import eu.etaxonomy.cdm.model.name.Rank;
76
import eu.etaxonomy.cdm.model.name.Registration;
77
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
78
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
79
import eu.etaxonomy.cdm.model.name.TaxonName;
80
import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
81
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
82
import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
83
import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
84
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
85
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
86
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
87
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
88
import eu.etaxonomy.cdm.model.reference.Reference;
89
import eu.etaxonomy.cdm.model.taxon.Taxon;
90
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
91
import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
92
import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
93
import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
94
import eu.etaxonomy.cdm.persistence.dao.name.IHomotypicalGroupDao;
95
import eu.etaxonomy.cdm.persistence.dao.name.INomenclaturalStatusDao;
96
import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
97
import eu.etaxonomy.cdm.persistence.dao.name.ITypeDesignationDao;
98
import eu.etaxonomy.cdm.persistence.dao.reference.IOriginalSourceDao;
99
import eu.etaxonomy.cdm.persistence.dto.TaxonNameParts;
100
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
101
import eu.etaxonomy.cdm.persistence.query.MatchMode;
102
import eu.etaxonomy.cdm.persistence.query.OrderHint;
103
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
104
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
105
import eu.etaxonomy.cdm.strategy.match.IMatchStrategy;
106
import eu.etaxonomy.cdm.strategy.match.IMatchable;
107
import eu.etaxonomy.cdm.strategy.match.IParsedMatchStrategy;
108
import eu.etaxonomy.cdm.strategy.match.MatchException;
109
import eu.etaxonomy.cdm.strategy.match.MatchStrategyFactory;
110
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
111

    
112
@Service
113
@Transactional(readOnly = true)
114
public class NameServiceImpl
115
          extends IdentifiableServiceBase<TaxonName,ITaxonNameDao>
116
          implements INameService {
117

    
118
    static private final Logger logger = LogManager.getLogger(NameServiceImpl.class);
119

    
120
    @Autowired
121
    private IOccurrenceService occurrenceService;
122
    @Autowired
123
    private ICollectionService collectionService;
124
    @Autowired
125
    private ITaxonService taxonService;
126
    @Autowired
127
    private ICommonService commonService;
128
    @Autowired
129
    private INomenclaturalStatusDao nomStatusDao;
130
    @Autowired
131
    private ITypeDesignationDao typeDesignationDao;
132
    @Autowired
133
    private IHomotypicalGroupDao homotypicalGroupDao;
134
    @Autowired
135
    private ICdmGenericDao genericDao;
136
    @Autowired
137
    private ILuceneIndexToolProvider luceneIndexToolProvider;
138
    @Autowired
139
    private IOriginalSourceDao sourcedDao;
140

    
141
    @Autowired
142
    // @Qualifier("defaultBeanInitializer")
143
    protected IBeanInitializer defaultBeanInitializer;
144

    
145
//***************************** CONSTRUCTOR **********************************/
146

    
147
    /**
148
     * Constructor
149
     */
150
    public NameServiceImpl(){}
151

    
152
//********************* METHODS ***********************************************//
153

    
154
    @Override
155
    @Transactional(readOnly = false)
156
    public DeleteResult delete(UUID nameUUID){
157
        NameDeletionConfigurator config = new NameDeletionConfigurator();
158
        DeleteResult result = delete(nameUUID, config);
159
        return result;
160
    }
161

    
162
    @Override
163
    public DeleteResult delete(TaxonName name){
164
        return delete(name.getUuid());
165
    }
166

    
167
    @Override
168
    @Transactional(readOnly = false)
169
    public DeleteResult delete(TaxonName name, NameDeletionConfigurator config) {
170
        DeleteResult result = new DeleteResult();
171

    
172
        if (name == null){
173
            result.setAbort();
174
            return result;
175
        }
176

    
177
        try{
178
            result = this.isDeletable(name, config, null);
179
        }catch(Exception e){
180
            result.addException(e);
181
            result.setError();
182
            return result;
183
        }
184
        if (result.isOk()){
185
        //remove references to this name
186
            removeNameRelationshipsByDeleteConfig(name, config);
187

    
188
           //remove name from homotypical group
189
            HomotypicalGroup homotypicalGroup = name.getHomotypicalGroup();
190
            if (homotypicalGroup != null){
191
                homotypicalGroup.removeTypifiedName(name, false);
192
            }
193

    
194
             //all type designation relationships are removed as they belong to the name
195
            deleteTypeDesignation(name, null);
196
            //if original spellings should be deleted, remove it from the nomenclatural source
197
            Set<TaxonName> namesToUpdate = new HashSet<>();
198
            for (Object o: result.getRelatedObjects()){
199
                if (o instanceof NomenclaturalSource && ((NomenclaturalSource)o).getNameUsedInSource() != null && ((NomenclaturalSource)o).getNameUsedInSource().equals(name)){
200
                    NomenclaturalSource nomSource = (NomenclaturalSource)o;
201
                    nomSource.setNameUsedInSource(null);
202
                    namesToUpdate.add(nomSource.getSourcedName());
203
                }
204
            }
205

    
206
            try{
207
                if (!namesToUpdate.isEmpty()){
208
                    Map<UUID, TaxonName> updatedNames = dao.saveOrUpdateAll(namesToUpdate);
209
                    Set<TaxonName> names = new HashSet<>(updatedNames.values());
210
                    result.addUpdatedObjects(names);
211
                }
212
                dao.delete(name);
213
                result.addDeletedObject(name);
214

    
215
            }catch(Exception e){
216
                result.addException(e);
217
                result.setError();
218
            }
219
            return result;
220
        }
221

    
222
        return result;
223
    }
224

    
225
    @Override
226
    @Transactional(readOnly = false)
227
    public DeleteResult delete(UUID nameUUID, NameDeletionConfigurator config) {
228

    
229
        TaxonName name = dao.load(nameUUID);
230
        return delete(name, config);
231
    }
232

    
233
    @Override
234
    @Transactional(readOnly = false)
235
    public UpdateResult cloneTypeDesignation(UUID nameUuid, SpecimenTypeDesignation baseDesignation,
236
            String accessionNumber, String barcode, String catalogNumber,
237
            UUID collectionUuid, SpecimenTypeDesignationStatus typeStatus, URI preferredStableUri){
238
        UpdateResult result = new UpdateResult();
239

    
240
        DerivedUnit baseSpecimen = HibernateProxyHelper.deproxy(occurrenceService.load(baseDesignation.getTypeSpecimen().getUuid(), Arrays.asList("collection")), DerivedUnit.class);
241
        DerivedUnit duplicate = DerivedUnit.NewInstance(baseSpecimen.getRecordBasis());
242
        DerivationEvent derivedFrom = baseSpecimen.getDerivedFrom();
243
        Collection<FieldUnit> fieldUnits = occurrenceService.findFieldUnits(baseSpecimen.getUuid(), null);
244
        if(fieldUnits.size()!=1){
245
            result.addException(new Exception("More than one or no field unit found for specimen"));
246
            result.setError();
247
            return result;
248
        }
249
        for (SpecimenOrObservationBase<?> original : derivedFrom.getOriginals()) {
250
            DerivationEvent.NewSimpleInstance(original, duplicate, derivedFrom.getType());
251
        }
252
        duplicate.setAccessionNumber(accessionNumber);
253
        duplicate.setBarcode(barcode);
254
        duplicate.setCatalogNumber(catalogNumber);
255
        duplicate.setCollection(collectionService.load(collectionUuid));
256
        SpecimenTypeDesignation typeDesignation = SpecimenTypeDesignation.NewInstance();
257
        typeDesignation.setTypeSpecimen(duplicate);
258
        typeDesignation.setTypeStatus(typeStatus);
259
        typeDesignation.getTypeSpecimen().setPreferredStableUri(preferredStableUri);
260

    
261
        TaxonName name = load(nameUuid);
262
        name.getTypeDesignations().add(typeDesignation);
263

    
264
        result.setCdmEntity(typeDesignation);
265
        result.addUpdatedObject(name);
266
        return result;
267
    }
268

    
269
    @Override
270
    @Transactional
271
    public DeleteResult deleteTypeDesignation(TaxonName name, TypeDesignationBase<?> typeDesignation){
272
    	if(typeDesignation != null && typeDesignation .isPersited()){
273
    		typeDesignation = HibernateProxyHelper.deproxy(typeDesignationDao.load(typeDesignation.getUuid()));
274
    	}
275

    
276
        DeleteResult result = new DeleteResult();
277
        if (name == null && typeDesignation == null){
278
            result.setError();
279
            return result;
280
        }else if (name != null && typeDesignation != null){
281
            removeSingleDesignation(name, typeDesignation);
282
        }else if (name != null){
283
            @SuppressWarnings("rawtypes")
284
            Set<TypeDesignationBase> designationSet = new HashSet<>(name.getTypeDesignations());
285
            for (TypeDesignationBase<?> desig : designationSet){
286
                desig = CdmBase.deproxy(desig);
287
                removeSingleDesignation(name, desig);
288
            }
289
        }else if (typeDesignation != null){
290
            Set<TaxonName> nameSet = new HashSet<>(typeDesignation.getTypifiedNames());
291
            for (TaxonName singleName : nameSet){
292
                singleName = CdmBase.deproxy(singleName);
293
                removeSingleDesignation(singleName, typeDesignation);
294
            }
295
        }
296
        result.addDeletedObject(typeDesignation);
297
        result.addUpdatedObject(name);
298
        return result;
299
    }
300

    
301

    
302
    @Override
303
    @Transactional(readOnly = false)
304
    public DeleteResult deleteTypeDesignation(UUID nameUuid, UUID typeDesignationUuid){
305
        TaxonName nameBase = load(nameUuid);
306
        TypeDesignationBase<?> typeDesignation = HibernateProxyHelper.deproxy(typeDesignationDao.load(typeDesignationUuid));
307
        return deleteTypeDesignation(nameBase, typeDesignation);
308
    }
309

    
310
    @Transactional
311
    private void removeSingleDesignation(TaxonName name, TypeDesignationBase<?> typeDesignation) {
312

    
313
        name.removeTypeDesignation(typeDesignation);
314
        if (typeDesignation.getTypifiedNames().isEmpty()){
315
            typeDesignation.removeType();
316
            if (!typeDesignation.getRegistrations().isEmpty()){
317
                for(Object reg: typeDesignation.getRegistrations()){
318
                    if (reg instanceof Registration){
319
                        ((Registration)reg).removeTypeDesignation(typeDesignation);
320
                    }
321
                }
322
            }
323

    
324
            typeDesignationDao.delete(typeDesignation);
325

    
326
        }
327
    }
328

    
329

    
330

    
331
    /**
332
     * @param name
333
     * @param config
334
     */
335
    private void removeNameRelationshipsByDeleteConfig(TaxonName name, NameDeletionConfigurator config) {
336
        try {
337
            if (config.isRemoveAllNameRelationships()){
338
                Set<NameRelationship> rels = getModifiableSet(name.getNameRelations());
339
                for (NameRelationship rel : rels){
340
                    name.removeNameRelationship(rel);
341
                }
342
            }else{
343
                //relations to this name
344
                Set<NameRelationship> rels = getModifiableSet(name.getRelationsToThisName());
345
                for (NameRelationship rel : rels){
346
                    if (config.isIgnoreHasBasionym() && NameRelationshipType.BASIONYM().equals(rel.getType() )){
347
                            name.removeNameRelationship(rel);
348
                    }else if (config.isIgnoreHasReplacedSynonym() && NameRelationshipType.REPLACED_SYNONYM().equals(rel.getType())){
349
                        name.removeNameRelationship(rel);
350
                    }
351
                }
352
                //relations from this name
353
                rels = getModifiableSet(name.getRelationsFromThisName());
354
                for (NameRelationship rel : rels){
355
                    if (config.isIgnoreIsBasionymFor() && NameRelationshipType.BASIONYM().equals(rel.getType())  ){
356
                        name.removeNameRelationship(rel);
357
                    }else if (config.isIgnoreIsReplacedSynonymFor() && NameRelationshipType.REPLACED_SYNONYM().equals(rel.getType())){
358
                        name.removeNameRelationship(rel);
359
                    }
360
                }
361
            }
362
        } catch (Exception e) {
363
            throw new RuntimeException(e);
364
        }
365
    }
366

    
367
    private Set<NameRelationship> getModifiableSet(Set<NameRelationship> relations) {
368
        Set<NameRelationship> rels = new HashSet<NameRelationship>();
369
        for (NameRelationship rel : relations){
370
            rels.add(rel);
371
        }
372
        return rels;
373
    }
374

    
375
//********************* METHODS ****************************************************************//
376

    
377
    /**
378
     * TODO candidate for harmonization
379
     * new name findByName
380
     */
381
    @Override
382
    @Deprecated
383
    public List<TaxonName> getNamesByNameCache(String nameCache){
384
        boolean includeAuthors = false;
385
        List<TaxonName> result = dao.findByName(includeAuthors, nameCache, MatchMode.EXACT, null, null, null, null);
386
        return result;
387
    }
388

    
389
    /**
390
     * TODO candidate for harmonization
391
     * new name saveHomotypicalGroups
392
     *
393
     * findByTitle
394
     */
395
    @Override
396
    @Deprecated
397
    public List<TaxonName> findNamesByTitleCache(String titleCache, MatchMode matchMode, List<String> propertyPaths){
398
        List<TaxonName> result = dao.findByTitle(titleCache, matchMode, null, null, null, propertyPaths);
399
        return result;
400
    }
401

    
402
    /**
403
     * TODO candidate for harmonization
404
     * new name saveHomotypicalGroups
405
     *
406
     * findByTitle
407
     */
408
    @Override
409
    @Deprecated
410
    public List<TaxonName> findNamesByNameCache(String nameCache, MatchMode matchMode, List<String> propertyPaths){
411
        List<TaxonName> result = dao.findByName(false, nameCache, matchMode, null, null, null , propertyPaths);
412
        return result;
413
    }
414

    
415
    @Override
416
    public Pager<TaxonNameParts> findTaxonNameParts(Optional<String> genusOrUninomial,
417
            Optional<String> infraGenericEpithet, Optional<String> specificEpithet,
418
            Optional<String> infraSpecificEpithet, Rank rank, Set<UUID> excludedNamesUuids,
419
            Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
420

    
421

    
422
        long count = dao.countTaxonNameParts(genusOrUninomial, infraGenericEpithet, specificEpithet, infraGenericEpithet, rank, excludedNamesUuids);
423

    
424
        List<TaxonNameParts> results;
425
        if(AbstractPagerImpl.hasResultsInRange(count, pageIndex, pageSize)){
426
            results = dao.findTaxonNameParts(genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
427
                    rank, excludedNamesUuids,
428
                    pageSize, pageIndex, orderHints);
429
        } else {
430
            results = new ArrayList<>();
431
        }
432

    
433
        return new DefaultPagerImpl<TaxonNameParts>(pageIndex, count, pageSize, results);
434
    }
435

    
436
    @Override
437
    public Pager<TaxonNameParts> findTaxonNameParts(TaxonNamePartsFilter filter, String namePartQueryString,
438
            Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
439

    
440
        return findTaxonNameParts(
441
                filter.uninomialQueryString(namePartQueryString),
442
                filter.infraGenericEpithet(namePartQueryString),
443
                filter.specificEpithet(namePartQueryString),
444
                filter.infraspecificEpithet(namePartQueryString),
445
                filter.getRank(),
446
                filter.getExludedNamesUuids(),
447
                pageSize, pageIndex, orderHints);
448
    }
449

    
450
    /**
451
     * TODO candidate for harmonization
452
     * new name saveHomotypicalGroups
453
     */
454
    @Override
455
    @Transactional(readOnly = false)
456
    public Map<UUID, HomotypicalGroup> saveAllHomotypicalGroups(Collection<HomotypicalGroup> homotypicalGroups){
457
        return homotypicalGroupDao.saveAll(homotypicalGroups);
458
    }
459

    
460
    /**
461
     * TODO candidate for harmonization
462
     * new name saveTypeDesignations
463
     */
464
    @Override
465
    @Transactional(readOnly = false)
466
    public Map<UUID, TypeDesignationBase<?>> saveTypeDesignationAll(Collection<TypeDesignationBase<?>> typeDesignationCollection){
467
        return typeDesignationDao.saveAll(typeDesignationCollection);
468
    }
469

    
470
    /**
471
     * TODO candidate for harmonization
472
     * new name getNomenclaturalStatus
473
     */
474
    @Override
475
    public List<NomenclaturalStatus> getAllNomenclaturalStatus(int limit, int start){
476
        return nomStatusDao.list(limit, start);
477
    }
478

    
479
    @Override
480
    public NomenclaturalStatus loadNomenclaturalStatus(UUID uuid,  List<String> propertyPaths){
481
        return nomStatusDao.load(uuid, propertyPaths);
482
    }
483

    
484
    /**
485
     * TODO candidate for harmonization
486
     * new name getTypeDesignations
487
     */
488
    @Override
489
    public List<TypeDesignationBase<?>> getAllTypeDesignations(int limit, int start){
490
        return typeDesignationDao.getAllTypeDesignations(limit, start);
491
    }
492

    
493
    @Override
494
    public TypeDesignationBase<?> loadTypeDesignation(int id, List<String> propertyPaths){
495
        return typeDesignationDao.load(id, propertyPaths);
496
    }
497

    
498
    @Override
499
    public TypeDesignationBase<?> loadTypeDesignation(UUID uuid, List<String> propertyPaths){
500
        return typeDesignationDao.load(uuid, propertyPaths);
501
    }
502

    
503
    @Override
504
    public List<TypeDesignationBase<?>> loadTypeDesignations(List<UUID> uuids, List<String> propertyPaths){
505
    	if(uuids == null) {
506
            return null;
507
        }
508

    
509
        List<TypeDesignationBase<?>> entities = new ArrayList<>();
510
        for(UUID uuid : uuids) {
511
            entities.add(uuid == null ? null : typeDesignationDao.load(uuid, propertyPaths));
512
        }
513
        return entities;
514
    }
515

    
516
    /**
517
     * FIXME Candidate for harmonization
518
     * homotypicalGroupService.list
519
     */
520
    @Override
521
    public List<HomotypicalGroup> getAllHomotypicalGroups(int limit, int start){
522
        return homotypicalGroupDao.list(limit, start);
523
    }
524

    
525

    
526
    @Override
527
    public List<NomenclaturalSource> listOriginalSpellings(Integer pageSize, Integer pageNumber,
528
            List<OrderHint> orderHints, List<String> propertyPaths) {
529

    
530
        Long numberOfResults = sourcedDao.countWithNameUsedInSource(NomenclaturalSource.class);
531
        List<NomenclaturalSource> results = new ArrayList<>();
532
        if(numberOfResults > 0) {
533
            results = sourcedDao.listWithNameUsedInSource(NomenclaturalSource.class, pageSize, pageNumber, orderHints, propertyPaths);
534
        }
535
        return results;
536
    }
537

    
538
    @Override
539
    public List<NameRelationship> listNameRelationships(Set<NameRelationshipType> types,
540
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
541

    
542
        Long numberOfResults = dao.countNameRelationships(types);
543
        List<NameRelationship> results = new ArrayList<>();
544
        if(numberOfResults > 0) {
545
            results = dao.getNameRelationships(types, pageSize, pageNumber, orderHints, propertyPaths);
546
        }
547
        return results;
548
    }
549

    
550
    @Override
551
    public List<HybridRelationship> listHybridRelationships(Set<HybridRelationshipType> types,
552
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
553

    
554
        Long numberOfResults = dao.countHybridRelationships(types);
555
        List<HybridRelationship> results = new ArrayList<>();
556
        if(numberOfResults > 0) {
557
            results = dao.getHybridRelationships(types, pageSize, pageNumber, orderHints, propertyPaths);
558
        }
559
        return results;
560
    }
561

    
562

    
563
    @Override
564
    @Autowired
565
    protected void setDao(ITaxonNameDao dao) {
566
        this.dao = dao;
567
    }
568

    
569
    @Override
570
    public Pager<HybridRelationship> getHybridNames(INonViralName name,	HybridRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
571
        Integer numberOfResults = dao.countHybridNames(name, type);
572

    
573
        List<HybridRelationship> results = new ArrayList<HybridRelationship>();
574
        if(AbstractPagerImpl.hasResultsInRange(numberOfResults.longValue(), pageNumber, pageSize)) { // no point checking again
575
            results = dao.getHybridNames(name, type, pageSize, pageNumber,orderHints,propertyPaths);
576
        }
577

    
578
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
579
    }
580

    
581
    @Override
582
    public List<NameRelationship> listNameRelationships(TaxonName name,	Direction direction, NameRelationshipType type, Integer pageSize,
583
            Integer pageNumber, List<OrderHint> orderHints,	List<String> propertyPaths) {
584

    
585
        Integer numberOfResults = dao.countNameRelationships(name, direction, type);
586

    
587
        List<NameRelationship> results = new ArrayList<NameRelationship>();
588
        if (AbstractPagerImpl.hasResultsInRange(numberOfResults.longValue(), pageNumber, pageSize)) { // no point checking again
589
            results = dao.getNameRelationships(name, direction, type, pageSize,	pageNumber, orderHints, propertyPaths);
590
        }
591
        return results;
592
    }
593

    
594

    
595
    protected LuceneSearch prepareFindByFuzzyNameSearch(Class<? extends CdmBase> clazz,
596
            INonViralName nvn,
597
            float accuracy,
598
            int maxNoOfResults,
599
            List<Language> languages,
600
            boolean highlightFragments) {
601

    
602
        String similarity = Float.toString(accuracy);
603
        String searchSuffix = "~" + similarity;
604

    
605
        Builder finalQueryBuilder = new Builder();
606
        finalQueryBuilder.setDisableCoord(false);
607
        Builder textQueryBuilder = new Builder();
608
        textQueryBuilder.setDisableCoord(false);
609

    
610
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, TaxonName.class);
611
        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonName.class);
612

    
613
//    	SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
614
//    	luceneSearch.setSortFields(sortFields);
615

    
616
        // ---- search criteria
617
        luceneSearch.setCdmTypRestriction(clazz);
618

    
619
        FuzzyLikeThisQuery fltq = new FuzzyLikeThisQuery(maxNoOfResults, luceneSearch.getAnalyzer());
620
        if(nvn.getGenusOrUninomial() != null && !nvn.getGenusOrUninomial().equals("")) {
621
            fltq.addTerms(nvn.getGenusOrUninomial().toLowerCase(), "genusOrUninomial", accuracy, 3);
622
        } else {
623
            //textQuery.add(new RegexQuery (new Term ("genusOrUninomial", "^[a-zA-Z]*")), Occur.MUST_NOT);
624
            textQueryBuilder.add(queryFactory.newTermQuery("genusOrUninomial", "_null_", false), Occur.MUST);
625
        }
626

    
627
        if(nvn.getInfraGenericEpithet() != null && !nvn.getInfraGenericEpithet().equals("")){
628
            fltq.addTerms(nvn.getInfraGenericEpithet().toLowerCase(), "infraGenericEpithet", accuracy, 3);
629
        } else {
630
            //textQuery.add(new RegexQuery (new Term ("infraGenericEpithet", "^[a-zA-Z]*")), Occur.MUST_NOT);
631
            textQueryBuilder.add(queryFactory.newTermQuery("infraGenericEpithet", "_null_", false), Occur.MUST);
632
        }
633

    
634
        if(nvn.getSpecificEpithet() != null && !nvn.getSpecificEpithet().equals("")){
635
            fltq.addTerms(nvn.getSpecificEpithet().toLowerCase(), "specificEpithet", accuracy, 3);
636
        } else {
637
            //textQuery.add(new RegexQuery (new Term ("specificEpithet", "^[a-zA-Z]*")), Occur.MUST_NOT);
638
            textQueryBuilder.add(queryFactory.newTermQuery("specificEpithet", "_null_", false), Occur.MUST);
639
        }
640

    
641
        if(nvn.getInfraSpecificEpithet() != null && !nvn.getInfraSpecificEpithet().equals("")){
642
            fltq.addTerms(nvn.getInfraSpecificEpithet().toLowerCase(), "infraSpecificEpithet", accuracy, 3);
643
        } else {
644
            //textQuery.add(new RegexQuery (new Term ("infraSpecificEpithet", "^[a-zA-Z]*")), Occur.MUST_NOT);
645
            textQueryBuilder.add(queryFactory.newTermQuery("infraSpecificEpithet", "_null_", false), Occur.MUST);
646
        }
647

    
648
        if(nvn.getAuthorshipCache() != null && !nvn.getAuthorshipCache().equals("")){
649
            fltq.addTerms(nvn.getAuthorshipCache().toLowerCase(), "authorshipCache", accuracy, 3);
650
        } else {
651
            //textQuery.add(new RegexQuery (new Term ("authorshipCache", "^[a-zA-Z]*")), Occur.MUST_NOT);
652
        }
653

    
654
        textQueryBuilder.add(fltq, Occur.MUST);
655

    
656
        BooleanQuery textQuery = textQueryBuilder.build();
657
        finalQueryBuilder.add(textQuery, Occur.MUST);
658

    
659
        luceneSearch.setQuery(finalQueryBuilder.build());
660

    
661
        if(highlightFragments){
662
            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
663
        }
664
        return luceneSearch;
665
    }
666

    
667
    protected LuceneSearch prepareFindByFuzzyNameCacheSearch(Class<? extends CdmBase> clazz,
668
            String name,
669
            float accuracy,
670
            int maxNoOfResults,
671
            List<Language> languages,
672
            boolean highlightFragments) {
673

    
674
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, TaxonName.class);
675
        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonName.class);
676

    
677
//    	SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
678
//    	luceneSearch.setSortFields(sortFields);
679

    
680
        // ---- search criteria
681
        luceneSearch.setCdmTypRestriction(clazz);
682
        FuzzyLikeThisQuery fltq = new FuzzyLikeThisQuery(maxNoOfResults, luceneSearch.getAnalyzer());
683

    
684
        fltq.addTerms(name, "nameCache", accuracy, 3);
685

    
686
         BooleanQuery finalQuery = new BooleanQuery(false);
687

    
688
         finalQuery.add(fltq, Occur.MUST);
689

    
690
        luceneSearch.setQuery(finalQuery);
691

    
692
        if(highlightFragments){
693
            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
694
        }
695
        return luceneSearch;
696
    }
697

    
698
    protected LuceneSearch prepareFindByExactNameSearch(Class<? extends CdmBase> clazz,
699
            String name,
700
            boolean wildcard,
701
            List<Language> languages,
702
            boolean highlightFragments) {
703
        Builder textQueryBuilder = new Builder();
704

    
705
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, TaxonName.class);
706
        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonName.class);
707

    
708
//    	SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING,  false)};
709
//    	luceneSearch.setSortFields(sortFields);
710

    
711
        // ---- search criteria
712
        luceneSearch.setCdmTypRestriction(clazz);
713

    
714
        if(name != null && !name.equals("")) {
715
            if(wildcard) {
716
                textQueryBuilder.add(new WildcardQuery(new Term("nameCache", name + "*")), Occur.MUST);
717
            } else {
718
                textQueryBuilder.add(queryFactory.newTermQuery("nameCache", name, false), Occur.MUST);
719
            }
720
        }
721

    
722
        luceneSearch.setQuery(textQueryBuilder.build());
723

    
724
        if(highlightFragments){
725
            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
726
        }
727
        return luceneSearch;
728
    }
729

    
730
    @Override
731
    public List<SearchResult<TaxonName>> findByNameFuzzySearch(
732
            String name,
733
            float accuracy,
734
            List<Language> languages,
735
            boolean highlightFragments,
736
            List<String> propertyPaths,
737
            int maxNoOfResults) throws IOException, LuceneParseException {
738

    
739
        logger.info("Name to fuzzy search for : " + name);
740
        // parse the input name
741
        NonViralNameParserImpl parser = new NonViralNameParserImpl();
742
        INonViralName nvn = parser.parseFullName(name);
743
        if(name != null && !name.equals("") && nvn == null) {
744
            throw new LuceneParseException("Could not parse name " + name);
745
        }
746
        LuceneSearch luceneSearch = prepareFindByFuzzyNameSearch(null, nvn, accuracy, maxNoOfResults, languages, highlightFragments);
747

    
748
        // --- execute search
749
        TopDocs topDocs = luceneSearch.executeSearch(maxNoOfResults);
750

    
751

    
752
        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
753
        idFieldMap.put(CdmBaseType.TAXON_NAME, "id");
754

    
755
        // --- initialize taxa, highlight matches ....
756
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
757

    
758
        List<SearchResult<TaxonName>> searchResults = searchResultBuilder.createResultSet(
759
                topDocs, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
760

    
761
        return searchResults;
762

    
763
    }
764

    
765
    @Override
766
    public List<DocumentSearchResult> findByNameFuzzySearch(
767
            String name,
768
            float accuracy,
769
            List<Language> languages,
770
            boolean highlightFragments,
771
            int maxNoOfResults) throws IOException, LuceneParseException {
772

    
773
        logger.info("Name to fuzzy search for : " + name);
774
        // parse the input name
775
        NonViralNameParserImpl parser = new NonViralNameParserImpl();
776
        INonViralName nvn = parser.parseFullName(name);
777
        if(name != null && !name.equals("") && nvn == null) {
778
            throw new LuceneParseException("Could not parse name " + name);
779
        }
780
        LuceneSearch luceneSearch = prepareFindByFuzzyNameSearch(null, nvn, accuracy, maxNoOfResults, languages, highlightFragments);
781

    
782
        // --- execute search
783
        TopDocs topDocs = luceneSearch.executeSearch(maxNoOfResults);
784

    
785
        // --- initialize taxa, highlight matches ....
786
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
787

    
788
        List<DocumentSearchResult> searchResults = searchResultBuilder.createResultSet(topDocs, luceneSearch.getHighlightFields());
789

    
790
        return searchResults;
791
    }
792

    
793
    @Override
794
    public List<DocumentSearchResult> findByFuzzyNameCacheSearch(
795
            String name,
796
            float accuracy,
797
            List<Language> languages,
798
            boolean highlightFragments,
799
            int maxNoOfResults) throws IOException, LuceneParseException {
800

    
801
        logger.info("Name to fuzzy search for : " + name);
802

    
803
        LuceneSearch luceneSearch = prepareFindByFuzzyNameCacheSearch(null, name, accuracy, maxNoOfResults, languages, highlightFragments);
804

    
805
        // --- execute search
806
        TopDocs topDocs = luceneSearch.executeSearch(maxNoOfResults);
807

    
808
        // --- initialize taxa, highlight matches ....
809
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
810

    
811
        List<DocumentSearchResult> searchResults = searchResultBuilder.createResultSet(topDocs, luceneSearch.getHighlightFields());
812

    
813
        return searchResults;
814
    }
815

    
816
    @Override
817
    public List<DocumentSearchResult> findByNameExactSearch(
818
            String name,
819
            boolean wildcard,
820
            List<Language> languages,
821
            boolean highlightFragments,
822
            int maxNoOfResults) throws IOException, LuceneParseException {
823

    
824
        logger.info("Name to exact search for : " + name);
825

    
826
        LuceneSearch luceneSearch = prepareFindByExactNameSearch(null, name, wildcard, languages, highlightFragments);
827

    
828
        // --- execute search
829

    
830
        TopDocs topDocs = luceneSearch.executeSearch(maxNoOfResults);
831

    
832
        // --- initialize taxa, highlight matches ....
833
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
834

    
835
        List<DocumentSearchResult> searchResults = searchResultBuilder.createResultSet(topDocs, luceneSearch.getHighlightFields());
836

    
837
        return searchResults;
838
    }
839

    
840
    @Override
841
    public Pager<NameRelationship> pageNameRelationships(TaxonName name, Direction direction, NameRelationshipType type, Integer pageSize,
842
            Integer pageNumber, List<OrderHint> orderHints,	List<String> propertyPaths) {
843
        List<NameRelationship> results = listNameRelationships(name, direction, type, pageSize, pageNumber, orderHints, propertyPaths);
844
        return new DefaultPagerImpl<>(pageNumber, results.size(), pageSize, results);
845
    }
846

    
847
    @Override
848
    public List<NameRelationship> listFromNameRelationships(TaxonName name, NameRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
849
        return listNameRelationships(name, Direction.relatedFrom, type, pageSize, pageNumber, orderHints, propertyPaths);
850
    }
851

    
852
    @Override
853
    public Pager<NameRelationship> pageFromNameRelationships(TaxonName name, NameRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
854
        List<NameRelationship> results = listNameRelationships(name, Direction.relatedFrom, type, pageSize, pageNumber, orderHints, propertyPaths);
855
        return new DefaultPagerImpl<>(pageNumber, results.size(), pageSize, results);
856
    }
857

    
858
    @Override
859
    public List<NameRelationship> listToNameRelationships(TaxonName name, NameRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
860
        return listNameRelationships(name, Direction.relatedTo, type, pageSize, pageNumber, orderHints, propertyPaths);
861
    }
862

    
863
    @Override
864
    public Pager<NameRelationship> pageToNameRelationships(TaxonName name, NameRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
865
        List<NameRelationship> results = listNameRelationships(name, Direction.relatedTo, type, pageSize, pageNumber, orderHints, propertyPaths);
866
        return new DefaultPagerImpl<>(pageNumber, results.size(), pageSize, results);
867
    }
868

    
869
    @Override
870
    public Pager<TypeDesignationBase> getTypeDesignations(TaxonName name, SpecimenTypeDesignationStatus status,
871
            Integer pageSize, Integer pageNumber) {
872
        return getTypeDesignations(name, status, pageSize, pageNumber, null);
873
    }
874

    
875
    @Override
876
    public Pager<TypeDesignationBase> getTypeDesignations(TaxonName name, SpecimenTypeDesignationStatus status,
877
                Integer pageSize, Integer pageNumber, List<String> propertyPaths){
878
        long numberOfResults = dao.countTypeDesignations(name, status);
879

    
880
        List<TypeDesignationBase> results = new ArrayList<>();
881
        if(AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)) {
882
            results = dao.getTypeDesignations(name, null, status, pageSize, pageNumber, propertyPaths);
883
        }
884
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
885
    }
886

    
887
    @Override
888
    public List<TypeDesignationBase> getTypeDesignationsInHomotypicalGroup(UUID nameUuid, Integer pageSize,
889
            Integer pageNumber, List<String> propertyPaths){
890
        TaxonName name = load(nameUuid, Arrays.asList("nomenclaturalSource.citation.authorship"));
891
        Set<TypeDesignationBase<?>> typeDesignations = name.getHomotypicalGroup().getTypeDesignations();
892
        List<TypeDesignationBase> result = defaultBeanInitializer.initializeAll(new ArrayList(typeDesignations), propertyPaths);
893
        return result;
894
    }
895

    
896
    /**
897
     * FIXME Candidate for harmonization
898
     * rename search
899
     */
900
    @Override
901
    public Pager<TaxonName> searchNames(String uninomial,String infraGenericEpithet, String specificEpithet, String infraspecificEpithet, Rank rank, Integer pageSize,	Integer pageNumber, List<OrderHint> orderHints,
902
            List<String> propertyPaths) {
903
        long numberOfResults = dao.countNames(uninomial, infraGenericEpithet, specificEpithet, infraspecificEpithet, rank);
904

    
905
        List<TaxonName> results = new ArrayList<>();
906
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
907
            results = dao.searchNames(uninomial, infraGenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber, orderHints, propertyPaths);
908
        }
909

    
910
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
911
    }
912

    
913
    @Override
914
    public List<UuidAndTitleCache> getUuidAndTitleCacheOfNames(Integer limit, String pattern) {
915
        return dao.getUuidAndTitleCacheOfNames(limit, pattern);
916
    }
917

    
918
    @Override
919
    public Pager<TaxonName> findByName(Class<TaxonName> clazz, String queryString, MatchMode matchmode, List<Criterion> criteria,
920
            Integer pageSize,Integer pageNumber, List<OrderHint> orderHints,List<String> propertyPaths) {
921
         Long numberOfResults = dao.countByName(clazz, queryString, matchmode, criteria);
922

    
923
         List<TaxonName> results = new ArrayList<>();
924
         if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
925
                results = dao.findByName(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
926
         }
927

    
928
         return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
929
    }
930

    
931
    @Override
932
    public List<TaxonName> findByFullTitle(Class<TaxonName> clazz, String queryString, MatchMode matchmode, List<Criterion> criteria,
933
            Integer pageSize,Integer pageNumber, List<OrderHint> orderHints,List<String> propertyPaths) {
934
         Long numberOfResults = dao.countByFullTitle(clazz, queryString, matchmode, criteria);
935

    
936
         List<TaxonName> results = new ArrayList<>();
937
         if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
938
                results = dao.findByFullTitle(queryString, matchmode, pageSize, pageNumber, criteria, propertyPaths);
939
         }
940

    
941
         return results;
942
    }
943

    
944

    
945
    @Override
946
    public HomotypicalGroup findHomotypicalGroup(UUID uuid) {
947
        return homotypicalGroupDao.findByUuid(uuid);
948
    }
949

    
950
    @Override
951
    @Transactional(readOnly = false)
952
    public UpdateResult updateCaches(Class<? extends TaxonName> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonName> cacheStrategy, IProgressMonitor monitor) {
953
        if (clazz == null){
954
            clazz = TaxonName.class;
955
        }
956
        return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);
957
    }
958

    
959

    
960
    @Override
961
    public List<TaggedText> getTaggedName(UUID uuid) {
962
        TaxonName taxonName = dao.load(uuid);
963
        List<TaggedText> taggedName = taxonName.getTaggedName();
964
        return taggedName;
965
    }
966

    
967

    
968
    public DeleteResult isDeletable(TaxonName name, DeleteConfiguratorBase config, UUID taxonUuid){
969
        DeleteResult result = new DeleteResult();
970

    
971
         NameDeletionConfigurator nameConfig = null;
972
        if (config instanceof NameDeletionConfigurator){
973
            nameConfig = (NameDeletionConfigurator) config;
974
        }else{
975
             result.addException(new Exception("The delete configurator should be of the type NameDeletionConfigurator."));
976
             result.setError();
977
             return result;
978
        }
979

    
980
        if (!name.getNameRelations().isEmpty() && !nameConfig.isRemoveAllNameRelationships()){
981
            HomotypicalGroup homotypicalGroup = HibernateProxyHelper.deproxy(name.getHomotypicalGroup(), HomotypicalGroup.class);
982

    
983
            if (!nameConfig.isIgnoreIsBasionymFor() && homotypicalGroup.getBasionyms().contains(name)){
984
                result.addException(new Exception( "Name can't be deleted as it is a basionym."));
985
                result.setAbort();
986
            }
987
            if (!nameConfig.isIgnoreHasBasionym() && (name.getBasionyms().size()>0)){
988
                result.addException(new Exception( "Name can't be deleted as it has a basionym."));
989
                result.setAbort();
990
            }
991
            Set<NameRelationship> relationships = name.getNameRelations();
992
            for (NameRelationship rel: relationships){
993
                if (!rel.getType().equals(NameRelationshipType.BASIONYM())){
994
                    result.addException(new Exception("Name can't be deleted as it is used in name relationship(s). Remove name relationships prior to deletion."));
995
                    result.setAbort();
996
                    break;
997
                }
998
            }
999
        }
1000
        //concepts
1001
        if (!name.getTaxonBases().isEmpty()){
1002
            boolean isDeletableTaxon = true;
1003
            List <TaxonBase> notDeletedTaxonBases = name.getTaxonBases().stream()
1004
                    .filter((taxonBase) -> !taxonBase.getUuid().equals(taxonUuid))
1005
                    .collect(Collectors.toList());
1006
            if (!notDeletedTaxonBases.isEmpty()){
1007
                result.addException(new Exception("Name can't be deleted as it is used in concept(s). Remove or change concept prior to deletion."));
1008
                result.setAbort();
1009
            }
1010
        }
1011

    
1012
        //hybrid relationships
1013
        if (name.isNonViral()){
1014
            INonViralName nvn = name;
1015
            Set<HybridRelationship> parentHybridRelations = nvn.getHybridParentRelations();
1016
            //Hibernate.initialize(parentHybridRelations);
1017
            if (! parentHybridRelations.isEmpty()){
1018
                result.addException(new Exception("Name can't be deleted as it is a parent in (a) hybrid relationship(s). Remove hybrid relationships prior to deletion."));
1019
                result.setAbort();
1020
            }
1021
        }
1022
        Set<CdmBase> referencingObjects = genericDao.getReferencingObjectsForDeletion(name);
1023
        for (CdmBase referencingObject : referencingObjects){
1024
            //DerivedUnit?.storedUnder
1025
            if (referencingObject.isInstanceOf(DerivedUnit.class)){
1026
                String message = "Name can't be deleted as it is used as derivedUnit#storedUnder by %s. Remove 'stored under' prior to deleting this name";
1027
                message = String.format(message, CdmBase.deproxy(referencingObject, DerivedUnit.class).getTitleCache());
1028
                result.addException(new ReferencedObjectUndeletableException(message));
1029
                result.addRelatedObject(referencingObject);
1030
                result.setAbort();
1031
            }
1032

    
1033
            //DescriptionElementSource#nameUsedInSource
1034
            else if (referencingObject.isInstanceOf(DescriptionElementSource.class) && !referencingObject.isInstanceOf(NomenclaturalSource.class) ){
1035
                String message = "Name can't be deleted as it is used as descriptionElementSource#nameUsedInSource";
1036
                result.addException(new ReferencedObjectUndeletableException(message));
1037
                result.addRelatedObject(referencingObject);
1038
                result.setAbort();
1039
            }
1040
            //NameTypeDesignation#typeName
1041
            else if (referencingObject.isInstanceOf(NameTypeDesignation.class)){
1042
                NameTypeDesignation typeDesignation = HibernateProxyHelper.deproxy(referencingObject, NameTypeDesignation.class);
1043

    
1044
                if (typeDesignation.getTypeName().equals(name) && !typeDesignation.getTypifiedNames().isEmpty()){
1045
                    String message = "Name can't be deleted as it is used as a name type in a NameTypeDesignation";
1046
                    result.addException(new ReferencedObjectUndeletableException(message));
1047
                    result.addRelatedObject(referencingObject);
1048
                    result.setAbort();
1049
                }
1050
            }
1051
            //DeterminationEvent#taxonName
1052
            else if (referencingObject.isInstanceOf(DeterminationEvent.class)){
1053
                String message = "Name can't be deleted as it is used as a determination event";
1054
                result.addException(new ReferencedObjectUndeletableException(message));
1055
                result.addRelatedObject(referencingObject);
1056
                result.setAbort();
1057
            }
1058
            //original spelling
1059
            else if (referencingObject.isInstanceOf(NomenclaturalSource.class) && !((NameDeletionConfigurator)config).isIgnoreIsOriginalSpellingFor()){
1060
                if (((NomenclaturalSource)referencingObject).getNameUsedInSource() != null && ((NomenclaturalSource)referencingObject).getNameUsedInSource().equals(name)){
1061
                    String message = "Name can't be deleted as it is used as original spelling";
1062
                    result.addException(new ReferencedObjectUndeletableException(message));
1063
                    result.addRelatedObject(referencingObject);
1064
                    result.setAbort();
1065
                }
1066
            }
1067
            if (referencingObject.isInstanceOf(NomenclaturalSource.class)){
1068
                if (((NomenclaturalSource)referencingObject).getNameUsedInSource() != null && ((NomenclaturalSource)referencingObject).getNameUsedInSource().equals(name)){
1069
                    result.addRelatedObject(referencingObject);
1070
                }
1071

    
1072
            }
1073
        }
1074

    
1075
        //TODO inline references
1076

    
1077

    
1078
        if (!nameConfig.isIgnoreIsReplacedSynonymFor() && name.isReplacedSynonym()){
1079
            String message = "Name can't be deleted as it is a replaced synonym.";
1080
            result.addException(new Exception(message));
1081
            result.setAbort();
1082
        }
1083
        if (!nameConfig.isIgnoreHasReplacedSynonym() && (name.getReplacedSynonyms().size()>0)){
1084
            String message = "Name can't be deleted as it has a replaced synonym.";
1085
            result.addException(new Exception(message));
1086
            result.setAbort();
1087
        }
1088
        return result;
1089

    
1090
    }
1091

    
1092

    
1093
    @Override
1094
    public DeleteResult isDeletable(UUID nameUUID, DeleteConfiguratorBase config){
1095
        TaxonName name = this.load(nameUUID);
1096
        return isDeletable(name, config, null);
1097
    }
1098

    
1099
    @Override
1100
    public DeleteResult isDeletable(UUID nameUUID, DeleteConfiguratorBase config, UUID taxonUuid){
1101
        TaxonName name = this.load(nameUUID);
1102
        return isDeletable(name, config, taxonUuid);
1103
    }
1104

    
1105
    @Override
1106
    @Transactional(readOnly = true)
1107
    public UpdateResult setAsGroupsBasionym(UUID nameUuid) {
1108
        TaxonName name = dao.load(nameUuid);
1109
        UpdateResult result = new UpdateResult();
1110
        name.setAsGroupsBasionym();
1111
        result.addUpdatedObject(name);
1112
        return result;
1113

    
1114
    }
1115

    
1116
    @Override
1117
    public List<HashMap<String,String>> getNameRecords(){
1118
		return dao.getNameRecords();
1119

    
1120
    }
1121

    
1122
    @Override
1123
    public List<TypeDesignationStatusBase> getTypeDesignationStatusInUse(){
1124
        return typeDesignationDao.getTypeDesignationStatusInUse();
1125
    }
1126

    
1127
    @Override
1128
    public Collection<TypeDesignationStatusFilter> getTypeDesignationStatusFilterTerms(List<Language> preferredLanguages){
1129
        List<TypeDesignationStatusBase> termList = typeDesignationDao.getTypeDesignationStatusInUse();
1130
        Map<String, TypeDesignationStatusFilter>  filterMap = new HashMap<>();
1131
        for(TypeDesignationStatusBase term : termList){
1132
            TypeDesignationStatusFilter filter = new TypeDesignationStatusFilter(term, preferredLanguages, true);
1133
            String key = filter.getKey();
1134
            if(filterMap.containsKey(key)){
1135
                filterMap.get(key).addStatus(term);
1136
            } else {
1137
                filterMap.put(key, filter);
1138
            }
1139
        }
1140
        return filterMap.values();
1141
    }
1142

    
1143
    @Override
1144
    public <S extends TaxonName> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1145
            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths) {
1146
        return page(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
1147
    }
1148

    
1149
    @Override
1150
    public <S extends TaxonName> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1151
            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths, boolean includeUnpublished) {
1152

    
1153
        List<S> records;
1154
        long resultSize = dao.count(clazz, restrictions);
1155
        if(AbstractPagerImpl.hasResultsInRange(resultSize, pageIndex, pageSize)){
1156
            records = dao.list(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, includeUnpublished);
1157
        } else {
1158
            records = new ArrayList<>();
1159
        }
1160
        Pager<S> pager = new DefaultPagerImpl<>(pageIndex, resultSize, pageSize, records);
1161
        return pager;
1162
    }
1163

    
1164
    @Override
1165
    public List<UuidAndTitleCache> getUuidAndTitleCacheOfSynonymy(Integer limit, UUID taxonUuid) {
1166
        List<String> propertyPaths = new ArrayList<>();
1167
        propertyPaths.add("synonyms.name.*");
1168
        TaxonBase<?> taxonBase = taxonService.load(taxonUuid, propertyPaths);
1169
        if (taxonBase instanceof Taxon){
1170
            Taxon taxon = (Taxon)taxonBase;
1171
            Set<TaxonName> names = taxon.getSynonymNames();
1172
            List<UuidAndTitleCache> uuidAndTitleCacheList = new ArrayList<>();
1173
            UuidAndTitleCache<TaxonName> uuidAndTitleCache;
1174
            for (TaxonName name: names){
1175
                uuidAndTitleCache = new UuidAndTitleCache<TaxonName>(TaxonName.class, name.getUuid(), name.getId(), name.getTitleCache());
1176
                uuidAndTitleCacheList.add(uuidAndTitleCache);
1177
            }
1178
        }
1179
        return null;
1180
    }
1181

    
1182
    @Override
1183
    public UpdateResult parseName(String stringToBeParsed, NomenclaturalCode code, Rank preferredRank, boolean doDeduplicate) {
1184
        TaxonName name = TaxonNameFactory.NewNameInstance(code, preferredRank);
1185
        return parseName(name, stringToBeParsed, preferredRank, true, doDeduplicate);
1186
    }
1187

    
1188
    @Override
1189
    public UpdateResult parseName(TaxonName nameToBeFilled, String stringToBeParsed, Rank preferredRank,
1190
            boolean doEmpty, boolean doDeduplicate){
1191

    
1192
        UpdateResult result = new UpdateResult();
1193
        NonViralNameParserImpl nonViralNameParser = NonViralNameParserImpl.NewInstance();
1194
        nonViralNameParser.parseReferencedName(nameToBeFilled, stringToBeParsed, preferredRank, doEmpty);
1195
        TaxonName name = nameToBeFilled;
1196
        if(doDeduplicate) {
1197
            try {
1198
//              Level sqlLogLevel = LogManager.getLogger("org.hibernate.SQL").getLevel();
1199
//              LogUtils.setLevel("org.hibernate.SQL", Level.TRACE);
1200

    
1201
                //references
1202
                if (name.getNomenclaturalReference()!= null && !name.getNomenclaturalReference().isPersited()){
1203
                    Reference nomRef = name.getNomenclaturalReference();
1204
                    IMatchStrategy referenceMatcher = MatchStrategyFactory.NewParsedReferenceInstance(nomRef);
1205
                    List<Reference> matchingReferences = commonService.findMatching(nomRef, referenceMatcher);
1206
                    if(matchingReferences.size() >= 1){
1207
                        Reference duplicate = findBestMatching(nomRef, matchingReferences, referenceMatcher);
1208
                        name.setNomenclaturalReference(duplicate);
1209
                    }else{
1210
                        if (nomRef.getInReference() != null){
1211
                            List<Reference> matchingInReferences = commonService.findMatching(nomRef.getInReference(), MatchStrategyFactory.NewParsedReferenceInstance(nomRef.getInReference()));
1212
                            if(matchingInReferences.size() >= 1){
1213
                                Reference duplicate = findBestMatching(nomRef, matchingInReferences, referenceMatcher);
1214
                                nomRef.setInReference(duplicate);
1215
                            }
1216
                        }
1217
                        TeamOrPersonBase<?> author = deduplicateAuthor(nomRef.getAuthorship());
1218
                        nomRef.setAuthorship(author);
1219
                    }
1220
                }
1221
                Reference nomRef = name.getNomenclaturalReference();
1222

    
1223
                //authors
1224
                IParsedMatchStrategy authorMatcher = MatchStrategyFactory.NewParsedTeamOrPersonInstance();
1225
                if (name.getCombinationAuthorship()!= null && !name.getCombinationAuthorship().isPersited()){
1226
                    //use same nom.ref. author if possible (should always be possible if nom.ref. exists)
1227
                    if (nomRef != null && nomRef.getAuthorship() != null){
1228
                        if(authorMatcher.invoke(name.getCombinationAuthorship(), nomRef.getAuthorship()).isSuccessful()){
1229
                            name.setCombinationAuthorship(nomRef.getAuthorship());
1230
                        }
1231
                    }
1232
                    name.setCombinationAuthorship(deduplicateAuthor(name.getCombinationAuthorship()));
1233
                }
1234
                if (name.getExCombinationAuthorship()!= null && !name.getExCombinationAuthorship().isPersited()){
1235
                    name.setExCombinationAuthorship(deduplicateAuthor(name.getExCombinationAuthorship()));
1236
                }
1237
                if (name.getBasionymAuthorship()!= null && !name.getBasionymAuthorship().isPersited()){
1238
                    name.setBasionymAuthorship(deduplicateAuthor(name.getBasionymAuthorship()));
1239
                }
1240
                if (name.getExBasionymAuthorship()!= null && !name.getExBasionymAuthorship().isPersited()){
1241
                    name.setExBasionymAuthorship(deduplicateAuthor(name.getExBasionymAuthorship()));
1242
                }
1243

    
1244
                //originalSpelling
1245
                if (name.getOriginalSpelling()!= null && !name.getOriginalSpelling().isPersited()){
1246
                    TaxonName origName = name.getOriginalSpelling();
1247
                    IMatchStrategy nameMatcher = MatchStrategyFactory.NewParsedOriginalSpellingInstance();
1248
                    List<TaxonName> matchingNames = commonService.findMatching(origName, nameMatcher);
1249
                    if(matchingNames.size() >= 1){
1250
                        TaxonName duplicate = findBestMatching(origName, matchingNames, nameMatcher);
1251
                        name.setOriginalSpelling(duplicate);
1252
                    }
1253
                }
1254
//              LogUtils.setLevel("org.hibernate.SQL", sqlLogLevel);
1255
            } catch (MatchException e) {
1256
                throw new RuntimeException(e);
1257
            }
1258
        }
1259
        result.setCdmEntity(name);
1260
        return result;
1261
    }
1262

    
1263
    private TeamOrPersonBase<?> deduplicateAuthor(TeamOrPersonBase<?> authorship) throws MatchException {
1264
        if (authorship == null){
1265
            return null;
1266
        }
1267
        IParsedMatchStrategy authorMatcher = MatchStrategyFactory.NewParsedTeamOrPersonInstance();
1268
        List<TeamOrPersonBase<?>> matchingAuthors = commonService.findMatching(authorship, authorMatcher);
1269
        if(matchingAuthors.size() >= 1){
1270
            TeamOrPersonBase<?> duplicate = findBestMatching(authorship, matchingAuthors, authorMatcher);
1271
            return duplicate;
1272
        }else{
1273
            if (authorship instanceof Team){
1274
                deduplicateTeam((Team)authorship);
1275
            }
1276
            return authorship;
1277
        }
1278
    }
1279

    
1280
    private void deduplicateTeam(Team team) throws MatchException {
1281
        List<Person> members = team.getTeamMembers();
1282
        IParsedMatchStrategy personMatcher = MatchStrategyFactory.NewParsedPersonInstance();
1283
        for (int i =0; i< members.size(); i++){
1284
            Person person = CdmBase.deproxy(members.get(i));
1285
            List<Person> matchingPersons = commonService.findMatching(person, personMatcher);
1286
            if (matchingPersons.size() > 0){
1287
                person = findBestMatching(person, matchingPersons, personMatcher);
1288
                members.set(i, person);
1289
            }
1290
        }
1291
    }
1292

    
1293
    private <M extends IMatchable> M findBestMatching(M matchable, List<M> matchingList,
1294
            IMatchStrategy matcher) {
1295
        // FIXME TODO resolve multiple duplications. Use first match for a start
1296
        if(matchingList.isEmpty()){
1297
            return null;
1298
        }
1299
        M bestMatching = matchingList.iterator().next();
1300
        return bestMatching;
1301
    }
1302
}
(70-70/95)