Implement subtree filtering using fields
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / util / NetconfBaseOps.java
index d3bbb95797ed1446fc341fbaa08d8b3c2d91c630..8df170e8fc34f934a93db7c6bae9c6c9d6ef0cb7 100644 (file)
@@ -11,6 +11,7 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.COMMIT_RPC_CONTENT;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.EDIT_CONTENT_NODEID;
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.GET_RPC_CONTENT;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_NODEID;
@@ -37,13 +38,18 @@ import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTr
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toId;
 
+import com.google.common.collect.Iterables;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
@@ -208,27 +214,93 @@ public final class NetconfBaseOps {
         return future;
     }
 
+    private ListenableFuture<? extends DOMRpcResult> getConfig(final FutureCallback<DOMRpcResult> callback,
+            final QName datastore, final Optional<YangInstanceIdentifier> filterPath,
+            final List<YangInstanceIdentifier> fields) {
+        requireNonNull(callback);
+        requireNonNull(datastore);
+
+        final NormalizedNode<?, ?> rpcInput;
+        if (isFilterPresent(filterPath)) {
+            final DataContainerChild<?, ?> node = transformer.toFilterStructure(
+                    Collections.singletonList(FieldsFilter.of(filterPath.get(), fields)));
+            rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, getSourceNode(datastore), node);
+        } else if (containsEmptyPath(fields)) {
+            rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, getSourceNode(datastore));
+        } else {
+            final DataContainerChild<?, ?> subtreeFilter = getSubtreeFilterFromRootFields(fields);
+            rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID,
+                    getSourceNode(datastore), subtreeFilter);
+        }
+        final ListenableFuture<? extends DOMRpcResult> response = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME, rpcInput);
+        Futures.addCallback(response, callback, MoreExecutors.directExecutor());
+        return response;
+    }
+
+    /**
+     * Calling GET-CONFIG RPC with subtree filter that is specified by {@link YangInstanceIdentifier}.
+     *
+     * @param callback   RPC response callback
+     * @param filterPath path to requested data
+     * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
+     */
     public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfigRunningData(
             final FutureCallback<DOMRpcResult> callback, final Optional<YangInstanceIdentifier> filterPath) {
         return extractData(filterPath, getConfigRunning(callback, filterPath));
     }
 
+    /**
+     * Calling GET-CONFIG RPC with subtree filter tha tis specified by parent {@link YangInstanceIdentifier} and list
+     * of specific fields that caller would like to read. Field paths are relative to parent path.
+     *
+     * @param callback   RPC response callback
+     * @param filterPath parent path to requested data
+     * @param fields     paths to specific fields that are selected under parent path
+     * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
+     */
     public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfigRunningData(
             final FutureCallback<DOMRpcResult> callback, final Optional<YangInstanceIdentifier> filterPath,
             final List<YangInstanceIdentifier> fields) {
-        // FIXME: implement this method
-        throw new UnsupportedOperationException();
+        if (fields.isEmpty()) {
+            // RFC doesn't allow to build subtree filter that would expect just empty element in response
+            return Futures.immediateFailedFuture(new IllegalArgumentException(
+                "Failed to build NETCONF GET-CONFIG RPC: provided list of fields is empty; filter path: "
+                    + filterPath));
+        }
+        final ListenableFuture<? extends DOMRpcResult> response = getConfigRunning(callback, filterPath, fields);
+        return extractData(filterPath, response);
     }
 
+    /**
+     * Calling GET RPC with subtree filter that is specified by {@link YangInstanceIdentifier}.
+     *
+     * @param callback   RPC response callback
+     * @param filterPath path to requested data
+     * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
+     */
     public ListenableFuture<Optional<NormalizedNode<?, ?>>> getData(final FutureCallback<DOMRpcResult> callback,
                                                                     final Optional<YangInstanceIdentifier> filterPath) {
         return extractData(filterPath, get(callback, filterPath));
     }
 
