This improves type-safety and moves concerns.
JIRA: NETCONF-773
Change-Id: I806563be3520feb31711b7e45f4956231b6e11b2
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
import static java.util.Objects.requireNonNull;
import java.net.URI;
-import javax.xml.xpath.XPathExpressionException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.opendaylight.yangtools.concepts.Immutable;
-import org.opendaylight.yangtools.yang.xpath.api.YangXPathExpression;
-import org.opendaylight.yangtools.yang.xpath.api.YangXPathMathMode;
-import org.opendaylight.yangtools.yang.xpath.api.YangXPathParserFactory;
/**
* This class represents a {@code filter} parameter as defined in
public final class FilterParameter implements Immutable {
private static final URI CAPABILITY = URI.create("urn:ietf:params:restconf:capability:filter:1.0");
- private final YangXPathExpression value;
+ // FIXME: can we have a parsed, but not bound version of an XPath, please?
+ private final String value;
- private FilterParameter(final YangXPathExpression value) {
+ private FilterParameter(final String value) {
this.value = requireNonNull(value);
}
- public static FilterParameter forUriValue(final YangXPathParserFactory parserFactory, final String uriValue)
- throws XPathExpressionException {
- return new FilterParameter(parserFactory.newParser(YangXPathMathMode.EXACT).parseExpression(uriValue));
- }
-
- public YangXPathExpression value() {
- return value;
+ public static FilterParameter forUriValue(final String uriValue) {
+ return new FilterParameter(uriValue);
}
public static String uriName() {
return "filter";
}
+ public String uriValue() {
+ return value;
+ }
+
public static URI capabilityUri() {
return CAPABILITY;
}
package org.opendaylight.restconf.nb.rfc8040.rests.services.impl;
import static com.google.common.base.Preconditions.checkState;
-import static java.util.Objects.requireNonNull;
import java.net.URI;
-import java.time.Instant;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeFormatterBuilder;
-import java.time.format.DateTimeParseException;
-import java.time.temporal.ChronoField;
-import java.time.temporal.TemporalAccessor;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import javax.ws.rs.Path;
import javax.ws.rs.core.UriInfo;
-import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMNotificationService;
import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfStreamsConstants;
import org.opendaylight.restconf.nb.rfc8040.streams.Configuration;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
* Parser and holder of query paramteres from uriInfo for notifications.
*/
public static final class NotificationQueryParams {
- private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
- .appendValue(ChronoField.YEAR, 4).appendLiteral('-')
- .appendValue(ChronoField.MONTH_OF_YEAR, 2).appendLiteral('-')
- .appendValue(ChronoField.DAY_OF_MONTH, 2).appendLiteral('T')
- .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral(':')
- .appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral(':')
- .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
- .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
- .appendOffset("+HH:MM", "Z").toFormatter();
-
- private final @NonNull Instant startTime;
- private final Instant stopTime;
- private final String filter;
+ private final StartTimeParameter startTime;
+ private final StopTimeParameter stopTime;
+ private final FilterParameter filter;
private final boolean skipNotificationData;
- private NotificationQueryParams(final Instant startTime, final Instant stopTime, final String filter,
- final boolean skipNotificationData) {
- this.startTime = requireNonNull(startTime);
+ private NotificationQueryParams(final StartTimeParameter startTime, final StopTimeParameter stopTime,
+ final FilterParameter filter, final boolean skipNotificationData) {
+ this.startTime = startTime;
this.stopTime = stopTime;
this.filter = filter;
this.skipNotificationData = skipNotificationData;
}
static NotificationQueryParams fromUriInfo(final UriInfo uriInfo) {
- Instant startTime = null;
- Instant stopTime = null;
- String filter = null;
+ StartTimeParameter startTime = null;
+ StopTimeParameter stopTime = null;
+ FilterParameter filter = null;
boolean skipNotificationData = false;
for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
case 0:
break;
case 1:
- startTime = parseDateFromQueryParam(paramValues.get(0));
+ final String str = paramValues.get(0);
+ try {
+ startTime = StartTimeParameter.forUriValue(str);
+ } catch (IllegalArgumentException e) {
+ throw new RestconfDocumentedException("Invalid start-time date: " + str, e);
+ }
break;
default:
throw new RestconfDocumentedException("Start-time parameter can be used only once.");
case 0:
break;
case 1:
- stopTime = parseDateFromQueryParam(paramValues.get(0));
+ final String str = paramValues.get(0);
+ try {
+ stopTime = StopTimeParameter.forUriValue(str);
+ } catch (IllegalArgumentException e) {
+ throw new RestconfDocumentedException("Invalid stop-time date: " + str, e);
+ }
break;
default:
throw new RestconfDocumentedException("Stop-time parameter can be used only once.");
}
} else if (paramName.equals(FilterParameter.uriName())) {
if (!paramValues.isEmpty()) {
- // FIXME: use FilterParameter
- filter = paramValues.get(0);
+ filter = FilterParameter.forUriValue(paramValues.get(0));
}
} else if (paramName.equals("odl-skip-notification-data")) {
switch (paramValues.size()) {
throw new RestconfDocumentedException("Stop-time parameter has to be used with start-time parameter.");
}
- return new NotificationQueryParams(startTime == null ? Instant.now() : startTime, stopTime, filter,
- skipNotificationData);
- }
-
-
- /**
- * Parse input of query parameters - start-time or stop-time - from {@link DateAndTime} format
- * to {@link Instant} format.
- *
- * @param uriValue Start-time or stop-time as string in {@link DateAndTime} format.
- * @return Parsed {@link Instant} by entry.
- */
- private static @NonNull Instant parseDateFromQueryParam(final String uriValue) {
- final TemporalAccessor accessor;
- try {
- accessor = FORMATTER.parse(new DateAndTime(uriValue).getValue());
- } catch (final DateTimeParseException | IllegalArgumentException e) {
- throw new RestconfDocumentedException("Cannot parse of value in date: " + uriValue, e);
- }
- return Instant.from(accessor);
+ return new NotificationQueryParams(startTime, stopTime, filter, skipNotificationData);
}
/**
*
* @return start-time
*/
- public @NonNull Instant startTime() {
+ public @Nullable StartTimeParameter startTime() {
return startTime;
}
*
* @return stop-time
*/
- public @Nullable Instant stopTime() {
+ public @Nullable StopTimeParameter stopTime() {
return stopTime;
}
*
* @return filter
*/
- public @Nullable String filter() {
+ public @Nullable FilterParameter filter() {
return filter;
}
notificationListenerAdapter.setCloseVars(dataBroker, handlersHolder.getSchemaHandler());
final MapEntryNode mapToStreams = RestconfMappingNodeUtil.mapYangNotificationStreamByIetfRestconfMonitoring(
notificationListenerAdapter.getSchemaPath().lastNodeIdentifier(),
- schemaContext.getNotifications(), notificationQueryParams.startTime(),
+ schemaContext.getNotifications(), notificationListenerAdapter.getStart(),
notificationListenerAdapter.getOutputType(), uri);
// FIXME: how does this correlate with the transaction notificationListenerAdapter.close() will do?
final MapEntryNode mapToStreams =
RestconfMappingNodeUtil.mapDataChangeNotificationStreamByIetfRestconfMonitoring(listener.getPath(),
- notificationQueryParams.startTime(), listener.getOutputType(), uri, schemaContext, serializedPath);
+ listener.getStart(), listener.getOutputType(), uri, schemaContext, serializedPath);
final DOMDataTreeWriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
writeDataToDS(writeTransaction, mapToStreams);
submitData(writeTransaction);
*/
package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.annotations.VisibleForTesting;
import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import javax.xml.xpath.XPathExpressionException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.FilterParameter;
+import org.opendaylight.restconf.nb.rfc8040.StartTimeParameter;
+import org.opendaylight.restconf.nb.rfc8040.StopTimeParameter;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
/**
* Features of query parameters part of both notifications.
*/
abstract class AbstractQueryParams extends AbstractNotificationsData {
+ private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
+ .appendValue(ChronoField.YEAR, 4).appendLiteral('-')
+ .appendValue(ChronoField.MONTH_OF_YEAR, 2).appendLiteral('-')
+ .appendValue(ChronoField.DAY_OF_MONTH, 2).appendLiteral('T')
+ .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral(':')
+ .appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral(':')
+ .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+ .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
+ .appendOffset("+HH:MM", "Z").toFormatter();
+
// FIXME: these should be final
- private Instant startTime = null;
- private Instant stopTime = null;
+ private Instant start = null;
+ private Instant stop = null;
private boolean leafNodesOnly = false;
private boolean skipNotificationData = false;
- @VisibleForTesting
public final Instant getStart() {
- return startTime;
+ return start;
}
/**
* @param filter Indicates which subset of all possible events are of interest.
* @param leafNodesOnly If TRUE, notifications will contain changes of leaf nodes only.
*/
- public abstract void setQueryParams(Instant startTime, Instant stopTime, String filter,
- boolean leafNodesOnly, boolean skipNotificationData);
-
@SuppressWarnings("checkstyle:hiddenField")
- final void setQueryParams(final Instant startTime, final Instant stopTime, final boolean leafNodesOnly,
- final boolean skipNotificationData) {
- this.startTime = requireNonNull(startTime);
- this.stopTime = stopTime;
+ public final void setQueryParams(final StartTimeParameter startTime, final StopTimeParameter stopTime,
+ final FilterParameter filter, final boolean leafNodesOnly, final boolean skipNotificationData) {
+ start = startTime == null ? Instant.now() : parseDateAndTime(startTime.value());
+ stop = stopTime == null ? null : parseDateAndTime(stopTime.value());
this.leafNodesOnly = leafNodesOnly;
this.skipNotificationData = skipNotificationData;
+
+ if (filter != null) {
+ try {
+ setFilter(filter.uriValue());
+ } catch (XPathExpressionException e) {
+ throw new IllegalArgumentException("Failed to get filter", e);
+ }
+ }
+ }
+
+ abstract void setFilter(@Nullable String xpathString) throws XPathExpressionException;
+
+ /**
+ * Parse input of query parameters - start-time or stop-time - from {@link DateAndTime} format
+ * to {@link Instant} format.
+ *
+ * @param uriValue Start-time or stop-time as string in {@link DateAndTime} format.
+ * @return Parsed {@link Instant} by entry.
+ */
+ private static @NonNull Instant parseDateAndTime(final DateAndTime dateAndTime) {
+ final TemporalAccessor accessor;
+ try {
+ accessor = FORMATTER.parse(dateAndTime.getValue());
+ } catch (final DateTimeParseException e) {
+ throw new RestconfDocumentedException("Cannot parse of value in date: " + dateAndTime, e);
+ }
+ return Instant.from(accessor);
}
/**
@SuppressWarnings("checkstyle:IllegalCatch")
<T extends BaseListenerInterface> boolean checkStartStop(final Instant now, final T listener) {
- if (stopTime != null) {
- if (startTime.compareTo(now) < 0 && stopTime.compareTo(now) > 0) {
+ if (stop != null) {
+ if (start.compareTo(now) < 0 && stop.compareTo(now) > 0) {
return true;
}
- if (stopTime.compareTo(now) < 0) {
+ if (stop.compareTo(now) < 0) {
try {
listener.close();
} catch (final Exception e) {
throw new RestconfDocumentedException("Problem with unregister listener." + e);
}
}
- } else if (startTime != null) {
- if (startTime.compareTo(now) < 0) {
- startTime = null;
+ } else if (start != null) {
+ if (start.compareTo(now) < 0) {
+ start = null;
return true;
}
} else {
}
}
- private DataTreeCandidateFormatter getFormatter(final String filter) throws XPathExpressionException {
- final DataTreeCandidateFormatterFactory factory = getFormatterFactory();
- return filter == null || filter.isEmpty() ? factory.getFormatter() : factory.getFormatter(filter);
- }
-
@Override
- public void setQueryParams(final Instant startTime, final Instant stopTime, final String filter,
- final boolean leafNodesOnly, final boolean skipNotificationData) {
- setQueryParams(startTime, stopTime, leafNodesOnly, skipNotificationData);
- try {
- formatter = getFormatter(filter);
- } catch (final XPathExpressionException e) {
- throw new IllegalArgumentException("Failed to get filter", e);
- }
+ final void setFilter(final String filter) throws XPathExpressionException {
+ final DataTreeCandidateFormatterFactory factory = getFormatterFactory();
+ formatter = filter == null || filter.isEmpty() ? factory.getFormatter() : factory.getFormatter(filter);
}
@Override
import java.time.Instant;
import java.util.Optional;
import javax.xml.xpath.XPathExpressionException;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.dom.api.DOMNotification;
import org.opendaylight.mdsal.dom.api.DOMNotificationListener;
import org.opendaylight.mdsal.dom.api.DOMNotificationService;
}
}
- private NotificationFormatter getFormatter(final String filter) throws XPathExpressionException {
- NotificationFormatterFactory factory = getFormatterFactory();
- return filter == null || filter.isEmpty() ? factory.getFormatter() : factory.getFormatter(filter);
- }
-
@Override
- public void setQueryParams(final Instant startTime, final Instant stopTime, final String filter,
- final boolean leafNodesOnly, final boolean skipNotificationData) {
- setQueryParams(startTime, stopTime, leafNodesOnly, skipNotificationData);
- try {
- formatter = getFormatter(filter);
- } catch (XPathExpressionException e) {
- throw new IllegalArgumentException("Failed to get filter", e);
- }
+ final void setFilter(final @Nullable String filter) throws XPathExpressionException {
+ final NotificationFormatterFactory factory = getFormatterFactory();
+ formatter = filter == null || filter.isEmpty() ? factory.getFormatter() : factory.getFormatter(filter);
}
/**
*/
package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
-import static java.time.Instant.EPOCH;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.restconf.nb.rfc8040.StartTimeParameter;
import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchCont;
import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1;
final NotificationOutputTypeGrouping.NotificationOutputType outputType,
final boolean leafNodesOnly, final boolean skipNotificationData) {
super(path, streamName, outputType);
- setQueryParams(EPOCH, null, null, leafNodesOnly, skipNotificationData);
+ setQueryParams(StartTimeParameter.forUriValue("1970-01-01T00:00:00Z"), null, null, leafNodesOnly,
+ skipNotificationData);
}
@Override
protected void post(final String data) {
- this.lastNotification = data;
+ lastNotification = data;
notificationLatch.countDown();
}
LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
JSONAssert.assertEquals(json, withFakeDate, false);
- this.lastNotification = null;
+ lastNotification = null;
notificationLatch = new CountDownLatch(1);
}
- public void assertXmlSimilar(String xml) {
+ public void assertXmlSimilar(final String xml) {
awaitUntillNotification(xml);
LOG.info("lastNotification: {}", lastNotification);
LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
- this.lastNotification = null;
+ lastNotification = null;
notificationLatch = new CountDownLatch(1);
}
- public String awaitUntillNotification(String xml) {
+ public String awaitUntillNotification(final String xml) {
// FIXME: use awaitility
if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
fail("Timed out waiting for notification for: " + xml);
return doc.toString();
}
- static String withFakeXmlDate(String in) {
+ static String withFakeXmlDate(final String in) {
return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
}