Extend Websocket streams for data-less notifications
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / streams / listeners / ListenerAdapter.java
index 12a6ae4904dd63b6a2c66b4e1e0c86c12833b313..d9d6eb3533182fa62a08b294d0d44c3dc75d859b 100644 (file)
@@ -7,12 +7,14 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
 import java.io.IOException;
 import java.time.Instant;
 import java.util.Collection;
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 import javax.xml.stream.XMLStreamException;
@@ -48,6 +50,9 @@ import org.w3c.dom.Node;
 public class ListenerAdapter extends AbstractCommonSubscriber implements ClusteredDOMDataTreeChangeListener {
 
     private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapter.class);
+    private static final String DATA_CHANGE_EVENT = "data-change-event";
+    private static final String PATH = "path";
+    private static final String OPERATION = "operation";
 
     private final YangInstanceIdentifier path;
     private final String streamName;
@@ -62,13 +67,12 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
      */
     ListenerAdapter(final YangInstanceIdentifier path, final String streamName,
             final NotificationOutputType outputType) {
-        register(this);
         setLocalNameOfPath(path.getLastPathArgument().getNodeType().getLocalName());
 
-        this.outputType = Preconditions.checkNotNull(outputType);
-        this.path = Preconditions.checkNotNull(path);
-        Preconditions.checkArgument(streamName != null && !streamName.isEmpty());
-        this.streamName = streamName;
+        this.outputType = requireNonNull(outputType);
+        this.path = requireNonNull(path);
+        this.streamName = requireNonNull(streamName);
+        checkArgument(!streamName.isEmpty());
     }
 
     @Override
@@ -114,13 +118,11 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
      * @param xml XML-formatted data.
      */
     private void prepareAndPostData(final String xml) {
-        final Event event = new Event(EventType.NOTIFY);
         if (this.outputType.equals(NotificationOutputType.JSON)) {
-            event.setData(XML.toJSONObject(xml).toString());
+            post(XML.toJSONObject(xml).toString());
         } else {
-            event.setData(xml);
+            post(xml);
         }
-        post(event);
     }
 
     /**
@@ -147,7 +149,6 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
     /**
      * Adds values to data changed notification event element.
      */
-    @SuppressWarnings("checkstyle:hiddenField")
     private void addValuesToDataChangedNotificationEventElement(final Document doc,
             final Element dataChangedNotificationEventElement, final Collection<DataTreeCandidate> dataTreeCandidates,
             final SchemaContext schemaContext, final DataSchemaContextTree dataSchemaContextTree) {
@@ -158,8 +159,14 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
                 continue;
             }
             YangInstanceIdentifier yiid = dataTreeCandidate.getRootPath();
-            addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, candidateNode,
-                    yiid.getParent(), schemaContext, dataSchemaContextTree);
+            boolean isSkipNotificationData = this.isSkipNotificationData();
+            if (isSkipNotificationData) {
+                createCreatedChangedDataChangeEventElementWithoutData(doc, dataChangedNotificationEventElement,
+                        dataTreeCandidate.getRootNode(), schemaContext);
+            } else {
+                addNodeToDataChangeNotificationEventElement(doc, dataChangedNotificationEventElement, candidateNode,
+                        yiid.getParent(), schemaContext, dataSchemaContextTree);
+            }
         }
     }
 
@@ -194,7 +201,7 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
                 .append(normalizedNode.getIdentifier()).build();
 
         final Optional<DataSchemaContextNode<?>> childrenSchemaNode = dataSchemaContextTree.findChild(yiid);
-        Preconditions.checkState(childrenSchemaNode.isPresent());
+        checkState(childrenSchemaNode.isPresent());
         boolean isNodeMixin = childrenSchemaNode.get().isMixin();
         boolean isSkippedNonLeaf = getLeafNodesOnly() && !(normalizedNode instanceof LeafNode);
         if (!isNodeMixin && !isSkippedNonLeaf) {
@@ -209,7 +216,7 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
                     break;
                 case DELETE:
                 case DISAPPEARED:
-                    node = createDataChangeEventElement(doc, yiid, schemaContext);
+                    node = createDataChangeEventElement(doc, yiid, schemaContext, Operation.DELETED);
                     break;
                 case UNMODIFIED:
                 default:
@@ -231,27 +238,60 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
      *
      * @param doc           {@link Document}
      * @param schemaContext Schema context.
+     * @param operation  Operation value
      * @return {@link Node} represented by changed event element.
      */
-    private Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier eventPath,
-            final SchemaContext schemaContext) {
-        final Element dataChangeEventElement = doc.createElement("data-change-event");
-        final Element pathElement = doc.createElement("path");
+    private static Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier eventPath,
+            final SchemaContext schemaContext, Operation operation) {
+        final Element dataChangeEventElement = doc.createElement(DATA_CHANGE_EVENT);
+        final Element pathElement = doc.createElement(PATH);
         addPathAsValueToElement(eventPath, pathElement, schemaContext);
         dataChangeEventElement.appendChild(pathElement);
 
-        final Element operationElement = doc.createElement("operation");
-        operationElement.setTextContent(Operation.DELETED.value);
+        final Element operationElement = doc.createElement(OPERATION);
+        operationElement.setTextContent(operation.value);
         dataChangeEventElement.appendChild(operationElement);
 
         return dataChangeEventElement;
     }
 
