Add SimpleQueryExecutor 15/93315/16
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 23 Oct 2020 20:46:07 +0000 (22:46 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Sat, 24 Oct 2020 22:36:13 +0000 (00:36 +0200)
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>
binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExecutor.java
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/DefaultQueryResult.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/DefaultQueryResultItem.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/SimpleQueryExecutor.java [new file with mode: 0644]
binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/query/QueryBuilderTest.java

index 77fbf348a26deb8c46e8a16f795ec7df1284ca2a..f89a94ffe8710fd01303b662f44a5428733871f5 100644 (file)
@@ -8,7 +8,6 @@
 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;
 
@@ -16,5 +15,5 @@ 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);
 }
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/DefaultQueryResult.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/DefaultQueryResult.java
new file mode 100644 (file)
index 0000000..516a83e
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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;
+    }
+}
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/DefaultQueryResultItem.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/DefaultQueryResultItem.java
new file mode 100644 (file)
index 0000000..cecc684
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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
diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/SimpleQueryExecutor.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/query/SimpleQueryExecutor.java
new file mode 100644 (file)
index 0000000..608dcb3
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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());
+        }
+    }
+}
index 1a0d3ac7dd195af22bda9235520d8330ef44b535..0aa1d1ceb5ace60e6ec88d95d467ed599f94b58e 100644 (file)
  */
 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;
     }
 }