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.util.ArrayList;
|
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.log4j.Logger;
|
21
|
import org.springframework.beans.factory.annotation.Autowired;
|
22
|
import org.springframework.stereotype.Service;
|
23
|
import org.springframework.transaction.annotation.Transactional;
|
24
|
|
25
|
import eu.etaxonomy.cdm.api.service.config.NodeDeletionConfigurator.ChildHandling;
|
26
|
import eu.etaxonomy.cdm.api.service.config.TermNodeDeletionConfigurator;
|
27
|
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
|
28
|
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
|
29
|
import eu.etaxonomy.cdm.model.common.CdmBase;
|
30
|
import eu.etaxonomy.cdm.model.description.Character;
|
31
|
import eu.etaxonomy.cdm.model.description.Feature;
|
32
|
import eu.etaxonomy.cdm.model.description.MeasurementUnit;
|
33
|
import eu.etaxonomy.cdm.model.description.StatisticalMeasure;
|
34
|
import eu.etaxonomy.cdm.model.term.DefinedTerm;
|
35
|
import eu.etaxonomy.cdm.model.term.DefinedTermBase;
|
36
|
import eu.etaxonomy.cdm.model.term.Representation;
|
37
|
import eu.etaxonomy.cdm.model.term.TermNode;
|
38
|
import eu.etaxonomy.cdm.model.term.TermTree;
|
39
|
import eu.etaxonomy.cdm.model.term.TermType;
|
40
|
import eu.etaxonomy.cdm.model.term.TermVocabulary;
|
41
|
import eu.etaxonomy.cdm.persistence.dao.term.ITermNodeDao;
|
42
|
import eu.etaxonomy.cdm.persistence.dto.CharacterDto;
|
43
|
import eu.etaxonomy.cdm.persistence.dto.CharacterNodeDto;
|
44
|
import eu.etaxonomy.cdm.persistence.dto.MergeResult;
|
45
|
import eu.etaxonomy.cdm.persistence.dto.TermDto;
|
46
|
import eu.etaxonomy.cdm.persistence.dto.TermNodeDto;
|
47
|
import eu.etaxonomy.cdm.persistence.dto.TermVocabularyDto;
|
48
|
import eu.etaxonomy.cdm.persistence.query.OrderHint;
|
49
|
|
50
|
/**
|
51
|
* @author a.mueller
|
52
|
* @since Jul 22, 2019
|
53
|
*/
|
54
|
@Service
|
55
|
@Transactional(readOnly = false)
|
56
|
public class TermNodeServiceImpl
|
57
|
extends VersionableServiceBase<TermNode, ITermNodeDao>
|
58
|
implements ITermNodeService {
|
59
|
|
60
|
@SuppressWarnings("unused")
|
61
|
private static final Logger logger = Logger.getLogger(TermNodeServiceImpl.class);
|
62
|
|
63
|
@Override
|
64
|
@Autowired
|
65
|
protected void setDao(ITermNodeDao dao) {
|
66
|
this.dao = dao;
|
67
|
}
|
68
|
|
69
|
@Autowired
|
70
|
private ITermService termService;
|
71
|
|
72
|
@Autowired
|
73
|
private IVocabularyService vocabularyService;
|
74
|
|
75
|
@Override
|
76
|
public List<TermNode> list(TermType termType, Integer limit, Integer start,
|
77
|
List<OrderHint> orderHints, List<String> propertyPaths){
|
78
|
return dao.list(termType, limit, start, orderHints, propertyPaths);
|
79
|
}
|
80
|
|
81
|
@Override
|
82
|
@Transactional(readOnly = false)
|
83
|
public DeleteResult deleteNode(UUID nodeUuid, TermNodeDeletionConfigurator config) {
|
84
|
DeleteResult result = new DeleteResult();
|
85
|
TermNode<Feature> node = CdmBase.deproxy(dao.load(nodeUuid));
|
86
|
result = isDeletable(nodeUuid, config);
|
87
|
if (result.isOk()){
|
88
|
TermNode<Feature> parent = node.getParent();
|
89
|
parent = CdmBase.deproxy(parent, TermNode.class);
|
90
|
List<TermNode> children = new ArrayList<>(node.getChildNodes());
|
91
|
|
92
|
if (config.getChildHandling().equals(ChildHandling.DELETE)){
|
93
|
|
94
|
for (TermNode child: children){
|
95
|
deleteNode(child.getUuid(), config);
|
96
|
// node.removeChild(child);
|
97
|
}
|
98
|
if (parent != null){
|
99
|
parent.removeChild(node);
|
100
|
}
|
101
|
|
102
|
} else{
|
103
|
|
104
|
if (parent != null){
|
105
|
parent.removeChild(node);
|
106
|
for (TermNode child: children){
|
107
|
node.removeChild(child);
|
108
|
parent.addChild(child);
|
109
|
}
|
110
|
}else{
|
111
|
result.setAbort();
|
112
|
result.addException(new ReferencedObjectUndeletableException("The root node can not be deleted without its child nodes"));
|
113
|
return result;
|
114
|
}
|
115
|
}
|
116
|
|
117
|
dao.delete(node);
|
118
|
result.addDeletedObject(node);
|
119
|
if(parent!=null){
|
120
|
result.addUpdatedObject(parent);
|
121
|
}
|
122
|
if (config.isDeleteElement()){
|
123
|
DefinedTermBase term = node.getTerm();
|
124
|
termService.delete(term.getUuid());
|
125
|
result.addDeletedObject(term);
|
126
|
}
|
127
|
}
|
128
|
return result;
|
129
|
}
|
130
|
private UpdateResult createChildNode(UUID parentNodeUUID, UUID nodeUuid, DefinedTermBase term, UUID vocabularyUuid){
|
131
|
UpdateResult result = createChildNode(parentNodeUUID, term, vocabularyUuid);
|
132
|
result.getCdmEntity().setUuid(nodeUuid);
|
133
|
return result;
|
134
|
}
|
135
|
|
136
|
|
137
|
@Override
|
138
|
public UpdateResult createChildNode(UUID parentNodeUuid, DefinedTermBase term, UUID vocabularyUuid){
|
139
|
TermVocabulary vocabulary = vocabularyService.load(vocabularyUuid);
|
140
|
|
141
|
vocabulary.addTerm(term);
|
142
|
vocabularyService.save(vocabulary);
|
143
|
return addChildNode(parentNodeUuid, term.getUuid());
|
144
|
}
|
145
|
|
146
|
@Override
|
147
|
public UpdateResult addChildNode(UUID nodeUUID, UUID termChildUuid){
|
148
|
return addChildNode(nodeUUID, termChildUuid, 0);
|
149
|
}
|
150
|
|
151
|
@Override
|
152
|
public UpdateResult addChildNode(UUID nodeUUID, UUID termChildUuid, int position){
|
153
|
UpdateResult result = new UpdateResult();
|
154
|
|
155
|
TermNode node = load(nodeUUID);
|
156
|
DefinedTermBase child = HibernateProxyHelper.deproxy(termService.load(termChildUuid), DefinedTermBase.class);
|
157
|
|
158
|
if(!node.getGraph().isAllowDuplicates() && node.getGraph().getDistinctTerms().contains(child)){
|
159
|
result.setError();
|
160
|
result.addException(new Exception("This term tree does not allow duplicate terms."));
|
161
|
return result;
|
162
|
}
|
163
|
|
164
|
TermNode childNode;
|
165
|
if(position<0) {
|
166
|
childNode = node.addChild(child);
|
167
|
}
|
168
|
else{
|
169
|
childNode = node.addChild(child, position);
|
170
|
}
|
171
|
save(childNode);
|
172
|
result.addUpdatedObject(node);
|
173
|
result.setCdmEntity(childNode);
|
174
|
return result;
|
175
|
}
|
176
|
|
177
|
@Override
|
178
|
public DeleteResult isDeletable(UUID nodeUuid, TermNodeDeletionConfigurator config){
|
179
|
TermNode<Feature> node = load(nodeUuid);
|
180
|
DeleteResult result = new DeleteResult();
|
181
|
Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(node);
|
182
|
for (CdmBase ref:references){
|
183
|
if (ref instanceof TermNode){
|
184
|
break;
|
185
|
}
|
186
|
if (ref instanceof TermTree){
|
187
|
TermTree<Feature> refTree = HibernateProxyHelper.deproxy(ref, TermTree.class);
|
188
|
if (node.getGraph().equals((refTree))){
|
189
|
break;
|
190
|
}
|
191
|
}
|
192
|
result.setAbort();
|
193
|
result.addException(new ReferencedObjectUndeletableException("The featureNode is referenced by " + ref.getUserFriendlyDescription() +" with id " +ref.getId()));
|
194
|
|
195
|
}
|
196
|
return result;
|
197
|
}
|
198
|
|
199
|
@Override
|
200
|
public UpdateResult moveNode(UUID movedNodeUuid, UUID targetNodeUuid, int position) {
|
201
|
UpdateResult result = new UpdateResult();
|
202
|
List<String> propertyPaths = new ArrayList<>();
|
203
|
propertyPaths.add("parent");
|
204
|
propertyPaths.add("parent.children");
|
205
|
propertyPaths.add("children");
|
206
|
TermNode test = load(movedNodeUuid, propertyPaths);
|
207
|
TermNode movedNode = CdmBase.deproxy(load(movedNodeUuid, propertyPaths), TermNode.class);
|
208
|
TermNode targetNode = CdmBase.deproxy(load(targetNodeUuid, propertyPaths));
|
209
|
TermNode parent = CdmBase.deproxy(movedNode.getParent());
|
210
|
parent.removeChild(movedNode);
|
211
|
if(position < 0){
|
212
|
targetNode.addChild(movedNode);
|
213
|
}
|
214
|
else{
|
215
|
targetNode.addChild(movedNode, position);
|
216
|
}
|
217
|
result.addUpdatedObject(targetNode);
|
218
|
if(parent!=null){
|
219
|
result.addUpdatedObject(parent);
|
220
|
}
|
221
|
result.setCdmEntity(movedNode);
|
222
|
return result;
|
223
|
}
|
224
|
|
225
|
@Override
|
226
|
public UpdateResult moveNode(UUID movedNodeUuid, UUID targetNodeUuid) {
|
227
|
return moveNode(movedNodeUuid, targetNodeUuid, -1);
|
228
|
}
|
229
|
|
230
|
@Override
|
231
|
public UpdateResult saveTermNodeDtoList(List<TermNodeDto> dtos){
|
232
|
UpdateResult result = new UpdateResult();
|
233
|
MergeResult<TermNode> mergeResult;
|
234
|
List<UUID> uuids = new ArrayList<>();
|
235
|
dtos.stream().forEach(dto -> uuids.add(dto.getUuid()));
|
236
|
List<TermNode> nodes = dao.list(uuids, null, 0, null, null);
|
237
|
//check all attributes for changes and adapt
|
238
|
for (TermNode node: nodes){
|
239
|
for (TermNodeDto dto: dtos){
|
240
|
|
241
|
if (dto.getUuid().equals(node.getUuid())){
|
242
|
// only node changes, everything else will be handled by the operations/service methods
|
243
|
if (!dto.getInapplicableIf().equals(node.getInapplicableIf())){
|
244
|
node.getInapplicableIf().clear();
|
245
|
node.getInapplicableIf().addAll(dto.getInapplicableIf());
|
246
|
}
|
247
|
if (!dto.getOnlyApplicableIf().equals(node.getOnlyApplicableIf())){
|
248
|
node.getOnlyApplicableIf().clear();
|
249
|
node.getOnlyApplicableIf().addAll(dto.getOnlyApplicableIf());
|
250
|
}
|
251
|
}
|
252
|
mergeResult = dao.merge(node, true);
|
253
|
result.addUpdatedObject(mergeResult.getMergedEntity());
|
254
|
}
|
255
|
}
|
256
|
return result;
|
257
|
}
|
258
|
|
259
|
@Override
|
260
|
public UpdateResult saveCharacterNodeDtoList(List<CharacterNodeDto> dtos){
|
261
|
MergeResult<TermNode> mergeResult;
|
262
|
UpdateResult result = new UpdateResult();
|
263
|
List<UUID> nodeUuids = new ArrayList<>();
|
264
|
|
265
|
dtos.stream().forEach(dto -> nodeUuids.add(dto.getUuid()));
|
266
|
List<TermNode> nodes = dao.list(nodeUuids, null, 0, null, null);
|
267
|
//check all attributes for changes and adapt
|
268
|
for (TermNode<Character> node: nodes){
|
269
|
for (CharacterNodeDto dto: dtos){
|
270
|
// TermNodeDto dto = dtoIterator.next();
|
271
|
if (dto.getUuid().equals(node.getUuid())){
|
272
|
|
273
|
if (!dto.getInapplicableIf().equals(node.getInapplicableIf())){
|
274
|
node.getInapplicableIf().clear();
|
275
|
node.getInapplicableIf().addAll(dto.getInapplicableIf());
|
276
|
}
|
277
|
if (!dto.getOnlyApplicableIf().equals(node.getOnlyApplicableIf())){
|
278
|
node.getOnlyApplicableIf().clear();
|
279
|
node.getOnlyApplicableIf().addAll(dto.getOnlyApplicableIf());
|
280
|
}
|
281
|
|
282
|
Character character = null;
|
283
|
CharacterDto characterDto = (CharacterDto) dto.getTerm();
|
284
|
character = HibernateProxyHelper.deproxy(node.getTerm(), Character.class);
|
285
|
if (characterDto.getRatioTo() != null){
|
286
|
TermNode ratioToStructure = this.load(characterDto.getRatioTo().getUuid());
|
287
|
character.setRatioToStructure(ratioToStructure);
|
288
|
}else{
|
289
|
character.setRatioToStructure(null);
|
290
|
}
|
291
|
|
292
|
//supportsXXX
|
293
|
//TODO add all other supportsXXX (6 are missing)
|
294
|
character.setSupportsCategoricalData(characterDto.isSupportsCategoricalData());
|
295
|
character.setSupportsQuantitativeData(characterDto.isSupportsQuantitativeData());
|
296
|
|
297
|
//availableForXXX
|
298
|
character.setAvailableForTaxon(characterDto.isAvailableForTaxon());
|
299
|
character.setAvailableForOccurrence(characterDto.isAvailableForOccurrence());
|
300
|
character.setAvailableForTaxonName(characterDto.isAvailableForTaxonName());
|
301
|
|
302
|
// representations
|
303
|
for (Representation rep: dto.getTerm().getRepresentations()){
|
304
|
Representation oldRep = character.getRepresentation(rep.getLanguage());
|
305
|
if (oldRep == null){
|
306
|
oldRep = new Representation();
|
307
|
oldRep.setLanguage(rep.getLanguage());
|
308
|
character.addRepresentation(oldRep);
|
309
|
}
|
310
|
oldRep.setLabel(rep.getLabel());
|
311
|
oldRep.setAbbreviatedLabel(rep.getAbbreviatedLabel());
|
312
|
oldRep.setText(rep.getText());
|
313
|
oldRep.setPlural(rep.getPlural());
|
314
|
}
|
315
|
Set<Representation> deleteRepresentations = new HashSet<>();
|
316
|
if (character.getRepresentations().size() > dto.getTerm().getRepresentations().size()){
|
317
|
for (Representation rep: character.getRepresentations()){
|
318
|
if(dto.getTerm().getRepresentation(rep.getLanguage()) == null){
|
319
|
deleteRepresentations.add(rep);
|
320
|
}
|
321
|
}
|
322
|
}
|
323
|
|
324
|
if (!deleteRepresentations.isEmpty()){
|
325
|
for (Representation rep: deleteRepresentations){
|
326
|
character.removeRepresentation(rep);
|
327
|
}
|
328
|
}
|
329
|
|
330
|
// structural modifier
|
331
|
if (characterDto.getStructureModifier() != null){
|
332
|
DefinedTerm structureModifier = (DefinedTerm) termService.load(characterDto.getStructureModifier().getUuid());
|
333
|
character.setStructureModifier(structureModifier);
|
334
|
}else{
|
335
|
character.setStructureModifier(null);
|
336
|
}
|
337
|
// recommended measurement units
|
338
|
character.getRecommendedMeasurementUnits().clear();
|
339
|
List<UUID> uuids = new ArrayList<>();
|
340
|
for (TermDto termDto: characterDto.getRecommendedMeasurementUnits()){
|
341
|
uuids.add(termDto.getUuid());
|
342
|
}
|
343
|
List<DefinedTermBase> terms;
|
344
|
if (!uuids.isEmpty()){
|
345
|
terms = termService.load(uuids, null);
|
346
|
Set<MeasurementUnit> measurementUnits = new HashSet<>();
|
347
|
for (DefinedTermBase term: terms){
|
348
|
if (term instanceof MeasurementUnit){
|
349
|
measurementUnits.add((MeasurementUnit)term);
|
350
|
}
|
351
|
}
|
352
|
character.getRecommendedMeasurementUnits().addAll(measurementUnits);
|
353
|
}
|
354
|
// statistical measures
|
355
|
character.getRecommendedStatisticalMeasures().clear();
|
356
|
uuids = new ArrayList<>();
|
357
|
for (TermDto termDto: characterDto.getRecommendedStatisticalMeasures()){
|
358
|
uuids.add(termDto.getUuid());
|
359
|
}
|
360
|
if (!uuids.isEmpty()){
|
361
|
terms = termService.load(uuids, null);
|
362
|
Set<StatisticalMeasure> statisticalMeasures = new HashSet<>();
|
363
|
for (DefinedTermBase<?> term: terms){
|
364
|
if (term instanceof StatisticalMeasure){
|
365
|
statisticalMeasures.add((StatisticalMeasure)term);
|
366
|
}
|
367
|
}
|
368
|
character.getRecommendedStatisticalMeasures().addAll(statisticalMeasures);
|
369
|
}
|
370
|
|
371
|
// recommended mod. vocabularies
|
372
|
character.getRecommendedModifierEnumeration().clear();
|
373
|
uuids = new ArrayList<>();
|
374
|
for (TermVocabularyDto termDto: characterDto.getRecommendedModifierEnumeration()){
|
375
|
uuids.add(termDto.getUuid());
|
376
|
}
|
377
|
List<TermVocabulary> termVocs;
|
378
|
if (!uuids.isEmpty()){
|
379
|
termVocs = vocabularyService.load(uuids, null);
|
380
|
for (TermVocabulary voc: termVocs){
|
381
|
character.addRecommendedModifierEnumeration(voc);
|
382
|
}
|
383
|
}
|
384
|
|
385
|
// supported state vocabularies
|
386
|
character.getSupportedCategoricalEnumerations().clear();
|
387
|
uuids = new ArrayList<>();
|
388
|
for (TermVocabularyDto termDto: characterDto.getSupportedCategoricalEnumerations()){
|
389
|
uuids.add(termDto.getUuid());
|
390
|
}
|
391
|
if (!uuids.isEmpty()){
|
392
|
termVocs = vocabularyService.load(uuids, null);
|
393
|
|
394
|
for (TermVocabulary voc: termVocs){
|
395
|
character.addSupportedCategoricalEnumeration(voc);
|
396
|
}
|
397
|
}
|
398
|
node.setTerm(character);
|
399
|
mergeResult = dao.merge(node, true);
|
400
|
result.addUpdatedObject(mergeResult.getMergedEntity());
|
401
|
}
|
402
|
}
|
403
|
}
|
404
|
return result;
|
405
|
}
|
406
|
|
407
|
@Override
|
408
|
public UpdateResult saveNewCharacterNodeDtoMap(Map<Character, CharacterNodeDto> dtos, UUID vocabularyUuid){
|
409
|
UpdateResult result = new UpdateResult();
|
410
|
UpdateResult resultLocal = new UpdateResult();
|
411
|
for (Entry<Character, CharacterNodeDto> dtoEntry: dtos.entrySet()){
|
412
|
resultLocal = createChildNode(dtoEntry.getValue().getParentUuid(), dtoEntry.getKey(), vocabularyUuid);
|
413
|
dtoEntry.getValue().setUuid(resultLocal.getCdmEntity().getUuid());
|
414
|
result.includeResult(resultLocal);
|
415
|
}
|
416
|
List<CharacterNodeDto> dtoList = new ArrayList<>(dtos.values());
|
417
|
result.includeResult(saveCharacterNodeDtoList(dtoList));
|
418
|
return result;
|
419
|
|
420
|
}
|
421
|
|
422
|
|
423
|
|
424
|
}
|