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