2 * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.sal.restconf.impl;
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import java.io.ByteArrayInputStream;
13 import java.io.ByteArrayOutputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.lang.annotation.Annotation;
18 import java.nio.charset.StandardCharsets;
19 import java.util.Collections;
20 import java.util.List;
21 import javax.ws.rs.core.MediaType;
22 import javax.ws.rs.core.MultivaluedHashMap;
23 import javax.ws.rs.core.MultivaluedMap;
24 import javax.ws.rs.core.PathSegment;
25 import javax.ws.rs.core.UriBuilder;
26 import javax.ws.rs.core.UriInfo;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.netconf.sal.rest.impl.JsonNormalizedNodeBodyReader;
29 import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeJsonBodyWriter;
30 import org.opendaylight.netconf.sal.restconf.api.JSONRestconfService;
31 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
32 import org.opendaylight.yangtools.yang.common.OperationFailedException;
33 import org.opendaylight.yangtools.yang.common.RpcError;
34 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
35 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * Implementation of the JSONRestconfService interface.
42 * @author Thomas Pantelis
44 public class JSONRestconfServiceImpl implements JSONRestconfService, AutoCloseable {
45 private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceImpl.class);
47 private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
49 @SuppressWarnings("checkstyle:IllegalCatch")
51 public void put(final String uriPath, final String payload) throws OperationFailedException {
52 Preconditions.checkNotNull(payload, "payload can't be null");
54 LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
56 final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
57 final NormalizedNodeContext context = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, false);
59 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
60 LOG.debug("Parsed NormalizedNode: {}", context.getData());
63 RestconfImpl.getInstance().updateConfigurationData(uriPath, context, new SimpleUriInfo(uriPath));
64 } catch (final Exception e) {
65 propagateExceptionAs(uriPath, e, "PUT");
69 @SuppressWarnings("checkstyle:IllegalCatch")
71 public void post(final String uriPath, final String payload)
72 throws OperationFailedException {
73 Preconditions.checkNotNull(payload, "payload can't be null");
75 LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
77 final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
78 final NormalizedNodeContext context = JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, true);
80 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
81 LOG.debug("Parsed NormalizedNode: {}", context.getData());
84 RestconfImpl.getInstance().createConfigurationData(uriPath, context, new SimpleUriInfo(uriPath));
85 } catch (final Exception e) {
86 propagateExceptionAs(uriPath, e, "POST");
90 @SuppressWarnings("checkstyle:IllegalCatch")
92 public void delete(final String uriPath) throws OperationFailedException {
93 LOG.debug("delete: uriPath: {}", uriPath);
96 RestconfImpl.getInstance().deleteConfigurationData(uriPath);
97 } catch (final Exception e) {
98 propagateExceptionAs(uriPath, e, "DELETE");
102 @SuppressWarnings("checkstyle:IllegalCatch")
104 public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
105 throws OperationFailedException {
106 LOG.debug("get: uriPath: {}", uriPath);
109 NormalizedNodeContext readData;
110 final SimpleUriInfo uriInfo = new SimpleUriInfo(uriPath);
111 if (datastoreType == LogicalDatastoreType.CONFIGURATION) {
112 readData = RestconfImpl.getInstance().readConfigurationData(uriPath, uriInfo);
114 readData = RestconfImpl.getInstance().readOperationalData(uriPath, uriInfo);
117 final Optional<String> result = Optional.of(toJson(readData));
119 LOG.debug("get returning: {}", result.get());
122 } catch (final Exception e) {
123 if (!isDataMissing(e)) {
124 propagateExceptionAs(uriPath, e, "GET");
127 LOG.debug("Data missing - returning absent");
128 return Optional.absent();
132 @SuppressWarnings("checkstyle:IllegalCatch")
134 public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
135 throws OperationFailedException {
136 Preconditions.checkNotNull(uriPath, "uriPath can't be null");
138 final String actualInput = input.isPresent() ? input.get() : null;
140 LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
142 String output = null;
144 NormalizedNodeContext outputContext;
145 if (actualInput != null) {
146 final InputStream entityStream = new ByteArrayInputStream(actualInput.getBytes(StandardCharsets.UTF_8));
147 final NormalizedNodeContext inputContext =
148 JsonNormalizedNodeBodyReader.readFrom(uriPath, entityStream, true);
150 LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
151 .getInstanceIdentifier());
152 LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
154 outputContext = RestconfImpl.getInstance().invokeRpc(uriPath, inputContext, null);
156 outputContext = RestconfImpl.getInstance().invokeRpc(uriPath, "", null);
159 if (outputContext.getData() != null) {
160 output = toJson(outputContext);
162 } catch (final Exception e) {
163 propagateExceptionAs(uriPath, e, "RPC");
166 return Optional.fromNullable(output);
170 public void close() {
173 private static String toJson(final NormalizedNodeContext readData) throws IOException {
174 final NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
175 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
176 writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
177 MediaType.APPLICATION_JSON_TYPE, null, outputStream);
178 return outputStream.toString(StandardCharsets.UTF_8.name());
181 private static boolean isDataMissing(final Exception exception) {
182 boolean dataMissing = false;
183 if (exception instanceof RestconfDocumentedException) {
184 final RestconfDocumentedException rde = (RestconfDocumentedException)exception;
185 if (!rde.getErrors().isEmpty()) {
186 if (rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING) {
195 private static void propagateExceptionAs(final String uriPath, final Exception exception, final String operation)
196 throws OperationFailedException {
197 LOG.debug("Error for uriPath: {}", uriPath, exception);
199 if (exception instanceof RestconfDocumentedException) {
200 throw new OperationFailedException(String.format(
201 "%s failed for URI %s", operation, uriPath), exception.getCause(),
202 toRpcErrors(((RestconfDocumentedException)exception).getErrors()));
205 throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
208 private static RpcError[] toRpcErrors(final List<RestconfError> from) {
209 final RpcError[] to = new RpcError[from.size()];
211 for (final RestconfError e: from) {
212 to[index++] = RpcResultBuilder.newError(toRpcErrorType(e.getErrorType()), e.getErrorTag().getTagValue(),
213 e.getErrorMessage());
219 private static ErrorType toRpcErrorType(final RestconfError.ErrorType errorType) {
222 return ErrorType.TRANSPORT;
225 return ErrorType.RPC;
228 return ErrorType.PROTOCOL;
231 return ErrorType.APPLICATION;
236 private static class SimpleUriInfo implements UriInfo {
237 private final String path;
238 private final MultivaluedMap<String, String> queryParams;
240 SimpleUriInfo(String path) {
241 this(path, new MultivaluedHashMap<>());
244 SimpleUriInfo(String path, MultivaluedMap<String, String> queryParams) {
246 this.queryParams = queryParams;
250 public String getPath() {
255 public String getPath(boolean decode) {
260 public List<PathSegment> getPathSegments() {
261 throw new UnsupportedOperationException();
265 public List<PathSegment> getPathSegments(boolean decode) {
266 throw new UnsupportedOperationException();
270 public URI getRequestUri() {
271 return URI.create(path);
275 public UriBuilder getRequestUriBuilder() {
276 return UriBuilder.fromUri(getRequestUri());
280 public URI getAbsolutePath() {
281 return getRequestUri();
285 public UriBuilder getAbsolutePathBuilder() {
286 return UriBuilder.fromUri(getAbsolutePath());
290 public URI getBaseUri() {
291 return URI.create("");
295 public UriBuilder getBaseUriBuilder() {
296 return UriBuilder.fromUri(getBaseUri());
300 public MultivaluedMap<String, String> getPathParameters() {
301 return new MultivaluedHashMap<>();
305 public MultivaluedMap<String, String> getPathParameters(boolean decode) {
306 return getPathParameters();
310 public MultivaluedMap<String, String> getQueryParameters() {
315 public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
316 return getQueryParameters();
320 public List<String> getMatchedURIs() {
321 return Collections.emptyList();
325 public List<String> getMatchedURIs(boolean decode) {
326 return getMatchedURIs();
330 public List<Object> getMatchedResources() {
331 return Collections.emptyList();
335 public URI resolve(URI uri) {
340 public URI relativize(URI uri) {