+    /**
+     * Calling GET RPC with subtree filter tha tis specified by parent {@link YangInstanceIdentifier} and list
+     * of specific fields that caller would like to read. Field paths are relative to parent path.
+     *
+     * @param callback   RPC response callback
+     * @param filterPath parent path to requested data
+     * @param fields     paths to specific fields that are selected under parent path
+     * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
+     */
     public ListenableFuture<Optional<NormalizedNode<?, ?>>> getData(final FutureCallback<DOMRpcResult> callback,
-            final Optional<YangInstanceIdentifier> path, final List<YangInstanceIdentifier> fields) {
-        // FIXME: implement this method
-        throw new UnsupportedOperationException();
+            final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
+        if (fields.isEmpty()) {
+            // RFC doesn't allow to build subtree filter that would expect just empty element in response
+            return Futures.immediateFailedFuture(new IllegalArgumentException(
+                    "Failed to build NETCONF GET RPC: provided list of fields is empty; filter path: " + filterPath));
+        }
+        final ListenableFuture<? extends DOMRpcResult> response = get(callback, filterPath, fields);
+        return extractData(filterPath, response);
     }
 
     private ListenableFuture<Optional<NormalizedNode<?, ?>>> extractData(
@@ -247,6 +319,11 @@ public final class NetconfBaseOps {
         return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath);
     }
 
+    private ListenableFuture<? extends DOMRpcResult> getConfigRunning(final FutureCallback<DOMRpcResult> callback,
+            final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
+        return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath, fields);
+    }
+
     public ListenableFuture<? extends DOMRpcResult> getConfigCandidate(final FutureCallback<DOMRpcResult> callback,
             final Optional<YangInstanceIdentifier> filterPath) {
         return getConfig(callback, NETCONF_CANDIDATE_QNAME, filterPath);
@@ -265,6 +342,46 @@ public final class NetconfBaseOps {
         return future;
     }
 
+    private ListenableFuture<? extends DOMRpcResult> get(final FutureCallback<DOMRpcResult> callback,
+            final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
+        requireNonNull(callback);
+
+        final NormalizedNode<?, ?> rpcInput;
+        if (isFilterPresent(filterPath)) {
+            rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, transformer.toFilterStructure(
+                    Collections.singletonList(FieldsFilter.of(filterPath.get(), fields))));
+        } else if (containsEmptyPath(fields)) {
+            rpcInput = GET_RPC_CONTENT;
+        } else {
+            final DataContainerChild<?, ?> subtreeFilter = getSubtreeFilterFromRootFields(fields);
+            rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, subtreeFilter);
+        }
+        final ListenableFuture<? extends DOMRpcResult> response = rpc.invokeRpc(NETCONF_GET_QNAME, rpcInput);
+        Futures.addCallback(response, callback, MoreExecutors.directExecutor());
+        return response;
+    }
+
+    private static boolean containsEmptyPath(final List<YangInstanceIdentifier> fields) {
+        return fields.stream().anyMatch(YangInstanceIdentifier::isEmpty);
+    }
+
+    private DataContainerChild<?, ?> getSubtreeFilterFromRootFields(final List<YangInstanceIdentifier> fields) {
+        final Map<YangInstanceIdentifier, List<YangInstanceIdentifier>> getConfigEntries = fields.stream()
+                .map(fieldPath -> {
+                    final YangInstanceIdentifier rootPath = YangInstanceIdentifier.create(
+                            Iterables.limit(fieldPath.getPathArguments(), 1));
+                    final YangInstanceIdentifier updatedFieldPath = YangInstanceIdentifier.create(
+                            Iterables.skip(fieldPath.getPathArguments(), 1));
+                    return new SimpleEntry<>(rootPath, updatedFieldPath);
+                })
+                .collect(Collectors.groupingBy(SimpleEntry::getKey,
+                        Collectors.mapping(SimpleEntry::getValue, Collectors.toList())));
+        final List<FieldsFilter> fieldsFilters = getConfigEntries.keySet().stream()
+                .map(rootPath -> FieldsFilter.of(rootPath, getConfigEntries.get(rootPath)))
+                .collect(Collectors.toList());
+        return transformer.toFilterStructure(fieldsFilters);
+    }
+
     private static boolean isFilterPresent(final Optional<YangInstanceIdentifier> filterPath) {
         return filterPath.isPresent() && !filterPath.get().isEmpty();
     }