From 04b98cadf3669bcce59df63268c06c94514ac919 Mon Sep 17 00:00:00 2001 From: Jakub Toth Date: Fri, 28 Oct 2016 12:55:00 +0200 Subject: [PATCH] Bug 6995 - Change event notification subscription usability PART1 * 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 --- .../rest/impl/RestconfCompositeWrapper.java | 48 +++++------ .../sal/restconf/impl/RestconfImpl.java | 80 +++++++++++++------ .../streams/listeners/ListenerAdapter.java | 32 ++++++-- .../sal/streams/listeners/Notificator.java | 45 ++++++++--- .../restful/utils/CreateStreamUtil.java | 15 +++- .../utils/RestconfStreamsConstants.java | 3 +- .../src/main/yang/sal-remote-augment.yang | 21 +++-- .../restconf/impl/test/BrokerFacadeTest.java | 11 +-- .../impl/test/URIParametersParsing.java | 63 ++++++++++----- .../impl/RestconfStreamsServiceTest.java | 10 ++- ...onfStreamsSubscriptionServiceImplTest.java | 36 +++++---- .../sal-remote-augment.yang | 8 ++ 12 files changed, 252 insertions(+), 120 deletions(-) diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java index df56bc4c0e..49e7965120 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestconfCompositeWrapper.java @@ -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); } } diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java index 669028f38e..25d081d73c 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java @@ -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 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 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 listStreamsBuilder = Builders @@ -309,14 +317,17 @@ public class RestconfImpl implements RestconfService { Set 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 fakeModules = new HashSet<>(); fakeModules.add(fakeModule); final SchemaContext fakeSchemaCtx = EffectiveSchemaContext.resolveSchemaContext(fakeModules); - final InstanceIdentifierContext instanceIdentifierContext = new InstanceIdentifierContext<>( - null, fakeContSchNode, mountPoint, fakeSchemaCtx); + final InstanceIdentifierContext 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 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 invokeSalRemoteRpcSubscribeRPC(final NormalizedNodeContext payload) { + private CheckedFuture + invokeSalRemoteRpcSubscribeRPC(final NormalizedNodeContext payload) { final ContainerNode value = (ContainerNode) payload.getData(); final QName rpcQName = payload.getInstanceIdentifierContext().getSchemaNode().getQName(); final Optional> 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> 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; } diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java index a8651c2163..b2cdc052af 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/ListenerAdapter.java @@ -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 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 data, final Element element, - final Operation operation) { + private void addValuesFromDataToElement(final Document doc, final Set 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); diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java index 0bd38652b7..c3746df597 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/streams/listeners/Notificator.java @@ -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 listenersByStreamName = new ConcurrentHashMap<>(); - private static Map> notificationListenersByStreamName = new ConcurrentHashMap<>(); + private static Map dataChangeListener = new ConcurrentHashMap<>(); + private static Map> 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 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 createNotificationListener(final List paths, final String streamName, final String outputType) { final List listListeners = new ArrayList<>(); diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/CreateStreamUtil.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/CreateStreamUtil.java index e4f867c613..261c4dba7e 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/CreateStreamUtil.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/CreateStreamUtil.java @@ -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, diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfStreamsConstants.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfStreamsConstants.java index c5dc5d2513..29cc47a895 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfStreamsConstants.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfStreamsConstants.java @@ -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() { diff --git a/restconf/sal-rest-connector/src/main/yang/sal-remote-augment.yang b/restconf/sal-rest-connector/src/main/yang/sal-remote-augment.yang index 8f5088f8bd..7726a9d0bd 100644 --- a/restconf/sal-rest-connector/src/main/yang/sal-remote-augment.yang +++ b/restconf/sal-rest-connector/src/main/yang/sal-remote-augment.yang @@ -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; } } diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java index ed3aba5fab..5238093ad1 100644 --- a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java @@ -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>, 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 mockRegistration = mock(ListenerRegistration.class); diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URIParametersParsing.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URIParametersParsing.java index ee608eae77..0575a0ab9e 100644 --- a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URIParametersParsing.java +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/URIParametersParsing.java @@ -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. 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 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 container = Builders.containerBuilder(rpcInputSchemaNode); + final DataContainerNodeAttrBuilder 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 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 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 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 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 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(null, rpcDef, null, schema), + container.build()); } } diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/rest/services/impl/RestconfStreamsServiceTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/rest/services/impl/RestconfStreamsServiceTest.java index 1e339ad18e..ef5464eae1 100644 --- a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/rest/services/impl/RestconfStreamsServiceTest.java +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/rest/services/impl/RestconfStreamsServiceTest.java @@ -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 diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfStreamsSubscriptionServiceImplTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfStreamsSubscriptionServiceImplTest.java index c147f28a5e..2a5ba718d4 100644 --- a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfStreamsSubscriptionServiceImplTest.java +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/services/impl/RestconfStreamsSubscriptionServiceImplTest.java @@ -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 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); } } diff --git a/restconf/sal-rest-connector/src/test/resources/datastore-and-scope-specification/sal-remote-augment.yang b/restconf/sal-rest-connector/src/test/resources/datastore-and-scope-specification/sal-remote-augment.yang index 83934568cc..f88f17b587 100644 --- a/restconf/sal-rest-connector/src/test/resources/datastore-and-scope-specification/sal-remote-augment.yang +++ b/restconf/sal-rest-connector/src/test/resources/datastore-and-scope-specification/sal-remote-augment.yang @@ -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 -- 2.36.6