057f011439fd67682726a6d1421d2e958c73b49d
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / TermNodeServiceImpl.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.cdm.api.service;
10
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Map.Entry;
17 import java.util.Set;
18 import java.util.UUID;
19
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;
25
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;
54
55 /**
56 * @author a.mueller
57 * @since Jul 22, 2019
58 */
59 @Service
60 @Transactional(readOnly = false)
61 public class TermNodeServiceImpl
62 extends VersionableServiceBase<TermNode, ITermNodeDao>
63 implements ITermNodeService {
64
65 @SuppressWarnings("unused")
66 private static final Logger logger = LogManager.getLogger(TermNodeServiceImpl.class);
67
68 @Override
69 @Autowired
70 protected void setDao(ITermNodeDao dao) {
71 this.dao = dao;
72 }
73
74 @Autowired
75 private ITermService termService;
76
77 @Autowired
78 private IVocabularyService vocabularyService;
79
80 @Override
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);
84 }
85
86 @Override
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);
93 if (result.isOk()){
94 TermNode<T> parent = node.getParent();
95 parent = CdmBase.deproxy(parent);
96 List<TermNode<T>> children = new ArrayList<>(node.getChildNodes());
97
98 if (config.getChildHandling().equals(ChildHandling.DELETE)){
99
100 for (TermNode<?> child: children){
101 deleteNode(child.getUuid(), config);
102 // node.removeChild(child);
103 }
104 if (parent != null){
105 parent.removeChild(node);
106 }
107 } else{
108 if (parent != null){
109 parent.removeChild(node);
110 for (TermNode<T> child: children){
111 node.removeChild(child);
112 parent.addChild(child);
113 }
114 }else{
115 result.setAbort();
116 result.addException(new ReferencedObjectUndeletableException("The root node can not be deleted without its child nodes"));
117 return result;
118 }
119 }
120
121 dao.delete(node);
122 result.addDeletedObject(node);
123 if(parent!=null){
124 result.addUpdatedObject(parent);
125 }
126 if (config.isDeleteElement()){
127 DefinedTermBase<?> term = node.getTerm();
128 termService.delete(term.getUuid());
129 result.addDeletedObject(term);
130 }
131 }
132 return result;
133 }
134
135 @Override
136 public UpdateResult createChildNode(UUID parentNodeUuid, DefinedTermBase term, UUID vocabularyUuid){
137 TermVocabulary vocabulary = vocabularyService.load(vocabularyUuid);
138
139 vocabulary.addTerm(term);
140 vocabularyService.save(vocabulary);
141 return addChildNode(parentNodeUuid, term.getUuid());
142 }
143
144 @Override
145 public UpdateResult addChildNode(UUID nodeUUID, UUID termChildUuid){
146 return addChildNode(nodeUUID, termChildUuid, 0);
147 }
148
149 @Override
150 public UpdateResult addChildNode(UUID nodeUUID, UUID termChildUuid, int position){
151 UpdateResult result = new UpdateResult();
152
153 TermNode node = load(nodeUUID);
154 if (node == null){
155 result.setError();
156 result.addException(new Exception("The parent node does not exist."));
157 return result;
158 }
159 DefinedTermBase child = HibernateProxyHelper.deproxy(termService.load(termChildUuid), DefinedTermBase.class);
160
161 if(node.getGraph() != null && !node.getGraph().isAllowDuplicates() && node.getGraph().getDistinctTerms().contains(child)){
162 result.setError();
163 result.addException(new Exception("This term tree does not allow duplicate terms."));
164 return result;
165 }
166
167 TermNode childNode;
168 if(position<0) {
169 childNode = node.addChild(child);
170 } else {
171 childNode = node.addChild(child, position);
172 }
173 save(childNode);
174 result.addUpdatedObject(node);
175 result.setCdmEntity(childNode);
176 return result;
177 }
178
179 @Override
180 public DeleteResult isDeletable(UUID nodeUuid, TermNodeDeletionConfigurator config){
181 TermNode<?> node = load(nodeUuid);
182 DeleteResult result = new DeleteResult();
183 if (node == null){
184 result.addException(new DataChangeNoRollbackException("The object is not available anymore."));
185 result.setAbort();
186 return result;
187 }
188 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(node);
189 for (CdmBase ref:references){
190 if (ref instanceof TermNode){
191 break;
192 }
193 if (ref instanceof TermTree){
194 TermTree<?> refTree = HibernateProxyHelper.deproxy(ref, TermTree.class);
195 if (node.getGraph().equals((refTree))){
196 break;
197 }
198 }
199 result.setAbort();
200 result.addException(new ReferencedObjectUndeletableException("The featureNode is referenced by " + ref.getUserFriendlyDescription() +" with id " +ref.getId()));
201 }
202 return result;
203 }
204
205 @Override
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");
212
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);
217 if(position < 0){
218 targetNode.addChild(movedNode);
219 }
220 else{
221 targetNode.addChild(movedNode, position);
222 }
223 result.addUpdatedObject(targetNode);
224 result.addUpdatedObject(parent);
225 result.setCdmEntity(movedNode);
226 return result;
227 }
228
229 @Override
230 public UpdateResult moveNode(UUID movedNodeUuid, UUID targetNodeUuid) {
231 return moveNode(movedNodeUuid, targetNodeUuid, -1);
232 }
233
234 @Override
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){
243
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);
248
249 }
250 MergeResult<TermNode> mergeResult = dao.merge(node, true);
251 result.addUpdatedObject(mergeResult.getMergedEntity());
252 }
253 }
254 return result;
255 }
256
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;
264 if (inApplicable){
265 setToUpdate = node.getInapplicableIf();
266 setForUpdate = dto.getInapplicableIf();
267 }else{
268 setToUpdate = node.getOnlyApplicableIf();
269 setForUpdate = dto.getOnlyApplicableIf();
270 }
271 for (FeatureState featureState: setToUpdate){
272 stillExist = false;
273 for (FeatureStateDto featureStateDto: setForUpdate){
274 if (featureStateDto.getUuid() != null && featureStateDto.getUuid().equals(featureState.getUuid())){
275 stillExist = true;
276 if (featureStateDto.getFeature().getUuid().equals(featureState.getFeature().getUuid()) && featureStateDto.getState().getUuid().equals(featureState.getState().getUuid())){
277 //do nothing
278 }else{
279 changeState.put(featureState, featureStateDto);
280 }
281 break;
282 }
283 }
284 if (!stillExist){
285 deleteState.add(featureState);
286 }
287 }
288
289 for (FeatureStateDto featureStateDto: setForUpdate){
290 stillExist = false;
291 if (featureStateDto.getUuid() == null){
292 newStates.add(featureStateDto);
293 }else{
294 for (FeatureState featureState: setToUpdate){
295 if (featureStateDto.getUuid() != null && featureStateDto.getUuid().equals(featureState.getUuid())){
296 stillExist = true;
297 break;
298 }
299 }
300 if (!stillExist){
301 newStates.add(featureStateDto);
302 }
303 }
304 }
305 if (inApplicable){
306 node.getInapplicableIf().removeAll(deleteState);
307 }else{
308 node.getOnlyApplicableIf().removeAll(deleteState);
309 }
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);
316 }
317 }
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);
321 }
322 if (inApplicable){
323 node.getInapplicableIf().add(change.getKey());
324 }else{
325 node.getOnlyApplicableIf().add(change.getKey());
326 }
327 }
328 for (FeatureStateDto stateDto: newStates){
329 Feature feature = null;
330 State state = 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);
335 }
336 DefinedTermBase<?> termState = termService.load(stateDto.getState().getUuid());
337 FeatureState newState = FeatureState.NewInstance(feature, termState);
338 if (inApplicable){
339 node.getInapplicableIf().add(newState);
340 }else{
341 node.getOnlyApplicableIf().add(newState);
342 }
343 }
344 }
345
346 @Override
347 public UpdateResult saveCharacterNodeDtoList(List<CharacterNodeDto> dtos){
348 MergeResult<TermNode> mergeResult;
349 UpdateResult result = new UpdateResult();
350 List<UUID> nodeUuids = new ArrayList<>();
351
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());
364 // }
365 // if (!dto.getOnlyApplicableIf().equals(node.getOnlyApplicableIf())){
366 // node.getOnlyApplicableIf().clear();
367 // node.getOnlyApplicableIf().addAll(dto.getOnlyApplicableIf());
368 // }
369
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);
376 }else{
377 character.setRatioToStructure(null);
378 }
379
380 //supportsXXX
381 //TODO add all other supportsXXX (6 are missing)
382 character.setSupportsCategoricalData(characterDto.isSupportsCategoricalData());
383 character.setSupportsQuantitativeData(characterDto.isSupportsQuantitativeData());
384
385 //availableForXXX
386 character.setAvailableForTaxon(characterDto.isAvailableForTaxon());
387 character.setAvailableForOccurrence(characterDto.isAvailableForOccurrence());
388 character.setAvailableForTaxonName(characterDto.isAvailableForTaxonName());
389
390 // representations
391 for (Representation rep: dto.getTerm().getRepresentations()){
392 Representation oldRep = character.getRepresentation(rep.getLanguage());
393 if (oldRep == null){
394 oldRep = Representation.NewInstance(null, null, null, rep.getLanguage());
395 character.addRepresentation(oldRep);
396 }
397 oldRep.setLabel(rep.getLabel());
398 oldRep.setAbbreviatedLabel(rep.getAbbreviatedLabel());
399 oldRep.setText(rep.getText());
400 oldRep.setPlural(rep.getPlural());
401 }
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);
407 }
408 }
409 }
410
411 if (!deleteRepresentations.isEmpty()){
412 for (Representation rep: deleteRepresentations){
413 character.removeRepresentation(rep);
414 }
415 }
416
417 // structural modifier
418 if (characterDto.getStructureModifier() != null){
419 DefinedTerm structureModifier = (DefinedTerm) termService.load(characterDto.getStructureModifier().getUuid());
420 character.setStructureModifier(structureModifier);
421 }else{
422 character.setStructureModifier(null);
423 }
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());
429 }
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);
437 }
438 }
439 character.getRecommendedMeasurementUnits().addAll(measurementUnits);
440 }
441 // statistical measures
442 character.getRecommendedStatisticalMeasures().clear();
443 uuids = new ArrayList<>();
444 for (TermDto termDto: characterDto.getRecommendedStatisticalMeasures()){
445 uuids.add(termDto.getUuid());
446 }
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);
453 }
454 }
455 character.getRecommendedStatisticalMeasures().addAll(statisticalMeasures);
456 }
457
458 // recommended mod. vocabularies
459 character.getRecommendedModifierEnumeration().clear();
460 uuids = new ArrayList<>();
461 for (TermVocabularyDto termDto: characterDto.getRecommendedModifierEnumeration()){
462 uuids.add(termDto.getUuid());
463 }
464 List<TermVocabulary> termVocs;
465 if (!uuids.isEmpty()){
466 termVocs = vocabularyService.load(uuids, null);
467 for (TermVocabulary<DefinedTerm> voc: termVocs){
468 character.addRecommendedModifierEnumeration(voc);
469 }
470 }
471
472 // supported state vocabularies
473 character.getSupportedCategoricalEnumerations().clear();
474 uuids = new ArrayList<>();
475 for (TermVocabularyDto termDto: characterDto.getSupportedCategoricalEnumerations()){
476 uuids.add(termDto.getUuid());
477 }
478 if (!uuids.isEmpty()){
479 termVocs = vocabularyService.load(uuids, null);
480
481 for (TermVocabulary voc: termVocs){
482 character.addSupportedCategoricalEnumeration(voc);
483 }
484 }
485 node.setTerm(character);
486 mergeResult = dao.merge(node, true);
487 result.addUpdatedObject(mergeResult.getMergedEntity());
488 }
489 }
490 }
491 return result;
492 }
493
494 @Override
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);
502 }
503 List<CharacterNodeDto> dtoList = new ArrayList<>(dtos.values());
504 result.includeResult(saveCharacterNodeDtoList(dtoList));
505 return result;
506 }
507 }