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.restconf.nb.rfc8040.rests.services.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.restconf.common.context.InstanceIdentifierContext;
31 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
32 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
33 import org.opendaylight.restconf.common.errors.RestconfError;
34 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
35 import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
36 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
37 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyReader;
38 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.NormalizedNodeJsonBodyWriter;
39 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.JSONRestconfService;
40 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.TransactionServicesWrapper;
41 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant;
42 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
43 import org.opendaylight.yangtools.yang.common.OperationFailedException;
44 import org.opendaylight.yangtools.yang.common.RpcError;
45 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
46 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * Implementation of the JSONRestconfService interface using the restconf Draft18 implementation.
53 * @author Thomas Pantelis
55 public class JSONRestconfServiceRfc8040Impl implements JSONRestconfService, AutoCloseable {
56 private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceRfc8040Impl.class);
58 private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
60 private final TransactionServicesWrapper services;
61 private final DOMMountPointServiceHandler mountPointServiceHandler;
63 public JSONRestconfServiceRfc8040Impl(final TransactionServicesWrapper services,
64 final DOMMountPointServiceHandler mountPointServiceHandler) {
65 this.services = services;
66 this.mountPointServiceHandler = mountPointServiceHandler;
69 @SuppressWarnings("checkstyle:IllegalCatch")
71 public void put(final String uriPath, final String payload) throws OperationFailedException {
72 Preconditions.checkNotNull(payload, "payload can't be null");
74 LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
76 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, false);
78 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
79 LOG.debug("Parsed NormalizedNode: {}", context.getData());
82 services.putData(uriPath, context, new SimpleUriInfo(uriPath));
83 } catch (final Exception e) {
84 propagateExceptionAs(uriPath, e, "PUT");
88 @SuppressWarnings("checkstyle:IllegalCatch")
90 public void post(final String uriPath, final String payload)
91 throws OperationFailedException {
92 Preconditions.checkNotNull(payload, "payload can't be null");
94 LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
96 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, true);
98 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
99 LOG.debug("Parsed NormalizedNode: {}", context.getData());
102 services.postData(uriPath, context, new SimpleUriInfo(uriPath));
103 } catch (final Exception e) {
104 propagateExceptionAs(uriPath, e, "POST");
108 @SuppressWarnings("checkstyle:IllegalCatch")
110 public void delete(final String uriPath) throws OperationFailedException {
111 LOG.debug("delete: uriPath: {}", uriPath);
114 services.deleteData(uriPath);
115 } catch (final Exception e) {
116 propagateExceptionAs(uriPath, e, "DELETE");
120 @SuppressWarnings("checkstyle:IllegalCatch")
122 public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
123 throws OperationFailedException {
124 LOG.debug("get: uriPath: {}", uriPath);
127 final MultivaluedMap<String, String> queryParams = new MultivaluedHashMap<>();
128 queryParams.putSingle(RestconfDataServiceConstant.ReadData.CONTENT,
129 datastoreType == LogicalDatastoreType.CONFIGURATION ? RestconfDataServiceConstant.ReadData.CONFIG :
130 RestconfDataServiceConstant.ReadData.NONCONFIG);
132 final Response response = services.readData(uriPath, new SimpleUriInfo(uriPath, queryParams));
133 final NormalizedNodeContext readData = (NormalizedNodeContext) response.getEntity();
135 final Optional<String> result = Optional.of(toJson(readData));
137 LOG.debug("get returning: {}", result.get());
140 } catch (final Exception e) {
141 if (!isDataMissing(e)) {
142 propagateExceptionAs(uriPath, e, "GET");
145 LOG.debug("Data missing - returning absent");
146 return Optional.absent();
150 @SuppressWarnings("checkstyle:IllegalCatch")
152 public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
153 throws OperationFailedException {
154 Preconditions.checkNotNull(uriPath, "uriPath can't be null");
156 final String actualInput = input.isPresent() ? input.get() : null;
158 LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
160 String output = null;
162 final NormalizedNodeContext inputContext = toNormalizedNodeContext(uriPath, actualInput, true);
164 LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
165 .getInstanceIdentifier());
166 LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
168 final NormalizedNodeContext outputContext =
169 services.invokeRpc(uriPath, inputContext, new SimpleUriInfo(uriPath));
171 if (outputContext.getData() != null) {
172 output = toJson(outputContext);
174 } catch (final Exception e) {
175 propagateExceptionAs(uriPath, e, "RPC");
178 return Optional.fromNullable(output);
182 public void close() {
185 private NormalizedNodeContext toNormalizedNodeContext(final String uriPath, @Nullable final String payload,
186 final boolean isPost) throws OperationFailedException {
187 final InstanceIdentifierContext<?> instanceIdentifierContext = ParserIdentifier.toInstanceIdentifier(
188 uriPath, SchemaContextHandler.getActualSchemaContext(),
189 Optional.of(mountPointServiceHandler.get()));
191 if (payload == null) {
192 return new NormalizedNodeContext(instanceIdentifierContext, null);
195 final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
197 return JsonNormalizedNodeBodyReader.readFrom(instanceIdentifierContext, entityStream, isPost);
198 } catch (final IOException e) {
199 propagateExceptionAs(uriPath, e, "GET");
204 private static String toJson(final NormalizedNodeContext readData) throws IOException {
205 final NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
206 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
207 writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
208 MediaType.APPLICATION_JSON_TYPE, null, outputStream);
209 return outputStream.toString(StandardCharsets.UTF_8.name());
212 private static boolean isDataMissing(final Exception exception) {
213 if (exception instanceof RestconfDocumentedException) {
214 final RestconfDocumentedException rde = (RestconfDocumentedException)exception;
215 return !rde.getErrors().isEmpty() && rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING;
221 private static void propagateExceptionAs(final String uriPath, final Exception exception, final String operation)
222 throws OperationFailedException {
223 LOG.debug("Error for uriPath: {}", uriPath, exception);
225 if (exception instanceof RestconfDocumentedException) {
226 throw new OperationFailedException(String.format(
227 "%s failed for URI %s", operation, uriPath), exception.getCause(),
228 toRpcErrors(((RestconfDocumentedException)exception).getErrors()));
231 throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
234 private static RpcError[] toRpcErrors(final List<RestconfError> from) {
235 final RpcError[] to = new RpcError[from.size()];
237 for (final RestconfError e: from) {
238 to[index++] = RpcResultBuilder.newError(toRpcErrorType(e.getErrorType()), e.getErrorTag().getTagValue(),
239 e.getErrorMessage());
245 private static ErrorType toRpcErrorType(final RestconfError.ErrorType errorType) {
248 return ErrorType.TRANSPORT;
250 return ErrorType.RPC;
252 return ErrorType.PROTOCOL;
254 return ErrorType.APPLICATION;
258 private static class SimpleUriInfo implements UriInfo {
259 private final String path;
260 private final MultivaluedMap<String, String> queryParams;
262 SimpleUriInfo(final String path) {
263 this(path, new MultivaluedHashMap<>());
266 SimpleUriInfo(final String path, final MultivaluedMap<String, String> queryParams) {
268 this.queryParams = queryParams;
272 public String getPath() {
277 public String getPath(final boolean decode) {
282 public List<PathSegment> getPathSegments() {
283 throw new UnsupportedOperationException();
287 public List<PathSegment> getPathSegments(final boolean decode) {
288 throw new UnsupportedOperationException();
292 public URI getRequestUri() {
293 return URI.create(path);
297 public UriBuilder getRequestUriBuilder() {
298 return UriBuilder.fromUri(getRequestUri());
302 public URI getAbsolutePath() {
303 return getRequestUri();
307 public UriBuilder getAbsolutePathBuilder() {
308 return UriBuilder.fromUri(getAbsolutePath());
312 public URI getBaseUri() {
313 return URI.create("");
317 public UriBuilder getBaseUriBuilder() {
318 return UriBuilder.fromUri(getBaseUri());
322 public MultivaluedMap<String, String> getPathParameters() {
323 return new MultivaluedHashMap<>();
327 public MultivaluedMap<String, String> getPathParameters(final boolean decode) {
328 return getPathParameters();
332 public MultivaluedMap<String, String> getQueryParameters() {
337 public MultivaluedMap<String, String> getQueryParameters(final boolean decode) {
338 return getQueryParameters();
342 public List<String> getMatchedURIs() {
343 return Collections.emptyList();
347 public List<String> getMatchedURIs(final boolean decode) {
348 return getMatchedURIs();
352 public List<Object> getMatchedResources() {
353 return Collections.emptyList();
357 public URI resolve(final URI uri) {
362 public URI relativize(final URI uri) {