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