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;
17 import java.nio.charset.StandardCharsets;
18 import java.util.List;
19 import javax.annotation.Nullable;
20 import javax.ws.rs.core.MediaType;
21 import javax.ws.rs.core.MultivaluedMap;
22 import javax.ws.rs.core.Response;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
25 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
26 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
27 import org.opendaylight.restconf.common.errors.RestconfError;
28 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
29 import org.opendaylight.restconf.common.util.MultivaluedHashMap;
30 import org.opendaylight.restconf.common.util.SimpleUriInfo;
31 import org.opendaylight.restconf.nb.rfc8040.handlers.DOMMountPointServiceHandler;
32 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
33 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyReader;
34 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.NormalizedNodeJsonBodyWriter;
35 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.JSONRestconfService;
36 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.TransactionServicesWrapper;
37 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant;
38 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
39 import org.opendaylight.yangtools.yang.common.OperationFailedException;
40 import org.opendaylight.yangtools.yang.common.RpcError;
41 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
42 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 * Implementation of the JSONRestconfService interface using the restconf Draft18 implementation.
49 * @author Thomas Pantelis
51 public class JSONRestconfServiceRfc8040Impl implements JSONRestconfService, AutoCloseable {
52 private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceRfc8040Impl.class);
54 private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
56 private final TransactionServicesWrapper services;
57 private final DOMMountPointServiceHandler mountPointServiceHandler;
59 public JSONRestconfServiceRfc8040Impl(final TransactionServicesWrapper services,
60 final DOMMountPointServiceHandler mountPointServiceHandler) {
61 this.services = services;
62 this.mountPointServiceHandler = mountPointServiceHandler;
65 @SuppressWarnings("checkstyle:IllegalCatch")
67 public void put(final String uriPath, final String payload) throws OperationFailedException {
68 Preconditions.checkNotNull(payload, "payload can't be null");
70 LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
72 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, false);
74 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
75 LOG.debug("Parsed NormalizedNode: {}", context.getData());
78 services.putData(uriPath, context, new SimpleUriInfo(uriPath));
79 } catch (final Exception e) {
80 propagateExceptionAs(uriPath, e, "PUT");
84 @SuppressWarnings("checkstyle:IllegalCatch")
86 public void post(final String uriPath, final String payload)
87 throws OperationFailedException {
88 Preconditions.checkNotNull(payload, "payload can't be null");
90 LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
92 final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, true);
94 LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
95 LOG.debug("Parsed NormalizedNode: {}", context.getData());
98 services.postData(uriPath, context, new SimpleUriInfo(uriPath));
99 } catch (final Exception e) {
100 propagateExceptionAs(uriPath, e, "POST");
104 @SuppressWarnings("checkstyle:IllegalCatch")
106 public void delete(final String uriPath) throws OperationFailedException {
107 LOG.debug("delete: uriPath: {}", uriPath);
110 services.deleteData(uriPath);
111 } catch (final Exception e) {
112 propagateExceptionAs(uriPath, e, "DELETE");
116 @SuppressWarnings("checkstyle:IllegalCatch")
118 public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
119 throws OperationFailedException {
120 LOG.debug("get: uriPath: {}", uriPath);
123 final MultivaluedMap<String, String> queryParams = new MultivaluedHashMap<>();
124 queryParams.putSingle(RestconfDataServiceConstant.ReadData.CONTENT,
125 datastoreType == LogicalDatastoreType.CONFIGURATION ? RestconfDataServiceConstant.ReadData.CONFIG :
126 RestconfDataServiceConstant.ReadData.NONCONFIG);
128 final Response response = services.readData(uriPath, new SimpleUriInfo(uriPath, queryParams));
129 final NormalizedNodeContext readData = (NormalizedNodeContext) response.getEntity();
131 final Optional<String> result = Optional.of(toJson(readData));
133 LOG.debug("get returning: {}", result.get());
136 } catch (final Exception e) {
137 if (!isDataMissing(e)) {
138 propagateExceptionAs(uriPath, e, "GET");
141 LOG.debug("Data missing - returning absent");
142 return Optional.absent();
146 @SuppressWarnings("checkstyle:IllegalCatch")
148 public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
149 throws OperationFailedException {
150 Preconditions.checkNotNull(uriPath, "uriPath can't be null");
152 final String actualInput = input.isPresent() ? input.get() : null;
154 LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
156 String output = null;
158 final NormalizedNodeContext inputContext = toNormalizedNodeContext(uriPath, actualInput, true);
160 LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
161 .getInstanceIdentifier());
162 LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
164 final NormalizedNodeContext outputContext =
165 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, SchemaContextHandler.getActualSchemaContext(),
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 (final 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;