2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.cdm
.api
.service
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collection
;
15 import java
.util
.Enumeration
;
16 import java
.util
.HashSet
;
17 import java
.util
.List
;
18 import java
.util
.Locale
;
21 import java
.util
.UUID
;
23 import org
.apache
.commons
.lang
.StringUtils
;
24 import org
.apache
.log4j
.Logger
;
25 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
26 import org
.springframework
.beans
.factory
.annotation
.Qualifier
;
27 import org
.springframework
.stereotype
.Service
;
28 import org
.springframework
.transaction
.annotation
.Transactional
;
30 import eu
.etaxonomy
.cdm
.api
.service
.UpdateResult
.Status
;
31 import eu
.etaxonomy
.cdm
.api
.service
.config
.DeleteConfiguratorBase
;
32 import eu
.etaxonomy
.cdm
.api
.service
.config
.TermDeletionConfigurator
;
33 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
34 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
35 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
36 import eu
.etaxonomy
.cdm
.api
.service
.pager
.impl
.DefaultPagerImpl
;
37 import eu
.etaxonomy
.cdm
.common
.monitor
.IProgressMonitor
;
38 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
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
.OrderedTermBase
;
45 import eu
.etaxonomy
.cdm
.model
.common
.OrderedTermVocabulary
;
46 import eu
.etaxonomy
.cdm
.model
.common
.Representation
;
47 import eu
.etaxonomy
.cdm
.model
.common
.TermType
;
48 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
49 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
50 import eu
.etaxonomy
.cdm
.model
.location
.NamedAreaLevel
;
51 import eu
.etaxonomy
.cdm
.model
.location
.NamedAreaType
;
52 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
53 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.IDefinedTermDao
;
54 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.ILanguageStringBaseDao
;
55 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.ILanguageStringDao
;
56 import eu
.etaxonomy
.cdm
.persistence
.dao
.common
.IRepresentationDao
;
57 import eu
.etaxonomy
.cdm
.persistence
.dto
.TermDto
;
58 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
59 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
60 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
63 @Transactional(readOnly
= true)
64 public class TermServiceImpl
extends IdentifiableServiceBase
<DefinedTermBase
,IDefinedTermDao
> implements ITermService
{
65 @SuppressWarnings("unused")
66 private static final Logger logger
= Logger
.getLogger(TermServiceImpl
.class);
68 private ILanguageStringDao languageStringDao
;
71 private IVocabularyService vocabularyService
;
74 @Qualifier("langStrBaseDao")
75 private ILanguageStringBaseDao languageStringBaseDao
;
76 private IRepresentationDao representationDao
;
79 public void setLanguageStringDao(ILanguageStringDao languageStringDao
) {
80 this.languageStringDao
= languageStringDao
;
84 public void setRepresentationDao(IRepresentationDao representationDao
) {
85 this.representationDao
= representationDao
;
90 protected void setDao(IDefinedTermDao dao
) {
95 public <T
extends DefinedTermBase
> List
<T
> listByTermType(TermType termType
, Integer limit
, Integer start
,
96 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
97 return dao
.listByTermType(termType
, limit
, start
, orderHints
, propertyPaths
);
101 public DefinedTermBase
getByUri(URI uri
) {
102 return dao
.findByUri(uri
);
106 public Language
getLanguageByIso(String iso639
) {
107 return dao
.getLanguageByIso(iso639
);
111 public Language
getLanguageByLabel(String label
) {
112 return Language
.getLanguageByLabel(label
);
116 public List
<Language
> getLanguagesByLocale(Enumeration
<Locale
> locales
){
117 return dao
.getLanguagesByLocale(locales
);
121 public <TERM
extends DefinedTermBase
> TERM
findByIdInVocabulary(String id
, UUID vocabularyUuid
, Class
<TERM
> clazz
) throws IllegalArgumentException
{
122 List
<TERM
> list
= dao
.getDefinedTermByIdInVocabulary(id
, vocabularyUuid
, clazz
, null, null);
125 }else if (list
.size() == 1){
128 String message
= "There is more then 1 (%d) term with the same id in vocabulary. This is forbidden. Check the state of your database.";
129 throw new IllegalStateException(String
.format(message
, list
.size()));
135 public NamedArea
getAreaByTdwgAbbreviation(String tdwgAbbreviation
) {
136 if (StringUtils
.isBlank(tdwgAbbreviation
)){ //TDWG areas should always have a label
139 List
<NamedArea
> list
= dao
.getDefinedTermByIdInVocabulary(tdwgAbbreviation
, NamedArea
.uuidTdwgAreaVocabulary
, NamedArea
.class, null, null);
142 }else if (list
.size() == 1){
145 String message
= "There is more then 1 (%d) TDWG area with the same abbreviated label. This is forbidden. Check the state of your database.";
146 throw new IllegalStateException(String
.format(message
, list
.size()));
151 public <T
extends DefinedTermBase
> Pager
<T
> getGeneralizationOf(T definedTerm
, Integer pageSize
, Integer pageNumber
) {
152 long numberOfResults
= dao
.countGeneralizationOf(definedTerm
);
154 List
<T
> results
= new ArrayList
<>();
155 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
156 results
= dao
.getGeneralizationOf(definedTerm
, pageSize
, pageNumber
);
159 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
163 public <T
extends DefinedTermBase
> Pager
<T
> getIncludes(Collection
<T
> definedTerms
, Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
164 long numberOfResults
= dao
.countIncludes(definedTerms
);
166 List
<T
> results
= new ArrayList
<>();
167 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
168 results
= dao
.getIncludes(definedTerms
, pageSize
, pageNumber
,propertyPaths
);
171 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
175 public Pager
<Media
> getMedia(DefinedTermBase definedTerm
, Integer pageSize
, Integer pageNumber
) {
176 long numberOfResults
= dao
.countMedia(definedTerm
);
178 List
<Media
> results
= new ArrayList
<>();
179 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
180 results
= dao
.getMedia(definedTerm
, pageSize
, pageNumber
);
183 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
187 public <T
extends DefinedTermBase
> Pager
<T
> getPartOf(Set
<T
> definedTerms
,Integer pageSize
, Integer pageNumber
, List
<String
> propertyPaths
) {
188 long numberOfResults
= dao
.countPartOf(definedTerms
);
190 List
<T
> results
= new ArrayList
<>();
191 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
192 results
= dao
.getPartOf(definedTerms
, pageSize
, pageNumber
, propertyPaths
);
195 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
199 public Pager
<NamedArea
> list(NamedAreaLevel level
, NamedAreaType type
, Integer pageSize
, Integer pageNumber
,
200 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
201 long numberOfResults
= dao
.count(level
, type
);
203 List
<NamedArea
> results
= new ArrayList
<>();
204 if (numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
205 results
= dao
.list(level
, type
, pageSize
, pageNumber
, orderHints
, propertyPaths
);
208 return new DefaultPagerImpl
<>(pageNumber
, numberOfResults
, pageSize
, results
);
212 public <T
extends DefinedTermBase
> Pager
<T
> findByRepresentationText(String label
, Class
<T
> clazz
, Integer pageSize
, Integer pageNumber
) {
213 long numberOfResults
= dao
.countDefinedTermByRepresentationText(label
,clazz
);
215 List
<T
> results
= new ArrayList
<>();
216 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
217 results
= dao
.getDefinedTermByRepresentationText(label
, clazz
, pageSize
, pageNumber
);
220 return new DefaultPagerImpl
<T
>(pageNumber
, numberOfResults
, pageSize
, results
);
224 public <T
extends DefinedTermBase
> Pager
<T
> findByRepresentationAbbreviation(String abbrev
, Class
<T
> clazz
, Integer pageSize
, Integer pageNumber
) {
225 long numberOfResults
= dao
.countDefinedTermByRepresentationAbbrev(abbrev
,clazz
);
227 List
<T
> results
= new ArrayList
<>();
228 if(numberOfResults
> 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
229 results
= dao
.getDefinedTermByRepresentationAbbrev(abbrev
, clazz
, pageSize
, pageNumber
);
232 return new DefaultPagerImpl
<T
>(pageNumber
, numberOfResults
, pageSize
, results
);
236 public List
<LanguageString
> getAllLanguageStrings(int limit
, int start
) {
237 return languageStringDao
.list(limit
, start
);
241 public List
<Representation
> getAllRepresentations(int limit
, int start
) {
242 return representationDao
.list(limit
,start
);
246 public UUID
saveLanguageData(LanguageStringBase languageData
) {
247 return languageStringBaseDao
.save(languageData
).getUuid();
251 /** @deprecated use {@link #delete(DefinedTermBase, TermDeletionConfigurator)} instead
252 * to allow DeleteResult return type*/
255 public DeleteResult
delete(DefinedTermBase term
){
256 DeleteResult result
= new DeleteResult();
258 TermDeletionConfigurator defaultConfig
= new TermDeletionConfigurator();
259 result
= delete(term
, defaultConfig
);
265 @Transactional(readOnly
= false)
266 public DeleteResult
delete(UUID termUuid
){
267 DeleteResult result
= new DeleteResult();
269 TermDeletionConfigurator defaultConfig
= new TermDeletionConfigurator();
270 result
= delete(dao
.load(termUuid
), defaultConfig
);
275 public DeleteResult
delete(DefinedTermBase term
, TermDeletionConfigurator config
){
277 config
= new TermDeletionConfigurator();
279 Set
<DefinedTermBase
> termsToSave
= new HashSet
<DefinedTermBase
>();
281 DeleteResult result
= isDeletable(term
.getUuid(), config
);
284 Set
<DefinedTermBase
> specificTerms
= term
.getGeneralizationOf();
285 if (specificTerms
.size()>0){
286 if (config
.isDeleteGeneralizationOfRelations()){
287 DefinedTermBase generalTerm
= term
.getKindOf();
288 for (DefinedTermBase specificTerm
: specificTerms
){
289 term
.removeGeneralization(specificTerm
);
290 if (generalTerm
!= null){
291 generalTerm
.addGeneralizationOf(specificTerm
);
292 termsToSave
.add(generalTerm
);
296 //TODO Exception type
297 String message
= "This term has specifing terms. Move or delete specifiing terms prior to delete or change delete configuration.";
298 result
.addRelatedObjects(specificTerms
);
300 Exception ex
= new DataChangeNoRollbackException(message
);
301 result
.addException(ex
);
306 DefinedTermBase generalTerm
= term
.getKindOf();
307 if (generalTerm
!= null){
308 if (config
.isDeleteKindOfRelations()){
309 generalTerm
.removeGeneralization(term
);
311 //TODO Exception type
312 String message
= "This term is kind of another term. Move or delete kind of relationship prior to delete or change delete configuration.";
313 result
.addRelatedObject(generalTerm
);
315 DataChangeNoRollbackException ex
= new DataChangeNoRollbackException(message
);
316 result
.addException(ex
);
322 DefinedTermBase parentTerm
= term
.getPartOf();
323 if (parentTerm
!= null){
324 if (! config
.isDeletePartOfRelations()){
325 //TODO Exception type
326 String message
= "This term is included in another term. Remove from parent term prior to delete or change delete configuration.";
327 result
.addRelatedObject(parentTerm
);
329 DataChangeNoRollbackException ex
= new DataChangeNoRollbackException(message
);
330 result
.addException(ex
);
336 Set
<DefinedTermBase
> includedTerms
= term
.getIncludes();
337 if (includedTerms
.size()> 0){
338 if (config
.isDeleteIncludedRelations()){
339 DefinedTermBase parent
= term
.getPartOf();
340 for (DefinedTermBase includedTerm
: includedTerms
){
341 term
.removeIncludes(includedTerm
);
343 parent
.addIncludes(includedTerm
);
344 termsToSave
.add(parent
);
348 //TODO Exception type
349 String message
= "This term includes other terms. Move or delete included terms prior to delete or change delete configuration.";
350 result
.addRelatedObjects(includedTerms
);
352 Exception ex
= new DataChangeNoRollbackException(message
);
353 result
.addException(ex
);
358 if (parentTerm
!= null){
359 if (config
.isDeletePartOfRelations()){
360 parentTerm
.removeIncludes(term
);
361 termsToSave
.add(parentTerm
);
363 //handled before "included in"
368 TermVocabulary voc
= term
.getVocabulary();
370 voc
.removeTerm(term
);
375 result
.addDeletedObject(term
);
376 dao
.saveOrUpdateAll(termsToSave
);
379 } catch (DataChangeNoRollbackException e
) {
380 result
.setStatus(Status
.ERROR
);
386 @Transactional(readOnly
= false)
387 public DeleteResult
delete(UUID termUuid
, TermDeletionConfigurator config
){
388 return delete(dao
.load(termUuid
), config
);
392 @Transactional(readOnly
= false)
393 public void updateTitleCache(Class
<?
extends DefinedTermBase
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<DefinedTermBase
> cacheStrategy
, IProgressMonitor monitor
) {
394 //TODO shouldnt this be TermBase instead of DefinedTermBase
396 clazz
= DefinedTermBase
.class;
398 super.updateTitleCacheImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
402 public DeleteResult
isDeletable(UUID termUuid
, DeleteConfiguratorBase config
){
403 TermDeletionConfigurator termConfig
= null;
404 if(config
instanceof TermDeletionConfigurator
){
405 termConfig
= (TermDeletionConfigurator
) config
;
407 DeleteResult result
= new DeleteResult();
408 DefinedTermBase term
= load(termUuid
);
410 if(termConfig
!=null){
412 Set
<DefinedTermBase
> specificTerms
= term
.getGeneralizationOf();
413 if (!specificTerms
.isEmpty() && !termConfig
.isDeleteGeneralizationOfRelations()){
414 result
.getRelatedObjects().addAll(specificTerms
);
418 DefinedTermBase generalTerm
= term
.getKindOf();
419 if (generalTerm
!= null && !termConfig
.isDeleteKindOfRelations()){
421 result
.getRelatedObjects().add(generalTerm
);
424 DefinedTermBase parentTerm
= term
.getPartOf();
425 if (parentTerm
!= null && !termConfig
.isDeletePartOfRelations()){
427 result
.getRelatedObjects().add(parentTerm
);
430 Set
<DefinedTermBase
> includedTerms
= term
.getIncludes();
431 if (!includedTerms
.isEmpty() && !termConfig
.isDeleteIncludedRelations()){
433 result
.getRelatedObjects().addAll(includedTerms
);
437 //gather remaining referenced objects
438 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(term
);
439 for (CdmBase cdmBase
: references
) {
440 if(result
.getRelatedObjects().contains(cdmBase
)){
441 result
.getRelatedObjects().remove(cdmBase
);
444 result
.getRelatedObjects().forEach(relatedObject
->{
445 String message
= "An object of " + relatedObject
.getClass().getName() + " with ID " + relatedObject
.getId() + " is referencing the object" ;
446 result
.addException(new ReferencedObjectUndeletableException(message
));
453 @Transactional(readOnly
= false)
454 public Map
<UUID
, Representation
> saveOrUpdateRepresentations(Collection
<Representation
> representations
){
455 return representationDao
.saveOrUpdateAll(representations
);
459 @Transactional(readOnly
= true)
460 public List
<UuidAndTitleCache
<NamedArea
>> getUuidAndTitleCache(List
<TermVocabulary
> vocs
, Integer limit
, String pattern
, Language lang
) {
461 List
<NamedArea
> areas
= dao
.getUuidAndTitleCache(vocs
, limit
, pattern
);
463 List
<UuidAndTitleCache
<NamedArea
>> result
= new ArrayList();
464 UuidAndTitleCache
<NamedArea
> uuidAndTitleCache
;
465 for (NamedArea area
: areas
){
466 uuidAndTitleCache
= new UuidAndTitleCache
<>(area
.getUuid(), area
.getId(), area
.labelWithLevel(area
, lang
));
467 result
.add(uuidAndTitleCache
);
474 public Collection
<TermDto
> getIncludesAsDto(
475 TermDto parentTerm
) {
476 return dao
.getIncludesAsDto(parentTerm
);
480 public Collection
<TermDto
> getKindOfsAsDto(
481 TermDto parentTerm
) {
482 return dao
.getKindOfsAsDto(parentTerm
);
485 @Transactional(readOnly
= false)
487 public void moveTerm(TermDto termDto
, UUID parentUUID
) {
488 moveTerm(termDto
, parentUUID
, null);
491 @SuppressWarnings({ "rawtypes", "unchecked" })
492 @Transactional(readOnly
= false)
494 public void moveTerm(TermDto termDto
, UUID parentUuid
, TermMovePosition termMovePosition
) {
495 boolean isKindOf
= termDto
.getKindOfUuid()!=null && termDto
.getKindOfUuid().equals(parentUuid
);
496 TermVocabulary vocabulary
= HibernateProxyHelper
.deproxy(vocabularyService
.load(termDto
.getVocabularyUuid()));
497 DefinedTermBase parent
= HibernateProxyHelper
.deproxy(dao
.load(parentUuid
));
499 //new parent is a vocabulary
500 TermVocabulary parentVocabulary
= HibernateProxyHelper
.deproxy(vocabularyService
.load(parentUuid
));
501 DefinedTermBase term
= HibernateProxyHelper
.deproxy(dao
.load(termDto
.getUuid()));
502 if(parentVocabulary
!=null){
503 term
.setKindOf(null);
504 term
.setPartOf(null);
506 vocabulary
.removeTerm(term
);
507 parentVocabulary
.addTerm(term
);
509 vocabularyService
.saveOrUpdate(parentVocabulary
);
512 DefinedTermBase term
= HibernateProxyHelper
.deproxy(dao
.load(termDto
.getUuid()));
513 //new parent is a term
514 if(parent
.isInstanceOf(OrderedTermBase
.class)
515 && term
.isInstanceOf(OrderedTermBase
.class)
516 && termMovePosition
!=null
517 && HibernateProxyHelper
.deproxy(parent
, OrderedTermBase
.class).getVocabulary().isInstanceOf(OrderedTermVocabulary
.class)) {
518 //new parent is an ordered term
519 OrderedTermBase orderedTerm
= HibernateProxyHelper
.deproxy(term
, OrderedTermBase
.class);
520 OrderedTermBase targetOrderedDefinedTerm
= HibernateProxyHelper
.deproxy(parent
, OrderedTermBase
.class);
521 OrderedTermVocabulary otVoc
= HibernateProxyHelper
.deproxy(targetOrderedDefinedTerm
.getVocabulary(), OrderedTermVocabulary
.class);
522 if(termMovePosition
.equals(TermMovePosition
.BEFORE
)) {
523 orderedTerm
.getVocabulary().removeTerm(orderedTerm
);
524 otVoc
.addTermAbove(orderedTerm
, targetOrderedDefinedTerm
);
525 if (targetOrderedDefinedTerm
.getPartOf() != null){
526 targetOrderedDefinedTerm
.getPartOf().addIncludes(orderedTerm
);
529 else if(termMovePosition
.equals(TermMovePosition
.AFTER
)) {
530 orderedTerm
.getVocabulary().removeTerm(orderedTerm
);
531 otVoc
.addTermBelow(orderedTerm
, targetOrderedDefinedTerm
);
532 if (targetOrderedDefinedTerm
.getPartOf() != null){
533 targetOrderedDefinedTerm
.getPartOf().addIncludes(orderedTerm
);
536 else if(termMovePosition
.equals(TermMovePosition
.ON
)) {
537 orderedTerm
.getVocabulary().removeTerm(orderedTerm
);
538 targetOrderedDefinedTerm
.addIncludes(orderedTerm
);
539 targetOrderedDefinedTerm
.getVocabulary().addTerm(orderedTerm
);
543 vocabulary
.removeTerm(term
);
545 parent
.addGeneralizationOf(term
);
548 parent
.addIncludes(term
);
550 parent
.getVocabulary().addTerm(term
);
552 vocabularyService
.saveOrUpdate(parent
.getVocabulary());
556 @SuppressWarnings({ "rawtypes", "unchecked" })
557 @Transactional(readOnly
= false)
559 public TermDto
addNewTerm(TermType termType
, UUID parentUUID
, boolean isKindOf
) {
560 DefinedTermBase term
= termType
.getEmptyDefinedTermBase();
562 DefinedTermBase parent
= dao
.load(parentUUID
);
564 parent
.addGeneralizationOf(term
);
567 parent
.addIncludes(term
);
569 parent
.getVocabulary().addTerm(term
);
570 dao
.saveOrUpdate(parent
);
571 return TermDto
.fromTerm(term
, true);
574 public enum TermMovePosition
{