BUG-1690: catch wildcard InstanceIdentifiers
[controller.git] / opendaylight / md-sal / sal-binding-broker / src / main / java / org / opendaylight / controller / md / sal / binding / impl / AbstractWriteTransaction.java
index 5ce66874b129ce3684cca1638a4308646165d058..4597f0646c030b311ac205613c8d3d4f33a05d39 100644 (file)
  */
 package org.opendaylight.controller.md.sal.binding.impl;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.Collections;
 import java.util.Map.Entry;
-
-import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
 import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.util.concurrent.ListenableFuture;
 
 /**
  *
  * Abstract Base Transaction for transactions which are backed by
  * {@link DOMDataWriteTransaction}
  */
-public class AbstractWriteTransaction<T extends DOMDataWriteTransaction> extends
+public abstract class AbstractWriteTransaction<T extends DOMDataWriteTransaction> extends
         AbstractForwardedTransaction<T> {
 
-    private static final Logger LOG = LoggerFactory.getLogger(AbstractWriteTransaction.class);
-
-    protected AbstractWriteTransaction(final T delegate,
-            final BindingToNormalizedNodeCodec codec) {
+    protected AbstractWriteTransaction(final T delegate, final BindingToNormalizedNodeCodec codec) {
         super(delegate, codec);
     }
 
-    protected final void doPut(final LogicalDatastoreType store,
-            final InstanceIdentifier<?> path, final DataObject data) {
-        final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = getCodec()
-                .toNormalizedNode(path, data);
+    public final <U extends DataObject> void put(final LogicalDatastoreType store,
+            final InstanceIdentifier<U> path, final U data, final boolean createParents) {
+        Preconditions.checkArgument(!path.isWildcarded(), "Cannot put data into wildcarded path %s", path);
+
+        final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalized = getCodec().toNormalizedNode(path, data);
+        if (createParents) {
+            ensureParentsByMerge(store, normalized.getKey(), path);
+        } else {
+            ensureListParentIfNeeded(store,path,normalized);
+        }
+
         getDelegate().put(store, normalized.getKey(), normalized.getValue());
     }
 
-    protected final void doMerge(final LogicalDatastoreType store,
-            final InstanceIdentifier<?> path, final DataObject data) {
-        final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = getCodec()
-                .toNormalizedNode(path, data);
+    public final <U extends DataObject> void merge(final LogicalDatastoreType store,
+            final InstanceIdentifier<U> path, final U data,final boolean createParents) {
+        Preconditions.checkArgument(!path.isWildcarded(), "Cannot merge data into wildcarded path %s", path);
+
+        final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalized = getCodec().toNormalizedNode(path, data);
+        if (createParents) {
+            ensureParentsByMerge(store, normalized.getKey(), path);
+        } else {
+            ensureListParentIfNeeded(store,path,normalized);
+        }
+
         getDelegate().merge(store, normalized.getKey(), normalized.getValue());
     }
 
+    /**
+     *
+     * Ensures list parent if item is list, otherwise noop.
+     *
+     * <p>
+     * One of properties of binding specification is that it is imposible
+     * to represent list as a whole and thus it is impossible to write
+     * empty variation of MapNode without creating parent node, with
+     * empty list.
+     *
+     * <p>
+     * This actually makes writes such as
+     * <pre>
+     * put("Nodes", new NodesBuilder().build());
+     * put("Nodes/Node[key]", new NodeBuilder().setKey("key").build());
+     * </pre>
+     * To result in three DOM operations:
+     * <pre>
+     * put("/nodes",domNodes);
+     * merge("/nodes/node",domNodeList);
+     * put("/nodes/node/node[key]",domNode);
+     * </pre>
+     *
+     *
+     * In order to allow that to be inserted if necessary, if we know
+     * item is list item, we will try to merge empty MapNode or OrderedNodeMap
+     * to ensure list exists.
+     *
+     * @param store Data Store type
+     * @param path Path to data (Binding Aware)
+     * @param normalized Normalized version of data to be written
+     */
+    private void ensureListParentIfNeeded(final LogicalDatastoreType store, final InstanceIdentifier<?> path,
+            final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalized) {
+        if (Identifiable.class.isAssignableFrom(path.getTargetType())) {
+            YangInstanceIdentifier parentMapPath = getParent(normalized.getKey()).get();
+            NormalizedNode<?, ?> emptyParent = getCodec().getDefaultNodeFor(parentMapPath);
+            getDelegate().merge(store, parentMapPath, emptyParent);
+        }
+    }
+
+    // FIXME (should be probaly part of InstanceIdentifier)
+    protected static Optional<YangInstanceIdentifier> getParent(
+            final YangInstanceIdentifier child) {
+
+        Iterable<PathArgument> mapEntryItemPath = child.getPathArguments();
+        int parentPathSize = Iterables.size(mapEntryItemPath) - 1;
+        if (parentPathSize > 1) {
+            return Optional.of(YangInstanceIdentifier.create(Iterables.limit(mapEntryItemPath,  parentPathSize)));
+        } else if(parentPathSize == 0) {
+            return Optional.of(YangInstanceIdentifier.create(Collections.<PathArgument>emptyList()));
+        } else {
+            return Optional.absent();
+        }
+    }
+
+    /**
+     * Subclasses of this class are required to implement creation of parent
+     * nodes based on behaviour of their underlying transaction.
+     *
+     * @param store
+     * @param key
+     * @param path
+     */
+    protected abstract void ensureParentsByMerge(LogicalDatastoreType store,
+            YangInstanceIdentifier key, InstanceIdentifier<?> path);
+
     protected final void doDelete(final LogicalDatastoreType store,
             final InstanceIdentifier<?> path) {
-        final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = getCodec().toNormalized(path);
+        Preconditions.checkArgument(!path.isWildcarded(), "Cannot delete wildcarded path %s", path);
+
+        final YangInstanceIdentifier normalized = getCodec().toNormalized(path);
         getDelegate().delete(store, normalized);
     }
 
-    protected final ListenableFuture<RpcResult<TransactionStatus>> doCommit() {
-        return getDelegate().commit();
+    protected final CheckedFuture<Void,TransactionCommitFailedException> doSubmit() {
+        return getDelegate().submit();
     }
 
     protected final boolean doCancel() {