BUG 1330 - list key counts|values diff in payload and URI
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / RestconfImpl.java
index 3834a383db8e94da989f7b1bd4076285cd250935..ae48ca7196904a135429bd9aae88626f947924d1 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Future;
 import javax.ws.rs.core.Response;
@@ -51,6 +52,8 @@ import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
 import org.opendaylight.yangtools.yang.data.api.Node;
 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
@@ -673,6 +676,7 @@ public class RestconfImpl implements RestconfService {
 
         MountInstance mountPoint = iiWithData.getMountPoint();
         final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint);
+        validateListKeysEqualityInPayloadAndUri(iiWithData, payload);
         RpcResult<TransactionStatus> status = null;
 
         try {
@@ -694,6 +698,52 @@ public class RestconfImpl implements RestconfService {
         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
     }
 
+    /**
+     * Validates whether keys in {@code payload} are equal to values of keys in
+     * {@code iiWithData} for list schema node
+     *
+     * @throws RestconfDocumentedException
+     *             if key values or key count in payload and URI isn't equal
+     *
+     */
+    private void validateListKeysEqualityInPayloadAndUri(final InstanceIdWithSchemaNode iiWithData,
+            final CompositeNode payload) {
+        if (iiWithData.getSchemaNode() instanceof ListSchemaNode) {
+            final List<QName> keyDefinitions = ((ListSchemaNode) iiWithData.getSchemaNode()).getKeyDefinition();
+            final PathArgument lastPathArgument = iiWithData.getInstanceIdentifier().getLastPathArgument();
+            if (lastPathArgument instanceof NodeIdentifierWithPredicates) {
+                final Map<QName, Object> uriKeyValues = ((NodeIdentifierWithPredicates) lastPathArgument)
+                        .getKeyValues();
+                isEqualUriAndPayloadKeyValues(uriKeyValues, payload, keyDefinitions);
+            }
+        }
+    }
+
+    private void isEqualUriAndPayloadKeyValues(final Map<QName, Object> uriKeyValues, final CompositeNode payload,
+            final List<QName> keyDefinitions) {
+        for (QName keyDefinition : keyDefinitions) {
+            final Object uriKeyValue = uriKeyValues.get(keyDefinition);
+            // should be caught during parsing URI to InstanceIdentifier
+            if (uriKeyValue == null) {
+                throw new RestconfDocumentedException("Missing key " + keyDefinition + " in URI.",
+                        ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+            }
+            final List<SimpleNode<?>> payloadKeyValues = payload.getSimpleNodesByName(keyDefinition.getLocalName());
+            if (payloadKeyValues.isEmpty()) {
+                throw new RestconfDocumentedException("Missing key " + keyDefinition.getLocalName()
+                        + " in the message body.", ErrorType.PROTOCOL,
+                        ErrorTag.DATA_MISSING);
+            }
+
+            Object payloadKeyValue = payloadKeyValues.iterator().next().getValue();
+            if (!uriKeyValue.equals(payloadKeyValue)) {
+                throw new RestconfDocumentedException("The value '"+uriKeyValue+ "' for key '" + keyDefinition.getLocalName() +
+                        "' specified in the URI doesn't match the value '" + payloadKeyValue + "' specified in the message body. ",
+                        ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+            }
+        }
+    }
+
     @Override
     public Response createConfigurationData(final String identifier, final CompositeNode payload) {
         if( payload == null ) {
@@ -877,7 +927,7 @@ public class RestconfImpl implements RestconfService {
         broker.registerToListenDataChanges(listener);
 
         final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
-        UriBuilder port = uriBuilder.port(WebSocketServer.PORT);
+        UriBuilder port = uriBuilder.port(WebSocketServer.getInstance().getPort());
         final URI uriToWebsocketServer = port.replacePath(streamName).build();
 
         return Response.status(Status.OK).location(uriToWebsocketServer).build();
@@ -1136,6 +1186,7 @@ public class RestconfImpl implements RestconfService {
             final DataNodeContainer schema, final MountInstance mountPoint,
             final QName currentAugment ) {
         final List<NodeWrapper<?>> children = compositeNodeBuilder.getValues();
+        checkNodeMultiplicityAccordingToSchema(schema,children);
         for (final NodeWrapper<? extends Object> child : children) {
             final List<DataSchemaNode> potentialSchemaNodes =
                     this.controllerContext.findInstanceDataChildrenByName(
@@ -1188,9 +1239,32 @@ public class RestconfImpl implements RestconfService {
 
                 if (!foundKey) {
                     throw new RestconfDocumentedException(
-                            "Missing key in URI \"" + listKey.getLocalName() +
-                            "\" of list \"" + listSchemaNode.getQName().getLocalName() + "\"",
-                            ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
+                            "Missing key '" + listKey.getLocalName() +
+                            "' for list '" + listSchemaNode.getQName().getLocalName() + "' in the message body",
+                            ErrorType.PROTOCOL, ErrorTag.DATA_MISSING );
+                }
+            }
+        }
+    }
+
+    private void checkNodeMultiplicityAccordingToSchema(final DataNodeContainer dataNodeContainer,
+            final List<NodeWrapper<?>> nodes) {
+        Map<String, Integer> equalNodeNamesToCounts = new HashMap<String, Integer>();
+        for (NodeWrapper<?> child : nodes) {
+            Integer count = equalNodeNamesToCounts.get(child.getLocalName());
+            equalNodeNamesToCounts.put(child.getLocalName(), count == null ? 1 : ++count);
+        }
+
+        for (DataSchemaNode childSchemaNode : dataNodeContainer.getChildNodes()) {
+            if (childSchemaNode instanceof ContainerSchemaNode || childSchemaNode instanceof LeafSchemaNode) {
+                String localName = childSchemaNode.getQName().getLocalName();
+                Integer count = equalNodeNamesToCounts.get(localName);
+                if (count != null && count > 1) {
+                    throw new RestconfDocumentedException(
+                            "Multiple input data elements were specified for '"
+                            + childSchemaNode.getQName().getLocalName()
+                            + "'. The data for this element type can only be specified once.",
+                            ErrorType.APPLICATION, ErrorTag.BAD_ELEMENT);
                 }
             }
         }