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.
9 package eu
.etaxonomy
.cdm
.api
.service
;
11 import java
.util
.ArrayList
;
12 import java
.util
.HashMap
;
13 import java
.util
.HashSet
;
14 import java
.util
.List
;
16 import java
.util
.Map
.Entry
;
18 import java
.util
.UUID
;
20 import org
.apache
.logging
.log4j
.LogManager
;
21 import org
.apache
.logging
.log4j
.Logger
;
22 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
23 import org
.springframework
.stereotype
.Service
;
24 import org
.springframework
.transaction
.annotation
.Transactional
;
26 import eu
.etaxonomy
.cdm
.api
.service
.config
.NodeDeletionConfigurator
.ChildHandling
;
27 import eu
.etaxonomy
.cdm
.api
.service
.config
.TermNodeDeletionConfigurator
;
28 import eu
.etaxonomy
.cdm
.api
.service
.exception
.DataChangeNoRollbackException
;
29 import eu
.etaxonomy
.cdm
.api
.service
.exception
.ReferencedObjectUndeletableException
;
30 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
31 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
32 import eu
.etaxonomy
.cdm
.model
.description
.Character
;
33 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
34 import eu
.etaxonomy
.cdm
.model
.description
.FeatureState
;
35 import eu
.etaxonomy
.cdm
.model
.description
.MeasurementUnit
;
36 import eu
.etaxonomy
.cdm
.model
.description
.State
;
37 import eu
.etaxonomy
.cdm
.model
.description
.StatisticalMeasure
;
38 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
39 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTermBase
;
40 import eu
.etaxonomy
.cdm
.model
.term
.Representation
;
41 import eu
.etaxonomy
.cdm
.model
.term
.TermNode
;
42 import eu
.etaxonomy
.cdm
.model
.term
.TermTree
;
43 import eu
.etaxonomy
.cdm
.model
.term
.TermType
;
44 import eu
.etaxonomy
.cdm
.model
.term
.TermVocabulary
;
45 import eu
.etaxonomy
.cdm
.persistence
.dao
.term
.ITermNodeDao
;
46 import eu
.etaxonomy
.cdm
.persistence
.dto
.CharacterDto
;
47 import eu
.etaxonomy
.cdm
.persistence
.dto
.CharacterNodeDto
;
48 import eu
.etaxonomy
.cdm
.persistence
.dto
.FeatureStateDto
;
49 import eu
.etaxonomy
.cdm
.persistence
.dto
.MergeResult
;
50 import eu
.etaxonomy
.cdm
.persistence
.dto
.TermDto
;
51 import eu
.etaxonomy
.cdm
.persistence
.dto
.TermNodeDto
;
52 import eu
.etaxonomy
.cdm
.persistence
.dto
.TermVocabularyDto
;
53 import eu
.etaxonomy
.cdm
.persistence
.query
.OrderHint
;
60 @Transactional(readOnly
= false)
61 public class TermNodeServiceImpl
62 extends VersionableServiceBase
<TermNode
, ITermNodeDao
>
63 implements ITermNodeService
{
65 @SuppressWarnings("unused")
66 private static final Logger logger
= LogManager
.getLogger(TermNodeServiceImpl
.class);
70 protected void setDao(ITermNodeDao dao
) {
75 private ITermService termService
;
78 private IVocabularyService vocabularyService
;
81 public List
<TermNode
> list(TermType termType
, Integer limit
, Integer start
,
82 List
<OrderHint
> orderHints
, List
<String
> propertyPaths
){
83 return dao
.list(termType
, limit
, start
, orderHints
, propertyPaths
);
87 @Transactional(readOnly
= false)
88 public <T
extends DefinedTermBase
<?
>> DeleteResult
deleteNode(UUID nodeUuid
, TermNodeDeletionConfigurator config
) {
89 DeleteResult result
= new DeleteResult();
90 @SuppressWarnings("unchecked")
91 TermNode
<T
> node
= CdmBase
.deproxy(dao
.load(nodeUuid
));
92 result
= isDeletable(nodeUuid
, config
);
94 TermNode
<T
> parent
= node
.getParent();
95 parent
= CdmBase
.deproxy(parent
);
96 List
<TermNode
<T
>> children
= new ArrayList
<>(node
.getChildNodes());
98 if (config
.getChildHandling().equals(ChildHandling
.DELETE
)){
100 for (TermNode
<?
> child
: children
){
101 deleteNode(child
.getUuid(), config
);
102 // node.removeChild(child);
105 parent
.removeChild(node
);
109 parent
.removeChild(node
);
110 for (TermNode
<T
> child
: children
){
111 node
.removeChild(child
);
112 parent
.addChild(child
);
116 result
.addException(new ReferencedObjectUndeletableException("The root node can not be deleted without its child nodes"));
122 result
.addDeletedObject(node
);
124 result
.addUpdatedObject(parent
);
126 if (config
.isDeleteElement()){
127 DefinedTermBase
<?
> term
= node
.getTerm();
128 termService
.delete(term
.getUuid());
129 result
.addDeletedObject(term
);
136 public UpdateResult
createChildNode(UUID parentNodeUuid
, DefinedTermBase term
, UUID vocabularyUuid
){
137 TermVocabulary vocabulary
= vocabularyService
.load(vocabularyUuid
);
139 vocabulary
.addTerm(term
);
140 vocabularyService
.save(vocabulary
);
141 return addChildNode(parentNodeUuid
, term
.getUuid());
145 public UpdateResult
addChildNode(UUID nodeUUID
, UUID termChildUuid
){
146 return addChildNode(nodeUUID
, termChildUuid
, 0);
150 public UpdateResult
addChildNode(UUID nodeUUID
, UUID termChildUuid
, int position
){
151 UpdateResult result
= new UpdateResult();
153 TermNode node
= load(nodeUUID
);
156 result
.addException(new Exception("The parent node does not exist."));
159 DefinedTermBase child
= HibernateProxyHelper
.deproxy(termService
.load(termChildUuid
), DefinedTermBase
.class);
161 if(node
.getGraph() != null && !node
.getGraph().isAllowDuplicates() && node
.getGraph().getDistinctTerms().contains(child
)){
163 result
.addException(new Exception("This term tree does not allow duplicate terms."));
169 childNode
= node
.addChild(child
);
171 childNode
= node
.addChild(child
, position
);
174 result
.addUpdatedObject(node
);
175 result
.setCdmEntity(childNode
);
180 public DeleteResult
isDeletable(UUID nodeUuid
, TermNodeDeletionConfigurator config
){
181 TermNode
<?
> node
= load(nodeUuid
);
182 DeleteResult result
= new DeleteResult();
184 result
.addException(new DataChangeNoRollbackException("The object is not available anymore."));
188 Set
<CdmBase
> references
= commonService
.getReferencingObjectsForDeletion(node
);
189 for (CdmBase ref
:references
){
190 if (ref
instanceof TermNode
){
193 if (ref
instanceof TermTree
){
194 TermTree
<?
> refTree
= HibernateProxyHelper
.deproxy(ref
, TermTree
.class);
195 if (node
.getGraph().equals((refTree
))){
200 result
.addException(new ReferencedObjectUndeletableException("The featureNode is referenced by " + ref
.getUserFriendlyDescription() +" with id " +ref
.getId()));
206 public UpdateResult
moveNode(UUID movedNodeUuid
, UUID targetNodeUuid
, int position
) {
207 UpdateResult result
= new UpdateResult();
208 List
<String
> propertyPaths
= new ArrayList
<>();
209 propertyPaths
.add("parent");
210 propertyPaths
.add("parent.children");
211 propertyPaths
.add("children");
213 TermNode movedNode
= CdmBase
.deproxy(load(movedNodeUuid
, propertyPaths
), TermNode
.class);
214 TermNode
<?
> targetNode
= CdmBase
.deproxy(load(targetNodeUuid
, propertyPaths
));
215 TermNode
<?
> parent
= CdmBase
.deproxy(movedNode
.getParent());
216 parent
.removeChild(movedNode
);
218 targetNode
.addChild(movedNode
);
221 targetNode
.addChild(movedNode
, position
);
223 result
.addUpdatedObject(targetNode
);
224 result
.addUpdatedObject(parent
);
225 result
.setCdmEntity(movedNode
);
230 public UpdateResult
moveNode(UUID movedNodeUuid
, UUID targetNodeUuid
) {
231 return moveNode(movedNodeUuid
, targetNodeUuid
, -1);
235 public UpdateResult
saveTermNodeDtoList(List
<TermNodeDto
> dtos
){
236 UpdateResult result
= new UpdateResult();
237 List
<UUID
> uuids
= new ArrayList
<>();
238 dtos
.stream().forEach(dto
-> uuids
.add(dto
.getUuid()));
239 List
<TermNode
> nodes
= dao
.list(uuids
, null, 0, null, null);
240 //check all attributes for changes and adapt
241 for (TermNode
<?
> node
: nodes
){
242 for (TermNodeDto dto
: dtos
){
244 if (dto
.getUuid().equals(node
.getUuid())){
245 // only node changes, everything else will be handled by the operations/service methods
246 updateFeatureStates(node
, dto
, true);
247 updateFeatureStates(node
, dto
, false);
250 MergeResult
<TermNode
> mergeResult
= dao
.merge(node
, true);
251 result
.addUpdatedObject(mergeResult
.getMergedEntity());
257 private void updateFeatureStates(TermNode
<?
> node
, TermNodeDto dto
, boolean inApplicable
) {
258 Map
<FeatureState
, FeatureStateDto
> changeState
= new HashMap
<>();
259 Set
<FeatureStateDto
> newStates
= new HashSet
<>();
260 Set
<FeatureState
> deleteState
= new HashSet
<>();
261 boolean stillExist
= false;
262 Set
<FeatureState
> setToUpdate
= null;
263 Set
<FeatureStateDto
> setForUpdate
= null;
265 setToUpdate
= node
.getInapplicableIf();
266 setForUpdate
= dto
.getInapplicableIf();
268 setToUpdate
= node
.getOnlyApplicableIf();
269 setForUpdate
= dto
.getOnlyApplicableIf();
271 for (FeatureState featureState
: setToUpdate
){
273 for (FeatureStateDto featureStateDto
: setForUpdate
){
274 if (featureStateDto
.getUuid() != null && featureStateDto
.getUuid().equals(featureState
.getUuid())){
276 if (featureStateDto
.getFeature().getUuid().equals(featureState
.getFeature().getUuid()) && featureStateDto
.getState().getUuid().equals(featureState
.getState().getUuid())){
279 changeState
.put(featureState
, featureStateDto
);
285 deleteState
.add(featureState
);
289 for (FeatureStateDto featureStateDto
: setForUpdate
){
291 if (featureStateDto
.getUuid() == null){
292 newStates
.add(featureStateDto
);
294 for (FeatureState featureState
: setToUpdate
){
295 if (featureStateDto
.getUuid() != null && featureStateDto
.getUuid().equals(featureState
.getUuid())){
301 newStates
.add(featureStateDto
);
306 node
.getInapplicableIf().removeAll(deleteState
);
308 node
.getOnlyApplicableIf().removeAll(deleteState
);
310 for (Entry
<FeatureState
, FeatureStateDto
> change
: changeState
.entrySet()){
311 if (!change
.getKey().getFeature().getUuid().equals(change
.getValue().getFeature().getUuid())){
312 DefinedTermBase
<?
> term
= termService
.load(change
.getValue().getFeature().getUuid());
313 if (term
instanceof Feature
){
314 Feature feature
= HibernateProxyHelper
.deproxy(term
, Feature
.class);
315 change
.getKey().setFeature(feature
);
318 if (!change
.getKey().getState().getUuid().equals(change
.getValue().getState().getUuid())){
319 DefinedTermBase
<?
> term
= termService
.load(change
.getValue().getState().getUuid());
320 change
.getKey().setState(term
);
323 node
.getInapplicableIf().add(change
.getKey());
325 node
.getOnlyApplicableIf().add(change
.getKey());
328 for (FeatureStateDto stateDto
: newStates
){
329 Feature feature
= null;
331 DefinedTermBase
<?
> term
= termService
.find(stateDto
.getFeature().getUuid());
332 term
= HibernateProxyHelper
.deproxy(term
);
333 if (term
instanceof Character
){
334 feature
= HibernateProxyHelper
.deproxy(term
, Character
.class);
336 DefinedTermBase
<?
> termState
= termService
.load(stateDto
.getState().getUuid());
337 FeatureState newState
= FeatureState
.NewInstance(feature
, termState
);
339 node
.getInapplicableIf().add(newState
);
341 node
.getOnlyApplicableIf().add(newState
);
347 public UpdateResult
saveCharacterNodeDtoList(List
<CharacterNodeDto
> dtos
){
348 MergeResult
<TermNode
> mergeResult
;
349 UpdateResult result
= new UpdateResult();
350 List
<UUID
> nodeUuids
= new ArrayList
<>();
352 dtos
.stream().forEach(dto
-> nodeUuids
.add(dto
.getUuid()));
353 List
<TermNode
> nodes
= dao
.list(nodeUuids
, null, 0, null, null);
354 //check all attributes for changes and adapt
355 for (TermNode
<Character
> node
: nodes
){
356 for (CharacterNodeDto dto
: dtos
){
357 // TermNodeDto dto = dtoIterator.next();
358 if (dto
.getUuid().equals(node
.getUuid())){
359 updateFeatureStates(node
, dto
, true);
360 updateFeatureStates(node
, dto
, false);
361 // if (!dto.getInapplicableIf().equals(node.getInapplicableIf())){
362 // node.getInapplicableIf().clear();
363 // node.getInapplicableIf().addAll(dto.getInapplicableIf());
365 // if (!dto.getOnlyApplicableIf().equals(node.getOnlyApplicableIf())){
366 // node.getOnlyApplicableIf().clear();
367 // node.getOnlyApplicableIf().addAll(dto.getOnlyApplicableIf());
370 Character character
= null;
371 CharacterDto characterDto
= (CharacterDto
) dto
.getTerm();
372 character
= HibernateProxyHelper
.deproxy(node
.getTerm(), Character
.class);
373 if (characterDto
.getRatioTo() != null){
374 TermNode ratioToStructure
= this.load(characterDto
.getRatioTo().getUuid());
375 character
.setRatioToStructure(ratioToStructure
);
377 character
.setRatioToStructure(null);
381 //TODO add all other supportsXXX (6 are missing)
382 character
.setSupportsCategoricalData(characterDto
.isSupportsCategoricalData());
383 character
.setSupportsQuantitativeData(characterDto
.isSupportsQuantitativeData());
386 character
.setAvailableForTaxon(characterDto
.isAvailableForTaxon());
387 character
.setAvailableForOccurrence(characterDto
.isAvailableForOccurrence());
388 character
.setAvailableForTaxonName(characterDto
.isAvailableForTaxonName());
391 for (Representation rep
: dto
.getTerm().getRepresentations()){
392 Representation oldRep
= character
.getRepresentation(rep
.getLanguage());
394 oldRep
= Representation
.NewInstance(null, null, null, rep
.getLanguage());
395 character
.addRepresentation(oldRep
);
397 oldRep
.setLabel(rep
.getLabel());
398 oldRep
.setAbbreviatedLabel(rep
.getAbbreviatedLabel());
399 oldRep
.setText(rep
.getText());
400 oldRep
.setPlural(rep
.getPlural());
402 Set
<Representation
> deleteRepresentations
= new HashSet
<>();
403 if (character
.getRepresentations().size() > dto
.getTerm().getRepresentations().size()){
404 for (Representation rep
: character
.getRepresentations()){
405 if(dto
.getTerm().getRepresentation(rep
.getLanguage()) == null){
406 deleteRepresentations
.add(rep
);
411 if (!deleteRepresentations
.isEmpty()){
412 for (Representation rep
: deleteRepresentations
){
413 character
.removeRepresentation(rep
);
417 // structural modifier
418 if (characterDto
.getStructureModifier() != null){
419 DefinedTerm structureModifier
= (DefinedTerm
) termService
.load(characterDto
.getStructureModifier().getUuid());
420 character
.setStructureModifier(structureModifier
);
422 character
.setStructureModifier(null);
424 // recommended measurement units
425 character
.getRecommendedMeasurementUnits().clear();
426 List
<UUID
> uuids
= new ArrayList
<>();
427 for (TermDto termDto
: characterDto
.getRecommendedMeasurementUnits()){
428 uuids
.add(termDto
.getUuid());
430 List
<DefinedTermBase
> terms
;
431 if (!uuids
.isEmpty()){
432 terms
= termService
.load(uuids
, null);
433 Set
<MeasurementUnit
> measurementUnits
= new HashSet
<>();
434 for (DefinedTermBase
<?
> term
: terms
){
435 if (term
instanceof MeasurementUnit
){
436 measurementUnits
.add((MeasurementUnit
)term
);
439 character
.getRecommendedMeasurementUnits().addAll(measurementUnits
);
441 // statistical measures
442 character
.getRecommendedStatisticalMeasures().clear();
443 uuids
= new ArrayList
<>();
444 for (TermDto termDto
: characterDto
.getRecommendedStatisticalMeasures()){
445 uuids
.add(termDto
.getUuid());
447 if (!uuids
.isEmpty()){
448 terms
= termService
.load(uuids
, null);
449 Set
<StatisticalMeasure
> statisticalMeasures
= new HashSet
<>();
450 for (DefinedTermBase
<?
> term
: terms
){
451 if (term
instanceof StatisticalMeasure
){
452 statisticalMeasures
.add((StatisticalMeasure
)term
);
455 character
.getRecommendedStatisticalMeasures().addAll(statisticalMeasures
);
458 // recommended mod. vocabularies
459 character
.getRecommendedModifierEnumeration().clear();
460 uuids
= new ArrayList
<>();
461 for (TermVocabularyDto termDto
: characterDto
.getRecommendedModifierEnumeration()){
462 uuids
.add(termDto
.getUuid());
464 List
<TermVocabulary
> termVocs
;
465 if (!uuids
.isEmpty()){
466 termVocs
= vocabularyService
.load(uuids
, null);
467 for (TermVocabulary
<DefinedTerm
> voc
: termVocs
){
468 character
.addRecommendedModifierEnumeration(voc
);
472 // supported state vocabularies
473 character
.getSupportedCategoricalEnumerations().clear();
474 uuids
= new ArrayList
<>();
475 for (TermVocabularyDto termDto
: characterDto
.getSupportedCategoricalEnumerations()){
476 uuids
.add(termDto
.getUuid());
478 if (!uuids
.isEmpty()){
479 termVocs
= vocabularyService
.load(uuids
, null);
481 for (TermVocabulary voc
: termVocs
){
482 character
.addSupportedCategoricalEnumeration(voc
);
485 node
.setTerm(character
);
486 mergeResult
= dao
.merge(node
, true);
487 result
.addUpdatedObject(mergeResult
.getMergedEntity());
495 public UpdateResult
saveNewCharacterNodeDtoMap(Map
<Character
, CharacterNodeDto
> dtos
, UUID vocabularyUuid
){
496 UpdateResult result
= new UpdateResult();
497 UpdateResult resultLocal
= new UpdateResult();
498 for (Entry
<Character
, CharacterNodeDto
> dtoEntry
: dtos
.entrySet()){
499 resultLocal
= createChildNode(dtoEntry
.getValue().getParentUuid(), dtoEntry
.getKey(), vocabularyUuid
);
500 dtoEntry
.getValue().setUuid(resultLocal
.getCdmEntity().getUuid());
501 result
.includeResult(resultLocal
);
503 List
<CharacterNodeDto
> dtoList
= new ArrayList
<>(dtos
.values());
504 result
.includeResult(saveCharacterNodeDtoList(dtoList
));