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