Bug 6995 - Change event notification subscription usability PART1 31/47731/9
authorJakub Toth <jatoth@cisco.com>
Fri, 28 Oct 2016 10:55:00 +0000 (12:55 +0200)
committerJakub Toth <jatoth@cisco.com>
Mon, 7 Nov 2016 20:23:23 +0000 (20:23 +0000)
  * added leaf of notification-output-type to grouping for use
    both augmentations of notifications:
      * data-change notifications
      * yang notifications
  * implemented support of output type for data-change
    notifications (in draft02 and in latest draft)
  * fixed tests to support output of notification

Change-Id: I0b8b9803d1bd2daa7f319e9332ecaf1e96da16ab
Signed-off-by: Jakub Toth <jatoth@cisco.com>
12 files changed:
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/CreateStreamUtil.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfStreamsConstants.java
restconf/sal-rest-connector/src/main/yang/sal-remote-augment.yang
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URIParametersParsing.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/rest/services/impl/RestconfStreamsServiceTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfStreamsSubscriptionServiceImplTest.java
restconf/sal-rest-connector/src/test/resources/datastore-and-scope-specification/sal-remote-augment.yang

index df56bc4c0e70a7ada81e6ab0d38169a6d60eed57..49e79651207627133c282073b0cc7bf4f4447ee2 100644 (file)
@@ -9,7 +9,6 @@
 package org.opendaylight.netconf.sal.rest.impl;
 
 import com.google.common.base.Preconditions;
-import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContext;
@@ -31,97 +30,100 @@ public class RestconfCompositeWrapper implements RestconfService, SchemaRetrieva
 
     @Override
     public Object getRoot() {
-        return restconf.getRoot();
+        return this.restconf.getRoot();
     }
 
     @Override
     public NormalizedNodeContext getModules(final UriInfo uriInfo) {
-        return restconf.getModules(uriInfo);
+        return this.restconf.getModules(uriInfo);
     }
 
     @Override
     public NormalizedNodeContext getModules(final String identifier, final UriInfo uriInfo) {
-        return restconf.getModules(identifier, uriInfo);
+        return this.restconf.getModules(identifier, uriInfo);
     }
 
     @Override
     public NormalizedNodeContext getModule(final String identifier, final UriInfo uriInfo) {
-        return restconf.getModule(identifier, uriInfo);
+        return this.restconf.getModule(identifier, uriInfo);
     }
 
     @Override
     public NormalizedNodeContext getOperations(final UriInfo uriInfo) {
-        return restconf.getOperations(uriInfo);
+        return this.restconf.getOperations(uriInfo);
     }
 
     @Override
     public NormalizedNodeContext getOperations(final String identifier, final UriInfo uriInfo) {
-        return restconf.getOperations(identifier, uriInfo);
+        return this.restconf.getOperations(identifier, uriInfo);
     }
 
     @Override
-    public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
-        return restconf.invokeRpc(identifier, payload, uriInfo);
+    public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+            final UriInfo uriInfo) {
+        return this.restconf.invokeRpc(identifier, payload, uriInfo);
     }
 
     @Override
     @Deprecated
     public NormalizedNodeContext invokeRpc(final String identifier, final String noPayload, final UriInfo uriInfo) {
-        return restconf.invokeRpc(identifier, noPayload, uriInfo);
+        return this.restconf.invokeRpc(identifier, noPayload, uriInfo);
     }
 
     @Override
     public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) {
-        return restconf.readConfigurationData(identifier, uriInfo);
+        return this.restconf.readConfigurationData(identifier, uriInfo);
     }
 
     @Override
     public NormalizedNodeContext readOperationalData(final String identifier, final UriInfo uriInfo) {
-        return restconf.readOperationalData(identifier, uriInfo);
+        return this.restconf.readOperationalData(identifier, uriInfo);
     }
 
     @Override
     public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload) {
-        return restconf.updateConfigurationData(identifier, payload);
+        return this.restconf.updateConfigurationData(identifier, payload);
     }
 
     @Override
-    public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
-        return restconf.createConfigurationData(identifier, payload, uriInfo);
+    public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
+            final UriInfo uriInfo) {
+        return this.restconf.createConfigurationData(identifier, payload, uriInfo);
     }
 
     @Override
     public Response createConfigurationData(final NormalizedNodeContext payload, final UriInfo uriInfo) {
-        return restconf.createConfigurationData(payload, uriInfo);
+        return this.restconf.createConfigurationData(payload, uriInfo);
     }
 
     @Override
     public Response deleteConfigurationData(final String identifier) {
-        return restconf.deleteConfigurationData(identifier);
+        return this.restconf.deleteConfigurationData(identifier);
     }
 
     @Override
     public Response subscribeToStream(final String identifier, final UriInfo uriInfo) {
-        return restconf.subscribeToStream(identifier, uriInfo);
+        return this.restconf.subscribeToStream(identifier, uriInfo);
     }
 
     @Override
     public NormalizedNodeContext getAvailableStreams(final UriInfo uriInfo) {
-        return restconf.getAvailableStreams(uriInfo);
+        return this.restconf.getAvailableStreams(uriInfo);
     }
 
     @Override
-    public PATCHStatusContext patchConfigurationData(final String identifier, PATCHContext payload, UriInfo uriInfo) {
-        return restconf.patchConfigurationData(identifier, payload, uriInfo);
+    public PATCHStatusContext patchConfigurationData(final String identifier, final PATCHContext payload,
+            final UriInfo uriInfo) {
+        return this.restconf.patchConfigurationData(identifier, payload, uriInfo);
     }
 
     @Override
     public PATCHStatusContext patchConfigurationData(final PATCHContext context, final UriInfo uriInfo) {
-        return restconf.patchConfigurationData(context, uriInfo);
+        return this.restconf.patchConfigurationData(context, uriInfo);
     }
 
     @Override
     public SchemaExportContext getSchema(final String mountId) {
-        return schema.getSchema(mountId);
+        return this.schema.getSchema(mountId);
     }
 }
