Remove explicit default super-constructor calls
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / netconf / sal / streams / listeners / ListenerAdapter.java
1 /*
2  * Copyright (c) 2014, 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.netconf.sal.streams.listeners;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import java.io.IOException;
13 import java.util.Collection;
14 import java.util.Map;
15 import javax.annotation.Nonnull;
16 import javax.xml.stream.XMLStreamException;
17 import javax.xml.transform.dom.DOMResult;
18 import org.json.XML;
19 import org.opendaylight.controller.md.sal.dom.api.ClusteredDOMDataTreeChangeListener;
20 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
21 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
27 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
32 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
33 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
34 import org.opendaylight.yangtools.yang.model.api.Module;
35 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
36 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39 import org.w3c.dom.Document;
40 import org.w3c.dom.Element;
41 import org.w3c.dom.Node;
42
43 /**
44  * {@link ListenerAdapter} is responsible to track events, which occurred by
45  * changing data in data source.
46  */
47 public class ListenerAdapter extends AbstractCommonSubscriber implements ClusteredDOMDataTreeChangeListener {
48
49     private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapter.class);
50
51     private final YangInstanceIdentifier path;
52     private final String streamName;
53     private final NotificationOutputType outputType;
54
55     private Collection<DataTreeCandidate> dataTreeCandidates;
56
57     /**
58      * Creates new {@link ListenerAdapter} listener specified by path and stream
59      * name and register for subscribing.
60      *
61      * @param path
62      *            Path to data in data store.
63      * @param streamName
64      *            The name of the stream.
65      * @param outputType
66      *            Type of output on notification (JSON, XML)
67      */
68     ListenerAdapter(final YangInstanceIdentifier path, final String streamName,
69             final NotificationOutputType outputType) {
70         register(this);
71         this.outputType = Preconditions.checkNotNull(outputType);
72         this.path = Preconditions.checkNotNull(path);
73         Preconditions.checkArgument((streamName != null) && !streamName.isEmpty());
74         this.streamName = streamName;
75     }
76
77     @Override
78     public void onDataTreeChanged(@Nonnull Collection<DataTreeCandidate> dataTreeCandidates) {
79         this.dataTreeCandidates = dataTreeCandidates;
80         final String xml = prepareXml();
81         if (checkQueryParams(xml, this)) {
82             prepareAndPostData(xml);
83         }
84     }
85
86     /**
87      * Gets the name of the stream.
88      *
89      * @return The name of the stream.
90      */
91     @Override
92     public String getStreamName() {
93         return this.streamName;
94     }
95
96     @Override
97     public String getOutputType() {
98         return this.outputType.getName();
99     }
100
101     /**
102      * Get path pointed to data in data store.
103      *
104      * @return Path pointed to data in data store.
105      */
106     public YangInstanceIdentifier getPath() {
107         return this.path;
108     }
109
110     /**
111      * Prepare data of notification and data to client.
112      *
113      * @param xml   data
114      */
115     private void prepareAndPostData(final String xml) {
116         final Event event = new Event(EventType.NOTIFY);
117         if (this.outputType.equals(NotificationOutputType.JSON)) {
118             event.setData(XML.toJSONObject(xml).toString());
119         } else {
120             event.setData(xml);
121         }
122         post(event);
123     }
124
125     /**
126      * Tracks events of data change by customer.
127      */
128
129     /**
130      * Prepare data in printable form and transform it to String.
131      *
132      * @return Data in printable form.
133      */
134     private String prepareXml() {
135         final SchemaContext schemaContext = ControllerContext.getInstance().getGlobalSchema();
136         final DataSchemaContextTree dataContextTree = DataSchemaContextTree.from(schemaContext);
137         final Document doc = createDocument();
138         final Element notificationElement = basePartDoc(doc);
139
140         final Element dataChangedNotificationEventElement = doc.createElementNS(
141                 "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "data-changed-notification");
142
143         addValuesToDataChangedNotificationEventElement(doc, dataChangedNotificationEventElement,
144                                                             this.dataTreeCandidates, schemaContext, dataContextTree);
145         notificationElement.appendChild(dataChangedNotificationEventElement);
146         return transformDoc(doc);
147     }
148
149     /**
150      * Adds values to data changed notification event element.
151      *
152      * @param doc
153      *            {@link Document}
154      * @param dataChangedNotificationEventElement
155      *            {@link Element}
156      * @param dataTreeCandidates
157      *            {@link DataTreeCandidate}
158      */
159     private void addValuesToDataChangedNotificationEventElement(final Document doc,
160             final Element dataChangedNotificationEventElement,
161             final Collection<DataTreeCandidate> dataTreeCandidates,
162             final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
163
164         for (DataTreeCandidate dataTreeCandidate : dataTreeCandidates) {
165             DataTreeCandidateNode candidateNode = dataTreeCandidate.getRootNode();
166             if (candidateNode == null) {
167                 continue;
168             }
169             YangInstanceIdentifier yiid = dataTreeCandidate.getRootPath();
170             addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, candidateNode,
171                     yiid.getParent(), schemaContext, dataSchemaContextTree);
172         }
173     }
174
175     private void addNodeToDataChangeNotificationEventElement(final Document doc,
176                              final Element dataChangedNotificationEventElement, DataTreeCandidateNode candidateNode,
177                              YangInstanceIdentifier parentYiid, SchemaContext schemaContext,
178                              DataSchemaContextTree dataSchemaContextTree) {
179
180         Optional<NormalizedNode<?,?>> optionalNormalizedNode = Optional.absent();
181         switch (candidateNode.getModificationType()) {
182             case APPEARED:
183             case SUBTREE_MODIFIED:
184             case WRITE:
185                 optionalNormalizedNode = candidateNode.getDataAfter();
186                 break;
187             case DELETE:
188             case DISAPPEARED:
189                 optionalNormalizedNode = candidateNode.getDataBefore();
190                 break;
191             case UNMODIFIED:
192             default:
193                 break;
194         }
195
196         if (!optionalNormalizedNode.isPresent()) {
197             LOG.error("No node present in notification for {}", candidateNode);
198             return;
199         }
200
201         NormalizedNode<?,?> normalizedNode = optionalNormalizedNode.get();
202         YangInstanceIdentifier yiid = YangInstanceIdentifier.builder(parentYiid)
203                                                             .append(normalizedNode.getIdentifier()).build();
204
205         boolean isNodeMixin = ControllerContext.getInstance().isNodeMixin(yiid);
206         boolean isSkippedNonLeaf = getLeafNodesOnly() && !(normalizedNode instanceof LeafNode);
207         if (!isNodeMixin && !isSkippedNonLeaf) {
208             Node node = null;
209             switch (candidateNode.getModificationType()) {
210                 case APPEARED:
211                 case SUBTREE_MODIFIED:
212                 case WRITE:
213                     Operation op = candidateNode.getDataBefore().isPresent() ? Operation.UPDATED : Operation.CREATED;
214                     node = createCreatedChangedDataChangeEventElement(doc, yiid, normalizedNode, op,
215                             schemaContext, dataSchemaContextTree);
216                     break;
217                 case DELETE:
218                 case DISAPPEARED:
219                     node = createDataChangeEventElement(doc, yiid, Operation.DELETED);
220                     break;
221                 case UNMODIFIED:
222                 default:
223                     break;
224             }
225             if (node != null) {
226                 dataChangedNotificationEventElement.appendChild(node);
227             }
228         }
229
230         for (DataTreeCandidateNode childNode : candidateNode.getChildNodes()) {
231             addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, childNode,
232                                                                         yiid, schemaContext, dataSchemaContextTree);
233         }
234     }
235
236     /**
237      * Creates changed event element from data.
238      *
239      * @param doc
240      *            {@link Document}
241      * @param path
242      *            Path to data in data store.
243      * @param operation
244      *            {@link Operation}
245      * @return {@link Node} node represented by changed event element.
246      */
247     private Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier path,
248             final Operation operation) {
249         final Element dataChangeEventElement = doc.createElement("data-change-event");
250         final Element pathElement = doc.createElement("path");
251         addPathAsValueToElement(path, pathElement);
252         dataChangeEventElement.appendChild(pathElement);
253
254         final Element operationElement = doc.createElement("operation");
255         operationElement.setTextContent(operation.value);
256         dataChangeEventElement.appendChild(operationElement);
257
258         return dataChangeEventElement;
259     }
260
261     private Node createCreatedChangedDataChangeEventElement(final Document doc,
262             YangInstanceIdentifier path, NormalizedNode normalized, final Operation operation,
263             final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
264         final Element dataChangeEventElement = doc.createElement("data-change-event");
265         final Element pathElement = doc.createElement("path");
266         addPathAsValueToElement(path, pathElement);
267         dataChangeEventElement.appendChild(pathElement);
268
269         final Element operationElement = doc.createElement("operation");
270         operationElement.setTextContent(operation.value);
271         dataChangeEventElement.appendChild(operationElement);
272
273         try {
274             SchemaPath nodePath;
275             if ((normalized instanceof MapEntryNode) || (normalized instanceof UnkeyedListEntryNode)) {
276                 nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath();
277             } else {
278                 nodePath = dataSchemaContextTree.getChild(path).getDataSchemaNode().getPath().getParent();
279             }
280             final DOMResult domResult = writeNormalizedNode(normalized, schemaContext, nodePath);
281             final Node result = doc.importNode(domResult.getNode().getFirstChild(), true);
282             final Element dataElement = doc.createElement("data");
283             dataElement.appendChild(result);
284             dataChangeEventElement.appendChild(dataElement);
285         } catch (final IOException e) {
286             LOG.error("Error in writer ", e);
287         } catch (final XMLStreamException e) {
288             LOG.error("Error processing stream", e);
289         }
290
291         return dataChangeEventElement;
292     }
293
294     /**
295      * Adds path as value to element.
296      *
297      * @param path
298      *            Path to data in data store.
299      * @param element
300      *            {@link Element}
301      */
302     @SuppressWarnings("rawtypes")
303     private void addPathAsValueToElement(final YangInstanceIdentifier path, final Element element) {
304         final YangInstanceIdentifier normalizedPath = ControllerContext.getInstance().toXpathRepresentation(path);
305         final StringBuilder textContent = new StringBuilder();
306
307         for (final PathArgument pathArgument : normalizedPath.getPathArguments()) {
308             if (pathArgument instanceof YangInstanceIdentifier.AugmentationIdentifier) {
309                 continue;
310             }
311             textContent.append("/");
312             writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType());
313             if (pathArgument instanceof NodeIdentifierWithPredicates) {
314                 final Map<QName, Object> predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues();
315                 for (final QName keyValue : predicates.keySet()) {
316                     final String predicateValue = String.valueOf(predicates.get(keyValue));
317                     textContent.append("[");
318                     writeIdentifierWithNamespacePrefix(element, textContent, keyValue);
319                     textContent.append("='");
320                     textContent.append(predicateValue);
321                     textContent.append("'");
322                     textContent.append("]");
323                 }
324             } else if (pathArgument instanceof NodeWithValue) {
325                 textContent.append("[.='");
326                 textContent.append(((NodeWithValue) pathArgument).getValue());
327                 textContent.append("'");
328                 textContent.append("]");
329             }
330         }
331         element.setTextContent(textContent.toString());
332     }
333
334     /**
335      * Writes identifier that consists of prefix and QName.
336      *
337      * @param element
338      *            {@link Element}
339      * @param textContent
340      *            StringBuilder
341      * @param qualifiedName
342      *            QName
343      */
344     private static void writeIdentifierWithNamespacePrefix(final Element element, final StringBuilder textContent,
345             final QName qualifiedName) {
346         final Module module = ControllerContext.getInstance().getGlobalSchema()
347                 .findModuleByNamespaceAndRevision(qualifiedName.getNamespace(), qualifiedName.getRevision());
348
349         textContent.append(module.getName());
350         textContent.append(":");
351         textContent.append(qualifiedName.getLocalName());
352     }
353
354     /**
355      * Consists of three types {@link Operation#CREATED},
356      * {@link Operation#UPDATED} and {@link Operation#DELETED}.
357      */
358     private enum Operation {
359         CREATED("created"), UPDATED("updated"), DELETED("deleted");
360
361         private final String value;
362
363         Operation(final String value) {
364             this.value = value;
365         }
366     }
367 }