Merge "Fix: Update OpenSSF Scorecard to RelEng reusable"
[cps.git] / cps-rest / src / main / java / org / onap / cps / rest / controller / DataRestController.java
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2020-2022 Bell Canada.
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2021-2025 Nordix Foundation
6  *  Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
7  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
8  *  ================================================================================
9  *  Licensed under the Apache License, Version 2.0 (the "License");
10  *  you may not use this file except in compliance with the License.
11  *  You may obtain a copy of the License at
12  *
13  *        http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *  Unless required by applicable law or agreed to in writing, software
16  *  distributed under the License is distributed on an "AS IS" BASIS,
17  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  *  See the License for the specific language governing permissions and
19  *  limitations under the License.
20  *
21  *  SPDX-License-Identifier: Apache-2.0
22  *  ============LICENSE_END=========================================================
23  */
24
25 package org.onap.cps.rest.controller;
26
27 import io.micrometer.core.annotation.Timed;
28 import jakarta.validation.ValidationException;
29 import java.time.OffsetDateTime;
30 import java.time.format.DateTimeFormatter;
31 import java.util.List;
32 import java.util.Map;
33 import lombok.RequiredArgsConstructor;
34 import org.apache.commons.lang3.StringUtils;
35 import org.onap.cps.api.CpsDataService;
36 import org.onap.cps.api.CpsFacade;
37 import org.onap.cps.api.parameters.FetchDescendantsOption;
38 import org.onap.cps.rest.api.CpsDataApi;
39 import org.onap.cps.utils.ContentType;
40 import org.onap.cps.utils.JsonObjectMapper;
41 import org.onap.cps.utils.XmlFileUtils;
42 import org.springframework.http.HttpStatus;
43 import org.springframework.http.ResponseEntity;
44 import org.springframework.web.bind.annotation.RequestMapping;
45 import org.springframework.web.bind.annotation.RestController;
46
47 @RestController
48 @RequestMapping("${rest.api.cps-base-path}")
49 @RequiredArgsConstructor
50 public class DataRestController implements CpsDataApi {
51
52     private static final String ROOT_XPATH = "/";
53     private static final String ISO_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
54     private static final DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_FORMAT);
55
56     private final CpsFacade cpsFacade;
57     private final CpsDataService cpsDataService;
58     private final JsonObjectMapper jsonObjectMapper;
59
60     @Override
61     public ResponseEntity<String> createNode(final String apiVersion,
62                                              final String dataspaceName, final String anchorName,
63                                              final String nodeData, final String parentNodeXpath,
64                                              final Boolean dryRunEnabled, final String observedTimestamp,
65                                              final String contentTypeInHeader) {
66         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
67         if (Boolean.TRUE.equals(dryRunEnabled)) {
68             cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
69             return ResponseEntity.ok().build();
70         } else {
71             if (isRootXpath(parentNodeXpath)) {
72                 cpsDataService.saveData(dataspaceName, anchorName, nodeData,
73                         toOffsetDateTime(observedTimestamp), contentType);
74             } else {
75                 cpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath,
76                         nodeData, toOffsetDateTime(observedTimestamp), contentType);
77             }
78             return ResponseEntity.status(HttpStatus.CREATED).build();
79         }
80     }
81
82     @Override
83     public ResponseEntity<Void> deleteDataNode(final String apiVersion, final String dataspaceName,
84                                                final String anchorName, final String xpath,
85                                                final String observedTimestamp) {
86         cpsDataService.deleteDataNode(dataspaceName, anchorName, xpath, toOffsetDateTime(observedTimestamp));
87         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
88     }
89
90     @Override
91     public ResponseEntity<String> addListElements(final String apiVersion, final String dataspaceName,
92                                                   final String anchorName, final String parentNodeXpath,
93                                                   final String nodeData, final Boolean dryRunEnabled,
94                                                   final String observedTimestamp, final String contentTypeInHeader) {
95         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
96         if (Boolean.TRUE.equals(dryRunEnabled)) {
97             cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
98             return ResponseEntity.ok().build();
99         } else {
100             cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath,
101                     nodeData, toOffsetDateTime(observedTimestamp), contentType);
102         }
103         return ResponseEntity.status(HttpStatus.CREATED).build();
104     }
105
106     @Override
107     @Timed(value = "cps.data.controller.datanode.get.v1", description = "Time taken to get data node")
108     public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String dataspaceName,
109                                                               final String anchorName,
110                                                               final String xpath,
111                                                               final Boolean includeDescendants) {
112         final FetchDescendantsOption fetchDescendantsOption =
113             FetchDescendantsOption.getFetchDescendantsOption(includeDescendants);
114         final Map<String, Object> dataNodeAsMap =
115             cpsFacade.getFirstDataNodeByAnchor(dataspaceName, anchorName, xpath, fetchDescendantsOption);
116         return new ResponseEntity<>(dataNodeAsMap, HttpStatus.OK);
117     }
118
119     @Override
120     @Timed(value = "cps.data.controller.datanode.get.v2", description = "Time taken to get data node")
121     public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName,
122                                                                 final String xpath,
123                                                                 final String fetchDescendantsOptionAsString,
124                                                                 final String contentTypeInHeader) {
125         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
126         final FetchDescendantsOption fetchDescendantsOption =
127                 FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
128         final List<Map<String, Object>> dataNodesAsMaps =
129             cpsFacade.getDataNodesByAnchor(dataspaceName, anchorName, xpath, fetchDescendantsOption);
130         return buildResponseEntity(dataNodesAsMaps, contentType);
131     }
132
133     @Override
134     @Timed(value = "cps.data.controller.datanode.get.v3", description = "Time taken to get data node")
135     public ResponseEntity<Object> getNodeByDataspaceAndAnchorV3(final String dataspaceName, final String anchorName,
136                                                                 final String xpath,
137                                                                 final String fetchDescendantsOptionAsString,
138                                                                 final String contentTypeInHeader) {
139         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
140         final FetchDescendantsOption fetchDescendantsOption =
141             FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
142         final Map<String, Object> dataNodesAsMap =
143             cpsFacade.getDataNodesByAnchorV3(dataspaceName, anchorName, xpath, fetchDescendantsOption);
144         return buildResponseEntity(dataNodesAsMap, contentType);
145     }
146
147     @Override
148     public ResponseEntity<Object> updateNodeLeaves(final String apiVersion, final String dataspaceName,
149                                                    final String anchorName, final String nodeData,
150                                                    final String parentNodeXpath, final Boolean dryRunEnabled,
151                                                    final String observedTimestamp, final String contentTypeInHeader) {
152         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
153         if (Boolean.TRUE.equals(dryRunEnabled)) {
154             cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
155             return ResponseEntity.ok().build();
156         } else {
157             cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath,
158                     nodeData, toOffsetDateTime(observedTimestamp), contentType);
159         }
160         return ResponseEntity.status(HttpStatus.OK).build();
161     }
162
163     @Override
164     public ResponseEntity<Object> replaceNode(final String apiVersion, final String dataspaceName,
165                                               final String anchorName, final String nodeData,
166                                               final String parentNodeXpath, final Boolean dryRunEnabled,
167                                               final String observedTimestamp, final String contentTypeInHeader) {
168         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
169         if (Boolean.TRUE.equals(dryRunEnabled)) {
170             cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
171             return ResponseEntity.ok().build();
172         } else {
173             cpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath,
174                     nodeData, toOffsetDateTime(observedTimestamp), contentType);
175         }
176         return ResponseEntity.status(HttpStatus.OK).build();
177     }
178
179     @Override
180     public ResponseEntity<Object> replaceListContent(final String apiVersion, final String dataspaceName,
181                                                      final String anchorName, final String parentNodeXpath,
182                                                      final String nodeData, final Boolean dryRunEnabled,
183                                                      final String observedTimestamp, final String contentTypeInHeader) {
184         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
185         if (Boolean.TRUE.equals(dryRunEnabled)) {
186             cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData,
187                     ContentType.JSON);
188             return ResponseEntity.ok().build();
189         } else {
190             cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath,
191                     nodeData, toOffsetDateTime(observedTimestamp), contentType);
192         }
193         return ResponseEntity.status(HttpStatus.OK).build();
194     }
195
196     @Override
197     public ResponseEntity<Void> deleteListOrListElement(final String dataspaceName, final String anchorName,
198                                                         final String listElementXpath, final String observedTimestamp) {
199         cpsDataService
200             .deleteListOrListElement(dataspaceName, anchorName, listElementXpath, toOffsetDateTime(observedTimestamp));
201         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
202     }
203
204     private ResponseEntity<Object> buildResponseEntity(final Object dataMaps, final ContentType contentType) {
205         final String responseData;
206         if (ContentType.XML.equals(contentType)) {
207             responseData = XmlFileUtils.convertDataMapsToXml(dataMaps);
208         } else {
209             responseData = jsonObjectMapper.asJsonString(dataMaps);
210         }
211         return new ResponseEntity<>(responseData, HttpStatus.OK);
212     }
213
214     private static boolean isRootXpath(final String xpath) {
215         return ROOT_XPATH.equals(xpath);
216     }
217
218     private static OffsetDateTime toOffsetDateTime(final String datetTimestamp) {
219         try {
220             return StringUtils.isEmpty(datetTimestamp)
221                 ? null : OffsetDateTime.parse(datetTimestamp, ISO_TIMESTAMP_FORMATTER);
222         } catch (final Exception exception) {
223             throw new ValidationException(
224                 String.format("observed-timestamp must be in '%s' format", ISO_TIMESTAMP_FORMAT));
225         }
226     }
227 }