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