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