ref #9359, ref #10072 final adaptations for upgrade to log4j2 in cdmlib
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / NameServiceImpl.java
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 }