Merge "Adjust test-tool to yangtools changes"
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / JSONRestconfServiceRfc8040Impl.java
1 /*
2  * Copyright (c) 2015 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
9
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;
45
46 /**
47  * Implementation of the JSONRestconfService interface using the restconf Draft18 implementation.
48  *
49  * @author Thomas Pantelis
50  */
51 public class JSONRestconfServiceRfc8040Impl implements JSONRestconfService, AutoCloseable {
52     private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceRfc8040Impl.class);
53
54     private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
55
56     private final TransactionServicesWrapper services;
57     private final DOMMountPointServiceHandler mountPointServiceHandler;
58
59     public JSONRestconfServiceRfc8040Impl(final TransactionServicesWrapper services,
60             final DOMMountPointServiceHandler mountPointServiceHandler) {
61         this.services = services;
62         this.mountPointServiceHandler = mountPointServiceHandler;
63     }
64
65     @SuppressWarnings("checkstyle:IllegalCatch")
66     @Override
67     public void put(final String uriPath, final String payload) throws OperationFailedException {
68         Preconditions.checkNotNull(payload, "payload can't be null");
69
70         LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
71
72         final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, false);
73
74         LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
75         LOG.debug("Parsed NormalizedNode: {}", context.getData());
76
77         try {
78             services.putData(uriPath, context, new SimpleUriInfo(uriPath));
79         } catch (final Exception e) {
80             propagateExceptionAs(uriPath, e, "PUT");
81         }
82     }
83
84     @SuppressWarnings("checkstyle:IllegalCatch")
85     @Override
86     public void post(final String uriPath, final String payload)
87             throws OperationFailedException {
88         Preconditions.checkNotNull(payload, "payload can't be null");
89
90         LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
91
92         final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, true);
93
94         LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
95         LOG.debug("Parsed NormalizedNode: {}", context.getData());
96
97         try {
98             services.postData(uriPath, context, new SimpleUriInfo(uriPath));
99         } catch (final Exception e) {
100             propagateExceptionAs(uriPath, e, "POST");
101         }
102     }
103
104     @SuppressWarnings("checkstyle:IllegalCatch")
105     @Override
106     public void delete(final String uriPath) throws OperationFailedException {
107         LOG.debug("delete: uriPath: {}", uriPath);
108
109         try {
110             services.deleteData(uriPath);
111         } catch (final Exception e) {
112             propagateExceptionAs(uriPath, e, "DELETE");
113         }
114     }
115
116     @SuppressWarnings("checkstyle:IllegalCatch")
117     @Override
118     public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
119             throws OperationFailedException {
120         LOG.debug("get: uriPath: {}", uriPath);
121
122         try {
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);
127
128             final Response response = services.readData(uriPath, new SimpleUriInfo(uriPath, queryParams));
129             final NormalizedNodeContext readData = (NormalizedNodeContext) response.getEntity();
130
131             final Optional<String> result = Optional.of(toJson(readData));
132
133             LOG.debug("get returning: {}", result.get());
134
135             return result;
136         } catch (final Exception e) {
137             if (!isDataMissing(e)) {
138                 propagateExceptionAs(uriPath, e, "GET");
139             }
140
141             LOG.debug("Data missing - returning absent");
142             return Optional.absent();
143         }
144     }
145
146     @SuppressWarnings("checkstyle:IllegalCatch")
147     @Override
148     public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
149             throws OperationFailedException {
150         Preconditions.checkNotNull(uriPath, "uriPath can't be null");
151
152         final String actualInput = input.isPresent() ? input.get() : null;
153
154         LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
155
156         String output = null;
157         try {
158             final NormalizedNodeContext inputContext = toNormalizedNodeContext(uriPath, actualInput, true);
159
160             LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
161                     .getInstanceIdentifier());
162             LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
163
164             final NormalizedNodeContext outputContext =
165                     services.invokeRpc(uriPath, inputContext, new SimpleUriInfo(uriPath));
166
167             if (outputContext.getData() != null) {
168                 output = toJson(outputContext);
169             }
170         } catch (final Exception e) {
171             propagateExceptionAs(uriPath, e, "RPC");
172         }
173
174         return Optional.fromNullable(output);
175     }
176
177     @Override
178     public void close() {
179     }
180
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()));
186
187         if (payload == null) {
188             return new NormalizedNodeContext(instanceIdentifierContext, null);
189         }
190
191         final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
192         try {
193             return JsonNormalizedNodeBodyReader.readFrom(instanceIdentifierContext, entityStream, isPost);
194         } catch (final IOException e) {
195             propagateExceptionAs(uriPath, e, "GET");
196             return null;
197         }
198     }
199
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());
206     }
207
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;
212         }
213
214         return false;
215     }
216
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);
220
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()));
225         }
226
227         throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
228     }
229
230     private static RpcError[] toRpcErrors(final List<RestconfError> from) {
231         final RpcError[] to = new RpcError[from.size()];
232         int index = 0;
233         for (final RestconfError e: from) {
234             to[index++] = RpcResultBuilder.newError(toRpcErrorType(e.getErrorType()), e.getErrorTag().getTagValue(),
235                     e.getErrorMessage());
236         }
237
238         return to;
239     }
240
241     private static ErrorType toRpcErrorType(final RestconfError.ErrorType errorType) {
242         switch (errorType) {
243             case TRANSPORT:
244                 return ErrorType.TRANSPORT;
245             case RPC:
246                 return ErrorType.RPC;
247             case PROTOCOL:
248                 return ErrorType.PROTOCOL;
249             default:
250                 return ErrorType.APPLICATION;
251         }
252     }
253 }