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.handlers.DOMMountPointServiceHandler;
33 import org.opendaylight.restconf.jersey.providers.JsonNormalizedNodeBodyReader;
34 import org.opendaylight.restconf.jersey.providers.NormalizedNodeJsonBodyWriter;
35 import org.opendaylight.restconf.restful.services.api.TransactionServicesWrapper;
36 import org.opendaylight.restconf.restful.utils.RestconfDataServiceConstant;
37 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
38 import org.opendaylight.yangtools.yang.common.OperationFailedException;
39 import org.opendaylight.yangtools.yang.common.RpcError;
40 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
41 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * Implementation of the JSONRestconfService interface using the restconf Draft18 implementation.
48 * @author Thomas Pantelis
50 public class JSONRestconfServiceDraft18 implements JSONRestconfService, AutoCloseable {
51 private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceDraft18.class);
53 private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
55 private final TransactionServicesWrapper services;
56 private final DOMMountPointServiceHandler mountPointServiceHandler;
58 public JSONRestconfServiceDraft18(final TransactionServicesWrapper services,
59 final DOMMountPointServiceHandler mountPointServiceHandler) {
60 this.services = services;
61 this.mountPointServiceHandler = mountPointServiceHandler;
64 @SuppressWarnings("checkstyle:IllegalCatch")
66 public void put(final String uriPath, final String payload) throws OperationFailedException {
67 Preconditions.checkNotNull(payload, "payload can't be null");
69 LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
71 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, false);
73 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
74 LOG.debug("Parsed NormalizedNode: {}", context.getData());
77 services.putData(uriPath, context, new SimpleUriInfo(uriPath));
78 } catch (final Exception e) {
79 propagateExceptionAs(uriPath, e, "PUT");
83 @SuppressWarnings("checkstyle:IllegalCatch")
85 public void post(final String uriPath, final String payload)
86 throws OperationFailedException {
87 Preconditions.checkNotNull(payload, "payload can't be null");
89 LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
91 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, true);
93 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
94 LOG.debug("Parsed NormalizedNode: {}", context.getData());
97 services.postData(uriPath, context, new SimpleUriInfo(uriPath));
98 } catch (final Exception e) {
99 propagateExceptionAs(uriPath, e, "POST");
103 @SuppressWarnings("checkstyle:IllegalCatch")
105 public void delete(final String uriPath) throws OperationFailedException {
106 LOG.debug("delete: uriPath: {}", uriPath);
109 services.deleteData(uriPath);
110 } catch (final Exception e) {
111 propagateExceptionAs(uriPath, e, "DELETE");
115 @SuppressWarnings("checkstyle:IllegalCatch")
117 public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
118 throws OperationFailedException {
119 LOG.debug("get: uriPath: {}", uriPath);
122 final MultivaluedMap<String, String> queryParams = new MultivaluedHashMap<>();
123 queryParams.putSingle(RestconfDataServiceConstant.ReadData.CONTENT,
124 datastoreType == LogicalDatastoreType.CONFIGURATION ? RestconfDataServiceConstant.ReadData.CONFIG :
125 RestconfDataServiceConstant.ReadData.NONCONFIG);
127 final Response response = services.readData(uriPath, new SimpleUriInfo(uriPath, queryParams));
128 NormalizedNodeContext readData = (NormalizedNodeContext) response.getEntity();
130 final Optional<String> result = Optional.of(toJson(readData));
132 LOG.debug("get returning: {}", result.get());
135 } catch (final Exception e) {
136 if (!isDataMissing(e)) {
137 propagateExceptionAs(uriPath, e, "GET");
140 LOG.debug("Data missing - returning absent");
141 return Optional.absent();
145 @SuppressWarnings("checkstyle:IllegalCatch")
147 public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
148 throws OperationFailedException {
149 Preconditions.checkNotNull(uriPath, "uriPath can't be null");
151 final String actualInput = input.isPresent() ? input.get() : null;
153 LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
155 String output = null;
157 final NormalizedNodeContext inputContext = toNormalizedNodeContext(uriPath, actualInput, true);
159 LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
160 .getInstanceIdentifier());
161 LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
163 NormalizedNodeContext outputContext = services.invokeRpc(uriPath, inputContext, new SimpleUriInfo(uriPath));
165 if (outputContext.getData() != null) {
166 output = toJson(outputContext);
168 } catch (final Exception e) {
169 propagateExceptionAs(uriPath, e, "RPC");
172 return Optional.fromNullable(output);
176 public void close() {
179 private NormalizedNodeContext toNormalizedNodeContext(final String uriPath, @Nullable final String payload,
180 final boolean isPost) throws OperationFailedException {
181 final InstanceIdentifierContext<?> instanceIdentifierContext = ParserIdentifier.toInstanceIdentifier(
182 uriPath, ControllerContext.getInstance().getGlobalSchema(),
183 Optional.of(mountPointServiceHandler.get()));
185 if (payload == null) {
186 return new NormalizedNodeContext(instanceIdentifierContext, null);
189 final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
191 return JsonNormalizedNodeBodyReader.readFrom(instanceIdentifierContext, entityStream, isPost);
192 } catch (IOException e) {
193 propagateExceptionAs(uriPath, e, "GET");
198 private static String toJson(final NormalizedNodeContext readData) throws IOException {
199 final NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
200 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
201 writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
202 MediaType.APPLICATION_JSON_TYPE, null, outputStream);
203 return outputStream.toString(StandardCharsets.UTF_8.name());
206 private static boolean isDataMissing(final Exception exception) {
207 if (exception instanceof RestconfDocumentedException) {
208 final RestconfDocumentedException rde = (RestconfDocumentedException)exception;
209 return !rde.getErrors().isEmpty() && rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING;
215 private static void propagateExceptionAs(final String uriPath, final Exception exception, final String operation)
216 throws OperationFailedException {
217 LOG.debug("Error for uriPath: {}", uriPath, exception);
219 if (exception instanceof RestconfDocumentedException) {
220 throw new OperationFailedException(String.format(
221 "%s failed for URI %s", operation, uriPath), exception.getCause(),
222 toRpcErrors(((RestconfDocumentedException)exception).getErrors()));
225 throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
228 private static RpcError[] toRpcErrors(final List<RestconfError> from) {
229 final RpcError[] to = new RpcError[from.size()];
231 for (final RestconfError e: from) {
232 to[index++] = RpcResultBuilder.newError(toRpcErrorType(e.getErrorType()), e.getErrorTag().getTagValue(),
233 e.getErrorMessage());
239 private static ErrorType toRpcErrorType(final RestconfError.ErrorType errorType) {
242 return ErrorType.TRANSPORT;
244 return ErrorType.RPC;
246 return ErrorType.PROTOCOL;
248 return ErrorType.APPLICATION;
252 private static class SimpleUriInfo implements UriInfo {
253 private final String path;
254 private final MultivaluedMap<String, String> queryParams;
256 SimpleUriInfo(String path) {
257 this(path, new MultivaluedHashMap<>());
260 SimpleUriInfo(String path, MultivaluedMap<String, String> queryParams) {
262 this.queryParams = queryParams;
266 public String getPath() {
271 public String getPath(boolean decode) {
276 public List<PathSegment> getPathSegments() {
277 throw new UnsupportedOperationException();
281 public List<PathSegment> getPathSegments(boolean decode) {
282 throw new UnsupportedOperationException();
286 public URI getRequestUri() {
287 return URI.create(path);
291 public UriBuilder getRequestUriBuilder() {
292 return UriBuilder.fromUri(getRequestUri());
296 public URI getAbsolutePath() {
297 return getRequestUri();
301 public UriBuilder getAbsolutePathBuilder() {
302 return UriBuilder.fromUri(getAbsolutePath());
306 public URI getBaseUri() {
307 return URI.create("");
311 public UriBuilder getBaseUriBuilder() {
312 return UriBuilder.fromUri(getBaseUri());
316 public MultivaluedMap<String, String> getPathParameters() {
317 return new MultivaluedHashMap<>();
321 public MultivaluedMap<String, String> getPathParameters(boolean decode) {
322 return getPathParameters();
326 public MultivaluedMap<String, String> getQueryParameters() {
331 public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
332 return getQueryParameters();
336 public List<String> getMatchedURIs() {
337 return Collections.emptyList();
341 public List<String> getMatchedURIs(boolean decode) {
342 return getMatchedURIs();
346 public List<Object> getMatchedResources() {
347 return Collections.emptyList();
351 public URI resolve(URI uri) {
356 public URI relativize(URI uri) {