2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.restful.utils;
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Strings;
13 import java.text.DateFormat;
14 import java.text.ParseException;
15 import java.text.SimpleDateFormat;
16 import java.util.ArrayList;
17 import java.util.Date;
18 import java.util.HashMap;
19 import java.util.List;
21 import java.util.Map.Entry;
22 import javax.ws.rs.core.UriBuilder;
23 import javax.ws.rs.core.UriInfo;
24 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
25 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
26 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
28 import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
29 import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
30 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
31 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
32 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
33 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
34 import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
35 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
36 import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer;
37 import org.opendaylight.restconf.handlers.DOMDataBrokerHandler;
38 import org.opendaylight.restconf.handlers.NotificationServiceHandler;
39 import org.opendaylight.restconf.handlers.SchemaContextHandler;
40 import org.opendaylight.restconf.utils.RestconfConstants;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
42 import org.opendaylight.yangtools.concepts.ListenerRegistration;
43 import org.opendaylight.yangtools.yang.common.QName;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
47 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
55 * Subscribe to stream util class
58 public final class SubscribeToStreamUtil {
60 private static final Logger LOG = LoggerFactory.getLogger(SubscribeToStreamUtil.class);
62 private SubscribeToStreamUtil() {
63 throw new UnsupportedOperationException("Util class");
72 * - string of enum value
75 public static <T> T parseURIEnum(final Class<T> clazz, final String value) {
76 if ((value == null) || value.equals("")) {
79 return StreamUtil.resolveEnum(clazz, value);
83 * Prepare map of values from URI
89 public static Map<String, String> mapValuesFromUri(final String identifier) {
90 final HashMap<String, String> result = new HashMap<>();
91 final String[] tokens = identifier.split(String.valueOf(RestconfConstants.SLASH));
92 for (final String token : tokens) {
93 final String[] paramToken = token.split(String.valueOf(RestconfStreamsConstants.EQUAL));
94 if (paramToken.length == 2) {
95 result.put(paramToken[0], paramToken[1]);
102 * Register data change listener in dom data broker and set it to listener
106 * - {@link LogicalDatastoreType}
108 * - {@link DataChangeScope}
110 * - listener on specific stream
111 * @param domDataBroker
112 * - data broker for register data change listener
114 private static void registration(final LogicalDatastoreType ds, final DataChangeScope scope,
115 final ListenerAdapter listener, final DOMDataBroker domDataBroker) {
116 if (listener.isListening()) {
120 final YangInstanceIdentifier path = listener.getPath();
121 final ListenerRegistration<DOMDataChangeListener> registration = domDataBroker.registerDataChangeListener(ds,
122 path, listener, scope);
124 listener.setRegistration(registration);
128 * Get port from web socket server. If doesn't exit, create it.
132 private static int prepareNotificationPort() {
133 int port = RestconfStreamsConstants.NOTIFICATION_PORT;
135 final WebSocketServer webSocketServer = WebSocketServer.getInstance();
136 port = webSocketServer.getPort();
137 } catch (final NullPointerException e) {
138 WebSocketServer.createInstance(RestconfStreamsConstants.NOTIFICATION_PORT);
144 * Register listeners by streamName in identifier to listen to yang notifications
147 * - identifier as stream name
149 * - for getting base URI information
151 * - start-time query parameter
153 * - stop-time query parameter
154 * @param notifiServiceHandler
155 * - DOMNotificationService handler for register listeners
156 * @return location for listening
158 public static URI notifStream(final String identifier, final UriInfo uriInfo, final Date start, final Date stop,
159 final NotificationServiceHandler notifiServiceHandler) {
160 final String streamName = Notificator.createStreamNameFromUri(identifier);
161 if (Strings.isNullOrEmpty(streamName)) {
162 throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
164 final List<NotificationListenerAdapter> listeners = Notificator.getNotificationListenerFor(streamName);
165 if ((listeners == null) || listeners.isEmpty()) {
166 throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL,
167 ErrorTag.UNKNOWN_ELEMENT);
170 for (final NotificationListenerAdapter listener : listeners) {
171 registerToListenNotification(listener, notifiServiceHandler);
172 listener.setTime(start, stop);
175 final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
176 int notificationPort = RestconfStreamsConstants.NOTIFICATION_PORT;
178 final WebSocketServer webSocketServerInstance = WebSocketServer.getInstance();
179 notificationPort = webSocketServerInstance.getPort();
180 } catch (final NullPointerException e) {
181 WebSocketServer.createInstance(RestconfStreamsConstants.NOTIFICATION_PORT);
183 final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme("ws");
184 final URI uriToWebsocketServer = uriToWebsocketServerBuilder.replacePath(streamName).build();
186 return uriToWebsocketServer;
189 private static void registerToListenNotification(final NotificationListenerAdapter listener,
190 final NotificationServiceHandler notificationServiceHandler) {
191 if (listener.isListening()) {
195 final SchemaPath path = listener.getSchemaPath();
196 final ListenerRegistration<DOMNotificationListener> registration =
197 notificationServiceHandler.get().registerNotificationListener(listener, path);
199 listener.setRegistration(registration);
203 * Prepare InstanceIdentifierContext for Location leaf
205 * @param schemaHandler
206 * - schemaContext handler
207 * @return InstanceIdentifier of Location leaf
209 public static InstanceIdentifierContext<?> prepareIIDSubsStreamOutput(final SchemaContextHandler schemaHandler) {
210 final QName qnameBase = QName.create("subscribe:to:notification", "2016-10-28", "notifi");
211 final DataSchemaNode location = ((ContainerSchemaNode) schemaHandler.get()
212 .findModuleByNamespaceAndRevision(qnameBase.getNamespace(), qnameBase.getRevision())
213 .getDataChildByName(qnameBase)).getDataChildByName(QName.create(qnameBase, "location"));
214 final List<PathArgument> path = new ArrayList<>();
215 path.add(NodeIdentifier.create(qnameBase));
216 path.add(NodeIdentifier.create(QName.create(qnameBase, "location")));
218 return new InstanceIdentifierContext<SchemaNode>(YangInstanceIdentifier.create(path), location, null,
219 schemaHandler.get());
223 * Register listener by streamName in identifier to listen to yang notifications
226 * - identifier as stream name
228 * - for getting base URI information
230 * - start-time query parameter
232 * - stop-time query parameter
233 * @param domDataBrokerHandler
234 * - DOMDataBroker handler for register listener
235 * @return location for listening
237 public static URI dataSubs(final String identifier, final UriInfo uriInfo, final Date start, final Date stop,
238 final DOMDataBrokerHandler domDataBrokerHandler) {
239 final Map<String, String> mapOfValues = SubscribeToStreamUtil.mapValuesFromUri(identifier);
241 final LogicalDatastoreType ds = SubscribeToStreamUtil.parseURIEnum(LogicalDatastoreType.class,
242 mapOfValues.get(RestconfStreamsConstants.DATASTORE_PARAM_NAME));
244 final String msg = "Stream name doesn't contains datastore value (pattern /datastore=)";
246 throw new RestconfDocumentedException(msg, ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
249 final DataChangeScope scope = SubscribeToStreamUtil.parseURIEnum(DataChangeScope.class,
250 mapOfValues.get(RestconfStreamsConstants.SCOPE_PARAM_NAME));
252 final String msg = "Stream name doesn't contains datastore value (pattern /scope=)";
254 throw new RestconfDocumentedException(msg, ErrorType.APPLICATION, ErrorTag.MISSING_ATTRIBUTE);
257 final String streamName = Notificator.createStreamNameFromUri(identifier);
259 final ListenerAdapter listener = Notificator.getListenerFor(streamName);
260 Preconditions.checkNotNull(listener, "Listener doesn't exist : " + streamName);
262 listener.setTimer(start, stop);
264 SubscribeToStreamUtil.registration(ds, scope, listener, domDataBrokerHandler.get());
266 final int port = SubscribeToStreamUtil.prepareNotificationPort();
268 final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
269 final UriBuilder uriToWebSocketServer =
270 uriBuilder.port(port).scheme(RestconfStreamsConstants.SCHEMA_SUBSCIBRE_URI);
271 return uriToWebSocketServer.replacePath(streamName).build();
274 public static Date parseDateFromQueryParam(final Entry<String, List<String>> entry) {
275 final DateAndTime event = new DateAndTime(entry.getValue().iterator().next());
276 String numOf_ms = "";
277 final String value = event.getValue();
278 if (value.contains(".")) {
279 numOf_ms = numOf_ms + ".";
280 final int lastChar = value.contains("Z") ? value.indexOf("Z") : (value.contains("+") ? value.indexOf("+")
281 : (value.contains("-") ? value.indexOf("-") : value.length()));
282 for (int i = 0; i < (lastChar - value.indexOf(".") - 1); i++) {
283 numOf_ms = numOf_ms + "S";
287 if (!value.contains("Z")) {
290 final DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" + numOf_ms + zone);
293 return dateFormatter.parse(value.contains("Z") ? value.replace('T', ' ').substring(0, value.indexOf("Z"))
294 : value.replace('T', ' '));
295 } catch (final ParseException e) {
296 throw new RestconfDocumentedException("Cannot parse of value in date: " + value + e);