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