Added comments to the opendaylight-inventory.yang file to help describe
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / RestconfImpl.java
index ad682bc8291d50e52d674607e30142009bac459e..9700d48bc298f30576796d76a72489d5ae0900da 100644 (file)
@@ -12,9 +12,9 @@ import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-
 import java.net.URI;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -27,32 +27,20 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
-
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
-
 import org.apache.commons.lang3.StringUtils;
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
 import org.opendaylight.controller.sal.rest.api.Draft02;
 import org.opendaylight.controller.sal.rest.api.RestconfService;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.controller.sal.restconf.rpc.impl.BrokerRpcExecutor;
 import org.opendaylight.controller.sal.restconf.rpc.impl.MountPointRpcExecutor;
 import org.opendaylight.controller.sal.restconf.rpc.impl.RpcExecutor;
-import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
-import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
-import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
-import org.opendaylight.controller.sal.restconf.impl.EmptyNodeWrapper;
-import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO;
-import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode;
-import org.opendaylight.controller.sal.restconf.impl.NodeWrapper;
-import org.opendaylight.controller.sal.restconf.impl.RestCodec;
-import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
-import org.opendaylight.controller.sal.restconf.impl.StructuredData;
-import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
-import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
 import org.opendaylight.controller.sal.streams.listeners.Notificator;
 import org.opendaylight.controller.sal.streams.websockets.WebSocketServer;
@@ -66,6 +54,7 @@ import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdent
 import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
 import org.opendaylight.yangtools.yang.data.api.Node;
 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
+import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
 import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