+    /**
+     * Creates data change notification element without data element.
+     *
+     * @param doc
+     *       {@link Document}
+     * @param dataChangedNotificationEventElement
+     *       {@link Element}
+     * @param candidateNode
+     *       {@link DataTreeCandidateNode}
+     */
+    private void createCreatedChangedDataChangeEventElementWithoutData(final Document doc,
+            final Element dataChangedNotificationEventElement, final DataTreeCandidateNode candidateNode,
+            final SchemaContext schemaContext) {
+        final Operation operation;
+        switch (candidateNode.getModificationType()) {
+            case APPEARED:
+            case SUBTREE_MODIFIED:
+            case WRITE:
+                operation = candidateNode.getDataBefore().isPresent() ? Operation.UPDATED : Operation.CREATED;
+                break;
+            case DELETE:
+            case DISAPPEARED:
+                operation = Operation.DELETED;
+                break;
+            case UNMODIFIED:
+            default:
+                return;
+        }
+        Node dataChangeEventElement = createDataChangeEventElement(doc, getPath(), schemaContext, operation);
+        dataChangedNotificationEventElement.appendChild(dataChangeEventElement);
+    }
+
     private Node createCreatedChangedDataChangeEventElement(final Document doc, final YangInstanceIdentifier eventPath,
             final NormalizedNode<?, ?> normalized, final Operation operation, final SchemaContext schemaContext,
             final DataSchemaContextTree dataSchemaContextTree) {
-        final Element dataChangeEventElement = doc.createElement("data-change-event");
-        final Element pathElement = doc.createElement("path");
+        final Element dataChangeEventElement = doc.createElement(DATA_CHANGE_EVENT);
+        final Element pathElement = doc.createElement(PATH);
         addPathAsValueToElement(eventPath, pathElement, schemaContext);
         dataChangeEventElement.appendChild(pathElement);
 
@@ -262,7 +302,7 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
         try {
             SchemaPath nodePath;
             final Optional<DataSchemaContextNode<?>> childrenSchemaNode = dataSchemaContextTree.findChild(eventPath);
-            Preconditions.checkState(childrenSchemaNode.isPresent());
+            checkState(childrenSchemaNode.isPresent());
             if (normalized instanceof MapEntryNode || normalized instanceof UnkeyedListEntryNode) {
                 nodePath = childrenSchemaNode.get().getDataSchemaNode().getPath();
             } else {
@@ -289,8 +329,7 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
      * @param element       {@link Element}
      * @param schemaContext Schema context.
      */
-    @SuppressWarnings("rawtypes")
-    private void addPathAsValueToElement(final YangInstanceIdentifier eventPath, final Element element,
+    private static void addPathAsValueToElement(final YangInstanceIdentifier eventPath, final Element element,
             final SchemaContext schemaContext) {
         final StringBuilder textContent = new StringBuilder();
 
@@ -301,22 +340,15 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
             textContent.append("/");
             writeIdentifierWithNamespacePrefix(textContent, pathArgument.getNodeType(), schemaContext);
             if (pathArgument instanceof NodeIdentifierWithPredicates) {
-                final Map<QName, Object> predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues();
-                for (final Entry<QName, Object> entry : predicates.entrySet()) {
+                for (final Entry<QName, Object> entry : ((NodeIdentifierWithPredicates) pathArgument).entrySet()) {
                     final QName keyValue = entry.getKey();
                     final String predicateValue = String.valueOf(entry.getValue());
                     textContent.append("[");
                     writeIdentifierWithNamespacePrefix(textContent, keyValue, schemaContext);
-                    textContent.append("='");
-                    textContent.append(predicateValue);
-                    textContent.append("'");
-                    textContent.append("]");
+                    textContent.append("='").append(predicateValue).append("']");
                 }
             } else if (pathArgument instanceof NodeWithValue) {
-                textContent.append("[.='");
-                textContent.append(((NodeWithValue) pathArgument).getValue());
-                textContent.append("'");
-                textContent.append("]");
+                textContent.append("[.='").append(((NodeWithValue<?>) pathArgument).getValue()).append("']");
             }
         }
         element.setTextContent(textContent.toString());
@@ -362,9 +394,9 @@ public class ListenerAdapter extends AbstractCommonSubscriber implements Cluster
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
-                .add("path", path)
+                .add(PATH, path)
                 .add("stream-name", streamName)
                 .add("output-type", outputType)
                 .toString();
     }
-}
\ No newline at end of file
+}