X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=restconf%2Fsal-rest-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fsal%2Frestconf%2Fimpl%2FRestconfImpl.java;h=eff4885f630de665ee3029c7cf9ee4fe06c220a7;hb=ddc71443d58ac78f8a593be88e181310f1e4b9c8;hp=669028f38e7142dc668f2516015dc895df0f6a93;hpb=283cab6fc29ad812e6f706bd6bdb00bf2142ee1c;p=netconf.git 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..eff4885f63 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 @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import java.net.URI; import java.net.URISyntaxException; +import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -33,6 +34,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; @@ -62,6 +64,8 @@ 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.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime; +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; @@ -82,7 +86,9 @@ import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; @@ -131,6 +137,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 +158,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 +225,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 +253,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 +287,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 +322,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 +377,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 +430,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 +502,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 +528,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 +544,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 +573,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 +856,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 +969,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; @@ -1017,17 +1048,118 @@ public class RestconfImpl implements RestconfService { * */ @Override - public Response subscribeToStream(final String identifier, final UriInfo uriInfo) { + public NormalizedNodeContext subscribeToStream(final String identifier, final UriInfo uriInfo) { + boolean startTime_used = false; + boolean stopTime_used = false; + boolean filter_used = false; + Date start = null; + Date stop = null; + String filter = null; + + for (final Entry> entry : uriInfo.getQueryParameters().entrySet()) { + switch (entry.getKey()) { + case "start-time": + if (!startTime_used) { + startTime_used = true; + start = parseDateFromQueryParam(entry); + } else { + throw new RestconfDocumentedException("Start-time parameter can be used only once."); + } + break; + case "stop-time": + if (!stopTime_used) { + stopTime_used = true; + stop = parseDateFromQueryParam(entry); + } else { + throw new RestconfDocumentedException("Stop-time parameter can be used only once."); + } + break; + case "filter": + if (!filter_used) { + filter_used = true; + filter = entry.getValue().iterator().next(); + } else { + throw new RestconfDocumentedException("Filter parameter can be used only once."); + } + break; + default: + throw new RestconfDocumentedException("Bad parameter used with notifications: " + entry.getKey()); + } + } + if(!startTime_used && stopTime_used){ + throw new RestconfDocumentedException("Stop-time parameter has to be used with start-time parameter."); + } + URI response = null; if (identifier.contains(DATA_SUBSCR)) { - return dataSubs(identifier, uriInfo); + response = dataSubs(identifier, uriInfo, start, stop, filter); } else if (identifier.contains(NOTIFICATION_STREAM)) { - return notifStream(identifier, uriInfo); + response = notifStream(identifier, uriInfo, start, stop, filter); } + + if(response != null){ + // prepare node with value of location + final InstanceIdentifierContext iid = prepareIIDSubsStreamOutput(); + final NormalizedNodeAttrBuilder> builder = ImmutableLeafNodeBuilder + .create().withValue(response.toString()); + builder.withNodeIdentifier( + NodeIdentifier.create(QName.create("subscribe:to:notification", "2016-10-28", "location"))); + + // prepare new header with location + final Map headers = new HashMap<>(); + headers.put("Location", response); + + return new NormalizedNodeContext(iid, builder.build(), headers); + } + final String msg = "Bad type of notification of sal-remote"; LOG.warn(msg); throw new RestconfDocumentedException(msg); } + private Date parseDateFromQueryParam(final Entry> entry) { + final DateAndTime event = new DateAndTime(entry.getValue().iterator().next()); + String numOf_ms = ""; + final String value = event.getValue(); + if (value.contains(".")) { + numOf_ms = numOf_ms + "."; + final int lastChar = value.contains("Z") ? value.indexOf("Z") : (value.contains("+") ? value.indexOf("+") + : (value.contains("-") ? value.indexOf("-") : value.length())); + for (int i = 0; i < (lastChar - value.indexOf(".") - 1); i++) { + numOf_ms = numOf_ms + "S"; + } + } + String zone = ""; + if (!value.contains("Z")) { + zone = zone + "XXX"; + } + final DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" + numOf_ms + zone); + + try { + return dateFormatter.parse(value.contains("Z") ? value.replace('T', ' ').substring(0, value.indexOf("Z")) + : value.replace('T', ' ')); + } catch (final ParseException e) { + throw new RestconfDocumentedException("Cannot parse of value in date: " + value + e); + } + } + + /** + * @return {@link InstanceIdentifierContext} of location leaf for + * notification + */ + private InstanceIdentifierContext prepareIIDSubsStreamOutput() { + final QName qnameBase = QName.create("subscribe:to:notification", "2016-10-28", "notifi"); + final SchemaContext schemaCtx = ControllerContext.getInstance().getGlobalSchema(); + final DataSchemaNode location = ((ContainerSchemaNode) schemaCtx + .findModuleByNamespaceAndRevision(qnameBase.getNamespace(), qnameBase.getRevision()) + .getDataChildByName(qnameBase)).getDataChildByName(QName.create(qnameBase, "location")); + final List path = new ArrayList<>(); + path.add(NodeIdentifier.create(qnameBase)); + path.add(NodeIdentifier.create(QName.create(qnameBase, "location"))); + + return new InstanceIdentifierContext(YangInstanceIdentifier.create(path), location, null, + schemaCtx); + } + /** * Register notification listener by stream name * @@ -1035,9 +1167,16 @@ public class RestconfImpl implements RestconfService { * - stream name * @param uriInfo * - uriInfo - * @return {@link Response} + * @param stop + * - stop-time of getting notification + * @param start + * - start-time of getting notification + * @param filter + * - indicate wh ich subset of allpossible events are of interest + * @return {@link URI} of location */ - private Response notifStream(final String identifier, final UriInfo uriInfo) { + private URI notifStream(final String identifier, final UriInfo uriInfo, final Date start, final Date stop, + final String filter) { final String streamName = Notificator.createStreamNameFromUri(identifier); if (Strings.isNullOrEmpty(streamName)) { throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE); @@ -1050,6 +1189,7 @@ public class RestconfImpl implements RestconfService { for (final NotificationListenerAdapter listener : listeners) { this.broker.registerToListenNotification(listener); + listener.setQueryParams(start, stop, filter); } final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder(); @@ -1063,7 +1203,7 @@ public class RestconfImpl implements RestconfService { final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme("ws"); final URI uriToWebsocketServer = uriToWebsocketServerBuilder.replacePath(streamName).build(); - return Response.status(Status.OK).location(uriToWebsocketServer).build(); + return uriToWebsocketServer; } /** @@ -1073,9 +1213,16 @@ public class RestconfImpl implements RestconfService { * - stream name * @param uriInfo * - uri info - * @return {@link Response} + * @param stop + * - start-time of getting notification + * @param start + * - stop-time of getting notification + * @param filter + * - indicate which subset of all possible events are of interest + * @return {@link URI} of location */ - private Response dataSubs(final String identifier, final UriInfo uriInfo) { + private URI dataSubs(final String identifier, final UriInfo uriInfo, final Date start, final Date stop, + final String filter) { final String streamName = Notificator.createStreamNameFromUri(identifier); if (Strings.isNullOrEmpty(streamName)) { throw new RestconfDocumentedException("Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE); @@ -1085,6 +1232,7 @@ public class RestconfImpl implements RestconfService { if (listener == null) { throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT); } + listener.setQueryParams(start, stop, filter); final Map paramToValues = resolveValuesFromUri(identifier); final LogicalDatastoreType datastore = parserURIEnumParameter(LogicalDatastoreType.class, @@ -1112,11 +1260,12 @@ public class RestconfImpl implements RestconfService { final UriBuilder uriToWebsocketServerBuilder = uriBuilder.port(notificationPort).scheme("ws"); final URI uriToWebsocketServer = uriToWebsocketServerBuilder.replacePath(streamName).build(); - return Response.status(Status.OK).location(uriToWebsocketServer).build(); + return uriToWebsocketServer; } @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 +1306,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; }