private Registration registration;
// FIXME: these should be final
+ private @NonNull EventFormatter<T> formatter;
private Instant start = null;
private Instant stop = null;
- private boolean leafNodesOnly = false;
- private boolean skipNotificationData = false;
- private boolean changedLeafNodesOnly = false;
- private boolean childNodesOnly = false;
- private EventFormatter<T> formatter;
AbstractCommonSubscriber(final String streamName, final NotificationOutputType outputType,
final EventFormatterFactory<T> formatterFactory) {
this.outputType = requireNonNull(outputType);
this.formatterFactory = requireNonNull(formatterFactory);
- formatter = formatterFactory.getFormatter();
+ formatter = formatterFactory.emptyFormatter();
}
@Override
stop = stopTime == null ? null : parseDateAndTime(stopTime.value());
final var leafNodes = params.leafNodesOnly();
- leafNodesOnly = leafNodes != null && leafNodes.value();
-
final var skipData = params.skipNotificationData();
- skipNotificationData = skipData != null && skipData.value();
-
final var changedLeafNodes = params.changedLeafNodesOnly();
- changedLeafNodesOnly = changedLeafNodes != null && changedLeafNodes.value();
-
final var childNodes = params.childNodesOnly();
- childNodesOnly = childNodes != null && childNodes.value();
+
+ final var textParams = new TextParameters(
+ leafNodes != null && leafNodes.value(),
+ skipData != null && skipData.value(),
+ changedLeafNodes != null && changedLeafNodes.value(),
+ childNodes != null && childNodes.value());
final var filter = params.filter();
- final String filterValue = filter == null ? null : filter.paramValue();
+ final var filterValue = filter == null ? null : filter.paramValue();
+
+ final EventFormatter<T> newFormatter;
if (filterValue != null && !filterValue.isEmpty()) {
try {
- formatter = formatterFactory.getFormatter(filterValue);
+ newFormatter = formatterFactory.getFormatter(textParams, filterValue);
} catch (XPathExpressionException e) {
throw new IllegalArgumentException("Failed to get filter", e);
}
} else {
- formatter = formatterFactory.getFormatter();
+ newFormatter = formatterFactory.getFormatter(textParams);
}
- }
-
- /**
- * 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 only notify about leaf node changes and report only changed nodes.
- *
- * @return true if this query should only notify about leaf node changes and report only changed nodes
- */
- final boolean getChangedLeafNodesOnly() {
- return changedLeafNodesOnly;
- }
-
- /**
- * 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;
- }
- /**
- * Check whether this query should only notify about child node changes.
- *
- * @return true if this query should only notify about child node changes
- */
- final boolean getChildNodesOnly() {
- return childNodesOnly;
+ // Single assign
+ formatter = newFormatter;
}
- final EventFormatter<T> formatter() {
+ final @NonNull EventFormatter<T> formatter() {
return formatter;
}
final Optional<String> maybeOutput;
try {
- maybeOutput = formatter().eventData(effectiveModel(), notification, eventInstant, getLeafNodesOnly(),
- isSkipNotificationData(), getChangedLeafNodesOnly(), getChildNodesOnly());
+ maybeOutput = formatter().eventData(effectiveModel(), notification, eventInstant);
} catch (Exception e) {
LOG.error("Failed to process notification {}", notification, e);
return;
this.context = requireNonNull(context);
}
- public final boolean serialize(final DataTreeCandidate candidate, final boolean leafNodesOnly,
- final boolean skipData, final boolean changedLeafNodesOnly, final boolean childNodesOnly) throws T {
- if (leafNodesOnly || changedLeafNodesOnly) {
+ public final boolean serialize(final DataTreeCandidate candidate, final TextParameters params) throws T {
+ final var skipData = params.skipData();
+ final var changedLeafNodesOnly = params.changedLeafNodesOnly();
+ if (changedLeafNodesOnly || params.leafNodesOnly()) {
return serializeLeafNodesOnly(mutableRootPath(candidate), candidate.getRootNode(), skipData,
changedLeafNodesOnly);
}
- if (childNodesOnly) {
+ if (params.childNodesOnly()) {
serializeChildNodesOnly(mutableRootPath(candidate), candidate.getRootNode(), skipData);
return true;
}
* Base formatter for DataTreeCandidates which only handles exporting to a document for filter checking purpose.
*/
abstract class DataTreeCandidateFormatter extends EventFormatter<Collection<DataTreeCandidate>> {
- DataTreeCandidateFormatter() {
-
+ DataTreeCandidateFormatter(final TextParameters textParams) {
+ super(textParams);
}
- DataTreeCandidateFormatter(final String xpathFilter) throws XPathExpressionException {
- super(xpathFilter);
+ DataTreeCandidateFormatter(final TextParameters textParams, final String xpathFilter)
+ throws XPathExpressionException {
+ super(textParams, xpathFilter);
}
@Override
package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
import java.util.Collection;
-import javax.xml.xpath.XPathExpressionException;
import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
-interface DataTreeCandidateFormatterFactory extends EventFormatterFactory<Collection<DataTreeCandidate>> {
-
- @Override
- DataTreeCandidateFormatter getFormatter();
-
- @Override
- DataTreeCandidateFormatter getFormatter(String xpathFilter) throws XPathExpressionException;
+abstract class DataTreeCandidateFormatterFactory extends EventFormatterFactory<Collection<DataTreeCandidate>> {
+ DataTreeCandidateFormatterFactory(final DataTreeCandidateFormatter emptyFormatter) {
+ super(emptyFormatter);
+ }
}
*/
package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
+import static java.util.Objects.requireNonNull;
+
import java.io.IOException;
import java.time.Instant;
import java.time.OffsetDateTime;
DBF = f;
}
+ private final TextParameters textParams;
private final XPathExpression filter;
- EventFormatter() {
+ EventFormatter(final TextParameters textParams) {
+ this.textParams = requireNonNull(textParams);
filter = null;
}
- EventFormatter(final String xpathFilter) throws XPathExpressionException {
+ EventFormatter(final TextParameters params, final String xpathFilter) throws XPathExpressionException {
+ textParams = requireNonNull(params);
+
final XPath xpath;
synchronized (XPF) {
xpath = XPF.newXPath();
filter = xpath.compile(xpathFilter);
}
- final Optional<String> eventData(final EffectiveModelContext schemaContext, final T input, final Instant now,
- final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
- final boolean childNodeOnly) throws Exception {
+ final Optional<String> eventData(final EffectiveModelContext schemaContext, final T input, final Instant now)
+ throws Exception {
if (!filterMatches(schemaContext, input, now)) {
return Optional.empty();
}
- return Optional.ofNullable(
- createText(schemaContext, input, now, leafNodesOnly, skipData, changedLeafNodesOnly, childNodeOnly));
+ return Optional.ofNullable(createText(textParams, schemaContext, input, now));
}
/**
/**
* Format the input data into string representation of the data provided.
*
+ * @param params output text parameters
* @param schemaContext context to use for the export
* @param input input data
* @param now time the event happened
- * @param leafNodesOnly option to include only leaves in the result
- * @param skipData option to skip data in the result, only paths would be included
- * @param changedLeafNodesOnly option to include only changed leaves in the result
- * @param childNodesOnly option to include only children in the result
* @return String representation of the formatted data
* @throws Exception if the underlying formatters fail to export the data to the requested format
*/
- abstract String createText(EffectiveModelContext schemaContext, T input, Instant now, boolean leafNodesOnly,
- boolean skipData, boolean changedLeafNodesOnly, boolean childNodeOnly) throws Exception;
+ abstract String createText(TextParameters params, EffectiveModelContext schemaContext, T input, Instant now)
+ throws Exception;
private boolean filterMatches(final EffectiveModelContext schemaContext, final T input, final Instant now)
throws IOException {
*/
package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
+import static java.util.Objects.requireNonNull;
+
import javax.xml.xpath.XPathExpressionException;
+import org.eclipse.jdt.annotation.NonNull;
+
+abstract class EventFormatterFactory<T> {
+ private final @NonNull EventFormatter<T> emptyFormatter;
+
+ EventFormatterFactory(final EventFormatter<T> emptyFormatter) {
+ this.emptyFormatter = requireNonNull(emptyFormatter);
+ }
+
+ final @NonNull EventFormatter<T> emptyFormatter() {
+ return emptyFormatter;
+ }
+
+ final @NonNull EventFormatter<T> getFormatter(final @NonNull TextParameters textParamaters) {
+ return textParamaters.equals(TextParameters.EMPTY) ? emptyFormatter : newFormatter(textParamaters);
+ }
+
+ abstract @NonNull EventFormatter<T> getFormatter(@NonNull TextParameters textParamaters, String xpathFilter)
+ throws XPathExpressionException;
-interface EventFormatterFactory<T> {
+ abstract @NonNull EventFormatter<T> newFormatter(@NonNull TextParameters textParamaters);
- EventFormatter<T> getFormatter();
- EventFormatter<T> getFormatter(String xpathFilter) throws XPathExpressionException;
}
public static final String NETCONF_NOTIFICATION_NAMESPACE = "urn-ietf-params-xml-ns-netconf-notification-1.0";
private final JSONCodecFactorySupplier codecSupplier;
- private JSONDataTreeCandidateFormatter(final JSONCodecFactorySupplier codecSupplier) {
+ private JSONDataTreeCandidateFormatter(final TextParameters textParams,
+ final JSONCodecFactorySupplier codecSupplier) {
+ super(textParams);
this.codecSupplier = requireNonNull(codecSupplier);
}
- private JSONDataTreeCandidateFormatter(final String xpathFilter, final JSONCodecFactorySupplier codecSupplier)
- throws XPathExpressionException {
- super(xpathFilter);
+ private JSONDataTreeCandidateFormatter(final TextParameters textParams, final String xpathFilter,
+ final JSONCodecFactorySupplier codecSupplier) throws XPathExpressionException {
+ super(textParams, xpathFilter);
this.codecSupplier = requireNonNull(codecSupplier);
}
public static DataTreeCandidateFormatterFactory createFactory(
final JSONCodecFactorySupplier codecSupplier) {
- requireNonNull(codecSupplier);
- return new DataTreeCandidateFormatterFactory() {
+ final var empty = new JSONDataTreeCandidateFormatter(TextParameters.EMPTY, codecSupplier);
+ return new DataTreeCandidateFormatterFactory(empty) {
@Override
- public DataTreeCandidateFormatter getFormatter(final String xpathFilter)
- throws XPathExpressionException {
- return new JSONDataTreeCandidateFormatter(xpathFilter, codecSupplier);
+ DataTreeCandidateFormatter newFormatter(final TextParameters textParams) {
+ return new JSONDataTreeCandidateFormatter(textParams, codecSupplier);
}
@Override
- public DataTreeCandidateFormatter getFormatter() {
- return new JSONDataTreeCandidateFormatter(codecSupplier);
+ DataTreeCandidateFormatter getFormatter(final TextParameters textParams, final String xpathFilter)
+ throws XPathExpressionException {
+ return new JSONDataTreeCandidateFormatter(textParams, xpathFilter, codecSupplier);
}
};
}
@Override
- String createText(final EffectiveModelContext schemaContext, final Collection<DataTreeCandidate> input,
- final Instant now, final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
- final boolean childNodesOnly) throws IOException {
+ String createText(final TextParameters params, final EffectiveModelContext schemaContext,
+ final Collection<DataTreeCandidate> input, final Instant now) throws IOException {
final Writer writer = new StringWriter();
final JsonWriter jsonWriter = new JsonWriter(writer).beginObject();
final var serializer = new JsonDataTreeCandidateSerializer(schemaContext, codecSupplier, jsonWriter);
boolean nonEmpty = false;
for (var candidate : input) {
- nonEmpty |= serializer.serialize(candidate, leafNodesOnly, skipData, changedLeafNodesOnly, childNodesOnly);
+ nonEmpty |= serializer.serialize(candidate, params);
}
// data-change-event
final class JSONNotificationFormatter extends NotificationFormatter {
private final JSONCodecFactorySupplier codecSupplier;
- private JSONNotificationFormatter(final JSONCodecFactorySupplier codecSupplier) {
+ private JSONNotificationFormatter(final TextParameters textParams, final JSONCodecFactorySupplier codecSupplier) {
+ super(textParams);
this.codecSupplier = requireNonNull(codecSupplier);
}
- private JSONNotificationFormatter(final String xpathFilter, final JSONCodecFactorySupplier codecSupplier)
- throws XPathExpressionException {
- super(xpathFilter);
+ private JSONNotificationFormatter(final TextParameters textParams, final String xpathFilter,
+ final JSONCodecFactorySupplier codecSupplier) throws XPathExpressionException {
+ super(textParams, xpathFilter);
this.codecSupplier = requireNonNull(codecSupplier);
}
static NotificationFormatterFactory createFactory(final JSONCodecFactorySupplier codecSupplier) {
- requireNonNull(codecSupplier);
- return new NotificationFormatterFactory() {
+ final var empty = new JSONNotificationFormatter(TextParameters.EMPTY, codecSupplier);
+ return new NotificationFormatterFactory(empty) {
@Override
- public JSONNotificationFormatter getFormatter(final String xpathFilter)
+ JSONNotificationFormatter getFormatter(final TextParameters textParams, final String xpathFilter)
throws XPathExpressionException {
- return new JSONNotificationFormatter(xpathFilter, codecSupplier);
+ return new JSONNotificationFormatter(textParams, xpathFilter, codecSupplier);
}
@Override
- public JSONNotificationFormatter getFormatter() {
- return new JSONNotificationFormatter(codecSupplier);
+ JSONNotificationFormatter newFormatter(final TextParameters textParams) {
+ return new JSONNotificationFormatter(textParams, codecSupplier);
}
};
}
@Override
- String createText(final EffectiveModelContext schemaContext, final DOMNotification input, final Instant now,
- final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
- final boolean childNodesOnly) throws IOException {
+ String createText(final TextParameters params, final EffectiveModelContext schemaContext,
+ final DOMNotification input, final Instant now) throws IOException {
final Writer writer = new StringWriter();
final JsonWriter jsonWriter = new JsonWriter(writer).beginObject();
jsonWriter.name("ietf-restconf:notification").beginObject();
final Optional<String> maybeData;
try {
- maybeData = formatter().eventData(databindProvider.currentContext().modelContext(), dataTreeCandidates, now,
- getLeafNodesOnly(), isSkipNotificationData(), getChangedLeafNodesOnly(), getChildNodesOnly());
+ maybeData = formatter().eventData(databindProvider.currentContext().modelContext(), dataTreeCandidates,
+ now);
} catch (final Exception e) {
LOG.error("Failed to process notification {}",
dataTreeCandidates.stream().map(Object::toString).collect(Collectors.joining(",")), e);
XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
}
- NotificationFormatter() {
-
+ NotificationFormatter(final TextParameters textParams) {
+ super(textParams);
}
- NotificationFormatter(final String xpathFilter) throws XPathExpressionException {
- super(xpathFilter);
+ NotificationFormatter(final TextParameters textParams, final String xpathFilter) throws XPathExpressionException {
+ super(textParams, xpathFilter);
}
@Override
*/
package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
-import javax.xml.xpath.XPathExpressionException;
import org.opendaylight.mdsal.dom.api.DOMNotification;
-interface NotificationFormatterFactory extends EventFormatterFactory<DOMNotification> {
- @Override
- NotificationFormatter getFormatter();
-
- @Override
- NotificationFormatter getFormatter(String xpathFilter) throws XPathExpressionException;
+abstract class NotificationFormatterFactory extends EventFormatterFactory<DOMNotification> {
+ NotificationFormatterFactory(final EventFormatter<DOMNotification> emptyFormatter) {
+ super(emptyFormatter);
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Text formatting parameters.
+ *
+ * @param leafNodesOnly {@code true} if this query should only notify about leaf node changes
+ * @param skipData {@code true} if this query should notify about changes with data
+ * @param changedLeafNodesOnly {@code true} if this query should only notify about leaf node changes and report only
+ * changed nodes
+ * @param childNodesOnly {@code true} if this query should only notify about child node changes
+ */
+@NonNullByDefault
+record TextParameters(boolean leafNodesOnly, boolean skipData, boolean changedLeafNodesOnly, boolean childNodesOnly) {
+ static final TextParameters EMPTY = new TextParameters(false, false, false, false);
+}
\ No newline at end of file
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
public final class XMLDataTreeCandidateFormatter extends DataTreeCandidateFormatter {
- private static final XMLDataTreeCandidateFormatter INSTANCE = new XMLDataTreeCandidateFormatter();
+ private static final XMLDataTreeCandidateFormatter EMPTY = new XMLDataTreeCandidateFormatter(TextParameters.EMPTY);
- static final DataTreeCandidateFormatterFactory FACTORY =
- new DataTreeCandidateFormatterFactory() {
- @Override
- public XMLDataTreeCandidateFormatter getFormatter(final String xpathFilter)
- throws XPathExpressionException {
- return new XMLDataTreeCandidateFormatter(xpathFilter);
- }
-
- @Override
- public XMLDataTreeCandidateFormatter getFormatter() {
- return INSTANCE;
- }
- };
+ static final DataTreeCandidateFormatterFactory FACTORY = new DataTreeCandidateFormatterFactory(EMPTY) {
+ @Override
+ XMLDataTreeCandidateFormatter getFormatter(final TextParameters textParams, final String xpathFilter)
+ throws XPathExpressionException {
+ return new XMLDataTreeCandidateFormatter(textParams, xpathFilter);
+ }
- private XMLDataTreeCandidateFormatter() {
+ @Override
+ XMLDataTreeCandidateFormatter newFormatter(final TextParameters textParams) {
+ return new XMLDataTreeCandidateFormatter(textParams);
+ }
+ };
+ private XMLDataTreeCandidateFormatter(final TextParameters textParams) {
+ super(textParams);
}
- private XMLDataTreeCandidateFormatter(final String xpathFilter) throws XPathExpressionException {
- super(xpathFilter);
+ private XMLDataTreeCandidateFormatter(final TextParameters textParams, final String xpathFilter)
+ throws XPathExpressionException {
+ super(textParams, xpathFilter);
}
@Override
- String createText(final EffectiveModelContext schemaContext, final Collection<DataTreeCandidate> input,
- final Instant now, final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
- final boolean childNodeOnly) throws Exception {
+ String createText(final TextParameters params, final EffectiveModelContext schemaContext,
+ final Collection<DataTreeCandidate> input, final Instant now) throws Exception {
final var writer = new StringWriter();
boolean nonEmpty = false;
try {
final var serializer = new XmlDataTreeCandidateSerializer(schemaContext, xmlStreamWriter);
for (var candidate : input) {
- nonEmpty |= serializer.serialize(candidate, leafNodesOnly, skipData, changedLeafNodesOnly,
- childNodeOnly);
+ nonEmpty |= serializer.serialize(candidate, params);
}
// data-changed-notification
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
final class XMLNotificationFormatter extends NotificationFormatter {
- private static final XMLNotificationFormatter INSTANCE = new XMLNotificationFormatter();
+ private static final XMLNotificationFormatter EMPTY = new XMLNotificationFormatter(TextParameters.EMPTY);
- static final NotificationFormatterFactory FACTORY = new NotificationFormatterFactory() {
+ static final NotificationFormatterFactory FACTORY = new NotificationFormatterFactory(EMPTY) {
@Override
- public XMLNotificationFormatter getFormatter(final String xpathFilter) throws XPathExpressionException {
- return new XMLNotificationFormatter(xpathFilter);
+ XMLNotificationFormatter newFormatter(final TextParameters textParams) {
+ return new XMLNotificationFormatter(textParams);
}
@Override
- public XMLNotificationFormatter getFormatter() {
- return INSTANCE;
+ XMLNotificationFormatter getFormatter(final TextParameters textParams, final String xpathFilter)
+ throws XPathExpressionException {
+ return new XMLNotificationFormatter(textParams, xpathFilter);
}
};
- XMLNotificationFormatter() {
-
+ XMLNotificationFormatter(final TextParameters textParams) {
+ super(textParams);
}
- XMLNotificationFormatter(final String xpathFilter) throws XPathExpressionException {
- super(xpathFilter);
+ XMLNotificationFormatter(final TextParameters textParams, final String xpathFilter)
+ throws XPathExpressionException {
+ super(textParams, xpathFilter);
}
@Override
- String createText(final EffectiveModelContext schemaContext, final DOMNotification input, final Instant now,
- final boolean leafNodesOnly, final boolean skipData, final boolean changedLeafNodesOnly,
- final boolean childNodesOnly) throws IOException {
+ String createText(final TextParameters params, final EffectiveModelContext schemaContext,
+ final DOMNotification input, final Instant now) throws IOException {
final var writer = new StringWriter();
try {
private static String prepareJson(final DOMNotification notificationData, final Absolute schemaPathNotifi)
throws Exception {
- final NotificationListenerAdapter notifiAdapter = ListenersBroker.getInstance().registerNotificationListener(
+ final var notifiAdapter = ListenersBroker.getInstance().registerNotificationListener(
schemaPathNotifi, "json-stream", NotificationOutputType.JSON);
- return notifiAdapter.formatter()
- .eventData(SCHEMA_CONTEXT, notificationData, Instant.now(), false, false, false, false).orElseThrow();
+ return notifiAdapter.formatter().eventData(SCHEMA_CONTEXT, notificationData, Instant.now()).orElseThrow();
}
}
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.mdsal.dom.api.DOMNotification;
-import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping;
+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.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
private static String prepareXmlResult(final DOMNotification notificationData, final Absolute schemaPathNotifi)
throws Exception {
- final NotificationListenerAdapter notifiAdapter = ListenersBroker.getInstance().registerNotificationListener(
- schemaPathNotifi, "xml-stream", NotificationOutputTypeGrouping.NotificationOutputType.XML);
- return notifiAdapter.formatter().eventData(SCHEMA_CONTEXT, notificationData, Instant.now(), false,
- false, false, false).orElseThrow();
+ final var notifiAdapter = ListenersBroker.getInstance().registerNotificationListener(
+ schemaPathNotifi, "xml-stream", NotificationOutputType.XML);
+ return notifiAdapter.formatter().eventData(SCHEMA_CONTEXT, notificationData, Instant.now()).orElseThrow();
}
}