Switch time keeping to java.time interfaces
[netconf.git] / restconf / sal-rest-connector / 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.InstanceIdentifierContext;
29 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
30 import org.opendaylight.netconf.sal.restconf.impl.PATCHContext;
31 import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
32 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
33 import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
34 import org.opendaylight.netconf.sal.restconf.impl.WriterParameters;
35 import org.opendaylight.restconf.RestConnectorProvider;
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 withDefa_used = 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 (!withDefa_used) {
98                         withDefa_used = 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             }
105         }
106         boolean tagged = false;
107         if (withDefa_used) {
108             if ("report-all-tagged".equals(withDefa)) {
109                 tagged = true;
110                 withDefa = null;
111             }
112             if ("report-all".equals(withDefa)) {
113                 withDefa = null;
114             }
115         }
116
117         final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(
118                 instanceIdentifier, uriInfo, tagged);
119
120         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
121         final DOMTransactionChain transactionChain;
122         if (mountPoint == null) {
123             transactionChain = this.transactionChainHandler.get();
124         } else {
125             transactionChain = transactionChainOfMountPoint(mountPoint);
126         }
127
128         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
129                 instanceIdentifier, mountPoint, transactionChain);
130         final NormalizedNode<?, ?> node =
131                 ReadDataTransactionUtil.readData(identifier, parameters.getContent(), transactionNode, withDefa,
132                         schemaContextRef, uriInfo);
133         if (identifier.contains(STREAM_PATH) && identifier.contains(STREAM_ACCESS_PATH_PART)
134                 && identifier.contains(STREAM_LOCATION_PATH_PART)) {
135             final String value = (String) node.getValue();
136             final String streamName = value.substring(
137                     value.indexOf(CREATE_NOTIFICATION_STREAM.toString() + RestconfConstants.SLASH),
138                     value.length());
139             this.delegRestconfSubscrService.subscribeToStream(streamName, uriInfo);
140         }
141         if (node == null) {
142             throw new RestconfDocumentedException(
143                     "Request could not be completed because the relevant data model content does not exist",
144                     RestconfError.ErrorType.PROTOCOL,
145                     RestconfError.ErrorTag.DATA_MISSING);
146         }
147
148         if ((parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL))
149                     || parameters.getContent().equals(RestconfDataServiceConstant.ReadData.CONFIG)) {
150             return Response.status(200)
151                     .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters))
152                     .header("ETag", '"' + node.getNodeType().getModule().getFormattedRevision()
153                         + node.getNodeType().getLocalName() + '"')
154                     .header("Last-Modified", FORMATTER.format(LocalDateTime.now(Clock.systemUTC())))
155                     .build();
156         }
157
158         return Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node, parameters)).build();
159     }
160
161     @Override
162     public Response putData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
163         Preconditions.checkNotNull(payload);
164
165         boolean insert_used = false;
166         boolean point_used = false;
167         String insert = null;
168         String point = null;
169
170         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
171             switch (entry.getKey()) {
172                 case "insert":
173                     if (!insert_used) {
174                         insert_used = true;
175                         insert = entry.getValue().iterator().next();
176                     } else {
177                         throw new RestconfDocumentedException("Insert parameter can be used only once.");
178                     }
179                     break;
180                 case "point":
181                     if (!point_used) {
182                         point_used = true;
183                         point = entry.getValue().iterator().next();
184                     } else {
185                         throw new RestconfDocumentedException("Point parameter can be used only once.");
186                     }
187                     break;
188                 default:
189                     throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
190             }
191         }
192
193         checkQueryParams(insert_used, point_used, insert);
194
195         final InstanceIdentifierContext<? extends SchemaNode> iid = payload
196                 .getInstanceIdentifierContext();
197
198         PutDataTransactionUtil.validInputData(iid.getSchemaNode(), payload);
199         PutDataTransactionUtil.validTopLevelNodeName(iid.getInstanceIdentifier(), payload);
200         PutDataTransactionUtil.validateListKeysEqualityInPayloadAndUri(payload);
201
202         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
203         final DOMTransactionChain transactionChain;
204         final SchemaContextRef ref;
205         if (mountPoint == null) {
206             transactionChain = this.transactionChainHandler.get();
207             ref = new SchemaContextRef(this.schemaContextHandler.get());
208         } else {
209             transactionChain = transactionChainOfMountPoint(mountPoint);
210             ref = new SchemaContextRef(mountPoint.getSchemaContext());
211         }
212
213         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
214                 payload.getInstanceIdentifierContext(), mountPoint, transactionChain);
215         return PutDataTransactionUtil.putData(payload, ref, transactionNode, insert, point);
216     }
217
218     private static void checkQueryParams(final boolean insert_used, final boolean point_used, final String insert) {
219         if (point_used && !insert_used) {
220             throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
221         }
222         if (point_used && (insert.equals("first") || insert.equals("last"))) {
223             throw new RestconfDocumentedException(
224                     "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
225         }
226     }
227
228     @Override
229     public Response postData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
230         return postData(payload, uriInfo);
231     }
232
233     @Override
234     public Response postData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
235         Preconditions.checkNotNull(payload);
236
237         boolean insert_used = false;
238         boolean point_used = false;
239         String insert = null;
240         String point = null;
241
242         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
243             switch (entry.getKey()) {
244                 case "insert":
245                     if (!insert_used) {
246                         insert_used = true;
247                         insert = entry.getValue().iterator().next();
248                     } else {
249                         throw new RestconfDocumentedException("Insert parameter can be used only once.");
250                     }
251                     break;
252                 case "point":
253                     if (!point_used) {
254                         point_used = true;
255                         point = entry.getValue().iterator().next();
256                     } else {
257                         throw new RestconfDocumentedException("Point parameter can be used only once.");
258                     }
259                     break;
260                 default:
261                     throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
262             }
263         }
264
265         checkQueryParams(insert_used, point_used, insert);
266
267         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
268         final DOMTransactionChain transactionChain;
269         final SchemaContextRef ref;
270         if (mountPoint == null) {
271             transactionChain = this.transactionChainHandler.get();
272             ref = new SchemaContextRef(this.schemaContextHandler.get());
273         } else {
274             transactionChain = transactionChainOfMountPoint(mountPoint);
275             ref = new SchemaContextRef(mountPoint.getSchemaContext());
276         }
277         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
278                 payload.getInstanceIdentifierContext(), mountPoint, transactionChain);
279         return PostDataTransactionUtil.postData(uriInfo, payload, transactionNode, ref, insert, point);
280     }
281
282     @Override
283     public Response deleteData(final String identifier) {
284         final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
285         final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
286                 identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
287
288         final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
289         final DOMTransactionChain transactionChain;
290         if (mountPoint == null) {
291             transactionChain = this.transactionChainHandler.get();
292         } else {
293             transactionChain = transactionChainOfMountPoint(mountPoint);
294         }
295
296         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint,
297                 transactionChain);
298         return DeleteDataTransactionUtil.deleteData(transactionNode);
299     }
300
301     @Override
302     public PATCHStatusContext patchData(final String identifier, final PATCHContext context, final UriInfo uriInfo) {
303         return patchData(context, uriInfo);
304     }
305
306     @Override
307     public PATCHStatusContext patchData(final PATCHContext context, final UriInfo uriInfo) {
308         Preconditions.checkNotNull(context);
309         final DOMMountPoint mountPoint = context.getInstanceIdentifierContext().getMountPoint();
310
311         final DOMTransactionChain transactionChain;
312         final SchemaContextRef ref;
313         if (mountPoint == null) {
314             transactionChain = this.transactionChainHandler.get();
315             ref = new SchemaContextRef(this.schemaContextHandler.get());
316         } else {
317             transactionChain = transactionChainOfMountPoint(mountPoint);
318             ref = new SchemaContextRef(mountPoint.getSchemaContext());
319         }
320
321         final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
322                 context.getInstanceIdentifierContext(), mountPoint, transactionChain);
323
324         return PatchDataTransactionUtil.patchData(context, transactionNode, ref);
325     }
326
327     /**
328      * Prepare transaction chain to access data of mount point
329      * @param mountPoint
330      *            - mount point reference
331      * @return {@link DOMTransactionChain}
332      */
333     private static DOMTransactionChain transactionChainOfMountPoint(@Nonnull final DOMMountPoint mountPoint) {
334         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
335         if (domDataBrokerService.isPresent()) {
336             return domDataBrokerService.get().createTransactionChain(RestConnectorProvider.transactionListener);
337         }
338
339         final String errMsg = "DOM data broker service isn't available for mount point " + mountPoint.getIdentifier();
340         LOG.warn(errMsg);
341         throw new RestconfDocumentedException(errMsg);
342     }
343 }