Merge branch 'release/5.1.0'
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / TermServiceImpl.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.net.URI;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Enumeration;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Locale;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.UUID;
23
24 import org.apache.commons.lang.StringUtils;
25 import org.apache.log4j.Logger;
26 import org.springframework.beans.factory.annotation.Autowired;
27 import org.springframework.beans.factory.annotation.Qualifier;
28 import org.springframework.stereotype.Service;
29 import org.springframework.transaction.annotation.Transactional;
30
31 import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
32 import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
33 import eu.etaxonomy.cdm.api.service.config.TermDeletionConfigurator;
34 import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
35 import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
36 import eu.etaxonomy.cdm.api.service.pager.Pager;
37 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
38 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
39 import eu.etaxonomy.cdm.model.common.CdmBase;
40 import eu.etaxonomy.cdm.model.common.DefinedTermBase;
41 import eu.etaxonomy.cdm.model.common.Language;
42 import eu.etaxonomy.cdm.model.common.LanguageString;
43 import eu.etaxonomy.cdm.model.common.LanguageStringBase;
44 import eu.etaxonomy.cdm.model.common.Representation;
45 import eu.etaxonomy.cdm.model.common.TermBase;
46 import eu.etaxonomy.cdm.model.common.TermType;
47 import eu.etaxonomy.cdm.model.common.TermVocabulary;
48 import eu.etaxonomy.cdm.model.location.NamedArea;
49 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
50 import eu.etaxonomy.cdm.model.location.NamedAreaType;
51 import eu.etaxonomy.cdm.model.media.Media;
52 import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
53 import eu.etaxonomy.cdm.persistence.dao.common.ILanguageStringBaseDao;
54 import eu.etaxonomy.cdm.persistence.dao.common.ILanguageStringDao;
55 import eu.etaxonomy.cdm.persistence.dao.common.IRepresentationDao;
56 import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
57 import eu.etaxonomy.cdm.persistence.query.OrderHint;
58 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
59
60 @Service
61 @Transactional(readOnly = true)
62 public class TermServiceImpl extends IdentifiableServiceBase<DefinedTermBase,IDefinedTermDao> implements ITermService{
63 @SuppressWarnings("unused")
64 private static final Logger logger = Logger.getLogger(TermServiceImpl.class);
65
66 private ILanguageStringDao languageStringDao;
67
68 @Autowired
69 @Qualifier("langStrBaseDao")
70 private ILanguageStringBaseDao languageStringBaseDao;
71 private IRepresentationDao representationDao;
72
73 @Autowired
74 public void setLanguageStringDao(ILanguageStringDao languageStringDao) {
75 this.languageStringDao = languageStringDao;
76 }
77
78 @Autowired
79 public void setRepresentationDao(IRepresentationDao representationDao) {
80 this.representationDao = representationDao;
81 }
82
83 @Override
84 @Autowired
85 protected void setDao(IDefinedTermDao dao) {
86 this.dao = dao;
87 }
88
89 @Override
90 public <T extends DefinedTermBase> List<T> listByTermType(TermType termType, Integer limit, Integer start,
91 List<OrderHint> orderHints, List<String> propertyPaths) {
92 return dao.listByTermType(termType, limit, start, orderHints, propertyPaths);
93 }
94
95 @Override
96 public DefinedTermBase getByUri(URI uri) {
97 return dao.findByUri(uri);
98 }
99
100 @Override
101 public Language getLanguageByIso(String iso639) {
102 return dao.getLanguageByIso(iso639);
103 }
104
105 @Override
106 public Language getLanguageByLabel(String label) {
107 return Language.getLanguageByLabel(label);
108 }
109
110 @Override
111 public List<Language> getLanguagesByLocale(Enumeration<Locale> locales){
112 return dao.getLanguagesByLocale(locales);
113 }
114
115 @Override
116 public <TERM extends DefinedTermBase> TERM findByIdInVocabulary(String id, UUID vocabularyUuid, Class<TERM> clazz) throws IllegalArgumentException {
117 List<TERM> list = dao.getDefinedTermByIdInVocabulary(id, vocabularyUuid, clazz, null, null);
118 if (list.isEmpty()){
119 return null;
120 }else if (list.size() == 1){
121 return list.get(0);
122 }else{
123 String message = "There is more then 1 (%d) term with the same id in vocabulary. This is forbidden. Check the state of your database.";
124 throw new IllegalStateException(String.format(message, list.size()));
125 }
126 }
127
128
129 @Override
130 public NamedArea getAreaByTdwgAbbreviation(String tdwgAbbreviation) {
131 if (StringUtils.isBlank(tdwgAbbreviation)){ //TDWG areas should always have a label
132 return null;
133 }
134 List<NamedArea> list = dao.getDefinedTermByIdInVocabulary(tdwgAbbreviation, NamedArea.uuidTdwgAreaVocabulary, NamedArea.class, null, null);
135 if (list.isEmpty()){
136 return null;
137 }else if (list.size() == 1){
138 return list.get(0);
139 }else{
140 String message = "There is more then 1 (%d) TDWG area with the same abbreviated label. This is forbidden. Check the state of your database.";
141 throw new IllegalStateException(String.format(message, list.size()));
142 }
143 }
144
145 @Override
146 public <T extends DefinedTermBase> Pager<T> getGeneralizationOf(T definedTerm, Integer pageSize, Integer pageNumber) {
147 long numberOfResults = dao.countGeneralizationOf(definedTerm);
148
149 List<T> results = new ArrayList<>();
150 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
151 results = dao.getGeneralizationOf(definedTerm, pageSize, pageNumber);
152 }
153
154 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
155 }
156
157 @Override
158 public <T extends DefinedTermBase> Pager<T> getIncludes(Collection<T> definedTerms, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
159 long numberOfResults = dao.countIncludes(definedTerms);
160
161 List<T> results = new ArrayList<>();
162 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
163 results = dao.getIncludes(definedTerms, pageSize, pageNumber,propertyPaths);
164 }
165
166 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
167 }
168
169 @Override
170 public Pager<Media> getMedia(DefinedTermBase definedTerm, Integer pageSize, Integer pageNumber) {
171 long numberOfResults = dao.countMedia(definedTerm);
172
173 List<Media> results = new ArrayList<>();
174 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
175 results = dao.getMedia(definedTerm, pageSize, pageNumber);
176 }
177
178 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
179 }
180
181 @Override
182 public <T extends DefinedTermBase> Pager<T> getPartOf(Set<T> definedTerms,Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
183 long numberOfResults = dao.countPartOf(definedTerms);
184
185 List<T> results = new ArrayList<>();
186 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
187 results = dao.getPartOf(definedTerms, pageSize, pageNumber, propertyPaths);
188 }
189
190 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
191 }
192
193 @Override
194 public Pager<NamedArea> list(NamedAreaLevel level, NamedAreaType type, Integer pageSize, Integer pageNumber,
195 List<OrderHint> orderHints, List<String> propertyPaths) {
196 long numberOfResults = dao.count(level, type);
197
198 List<NamedArea> results = new ArrayList<>();
199 if (numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
200 results = dao.list(level, type, pageSize, pageNumber, orderHints, propertyPaths);
201 }
202
203 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
204 }
205
206 @Override
207 public <T extends DefinedTermBase> Pager<T> findByRepresentationText(String label, Class<T> clazz, Integer pageSize, Integer pageNumber) {
208 long numberOfResults = dao.countDefinedTermByRepresentationText(label,clazz);
209
210 List<T> results = new ArrayList<>();
211 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
212 results = dao.getDefinedTermByRepresentationText(label, clazz, pageSize, pageNumber);
213 }
214
215 return new DefaultPagerImpl<T>(pageNumber, numberOfResults, pageSize, results);
216 }
217
218 @Override
219 public <T extends DefinedTermBase> Pager<T> findByRepresentationAbbreviation(String abbrev, Class<T> clazz, Integer pageSize, Integer pageNumber) {
220 long numberOfResults = dao.countDefinedTermByRepresentationAbbrev(abbrev,clazz);
221
222 List<T> results = new ArrayList<>();
223 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
224 results = dao.getDefinedTermByRepresentationAbbrev(abbrev, clazz, pageSize, pageNumber);
225 }
226
227 return new DefaultPagerImpl<T>(pageNumber, numberOfResults, pageSize, results);
228 }
229
230 @Override
231 public List<LanguageString> getAllLanguageStrings(int limit, int start) {
232 return languageStringDao.list(limit, start);
233 }
234
235 @Override
236 public List<Representation> getAllRepresentations(int limit, int start) {
237 return representationDao.list(limit,start);
238 }
239
240 @Override
241 public UUID saveLanguageData(LanguageStringBase languageData) {
242 return languageStringBaseDao.save(languageData).getUuid();
243 }
244
245
246 /** @deprecated use {@link #delete(DefinedTermBase, TermDeletionConfigurator)} instead
247 * to allow DeleteResult return type*/
248 @Override
249 @Deprecated
250 public DeleteResult delete(DefinedTermBase term){
251 DeleteResult result = new DeleteResult();
252
253 TermDeletionConfigurator defaultConfig = new TermDeletionConfigurator();
254 result = delete(term, defaultConfig);
255 return result;
256 }
257
258 @Override
259 @Deprecated
260 @Transactional(readOnly = false)
261 public DeleteResult delete(UUID termUuid){
262 DeleteResult result = new DeleteResult();
263
264 TermDeletionConfigurator defaultConfig = new TermDeletionConfigurator();
265 result = delete(dao.load(termUuid), defaultConfig);
266 return result;
267 }
268
269 @Override
270 public DeleteResult delete(DefinedTermBase term, TermDeletionConfigurator config){
271 if (config == null){
272 config = new TermDeletionConfigurator();
273 }
274 // boolean isInternal = config.isInternal();
275
276 Set<DefinedTermBase> termsToSave = new HashSet<DefinedTermBase>();
277
278 DeleteResult result = isDeletable(term.getUuid(), config);
279 //CdmBase.deproxy(dao.merge(term), DefinedTermBase.class);
280 try {
281 //generalization of
282 Set<DefinedTermBase> specificTerms = term.getGeneralizationOf();
283 if (specificTerms.size()>0){
284 if (config.isDeleteGeneralizationOfRelations()){
285 DefinedTermBase generalTerm = term.getKindOf();
286 for (DefinedTermBase specificTerm: specificTerms){
287 term.removeGeneralization(specificTerm);
288 if (generalTerm != null){
289 generalTerm.addGeneralizationOf(specificTerm);
290 termsToSave.add(generalTerm);
291 }
292 }
293 }else{
294 //TODO Exception type
295 String message = "This term has specifing terms. Move or delete specifiing terms prior to delete or change delete configuration.";
296 result.addRelatedObjects(specificTerms);
297 result.setAbort();
298 Exception ex = new DataChangeNoRollbackException(message);
299 result.addException(ex);
300 }
301 }
302
303 //kind of
304 DefinedTermBase generalTerm = term.getKindOf();
305 if (generalTerm != null){
306 if (config.isDeleteKindOfRelations()){
307 generalTerm.removeGeneralization(term);
308 }else{
309 //TODO Exception type
310 String message = "This term is kind of another term. Move or delete kind of relationship prior to delete or change delete configuration.";
311 result.addRelatedObject(generalTerm);
312 result.setAbort();
313 DataChangeNoRollbackException ex = new DataChangeNoRollbackException(message);
314 result.addException(ex);
315 throw ex;
316 }
317 }
318
319 //part of
320 DefinedTermBase parentTerm = term.getPartOf();
321 if (parentTerm != null){
322 if (! config.isDeletePartOfRelations()){
323 //TODO Exception type
324 String message = "This term is included in another term. Remove from parent term prior to delete or change delete configuration.";
325 result.addRelatedObject(parentTerm);
326 result.setAbort();
327 DataChangeNoRollbackException ex = new DataChangeNoRollbackException(message);
328 result.addException(ex);
329 }
330 }
331
332
333 //included in
334 Set<DefinedTermBase> includedTerms = term.getIncludes();
335 if (includedTerms.size()> 0){
336 // if (config.isDeleteIncludedTerms()){
337 // for (DefinedTermBase includedTerm: includedTerms){
338 // config.setCheck(true);
339 // DeleteResult includedResult = this.delete(includedTerm, config);
340 //// config.setCheck(isCheck);
341 // result.includeResult(includedResult);
342 // }
343 // }else
344 if (config.isDeleteIncludedRelations()){
345 DefinedTermBase parent = term.getPartOf();
346 for (DefinedTermBase includedTerm: includedTerms){
347 term.removeIncludes(includedTerm);
348 if (parent != null){
349 parent.addIncludes(includedTerm);
350 termsToSave.add(parent);
351 }
352 }
353 }else{
354 //TODO Exception type
355 String message = "This term includes other terms. Move or delete included terms prior to delete or change delete configuration.";
356 result.addRelatedObjects(includedTerms);
357 result.setAbort();
358 Exception ex = new DataChangeNoRollbackException(message);
359 result.addException(ex);
360 }
361 }
362
363 //part of
364 if (parentTerm != null){
365 if (config.isDeletePartOfRelations()){
366 parentTerm.removeIncludes(term);
367 termsToSave.add(parentTerm);
368 }else{
369 //handelede before "included in"
370 }
371 }
372
373 // relatedObjects;
374
375
376 if (result.isOk()){
377 TermVocabulary voc = term.getVocabulary();
378 if (voc!= null){
379 voc.removeTerm(term);
380 }
381 //TODO save voc
382 if (true /*!config.isInternal()*/){
383 dao.delete(term);
384 result.addDeletedObject(term);
385 dao.saveOrUpdateAll(termsToSave);
386 // for (DeleteResult.PersistPair persistPair : result.getObjectsToDelete()){
387 // persistPair.dao.delete(persistPair.objectToPersist);
388 // }
389 // for (DeleteResult.PersistPair persistPair : result.getObjectsToSave()){
390 // persistPair.dao.saveOrUpdate(persistPair.objectToPersist);
391 // }
392
393 }
394 }
395 } catch (DataChangeNoRollbackException e) {
396 result.setStatus(Status.ERROR);
397 }
398 return result;
399 }
400
401 @Override
402 @Transactional(readOnly = false)
403 public DeleteResult delete(UUID termUuid, TermDeletionConfigurator config){
404 return delete(dao.load(termUuid), config);
405 }
406
407 @Override
408 @Transactional(readOnly = false)
409 public void updateTitleCache(Class<? extends DefinedTermBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<DefinedTermBase> cacheStrategy, IProgressMonitor monitor) {
410 //TODO shouldnt this be TermBase instead of DefinedTermBase
411 if (clazz == null){
412 clazz = DefinedTermBase.class;
413 }
414 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
415 }
416
417 @Override
418 public DeleteResult isDeletable(UUID termUuid, DeleteConfiguratorBase config){
419 DeleteResult result = new DeleteResult();
420 TermBase term = load(termUuid);
421 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(term);
422 if (references != null){
423 result.addRelatedObjects(references);
424 Iterator<CdmBase> iterator = references.iterator();
425 CdmBase ref;
426 while (iterator.hasNext()){
427 ref = iterator.next();
428 if (ref instanceof TermVocabulary){
429 result.getRelatedObjects().remove(ref);
430 }else{
431
432 String message = "An object of " + ref.getClass().getName() + " with ID " + ref.getId() + " is referencing the object" ;
433 result.addException(new ReferencedObjectUndeletableException(message));
434 result.setAbort();
435 }
436
437 }
438 }
439 return result;
440 }
441
442 @Override
443 @Transactional(readOnly = false)
444 public Map<UUID, Representation> saveOrUpdateRepresentations(Collection<Representation> representations){
445 return representationDao.saveOrUpdateAll(representations);
446 }
447
448 @Override
449 @Transactional(readOnly = true)
450 public List<UuidAndTitleCache<NamedArea>> getUuidAndTitleCache(List<TermVocabulary> vocs, Integer limit, String pattern, Language lang) {
451 List<NamedArea> areas = dao.getUuidAndTitleCache(vocs, limit, pattern);
452
453 List<UuidAndTitleCache<NamedArea>> result = new ArrayList();
454 UuidAndTitleCache<NamedArea> uuidAndTitleCache;
455 for (NamedArea area: areas){
456 uuidAndTitleCache = new UuidAndTitleCache<>(area.getUuid(), area.getId(), area.labelWithLevel(area, lang));
457 result.add(uuidAndTitleCache);
458 }
459
460 return result;
461 }
462
463
464 }