Split Restconf implementations (draft02 and RFC) - base api
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / netconf / sal / restconf / impl / JSONRestconfServiceDraft18.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.netconf.sal.restconf.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.net.URI;
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;
46
47 /**
48  * Implementation of the JSONRestconfService interface using the restconf Draft18 implementation.
49  *
50  * @author Thomas Pantelis
51  */
52 public class JSONRestconfServiceDraft18 implements JSONRestconfService, AutoCloseable {
53     private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceDraft18.class);
54
55     private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
56
57     private final TransactionServicesWrapper services;
58     private final DOMMountPointServiceHandler mountPointServiceHandler;
59
60     public JSONRestconfServiceDraft18(final TransactionServicesWrapper services,
61             final DOMMountPointServiceHandler mountPointServiceHandler) {
62         this.services = services;
63         this.mountPointServiceHandler = mountPointServiceHandler;
64     }
65
66     @SuppressWarnings("checkstyle:IllegalCatch")
67     @Override
68     public void put(final String uriPath, final String payload) throws OperationFailedException {
69         Preconditions.checkNotNull(payload, "payload can't be null");
70
71         LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
72
73         final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, false);
74
75         LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
76         LOG.debug("Parsed NormalizedNode: {}", context.getData());
77
78         try {
79             services.putData(uriPath, context, new SimpleUriInfo(uriPath));
80         } catch (final Exception e) {
81             propagateExceptionAs(uriPath, e, "PUT");
82         }
83     }
84
85     @SuppressWarnings("checkstyle:IllegalCatch")
86     @Override
87     public void post(final String uriPath, final String payload)
88             throws OperationFailedException {
89         Preconditions.checkNotNull(payload, "payload can't be null");
90
91         LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
92
93         final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, true);
94
95         LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
96         LOG.debug("Parsed NormalizedNode: {}", context.getData());
97
98         try {
99             services.postData(uriPath, context, new SimpleUriInfo(uriPath));
100         } catch (final Exception e) {
101             propagateExceptionAs(uriPath, e, "POST");
102         }
103     }
104
105     @SuppressWarnings("checkstyle:IllegalCatch")
106     @Override
107     public void delete(final String uriPath) throws OperationFailedException {
108         LOG.debug("delete: uriPath: {}", uriPath);
109
110         try {
111             services.deleteData(uriPath);
112         } catch (final Exception e) {
113             propagateExceptionAs(uriPath, e, "DELETE");
114         }
115     }
116
117     @SuppressWarnings("checkstyle:IllegalCatch")
118     @Override
119     public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
120             throws OperationFailedException {
121         LOG.debug("get: uriPath: {}", uriPath);
122
123         try {
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);
128
129             final Response response = services.readData(uriPath, new SimpleUriInfo(uriPath, queryParams));
130             NormalizedNodeContext readData = (NormalizedNodeContext) response.getEntity();
131
132             final Optional<String> result = Optional.of(toJson(readData));
133
134             LOG.debug("get returning: {}", result.get());
135
136             return result;
137         } catch (final Exception e) {
138             if (!isDataMissing(e)) {
139                 propagateExceptionAs(uriPath, e, "GET");
140             }
141
142             LOG.debug("Data missing - returning absent");
143             return Optional.absent();
144         }
145     }
146
147     @SuppressWarnings("checkstyle:IllegalCatch")
148     @Override
149     public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
150             throws OperationFailedException {
151         Preconditions.checkNotNull(uriPath, "uriPath can't be null");
152
153         final String actualInput = input.isPresent() ? input.get() : null;
154
155         LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
156
157         String output = null;
158         try {
159             final NormalizedNodeContext inputContext = toNormalizedNodeContext(uriPath, actualInput, true);
160
161             LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
162                     .getInstanceIdentifier());
163             LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
164
165             NormalizedNodeContext outputContext = 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, ControllerContext.getInstance().getGlobalSchema(),
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 (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
254     private static class SimpleUriInfo implements UriInfo {
255         private final String path;
256         private final MultivaluedMap<String, String> queryParams;
257
258         SimpleUriInfo(String path) {
259             this(path, new MultivaluedHashMap<>());
260         }
261
262         SimpleUriInfo(String path, MultivaluedMap<String, String> queryParams) {
263             this.path = path;
264             this.queryParams = queryParams;
265         }
266
267         @Override
268         public String getPath() {
269             return path;
270         }
271
272         @Override
273         public String getPath(boolean decode) {
274             return path;
275         }
276
277         @Override
278         public List<PathSegment> getPathSegments() {
279             throw new UnsupportedOperationException();
280         }
281
282         @Override
283         public List<PathSegment> getPathSegments(boolean decode) {
284             throw new UnsupportedOperationException();
285         }
286
287         @Override
288         public URI getRequestUri() {
289             return URI.create(path);
290         }
291
292         @Override
293         public UriBuilder getRequestUriBuilder() {
294             return UriBuilder.fromUri(getRequestUri());
295         }
296
297         @Override
298         public URI getAbsolutePath() {
299             return getRequestUri();
300         }
301
302         @Override
303         public UriBuilder getAbsolutePathBuilder() {
304             return UriBuilder.fromUri(getAbsolutePath());
305         }
306
307         @Override
308         public URI getBaseUri() {
309             return URI.create("");
310         }
311
312         @Override
313         public UriBuilder getBaseUriBuilder() {
314             return UriBuilder.fromUri(getBaseUri());
315         }
316
317         @Override
318         public MultivaluedMap<String, String> getPathParameters() {
319             return new MultivaluedHashMap<>();
320         }
321
322         @Override
323         public MultivaluedMap<String, String> getPathParameters(boolean decode) {
324             return getPathParameters();
325         }
326
327         @Override
328         public MultivaluedMap<String, String> getQueryParameters() {
329             return queryParams;
330         }
331
332         @Override
333         public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
334             return getQueryParameters();
335         }
336
337         @Override
338         public List<String> getMatchedURIs() {
339             return Collections.emptyList();
340         }
341
342         @Override
343         public List<String> getMatchedURIs(boolean decode) {
344             return getMatchedURIs();
345         }
346
347         @Override
348         public List<Object> getMatchedResources() {
349             return Collections.emptyList();
350         }
351
352         @Override
353         public URI resolve(URI uri) {
354             return uri;
355         }
356
357         @Override
358         public URI relativize(URI uri) {
359             return uri;
360         }
361     }
362 }