This refactors the interace a bit and provides a basic evaluator.
The idea is that given a root object it will evaluate the query.
JIRA: MDSAL-605
Change-Id: Ifcf8880d502eed93fa243a2c1e2f50a116306fe5
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
package org.opendaylight.mdsal.binding.api.query;
import com.google.common.annotations.Beta;
-import com.google.common.util.concurrent.ListenableFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.opendaylight.yangtools.yang.binding.DataObject;
@NonNullByDefault
public interface QueryExecutor {
- <T extends DataObject> ListenableFuture<? extends QueryResult<T>> executeQuery(QueryExpression<T> query);
+ <T extends DataObject> QueryResult<T> executeQuery(QueryExpression<T> query);
}
--- /dev/null
+/*
+ * Copyright (c) 2020 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.mdsal.binding.dom.adapter.query;
+
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Function;
+import com.google.common.base.VerifyException;
+import com.google.common.collect.Lists;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Spliterator;
+import java.util.stream.Stream;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.gaul.modernizer_maven_annotations.SuppressModernizer;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+@NonNullByDefault
+@SuppressModernizer
+final class DefaultQueryResult<T extends DataObject>
+ implements QueryResult<T>, Function<Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>, QueryResult.Item<T>> {
+ private static final VarHandle ITEM_CODEC;
+
+ static {
+ try {
+ ITEM_CODEC = MethodHandles.lookup().findVarHandle(DefaultQueryResult.class,
+ "itemCodec", BindingDataObjectCodecTreeNode.class);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private final List<? extends Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> domResult;
+ private final BindingCodecTree codec;
+
+ @SuppressWarnings("unused")
+ @SuppressFBWarnings(value = "NP_STORE_INTO_NONNULL_FIELD", justification = "Ungrokked type annotation")
+ private volatile @Nullable BindingDataObjectCodecTreeNode<T> itemCodec = null;
+
+ DefaultQueryResult(final BindingCodecTree codec,
+ final List<? extends Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> domResult) {
+ this.codec = requireNonNull(codec);
+ this.domResult = requireNonNull(domResult);
+ }
+
+ @Override
+ public Spliterator<? extends Item<T>> spliterator() {
+ return Lists.transform(domResult, this).spliterator();
+ }
+
+ @Override
+ public Stream<? extends Item<T>> stream() {
+ return domResult.stream().map(this);
+ }
+
+ @Override
+ public Stream<? extends Item<T>> parallelStream() {
+ return domResult.parallelStream().map(this);
+ }
+
+ @Override
+ public Item<T> apply(final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> domItem) {
+ return new DefaultQueryResultItem<>(this, domItem);
+ }
+
+ T createObject(final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> domItem) {
+ final @Nullable BindingDataObjectCodecTreeNode<T> local =
+ (BindingDataObjectCodecTreeNode<T>) ITEM_CODEC.getAcquire(this);
+ return (local != null ? local : loadItemCodec(domItem.getKey())).deserialize(domItem.getValue());
+ }
+
+ InstanceIdentifier<T> createPath(final YangInstanceIdentifier domPath) {
+ return verifyNotNull(codec.getInstanceIdentifierCodec().toBinding(domPath), "path");
+ }
+
+ private BindingDataObjectCodecTreeNode<T> loadItemCodec(final YangInstanceIdentifier domPath) {
+ final BindingCodecTreeNode codecNode = codec.getSubtreeCodec(domPath);
+ if (!(codecNode instanceof BindingDataObjectCodecTreeNode)) {
+ throw new VerifyException("Unexpected codec " + codecNode);
+ }
+
+ @SuppressWarnings("unchecked")
+ final BindingDataObjectCodecTreeNode<T> ret = (BindingDataObjectCodecTreeNode<T>) codecNode;
+ final Object witness = ITEM_CODEC.compareAndExchangeRelease(this, null, ret);
+ return witness == null ? ret : (BindingDataObjectCodecTreeNode<T>) witness;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2020 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.mdsal.binding.dom.adapter.query;
+
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.VarHandle;
+import java.util.Map.Entry;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+@NonNullByDefault
+final class DefaultQueryResultItem<T extends DataObject> implements QueryResult.Item<T> {
+ private static final VarHandle OBJECT;
+ private static final VarHandle PATH;
+
+ static {
+ final Lookup lookup = MethodHandles.lookup();
+ try {
+ OBJECT = lookup.findVarHandle(DefaultQueryResultItem.class, "object", DataObject.class);
+ PATH = lookup.findVarHandle(DefaultQueryResultItem.class, "path", InstanceIdentifier.class);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> domItem;
+ private final DefaultQueryResult<T> result;
+
+ @SuppressWarnings("unused")
+ @SuppressFBWarnings(value = "NP_STORE_INTO_NONNULL_FIELD", justification = "Ungrokked type annotation")
+ private volatile @Nullable InstanceIdentifier<T> path = null;
+ @SuppressWarnings("unused")
+ @SuppressFBWarnings(value = "NP_STORE_INTO_NONNULL_FIELD", justification = "Ungrokked type annotation")
+ private volatile @Nullable T object = null;
+
+ DefaultQueryResultItem(final DefaultQueryResult<T> result,
+ final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> domItem) {
+ this.result = requireNonNull(result);
+ this.domItem = requireNonNull(domItem);
+ }
+
+ @Override
+ public T object() {
+ final @Nullable T local = (T) OBJECT.getAcquire(this);
+ return local != null ? local : loadObject();
+ }
+
+ @Override
+ public InstanceIdentifier<T> path() {
+ final @Nullable InstanceIdentifier<T> local = (InstanceIdentifier<T>) PATH.getAcquire(this);
+ return local != null ? local : loadPath();
+ }
+
+ private T loadObject() {
+ final T ret = result.createObject(domItem);
+ final Object witness = OBJECT.compareAndExchangeRelease(this, null, ret);
+ return witness == null ? ret : (T) witness;
+ }
+
+ @SuppressWarnings("unchecked")
+ private InstanceIdentifier<T> loadPath() {
+ final InstanceIdentifier<T> ret = result.createPath(domItem.getKey());
+ final Object witness = PATH.compareAndExchangeRelease(this, null, ret);
+ return witness == null ? ret : (InstanceIdentifier<T>) witness;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2020 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.mdsal.binding.dom.adapter.query;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.query.QueryExecutor;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
+import org.opendaylight.mdsal.dom.api.query.DOMQuery;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryLike;
+import org.opendaylight.mdsal.dom.spi.query.DOMQueryEvaluator;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.DataRoot;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+@Beta
+public final class SimpleQueryExecutor implements QueryExecutor {
+ private final BindingCodecTree codec;
+ private final NormalizedNode<?, ?> root;
+
+ public SimpleQueryExecutor(final BindingCodecTree codec, final NormalizedNode<?, ?> root) {
+ this.codec = requireNonNull(codec);
+ this.root = requireNonNull(root);
+ }
+
+ @Override
+ public <T extends @NonNull DataObject> QueryResult<T> executeQuery(final QueryExpression<T> query) {
+ checkArgument(query instanceof DOMQueryLike, "Unsupported expression %s", query);
+ final DOMQuery domQuery = ((DOMQueryLike) query).asDOMQuery();
+ return new DefaultQueryResult<>(codec, DOMQueryEvaluator.evaluate(domQuery, root));
+ }
+
+ public static @NonNull Builder builder(final BindingCodecTree codec) {
+ return new Builder(codec);
+ }
+
+ public static final class Builder implements org.opendaylight.yangtools.concepts.Builder<SimpleQueryExecutor> {
+ private final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> rootBuilder = Builders.containerBuilder()
+ .withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME));
+ private final BindingCodecTree codec;
+
+ Builder(final BindingCodecTree codec) {
+ this.codec = requireNonNull(codec);
+ }
+
+ public <T extends ChildOf<? extends DataRoot>> @NonNull Builder add(final @NonNull T data) {
+ @SuppressWarnings("unchecked")
+ final BindingDataObjectCodecTreeNode<T> dataCodec = (BindingDataObjectCodecTreeNode<T>)
+ codec.getSubtreeCodec(InstanceIdentifier.create(data.implementedInterface()));
+ rootBuilder.withChild((DataContainerChild<?, ?>) verifyNotNull(dataCodec).serialize(data));
+ return this;
+ }
+
+ @Override
+ public SimpleQueryExecutor build() {
+ return new SimpleQueryExecutor(codec, rootBuilder.build());
+ }
+ }
+}
*/
package org.opendaylight.mdsal.binding.dom.adapter.query;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.base.Stopwatch;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.opendaylight.mdsal.binding.api.query.QueryExecutor;
import org.opendaylight.mdsal.binding.api.query.QueryExpression;
import org.opendaylight.mdsal.binding.api.query.QueryFactory;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
+import org.opendaylight.mdsal.binding.api.query.QueryResult.Item;
import org.opendaylight.mdsal.binding.api.query.QueryStructureException;
import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
import org.opendaylight.mdsal.binding.dom.codec.impl.DefaultBindingCodecTreeFactory;
import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
+import org.opendaylight.yang.gen.v1.mdsal.query.norev.Foo;
+import org.opendaylight.yang.gen.v1.mdsal.query.norev.FooBuilder;
+import org.opendaylight.yang.gen.v1.mdsal.query.norev.first.grp.System;
+import org.opendaylight.yang.gen.v1.mdsal.query.norev.first.grp.SystemBuilder;
+import org.opendaylight.yang.gen.v1.mdsal.query.norev.second.grp.Alarms;
+import org.opendaylight.yang.gen.v1.mdsal.query.norev.second.grp.AlarmsBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.Top;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.top.level.list.NestedList;
+import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.util.BindingMap;
+import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.Uint64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class QueryBuilderTest {
+ private static final Logger LOG = LoggerFactory.getLogger(QueryBuilderTest.class);
private static BindingCodecTree CODEC;
+ private static QueryExecutor EXECUTOR;
private final QueryFactory factory = new DefaultQueryFactory(CODEC);
@BeforeClass
public static void beforeClass() {
CODEC = new DefaultBindingCodecTreeFactory().create(BindingRuntimeHelpers.createRuntimeContext());
+ EXECUTOR = SimpleQueryExecutor.builder(CODEC)
+ .add(new FooBuilder()
+ .setSystem(BindingMap.of(
+ new SystemBuilder().setName("first").setAlarms(BindingMap.of(
+ new AlarmsBuilder()
+ .setId(Uint64.ZERO)
+ .setCritical(Empty.getInstance())
+ .setAffectedUsers(BindingMap.of(
+ // TODO: fill
+ )).build(),
+ new AlarmsBuilder()
+ .setId(Uint64.ONE)
+ .setAffectedUsers(BindingMap.of(
+ // TODO: fill
+ )).build(),
+ new AlarmsBuilder()
+ .setId(Uint64.TWO)
+ .setCritical(Empty.getInstance())
+ .setAffectedUsers(BindingMap.of(
+ // TODO: fill
+ )).build())).build(),
+ new SystemBuilder().setName("second").setAlarms(BindingMap.of(
+ new AlarmsBuilder()
+ .setId(Uint64.ZERO)
+ .setCritical(Empty.getInstance())
+ .setAffectedUsers(BindingMap.of(
+ // TODO: fill
+ )).build())).build()
+ ))
+ .build())
+ .build();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ CODEC = null;
+ EXECUTOR = null;
}
@Test
public void bar() throws QueryStructureException {
- final QueryExpression<TopLevelList> query = factory.querySubtree(InstanceIdentifier.builder(Top.class).build())
+ final Stopwatch sw = Stopwatch.createStarted();
+ final QueryExpression<TopLevelList> query = factory.querySubtree(InstanceIdentifier.create(Top.class))
.extractChild(TopLevelList.class)
.matching()
.childObject(NestedList.class)
.leaf(NestedList::getName).contains("foo")
.and().leaf(TopLevelList::getName).valueEquals("bar")
.build();
-//
-// // Execution start
-// final Result<TopLevelList> result = query.getResult();
-// // Execution fetch
-// final TopLevelList value = result.getValue();
+ LOG.info("Query built in {}", sw);
+
+ assertEquals(0, execute(query).getItems().size());
+ }
+
+ @Test
+ public void testFindCriticalAlarms() throws QueryStructureException {
+ final Stopwatch sw = Stopwatch.createStarted();
+ final QueryExpression<Alarms> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
+ .extractChild(System.class)
+ .extractChild(Alarms.class)
+ .matching()
+ .leaf(Alarms::getCritical).nonNull()
+ .build();
+ LOG.info("Query built in {}", sw);
+
+ final List<? extends Item<@NonNull Alarms>> items = execute(query).getItems();
+ assertEquals(3, items.size());
+ }
+
+ @Test
+ public void testFindNonCriticalAlarms() throws QueryStructureException {
+ final Stopwatch sw = Stopwatch.createStarted();
+ final QueryExpression<Alarms> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
+ .extractChild(System.class)
+ .extractChild(Alarms.class)
+ .matching()
+ .leaf(Alarms::getCritical).isNull()
+ .build();
+ LOG.info("Query built in {}", sw);
+
+ final List<? extends Item<@NonNull Alarms>> items = execute(query).getItems();
+ assertEquals(1, items.size());
+ }
+
+ @Test
+ public void testFindZeroAlarms() throws QueryStructureException {
+ final Stopwatch sw = Stopwatch.createStarted();
+ final QueryExpression<Alarms> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
+ .extractChild(System.class)
+ .extractChild(Alarms.class)
+ .matching()
+ .leaf(Alarms::getId).valueEquals(Uint64.ZERO)
+ .build();
+ LOG.info("Query built in {}", sw);
+
+ final List<? extends Item<@NonNull Alarms>> items = execute(query).getItems();
+ assertEquals(2, items.size());
+ }
+
+ private static <T extends @NonNull DataObject> QueryResult<T> execute(final QueryExpression<T> query) {
+ final Stopwatch sw = Stopwatch.createStarted();
+ final QueryResult<T> result = EXECUTOR.executeQuery(query);
+ LOG.info("Query executed in {}", sw);
+ return result;
}
}