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