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

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

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

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

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

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

    
144
//***************************** CONSTRUCTOR **********************************/
145

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

    
151
//********************* METHODS ***********************************************//
152

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

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

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

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

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

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

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

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

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

    
221
        return result;
222
    }
223

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

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

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

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

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

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

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

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

    
300

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

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

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

    
323
            typeDesignationDao.delete(typeDesignation);
324

    
325
        }
326
    }
327

    
328

    
329

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

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

    
374
//********************* METHODS ****************************************************************//
375

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

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

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

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

    
420

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
524

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

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

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

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

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

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

    
561

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

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

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

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

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

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

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

    
593

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
685
         BooleanQuery finalQuery = new BooleanQuery(false);
686

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

    
689
        luceneSearch.setQuery(finalQuery);
690

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

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

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

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

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

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

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

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

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

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

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

    
750

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

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

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

    
760
        return searchResults;
761

    
762
    }
763

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

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

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

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

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

    
789
        return searchResults;
790
    }
791

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

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

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

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

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

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

    
812
        return searchResults;
813
    }
814

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

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

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

    
827
        // --- execute search
828

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

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

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

    
836
        return searchResults;
837
    }
838

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
940
         return results;
941
    }
942

    
943

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

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

    
958

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

    
966

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

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

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

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

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

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

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

    
1071
            }
1072
        }
1073

    
1074
        //TODO inline references
1075

    
1076

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

    
1089
    }
1090

    
1091

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

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

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

    
1113
    }
1114

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

    
1119
    }
1120

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

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

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

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

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

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

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

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

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

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

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

    
1243
                //originalSpelling
1244
                if (name.getOriginalSpelling()!= null && !name.getOriginalSpelling().isPersited()){
1245
                    TaxonName origName = name.getOriginalSpelling();
1246
                    IMatchStrategy nameMatcher = MatchStrategyFactory.NewParsedOriginalSpellingInstance();
1247
                    List<TaxonName> matchingNames = commonService.findMatching(origName, nameMatcher);
1248
                    if(matchingNames.size() >= 1){
1249
                        TaxonName duplicate = findBestMatching(origName, matchingNames, nameMatcher);
1250
                        name.setOriginalSpelling(duplicate);
1251
                    }
1252
                }
1253

    
1254
//              Logger.getLogger("org.hibernate.SQL").setLevel(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)