Merge "BUG 932 - Swagger HTTP POST contains incorrect object"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / RestconfImpl.java
index ce18f7c67d596cbe7d886f6b8a8737da8c1138fa..b94f6a6166c47f3b1308f7dc4696341fe5ec202b 100644 (file)
@@ -21,7 +21,6 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -37,6 +36,8 @@ import javax.ws.rs.core.UriInfo;
 import org.apache.commons.lang3.StringUtils;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
 import org.opendaylight.controller.sal.rest.api.Draft02;
@@ -52,7 +53,6 @@ import org.opendaylight.controller.sal.streams.websockets.WebSocketServer;
 import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
@@ -62,12 +62,8 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.parser.CnSnToNormalizedNodeParserFactory;
-import org.opendaylight.yangtools.yang.data.composite.node.schema.cnsn.serializer.CnSnFromNormalizedNodeSerializerFactory;
 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
 import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
@@ -613,19 +609,8 @@ public class RestconfImpl implements RestconfService {
     private void checkRpcSuccessAndThrowException(final RpcResult<CompositeNode> rpcResult) {
         if (rpcResult.isSuccessful() == false) {
 
-            Collection<RpcError> rpcErrors = rpcResult.getErrors();
-            if (rpcErrors == null || rpcErrors.isEmpty()) {
-                throw new RestconfDocumentedException(
-                        "The operation was not successful and there were no RPC errors returned", ErrorType.RPC,
-                        ErrorTag.OPERATION_FAILED);
-            }
-
-            List<RestconfError> errorList = Lists.newArrayList();
-            for (RpcError rpcError : rpcErrors) {
-                errorList.add(new RestconfError(rpcError));
-            }
-
-            throw new RestconfDocumentedException(errorList);
+            throw new RestconfDocumentedException("The operation was not successful", null,
+                    rpcResult.getErrors());
         }
     }
 
@@ -636,7 +621,8 @@ public class RestconfImpl implements RestconfService {
         NormalizedNode<?, ?> data = null;
         YangInstanceIdentifier normalizedII;
         if (mountPoint != null) {
-            normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData.getInstanceIdentifier());
+            normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData
+                    .getInstanceIdentifier());
             data = broker.readConfigurationData(mountPoint, normalizedII);
         } else {
             normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier());
@@ -699,7 +685,8 @@ public class RestconfImpl implements RestconfService {
         NormalizedNode<?, ?> data = null;
         YangInstanceIdentifier normalizedII;
         if (mountPoint != null) {
-            normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData.getInstanceIdentifier());
+            normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData
+                    .getInstanceIdentifier());
             data = broker.readOperationalData(mountPoint, normalizedII);
         } else {
             normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier());
@@ -731,17 +718,50 @@ public class RestconfImpl implements RestconfService {
                 iiWithData.getSchemaNode());
 
         YangInstanceIdentifier normalizedII;
+        if (mountPoint != null) {
+            normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(
+                    iiWithData.getInstanceIdentifier());
+        } else {
+            normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier());
+        }
 
-        try {
-            if (mountPoint != null) {
-                normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData.getInstanceIdentifier());
-                broker.commitConfigurationDataPut(mountPoint, normalizedII, datastoreNormalizedNode).get();
-            } else {
-                normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier());
-                broker.commitConfigurationDataPut(normalizedII, datastoreNormalizedNode).get();
+        /*
+         * There is a small window where another write transaction could be updating the same data
+         * simultaneously and we get an OptimisticLockFailedException. This error is likely
+         * transient and The WriteTransaction#submit API docs state that a retry will likely
+         * succeed. So we'll try again if that scenario occurs. If it fails a third time then it
+         * probably will never succeed so we'll fail in that case.
+         *
+         * By retrying we're attempting to hide the internal implementation of the data store and
+         * how it handles concurrent updates from the restconf client. The client has instructed us
+         * to put the data and we should make every effort to do so without pushing optimistic lock
+         * failures back to the client and forcing them to handle it via retry (and having to
+         * document the behavior).
+         */
+        int tries = 2;
+        while(true) {
+            try {
+                if (mountPoint != null) {
+                    broker.commitConfigurationDataPut(mountPoint, normalizedII,
+                            datastoreNormalizedNode).checkedGet();
+                } else {
+                    broker.commitConfigurationDataPut(normalizedII,
+                            datastoreNormalizedNode).checkedGet();
+                }
+
+                break;
+            } catch (TransactionCommitFailedException e) {
+                if(e instanceof OptimisticLockFailedException) {
+                    if(--tries <= 0) {
+                        LOG.debug("Got OptimisticLockFailedException on last try - failing");
+                        throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
+                    }
+
+                    LOG.debug("Got OptimisticLockFailedException - trying again");
+                } else {
+                    throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
+                }
             }
-        } catch (Exception e) {
-            throw new RestconfDocumentedException("Error updating data", e);
         }
 
         return Response.status(Status.OK).build();
