Do not reference individual builders
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / 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.services.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableSet;
13 import java.util.Optional;
14 import org.eclipse.jdt.annotation.Nullable;
15 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
16 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
17 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
18 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
19 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
20 import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants;
21 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.ListenersBroker;
22 import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListenerAdapter;
23 import org.opendaylight.restconf.nb.rfc8040.utils.parser.IdentifierCodec;
24 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.CreateDataChangeEventSubscriptionInput1.Scope;
25 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping;
26 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
27 import org.opendaylight.yangtools.yang.common.ErrorTag;
28 import org.opendaylight.yangtools.yang.common.ErrorType;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.common.QNameModule;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
34 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
37 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
38 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
39 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
40 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
41 import org.opendaylight.yangtools.yang.model.api.Module;
42 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
43 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * Utility class for creation of data-change-event or YANG notification streams.
49  */
50 final class CreateStreamUtil {
51     private static final Logger LOG = LoggerFactory.getLogger(CreateStreamUtil.class);
52     private static final QNameModule SAL_REMOTE_AUGMENT = NotificationOutputTypeGrouping.QNAME.getModule();
53     private static final QName DATASTORE_QNAME =
54         QName.create(SAL_REMOTE_AUGMENT, RestconfStreamsConstants.DATASTORE_PARAM_NAME).intern();
55     private static final QName SCOPE_QNAME =
56         QName.create(SAL_REMOTE_AUGMENT, RestconfStreamsConstants.SCOPE_PARAM_NAME).intern();
57     private static final QName OUTPUT_TYPE_QNAME =
58         QName.create(SAL_REMOTE_AUGMENT, "notification-output-type").intern();
59     private static final NodeIdentifier DATASTORE_NODEID = NodeIdentifier.create(DATASTORE_QNAME);
60     private static final NodeIdentifier SCOPE_NODEID = NodeIdentifier.create(SCOPE_QNAME);
61     private static final NodeIdentifier OUTPUT_TYPE_NODEID = NodeIdentifier.create(OUTPUT_TYPE_QNAME);
62
63     private static final AugmentationIdentifier SAL_REMOTE_AUG_IDENTIFIER = new AugmentationIdentifier(
64         ImmutableSet.of(SCOPE_QNAME, DATASTORE_QNAME, OUTPUT_TYPE_QNAME));
65
66     private CreateStreamUtil() {
67         // Hidden on purpose
68     }
69
70     /**
71      * Create data-change-event or notification stream with POST operation via RPC.
72      *
73      * @param payload      Input of RPC - example in JSON (data-change-event stream):
74      *                     <pre>
75      *                     {@code
76      *                         {
77      *                             "input": {
78      *                                 "path": "/toaster:toaster/toaster:toasterStatus",
79      *                                 "sal-remote-augment:datastore": "OPERATIONAL",
80      *                                 "sal-remote-augment:scope": "ONE"
81      *                             }
82      *                         }
83      *                     }
84      *                     </pre>
85      * @param refSchemaCtx Reference to {@link EffectiveModelContext}.
86      * @return {@link DOMRpcResult} - Output of RPC - example in JSON:
87      *     <pre>
88      *     {@code
89      *         {
90      *             "output": {
91      *                 "stream-name": "toaster:toaster/toaster:toasterStatus/datastore=OPERATIONAL/scope=ONE"
92      *             }
93      *         }
94      *     }
95      *     </pre>
96      */
97     static DOMRpcResult createDataChangeNotifiStream(final NormalizedNodePayload payload,
98             final EffectiveModelContext refSchemaCtx) {
99         // parsing out of container with settings and path
100         final ContainerNode data = (ContainerNode) requireNonNull(payload).getData();
101         final QName qname = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
102         final YangInstanceIdentifier path = preparePath(data, qname);
103
104         // building of stream name
105         final StringBuilder streamNameBuilder = new StringBuilder(
106                 prepareDataChangeNotifiStreamName(path, requireNonNull(refSchemaCtx), data));
107         final NotificationOutputType outputType = prepareOutputType(data);
108         if (outputType.equals(NotificationOutputType.JSON)) {
109             streamNameBuilder.append('/').append(outputType.getName());
110         }
111         final String streamName = streamNameBuilder.toString();
112
113         // registration of the listener
114         ListenersBroker.getInstance().registerDataChangeListener(path, streamName, outputType);
115
116         // building of output
117         final QName outputQname = QName.create(qname, "output");
118         final QName streamNameQname = QName.create(qname, "stream-name");
119
120         return new DefaultDOMRpcResult(Builders.containerBuilder()
121             .withNodeIdentifier(new NodeIdentifier(outputQname))
122             .withChild(ImmutableNodes.leafNode(streamNameQname, streamName))
123             .build());
124     }
125
126     /**
127      * Prepare {@link NotificationOutputType}.
128      *
129      * @param data Container with stream settings (RPC create-stream).
130      * @return Parsed {@link NotificationOutputType}.
131      */
132     private static NotificationOutputType prepareOutputType(final ContainerNode data) {
133         final String outputName = extractStringLeaf(data, OUTPUT_TYPE_NODEID);
134         return outputName != null ? NotificationOutputType.valueOf(outputName) : NotificationOutputType.XML;
135     }
136
137     /**
138      * Prepare stream name.
139      *
140      * @param path          Path of element from which data-change-event notifications are going to be generated.
141      * @param schemaContext Schema context.
142      * @param data          Container with stream settings (RPC create-stream).
143      * @return Parsed stream name.
144      */
145     private static String prepareDataChangeNotifiStreamName(final YangInstanceIdentifier path,
146             final EffectiveModelContext schemaContext, final ContainerNode data) {
147         final String datastoreName = extractStringLeaf(data, DATASTORE_NODEID);
148         final LogicalDatastoreType datastoreType = datastoreName != null ? LogicalDatastoreType.valueOf(datastoreName)
149             : LogicalDatastoreType.CONFIGURATION;
150
151         final String scopeName = extractStringLeaf(data, SCOPE_NODEID);
152         // FIXME: this is not really used
153         final Scope scope = scopeName != null ? Scope.ofName(scopeName) : Scope.BASE;
154
155         return RestconfStreamsConstants.DATA_SUBSCRIPTION
156             + "/" + ListenersBroker.createStreamNameFromUri(IdentifierCodec.serialize(path, schemaContext)
157                 + "/" + RestconfStreamsConstants.DATASTORE_PARAM_NAME + "=" + datastoreType
158                 + "/" + RestconfStreamsConstants.SCOPE_PARAM_NAME + "=" + scope);
159     }
160
161     /**
162      * Prepare {@link YangInstanceIdentifier} of stream source.
163      *
164      * @param data          Container with stream settings (RPC create-stream).
165      * @param qualifiedName QName of the input RPC context (used only in debugging).
166      * @return Parsed {@link YangInstanceIdentifier} of data element from which the data-change-event notifications
167      *     are going to be generated.
168      */
169     private static YangInstanceIdentifier preparePath(final ContainerNode data, final QName qualifiedName) {
170         final Object pathValue = data.findChildByArg(new NodeIdentifier(QName.create(qualifiedName, "path")))
171             .map(DataContainerChild::body)
172             .orElse(null);
173         if (!(pathValue instanceof YangInstanceIdentifier)) {
174             LOG.debug("Instance identifier {} was not normalized correctly", qualifiedName);
175             throw new RestconfDocumentedException(
176                     "Instance identifier was not normalized correctly",
177                     ErrorType.APPLICATION,
178                     ErrorTag.OPERATION_FAILED);
179         }
180         return (YangInstanceIdentifier) pathValue;
181     }
182
183     private static @Nullable String extractStringLeaf(final ContainerNode data, final NodeIdentifier childName) {
184         final DataContainerChild augNode = data.childByArg(SAL_REMOTE_AUG_IDENTIFIER);
185         if (augNode instanceof AugmentationNode) {
186             final DataContainerChild enumNode = ((AugmentationNode) augNode).childByArg(childName);
187             if (enumNode instanceof LeafNode) {
188                 final Object value = enumNode.body();
189                 if (value instanceof String) {
190                     return (String) value;
191                 }
192             }
193         }
194         return null;
195     }
196
197     /**
198      * Create YANG notification stream using notification definition in YANG schema.
199      *
200      * @param notificationDefinition YANG notification definition.
201      * @param refSchemaCtx           Reference to {@link EffectiveModelContext}
202      * @param outputType             Output type (XML or JSON).
203      * @return {@link NotificationListenerAdapter}
204      */
205     static NotificationListenerAdapter createYangNotifiStream(final NotificationDefinition notificationDefinition,
206             final EffectiveModelContext refSchemaCtx, final NotificationOutputType outputType) {
207         final String streamName = parseNotificationStreamName(requireNonNull(notificationDefinition),
208                 requireNonNull(refSchemaCtx), requireNonNull(outputType.getName()));
209         final Optional<NotificationListenerAdapter> listenerForStreamName = ListenersBroker.getInstance()
210                 .getNotificationListenerFor(streamName);
211         return listenerForStreamName.orElseGet(() -> ListenersBroker.getInstance().registerNotificationListener(
212                 Absolute.of(notificationDefinition.getQName()), streamName, outputType));
213     }
214
215     private static String parseNotificationStreamName(final NotificationDefinition notificationDefinition,
216             final EffectiveModelContext refSchemaCtx, final String outputType) {
217         final QName notificationDefinitionQName = notificationDefinition.getQName();
218         final Module module = refSchemaCtx.findModule(
219                 notificationDefinitionQName.getModule().getNamespace(),
220                 notificationDefinitionQName.getModule().getRevision()).orElse(null);
221         requireNonNull(module, String.format("Module for namespace %s does not exist.",
222                 notificationDefinitionQName.getModule().getNamespace()));
223
224         final StringBuilder streamNameBuilder = new StringBuilder();
225         streamNameBuilder.append(RestconfStreamsConstants.NOTIFICATION_STREAM)
226                 .append('/')
227                 .append(module.getName())
228                 .append(':')
229                 .append(notificationDefinitionQName.getLocalName());
230         if (outputType.equals(NotificationOutputType.JSON.getName())) {
231             streamNameBuilder.append('/').append(NotificationOutputType.JSON.getName());
232         }
233         return streamNameBuilder.toString();
234     }
235 }