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.annotation.Nullable;
22 import javax.ws.rs.core.MediaType;
23 import javax.ws.rs.core.MultivaluedHashMap;
24 import javax.ws.rs.core.MultivaluedMap;
25 import javax.ws.rs.core.PathSegment;
26 import javax.ws.rs.core.Response;
27 import javax.ws.rs.core.UriBuilder;
28 import javax.ws.rs.core.UriInfo;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
30 import org.opendaylight.netconf.sal.restconf.api.JSONRestconfService;
31 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
32 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
33 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
34 import org.opendaylight.restconf.common.errors.RestconfError;
35 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
36 import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
37 import org.opendaylight.restconf.jersey.providers.JsonNormalizedNodeBodyReader;
38 import org.opendaylight.restconf.jersey.providers.NormalizedNodeJsonBodyWriter;
39 import org.opendaylight.restconf.restful.services.api.TransactionServicesWrapper;
40 import org.opendaylight.restconf.restful.utils.RestconfDataServiceConstant;
41 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
42 import org.opendaylight.yangtools.yang.common.OperationFailedException;
43 import org.opendaylight.yangtools.yang.common.RpcError;
44 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
45 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * Implementation of the JSONRestconfService interface using the restconf Draft18 implementation.
52 * @author Thomas Pantelis
54 public class JSONRestconfServiceDraft18 implements JSONRestconfService, AutoCloseable {
55 private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceDraft18.class);
57 private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
59 private final TransactionServicesWrapper services;
60 private final DOMMountPointServiceHandler mountPointServiceHandler;
62 public JSONRestconfServiceDraft18(final TransactionServicesWrapper services,
63 final DOMMountPointServiceHandler mountPointServiceHandler) {
64 this.services = services;
65 this.mountPointServiceHandler = mountPointServiceHandler;
68 @SuppressWarnings("checkstyle:IllegalCatch")
70 public void put(final String uriPath, final String payload) throws OperationFailedException {
71 Preconditions.checkNotNull(payload, "payload can't be null");
73 LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
75 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, false);
77 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
78 LOG.debug("Parsed NormalizedNode: {}", context.getData());
81 services.putData(uriPath, context, new SimpleUriInfo(uriPath));
82 } catch (final Exception e) {
83 propagateExceptionAs(uriPath, e, "PUT");
87 @SuppressWarnings("checkstyle:IllegalCatch")
89 public void post(final String uriPath, final String payload)
90 throws OperationFailedException {
91 Preconditions.checkNotNull(payload, "payload can't be null");
93 LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
95 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, true);
97 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
98 LOG.debug("Parsed NormalizedNode: {}", context.getData());
101 services.postData(uriPath, context, new SimpleUriInfo(uriPath));
102 } catch (final Exception e) {
103 propagateExceptionAs(uriPath, e, "POST");
107 @SuppressWarnings("checkstyle:IllegalCatch")
109 public void delete(final String uriPath) throws OperationFailedException {
110 LOG.debug("delete: uriPath: {}", uriPath);
113 services.deleteData(uriPath);
114 } catch (final Exception e) {
115 propagateExceptionAs(uriPath, e, "DELETE");
119 @SuppressWarnings("checkstyle:IllegalCatch")
121 public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
122 throws OperationFailedException {
123 LOG.debug("get: uriPath: {}", uriPath);
126 final MultivaluedMap<String, String> queryParams = new MultivaluedHashMap<>();
127 queryParams.putSingle(RestconfDataServiceConstant.ReadData.CONTENT,
128 datastoreType == LogicalDatastoreType.CONFIGURATION ? RestconfDataServiceConstant.ReadData.CONFIG :
129 RestconfDataServiceConstant.ReadData.NONCONFIG);
131 final Response response = services.readData(uriPath, new SimpleUriInfo(uriPath, queryParams));
132 NormalizedNodeContext readData = (NormalizedNodeContext) response.getEntity();
134 final Optional<String> result = Optional.of(toJson(readData));
136 LOG.debug("get returning: {}", result.get());
139 } catch (final Exception e) {
140 if (!isDataMissing(e)) {
141 propagateExceptionAs(uriPath, e, "GET");
144 LOG.debug("Data missing - returning absent");
145 return Optional.absent();
149 @SuppressWarnings("checkstyle:IllegalCatch")
151 public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
152 throws OperationFailedException {
153 Preconditions.checkNotNull(uriPath, "uriPath can't be null");
155 final String actualInput = input.isPresent() ? input.get() : null;
157 LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
159 String output = null;
161 final NormalizedNodeContext inputContext = toNormalizedNodeContext(uriPath, actualInput, true);
163 LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
164 .getInstanceIdentifier());
165 LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
167 NormalizedNodeContext outputContext = services.invokeRpc(uriPath, inputContext, new SimpleUriInfo(uriPath));
169 if (outputContext.getData() != null) {
170 output = toJson(outputContext);
172 } catch (final Exception e) {
173 propagateExceptionAs(uriPath, e, "RPC");
176 return Optional.fromNullable(output);
180 public void close() {
183 private NormalizedNodeContext toNormalizedNodeContext(final String uriPath, @Nullable final String payload,
184 final boolean isPost) throws OperationFailedException {
185 final InstanceIdentifierContext<?> instanceIdentifierContext = ParserIdentifier.toInstanceIdentifier(
186 uriPath, ControllerContext.getInstance().getGlobalSchema(),
187 Optional.of(mountPointServiceHandler.get()));
189 if (payload == null) {
190 return new NormalizedNodeContext(instanceIdentifierContext, null);
193 final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
195 return JsonNormalizedNodeBodyReader.readFrom(instanceIdentifierContext, entityStream, isPost);
196 } catch (IOException e) {
197 propagateExceptionAs(uriPath, e, "GET");
202 private static String toJson(final NormalizedNodeContext readData) throws IOException {
203 final NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
204 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
205 writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
206 MediaType.APPLICATION_JSON_TYPE, null, outputStream);
207 return outputStream.toString(StandardCharsets.UTF_8.name());
210 private static boolean isDataMissing(final Exception exception) {
211 if (exception instanceof RestconfDocumentedException) {
212 final RestconfDocumentedException rde = (RestconfDocumentedException)exception;
213 return !rde.getErrors().isEmpty() && rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING;
219 private static void propagateExceptionAs(final String uriPath, final Exception exception, final String operation)
220 throws OperationFailedException {
221 LOG.debug("Error for uriPath: {}", uriPath, exception);
223 if (exception instanceof RestconfDocumentedException) {
224 throw new OperationFailedException(String.format(
225 "%s failed for URI %s", operation, uriPath), exception.getCause(),
226 toRpcErrors(((RestconfDocumentedException)exception).getErrors()));
229 throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
232 private static RpcError[] toRpcErrors(final List<RestconfError> from) {
233 final RpcError[] to = new RpcError[from.size()];
235 for (final RestconfError e: from) {
236 to[index++] = RpcResultBuilder.newError(toRpcErrorType(e.getErrorType()), e.getErrorTag().getTagValue(),
237 e.getErrorMessage());
243 private static ErrorType toRpcErrorType(final RestconfError.ErrorType errorType) {
246 return ErrorType.TRANSPORT;
248 return ErrorType.RPC;
250 return ErrorType.PROTOCOL;
252 return ErrorType.APPLICATION;
256 private static class SimpleUriInfo implements UriInfo {
257 private final String path;
258 private final MultivaluedMap<String, String> queryParams;
260 SimpleUriInfo(String path) {
261 this(path, new MultivaluedHashMap<>());
264 SimpleUriInfo(String path, MultivaluedMap<String, String> queryParams) {
266 this.queryParams = queryParams;
270 public String getPath() {
275 public String getPath(boolean decode) {
280 public List<PathSegment> getPathSegments() {
281 throw new UnsupportedOperationException();
285 public List<PathSegment> getPathSegments(boolean decode) {
286 throw new UnsupportedOperationException();
290 public URI getRequestUri() {
291 return URI.create(path);
295 public UriBuilder getRequestUriBuilder() {
296 return UriBuilder.fromUri(getRequestUri());
300 public URI getAbsolutePath() {
301 return getRequestUri();
305 public UriBuilder getAbsolutePathBuilder() {
306 return UriBuilder.fromUri(getAbsolutePath());
310 public URI getBaseUri() {
311 return URI.create("");
315 public UriBuilder getBaseUriBuilder() {
316 return UriBuilder.fromUri(getBaseUri());
320 public MultivaluedMap<String, String> getPathParameters() {
321 return new MultivaluedHashMap<>();
325 public MultivaluedMap<String, String> getPathParameters(boolean decode) {
326 return getPathParameters();
330 public MultivaluedMap<String, String> getQueryParameters() {
335 public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
336 return getQueryParameters();
340 public List<String> getMatchedURIs() {
341 return Collections.emptyList();
345 public List<String> getMatchedURIs(boolean decode) {
346 return getMatchedURIs();
350 public List<Object> getMatchedResources() {
351 return Collections.emptyList();
355 public URI resolve(URI uri) {
360 public URI relativize(URI uri) {