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