Bug 6995 - Change event notification subscription usability PART1
[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.util.concurrent.CheckedFuture;
12 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
13 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
14 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
15 import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
16 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
17 import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
18 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
19 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
20 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
21 import org.opendaylight.restconf.common.references.SchemaContextRef;
22 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
23 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
28 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
31 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
32 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
33 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 /**
38  * Util class for streams
39  *
40  * <ul>
41  * <li>create stream
42  * <li>subscribe
43  * </ul>
44  *
45  */
46 public final class CreateStreamUtil {
47
48     private static final Logger LOG = LoggerFactory.getLogger(CreateStreamUtil.class);
49     private static final String OUTPUT_TYPE_PARAM_NAME = "notification-output-type";
50
51     private CreateStreamUtil() {
52         throw new UnsupportedOperationException("Util class");
53     }
54
55     /**
56      * Create stream with POST operation via RPC
57      *
58      * @param payload
59      *            - input of rpc - example in JSON:
60      *
61      *            <pre>
62      *            {@code
63      *            {
64      *                "input": {
65      *                    "path": "/toaster:toaster/toaster:toasterStatus",
66      *                    "sal-remote-augment:datastore": "OPERATIONAL",
67      *                    "sal-remote-augment:scope": "ONE"
68      *                }
69      *            }
70      *            }
71      *            </pre>
72      *
73      * @param refSchemaCtx
74      *            - reference to {@link SchemaContext} -
75      *            {@link SchemaContextRef}
76      * @return {@link CheckedFuture} with {@link DOMRpcResult} - This mean
77      *         output of RPC - example in JSON:
78      *
79      *         <pre>
80      *         {@code
81      *         {
82      *             "output": {
83      *                 "stream-name": "toaster:toaster/toaster:toasterStatus/datastore=OPERATIONAL/scope=ONE"
84      *             }
85      *         }
86      *         }
87      *         </pre>
88      *
89      */
90     public static DOMRpcResult createStream(final NormalizedNodeContext payload,
91             final SchemaContextRef refSchemaCtx) {
92         final ContainerNode data = (ContainerNode) payload.getData();
93         final QName qname = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
94         final YangInstanceIdentifier path = preparePath(data, qname);
95         final String streamName = prepareStream(path, refSchemaCtx.get(), data);
96
97         final QName outputQname = QName.create(qname, "output");
98         final QName streamNameQname = QName.create(qname, "stream-name");
99
100         final ContainerNode output = ImmutableContainerNodeBuilder.create()
101                 .withNodeIdentifier(new NodeIdentifier(outputQname))
102                 .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
103         final NotificationOutputType outputType = prepareOutputType(data);
104
105         if (!Notificator.existListenerFor(streamName)) {
106             Notificator.createListener(path, streamName, outputType);
107         }
108
109         return new DefaultDOMRpcResult(output);
110     }
111
112     /**
113      * @param data
114      *            - data of notification
115      * @return output type fo notification
116      */
117     private static NotificationOutputType prepareOutputType(final ContainerNode data) {
118         NotificationOutputType outputType = parseEnum(data, NotificationOutputType.class, OUTPUT_TYPE_PARAM_NAME);
119         return outputType = outputType == null ? NotificationOutputType.XML : outputType;
120     }
121
122     private static String prepareStream(final YangInstanceIdentifier path, final SchemaContext schemaContext,
123             final ContainerNode data) {
124         LogicalDatastoreType ds = parseEnum(data, LogicalDatastoreType.class,
125                 RestconfStreamsConstants.DATASTORE_PARAM_NAME);
126         ds = ds == null ? RestconfStreamsConstants.DEFAULT_DS : ds;
127
128         DataChangeScope scope = parseEnum(data, DataChangeScope.class, RestconfStreamsConstants.SCOPE_PARAM_NAME);
129         scope = scope == null ? RestconfStreamsConstants.DEFAULT_SCOPE : scope;
130
131         final String streamName = Notificator
132                 .createStreamNameFromUri(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext)
133                 + RestconfStreamsConstants.DS_URI + ds + RestconfStreamsConstants.SCOPE_URI + scope);
134         return streamName;
135     }
136
137     private static <T> T parseEnum(final ContainerNode data, final Class<T> clazz, final String paramName) {
138         final Optional<DataContainerChild<? extends PathArgument, ?>> augNode = data
139                 .getChild(RestconfStreamsConstants.SAL_REMOTE_AUG_IDENTIFIER);
140         if (!augNode.isPresent() && !(augNode instanceof AugmentationNode)) {
141             return null;
142         }
143         final Optional<DataContainerChild<? extends PathArgument, ?>> enumNode =
144                 ((AugmentationNode) augNode.get()).getChild(
145                         new NodeIdentifier(QName.create(RestconfStreamsConstants.SAL_REMOTE_AUGMENT, paramName)));
146         if (!enumNode.isPresent()) {
147             return null;
148         }
149         final Object value = enumNode.get().getValue();
150         if (!(value instanceof String)) {
151             return null;
152         }
153
154         return StreamUtil.resolveEnum(clazz, (String) value);
155     }
156
157     private static YangInstanceIdentifier preparePath(final ContainerNode data, final QName qName) {
158         final Optional<DataContainerChild<? extends PathArgument, ?>> path = data
159                 .getChild(new YangInstanceIdentifier.NodeIdentifier(QName.create(qName, "path")));
160         Object pathValue = null;
161         if (path.isPresent()) {
162             pathValue = path.get().getValue();
163         }
164         if (!(pathValue instanceof YangInstanceIdentifier)) {
165             final String errMsg = "Instance identifier was not normalized correctly ";
166             LOG.debug(errMsg + qName);
167             throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
168         }
169         return (YangInstanceIdentifier) pathValue;
170     }
171 }