Get Data under anchor using single root
[cps.git] / cps-ri / src / main / java / org / onap / cps / spi / impl / CpsDataPersistenceServiceImpl.java
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 Nordix Foundation
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2020-2021 Bell Canada.
6  *  ================================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.onap.cps.spi.impl;
23
24 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
25
26 import com.google.common.collect.ImmutableSet;
27 import com.google.common.collect.ImmutableSet.Builder;
28 import com.google.gson.Gson;
29 import com.google.gson.GsonBuilder;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.stream.Collectors;
35 import org.onap.cps.spi.CpsDataPersistenceService;
36 import org.onap.cps.spi.FetchDescendantsOption;
37 import org.onap.cps.spi.entities.AnchorEntity;
38 import org.onap.cps.spi.entities.DataspaceEntity;
39 import org.onap.cps.spi.entities.FragmentEntity;
40 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
41 import org.onap.cps.spi.model.DataNode;
42 import org.onap.cps.spi.model.DataNodeBuilder;
43 import org.onap.cps.spi.query.CpsPathQuery;
44 import org.onap.cps.spi.query.CpsPathQueryType;
45 import org.onap.cps.spi.repository.AnchorRepository;
46 import org.onap.cps.spi.repository.DataspaceRepository;
47 import org.onap.cps.spi.repository.FragmentRepository;
48 import org.springframework.beans.factory.annotation.Autowired;
49 import org.springframework.dao.DataIntegrityViolationException;
50 import org.springframework.stereotype.Service;
51
52 @Service
53 public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService {
54
55     @Autowired
56     private DataspaceRepository dataspaceRepository;
57
58     @Autowired
59     private AnchorRepository anchorRepository;
60
61     @Autowired
62     private FragmentRepository fragmentRepository;
63
64     private static final Gson GSON = new GsonBuilder().create();
65
66     @Override
67     public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath,
68         final DataNode dataNode) {
69         final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentXpath);
70         final FragmentEntity fragmentEntity =
71             toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode);
72         parentFragment.getChildFragments().add(fragmentEntity);
73         fragmentRepository.save(parentFragment);
74     }
75
76     @Override
77     public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) {
78         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
79         final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
80         final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity,
81             dataNode);
82         try {
83             fragmentRepository.save(fragmentEntity);
84         } catch (final DataIntegrityViolationException exception) {
85             throw  AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception);
86         }
87     }
88
89     /**
90      * Convert DataNode object into Fragment and places the result in the fragments placeholder. Performs same action
91      * for all DataNode children recursively.
92      *
93      * @param dataspaceEntity       dataspace
94      * @param anchorEntity          anchorEntity
95      * @param dataNodeToBeConverted dataNode
96      * @return a Fragment built from current DataNode
97      */
98     private static FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity,
99         final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) {
100         final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted);
101         final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
102         for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
103             final FragmentEntity childFragment =
104                 convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(),
105                     childDataNode);
106             childFragmentsImmutableSetBuilder.add(childFragment);
107         }
108         parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build());
109         return parentFragment;
110     }
111
112     private static FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity,
113         final AnchorEntity anchorEntity, final DataNode dataNode) {
114         return FragmentEntity.builder()
115             .dataspace(dataspaceEntity)
116             .anchor(anchorEntity)
117             .xpath(dataNode.getXpath())
118             .attributes(GSON.toJson(dataNode.getLeaves()))
119             .build();
120     }
121
122     @Override
123     public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
124         final FetchDescendantsOption fetchDescendantsOption) {
125         final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
126         return toDataNode(fragmentEntity, fetchDescendantsOption);
127     }
128
129     private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
130         final String xpath) {
131         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
132         final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
133         if (isRootXpath(xpath)) {
134             return fragmentRepository.getFirstByDataspaceAndAnchor(dataspaceEntity, anchorEntity);
135         } else {
136             return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity,
137                 xpath);
138         }
139     }
140
141     @Override
142     public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
143         final FetchDescendantsOption fetchDescendantsOption) {
144         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
145         final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
146         final CpsPathQuery cpsPathQuery = CpsPathQuery.createFrom(cpsPath);
147         final List<FragmentEntity> fragmentEntities;
148         if (CpsPathQueryType.XPATH_LEAF_VALUE.equals(cpsPathQuery.getCpsPathQueryType())) {
149             fragmentEntities = fragmentRepository
150                 .getByAnchorAndXpathAndLeafAttributes(anchorEntity.getId(), cpsPathQuery.getXpathPrefix(), cpsPathQuery
151                     .getLeafName(), cpsPathQuery.getLeafValue());
152         } else if (CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES.equals(cpsPathQuery.getCpsPathQueryType())) {
153             final String leafDataAsJson = GSON.toJson(cpsPathQuery.getLeavesData());
154             fragmentEntities = fragmentRepository
155                 .getByAnchorAndDescendentNameAndLeafValues(anchorEntity.getId(),
156                     cpsPathQuery.getDescendantName(), leafDataAsJson);
157         } else {
158             fragmentEntities = fragmentRepository
159                 .getByAnchorAndXpathEndsInDescendantName(anchorEntity.getId(), cpsPathQuery.getDescendantName());
160         }
161         return fragmentEntities.stream()
162             .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption))
163             .collect(Collectors.toUnmodifiableList());
164     }
165
166     private static DataNode toDataNode(final FragmentEntity fragmentEntity,
167         final FetchDescendantsOption fetchDescendantsOption) {
168         final Map<String, Object> leaves = GSON.fromJson(fragmentEntity.getAttributes(), Map.class);
169         final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
170         return new DataNodeBuilder()
171             .withXpath(fragmentEntity.getXpath())
172             .withLeaves(leaves)
173             .withChildDataNodes(childDataNodes).build();
174     }
175
176     private static List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
177         final FetchDescendantsOption fetchDescendantsOption) {
178         if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) {
179             return fragmentEntity.getChildFragments().stream()
180                 .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption))
181                 .collect(Collectors.toUnmodifiableList());
182         }
183         return Collections.emptyList();
184     }
185
186     @Override
187     public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
188         final Map<String, Object> leaves) {
189         final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
190         fragmentEntity.setAttributes(GSON.toJson(leaves));
191         fragmentRepository.save(fragmentEntity);
192     }
193
194     @Override
195     public void replaceDataNodeTree(final String dataspaceName, final String anchorName, final DataNode dataNode) {
196         final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath());
197         removeExistingDescendants(fragmentEntity);
198
199         fragmentEntity.setAttributes(GSON.toJson(dataNode.getLeaves()));
200         final Set<FragmentEntity> childFragmentEntities = dataNode.getChildDataNodes().stream().map(
201             childDataNode -> convertToFragmentWithAllDescendants(
202                 fragmentEntity.getDataspace(), fragmentEntity.getAnchor(), childDataNode)
203         ).collect(Collectors.toUnmodifiableSet());
204         fragmentEntity.setChildFragments(childFragmentEntities);
205
206         fragmentRepository.save(fragmentEntity);
207     }
208
209     private void removeExistingDescendants(final FragmentEntity fragmentEntity) {
210         fragmentEntity.setChildFragments(Collections.emptySet());
211         fragmentRepository.save(fragmentEntity);
212     }
213
214     private boolean isRootXpath(final String xpath) {
215         return "/".equals(xpath) || "".equals(xpath);
216     }
217 }