blob: 4cd004ad27972964542711a944a24433349b3044 [file] [log] [blame]
jingan7c5bf1f2017-02-09 02:58:09 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
jingan7c5bf1f2017-02-09 02:58:09 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.restconf.utils;
18
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.node.ObjectNode;
21import org.apache.commons.io.IOUtils;
22import org.onlab.osgi.DefaultServiceDirectory;
Sean Condon13b16812018-01-25 10:31:49 +000023import org.onosproject.restconf.api.RestconfError;
jingan7c5bf1f2017-02-09 02:58:09 -080024import org.onosproject.restconf.api.RestconfException;
Henry Yuc10f7fc2017-07-26 13:42:08 -040025import org.onosproject.restconf.api.RestconfRpcOutput;
Henry Yu14af7782017-03-09 19:33:36 -050026import org.onosproject.yang.model.DataNode;
Henry Yuc10f7fc2017-07-26 13:42:08 -040027import org.onosproject.yang.model.DefaultResourceData;
jingan7c5bf1f2017-02-09 02:58:09 -080028import org.onosproject.yang.model.ResourceData;
29import org.onosproject.yang.model.ResourceId;
Henry Yuc10f7fc2017-07-26 13:42:08 -040030import org.onosproject.yang.model.RpcOutput;
Henry Yu14af7782017-03-09 19:33:36 -050031import org.onosproject.yang.runtime.CompositeData;
32import org.onosproject.yang.runtime.CompositeStream;
33import org.onosproject.yang.runtime.DefaultCompositeData;
34import org.onosproject.yang.runtime.DefaultCompositeStream;
Henry Yu14af7782017-03-09 19:33:36 -050035import org.onosproject.yang.runtime.DefaultRuntimeContext;
36import org.onosproject.yang.runtime.RuntimeContext;
37import org.onosproject.yang.runtime.YangRuntimeService;
Henry Yuc10f7fc2017-07-26 13:42:08 -040038import org.slf4j.Logger;
39import org.slf4j.LoggerFactory;
Henry Yu14af7782017-03-09 19:33:36 -050040
Henry Yuc10f7fc2017-07-26 13:42:08 -040041import javax.ws.rs.core.Response;
42import javax.ws.rs.core.UriBuilder;
Henry Yu14af7782017-03-09 19:33:36 -050043import java.io.IOException;
44import java.io.InputStream;
Henry Yuc10f7fc2017-07-26 13:42:08 -040045import java.net.URI;
Sean Condon13b16812018-01-25 10:31:49 +000046import java.util.Optional;
jingan7c5bf1f2017-02-09 02:58:09 -080047
Sean Condon13b16812018-01-25 10:31:49 +000048import static javax.ws.rs.core.Response.Status.*;
jingan7c5bf1f2017-02-09 02:58:09 -080049
50/**
51 * Utilities used by the RESTCONF app.
52 */
53public final class RestconfUtils {
54 /**
55 * No instantiation.
56 */
57 private RestconfUtils() {
58 }
59
60 /**
61 * Data format required by YangRuntime Service.
62 */
jingan364cec32017-03-10 12:29:11 -080063 private static final String JSON_FORMAT = "JSON";
Henry Yuc10f7fc2017-07-26 13:42:08 -040064 private static final String SLASH = "/";
jingan7c5bf1f2017-02-09 02:58:09 -080065
66 private static final YangRuntimeService YANG_RUNTIME =
67 DefaultServiceDirectory.getService(YangRuntimeService.class);
68
Henry Yuc10f7fc2017-07-26 13:42:08 -040069 private static final Logger log = LoggerFactory.getLogger(RestconfUtils.class);
70
jingan7c5bf1f2017-02-09 02:58:09 -080071 /**
72 * Converts an input stream to JSON objectNode.
73 *
Henry Yu14af7782017-03-09 19:33:36 -050074 * @param inputStream the InputStream from Resource Data
jingan7c5bf1f2017-02-09 02:58:09 -080075 * @return JSON representation of the data resource
76 */
77 public static ObjectNode convertInputStreamToObjectNode(InputStream inputStream) {
78 ObjectNode rootNode;
79 ObjectMapper mapper = new ObjectMapper();
80 try {
81 rootNode = (ObjectNode) mapper.readTree(inputStream);
82 } catch (IOException e) {
Sean Condon13b16812018-01-25 10:31:49 +000083 throw new RestconfException("ERROR: InputStream failed to parse",
84 e, RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
85 Optional.empty());
jingan7c5bf1f2017-02-09 02:58:09 -080086 }
87 return rootNode;
88 }
89
90 /**
91 * Convert ObjectNode to InputStream.
92 *
Henry Yu14af7782017-03-09 19:33:36 -050093 * @param rootNode JSON representation of the data resource
jingan7c5bf1f2017-02-09 02:58:09 -080094 * @return the InputStream from Resource Data
95 */
96 public static InputStream convertObjectNodeToInputStream(ObjectNode rootNode) {
jingan364cec32017-03-10 12:29:11 -080097 String json = rootNode.toString();
jingan7c5bf1f2017-02-09 02:58:09 -080098 InputStream inputStream;
99 try {
100 inputStream = IOUtils.toInputStream(json);
101 } catch (Exception e) {
Sean Condon13b16812018-01-25 10:31:49 +0000102 throw new RestconfException("ERROR: Json Node failed to parse", e,
103 RestconfError.ErrorTag.MALFORMED_MESSAGE, BAD_REQUEST,
104 Optional.empty());
jingan7c5bf1f2017-02-09 02:58:09 -0800105 }
106 return inputStream;
107 }
108
109 /**
Henry Yu830b5dc2017-11-16 10:44:45 -0500110 * Convert URI to ResourceId. If the URI represents the datastore resource
111 * (i.e., the root of datastore), a null is returned.
jingan7c5bf1f2017-02-09 02:58:09 -0800112 *
Henry Yu14af7782017-03-09 19:33:36 -0500113 * @param uri URI of the data resource
jingan7c5bf1f2017-02-09 02:58:09 -0800114 * @return resource identifier
115 */
Henry Yuc10f7fc2017-07-26 13:42:08 -0400116 public static ResourceId convertUriToRid(URI uri) {
jingan7c5bf1f2017-02-09 02:58:09 -0800117 ResourceData resourceData = convertJsonToDataNode(uri, null);
118 return resourceData.resourceId();
119 }
120
121 /**
122 * Convert URI and ObjectNode to ResourceData.
123 *
Henry Yu14af7782017-03-09 19:33:36 -0500124 * @param uri URI of the data resource
125 * @param rootNode JSON representation of the data resource
jingan7c5bf1f2017-02-09 02:58:09 -0800126 * @return represents type of node in data store
127 */
Henry Yuc10f7fc2017-07-26 13:42:08 -0400128 public static ResourceData convertJsonToDataNode(URI uri,
Henry Yu14af7782017-03-09 19:33:36 -0500129 ObjectNode rootNode) {
130 RuntimeContext.Builder runtimeContextBuilder = new DefaultRuntimeContext.Builder();
jingan7c5bf1f2017-02-09 02:58:09 -0800131 runtimeContextBuilder.setDataFormat(JSON_FORMAT);
132 RuntimeContext context = runtimeContextBuilder.build();
Henry Yuc10f7fc2017-07-26 13:42:08 -0400133 ResourceData resourceData = null;
jingan7c5bf1f2017-02-09 02:58:09 -0800134 InputStream jsonData = null;
Henry Yuc10f7fc2017-07-26 13:42:08 -0400135 try {
136 if (rootNode != null) {
137 jsonData = convertObjectNodeToInputStream(rootNode);
138 }
139 String uriString = getRawUriPath(uri);
140
141 CompositeStream compositeStream = new DefaultCompositeStream(uriString, jsonData);
142 // CompositeStream --- YangRuntimeService ---> CompositeData.
143 CompositeData compositeData = YANG_RUNTIME.decode(compositeStream, context);
144 resourceData = compositeData.resourceData();
Sean Condon13b16812018-01-25 10:31:49 +0000145 } catch (RestconfException ex) {
146 throw ex;
Henry Yuc10f7fc2017-07-26 13:42:08 -0400147 } catch (Exception ex) {
Yuta HIGUCHI6f73bc62018-03-01 17:09:13 -0800148 log.error("convertJsonToDataNode failure: {}", ex.getMessage(), ex);
149 log.info("Failed JSON: \n{}", rootNode);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400150 log.debug("convertJsonToDataNode failure", ex);
Sean Condon13b16812018-01-25 10:31:49 +0000151 throw new RestconfException("ERROR: JSON cannot be converted to DataNode",
152 ex, RestconfError.ErrorTag.OPERATION_FAILED, INTERNAL_SERVER_ERROR,
153 Optional.of(uri.getPath()));
jingan7c5bf1f2017-02-09 02:58:09 -0800154 }
Henry Yuc10f7fc2017-07-26 13:42:08 -0400155 if (resourceData == null) {
156 throw new RestconfException("ERROR: JSON cannot be converted to DataNode",
Sean Condon13b16812018-01-25 10:31:49 +0000157 RestconfError.ErrorTag.DATA_MISSING, CONFLICT,
158 Optional.of(uri.getPath()), Optional.empty());
Henry Yuc10f7fc2017-07-26 13:42:08 -0400159 }
jingan7c5bf1f2017-02-09 02:58:09 -0800160 return resourceData;
161 }
162
Henry Yuc10f7fc2017-07-26 13:42:08 -0400163 private static String getRawUriPath(URI uri) {
164 String path = uri.getRawPath();
sonugupta-huawei8065a3b2017-09-08 16:50:17 +0530165 if (path.equals("/onos/restconf/data")) {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400166 return null;
167 }
168
sonugupta-huawei8065a3b2017-09-08 16:50:17 +0530169 return path.replaceAll("^/onos/restconf/data/", "")
170 .replaceAll("^/onos/restconf/operations/", "");
Henry Yuc10f7fc2017-07-26 13:42:08 -0400171 }
172
jingan7c5bf1f2017-02-09 02:58:09 -0800173 /**
174 * Convert Resource Id and Data Node to Json ObjectNode.
175 *
Henry Yu14af7782017-03-09 19:33:36 -0500176 * @param rid resource identifier
177 * @param dataNode represents type of node in data store
jingan7c5bf1f2017-02-09 02:58:09 -0800178 * @return JSON representation of the data resource
179 */
180 public static ObjectNode convertDataNodeToJson(ResourceId rid, DataNode dataNode) {
Henry Yu14af7782017-03-09 19:33:36 -0500181 RuntimeContext.Builder runtimeContextBuilder = DefaultRuntimeContext.builder();
jingan7c5bf1f2017-02-09 02:58:09 -0800182 runtimeContextBuilder.setDataFormat(JSON_FORMAT);
183 RuntimeContext context = runtimeContextBuilder.build();
184 DefaultResourceData.Builder resourceDataBuilder = DefaultResourceData.builder();
185 resourceDataBuilder.addDataNode(dataNode);
186 resourceDataBuilder.resourceId(rid);
187 ResourceData resourceData = resourceDataBuilder.build();
188 DefaultCompositeData.Builder compositeDataBuilder = DefaultCompositeData.builder();
189 compositeDataBuilder.resourceData(resourceData);
190 CompositeData compositeData = compositeDataBuilder.build();
Henry Yuc10f7fc2017-07-26 13:42:08 -0400191 ObjectNode rootNode = null;
192 try {
193 // CompositeData --- YangRuntimeService ---> CompositeStream.
194 CompositeStream compositeStream = YANG_RUNTIME.encode(compositeData, context);
195 InputStream inputStream = compositeStream.resourceData();
196 rootNode = convertInputStreamToObjectNode(inputStream);
197 } catch (Exception ex) {
198 log.error("convertInputStreamToObjectNode failure: {}", ex.getMessage());
199 log.debug("convertInputStreamToObjectNode failure", ex);
200 }
jingan7c5bf1f2017-02-09 02:58:09 -0800201 if (rootNode == null) {
202 throw new RestconfException("ERROR: InputStream can not be convert to ObjectNode",
Sean Condon13b16812018-01-25 10:31:49 +0000203 null, RestconfError.ErrorTag.DATA_MISSING, CONFLICT,
204 Optional.empty());
jingan7c5bf1f2017-02-09 02:58:09 -0800205 }
206 return rootNode;
207 }
Henry Yuc10f7fc2017-07-26 13:42:08 -0400208
209 /**
210 * Removes the last path segment from the given URI. That is, returns
211 * the parent of the given URI.
212 *
213 * @param uri given URI
214 * @return parent URI
215 */
216 public static URI rmLastPathSegment(URI uri) {
217 if (uri == null) {
218 return null;
219 }
220
221 UriBuilder builder = UriBuilder.fromUri(uri);
222 String newPath = rmLastPathSegmentStr(uri.getRawPath());
223 builder.replacePath(newPath);
224
225 return builder.build();
226 }
227
228 private static String rmLastPathSegmentStr(String rawPath) {
229 if (rawPath == null) {
230 return null;
231 }
232 int pos = rawPath.lastIndexOf(SLASH);
233 if (pos <= 0) {
234 return null;
235 }
236
237 return rawPath.substring(0, pos);
238 }
239
240 /**
241 * Creates a RESTCONF RPC output object from a given YANG RPC output object.
242 *
243 * @param cmdId resource ID of the RPC
244 * @param rpcOutput given RPC output in YANG format
245 * @return RPC output in RESTCONF format
246 */
247 public static RestconfRpcOutput convertRpcOutput(ResourceId cmdId, RpcOutput rpcOutput) {
248 RestconfRpcOutput restconfRpcOutput = new RestconfRpcOutput();
249
250 restconfRpcOutput.status(convertResponseStatus(rpcOutput.status()));
251 if (rpcOutput.data() != null) {
252 restconfRpcOutput.output(convertDataNodeToJson(cmdId, rpcOutput.data()));
253 }
254
255 return restconfRpcOutput;
256 }
257
258 private static Response.Status convertResponseStatus(RpcOutput.Status status) {
259 switch (status) {
260 case RPC_SUCCESS:
261 return OK;
262 case RPC_FAILURE:
263 return EXPECTATION_FAILED;
264 case RPC_NODATA:
265 return NO_CONTENT;
266 case RPC_TIMEOUT:
267 return REQUEST_TIMEOUT;
268 default:
269 return BAD_REQUEST;
270 }
271 }
jingan7c5bf1f2017-02-09 02:58:09 -0800272}