index 669028f38e7142dc668f2516015dc895df0f6a93..25d081d73c2e145609ed49c3462a6a0f5d1e302c 100644 (file)
@@ -62,6 +62,7 @@ import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
 import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
 import org.opendaylight.netconf.sal.streams.websockets.WebSocketServer;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -131,6 +132,8 @@ public class RestconfImpl implements RestconfService {
 
     private static final String SCOPE_PARAM_NAME = "scope";
 
+    private static final String OUTPUT_TYPE_PARAM_NAME = "notification-output-type";
+
     private static final String NETCONF_BASE = "urn:ietf:params:xml:ns:netconf:base:1.0";
 
     private static final String NETCONF_BASE_PAYLOAD_NAME = "data";
@@ -150,11 +153,14 @@ public class RestconfImpl implements RestconfService {
     static {
         try {
             final Date eventSubscriptionAugRevision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-07-08");
-            NETCONF_BASE_QNAME = QName.create(QNameModule.create(new URI(NETCONF_BASE), null), NETCONF_BASE_PAYLOAD_NAME );
+            NETCONF_BASE_QNAME = QName.create(QNameModule.create(new URI(NETCONF_BASE), null),
+                    NETCONF_BASE_PAYLOAD_NAME);
             SAL_REMOTE_AUGMENT = QNameModule.create(NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT,
                     eventSubscriptionAugRevision);
-            SAL_REMOTE_AUG_IDENTIFIER = new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(QName.create(SAL_REMOTE_AUGMENT, "scope"),
-                    QName.create(SAL_REMOTE_AUGMENT, "datastore")));
+            SAL_REMOTE_AUG_IDENTIFIER = new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(
+                    QName.create(SAL_REMOTE_AUGMENT, "scope"),
+                    QName.create(SAL_REMOTE_AUGMENT, "datastore"),
+                    QName.create(SAL_REMOTE_AUGMENT, "notification-output-type")));
         } catch (final ParseException e) {
             final String errMsg = "It wasn't possible to convert revision date of sal-remote-augment to date";
             LOG.debug(errMsg);
@@ -214,7 +220,8 @@ public class RestconfImpl implements RestconfService {
             throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
         }
 
-        final InstanceIdentifierContext<?> mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier);
+        final InstanceIdentifierContext<?> mountPointIdentifier = this.controllerContext
+                .toMountPointIdentifier(identifier);
         final DOMMountPoint mountPoint = mountPointIdentifier.getMountPoint();
         final Set<Module> modules = this.controllerContext.getAllModules(mountPoint);
         final MapNode mountPointModulesMap = makeModuleMapNode(modules);
@@ -241,7 +248,8 @@ public class RestconfImpl implements RestconfService {
         DOMMountPoint mountPoint = null;
         final SchemaContext schemaContext;
         if (identifier.contains(ControllerContext.MOUNT)) {
-            final InstanceIdentifierContext<?> mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier);
+            final InstanceIdentifierContext<?> mountPointIdentifier = this.controllerContext
+                    .toMountPointIdentifier(identifier);
             mountPoint = mountPointIdentifier.getMountPoint();
             module = this.controllerContext.findModuleByNameAndRevision(mountPoint, moduleNameAndRevision);
             schemaContext = mountPoint.getSchemaContext();
@@ -274,8 +282,8 @@ public class RestconfImpl implements RestconfService {
         final SchemaContext schemaContext = this.controllerContext.getGlobalSchema();
         final Set<String> availableStreams = Notificator.getStreamNames();
         final Module restconfModule = getRestconfModule();
-        final DataSchemaNode streamSchemaNode = this.controllerContext.getRestconfModuleRestConfSchemaNode(restconfModule,
-                Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
+        final DataSchemaNode streamSchemaNode = this.controllerContext
+                .getRestconfModuleRestConfSchemaNode(restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE);
         Preconditions.checkState(streamSchemaNode instanceof ListSchemaNode);
 
         final CollectionNodeBuilder<MapEntryNode, MapNode> listStreamsBuilder = Builders
@@ -309,14 +317,17 @@ public class RestconfImpl implements RestconfService {
         Set<Module> modules = null;
         DOMMountPoint mountPoint = null;
         if (identifier.contains(ControllerContext.MOUNT)) {
-            final InstanceIdentifierContext<?> mountPointIdentifier = this.controllerContext.toMountPointIdentifier(identifier);
+            final InstanceIdentifierContext<?> mountPointIdentifier = this.controllerContext
+                    .toMountPointIdentifier(identifier);
             mountPoint = mountPointIdentifier.getMountPoint();
             modules = this.controllerContext.getAllModules(mountPoint);
 
         } else {
-            final String errMsg = "URI has bad format. If operations behind mount point should be showed, URI has to end with ";
+            final String errMsg = "URI has bad format. If operations behind mount point should be showed, URI has to "
+                    + "end with ";
             LOG.debug(errMsg + ControllerContext.MOUNT + " for " + identifier);
-            throw new RestconfDocumentedException(errMsg + ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+            throw new RestconfDocumentedException(errMsg + ControllerContext.MOUNT, ErrorType.PROTOCOL,
+                    ErrorTag.INVALID_VALUE);
         }
 
         return operationsFromModulesToNormalizedContext(modules, mountPoint);
@@ -361,8 +372,8 @@ public class RestconfImpl implements RestconfService {
         final Set<Module> fakeModules = new HashSet<>();
         fakeModules.add(fakeModule);
         final SchemaContext fakeSchemaCtx = EffectiveSchemaContext.resolveSchemaContext(fakeModules);
-        final InstanceIdentifierContext<ContainerSchemaNode> instanceIdentifierContext = new InstanceIdentifierContext<>(
-                null, fakeContSchNode, mountPoint, fakeSchemaCtx);
+        final InstanceIdentifierContext<ContainerSchemaNode> instanceIdentifierContext =
+                new InstanceIdentifierContext<>(null, fakeContSchNode, mountPoint, fakeSchemaCtx);
         return new NormalizedNodeContext(instanceIdentifierContext, containerBuilder.build());
     }
 
@@ -414,7 +425,8 @@ public class RestconfImpl implements RestconfService {
     }
 
     @Override
-    public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
+    public NormalizedNodeContext invokeRpc(final String identifier, final NormalizedNodeContext payload,
+            final UriInfo uriInfo) {
         final SchemaPath type = payload.getInstanceIdentifierContext().getSchemaNode().getPath();
         final URI namespace = payload.getInstanceIdentifierContext().getSchemaNode().getQName().getNamespace();
         final CheckedFuture<DOMRpcResult, DOMRpcException> response;
@@ -485,11 +497,14 @@ public class RestconfImpl implements RestconfService {
                     throw new RestconfDocumentedException(cause.getMessage(), ErrorType.PROTOCOL,
                             ErrorTag.INVALID_VALUE);
                 } else if (cause instanceof DOMRpcImplementationNotAvailableException) {
-                    throw new RestconfDocumentedException(cause.getMessage(), ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED);
+                    throw new RestconfDocumentedException(cause.getMessage(), ErrorType.APPLICATION,
+                            ErrorTag.OPERATION_NOT_SUPPORTED);
                 }
-                throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",cause);
+                throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
+                        cause);
             } else {
-                throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",e);
+                throw new RestconfDocumentedException("The operation encountered an unexpected error while executing.",
+                        e);
             }
         } catch (final CancellationException e) {
             final String errMsg = "The operation was cancelled while executing.";
@@ -508,7 +523,8 @@ public class RestconfImpl implements RestconfService {
         }
     }
 
-    private CheckedFuture<DOMRpcResult, DOMRpcException> invokeSalRemoteRpcSubscribeRPC(final NormalizedNodeContext payload) {
+    private CheckedFuture<DOMRpcResult, DOMRpcException>
+            invokeSalRemoteRpcSubscribeRPC(final NormalizedNodeContext payload) {
         final ContainerNode value = (ContainerNode) payload.getData();
         final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName();
         final Optional<DataContainerChild<? extends PathArgument, ?>> path = value.getChild(new NodeIdentifier(
@@ -523,16 +539,22 @@ public class RestconfImpl implements RestconfService {
 
         final YangInstanceIdentifier pathIdentifier = ((YangInstanceIdentifier) pathValue);
         String streamName = (String) CREATE_DATA_SUBSCR;
+        NotificationOutputType outputType = null;
         if (!pathIdentifier.isEmpty()) {
             final String fullRestconfIdentifier = DATA_SUBSCR
                     + this.controllerContext.toFullRestconfIdentifier(pathIdentifier, null);
 
-            LogicalDatastoreType datastore = parseEnumTypeParameter(value, LogicalDatastoreType.class, DATASTORE_PARAM_NAME);
+            LogicalDatastoreType datastore =
+                    parseEnumTypeParameter(value, LogicalDatastoreType.class, DATASTORE_PARAM_NAME);
             datastore = datastore == null ? DEFAULT_DATASTORE : datastore;
 
             DataChangeScope scope = parseEnumTypeParameter(value, DataChangeScope.class, SCOPE_PARAM_NAME);
             scope = scope == null ? DEFAULT_SCOPE : scope;
 
+            outputType = parseEnumTypeParameter(value, NotificationOutputType.class,
+                    OUTPUT_TYPE_PARAM_NAME);
+            outputType = outputType == null ? NotificationOutputType.XML : outputType;
+
             streamName = Notificator.createStreamNameFromUri(fullRestconfIdentifier + "/datastore=" + datastore
                     + "/scope=" + scope);
         }
@@ -546,11 +568,12 @@ public class RestconfImpl implements RestconfService {
         final QName outputQname = QName.create(rpcQName, "output");
         final QName streamNameQname = QName.create(rpcQName, "stream-name");
 
-        final ContainerNode output = ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(outputQname))
-                .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
+        final ContainerNode output =
+                ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(outputQname))
+                        .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
 
         if (!Notificator.existListenerFor(streamName)) {
-            Notificator.createListener(pathIdentifier, streamName);
+            Notificator.createListener(pathIdentifier, streamName, outputType);
         }
 
         final DOMRpcResult defaultDOMRpcResult = new DefaultDOMRpcResult(output);
@@ -828,14 +851,16 @@ public class RestconfImpl implements RestconfService {
 
             if ( ! uriKeyValue.equals(dataKeyValue)) {
                 final String errMsg = "The value '" + uriKeyValue + "' for key '" + keyDefinition.getLocalName() +
-                        "' specified in the URI doesn't match the value '" + dataKeyValue + "' specified in the message body. ";
+                        "' specified in the URI doesn't match the value '" + dataKeyValue
+                        + "' specified in the message body. ";
                 throw new RestconfDocumentedException(errMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
             }
         }
     }
 
     @Override
-    public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload, final UriInfo uriInfo) {
+    public Response createConfigurationData(final String identifier, final NormalizedNodeContext payload,
+            final UriInfo uriInfo) {
        return createConfigurationData(payload, uriInfo);
     }
 
@@ -939,7 +964,8 @@ public class RestconfImpl implements RestconfService {
         }
     }
 
-    private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint, final YangInstanceIdentifier normalizedII) {
+    private URI resolveLocation(final UriInfo uriInfo, final String uriBehindBase, final DOMMountPoint mountPoint,
+            final YangInstanceIdentifier normalizedII) {
         if(uriInfo == null) {
             // This is null if invoked internally
             return null;
@@ -1116,7 +1142,8 @@ public class RestconfImpl implements RestconfService {
     }
 
     @Override
-    public PATCHStatusContext patchConfigurationData(final String identifier, final PATCHContext context, final UriInfo uriInfo) {
+    public PATCHStatusContext patchConfigurationData(final String identifier, final PATCHContext context,
+            final UriInfo uriInfo) {
         if (context == null) {
             throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
         }
@@ -1157,7 +1184,8 @@ public class RestconfImpl implements RestconfService {
             return null;
         }
         final Optional<DataContainerChild<? extends PathArgument, ?>> enumNode =
-                ((AugmentationNode) augNode.get()).getChild(new NodeIdentifier(QName.create(SAL_REMOTE_AUGMENT, paramName)));
+                ((AugmentationNode) augNode.get())
+                        .getChild(new NodeIdentifier(QName.create(SAL_REMOTE_AUGMENT, paramName)));
         if (!enumNode.isPresent()) {
             return null;
         }
index a8651c21638a93477a4f4ad933e141306ecdaf2f..b2cdc052af0387b23ae44f57aa7cb852d41ccb62 100644 (file)
@@ -42,9 +42,12 @@ import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMResult;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
+import org.json.JSONObject;
+import org.json.XML;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -85,16 +88,22 @@ public class ListenerAdapter implements DOMDataChangeListener {
     private Set<Channel> subscribers = new ConcurrentSet<>();
     private final EventBus eventBus;
     private final EventBusChangeRecorder eventBusChangeRecorder;
+    private final NotificationOutputType outputType;
 
     /**
-     * Creates new {@link ListenerAdapter} listener specified by path and stream name.
+     * Creates new {@link ListenerAdapter} listener specified by path and stream
+     * name.
      *
      * @param path
      *            Path to data in data store.
      * @param streamName
      *            The name of the stream.
+     * @param outputType
+     *            - type of output on notification (JSON, XML)
      */
-    ListenerAdapter(final YangInstanceIdentifier path, final String streamName) {
+    ListenerAdapter(final YangInstanceIdentifier path, final String streamName,
+            final NotificationOutputType outputType) {
+        this.outputType = outputType;
         Preconditions.checkNotNull(path);
         Preconditions.checkArgument((streamName != null) && !streamName.isEmpty());
         this.path = path;
@@ -110,7 +119,12 @@ public class ListenerAdapter implements DOMDataChangeListener {
                 || !change.getRemovedPaths().isEmpty()) {
             final String xml = prepareXmlFrom(change);
             final Event event = new Event(EventType.NOTIFY);
-            event.setData(xml);
+            if (this.outputType.equals(NotificationOutputType.JSON)) {
+                final JSONObject jsonObject = XML.toJSONObject(xml);
+                event.setData(jsonObject.toString());
+            } else {
+                event.setData(xml);
+            }
             this.eventBus.post(event);
         }
     }
@@ -230,6 +244,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
         final Document doc = createDocument();
         final Element notificationElement = doc.createElementNS("urn:ietf:params:xml:ns:netconf:notification:1.0",
                 "notification");
+
         doc.appendChild(notificationElement);
 
         final Element eventTimeElement = doc.createElement("eventTime");
@@ -238,6 +253,7 @@ public class ListenerAdapter implements DOMDataChangeListener {
 
         final Element dataChangedNotificationEventElement = doc.createElementNS(
                 "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "data-changed-notification");
+
         addValuesToDataChangedNotificationEventElement(doc, dataChangedNotificationEventElement, change,
                 schemaContext, dataContextTree);
         notificationElement.appendChild(dataChangedNotificationEventElement);
@@ -250,7 +266,8 @@ public class ListenerAdapter implements DOMDataChangeListener {
             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
             transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
             transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
-            transformer.transform(new DOMSource(doc), new StreamResult(new OutputStreamWriter(out, StandardCharsets.UTF_8)));
+            transformer.transform(new DOMSource(doc),
+                    new StreamResult(new OutputStreamWriter(out, StandardCharsets.UTF_8)));
             final byte[] charData = out.toByteArray();
             return new String(charData, "UTF-8");
         } catch (TransformerException | UnsupportedEncodingException e) {
@@ -324,8 +341,8 @@ public class ListenerAdapter implements DOMDataChangeListener {
      * @param operation
      *            {@link Operation}
      */
-    private void addValuesFromDataToElement(final Document doc, final Set<YangInstanceIdentifier> data, final Element element,
-            final Operation operation) {
+    private void addValuesFromDataToElement(final Document doc, final Set<YangInstanceIdentifier> data,
+            final Element element, final Operation operation) {
         if ((data == null) || data.isEmpty()) {
             return;
         }
@@ -363,7 +380,8 @@ public class ListenerAdapter implements DOMDataChangeListener {
      *            {@link Operation}
      * @return {@link Node} node represented by changed event element.
      */
-    private Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier path, final Operation operation) {
+    private Node createDataChangeEventElement(final Document doc, final YangInstanceIdentifier path,
+            final Operation operation) {
         final Element dataChangeEventElement = doc.createElement("data-change-event");
         final Element pathElement = doc.createElement("path");
         addPathAsValueToElement(path, pathElement);
index 0bd38652b766634f4b28088a4210d0c005beb2e7..c3746df5974f754cda8a1d8fd6ffd803354d977e 100644 (file)
@@ -14,7 +14,9 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,8 +27,9 @@ import org.slf4j.LoggerFactory;
  */
 public class Notificator {
 
-    private static Map<String, ListenerAdapter> listenersByStreamName = new ConcurrentHashMap<>();
-    private static Map<String, List<NotificationListenerAdapter>> notificationListenersByStreamName = new ConcurrentHashMap<>();
+    private static Map<String, ListenerAdapter> dataChangeListener = new ConcurrentHashMap<>();
+    private static Map<String, List<NotificationListenerAdapter>> notificationListenersByStreamName =
+            new ConcurrentHashMap<>();
 
     private static final Logger LOG = LoggerFactory.getLogger(Notificator.class);
     private static final Lock lock = new ReentrantLock();
@@ -38,7 +41,7 @@ public class Notificator {
      * Returns list of all stream names
      */
     public static Set<String> getStreamNames() {
-        return listenersByStreamName.keySet();
+        return dataChangeListener.keySet();
     }
 
     /**
@@ -49,7 +52,7 @@ public class Notificator {
      * @return {@link ListenerAdapter} specified by stream name.
      */
     public static ListenerAdapter getListenerFor(final String streamName) {
-        return listenersByStreamName.get(streamName);
+        return dataChangeListener.get(streamName);
     }
 
     /**
@@ -59,23 +62,28 @@ public class Notificator {
      * @return True if the listener exist, false otherwise.
      */
     public static boolean existListenerFor(final String streamName) {
-        return listenersByStreamName.containsKey(streamName);
+        return dataChangeListener.containsKey(streamName);
     }
 
     /**
-     * Creates new {@link ListenerAdapter} listener from {@link YangInstanceIdentifier} path and stream name.
+     * Creates new {@link ListenerAdapter} listener from
+     * {@link YangInstanceIdentifier} path and stream name.
      *
      * @param path
      *            Path to data in data repository.
      * @param streamName
      *            The name of the stream.
-     * @return New {@link ListenerAdapter} listener from {@link YangInstanceIdentifier} path and stream name.
+     * @param outputType
+     *            - Spcific type of output for notifications - XML or JSON
+     * @return New {@link ListenerAdapter} listener from
+     *         {@link YangInstanceIdentifier} path and stream name.
      */
-    public static ListenerAdapter createListener(final YangInstanceIdentifier path, final String streamName) {
-        final ListenerAdapter listener = new ListenerAdapter(path, streamName);
+    public static ListenerAdapter createListener(final YangInstanceIdentifier path, final String streamName,
+            final NotificationOutputType outputType) {
+        final ListenerAdapter listener = new ListenerAdapter(path, streamName, outputType);
         try {
             lock.lock();
-            listenersByStreamName.put(streamName, listener);
+            dataChangeListener.put(streamName, listener);
         } finally {
             lock.unlock();
         }
@@ -108,7 +116,7 @@ public class Notificator {
      * Removes all listeners.
      */
     public static void removeAllListeners() {
-        for (final ListenerAdapter listener : listenersByStreamName.values()) {
+        for (final ListenerAdapter listener : dataChangeListener.values()) {
             try {
                 listener.close();
             } catch (final Exception e) {
@@ -117,7 +125,7 @@ public class Notificator {
         }
         try {
             lock.lock();
-            listenersByStreamName = new ConcurrentHashMap<>();
+            dataChangeListener = new ConcurrentHashMap<>();
         } finally {
             lock.unlock();
         }
@@ -150,7 +158,7 @@ public class Notificator {
             }
             try {
                 lock.lock();
-                listenersByStreamName.remove(listener.getStreamName());
+                dataChangeListener.remove(listener.getStreamName());
             } finally {
                 lock.unlock();
             }
@@ -169,6 +177,17 @@ public class Notificator {
 
     }
 
+    /**
+     * Prepare listener for notification ({@link NotificationDefinition})
+     *
+     * @param paths
+     *            - paths of notifications
+     * @param streamName
+     *            - name of stream (generated by paths)
+     * @param outputType
+     *            - type of output for onNotification - XML or JSON
+     * @return List of {@link NotificationListenerAdapter} by paths
+     */
     public static List<NotificationListenerAdapter> createNotificationListener(final List<SchemaPath> paths,
             final String streamName, final String outputType) {
         final List<NotificationListenerAdapter> listListeners = new ArrayList<>();
index e4f867c61331ff2e0e2fb7eadf661e19b9183789..261c4dba7ec2ddbe27dde6b6ff994d89bc6ceb4b 100644 (file)
@@ -20,6 +20,7 @@ import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
 import org.opendaylight.restconf.common.references.SchemaContextRef;
 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
@@ -45,6 +46,7 @@ import org.slf4j.LoggerFactory;
 public final class CreateStreamUtil {
 
     private static final Logger LOG = LoggerFactory.getLogger(CreateStreamUtil.class);
+    private static final String OUTPUT_TYPE_PARAM_NAME = "notification-output-type";
 
     private CreateStreamUtil() {
         throw new UnsupportedOperationException("Util class");
@@ -98,14 +100,25 @@ public final class CreateStreamUtil {
         final ContainerNode output = ImmutableContainerNodeBuilder.create()
                 .withNodeIdentifier(new NodeIdentifier(outputQname))
                 .withChild(ImmutableNodes.leafNode(streamNameQname, streamName)).build();
+        final NotificationOutputType outputType = prepareOutputType(data);
 
         if (!Notificator.existListenerFor(streamName)) {
-            Notificator.createListener(path, streamName);
+            Notificator.createListener(path, streamName, outputType);
         }
 
         return new DefaultDOMRpcResult(output);
     }
 
+    /**
+     * @param data
+     *            - data of notification
+     * @return output type fo notification
+     */
+    private static NotificationOutputType prepareOutputType(final ContainerNode data) {
+        NotificationOutputType outputType = parseEnum(data, NotificationOutputType.class, OUTPUT_TYPE_PARAM_NAME);
+        return outputType = outputType == null ? NotificationOutputType.XML : outputType;
+    }
+
     private static String prepareStream(final YangInstanceIdentifier path, final SchemaContext schemaContext,
             final ContainerNode data) {
         LogicalDatastoreType ds = parseEnum(data, LogicalDatastoreType.class,
index c5dc5d251334c8cf953427671dcb218aabd7b206..29cc47a8956d68843f8e8335220066203347c1e4 100644 (file)
@@ -70,7 +70,8 @@ public final class RestconfStreamsConstants {
         }
         SAL_REMOTE_AUGMENT = QNameModule.create(NAMESPACE_EVENT_SUBSCRIPTION_AUGMENT, eventSubscriptionAugRevision);
         SAL_REMOTE_AUG_IDENTIFIER = new YangInstanceIdentifier.AugmentationIdentifier(Sets
-                .newHashSet(QName.create(SAL_REMOTE_AUGMENT, "scope"), QName.create(SAL_REMOTE_AUGMENT, "datastore")));
+                .newHashSet(QName.create(SAL_REMOTE_AUGMENT, "scope"), QName.create(SAL_REMOTE_AUGMENT, "datastore"),
+                        QName.create(SAL_REMOTE_AUGMENT, "notification-output-type")));
     }
 
     private RestconfStreamsConstants() {
index 8f5088f8bd0c8bf346bb9d131d43f4d5042611d6..7726a9d0bd6dff38c863a7309a778cd03a97a445 100644 (file)
@@ -12,6 +12,17 @@ module sal-remote-augment {
     revision "2014-07-08" {
     }
 
+    grouping notification-output-type-grouping{
+        leaf notification-output-type {
+            type enumeration {
+                enum JSON;
+                enum XML;
+            }
+            default "XML";
+            description "Input parameter which type of output will be parsed on notification";
+        }
+    }
+
     augment "/salrmt:create-data-change-event-subscription/salrmt:input" {
         leaf datastore {
             type enumeration {
@@ -26,17 +37,11 @@ module sal-remote-augment {
                 enum SUBTREE;
             }
         }
+        uses notification-output-type-grouping;
     }
 
     augment "/salrmt:create-notification-stream/salrmt:input" {
-        leaf notification-output-type {
-            type enumeration {
-                enum JSON;
-                enum XML;
-            }
-            default "XML";
-            description "Input parameter which type of output will be parsed on notification";
-        }
+        uses notification-output-type-grouping;
     }
 
 }
index ed3aba5fab41bf5bd0364e9cfc597e26243ef5e1..5238093ad19d3fcefeca730ef1372a05390c7289 100644 (file)
@@ -22,7 +22,6 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
-
 import com.google.common.base.Optional;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.CheckedFuture;
@@ -61,6 +60,7 @@ import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
 import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -97,10 +97,10 @@ public class BrokerFacadeTest {
     private final BrokerFacade brokerFacade = BrokerFacade.getInstance();
     private final NormalizedNode<?, ?> dummyNode = createDummyNode("test:module", "2014-01-09", "interfaces");
     private final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> dummyNodeInFuture =
-            wrapDummyNode(dummyNode);
+            wrapDummyNode(this.dummyNode);
     private final QName qname = TestUtils.buildQName("interfaces","test:module", "2014-01-09");
-    private final SchemaPath type = SchemaPath.create(true, qname);
-    private final YangInstanceIdentifier instanceID = YangInstanceIdentifier.builder().node(qname).build();
+    private final SchemaPath type = SchemaPath.create(true, this.qname);
+    private final YangInstanceIdentifier instanceID = YangInstanceIdentifier.builder().node(this.qname).build();
 
     @Before
     public void setUp() throws Exception {
@@ -299,7 +299,8 @@ public class BrokerFacadeTest {
 
     @Test
     public void testRegisterToListenDataChanges() {
-        final ListenerAdapter listener = Notificator.createListener(this.instanceID, "stream");
+        final ListenerAdapter listener = Notificator.createListener(this.instanceID, "stream",
+                NotificationOutputType.XML);
 
         @SuppressWarnings("unchecked")
         final ListenerRegistration<DOMDataChangeListener> mockRegistration = mock(ListenerRegistration.class);
index ee608eae7788453b50755c96810c13c7f012b59d..0575a0ab9e68112d242f1f6c6fbc264a2d161e59 100644 (file)
@@ -13,6 +13,7 @@ import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil.getRevisionFormat;
+import com.google.common.base.Preconditions;
 import java.io.FileNotFoundException;
 import java.text.ParseException;
 import java.util.Date;
@@ -21,8 +22,8 @@ import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
+import org.mockito.Mockito;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
@@ -32,20 +33,26 @@ import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
 import org.opendaylight.netconf.sal.streams.listeners.Notificator;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
 
@@ -66,13 +73,11 @@ public class URIParametersParsing {
     }
 
     @Test
-    @Ignore // URI parsing test - not able to catch a motivation + bad mocking response now - it needs to change Controller RPC table holder approach
     public void resolveURIParametersConcreteValues() {
         resolveURIParameters("OPERATIONAL", "SUBTREE", LogicalDatastoreType.OPERATIONAL, DataChangeScope.SUBTREE);
     }
 
     @Test
-    @Ignore // URI parsing test - not able to catch a motivation + bad mocking response now - it needs to change Controller RPC table holder approach
     public void resolveURIParametersDefaultValues() {
         resolveURIParameters(null, null, LogicalDatastoreType.CONFIGURATION, DataChangeScope.BASE);
     }
@@ -86,7 +91,7 @@ public class URIParametersParsing {
         final String datastoreValue = datastore == null ? "CONFIGURATION" : datastore;
         final String scopeValue = scope == null ? "BASE" : scope + "";
         Notificator.createListener(iiBuilder.build(), "dummyStreamName/datastore=" + datastoreValue + "/scope="
-                + scopeValue);
+                + scopeValue, NotificationOutputType.XML);
 
         final UriInfo mockedUriInfo = mock(UriInfo.class);
         @SuppressWarnings("unchecked")
@@ -99,16 +104,13 @@ public class URIParametersParsing {
          final UriBuilder uriBuilder = UriBuilder.fromUri("www.whatever.com");
          when(mockedUriInfo.getAbsolutePathBuilder()).thenReturn(uriBuilder);
 
-//       when(mockedBrokerFacade.invokeRpc(any(SchemaPath.class), any(NormalizedNode.class)))
-//       .thenReturn(Futures.<DOMRpcResult, DOMRpcException> immediateCheckedFuture(new DefaultDOMRpcResult(Builders.containerBuilder().build())));
-
         this.restconf.invokeRpc("sal-remote:create-data-change-event-subscription", prepareDomRpcNode(datastore, scope),
                 mockedUriInfo);
 
-        final ListenerAdapter listener = Notificator.getListenerFor("opendaylight-inventory:nodes/datastore="
+        final ListenerAdapter listener =
+                Notificator.getListenerFor("data-change-event-subscription/opendaylight-inventory:nodes/datastore="
                 + datastoreValue + "/scope=" + scopeValue);
         assertNotNull(listener);
-
     }
 
     private NormalizedNodeContext prepareDomRpcNode(final String datastore, final String scope) {
@@ -123,7 +125,9 @@ public class URIParametersParsing {
         final Module rpcSalRemoteModule = schema.findModuleByName("sal-remote", revDate);
         final Set<RpcDefinition> setRpcs = rpcSalRemoteModule.getRpcs();
         final QName rpcQName = QName.create(rpcSalRemoteModule.getQNameModule(), "create-data-change-event-subscription");
-        final QName rpcInputQName = QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote","2014-01-14","input");
+        final QName rpcInputQName =
+                QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "2014-01-14", "input");
+        final RpcDefinition rpcDef = Mockito.mock(RpcDefinition.class);
         ContainerSchemaNode rpcInputSchemaNode = null;
         for (final RpcDefinition rpc : setRpcs) {
             if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
@@ -133,29 +137,52 @@ public class URIParametersParsing {
         }
         assertNotNull("RPC ContainerSchemaNode was not found!", rpcInputSchemaNode);
 
-        final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> container = Builders.containerBuilder(rpcInputSchemaNode);
+        final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> container =
+                Builders.containerBuilder(rpcInputSchemaNode);
 
-        final QName pathQName = QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "2014-01-14", "path");
+        final QName pathQName =
+                QName.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote", "2014-01-14", "path");
         final DataSchemaNode pathSchemaNode = rpcInputSchemaNode.getDataChildByName(pathQName);
         assertTrue(pathSchemaNode instanceof LeafSchemaNode);
         final LeafNode<Object> pathNode = (Builders.leafBuilder((LeafSchemaNode) pathSchemaNode)
-                .withValue(YangInstanceIdentifier.builder().node(QName.create("urn:opendaylight:inventory", "2013-08-19", "nodes")).build())).build();
+                .withValue(YangInstanceIdentifier.builder()
+                        .node(QName.create("urn:opendaylight:inventory", "2013-08-19", "nodes")).build())).build();
         container.withChild(pathNode);
 
+        final AugmentationSchema augmentationSchema = rpcInputSchemaNode.getAvailableAugmentations().iterator().next();
+        Preconditions.checkNotNull(augmentationSchema);
+        final DataContainerNodeBuilder<AugmentationIdentifier, AugmentationNode> augmentationBuilder =
+                Builders.augmentationBuilder(augmentationSchema);
+
         final QName dataStoreQName = QName.create("urn:sal:restconf:event:subscription", "2014-7-8", "datastore");
-        final DataSchemaNode dsSchemaNode = rpcInputSchemaNode.getDataChildByName(dataStoreQName);
+        final DataSchemaNode dsSchemaNode = augmentationSchema.getDataChildByName(dataStoreQName);
         assertTrue(dsSchemaNode instanceof LeafSchemaNode);
         final LeafNode<Object> dsNode = (Builders.leafBuilder((LeafSchemaNode) dsSchemaNode)
                 .withValue(datastore)).build();
-        container.withChild(dsNode);
+        augmentationBuilder.withChild(dsNode);
 
         final QName scopeQName = QName.create("urn:sal:restconf:event:subscription", "2014-7-8", "scope");
-        final DataSchemaNode scopeSchemaNode = rpcInputSchemaNode.getDataChildByName(scopeQName);
+        final DataSchemaNode scopeSchemaNode = augmentationSchema.getDataChildByName(scopeQName);
         assertTrue(scopeSchemaNode instanceof LeafSchemaNode);
         final LeafNode<Object> scopeNode = (Builders.leafBuilder((LeafSchemaNode) scopeSchemaNode)
                 .withValue(scope)).build();
-        container.withChild(scopeNode);
+        augmentationBuilder.withChild(scopeNode);
+
+        final QName outputQName =
+                QName.create("urn:sal:restconf:event:subscription", "2014-7-8", "notification-output-type");
+        final DataSchemaNode outputSchemaNode = augmentationSchema.getDataChildByName(outputQName);
+        assertTrue(outputSchemaNode instanceof LeafSchemaNode);
+        final LeafNode<Object> outputNode =
+                (Builders.leafBuilder((LeafSchemaNode) outputSchemaNode).withValue("XML")).build();
+        augmentationBuilder.withChild(outputNode);
+
+        container.withChild(augmentationBuilder.build());
+
+        when(rpcDef.getInput()).thenReturn(rpcInputSchemaNode);
+        when(rpcDef.getPath()).thenReturn(SchemaPath.create(true, rpcQName));
+        when(rpcDef.getQName()).thenReturn(rpcQName);
 
-        return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, rpcInputSchemaNode, null, schema), container.build());
+        return new NormalizedNodeContext(new InstanceIdentifierContext<RpcDefinition>(null, rpcDef, null, schema),
+                container.build());
     }
 }
index 1e339ad18ec0bdae6ee06e77b5af49e7096ec910..ef5464eae1a0c248535307482873c776f888750c 100644 (file)
@@ -42,6 +42,7 @@ import org.opendaylight.restconf.handlers.SchemaContextHandler;
 import org.opendaylight.restconf.rest.services.api.RestconfStreamsService;
 import org.opendaylight.restconf.utils.mapping.RestconfMappingNodeConstants;
 import org.opendaylight.restconf.utils.mapping.RestconfMappingStreamConstants;
+import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
@@ -82,9 +83,12 @@ public class RestconfStreamsServiceTest {
         Notificator.removeAllListeners();
 
         // put test streams
-        Notificator.createListener(EMPTY, RestconfStreamsServiceTest.expectedStreams.get(0));
-        Notificator.createListener(EMPTY, RestconfStreamsServiceTest.expectedStreams.get(1));
-        Notificator.createListener(EMPTY, RestconfStreamsServiceTest.expectedStreams.get(2));
+        Notificator.createListener(EMPTY, RestconfStreamsServiceTest.expectedStreams.get(0),
+                NotificationOutputType.XML);
+        Notificator.createListener(EMPTY, RestconfStreamsServiceTest.expectedStreams.get(1),
+                NotificationOutputType.XML);
+        Notificator.createListener(EMPTY, RestconfStreamsServiceTest.expectedStreams.get(2),
+                NotificationOutputType.XML);
     }
 
     @AfterClass
index c147f28a5ee9515cd825e3f0a544099e8fb134c4..2a5ba718d4d997c261e55aacf221436c0ff0f06e 100644 (file)
@@ -12,7 +12,6 @@ import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
@@ -36,7 +35,8 @@ import org.opendaylight.yangtools.concepts.ListenerRegistration;
 
 public class RestconfStreamsSubscriptionServiceImplTest {
 
-    private static final String uri = "/restconf/15/data/ietf-restconf-monitoring:restconf-state/streams/stream/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE";
+    private static final String uri = "/restconf/17/data/ietf-restconf-monitoring:restconf-state/streams/stream/"
+            + "toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE";
     private static Field listenersByStreamName;
 
     @Mock
@@ -49,7 +49,7 @@ public class RestconfStreamsSubscriptionServiceImplTest {
         MockitoAnnotations.initMocks(this);
         final DOMDataBroker dataBroker = mock(DOMDataBroker.class);
         final ListenerRegistration<DOMDataChangeListener> listener = mock(ListenerRegistration.class);
-        doReturn(dataBroker).when(dataBrokerHandler).get();
+        doReturn(dataBroker).when(this.dataBrokerHandler).get();
         doReturn(listener).when(dataBroker).registerDataChangeListener(any(), any(), any(), any());
     }
 
@@ -59,7 +59,7 @@ public class RestconfStreamsSubscriptionServiceImplTest {
         final ListenerAdapter adapter = mock(ListenerAdapter.class);
         doReturn(false).when(adapter).isListening();
         listenersByStreamNameSetter.put("toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE", adapter);
-        listenersByStreamName = Notificator.class.getDeclaredField("listenersByStreamName");
+        listenersByStreamName = Notificator.class.getDeclaredField("dataChangeListener");
 
         listenersByStreamName.setAccessible(true);
         listenersByStreamName.set(Notificator.class, listenersByStreamNameSetter);
@@ -73,29 +73,35 @@ public class RestconfStreamsSubscriptionServiceImplTest {
     }
 
     @Test
-    public void testSubscribeToStream() {
+    public void testSubscribueToStream() {
         final UriBuilder uriBuilder = UriBuilder.fromUri(uri);
-        doReturn(uriBuilder).when(uriInfo).getAbsolutePathBuilder();
-        final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService = new RestconfStreamsSubscriptionServiceImpl(dataBrokerHandler);
-        final Response response = streamsSubscriptionService.subscribeToStream("toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE", uriInfo);
+        doReturn(uriBuilder).when(this.uriInfo).getAbsolutePathBuilder();
+        final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
+                new RestconfStreamsSubscriptionServiceImpl(this.dataBrokerHandler);
+        final Response response = streamsSubscriptionService
+                .subscribeToStream("toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE", this.uriInfo);
         assertEquals(200, response.getStatus());
-        assertEquals("ws://:8181/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE", response.getHeaderString("Location"));
+        assertEquals("ws://:8181/toaster:toaster/toasterStatus/datastore=OPERATIONAL/scope=ONE",
+                response.getHeaderString("Location"));
     }
 
     @Test(expected = RestconfDocumentedException.class)
     public void testSubscribeToStreamMissingDatastoreInPath() {
         final UriBuilder uriBuilder = UriBuilder.fromUri(uri);
-        doReturn(uriBuilder).when(uriInfo).getAbsolutePathBuilder();
-        final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService = new RestconfStreamsSubscriptionServiceImpl(dataBrokerHandler);
-        final Response response = streamsSubscriptionService.subscribeToStream("toaster:toaster/toasterStatus/scope=ONE", uriInfo);
+        doReturn(uriBuilder).when(this.uriInfo).getAbsolutePathBuilder();
+        final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
+                new RestconfStreamsSubscriptionServiceImpl(this.dataBrokerHandler);
+        streamsSubscriptionService.subscribeToStream("toaster:toaster/toasterStatus/scope=ONE", this.uriInfo);
     }
 
     @Test(expected = RestconfDocumentedException.class)
     public void testSubscribeToStreamMissingScopeInPath() {
         final UriBuilder uriBuilder = UriBuilder.fromUri(uri);
-        doReturn(uriBuilder).when(uriInfo).getAbsolutePathBuilder();
-        final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService = new RestconfStreamsSubscriptionServiceImpl(dataBrokerHandler);
-        final Response response = streamsSubscriptionService.subscribeToStream("toaster:toaster/toasterStatus/datastore=OPERATIONAL", uriInfo);
+        doReturn(uriBuilder).when(this.uriInfo).getAbsolutePathBuilder();
+        final RestconfStreamsSubscriptionServiceImpl streamsSubscriptionService =
+                new RestconfStreamsSubscriptionServiceImpl(this.dataBrokerHandler);
+        streamsSubscriptionService.subscribeToStream("toaster:toaster/toasterStatus/datastore=OPERATIONAL",
+                this.uriInfo);
     }
 
 }
index 83934568cc05ab1f3fc1fe031a91f228679fb901..f88f17b5872b360298f4609c5fc0af9680783739 100644 (file)
@@ -26,6 +26,14 @@ module sal-remote-augment {
                 enum SUBTREE;
             }
         }
+        leaf notification-output-type {
+            type enumeration {
+                enum JSON;
+                enum XML;
+            }
+            default "XML";
+            description "Input parameter which type of output will be parsed on notification";
+        }
     }
 
 }
\ No newline at end of file