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;
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;
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.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;
final CheckedFuture<DOMRpcResult, DOMRpcException> response;
final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
final SchemaContext schemaContext;
+
if (mountPoint != null) {
final Optional<DOMRpcService> mountRpcServices = mountPoint.getService(DOMRpcService.class);
if ( ! mountRpcServices.isPresent()) {
}
@Override
- public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload) {
+ public Response updateConfigurationData(final String identifier, final NormalizedNodeContext payload,
+ final UriInfo uriInfo) {
+ boolean insert_used = false;
+ boolean point_used = false;
+ String insert = null;
+ String point = null;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "insert":
+ if (!insert_used) {
+ insert_used = true;
+ insert = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Insert parameter can be used only once.");
+ }
+ break;
+ case "point":
+ if (!point_used) {
+ point_used = true;
+ point = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Point parameter can be used only once.");
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
+ }
+ }
+
+ if (point_used && !insert_used) {
+ throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
+ }
+ if (point_used && (insert.equals("first") || insert.equals("last"))) {
+ throw new RestconfDocumentedException(
+ "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
+ }
+
Preconditions.checkNotNull(identifier);
+
final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
validateInput(iiWithData.getSchemaNode(), payload);
while(true) {
if (mountPoint != null) {
- result = this.broker.commitMountPointDataPut(mountPoint, normalizedII, payload.getData());
+ result = this.broker.commitMountPointDataPut(mountPoint, normalizedII, payload.getData(), insert,
+ point);
} else {
result = this.broker.commitConfigurationDataPut(this.controllerContext.getGlobalSchema(), normalizedII,
- payload.getData());
+ payload.getData(), insert, point);
}
final CountDownLatch waiter = new CountDownLatch(1);
Futures.addCallback(result.getFutureOfPutData(), new FutureCallback<Void>() {
if (payload == null) {
throw new RestconfDocumentedException("Input is required.", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
}
-
- // FIXME: move this to parsing stage (we can have augmentation nodes here which do not have namespace)
-// final URI payloadNS = payload.getData().getNodeType().getNamespace();
-// if (payloadNS == null) {
-// throw new RestconfDocumentedException(
-// "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)",
-// ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE);
-// }
-
final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
final InstanceIdentifierContext<?> iiWithData = payload.getInstanceIdentifierContext();
final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier();
+ boolean insert_used = false;
+ boolean point_used = false;
+ String insert = null;
+ String point = null;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ switch (entry.getKey()) {
+ case "insert":
+ if (!insert_used) {
+ insert_used = true;
+ insert = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Insert parameter can be used only once.");
+ }
+ break;
+ case "point":
+ if (!point_used) {
+ point_used = true;
+ point = entry.getValue().iterator().next();
+ } else {
+ throw new RestconfDocumentedException("Point parameter can be used only once.");
+ }
+ break;
+ default:
+ throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey());
+ }
+ }
+
+ if (point_used && !insert_used) {
+ throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.");
+ }
+ if (point_used && (insert.equals("first") || insert.equals("last"))) {
+ throw new RestconfDocumentedException(
+ "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
+ }
+
CheckedFuture<Void, TransactionCommitFailedException> future;
if (mountPoint != null) {
- future = this.broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData());
+ future = this.broker.commitConfigurationDataPost(mountPoint, normalizedII, payload.getData(), insert,
+ point);
} else {
future = this.broker.commitConfigurationDataPost(this.controllerContext.getGlobalSchema(), normalizedII,
- payload.getData());
+ payload.getData(), insert, point);
}
final CountDownLatch waiter = new CountDownLatch(1);
* </ul>
*/
@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<String, List<String>> 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<NodeIdentifier, Object, LeafNode<Object>> 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<String, Object> 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<String, List<String>> 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<PathArgument> path = new ArrayList<>();
+ path.add(NodeIdentifier.create(qnameBase));
+ path.add(NodeIdentifier.create(QName.create(qnameBase, "location")));
+
+ return new InstanceIdentifierContext<SchemaNode>(YangInstanceIdentifier.create(path), location, null,
+ schemaCtx);
+ }
+
/**
* Register notification listener by stream name
*
* - 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);
for (final NotificationListenerAdapter listener : listeners) {
this.broker.registerToListenNotification(listener);
+ listener.setQueryParams(start, stop, filter);
}
final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
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;
}
/**
* - 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);
if (listener == null) {
throw new RestconfDocumentedException("Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT);
}
+ listener.setQueryParams(start, stop, filter);
final Map<String, String> paramToValues = resolveValuesFromUri(identifier);
final LogicalDatastoreType datastore = parserURIEnumParameter(LogicalDatastoreType.class,
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