@@ -262,7 +251,8 @@ public class RestconfImpl implements RestconfService {
                 operationsAsData.add(immutableSimpleNode);
 
                 String name = module.getName();
-                LeafSchemaNodeBuilder leafSchemaNodeBuilder = new LeafSchemaNodeBuilder(name, 0, rpcQName, null);
+                LeafSchemaNodeBuilder leafSchemaNodeBuilder = new LeafSchemaNodeBuilder(name, 0, rpcQName,
+                        SchemaPath.create(true, QName.create("dummy")));
                 final LeafSchemaNodeBuilder fakeRpcSchemaNode = leafSchemaNodeBuilder;
                 fakeRpcSchemaNode.setAugmenting(true);
 
@@ -401,13 +391,36 @@ public class RestconfImpl implements RestconfService {
         URI rpcNamespace = rpcName.getNamespace();
         if (Objects.equal(rpcNamespace.toString(), SAL_REMOTE_NAMESPACE) &&
             Objects.equal(rpcName.getLocalName(), SAL_REMOTE_RPC_SUBSRCIBE)) {
-
             return invokeSalRemoteRpcSubscribeRPC(payload, rpc.getRpcDefinition());
         }
 
+        validateInput( rpc.getRpcDefinition().getInput(), payload );
+
         return callRpc(rpc, payload);
     }
 
+    private void validateInput(DataSchemaNode inputSchema, CompositeNode payload) {
+        if( inputSchema != null && payload == null )
+        {
+            //expected a non null payload
+            throw new RestconfDocumentedException( "Input is required.",
+                                                   ErrorType.PROTOCOL,
+                                                   ErrorTag.MALFORMED_MESSAGE );
+        }
+        else if( inputSchema == null && payload != null )
+        {
+            //did not expect any input
+            throw new RestconfDocumentedException( "No input expected.",
+                                                   ErrorType.PROTOCOL,
+                                                   ErrorTag.MALFORMED_MESSAGE );
+        }
+        //else
+        //{
+            //TODO: Validate "mandatory" and "config" values here??? Or should those be
+        // validate in a more central location inside MD-SAL core.
+        //}
+    }
+
     private StructuredData invokeSalRemoteRpcSubscribeRPC(final CompositeNode payload,
                                                           final RpcDefinition rpc) {
         final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null);
@@ -455,8 +468,7 @@ public class RestconfImpl implements RestconfService {
             throw new RestconfDocumentedException(
                     "Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE );
         }
-        final RpcExecutor rpc = resolveIdentifierInInvokeRpc(identifier);
-        return callRpc(rpc, null);
+        return invokeRpc( identifier, (CompositeNode)null );
     }
 
     private RpcExecutor resolveIdentifierInInvokeRpc(final String identifier) {
@@ -554,7 +566,7 @@ public class RestconfImpl implements RestconfService {
     }
 
     @Override
-    public StructuredData readConfigurationData(final String identifier) {
+    public StructuredData readConfigurationData(final String identifier, UriInfo info) {
         final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
         CompositeNode data = null;
         MountInstance mountPoint = iiWithData.getMountPoint();
@@ -565,11 +577,57 @@ public class RestconfImpl implements RestconfService {
             data = broker.readConfigurationData(iiWithData.getInstanceIdentifier());
         }
 
+        data = pruneDataAtDepth( data, parseDepthParameter( info ) );
         return new StructuredData(data, iiWithData.getSchemaNode(), iiWithData.getMountPoint());
     }
 
+    @SuppressWarnings("unchecked")
+    private <T extends Node<?>> T pruneDataAtDepth( T node, Integer depth ) {
+        if( depth == null ) {
+            return node;
+        }
+
+        if( node instanceof CompositeNode ) {
+            ImmutableList.Builder<Node<?>> newChildNodes = ImmutableList.<Node<?>> builder();
+            if( depth > 1 ) {
+                for( Node<?> childNode: ((CompositeNode)node).getValue() ) {
+                    newChildNodes.add( pruneDataAtDepth( childNode, depth - 1 ) );
+                }
+            }
+
+            return (T) ImmutableCompositeNode.create( node.getNodeType(), newChildNodes.build() );
+        }
+        else { // SimpleNode
+            return node;
+        }
+    }
+
+    private Integer parseDepthParameter( UriInfo info ) {
+        String param = info.getQueryParameters( false ).getFirst( "depth" );
+        if( Strings.isNullOrEmpty( param ) || "unbounded".equals( param ) ) {
+            return null;
+        }
+
+        try {
+            Integer depth = Integer.valueOf( param );
+            if( depth < 1 ) {
+                throw new RestconfDocumentedException( new RestconfError(
+                        ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depth,
+                        null, "The depth parameter must be an integer > 1 or \"unbounded\"" ) );
+            }
+
+            return depth;
+        }
+        catch( NumberFormatException e ) {
+            throw new RestconfDocumentedException( new RestconfError(
+                    ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+                    "Invalid depth parameter: " + e.getMessage(),
+                    null, "The depth parameter must be an integer > 1 or \"unbounded\"" ) );
+        }
+    }
+
     @Override
-    public StructuredData readOperationalData(final String identifier) {
+    public StructuredData readOperationalData(final String identifier, UriInfo info) {
         final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
         CompositeNode data = null;
         MountInstance mountPoint = iiWithData.getMountPoint();
@@ -580,12 +638,16 @@ public class RestconfImpl implements RestconfService {
             data = broker.readOperationalData(iiWithData.getInstanceIdentifier());
         }
 
+        data = pruneDataAtDepth( data, parseDepthParameter( info ) );
         return new StructuredData(data, iiWithData.getSchemaNode(), mountPoint);
     }
 
     @Override
     public Response updateConfigurationData(final String identifier, final CompositeNode payload) {
         final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier);
+
+        validateInput(iiWithData.getSchemaNode(), payload);
+
         MountInstance mountPoint = iiWithData.getMountPoint();
         final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint);
         RpcResult<TransactionStatus> status = null;
@@ -610,6 +672,12 @@ public class RestconfImpl implements RestconfService {
 
     @Override
     public Response createConfigurationData(final String identifier, final CompositeNode payload) {
+        if( payload == null ) {
+            throw new RestconfDocumentedException( "Input is required.",
+                    ErrorType.PROTOCOL,
+                    ErrorTag.MALFORMED_MESSAGE );
+        }
+
         URI payloadNS = this.namespace(payload);
         if (payloadNS == null) {
             throw new RestconfDocumentedException(
@@ -685,6 +753,12 @@ public class RestconfImpl implements RestconfService {
 
     @Override
     public Response createConfigurationData(final CompositeNode payload) {
+        if( payload == null ) {
+            throw new RestconfDocumentedException( "Input is required.",
+                    ErrorType.PROTOCOL,
+                    ErrorTag.MALFORMED_MESSAGE );
+        }
+
         URI payloadNS = this.namespace(payload);
         if (payloadNS == null) {
             throw new RestconfDocumentedException(