Split Restconf implementations (draft02 and RFC) - move restconf
[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.restconf.common.context.InstanceIdentifierContext;
32 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
33 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
34 import org.opendaylight.restconf.common.errors.RestconfError;
35 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
36 import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
37 import org.opendaylight.restconf.jersey.providers.JsonNormalizedNodeBodyReader;
38 import org.opendaylight.restconf.jersey.providers.NormalizedNodeJsonBodyWriter;
39 import org.opendaylight.restconf.restful.services.api.TransactionServicesWrapper;
40 import org.opendaylight.restconf.restful.utils.RestconfDataServiceConstant;
41 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
42 import org.opendaylight.yangtools.yang.common.OperationFailedException;
43 import org.opendaylight.yangtools.yang.common.RpcError;
44 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
45 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * Implementation of the JSONRestconfService interface using the restconf Draft18 implementation.
51  *
52  * @author Thomas Pantelis
53  */
54 public class JSONRestconfServiceDraft18 implements JSONRestconfService, AutoCloseable {
55     private static final Logger LOG = LoggerFactory.getLogger(JSONRestconfServiceDraft18.class);
56
57     private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
58
59     private final TransactionServicesWrapper services;
60     private final DOMMountPointServiceHandler mountPointServiceHandler;
61
62     public JSONRestconfServiceDraft18(final TransactionServicesWrapper services,
63             final DOMMountPointServiceHandler mountPointServiceHandler) {
64         this.services = services;
65         this.mountPointServiceHandler = mountPointServiceHandler;
66     }
67
68     @SuppressWarnings("checkstyle:IllegalCatch")
69     @Override
70     public void put(final String uriPath, final String payload) throws OperationFailedException {
71         Preconditions.checkNotNull(payload, "payload can't be null");
72
73         LOG.debug("put: uriPath: {}, payload: {}", uriPath, payload);
74
75         final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, false);
76
77         LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
78         LOG.debug("Parsed NormalizedNode: {}", context.getData());
79
80         try {
81             services.putData(uriPath, context, new SimpleUriInfo(uriPath));
82         } catch (final Exception e) {
83             propagateExceptionAs(uriPath, e, "PUT");
84         }
85     }
86
87     @SuppressWarnings("checkstyle:IllegalCatch")
88     @Override
89     public void post(final String uriPath, final String payload)
90             throws OperationFailedException {
91         Preconditions.checkNotNull(payload, "payload can't be null");
92
93         LOG.debug("post: uriPath: {}, payload: {}", uriPath, payload);
94
95         final NormalizedNodeContext context = toNormalizedNodeContext(uriPath, payload, true);
96
97         LOG.debug("Parsed YangInstanceIdentifier: {}", context.getInstanceIdentifierContext().getInstanceIdentifier());
98         LOG.debug("Parsed NormalizedNode: {}", context.getData());
99
100         try {
101             services.postData(uriPath, context, new SimpleUriInfo(uriPath));
102         } catch (final Exception e) {
103             propagateExceptionAs(uriPath, e, "POST");
104         }
105     }
106
107     @SuppressWarnings("checkstyle:IllegalCatch")
108     @Override
109     public void delete(final String uriPath) throws OperationFailedException {
110         LOG.debug("delete: uriPath: {}", uriPath);
111
112         try {
113             services.deleteData(uriPath);
114         } catch (final Exception e) {
115             propagateExceptionAs(uriPath, e, "DELETE");
116         }
117     }
118
119     @SuppressWarnings("checkstyle:IllegalCatch")
120     @Override
121     public Optional<String> get(final String uriPath, final LogicalDatastoreType datastoreType)
122             throws OperationFailedException {
123         LOG.debug("get: uriPath: {}", uriPath);
124
125         try {
126             final MultivaluedMap<String, String> queryParams = new MultivaluedHashMap<>();
127             queryParams.putSingle(RestconfDataServiceConstant.ReadData.CONTENT,
128                     datastoreType == LogicalDatastoreType.CONFIGURATION ? RestconfDataServiceConstant.ReadData.CONFIG :
129                         RestconfDataServiceConstant.ReadData.NONCONFIG);
130
131             final Response response = services.readData(uriPath, new SimpleUriInfo(uriPath, queryParams));
132             NormalizedNodeContext readData = (NormalizedNodeContext) response.getEntity();
133
134             final Optional<String> result = Optional.of(toJson(readData));
135
136             LOG.debug("get returning: {}", result.get());
137
138             return result;
139         } catch (final Exception e) {
140             if (!isDataMissing(e)) {
141                 propagateExceptionAs(uriPath, e, "GET");
142             }
143
144             LOG.debug("Data missing - returning absent");
145             return Optional.absent();
146         }
147     }
148
149     @SuppressWarnings("checkstyle:IllegalCatch")
150     @Override
151     public Optional<String> invokeRpc(final String uriPath, final Optional<String> input)
152             throws OperationFailedException {
153         Preconditions.checkNotNull(uriPath, "uriPath can't be null");
154
155         final String actualInput = input.isPresent() ? input.get() : null;
156
157         LOG.debug("invokeRpc: uriPath: {}, input: {}", uriPath, actualInput);
158
159         String output = null;
160         try {
161             final NormalizedNodeContext inputContext = toNormalizedNodeContext(uriPath, actualInput, true);
162
163             LOG.debug("Parsed YangInstanceIdentifier: {}", inputContext.getInstanceIdentifierContext()
164                     .getInstanceIdentifier());
165             LOG.debug("Parsed NormalizedNode: {}", inputContext.getData());
166
167             NormalizedNodeContext outputContext = services.invokeRpc(uriPath, inputContext, new SimpleUriInfo(uriPath));
168
169             if (outputContext.getData() != null) {
170                 output = toJson(outputContext);
171             }
172         } catch (final Exception e) {
173             propagateExceptionAs(uriPath, e, "RPC");
174         }
175
176         return Optional.fromNullable(output);
177     }
178
179     @Override
180     public void close() {
181     }
182
183     private NormalizedNodeContext toNormalizedNodeContext(final String uriPath, @Nullable final String payload,
184             final boolean isPost) throws OperationFailedException {
185         final InstanceIdentifierContext<?> instanceIdentifierContext = ParserIdentifier.toInstanceIdentifier(
186                 uriPath, ControllerContext.getInstance().getGlobalSchema(),
187                 Optional.of(mountPointServiceHandler.get()));
188
189         if (payload == null) {
190             return new NormalizedNodeContext(instanceIdentifierContext, null);
191         }
192
193         final InputStream entityStream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
194         try {
195             return JsonNormalizedNodeBodyReader.readFrom(instanceIdentifierContext, entityStream, isPost);
196         } catch (IOException e) {
197             propagateExceptionAs(uriPath, e, "GET");
198             return null;
199         }
200     }
201
202     private static String toJson(final NormalizedNodeContext readData) throws IOException {
203         final NormalizedNodeJsonBodyWriter writer = new NormalizedNodeJsonBodyWriter();
204         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
205         writer.writeTo(readData, NormalizedNodeContext.class, null, EMPTY_ANNOTATIONS,
206                 MediaType.APPLICATION_JSON_TYPE, null, outputStream);
207         return outputStream.toString(StandardCharsets.UTF_8.name());
208     }
209
210     private static boolean isDataMissing(final Exception exception) {
211         if (exception instanceof RestconfDocumentedException) {
212             final RestconfDocumentedException rde = (RestconfDocumentedException)exception;
213             return !rde.getErrors().isEmpty() && rde.getErrors().get(0).getErrorTag() == ErrorTag.DATA_MISSING;
214         }
215
216         return false;
217     }
218
219     private static void propagateExceptionAs(final String uriPath, final Exception exception, final String operation)
220             throws OperationFailedException {
221         LOG.debug("Error for uriPath: {}", uriPath, exception);
222
223         if (exception instanceof RestconfDocumentedException) {
224             throw new OperationFailedException(String.format(
225                     "%s failed for URI %s", operation, uriPath), exception.getCause(),
226                     toRpcErrors(((RestconfDocumentedException)exception).getErrors()));
227         }
228
229         throw new OperationFailedException(String.format("%s failed for URI %s", operation, uriPath), exception);
230     }
231
232     private static RpcError[] toRpcErrors(final List<RestconfError> from) {
233         final RpcError[] to = new RpcError[from.size()];
234         int index = 0;
235         for (final RestconfError e: from) {
236             to[index++] = RpcResultBuilder.newError(toRpcErrorType(e.getErrorType()), e.getErrorTag().getTagValue(),
237                     e.getErrorMessage());
238         }
239
240         return to;
241     }
242
243     private static ErrorType toRpcErrorType(final RestconfError.ErrorType errorType) {
244         switch (errorType) {
245             case TRANSPORT:
246                 return ErrorType.TRANSPORT;
247             case RPC:
248                 return ErrorType.RPC;
249             case PROTOCOL:
250                 return ErrorType.PROTOCOL;
251             default:
252                 return ErrorType.APPLICATION;
253         }
254     }
255
256     private static class SimpleUriInfo implements UriInfo {
257         private final String path;
258         private final MultivaluedMap<String, String> queryParams;
259
260         SimpleUriInfo(String path) {
261             this(path, new MultivaluedHashMap<>());
262         }
263
264         SimpleUriInfo(String path, MultivaluedMap<String, String> queryParams) {
265             this.path = path;
266             this.queryParams = queryParams;
267         }
268
269         @Override
270         public String getPath() {
271             return path;
272         }
273
274         @Override
275         public String getPath(boolean decode) {
276             return path;
277         }
278
279         @Override
280         public List<PathSegment> getPathSegments() {
281             throw new UnsupportedOperationException();
282         }
283
284         @Override
285         public List<PathSegment> getPathSegments(boolean decode) {
286             throw new UnsupportedOperationException();
287         }
288
289         @Override
290         public URI getRequestUri() {
291             return URI.create(path);
292         }
293
294         @Override
295         public UriBuilder getRequestUriBuilder() {
296             return UriBuilder.fromUri(getRequestUri());
297         }
298
299         @Override
300         public URI getAbsolutePath() {
301             return getRequestUri();
302         }
303
304         @Override
305         public UriBuilder getAbsolutePathBuilder() {
306             return UriBuilder.fromUri(getAbsolutePath());
307         }
308
309         @Override
310         public URI getBaseUri() {
311             return URI.create("");
312         }
313
314         @Override
315         public UriBuilder getBaseUriBuilder() {
316             return UriBuilder.fromUri(getBaseUri());
317         }
318
319         @Override
320         public MultivaluedMap<String, String> getPathParameters() {
321             return new MultivaluedHashMap<>();
322         }
323
324         @Override
325         public MultivaluedMap<String, String> getPathParameters(boolean decode) {
326             return getPathParameters();
327         }
328
329         @Override
330         public MultivaluedMap<String, String> getQueryParameters() {
331             return queryParams;
332         }
333
334         @Override
335         public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
336             return getQueryParameters();
337         }
338
339         @Override
340         public List<String> getMatchedURIs() {
341             return Collections.emptyList();
342         }
343
344         @Override
345         public List<String> getMatchedURIs(boolean decode) {
346             return getMatchedURIs();
347         }
348
349         @Override
350         public List<Object> getMatchedResources() {
351             return Collections.emptyList();
352         }
353
354         @Override
355         public URI resolve(URI uri) {
356             return uri;
357         }
358
359         @Override
360         public URI relativize(URI uri) {
361             return uri;
362         }
363     }
364 }