Use FilterParameter for communicating its value
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / RestconfStreamsSubscriptionServiceImpl.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 com.google.common.base.Preconditions.checkState;
11
12 import java.net.URI;
13 import java.util.List;
14 import java.util.Map.Entry;
15 import java.util.Optional;
16 import javax.ws.rs.Path;
17 import javax.ws.rs.core.UriInfo;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
20 import org.opendaylight.mdsal.dom.api.DOMNotificationService;
21 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
22 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
23 import org.opendaylight.restconf.nb.rfc8040.FilterParameter;
24 import org.opendaylight.restconf.nb.rfc8040.StartTimeParameter;
25 import org.opendaylight.restconf.nb.rfc8040.StopTimeParameter;
26 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
27 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
28 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
29 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants;
30 import org.opendaylight.restconf.nb.rfc8040.streams.Configuration;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
34 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.Module;
37 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
38 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Implementation of {@link RestconfStreamsSubscriptionService}.
44  */
45 @Path("/")
46 public class RestconfStreamsSubscriptionServiceImpl implements RestconfStreamsSubscriptionService {
47     private static final Logger LOG = LoggerFactory.getLogger(RestconfStreamsSubscriptionServiceImpl.class);
48     private static final QName LOCATION_QNAME =
49         QName.create("subscribe:to:notification", "2016-10-28", "location").intern();
50     private static final NodeIdentifier LOCATION_NODEID = NodeIdentifier.create(LOCATION_QNAME);
51     private static final QName NOTIFI_QNAME = QName.create(LOCATION_QNAME, "notifi").intern();
52     private static final YangInstanceIdentifier LOCATION_PATH =
53         YangInstanceIdentifier.create(NodeIdentifier.create(NOTIFI_QNAME), LOCATION_NODEID);
54
55     private final SubscribeToStreamUtil streamUtils;
56     private final HandlersHolder handlersHolder;
57
58     /**
59      * Initialize holder of handlers with holders as parameters.
60      *
61      * @param dataBroker {@link DOMDataBroker}
62      * @param notificationService {@link DOMNotificationService}
63      * @param schemaHandler
64      *             handler of {@link SchemaContext}
65      * @param configuration
66      *             configuration for restconf {@link Configuration}}
67      */
68     public RestconfStreamsSubscriptionServiceImpl(final DOMDataBroker dataBroker,
69             final DOMNotificationService notificationService, final SchemaContextHandler schemaHandler,
70             final Configuration configuration) {
71         handlersHolder = new HandlersHolder(dataBroker, notificationService, schemaHandler);
72         streamUtils = configuration.isUseSSE() ? SubscribeToStreamUtil.serverSentEvents()
73                 : SubscribeToStreamUtil.webSockets();
74     }
75
76     @Override
77     public NormalizedNodePayload subscribeToStream(final String identifier, final UriInfo uriInfo) {
78         final NotificationQueryParams notificationQueryParams = NotificationQueryParams.fromUriInfo(uriInfo);
79
80         final URI response;
81         if (identifier.contains(RestconfStreamsConstants.DATA_SUBSCRIPTION)) {
82             response = streamUtils.subscribeToDataStream(identifier, uriInfo, notificationQueryParams, handlersHolder);
83         } else if (identifier.contains(RestconfStreamsConstants.NOTIFICATION_STREAM)) {
84             response = streamUtils.subscribeToYangStream(identifier, uriInfo, notificationQueryParams, handlersHolder);
85         } else {
86             final String msg = "Bad type of notification of sal-remote";
87             LOG.warn(msg);
88             throw new RestconfDocumentedException(msg);
89         }
90
91         // prepare node with value of location
92         return NormalizedNodePayload.ofLocation(prepareIIDSubsStreamOutput(handlersHolder.getSchemaHandler()),
93             LOCATION_NODEID, response);
94     }
95
96     /**
97      * Prepare InstanceIdentifierContext for Location leaf.
98      *
99      * @param schemaHandler Schema context handler.
100      * @return InstanceIdentifier of Location leaf.
101      */
102     private static InstanceIdentifierContext<?> prepareIIDSubsStreamOutput(final SchemaContextHandler schemaHandler) {
103         final Optional<Module> module = schemaHandler.get().findModule(NOTIFI_QNAME.getModule());
104         checkState(module.isPresent());
105         final DataSchemaNode notify = module.get().dataChildByName(NOTIFI_QNAME);
106         checkState(notify instanceof ContainerSchemaNode, "Unexpected non-container %s", notify);
107         final DataSchemaNode location = ((ContainerSchemaNode) notify).dataChildByName(LOCATION_QNAME);
108         checkState(location != null, "Missing location");
109
110         return new InstanceIdentifierContext<SchemaNode>(LOCATION_PATH, location, null, schemaHandler.get());
111     }
112
113     /**
114      * Holder of all handlers for notifications.
115      */
116     // FIXME: why do we even need this class?!
117     public static final class HandlersHolder {
118         private final DOMDataBroker dataBroker;
119         private final DOMNotificationService notificationService;
120         private final SchemaContextHandler schemaHandler;
121
122         private HandlersHolder(final DOMDataBroker dataBroker, final DOMNotificationService notificationService,
123                 final SchemaContextHandler schemaHandler) {
124             this.dataBroker = dataBroker;
125             this.notificationService = notificationService;
126             this.schemaHandler = schemaHandler;
127         }
128
129         /**
130          * Get {@link DOMDataBroker}.
131          *
132          * @return the dataBroker
133          */
134         public DOMDataBroker getDataBroker() {
135             return dataBroker;
136         }
137
138         /**
139          * Get {@link DOMNotificationService}.
140          *
141          * @return the notificationService
142          */
143         public DOMNotificationService getNotificationServiceHandler() {
144             return notificationService;
145         }
146
147         /**
148          * Get {@link SchemaContextHandler}.
149          *
150          * @return the schemaHandler
151          */
152         public SchemaContextHandler getSchemaHandler() {
153             return schemaHandler;
154         }
155     }
156
157     /**
158      * Parser and holder of query paramteres from uriInfo for notifications.
159      */
160     public static final class NotificationQueryParams {
161         private final StartTimeParameter startTime;
162         private final StopTimeParameter stopTime;
163         private final FilterParameter filter;
164         private final boolean skipNotificationData;
165
166         private NotificationQueryParams(final StartTimeParameter startTime, final StopTimeParameter stopTime,
167                 final FilterParameter filter, final boolean skipNotificationData) {
168             this.startTime = startTime;
169             this.stopTime = stopTime;
170             this.filter = filter;
171             this.skipNotificationData = skipNotificationData;
172         }
173
174         static NotificationQueryParams fromUriInfo(final UriInfo uriInfo) {
175             StartTimeParameter startTime = null;
176             StopTimeParameter stopTime = null;
177             FilterParameter filter = null;
178             boolean skipNotificationData = false;
179
180             for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
181                 final String paramName = entry.getKey();
182                 final List<String> paramValues = entry.getValue();
183                 if (paramName.equals(StartTimeParameter.uriName())) {
184                     switch (paramValues.size()) {
185                         case 0:
186                             break;
187                         case 1:
188                             final String str = paramValues.get(0);
189                             try {
190                                 startTime = StartTimeParameter.forUriValue(str);
191                             } catch (IllegalArgumentException e) {
192                                 throw new RestconfDocumentedException("Invalid start-time date: " + str, e);
193                             }
194                             break;
195                         default:
196                             throw new RestconfDocumentedException("Start-time parameter can be used only once.");
197                     }
198                 } else if (paramName.equals(StopTimeParameter.uriName())) {
199                     switch (paramValues.size()) {
200                         case 0:
201                             break;
202                         case 1:
203                             final String str = paramValues.get(0);
204                             try {
205                                 stopTime = StopTimeParameter.forUriValue(str);
206                             } catch (IllegalArgumentException e) {
207                                 throw new RestconfDocumentedException("Invalid stop-time date: " + str, e);
208                             }
209                             break;
210                         default:
211                             throw new RestconfDocumentedException("Stop-time parameter can be used only once.");
212                     }
213                 } else if (paramName.equals(FilterParameter.uriName())) {
214                     if (!paramValues.isEmpty()) {
215                         filter = FilterParameter.forUriValue(paramValues.get(0));
216                     }
217                 } else if (paramName.equals("odl-skip-notification-data")) {
218                     switch (paramValues.size()) {
219                         case 0:
220                             break;
221                         case 1:
222                             skipNotificationData = Boolean.parseBoolean(paramValues.get(0));
223                             break;
224                         default:
225                             throw new RestconfDocumentedException(
226                                 "Odl-skip-notification-data parameter can be used only once.");
227                     }
228                 } else {
229                     throw new RestconfDocumentedException("Bad parameter used with notifications: " + paramName);
230                 }
231             }
232             if (startTime == null && stopTime != null) {
233                 throw new RestconfDocumentedException("Stop-time parameter has to be used with start-time parameter.");
234             }
235
236             return new NotificationQueryParams(startTime, stopTime, filter, skipNotificationData);
237         }
238
239         /**
240          * Get start-time query parameter.
241          *
242          * @return start-time
243          */
244         public @Nullable StartTimeParameter startTime() {
245             return startTime;
246         }
247
248         /**
249          * Get stop-time query parameter.
250          *
251          * @return stop-time
252          */
253         public @Nullable StopTimeParameter stopTime() {
254             return stopTime;
255         }
256
257         /**
258          * Get filter query parameter.
259          *
260          * @return filter
261          */
262         public @Nullable FilterParameter filter() {
263             return filter;
264         }
265
266         /**
267          * Check whether this query should notify changes without data.
268          *
269          * @return true if this query should notify about changes with  data
270          */
271         public boolean isSkipNotificationData() {
272             return skipNotificationData;
273         }
274     }
275 }