@@ -846,12 +866,15 @@ public class RestconfImpl implements RestconfService {
 
         try {
             if (mountPoint != null) {
-                normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData.getInstanceIdentifier());
+                normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData
+                        .getInstanceIdentifier());
                 broker.commitConfigurationDataPost(mountPoint, normalizedII, datastoreNormalizedData);
             } else {
                 normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier());
                 broker.commitConfigurationDataPost(normalizedII, datastoreNormalizedData);
             }
+        } catch(RestconfDocumentedException e) {
+            throw e;
         } catch (Exception e) {
             throw new RestconfDocumentedException("Error creating data", e);
         }
@@ -890,13 +913,16 @@ public class RestconfImpl implements RestconfService {
 
         try {
             if (mountPoint != null) {
-                normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData.getInstanceIdentifier());
+                normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData
+                        .getInstanceIdentifier());
                 broker.commitConfigurationDataPost(mountPoint, normalizedII, datastoreNormalizedData);
 
             } else {
                 normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier());
                 broker.commitConfigurationDataPost(normalizedII, datastoreNormalizedData);
             }
+        } catch(RestconfDocumentedException e) {
+            throw e;
         } catch (Exception e) {
             throw new RestconfDocumentedException("Error creating data", e);
         }
