import static java.util.Objects.requireNonNull;
import com.google.common.base.Preconditions;
+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.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import javax.xml.xpath.XPathExpressionException;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.checkerframework.checker.lock.qual.Holding;
+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.NotificationQueryParams;
import org.opendaylight.restconf.nb.rfc8040.streams.StreamSessionHandler;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
import org.opendaylight.yangtools.concepts.Registration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Features of subscribing part of both notifications.
*/
-abstract class AbstractCommonSubscriber extends AbstractQueryParams implements BaseListenerInterface {
+abstract class AbstractCommonSubscriber extends AbstractNotificationsData implements BaseListenerInterface {
private static final Logger LOG = LoggerFactory.getLogger(AbstractCommonSubscriber.class);
+ 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();
@GuardedBy("this")
private final Set<StreamSessionHandler> subscribers = new HashSet<>();
@GuardedBy("this")
private Registration registration;
+ // FIXME: these should be final
+ private Instant start = null;
+ private Instant stop = null;
+ private boolean leafNodesOnly = false;
+ private boolean skipNotificationData = false;
+
@Override
public final synchronized boolean hasSubscribers() {
- return !this.subscribers.isEmpty();
+ return !subscribers.isEmpty();
}
@Override
public final synchronized Set<StreamSessionHandler> getSubscribers() {
- return new HashSet<>(this.subscribers);
+ return new HashSet<>(subscribers);
}
@Override
public final synchronized void close() throws InterruptedException, ExecutionException {
- if (this.registration != null) {
- this.registration.close();
- this.registration = null;
+ if (registration != null) {
+ registration.close();
+ registration = null;
}
deleteDataInDS().get();
- this.subscribers.clear();
+ subscribers.clear();
}
@Override
}
}
+ public final Instant getStart() {
+ return start;
+ }
+
+ /**
+ * Set query parameters for listener.
+ *
+ * @param params NotificationQueryParams to use.
+ */
+ public final void setQueryParams(final NotificationQueryParams params) {
+ final var startTime = params.startTime();
+ start = startTime == null ? Instant.now() : parseDateAndTime(startTime.value());
+
+ final var stopTime = params.stopTime();
+ stop = stopTime == null ? null : parseDateAndTime(stopTime.value());
+
+ final var leafNodes = params.leafNodesOnly();
+ leafNodesOnly = leafNodes == null ? false : leafNodes.value();
+
+ final var skipData = params.skipNotificationData();
+ skipNotificationData = skipData == null ? false : skipData.value();
+
+ final var filter = params.filter();
+ if (filter != null) {
+ try {
+ setFilter(filter.paramValue());
+ } catch (XPathExpressionException e) {
+ throw new IllegalArgumentException("Failed to get filter", e);
+ }
+ }
+ }
+
+ abstract void setFilter(@Nullable String xpathString) throws XPathExpressionException;
+
+ /**
+ * Check whether this query should only notify about leaf node changes.
+ *
+ * @return true if this query should only notify about leaf node changes
+ */
+ final boolean getLeafNodesOnly() {
+ return leafNodesOnly;
+ }
+
+ /**
+ * Check whether this query should notify changes without data.
+ *
+ * @return true if this query should notify about changes with data
+ */
+ final boolean isSkipNotificationData() {
+ return skipNotificationData;
+ }
+
/**
* Sets {@link Registration} registration.
*
}
}
}
+
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ final boolean checkStartStop(final Instant now) {
+ if (stop != null) {
+ if (start.compareTo(now) < 0 && stop.compareTo(now) > 0) {
+ return true;
+ }
+ if (stop.compareTo(now) < 0) {
+ try {
+ close();
+ } catch (final Exception e) {
+ throw new RestconfDocumentedException("Problem with unregister listener." + e);
+ }
+ }
+ } else if (start != null) {
+ if (start.compareTo(now) < 0) {
+ start = null;
+ return true;
+ }
+ } else {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 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);
+ }
}