Migrate restconf to MD-SAL APIs
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / utils / CreateStreamUtil.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.utils;
9
10 import com.google.common.base.Preconditions;
11 import java.util.ArrayList;
12 import java.util.List;
13 import java.util.Optional;
14 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
15 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
16 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
17 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
18 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
19 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
20 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
21 import org.opendaylight.restconf.common.util.DataChangeScope;
22 import org.opendaylight.restconf.nb.rfc8040.references.SchemaContextRef;
23 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter;
24 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.Notificator;
25 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
26 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
34 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
35 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
36 import org.opendaylight.yangtools.yang.model.api.Module;
37 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
39 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * Util class for streams.
45  *
46  * <ul>
47  * <li>create stream
48  * <li>subscribe
49  * </ul>
50  *
51  */
52 public final class CreateStreamUtil {
53
54     private static final Logger LOG = LoggerFactory.getLogger(CreateStreamUtil.class);
55     private static final String OUTPUT_TYPE_PARAM_NAME = "notification-output-type";
56
57     private CreateStreamUtil() {
58         throw new UnsupportedOperationException("Util class");
59     }
60
61     /**
62      * Create stream with POST operation via RPC.
63      *
64      * @param payload
65      *             input of rpc - example in JSON:
66      *
67      *            <pre>
68      *            {@code
69      *            {
70      *                "input": {
71      *                    "path": "/toaster:toaster/toaster:toasterStatus",
72      *                    "sal-remote-augment:datastore": "OPERATIONAL",
73      *                    "sal-remote-augment:scope": "ONE"
74      *                }
75      *            }
76      *            }
77      *            </pre>
78      *
79      * @param refSchemaCtx
80      *             reference to {@link SchemaContext} -
81      *            {@link SchemaContextRef}
82      * @return {@link DOMRpcResult} - This means output of RPC - example in JSON:
83      *
84      *         <pre>
85      *         {@code
86      *         {
87      *             "output": {
88      *                 "stream-name": "toaster:toaster/toaster:toasterStatus/datastore=OPERATIONAL/scope=ONE"
89      *             }
90      *         }
91      *         }
92      *         </pre>
93      *
94      */
95     public static DOMRpcResult createDataChangeNotifiStream(final NormalizedNodeContext payload,
96             final SchemaContextRef refSchemaCtx) {
97         final ContainerNode data = (ContainerNode) payload.getData();
98         final QName qname = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
99         final YangInstanceIdentifier path = preparePath(data, qname);
100         String streamName = prepareDataChangeNotifiStreamName(path, refSchemaCtx.get(), data);
101
102         final QName outputQname = QName.create(qname, "output");
103         final QName streamNameQname = QName.create(qname, "stream-name");
104
105         final NotificationOutputType outputType = prepareOutputType(data);
106         if (outputType.equals(NotificationOutputType.JSON)) {
107             streamName = streamName + "/JSON";
108         }
109
110         if (!Notificator.existListenerFor(streamName)) {
111             Notificator.createListener(path, streamName, outputType);
112         }
113
114         final ContainerNode output =
115                 ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(outputQname))
116                         .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
117         return new DefaultDOMRpcResult(output);
118     }
119
120     /**
121      * Prepare {@code NotificationOutputType}.
122      *
123      * @param data
124      *             data of notification
125      * @return output type fo notification
126      */
127     private static NotificationOutputType prepareOutputType(final ContainerNode data) {
128         NotificationOutputType outputType = parseEnum(data, NotificationOutputType.class, OUTPUT_TYPE_PARAM_NAME);
129         return outputType == null ? NotificationOutputType.XML : outputType;
130     }
131
132     private static String prepareDataChangeNotifiStreamName(final YangInstanceIdentifier path,
133                                                             final SchemaContext schemaContext,
134             final ContainerNode data) {
135         LogicalDatastoreType ds = parseEnum(data, LogicalDatastoreType.class,
136                 RestconfStreamsConstants.DATASTORE_PARAM_NAME);
137         ds = ds == null ? RestconfStreamsConstants.DEFAULT_DS : ds;
138
139         DataChangeScope scope = parseEnum(data, DataChangeScope.class, RestconfStreamsConstants.SCOPE_PARAM_NAME);
140         scope = scope == null ? RestconfStreamsConstants.DEFAULT_SCOPE : scope;
141
142         final String streamName = RestconfStreamsConstants.DATA_SUBSCR + "/"
143                 + Notificator
144                 .createStreamNameFromUri(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext)
145                 + RestconfStreamsConstants.DS_URI + ds + RestconfStreamsConstants.SCOPE_URI + scope);
146         return streamName;
147     }
148
149     private static <T> T parseEnum(final ContainerNode data, final Class<T> clazz, final String paramName) {
150         final Optional<DataContainerChild<? extends PathArgument, ?>> optAugNode = data.getChild(
151             RestconfStreamsConstants.SAL_REMOTE_AUG_IDENTIFIER);
152         if (!optAugNode.isPresent()) {
153             return null;
154         }
155         final DataContainerChild<? extends PathArgument, ?> augNode = optAugNode.get();
156         if (!(augNode instanceof AugmentationNode)) {
157             return null;
158         }
159         final Optional<DataContainerChild<? extends PathArgument, ?>> enumNode = ((AugmentationNode) augNode).getChild(
160             new NodeIdentifier(QName.create(RestconfStreamsConstants.SAL_REMOTE_AUGMENT, paramName)));
161         if (!enumNode.isPresent()) {
162             return null;
163         }
164         final Object value = enumNode.get().getValue();
165         if (!(value instanceof String)) {
166             return null;
167         }
168
169         return ResolveEnumUtil.resolveEnum(clazz, (String) value);
170     }
171
172     private static YangInstanceIdentifier preparePath(final ContainerNode data, final QName qualifiedName) {
173         final Optional<DataContainerChild<? extends PathArgument, ?>> path = data
174                 .getChild(new YangInstanceIdentifier.NodeIdentifier(QName.create(qualifiedName, "path")));
175         Object pathValue = null;
176         if (path.isPresent()) {
177             pathValue = path.get().getValue();
178         }
179         if (!(pathValue instanceof YangInstanceIdentifier)) {
180             LOG.debug("Instance identifier {} was not normalized correctly", qualifiedName);
181             throw new RestconfDocumentedException("Instance identifier was not normalized correctly",
182                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
183         }
184         return (YangInstanceIdentifier) pathValue;
185     }
186
187     /**
188      * Create stream with POST operation via RPC.
189      *
190      * @param notificatinoDefinition
191      *             input of RPC
192      * @param refSchemaCtx
193      *             schemaContext
194      * @param outputType
195      *              output type
196      * @return {@link DOMRpcResult}
197      */
198     public static List<NotificationListenerAdapter> createYangNotifiStream(
199             final NotificationDefinition notificatinoDefinition, final SchemaContextRef refSchemaCtx,
200             final String outputType) {
201         final List<SchemaPath> paths = new ArrayList<>();
202         final QName notificatinoDefinitionQName = notificatinoDefinition.getQName();
203         final Module module =
204                 refSchemaCtx.findModuleByNamespaceAndRevision(notificatinoDefinitionQName.getModule().getNamespace(),
205                         notificatinoDefinitionQName.getModule().getRevision());
206         Preconditions.checkNotNull(module,
207                 "Module for namespace " + notificatinoDefinitionQName.getModule().getNamespace() + " does not exist");
208         NotificationDefinition notifiDef = null;
209         for (final NotificationDefinition notification : module.getNotifications()) {
210             if (notification.getQName().equals(notificatinoDefinitionQName)) {
211                 notifiDef = notification;
212                 break;
213             }
214         }
215         final String moduleName = module.getName();
216         Preconditions.checkNotNull(notifiDef,
217                 "Notification " + notificatinoDefinitionQName + "doesn't exist in module " + moduleName);
218         paths.add(notifiDef.getPath());
219         String streamName = RestconfStreamsConstants.CREATE_NOTIFICATION_STREAM + "/";
220         streamName = streamName + moduleName + ":" + notificatinoDefinitionQName.getLocalName();
221         if (outputType.equals("JSON")) {
222             streamName = streamName + "/JSON";
223         }
224
225         if (Notificator.existNotificationListenerFor(streamName)) {
226             final List<NotificationListenerAdapter> notificationListenerFor =
227                     Notificator.getNotificationListenerFor(streamName);
228             return SubscribeToStreamUtil.pickSpecificListenerByOutput(notificationListenerFor, outputType);
229         }
230
231         return Notificator.createNotificationListener(paths, streamName, outputType);
232     }
233 }