@@ -912,7 +938,8 @@ public class RestconfImpl implements RestconfService {
 
         try {
             if (mountPoint != null) {
-                normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData.getInstanceIdentifier());
+                normalizedII = new DataNormalizer(mountPoint.getSchemaContext()).toNormalized(iiWithData
+                        .getInstanceIdentifier());
                 broker.commitConfigurationDataDelete(mountPoint, normalizedII);
             } else {
                 normalizedII = controllerContext.toNormalized(iiWithData.getInstanceIdentifier());
@@ -1086,7 +1113,12 @@ public class RestconfImpl implements RestconfService {
             iiBuilder = YangInstanceIdentifier.builder(iiOriginal);
         }
 
-        iiBuilder.node(schemaOfData.getQName());
+        if ((schemaOfData instanceof ListSchemaNode)) {
+            HashMap<QName, Object> keys = this.resolveKeysFromData(((ListSchemaNode) schemaOfData), data);
+            iiBuilder.nodeWithKey(schemaOfData.getQName(), keys);
+        } else {
+            iiBuilder.node(schemaOfData.getQName());
+        }
 
         YangInstanceIdentifier instance = iiBuilder.toInstance();
         DOMMountPoint mountPoint = null;
@@ -1097,6 +1129,34 @@ public class RestconfImpl implements RestconfService {
         return new InstanceIdWithSchemaNode(instance, schemaOfData, mountPoint);
     }
 
+    private HashMap<QName, Object> resolveKeysFromData(final ListSchemaNode listNode, final CompositeNode dataNode) {
+        final HashMap<QName, Object> keyValues = new HashMap<QName, Object>();
+        List<QName> _keyDefinition = listNode.getKeyDefinition();
+        for (final QName key : _keyDefinition) {
+            SimpleNode<? extends Object> head = null;
+            String localName = key.getLocalName();
+            List<SimpleNode<? extends Object>> simpleNodesByName = dataNode.getSimpleNodesByName(localName);
+            if (simpleNodesByName != null) {
+                head = Iterables.getFirst(simpleNodesByName, null);
+            }
+
+            Object dataNodeKeyValueObject = null;
+            if (head != null) {
+                dataNodeKeyValueObject = head.getValue();
+            }
+
+            if (dataNodeKeyValueObject == null) {
+                throw new RestconfDocumentedException("Data contains list \"" + dataNode.getNodeType().getLocalName()
+                        + "\" which does not contain key: \"" + key.getLocalName() + "\"", ErrorType.PROTOCOL,
+                        ErrorTag.INVALID_VALUE);
+            }
+
+            keyValues.put(key, dataNodeKeyValueObject);
+        }
+
+        return keyValues;
+    }
+
     private boolean endsWithMountPoint(final String identifier) {
         return identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/");
     }
@@ -1121,8 +1181,9 @@ public class RestconfImpl implements RestconfService {
 
     private CompositeNode normalizeNode(final Node<?> node, final DataSchemaNode schema, final DOMMountPoint mountPoint) {
         if (schema == null) {
-            QName nodeType = node == null ? null : node.getNodeType();
-            String localName = nodeType == null ? null : nodeType.getLocalName();
+            String localName = node == null ? null :
+                    node instanceof NodeWrapper ? ((NodeWrapper<?>)node).getLocalName() :
+                    node.getNodeType().getLocalName();
 
             throw new RestconfDocumentedException("Data schema node was not found for " + localName,
                     ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
@@ -1414,25 +1475,15 @@ public class RestconfImpl implements RestconfService {
     }
 
     private CompositeNode datastoreNormalizedNodeToCompositeNode(final NormalizedNode<?, ?> dataNode, final DataSchemaNode schema) {
-        Iterable<Node<?>> nodes = null;
+        Node<?> nodes = null;
         if (dataNode == null) {
             throw new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION, ErrorTag.DATA_MISSING,
                     "No data was found."));
         }
-        if (schema instanceof ContainerSchemaNode && dataNode instanceof ContainerNode) {
-            nodes = CnSnFromNormalizedNodeSerializerFactory.getInstance().getContainerNodeSerializer()
-                    .serialize((ContainerSchemaNode) schema, (ContainerNode) dataNode);
-        } else if (schema instanceof ListSchemaNode && dataNode instanceof MapNode) {
-            nodes = CnSnFromNormalizedNodeSerializerFactory.getInstance().getMapNodeSerializer()
-                    .serialize((ListSchemaNode) schema, (MapNode) dataNode);
-        } else if (schema instanceof ListSchemaNode && dataNode instanceof MapEntryNode) {
-            nodes = CnSnFromNormalizedNodeSerializerFactory.getInstance().getMapEntryNodeSerializer()
-                    .serialize((ListSchemaNode) schema, (MapEntryNode) dataNode);
-        }
+        nodes = DataNormalizer.toLegacy(dataNode);
         if (nodes != null) {
-            if (nodes.iterator().hasNext()) {
-                Node<?> nodeOldStruct = nodes.iterator().next();
-                return (CompositeNode) nodeOldStruct;
+            if (nodes instanceof CompositeNode) {
+                return (CompositeNode) nodes;
             } else {
                 LOG.error("The node " + dataNode.getNodeType() + " couldn't be transformed to compositenode.");
             }
@@ -1445,7 +1496,8 @@ public class RestconfImpl implements RestconfService {
                 "It wasn't possible to correctly interpret data."));
     }
 
-    private NormalizedNode<?, ?> compositeNodeToDatastoreNormalizedNode(final CompositeNode compNode, final DataSchemaNode schema) {
+    private NormalizedNode<?, ?> compositeNodeToDatastoreNormalizedNode(final CompositeNode compNode,
+            final DataSchemaNode schema) {
         List<Node<?>> lst = new ArrayList<Node<?>>();
         lst.add(compNode);
         if (schema instanceof ContainerSchemaNode) {
@@ -1462,7 +1514,8 @@ public class RestconfImpl implements RestconfService {
                 "It wasn't possible to translate specified data to datastore readable form."));
     }
 
-    private InstanceIdWithSchemaNode normalizeInstanceIdentifierWithSchemaNode(final InstanceIdWithSchemaNode iiWithSchemaNode) {
+    private InstanceIdWithSchemaNode normalizeInstanceIdentifierWithSchemaNode(
+            final InstanceIdWithSchemaNode iiWithSchemaNode) {
         return normalizeInstanceIdentifierWithSchemaNode(iiWithSchemaNode, false);
     }
 
@@ -1473,8 +1526,8 @@ public class RestconfImpl implements RestconfService {
                 iiWithSchemaNode.getMountPoint());
     }
 
-    private YangInstanceIdentifier instanceIdentifierToReadableFormForNormalizeNode(final YangInstanceIdentifier instIdentifier,
-            final boolean unwrapLastListNode) {
+    private YangInstanceIdentifier instanceIdentifierToReadableFormForNormalizeNode(
+            final YangInstanceIdentifier instIdentifier, final boolean unwrapLastListNode) {
         Preconditions.checkNotNull(instIdentifier, "Instance identifier can't be null");
         final List<PathArgument> result = new ArrayList<PathArgument>();
         final Iterator<PathArgument> iter = instIdentifier.getPathArguments().iterator();