c13cf84b2cc28c9e634d9393f38d46fc5f9e1993
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / restconf / restful / services / impl / RestconfDataServiceImpl.java
1 /*
2  * Copyright (c) 2016 Cisco 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.restful.services.impl;
9
10 import static org.opendaylight.restconf.restful.utils.RestconfStreamsConstants.CREATE_NOTIFICATION_STREAM;
11 import static org.opendaylight.restconf.restful.utils.RestconfStreamsConstants.STREAM_ACCESS_PATH_PART;
12 import static org.opendaylight.restconf.restful.utils.RestconfStreamsConstants.STREAM_LOCATION_PATH_PART;
13 import static org.opendaylight.restconf.restful.utils.RestconfStreamsConstants.STREAM_PATH;
14
15 import com.google.common.base.Optional;
16 import com.google.common.base.Preconditions;
17 import java.time.Clock;
18 import java.time.LocalDateTime;
19 import java.time.format.DateTimeFormatter;
20 import java.util.List;
21 import java.util.Map.Entry;
22 import javax.annotation.Nonnull;
23 import javax.ws.rs.core.Response;
24 import javax.ws.rs.core.UriInfo;
25 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
26 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
27 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
28 import org.opendaylight.netconf.sal.restconf.impl.PatchContext;
29 import org.opendaylight.netconf.sal.restconf.impl.PatchStatusContext;
30 import org.opendaylight.restconf.RestConnectorProvider;
31 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
32 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
33 import org.opendaylight.restconf.common.context.WriterParameters;
34 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
35 import org.opendaylight.restconf.common.errors.RestconfError;
36 import org.opendaylight.restconf.common.references.SchemaContextRef;
37 import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
38 import org.opendaylight.restconf.handlers.SchemaContextHandler;
39 import org.opendaylight.restconf.handlers.TransactionChainHandler;
40 import org.opendaylight.restconf.restful.services.api.RestconfDataService;
41 import org.opendaylight.restconf.restful.services.api.RestconfStreamsSubscriptionService;
42 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
43 import org.opendaylight.restconf.restful.utils.DeleteDataTransactionUtil;
44 import org.opendaylight.restconf.restful.utils.PatchDataTransactionUtil;
45 import org.opendaylight.restconf.restful.utils.PostDataTransactionUtil;
46 import org.opendaylight.restconf.restful.utils.PutDataTransactionUtil;
47 import org.opendaylight.restconf.restful.utils.ReadDataTransactionUtil;
48 import org.opendaylight.restconf.restful.utils.RestconfDataServiceConstant;
49 import org.opendaylight.restconf.utils.RestconfConstants;
50 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
51 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
52 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 /**
57  * Implementation of {@link RestconfDataService}.
58  */
59 public class RestconfDataServiceImpl implements RestconfDataService {
60
61     private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
62     private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
63
64     private final SchemaContextHandler schemaContextHandler;
65     private final TransactionChainHandler transactionChainHandler;
66     private final DOMMountPointServiceHandler mountPointServiceHandler;
67
68     private final RestconfStreamsSubscriptionService delegRestconfSubscrService;
69
70     public RestconfDataServiceImpl(final SchemaContextHandler schemaContextHandler,
71                                    final TransactionChainHandler transactionChainHandler,
72             final DOMMountPointServiceHandler mountPointServiceHandler,
73             final RestconfStreamsSubscriptionService delegRestconfSubscrService) {
74         this.schemaContextHandler = schemaContextHandler;
75         this.transactionChainHandler = transactionChainHandler;
76         this.mountPointServiceHandler = mountPointServiceHandler;
77         this.delegRestconfSubscrService = delegRestconfSubscrService;
78     }
79
80     @Override
81     public Response readData(final UriInfo uriInfo) {
82         return readData(null, uriInfo);
83     }
84
85     @Override
86     public Response readData(final String identifier, final UriInfo uriInfo) {
87         final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
88         final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
89                 identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
90
91         boolean withDefaUsed = false;
92         String withDefa = null;
93
94         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
95             switch (entry.getKey()) {
96                 case "with-defaults":
97                     if (!withDefaUsed) {
98                         withDefaUsed = true;
99                         withDefa = entry.getValue().iterator().next();
100                     } else {
101                         throw new RestconfDocumentedException("With-defaults parameter can be used only once.");
102                     }
103                     break;
104                 default:
105                     LOG.info("Unknown key : {}.", entry.getKey());
106                     break;
107             }
108         }
109         boolean tagged = false;
110         if (withDefaUsed) {
111             if ("report-all-tagged".equals(withDefa)) {
112                 tagged = true;
113                 withDefa = null;
114             }
115             if ("report-all".equals(withDefa)) {
116                 withDefa = null;
117             }
118         }
119
120         final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(
121                 instanceIdentifier, uriInfo, tagged);
122
123         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
124         final DOMTransactionChain transactionChain;
125         if (mountPoint == null) {
126             transactionChain = this.transactionChainHandler.get();
127         } else {
128             transactionChain = transactionChainOfMountPoint(mountPoint);
129         }
130
131         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
132                 instanceIdentifier, mountPoint, transactionChain);
133         final NormalizedNode<?, ?> node =
134                 ReadDataTransactionUtil.readData(identifier, parameters.getContent(), transactionNode, withDefa,
135                         schemaContextRef, uriInfo);
136         if (identifier.contains(STREAM_PATH) && identifier.contains(STREAM_ACCESS_PATH_PART)
137                 && identifier.contains(STREAM_LOCATION_PATH_PART)) {
138             final String value = (String) node.getValue();
139             final String streamName = value.substring(
140                     value.indexOf(CREATE_NOTIFICATION_STREAM.toString() + RestconfConstants.SLASH),
141                     value.length());
142             this.delegRestconfSubscrService.subscribeToStream(streamName, uriInfo);
143         }
144         if (node == null) {
145             throw new RestconfDocumentedException(
146                     "Request could not be completed because the relevant data model content does not exist",
147                     RestconfError.ErrorType.PROTOCOL,
148                     RestconfError.ErrorTag.DATA_MISSING);
149         }
150
151         if ((parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL))
152                     || parameters.getContent().equals(RestconfDataServiceConstant.ReadData.CONFIG)) {
153             return Response.status(200)
154                     .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters))
155                     .header("ETag", '"' + node.getNodeType().getModule().getFormattedRevision()
156                         + node.getNodeType().getLocalName() + '"')
157                     .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
158                     .build();
159         }
160
161         return Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)).build();
162     }
163
164     @Override
165     public Response putData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
166         Preconditions.checkNotNull(payload);
167
168         boolean insertUsed = false;
169         boolean pointUsed = false;
170         String insert = null;
171         String point = null;
172
173         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
174             switch (entry.getKey()) {
175                 case "insert":
176                     if (!insertUsed) {
177                         insertUsed = true;
178                         insert = entry.getValue().iterator().next();
179                     } else {
180                         throw new RestconfDocumentedException("Insert parameter can be used only once.");
181                     }
182                     break;
183                 case "point":
184                     if (!pointUsed) {
185                         pointUsed = true;
186                         point = entry.getValue().iterator().next();
187                     } else {
188                         throw new RestconfDocumentedException("Point parameter can be used only once.");
189                     }
190                     break;
191                 default:
192                     throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
193             }
194         }
195
196         checkQueryParams(insertUsed, pointUsed, insert);
197
198         final InstanceIdentifierContext<? extends SchemaNode> iid = payload
199                 .getInstanceIdentifierContext();
200
201         PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload);
202         PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
203         PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
204
205         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
206         final DOMTransactionChain transactionChain;
207         final SchemaContextRef ref;
208         if (mountPoint == null) {
209             transactionChain = this.transactionChainHandler.get();
210             ref = new SchemaContextRef(this.schemaContextHandler.get());
211         } else {
212             transactionChain = transactionChainOfMountPoint(mountPoint);
213             ref = new SchemaContextRef(mountPoint.getSchemaContext());
214         }
215
216         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
217                 payload.getInstanceIdentifierContext(), mountPoint, transactionChain);
218         return PutDataTransactionUtil.putData(payload, ref, transactionNode, insert, point);
219     }
220
221     private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed, final String insert) {
222         if (pointUsed && !insertUsed) {
223             throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
224         }
225         if (pointUsed && (insert.equals("first") || insert.equals("last"))) {
226             throw new RestconfDocumentedException(
227                     "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
228         }
229     }
230
231     @Override
232     public Response postData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
233         return postData(payload, uriInfo);
234     }
235
236     @Override
237     public Response postData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
238         Preconditions.checkNotNull(payload);
239
240         boolean insertUsed = false;
241         boolean pointUsed = false;
242         String insert = null;
243         String point = null;
244
245         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
246             switch (entry.getKey()) {
247                 case "insert":
248                     if (!insertUsed) {
249                         insertUsed = true;
250                         insert = entry.getValue().iterator().next();
251                     } else {
252                         throw new RestconfDocumentedException("Insert parameter can be used only once.");
253                     }
254                     break;
255                 case "point":
256                     if (!pointUsed) {
257                         pointUsed = true;
258                         point = entry.getValue().iterator().next();
259                     } else {
260                         throw new RestconfDocumentedException("Point parameter can be used only once.");
261                     }
262                     break;
263                 default:
264                     throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
265             }
266         }
267
268         checkQueryParams(insertUsed, pointUsed, insert);
269
270         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
271         final DOMTransactionChain transactionChain;
272         final SchemaContextRef ref;
273         if (mountPoint == null) {
274             transactionChain = this.transactionChainHandler.get();
275             ref = new SchemaContextRef(this.schemaContextHandler.get());
276         } else {
277             transactionChain = transactionChainOfMountPoint(mountPoint);
278             ref = new SchemaContextRef(mountPoint.getSchemaContext());
279         }
280         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
281                 payload.getInstanceIdentifierContext(), mountPoint, transactionChain);
282         return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode, ref, insert, point);
283     }
284
285     @Override
286     public Response deleteData(final String identifier) {
287         final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
288         final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
289                 identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
290
291         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
292         final DOMTransactionChain transactionChain;
293         if (mountPoint == null) {
294             transactionChain = this.transactionChainHandler.get();
295         } else {
296             transactionChain = transactionChainOfMountPoint(mountPoint);
297         }
298
299         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint,
300                 transactionChain);
301         return DeleteDataTransactionUtil.deleteData(transactionNode);
302     }
303
304     @Override
305     public PatchStatusContext patchData(final String identifier, final PatchContext context, final UriInfo uriInfo) {
306         return patchData(context, uriInfo);
307     }
308
309     @Override
310     public PatchStatusContext patchData(final PatchContext context, final UriInfo uriInfo) {
311         Preconditions.checkNotNull(context);
312         final DOMMountPoint mountPoint = context.getInstanceIdentifierContext().getMountPoint();
313
314         final DOMTransactionChain transactionChain;
315         final SchemaContextRef ref;
316         if (mountPoint == null) {
317             transactionChain = this.transactionChainHandler.get();
318             ref = new SchemaContextRef(this.schemaContextHandler.get());
319         } else {
320             transactionChain = transactionChainOfMountPoint(mountPoint);
321             ref = new SchemaContextRef(mountPoint.getSchemaContext());
322         }
323
324         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
325                 context.getInstanceIdentifierContext(), mountPoint, transactionChain);
326
327         return PatchDataTransactionUtil.patchData(context, transactionNode, ref);
328     }
329
330     /**
331      * Prepare transaction chain to access data of mount point.
332      * @param mountPoint
333      *            mount point reference
334      * @return {@link DOMTransactionChain}
335      */
336     private static DOMTransactionChain transactionChainOfMountPoint(@Nonnull final DOMMountPoint mountPoint) {
337         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
338         if (domDataBrokerService.isPresent()) {
339             return domDataBrokerService.get().createTransactionChain(RestConnectorProvider.TRANSACTION_CHAIN_LISTENER);
340         }
341
342         final String errMsg = "DOM data broker service isn't available for mount point " + mountPoint.getIdentifier();
343         LOG.warn(errMsg);
344         throw new RestconfDocumentedException(errMsg);
345     }
346 }