ref #6413 Throw exception for more than 1 or 0 FieldUnits
[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
10 package eu.etaxonomy.cdm.api.service;
11
12 import java.io.IOException;
13 import java.util.ArrayList;
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
23 import org.apache.log4j.Logger;
24 import org.apache.lucene.index.Term;
25 import org.apache.lucene.sandbox.queries.FuzzyLikeThisQuery;
26 import org.apache.lucene.search.BooleanClause.Occur;
27 import org.apache.lucene.search.BooleanQuery;
28 import org.apache.lucene.search.BooleanQuery.Builder;
29 import org.apache.lucene.search.TopDocs;
30 import org.apache.lucene.search.WildcardQuery;
31 import org.hibernate.criterion.Criterion;
32 import org.springframework.beans.factory.annotation.Autowired;
33 import org.springframework.beans.factory.annotation.Qualifier;
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.exception.ReferencedObjectUndeletableException;
40 import eu.etaxonomy.cdm.api.service.pager.Pager;
41 import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
42 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
43 import eu.etaxonomy.cdm.api.service.search.DocumentSearchResult;
44 import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
45 import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
46 import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
47 import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
48 import eu.etaxonomy.cdm.api.service.search.QueryFactory;
49 import eu.etaxonomy.cdm.api.service.search.SearchResult;
50 import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
51 import eu.etaxonomy.cdm.api.utility.TaxonNamePartsFilter;
52 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
53 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
54 import eu.etaxonomy.cdm.model.CdmBaseType;
55 import eu.etaxonomy.cdm.model.common.CdmBase;
56 import eu.etaxonomy.cdm.model.common.Language;
57 import eu.etaxonomy.cdm.model.common.ReferencedEntityBase;
58 import eu.etaxonomy.cdm.model.common.RelationshipBase;
59 import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
60 import eu.etaxonomy.cdm.model.common.SourcedEntityBase;
61 import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
62 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
63 import eu.etaxonomy.cdm.model.name.HybridRelationship;
64 import eu.etaxonomy.cdm.model.name.HybridRelationshipType;
65 import eu.etaxonomy.cdm.model.name.INonViralName;
66 import eu.etaxonomy.cdm.model.name.NameRelationship;
67 import eu.etaxonomy.cdm.model.name.NameRelationshipType;
68 import eu.etaxonomy.cdm.model.name.NameTypeDesignation;
69 import eu.etaxonomy.cdm.model.name.NomenclaturalStatus;
70 import eu.etaxonomy.cdm.model.name.Rank;
71 import eu.etaxonomy.cdm.model.name.Registration;
72 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
73 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
74 import eu.etaxonomy.cdm.model.name.TaxonName;
75 import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
76 import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
77 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
78 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
79 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
80 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
81 import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
82 import eu.etaxonomy.cdm.persistence.dao.common.IReferencedEntityDao;
83 import eu.etaxonomy.cdm.persistence.dao.common.ISourcedEntityDao;
84 import eu.etaxonomy.cdm.persistence.dao.name.IHomotypicalGroupDao;
85 import eu.etaxonomy.cdm.persistence.dao.name.INomenclaturalStatusDao;
86 import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
87 import eu.etaxonomy.cdm.persistence.dao.name.ITypeDesignationDao;
88 import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
89 import eu.etaxonomy.cdm.persistence.dao.term.IOrderedTermVocabularyDao;
90 import eu.etaxonomy.cdm.persistence.dao.term.ITermVocabularyDao;
91 import eu.etaxonomy.cdm.persistence.dto.TaxonNameParts;
92 import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
93 import eu.etaxonomy.cdm.persistence.query.MatchMode;
94 import eu.etaxonomy.cdm.persistence.query.OrderHint;
95 import eu.etaxonomy.cdm.strategy.cache.TaggedText;
96 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
97 import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
98
99
100 @Service
101 @Transactional(readOnly = true)
102 public class NameServiceImpl
103 extends IdentifiableServiceBase<TaxonName,ITaxonNameDao>
104 implements INameService {
105 static private final Logger logger = Logger.getLogger(NameServiceImpl.class);
106
107 @Autowired
108 protected ITermVocabularyDao vocabularyDao;
109 @Autowired
110 protected IOrderedTermVocabularyDao orderedVocabularyDao;
111 @Autowired
112 protected IOccurrenceService occurrenceService;
113 @Autowired
114 @Qualifier("refEntDao")
115 protected IReferencedEntityDao<ReferencedEntityBase> referencedEntityDao;
116 @Autowired
117 @Qualifier("sourcedEntityDao")
118 protected ISourcedEntityDao<SourcedEntityBase<?>> sourcedEntityDao;
119 @Autowired
120 private INomenclaturalStatusDao nomStatusDao;
121 @Autowired
122 private ITypeDesignationDao typeDesignationDao;
123 @Autowired
124 private IHomotypicalGroupDao homotypicalGroupDao;
125 @Autowired
126 private ICdmGenericDao genericDao;
127 @Autowired
128 private ILuceneIndexToolProvider luceneIndexToolProvider;
129
130 /**
131 * Constructor
132 */
133 public NameServiceImpl(){}
134
135 //********************* METHODS ****************************************************************//
136
137 @Override
138 @Transactional(readOnly = false)
139 public DeleteResult delete(UUID nameUUID){
140 NameDeletionConfigurator config = new NameDeletionConfigurator();
141 DeleteResult result = delete(nameUUID, config);
142 return result;
143 }
144
145 @Override
146 public DeleteResult delete(TaxonName name){
147 return delete(name.getUuid());
148 }
149
150 @Override
151 @Transactional(readOnly = false)
152 public DeleteResult delete(TaxonName name, NameDeletionConfigurator config) {
153 DeleteResult result = new DeleteResult();
154
155 if (name == null){
156 result.setAbort();
157 return result;
158 }
159
160 try{
161 result = this.isDeletable(name, config);
162 }catch(Exception e){
163 result.addException(e);
164 result.setError();
165 return result;
166 }
167 if (result.isOk()){
168 //remove references to this name
169 removeNameRelationshipsByDeleteConfig(name, config);
170
171 //remove name from homotypical group
172 HomotypicalGroup homotypicalGroup = name.getHomotypicalGroup();
173 if (homotypicalGroup != null){
174 homotypicalGroup.removeTypifiedName(name, false);
175 }
176
177 //all type designation relationships are removed as they belong to the name
178 deleteTypeDesignation(name, null);
179 // //type designations
180 // if (! name.getTypeDesignations().isEmpty()){
181 // String message = "Name can't be deleted as it has types. Remove types prior to deletion.";
182 // throw new ReferrencedObjectUndeletableException(message);
183 // }
184
185 try{
186 dao.delete(name);
187 result.addDeletedObject(name);
188 }catch(Exception e){
189 result.addException(e);
190 result.setError();
191 }
192 return result;
193 }
194
195 return result;
196 }
197
198 @Override
199 @Transactional(readOnly = false)
200 public DeleteResult delete(UUID nameUUID, NameDeletionConfigurator config) {
201
202 TaxonName name = dao.load(nameUUID);
203 return delete(name, config);
204 }
205
206 @Override
207 @Transactional(readOnly = false)
208 public UpdateResult cloneTypeDesignation(TaxonName name, SpecimenTypeDesignation baseDesignation,
209 String accessionNumber, eu.etaxonomy.cdm.model.occurrence.Collection collection, SpecimenTypeDesignationStatus typeStatus){
210 UpdateResult result = new UpdateResult();
211
212 DerivedUnit baseSpecimen = (DerivedUnit) occurrenceService.load(baseDesignation.getTypeSpecimen().getUuid());
213 DerivedUnit duplicate = DerivedUnit.NewInstance(baseSpecimen.getRecordBasis());
214 DerivationEvent derivedFrom = baseSpecimen.getDerivedFrom();
215 Collection<FieldUnit> fieldUnits = occurrenceService.findFieldUnits(baseSpecimen.getUuid(), null);
216 if(fieldUnits.size()!=1){
217 result.addException(new Exception("More than one or no field unit found for specimen"));
218 result.setError();
219 return result;
220 }
221 for (SpecimenOrObservationBase original : derivedFrom.getOriginals()) {
222 DerivationEvent.NewSimpleInstance(original, duplicate, derivedFrom.getType());
223 }
224 duplicate.setAccessionNumber(accessionNumber);
225 duplicate.setCollection(collection);
226 SpecimenTypeDesignation typeDesignation = SpecimenTypeDesignation.NewInstance();
227 typeDesignation.setTypeSpecimen(duplicate);
228 typeDesignation.setTypeStatus(typeStatus);
229 name.getTypeDesignations().add(typeDesignation);
230
231 result.setCdmEntity(typeDesignation);
232 result.addUpdatedObject(name);
233 return result;
234 }
235
236 @Override
237 @Transactional
238 public DeleteResult deleteTypeDesignation(TaxonName name, TypeDesignationBase<?> typeDesignation){
239 if(typeDesignation != null && typeDesignation .isPersited()){
240 typeDesignation = HibernateProxyHelper.deproxy(sourcedEntityDao.load(typeDesignation.getUuid()), TypeDesignationBase.class);
241 }
242
243 DeleteResult result = new DeleteResult();
244 if (name == null && typeDesignation == null){
245 result.setError();
246 return result;
247 }else if (name != null && typeDesignation != null){
248 removeSingleDesignation(name, typeDesignation);
249 }else if (name != null){
250 @SuppressWarnings("rawtypes")
251 Set<TypeDesignationBase<?>> designationSet = new HashSet(name.getTypeDesignations());
252 for (TypeDesignationBase<?> desig : designationSet){
253 desig = CdmBase.deproxy(desig);
254 removeSingleDesignation(name, desig);
255 }
256 }else if (typeDesignation != null){
257 @SuppressWarnings("unchecked")
258 Set<TaxonName> nameSet = new HashSet(typeDesignation.getTypifiedNames());
259 for (TaxonName singleName : nameSet){
260 singleName = CdmBase.deproxy(singleName);
261 removeSingleDesignation(singleName, typeDesignation);
262 }
263 }
264 result.addDeletedObject(typeDesignation);
265 result.addUpdatedObject(name);
266 return result;
267 }
268
269
270 @Override
271 @Transactional(readOnly = false)
272 public DeleteResult deleteTypeDesignation(UUID nameUuid, UUID typeDesignationUuid){
273 TaxonName nameBase = load(nameUuid);
274 TypeDesignationBase<?> typeDesignation = HibernateProxyHelper.deproxy(sourcedEntityDao.load(typeDesignationUuid), TypeDesignationBase.class);
275 return deleteTypeDesignation(nameBase, typeDesignation);
276 }
277
278 /**
279 * @param name
280 * @param typeDesignation
281 */
282 @Transactional
283 private void removeSingleDesignation(TaxonName name, TypeDesignationBase<?> typeDesignation) {
284
285 name.removeTypeDesignation(typeDesignation);
286 if (typeDesignation.getTypifiedNames().isEmpty()){
287 typeDesignation.removeType();
288 if (!typeDesignation.getRegistrations().isEmpty()){
289 for(Object reg: typeDesignation.getRegistrations()){
290 if (reg instanceof Registration){
291 ((Registration)reg).removeTypeDesignation(typeDesignation);
292 }
293 }
294 }
295
296 typeDesignationDao.delete(typeDesignation);
297
298 }
299 }
300
301
302
303 /**
304 * @param name
305 * @param config
306 */
307 private void removeNameRelationshipsByDeleteConfig(TaxonName name, NameDeletionConfigurator config) {
308 try {
309 if (config.isRemoveAllNameRelationships()){
310 Set<NameRelationship> rels = getModifiableSet(name.getNameRelations());
311 for (NameRelationship rel : rels){
312 name.removeNameRelationship(rel);
313 }
314 }else{
315 //relations to this name
316 Set<NameRelationship> rels = getModifiableSet(name.getRelationsToThisName());
317 for (NameRelationship rel : rels){
318 if (config.isIgnoreHasBasionym() && NameRelationshipType.BASIONYM().equals(rel.getType() )){
319 name.removeNameRelationship(rel);
320 }else if (config.isIgnoreHasReplacedSynonym() && NameRelationshipType.REPLACED_SYNONYM().equals(rel.getType())){
321 name.removeNameRelationship(rel);
322 }
323 }
324 //relations from this name
325 rels = getModifiableSet(name.getRelationsFromThisName());
326 for (NameRelationship rel : rels){
327 if (config.isIgnoreIsBasionymFor() && NameRelationshipType.BASIONYM().equals(rel.getType()) ){
328 name.removeNameRelationship(rel);
329 }else if (config.isIgnoreIsReplacedSynonymFor() && NameRelationshipType.REPLACED_SYNONYM().equals(rel.getType())){
330 name.removeNameRelationship(rel);
331 }
332 }
333
334 }
335 } catch (Exception e) {
336 throw new RuntimeException(e);
337 }
338 }
339
340 /**
341 * @param name
342 * @return
343 */
344 private Set<NameRelationship> getModifiableSet(Set<NameRelationship> relations) {
345 Set<NameRelationship> rels = new HashSet<NameRelationship>();
346 for (NameRelationship rel : relations){
347 rels.add(rel);
348 }
349 return rels;
350 }
351
352 //********************* METHODS ****************************************************************//
353
354
355 /**
356 * TODO candidate for harmonization
357 * new name findByName
358 */
359 @Override
360 @Deprecated
361 public List<TaxonName> getNamesByNameCache(String nameCache){
362 boolean includeAuthors = false;
363 List result = dao.findByName(includeAuthors, nameCache, MatchMode.EXACT, null, null, null, null);
364 return result;
365 }
366
367 /**
368 * TODO candidate for harmonization
369 * new name saveHomotypicalGroups
370 *
371 * findByTitle
372 */
373 @Override
374 public List<TaxonName> findNamesByTitleCache(String titleCache, MatchMode matchMode, List<String> propertyPaths){
375 List result = dao.findByTitle(titleCache, matchMode, null, null, null, propertyPaths);
376 return result;
377 }
378
379 /**
380 * TODO candidate for harmonization
381 * new name saveHomotypicalGroups
382 *
383 * findByTitle
384 */
385 @Override
386 public List<TaxonName> findNamesByNameCache(String nameCache, MatchMode matchMode, List<String> propertyPaths){
387 List<TaxonName> result = dao.findByName(false, nameCache, matchMode, null, null, null , propertyPaths);
388 return result;
389 }
390
391 @Override
392 public Pager<TaxonNameParts> findTaxonNameParts(Optional<String> genusOrUninomial,
393 Optional<String> infraGenericEpithet, Optional<String> specificEpithet,
394 Optional<String> infraSpecificEpithet, Rank rank, Set<UUID> excludedNamesUuids,
395 Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
396
397
398 long count = dao.countTaxonNameParts(genusOrUninomial, infraGenericEpithet, specificEpithet, infraGenericEpithet, rank, excludedNamesUuids);
399
400 List<TaxonNameParts> results;
401 if(AbstractPagerImpl.hasResultsInRange(count, pageIndex, pageSize)){
402 results = dao.findTaxonNameParts(genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
403 rank, excludedNamesUuids,
404 pageSize, pageIndex, orderHints);
405 } else {
406 results = new ArrayList<>();
407 }
408
409 return new DefaultPagerImpl<TaxonNameParts>(pageIndex, count, pageSize, results);
410 }
411
412 /**
413 * {@inheritDoc}
414 */
415 @Override
416 public Pager<TaxonNameParts> findTaxonNameParts(TaxonNamePartsFilter filter, String namePartQueryString,
417 Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
418
419 return findTaxonNameParts(
420 filter.uninomialQueryString(namePartQueryString),
421 filter.infraGenericEpithet(namePartQueryString),
422 filter.specificEpithet(namePartQueryString),
423 filter.infraspecificEpithet(namePartQueryString),
424 filter.getRank(),
425 filter.getExludedNamesUuids(),
426 pageSize, pageIndex, orderHints);
427 }
428
429 /**
430 * TODO candidate for harmonization
431 * new name saveHomotypicalGroups
432 */
433 @Override
434 @Transactional(readOnly = false)
435 public Map<UUID, HomotypicalGroup> saveAllHomotypicalGroups(Collection<HomotypicalGroup> homotypicalGroups){
436 return homotypicalGroupDao.saveAll(homotypicalGroups);
437 }
438
439 /**
440 * TODO candidate for harmonization
441 * new name saveTypeDesignations
442 */
443 @Override
444 @Transactional(readOnly = false)
445 public Map<UUID, TypeDesignationBase<?>> saveTypeDesignationAll(Collection<TypeDesignationBase<?>> typeDesignationCollection){
446 return typeDesignationDao.saveAll(typeDesignationCollection);
447 }
448
449 /**
450 * TODO candidate for harmonization
451 * new name getNomenclaturalStatus
452 */
453 @Override
454 public List<NomenclaturalStatus> getAllNomenclaturalStatus(int limit, int start){
455 return nomStatusDao.list(limit, start);
456 }
457
458 /**
459 * TODO candidate for harmonization
460 * new name getTypeDesignations
461 */
462 @Override
463 public List<TypeDesignationBase<?>> getAllTypeDesignations(int limit, int start){
464 return typeDesignationDao.getAllTypeDesignations(limit, start);
465 }
466
467 @Override
468 public TypeDesignationBase<?> loadTypeDesignation(int id, List<String> propertyPaths){
469 return typeDesignationDao.load(id, propertyPaths);
470 }
471
472 @Override
473 public TypeDesignationBase<?> loadTypeDesignation(UUID uuid, List<String> propertyPaths){
474 return typeDesignationDao.load(uuid, propertyPaths);
475 }
476
477 /**
478 * FIXME Candidate for harmonization
479 * homotypicalGroupService.list
480 */
481 @Override
482 public List<HomotypicalGroup> getAllHomotypicalGroups(int limit, int start){
483 return homotypicalGroupDao.list(limit, start);
484 }
485
486 /**
487 * FIXME Candidate for harmonization
488 * remove
489 */
490 @Override
491 @Deprecated
492 public List<RelationshipBase> getAllRelationships(int limit, int start){
493 return dao.getAllRelationships(limit, start);
494 }
495
496
497
498 @Override
499 @Autowired
500 protected void setDao(ITaxonNameDao dao) {
501 this.dao = dao;
502 }
503
504 @Override
505 public Pager<HybridRelationship> getHybridNames(INonViralName name, HybridRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
506 Integer numberOfResults = dao.countHybridNames(name, type);
507
508 List<HybridRelationship> results = new ArrayList<HybridRelationship>();
509 if(AbstractPagerImpl.hasResultsInRange(numberOfResults.longValue(), pageNumber, pageSize)) { // no point checking again
510 results = dao.getHybridNames(name, type, pageSize, pageNumber,orderHints,propertyPaths);
511 }
512
513 return new DefaultPagerImpl<HybridRelationship>(pageNumber, numberOfResults, pageSize, results);
514 }
515
516 @Override
517 public List<NameRelationship> listNameRelationships(TaxonName name, Direction direction, NameRelationshipType type, Integer pageSize,
518 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
519
520 Integer numberOfResults = dao.countNameRelationships(name, direction, type);
521
522 List<NameRelationship> results = new ArrayList<NameRelationship>();
523 if (AbstractPagerImpl.hasResultsInRange(numberOfResults.longValue(), pageNumber, pageSize)) { // no point checking again
524 results = dao.getNameRelationships(name, direction, type, pageSize, pageNumber, orderHints, propertyPaths);
525 }
526 return results;
527 }
528
529
530 protected LuceneSearch prepareFindByFuzzyNameSearch(Class<? extends CdmBase> clazz,
531 INonViralName nvn,
532 float accuracy,
533 int maxNoOfResults,
534 List<Language> languages,
535 boolean highlightFragments) {
536 String similarity = Float.toString(accuracy);
537 String searchSuffix = "~" + similarity;
538
539
540 Builder finalQueryBuilder = new Builder();
541 finalQueryBuilder.setDisableCoord(false);
542 Builder textQueryBuilder = new Builder();
543 textQueryBuilder.setDisableCoord(false);
544
545 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, TaxonName.class);
546 QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonName.class);
547
548 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
549 // luceneSearch.setSortFields(sortFields);
550
551 // ---- search criteria
552 luceneSearch.setCdmTypRestriction(clazz);
553
554 FuzzyLikeThisQuery fltq = new FuzzyLikeThisQuery(maxNoOfResults, luceneSearch.getAnalyzer());
555 if(nvn.getGenusOrUninomial() != null && !nvn.getGenusOrUninomial().equals("")) {
556 fltq.addTerms(nvn.getGenusOrUninomial().toLowerCase(), "genusOrUninomial", accuracy, 3);
557 } else {
558 //textQuery.add(new RegexQuery (new Term ("genusOrUninomial", "^[a-zA-Z]*")), Occur.MUST_NOT);
559 textQueryBuilder.add(queryFactory.newTermQuery("genusOrUninomial", "_null_", false), Occur.MUST);
560 }
561
562 if(nvn.getInfraGenericEpithet() != null && !nvn.getInfraGenericEpithet().equals("")){
563 fltq.addTerms(nvn.getInfraGenericEpithet().toLowerCase(), "infraGenericEpithet", accuracy, 3);
564 } else {
565 //textQuery.add(new RegexQuery (new Term ("infraGenericEpithet", "^[a-zA-Z]*")), Occur.MUST_NOT);
566 textQueryBuilder.add(queryFactory.newTermQuery("infraGenericEpithet", "_null_", false), Occur.MUST);
567 }
568
569 if(nvn.getSpecificEpithet() != null && !nvn.getSpecificEpithet().equals("")){
570 fltq.addTerms(nvn.getSpecificEpithet().toLowerCase(), "specificEpithet", accuracy, 3);
571 } else {
572 //textQuery.add(new RegexQuery (new Term ("specificEpithet", "^[a-zA-Z]*")), Occur.MUST_NOT);
573 textQueryBuilder.add(queryFactory.newTermQuery("specificEpithet", "_null_", false), Occur.MUST);
574 }
575
576 if(nvn.getInfraSpecificEpithet() != null && !nvn.getInfraSpecificEpithet().equals("")){
577 fltq.addTerms(nvn.getInfraSpecificEpithet().toLowerCase(), "infraSpecificEpithet", accuracy, 3);
578 } else {
579 //textQuery.add(new RegexQuery (new Term ("infraSpecificEpithet", "^[a-zA-Z]*")), Occur.MUST_NOT);
580 textQueryBuilder.add(queryFactory.newTermQuery("infraSpecificEpithet", "_null_", false), Occur.MUST);
581 }
582
583 if(nvn.getAuthorshipCache() != null && !nvn.getAuthorshipCache().equals("")){
584 fltq.addTerms(nvn.getAuthorshipCache().toLowerCase(), "authorshipCache", accuracy, 3);
585 } else {
586 //textQuery.add(new RegexQuery (new Term ("authorshipCache", "^[a-zA-Z]*")), Occur.MUST_NOT);
587 }
588
589 textQueryBuilder.add(fltq, Occur.MUST);
590
591 BooleanQuery textQuery = textQueryBuilder.build();
592 finalQueryBuilder.add(textQuery, Occur.MUST);
593
594 luceneSearch.setQuery(finalQueryBuilder.build());
595
596 if(highlightFragments){
597 luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
598 }
599 return luceneSearch;
600 }
601
602 protected LuceneSearch prepareFindByFuzzyNameCacheSearch(Class<? extends CdmBase> clazz,
603 String name,
604 float accuracy,
605 int maxNoOfResults,
606 List<Language> languages,
607 boolean highlightFragments) {
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 FuzzyLikeThisQuery fltq = new FuzzyLikeThisQuery(maxNoOfResults, luceneSearch.getAnalyzer());
618
619 fltq.addTerms(name, "nameCache", accuracy, 3);
620
621 BooleanQuery finalQuery = new BooleanQuery(false);
622
623 finalQuery.add(fltq, Occur.MUST);
624
625 luceneSearch.setQuery(finalQuery);
626
627 if(highlightFragments){
628 luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
629 }
630 return luceneSearch;
631 }
632
633 protected LuceneSearch prepareFindByExactNameSearch(Class<? extends CdmBase> clazz,
634 String name,
635 boolean wildcard,
636 List<Language> languages,
637 boolean highlightFragments) {
638 Builder textQueryBuilder = new Builder();
639
640 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, TaxonName.class);
641 QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonName.class);
642
643 // SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
644 // luceneSearch.setSortFields(sortFields);
645
646 // ---- search criteria
647 luceneSearch.setCdmTypRestriction(clazz);
648
649 if(name != null && !name.equals("")) {
650 if(wildcard) {
651 textQueryBuilder.add(new WildcardQuery(new Term("nameCache", name + "*")), Occur.MUST);
652 } else {
653 textQueryBuilder.add(queryFactory.newTermQuery("nameCache", name, false), Occur.MUST);
654 }
655 }
656
657 luceneSearch.setQuery(textQueryBuilder.build());
658
659 if(highlightFragments){
660 luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
661 }
662 return luceneSearch;
663 }
664
665 @Override
666 public List<SearchResult<TaxonName>> findByNameFuzzySearch(
667 String name,
668 float accuracy,
669 List<Language> languages,
670 boolean highlightFragments,
671 List<String> propertyPaths,
672 int maxNoOfResults) throws IOException, LuceneParseException {
673
674 logger.info("Name to fuzzy search for : " + name);
675 // parse the input name
676 NonViralNameParserImpl parser = new NonViralNameParserImpl();
677 INonViralName nvn = parser.parseFullName(name);
678 if(name != null && !name.equals("") && nvn == null) {
679 throw new LuceneParseException("Could not parse name " + name);
680 }
681 LuceneSearch luceneSearch = prepareFindByFuzzyNameSearch(null, nvn, accuracy, maxNoOfResults, languages, highlightFragments);
682
683 // --- execute search
684 TopDocs topDocs = luceneSearch.executeSearch(maxNoOfResults);
685
686
687 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
688 idFieldMap.put(CdmBaseType.NONVIRALNAME, "id");
689
690 // --- initialize taxa, highlight matches ....
691 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
692
693 @SuppressWarnings("rawtypes")
694 List<SearchResult<TaxonName>> searchResults = searchResultBuilder.createResultSet(
695 topDocs, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
696
697 return searchResults;
698
699 }
700
701 @Override
702 public List<DocumentSearchResult> findByNameFuzzySearch(
703 String name,
704 float accuracy,
705 List<Language> languages,
706 boolean highlightFragments,
707 int maxNoOfResults) throws IOException, LuceneParseException {
708
709 logger.info("Name to fuzzy search for : " + name);
710 // parse the input name
711 NonViralNameParserImpl parser = new NonViralNameParserImpl();
712 INonViralName nvn = parser.parseFullName(name);
713 if(name != null && !name.equals("") && nvn == null) {
714 throw new LuceneParseException("Could not parse name " + name);
715 }
716 LuceneSearch luceneSearch = prepareFindByFuzzyNameSearch(null, nvn, accuracy, maxNoOfResults, languages, highlightFragments);
717
718 // --- execute search
719 TopDocs topDocs = luceneSearch.executeSearch(maxNoOfResults);
720
721 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
722
723 // --- initialize taxa, highlight matches ....
724 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
725
726 @SuppressWarnings("rawtypes")
727 List<DocumentSearchResult> searchResults = searchResultBuilder.createResultSet(topDocs, luceneSearch.getHighlightFields());
728
729 return searchResults;
730 }
731
732 @Override
733 public List<DocumentSearchResult> findByFuzzyNameCacheSearch(
734 String name,
735 float accuracy,
736 List<Language> languages,
737 boolean highlightFragments,
738 int maxNoOfResults) throws IOException, LuceneParseException {
739
740 logger.info("Name to fuzzy search for : " + name);
741
742 LuceneSearch luceneSearch = prepareFindByFuzzyNameCacheSearch(null, name, accuracy, maxNoOfResults, languages, highlightFragments);
743
744 // --- execute search
745 TopDocs topDocs = luceneSearch.executeSearch(maxNoOfResults);
746 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
747
748 // --- initialize taxa, highlight matches ....
749 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
750
751 @SuppressWarnings("rawtypes")
752 List<DocumentSearchResult> searchResults = searchResultBuilder.createResultSet(topDocs, luceneSearch.getHighlightFields());
753
754 return searchResults;
755 }
756
757 @Override
758 public List<DocumentSearchResult> findByNameExactSearch(
759 String name,
760 boolean wildcard,
761 List<Language> languages,
762 boolean highlightFragments,
763 int maxNoOfResults) throws IOException, LuceneParseException {
764
765 logger.info("Name to exact search for : " + name);
766
767 LuceneSearch luceneSearch = prepareFindByExactNameSearch(null, name, wildcard, languages, highlightFragments);
768
769 // --- execute search
770
771
772 TopDocs topDocs = luceneSearch.executeSearch(maxNoOfResults);
773
774 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
775
776 // --- initialize taxa, highlight matches ....
777 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
778
779 @SuppressWarnings("rawtypes")
780 List<DocumentSearchResult> searchResults = searchResultBuilder.createResultSet(topDocs, luceneSearch.getHighlightFields());
781
782 return searchResults;
783 }
784
785 @Override
786 public Pager<NameRelationship> pageNameRelationships(TaxonName name, Direction direction, NameRelationshipType type, Integer pageSize,
787 Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
788 List<NameRelationship> results = listNameRelationships(name, direction, type, pageSize, pageNumber, orderHints, propertyPaths);
789 return new DefaultPagerImpl<NameRelationship>(pageNumber, results.size(), pageSize, results);
790 }
791
792 @Override
793 public List<NameRelationship> listFromNameRelationships(TaxonName name, NameRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
794 return listNameRelationships(name, Direction.relatedFrom, type, pageSize, pageNumber, orderHints, propertyPaths);
795 }
796
797 @Override
798 public Pager<NameRelationship> pageFromNameRelationships(TaxonName name, NameRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
799 List<NameRelationship> results = listNameRelationships(name, Direction.relatedFrom, type, pageSize, pageNumber, orderHints, propertyPaths);
800 return new DefaultPagerImpl<NameRelationship>(pageNumber, results.size(), pageSize, results);
801 }
802
803 @Override
804 public List<NameRelationship> listToNameRelationships(TaxonName name, NameRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
805 return listNameRelationships(name, Direction.relatedTo, type, pageSize, pageNumber, orderHints, propertyPaths);
806 }
807
808 @Override
809 public Pager<NameRelationship> pageToNameRelationships(TaxonName name, NameRelationshipType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
810 List<NameRelationship> results = listNameRelationships(name, Direction.relatedTo, type, pageSize, pageNumber, orderHints, propertyPaths);
811 return new DefaultPagerImpl<NameRelationship>(pageNumber, results.size(), pageSize, results);
812 }
813
814 @Override
815 public Pager<TypeDesignationBase> getTypeDesignations(TaxonName name, SpecimenTypeDesignationStatus status,
816 Integer pageSize, Integer pageNumber) {
817 return getTypeDesignations(name, status, pageSize, pageNumber, null);
818 }
819
820 @Override
821 public Pager<TypeDesignationBase> getTypeDesignations(TaxonName name, SpecimenTypeDesignationStatus status,
822 Integer pageSize, Integer pageNumber, List<String> propertyPaths){
823 long numberOfResults = dao.countTypeDesignations(name, status);
824
825 List<TypeDesignationBase> results = new ArrayList<>();
826 if(AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)) {
827 results = dao.getTypeDesignations(name, null, status, pageSize, pageNumber, propertyPaths);
828 }
829
830 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
831 }
832
833 /**
834 * FIXME Candidate for harmonization
835 * rename search
836 */
837 @Override
838 public Pager<TaxonName> searchNames(String uninomial,String infraGenericEpithet, String specificEpithet, String infraspecificEpithet, Rank rank, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
839 List<String> propertyPaths) {
840 long numberOfResults = dao.countNames(uninomial, infraGenericEpithet, specificEpithet, infraspecificEpithet, rank);
841
842 List<TaxonName> results = new ArrayList<>();
843 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
844 results = dao.searchNames(uninomial, infraGenericEpithet, specificEpithet, infraspecificEpithet, rank, pageSize, pageNumber, orderHints, propertyPaths);
845 }
846
847 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
848 }
849
850 @Override
851 public List<UuidAndTitleCache> getUuidAndTitleCacheOfNames(Integer limit, String pattern) {
852 return dao.getUuidAndTitleCacheOfNames(limit, pattern);
853 }
854
855 @Override
856 public Pager<TaxonName> findByName(Class<TaxonName> clazz, String queryString, MatchMode matchmode, List<Criterion> criteria,
857 Integer pageSize,Integer pageNumber, List<OrderHint> orderHints,List<String> propertyPaths) {
858 Long numberOfResults = dao.countByName(clazz, queryString, matchmode, criteria);
859
860 List<TaxonName> results = new ArrayList<>();
861 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
862 results = dao.findByName(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
863 }
864
865 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
866 }
867
868 @Override
869 public HomotypicalGroup findHomotypicalGroup(UUID uuid) {
870 return homotypicalGroupDao.findByUuid(uuid);
871 }
872
873 @Override
874 @Transactional(readOnly = false)
875 public UpdateResult updateCaches(Class<? extends TaxonName> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonName> cacheStrategy, IProgressMonitor monitor) {
876 if (clazz == null){
877 clazz = TaxonName.class;
878 }
879 return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);
880 }
881
882
883 @Override
884 public List<TaggedText> getTaggedName(UUID uuid) {
885 TaxonName taxonName = dao.load(uuid);
886 List<TaggedText> taggedName = taxonName.getTaggedName();
887 return taggedName;
888 }
889
890
891 public DeleteResult isDeletable(TaxonName name, DeleteConfiguratorBase config){
892 DeleteResult result = new DeleteResult();
893
894 NameDeletionConfigurator nameConfig = null;
895 if (config instanceof NameDeletionConfigurator){
896 nameConfig = (NameDeletionConfigurator) config;
897 }else{
898 result.addException(new Exception("The delete configurator should be of the type NameDeletionConfigurator."));
899 result.setError();
900 return result;
901 }
902
903 if (!name.getNameRelations().isEmpty() && !nameConfig.isRemoveAllNameRelationships()){
904 HomotypicalGroup homotypicalGroup = HibernateProxyHelper.deproxy(name.getHomotypicalGroup(), HomotypicalGroup.class);
905
906 if (!nameConfig.isIgnoreIsBasionymFor() && homotypicalGroup.getBasionyms().contains(name)){
907 result.addException(new Exception( "Name can't be deleted as it is a basionym."));
908 result.setAbort();
909 }
910 if (!nameConfig.isIgnoreHasBasionym() && (name.getBasionyms().size()>0)){
911 result.addException(new Exception( "Name can't be deleted as it has a basionym."));
912 result.setAbort();
913 }
914 Set<NameRelationship> relationships = name.getNameRelations();
915 for (NameRelationship rel: relationships){
916 if (!rel.getType().equals(NameRelationshipType.BASIONYM())){
917 result.addException(new Exception("Name can't be deleted as it is used in name relationship(s). Remove name relationships prior to deletion."));
918 result.setAbort();
919 break;
920 }
921 }
922 }
923
924 //concepts
925 if (! name.getTaxonBases().isEmpty()){
926 result.addException(new Exception("Name can't be deleted as it is used in concept(s). Remove or change concept prior to deletion."));
927 result.setAbort();
928 }
929
930 //hybrid relationships
931 if (name.isNonViral()){
932 INonViralName nvn = name;
933 Set<HybridRelationship> parentHybridRelations = nvn.getHybridParentRelations();
934 //Hibernate.initialize(parentHybridRelations);
935 if (! parentHybridRelations.isEmpty()){
936 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."));
937 result.setAbort();
938 }
939 }
940 Set<CdmBase> referencingObjects = genericDao.getReferencingObjectsForDeletion(name);
941 for (CdmBase referencingObject : referencingObjects){
942 //DerivedUnit?.storedUnder
943 if (referencingObject.isInstanceOf(DerivedUnit.class)){
944 String message = "Name can't be deleted as it is used as derivedUnit#storedUnder by %s. Remove 'stored under' prior to deleting this name";
945 message = String.format(message, CdmBase.deproxy(referencingObject, DerivedUnit.class).getTitleCache());
946 result.addException(new ReferencedObjectUndeletableException(message));
947 result.addRelatedObject(referencingObject);
948 result.setAbort();
949 }
950 //DescriptionElementSource#nameUsedInSource
951 else if (referencingObject.isInstanceOf(DescriptionElementSource.class)){
952 String message = "Name can't be deleted as it is used as descriptionElementSource#nameUsedInSource";
953 result.addException(new ReferencedObjectUndeletableException(message));
954 result.addRelatedObject(referencingObject);
955 result.setAbort();
956 }
957 //NameTypeDesignation#typeName
958 else if (referencingObject.isInstanceOf(NameTypeDesignation.class)){
959 NameTypeDesignation typeDesignation = HibernateProxyHelper.deproxy(referencingObject, NameTypeDesignation.class);
960
961 if (typeDesignation.getTypeName().equals(name)){
962 String message = "Name can't be deleted as it is used as a name type in a NameTypeDesignation";
963 result.addException(new ReferencedObjectUndeletableException(message));
964 result.addRelatedObject(referencingObject);
965 result.setAbort();
966 }
967 }
968 //DeterminationEvent#taxonName
969 else if (referencingObject.isInstanceOf(DeterminationEvent.class)){
970 String message = "Name can't be deleted as it is used as a determination event";
971 result.addException(new ReferencedObjectUndeletableException(message));
972 result.addRelatedObject(referencingObject);
973 result.setAbort();
974 }
975 }
976
977 //TODO inline references
978
979
980 if (!nameConfig.isIgnoreIsReplacedSynonymFor() && name.isReplacedSynonym()){
981 String message = "Name can't be deleted as it is a replaced synonym.";
982 result.addException(new Exception(message));
983 result.setAbort();
984 }
985 if (!nameConfig.isIgnoreHasReplacedSynonym() && (name.getReplacedSynonyms().size()>0)){
986 String message = "Name can't be deleted as it has a replaced synonym.";
987 result.addException(new Exception(message));
988 result.setAbort();
989 }
990 return result;
991
992 }
993
994
995 @Override
996 public DeleteResult isDeletable(UUID nameUUID, DeleteConfiguratorBase config){
997 TaxonName name = this.load(nameUUID);
998 return isDeletable(name, config);
999 }
1000
1001 @Override
1002 @Transactional(readOnly = true)
1003 public UpdateResult setAsGroupsBasionym(UUID nameUuid) {
1004 TaxonName name = dao.load(nameUuid);
1005 UpdateResult result = new UpdateResult();
1006 name.setAsGroupsBasionym();
1007 result.addUpdatedObject(name);
1008 return result;
1009
1010 }
1011
1012 @Override
1013 public List<HashMap<String,String>> getNameRecords(){
1014 return dao.getNameRecords();
1015
1016 }
1017
1018 }