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.netconf.sal.restconf.impl.RestconfError.ErrorTag;
32 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
33 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
34 import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
35 import org.opendaylight.restconf.jersey.providers.JsonNormalizedNodeBodyReader;
36 import org.opendaylight.restconf.jersey.providers.NormalizedNodeJsonBodyWriter;
37 import org.opendaylight.restconf.restful.services.api.TransactionServicesWrapper;
38 import org.opendaylight.restconf.restful.utils.RestconfDataServiceConstant;
39 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
40 import org.opendaylight.yangtools.yang.common.OperationFailedException;
41 import org.opendaylight.yangtools.yang.common.RpcError;
42 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
43 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * Implementation of the JSONRestconfService interface using the restconf Draft18 implementation.
50 * @author Thomas Pantelis
52 public class JSONRestconfServiceDraft18 implements JSONRestconfService, AutoCloseable {
53 private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceDraft18.class);
55 private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
57 private final TransactionServicesWrapper services;
58 private final DOMMountPointServiceHandler mountPointServiceHandler;
60 public JSONRestconfServiceDraft18(final TransactionServicesWrapper services,
61 final DOMMountPointServiceHandler mountPointServiceHandler) {
62 this.services = services;
63 this.mountPointServiceHandler = mountPointServiceHandler;
66 @SuppressWarnings("checkstyle:IllegalCatch")
68 public void put(final String uriPath, final String payload) throws OperationFailedException {
69 Preconditions.checkNotNull(payload, "payload can't be null");
71 LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
73 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, false);
75 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
76 LOG.debug("Parsed NormalizedNode: {}", context.getData());
79 services.putData(uriPath, context, new SimpleUriInfo(uriPath));
80 } catch (final Exception e) {
81 propagateExceptionAs(uriPath, e, "PUT");
85 @SuppressWarnings("checkstyle:IllegalCatch")
87 public void post(final String uriPath, final String payload)
88 throws OperationFailedException {
89 Preconditions.checkNotNull(payload, "payload can't be null");
91 LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
93 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, true);
95 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
96 LOG.debug("Parsed NormalizedNode: {}", context.getData());
99 services.postData(uriPath, context, new SimpleUriInfo(uriPath));
100 } catch (final Exception e) {
101 propagateExceptionAs(uriPath, e, "POST");
105 @SuppressWarnings("checkstyle:IllegalCatch")
107 public void delete(final String uriPath) throws OperationFailedException {
108 LOG.debug("delete: uriPath: {}", uriPath);
111 services.deleteData(uriPath);
112 } catch (final Exception e) {
113 propagateExceptionAs(uriPath, e, "DELETE");
117 @SuppressWarnings("checkstyle:IllegalCatch")
119 public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
120 throws OperationFailedException {
121 LOG.debug("get: uriPath: {}", uriPath);
124 final MultivaluedMap<String, String> queryParams = new MultivaluedHashMap<>();
125 queryParams.putSingle(RestconfDataServiceConstant.ReadData.CONTENT,
126 datastoreType == LogicalDatastoreType.CONFIGURATION ? RestconfDataServiceConstant.ReadData.CONFIG :
127 RestconfDataServiceConstant.ReadData.NONCONFIG);
129 final Response response = services.readData(uriPath, new SimpleUriInfo(uriPath, queryParams));
130 NormalizedNodeContext readData = (NormalizedNodeContext) response.getEntity();
132 final Optional<String> result = Optional.of(toJson(readData));
134 LOG.debug("get returning: {}", result.get());
137 } catch (final Exception e) {
138 if (!isDataMissing(e)) {
139 propagateExceptionAs(uriPath, e, "GET");
142 LOG.debug("Data missing - returning absent");
143 return Optional.absent();
147 @SuppressWarnings("checkstyle:IllegalCatch")
149 public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
150 throws OperationFailedException {
151 Preconditions.checkNotNull(uriPath, "uriPath can't be null");
153 final String actualInput = input.isPresent() ? input.get() : null;
155 LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
157 String output = null;
159 final NormalizedNodeContext inputContext = toNormalizedNodeContext(uriPath, actualInput, true);
161 LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
162 .getInstanceIdentifier());
163 LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
165 NormalizedNodeContext outputContext = services.invokeRpc(uriPath, inputContext, new SimpleUriInfo(uriPath));
167 if (outputContext.getData() != null) {
168 output = toJson(outputContext);
170 } catch (final Exception e) {
171 propagateExceptionAs(uriPath, e, "RPC");
174 return Optional.fromNullable(output);
178 public void close() {
181 private NormalizedNodeContext toNormalizedNodeContext(final String uriPath, @Nullable final String payload,
182 final boolean isPost) throws OperationFailedException {
183 final InstanceIdentifierContext<?> instanceIdentifierContext = ParserIdentifier.toInstanceIdentifier(
184 uriPath, ControllerContext.getInstance().getGlobalSchema(),
185 Optional.of(mountPointServiceHandler.get()));
187 if (payload == null) {
188 return new NormalizedNodeContext(instanceIdentifierContext, null);
191 final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
193 return JsonNormalizedNodeBodyReader.readFrom(instanceIdentifierContext, entityStream, isPost);
194 } catch (IOException e) {
195 propagateExceptionAs(uriPath, e, "GET");
200 private static String toJson(final NormalizedNodeContext readData) throws IOException {
201 final NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
202 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
203 writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
204 MediaType.APPLICATION_JSON_TYPE, null, outputStream);
205 return outputStream.toString(StandardCharsets.UTF_8.name());
208 private static boolean isDataMissing(final Exception exception) {
209 if (exception instanceof RestconfDocumentedException) {
210 final RestconfDocumentedException rde = (RestconfDocumentedException)exception;
211 return !rde.getErrors().isEmpty() && rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING;
217 private static void propagateExceptionAs(final String uriPath, final Exception exception, final String operation)
218 throws OperationFailedException {
219 LOG.debug("Error for uriPath: {}", uriPath, exception);
221 if (exception instanceof RestconfDocumentedException) {
222 throw new OperationFailedException(String.format(
223 "%s failed for URI %s", operation, uriPath), exception.getCause(),
224 toRpcErrors(((RestconfDocumentedException)exception).getErrors()));
227 throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
230 private static RpcError[] toRpcErrors(final List<RestconfError> from) {
231 final RpcError[] to = new RpcError[from.size()];
233 for (final RestconfError e: from) {
234 to[index++] = RpcResultBuilder.newError(toRpcErrorType(e.getErrorType()), e.getErrorTag().getTagValue(),
235 e.getErrorMessage());
241 private static ErrorType toRpcErrorType(final RestconfError.ErrorType errorType) {
244 return ErrorType.TRANSPORT;
246 return ErrorType.RPC;
248 return ErrorType.PROTOCOL;
250 return ErrorType.APPLICATION;
254 private static class SimpleUriInfo implements UriInfo {
255 private final String path;
256 private final MultivaluedMap<String, String> queryParams;
258 SimpleUriInfo(String path) {
259 this(path, new MultivaluedHashMap<>());
262 SimpleUriInfo(String path, MultivaluedMap<String, String> queryParams) {
264 this.queryParams = queryParams;
268 public String getPath() {
273 public String getPath(boolean decode) {
278 public List<PathSegment> getPathSegments() {
279 throw new UnsupportedOperationException();
283 public List<PathSegment> getPathSegments(boolean decode) {
284 throw new UnsupportedOperationException();
288 public URI getRequestUri() {
289 return URI.create(path);
293 public UriBuilder getRequestUriBuilder() {
294 return UriBuilder.fromUri(getRequestUri());
298 public URI getAbsolutePath() {
299 return getRequestUri();
303 public UriBuilder getAbsolutePathBuilder() {
304 return UriBuilder.fromUri(getAbsolutePath());
308 public URI getBaseUri() {
309 return URI.create("");
313 public UriBuilder getBaseUriBuilder() {
314 return UriBuilder.fromUri(getBaseUri());
318 public MultivaluedMap<String, String> getPathParameters() {
319 return new MultivaluedHashMap<>();
323 public MultivaluedMap<String, String> getPathParameters(boolean decode) {
324 return getPathParameters();
328 public MultivaluedMap<String, String> getQueryParameters() {
333 public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
334 return getQueryParameters();
338 public List<String> getMatchedURIs() {
339 return Collections.emptyList();
343 public List<String> getMatchedURIs(boolean decode) {
344 return getMatchedURIs();
348 public List<Object> getMatchedResources() {
349 return Collections.emptyList();
353 public URI resolve(URI uri) {
358 public URI relativize(URI uri) {