X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-rest-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fsal%2Frestconf%2Fimpl%2FRestconfImpl.java;h=4716a02be2f26230721be7e08eb697eaeb0224cc;hp=2e198ec053d64e018c62224fea94f46167e2b300;hb=44a86821d69cd804b6b23b437e0b27136eaac2b5;hpb=a9b4056862816a1f6b904a574dcc29f2706d7da7 diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java index 2e198ec053..4716a02be2 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java @@ -1,5 +1,6 @@ /** * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2014 Brocade Communication Systems, Inc. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, @@ -9,47 +10,43 @@ package org.opendaylight.controller.sal.restconf.impl; import com.google.common.base.Objects; import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; 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; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Date; 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.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.ResponseException; -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.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.streams.listeners.ListenerAdapter; import org.opendaylight.controller.sal.streams.listeners.Notificator; 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.RpcError; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; @@ -57,12 +54,13 @@ 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.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.FeatureDefinition; -import org.opendaylight.yangtools.yang.model.api.GroupingDefinition; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; @@ -76,34 +74,15 @@ import org.opendaylight.yangtools.yang.model.util.EmptyType; import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder; import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder; -@SuppressWarnings("all") public class RestconfImpl implements RestconfService { private final static RestconfImpl INSTANCE = new RestconfImpl(); + private static final int CHAR_NOT_FOUND = -1; + private final static String MOUNT_POINT_MODULE_NAME = "ietf-netconf"; private final static SimpleDateFormat REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); - private final static String RESTCONF_MODULE_DRAFT02_REVISION = "2013-10-19"; - - private final static String RESTCONF_MODULE_DRAFT02_NAME = "ietf-restconf"; - - private final static String RESTCONF_MODULE_DRAFT02_NAMESPACE = "urn:ietf:params:xml:ns:yang:ietf-restconf"; - - private final static String RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE = "restconf"; - - private final static String RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE = "restconf"; - - private final static String RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE = "modules"; - - private final static String RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE = "module"; - - private final static String RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE = "streams"; - - private final static String RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE = "stream"; - - private final static String RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE = "operations"; - private final static String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"; private final static String SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription"; @@ -132,8 +111,8 @@ public class RestconfImpl implements RestconfService { final Module restconfModule = this.getRestconfModule(); final List> modulesAsData = new ArrayList>(); - final DataSchemaNode moduleSchemaNode = - this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE); + final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode( + restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE); Set allModules = this.controllerContext.getAllModules(); for (final Module module : allModules) { @@ -141,8 +120,8 @@ public class RestconfImpl implements RestconfService { modulesAsData.add(moduleCompositeNode); } - final DataSchemaNode modulesSchemaNode = - this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE); + final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode( + restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE); QName qName = modulesSchemaNode.getQName(); final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData); return new StructuredData(modulesNode, modulesSchemaNode, null); @@ -154,14 +133,14 @@ public class RestconfImpl implements RestconfService { final List> streamsAsData = new ArrayList>(); Module restconfModule = this.getRestconfModule(); - final DataSchemaNode streamSchemaNode = - this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE); + final DataSchemaNode streamSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode( + restconfModule, Draft02.RestConfModule.STREAM_LIST_SCHEMA_NODE); for (final String streamName : availableStreams) { streamsAsData.add(this.toStreamCompositeNode(streamName, streamSchemaNode)); } - final DataSchemaNode streamsSchemaNode = - this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE); + final DataSchemaNode streamsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode( + restconfModule, Draft02.RestConfModule.STREAMS_CONTAINER_SCHEMA_NODE); QName qName = streamsSchemaNode.getQName(); final CompositeNode streamsNode = NodeFactory.createImmutableCompositeNode(qName, null, streamsAsData); return new StructuredData(streamsNode, streamsSchemaNode, null); @@ -178,22 +157,22 @@ public class RestconfImpl implements RestconfService { modules = this.controllerContext.getAllModules(mountPoint); } else { - throw new ResponseException(Status.BAD_REQUEST, - "URI has bad format. If modules behind mount point should be showed, URI has to end with " + - ControllerContext.MOUNT); + throw new RestconfDocumentedException( + "URI has bad format. If modules behind mount point should be showed, URI has to end with " + + ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } final List> modulesAsData = new ArrayList>(); Module restconfModule = this.getRestconfModule(); - final DataSchemaNode moduleSchemaNode = - this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE); + final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode( + restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE); for (final Module module : modules) { modulesAsData.add(this.toModuleCompositeNode(module, moduleSchemaNode)); } - final DataSchemaNode modulesSchemaNode = - this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE); + final DataSchemaNode modulesSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode( + restconfModule, Draft02.RestConfModule.MODULES_CONTAINER_SCHEMA_NODE); QName qName = modulesSchemaNode.getQName(); final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData); return new StructuredData(modulesNode, modulesSchemaNode, mountPoint); @@ -215,14 +194,15 @@ public class RestconfImpl implements RestconfService { } if (module == null) { - throw new ResponseException(Status.BAD_REQUEST, + throw new RestconfDocumentedException( "Module with name '" + moduleNameAndRevision.getLocalName() + "' and revision '" + - moduleNameAndRevision.getRevision() + "' was not found."); + moduleNameAndRevision.getRevision() + "' was not found.", + ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT ); } Module restconfModule = this.getRestconfModule(); - final DataSchemaNode moduleSchemaNode = - this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE); + final DataSchemaNode moduleSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode( + restconfModule, Draft02.RestConfModule.MODULE_LIST_SCHEMA_NODE); final CompositeNode moduleNode = this.toModuleCompositeNode(module, moduleSchemaNode); return new StructuredData(moduleNode, moduleSchemaNode, mountPoint); } @@ -244,9 +224,9 @@ public class RestconfImpl implements RestconfService { modules = this.controllerContext.getAllModules(mountPoint); } else { - throw new ResponseException(Status.BAD_REQUEST, - "URI has bad format. If operations behind mount point should be showed, URI has to end with " + - ControllerContext.MOUNT); + throw new RestconfDocumentedException( + "URI has bad format. If operations behind mount point should be showed, URI has to end with " + + ControllerContext.MOUNT, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } return this.operationsFromModulesToStructuredData(modules, mountPoint); @@ -256,12 +236,12 @@ public class RestconfImpl implements RestconfService { final MountInstance mountPoint) { final List> operationsAsData = new ArrayList>(); Module restconfModule = this.getRestconfModule(); - final DataSchemaNode operationsSchemaNode = - this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE); + final DataSchemaNode operationsSchemaNode = controllerContext.getRestconfModuleRestConfSchemaNode( + restconfModule, Draft02.RestConfModule.OPERATIONS_CONTAINER_SCHEMA_NODE); QName qName = operationsSchemaNode.getQName(); SchemaPath path = operationsSchemaNode.getPath(); ContainerSchemaNodeBuilder containerSchemaNodeBuilder = - new ContainerSchemaNodeBuilder(RESTCONF_MODULE_DRAFT02_NAME, 0, qName, path); + new ContainerSchemaNodeBuilder(Draft02.RestConfModule.NAME, 0, qName, path); final ContainerSchemaNodeBuilder fakeOperationsSchemaNode = containerSchemaNodeBuilder; for (final Module module : modules) { Set rpcs = module.getRpcs(); @@ -272,7 +252,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); @@ -289,11 +270,11 @@ public class RestconfImpl implements RestconfService { } private Module getRestconfModule() { - QName qName = QName.create(RESTCONF_MODULE_DRAFT02_NAMESPACE, RESTCONF_MODULE_DRAFT02_REVISION, - RESTCONF_MODULE_DRAFT02_NAME); - final Module restconfModule = this.controllerContext.findModuleByNameAndRevision(qName); + Module restconfModule = controllerContext.getRestconfModule(); if (restconfModule == null) { - throw new ResponseException(Status.INTERNAL_SERVER_ERROR, "Restconf module was not found."); + throw new RestconfDocumentedException( + "ietf-restconf module was not found.", ErrorType.APPLICATION, + ErrorTag.OPERATION_NOT_SUPPORTED ); } return restconfModule; @@ -313,8 +294,9 @@ public class RestconfImpl implements RestconfService { Iterable split = splitter.split(moduleNameAndRevision); final List pathArgs = Lists.newArrayList(split); if (pathArgs.size() < 2) { - throw new ResponseException(Status.BAD_REQUEST, - "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'"); + throw new RestconfDocumentedException( + "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } try { @@ -324,7 +306,9 @@ public class RestconfImpl implements RestconfService { return QName.create(null, moduleRevision, moduleName); } catch (ParseException e) { - throw new ResponseException(Status.BAD_REQUEST, "URI has bad format. It should be \'moduleName/yyyy-MM-dd\'"); + throw new RestconfDocumentedException( + "URI has bad format. It should be \'moduleName/yyyy-MM-dd\'", + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } } @@ -396,66 +380,6 @@ public class RestconfImpl implements RestconfService { return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.getQName(), null, moduleNodeValues); } - private DataSchemaNode getSchemaNode(final Module restconfModule, final String schemaNodeName) { - Set groupings = restconfModule.getGroupings(); - - final Predicate filter = new Predicate() { - @Override - public boolean apply(final GroupingDefinition g) { - return Objects.equal(g.getQName().getLocalName(), - RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE); - } - }; - - Iterable filteredGroups = Iterables.filter(groupings, filter); - - final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null); - - List instanceDataChildrenByName = - this.controllerContext.findInstanceDataChildrenByName(restconfGrouping, - RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE); - final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null); - - if (Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE)) { - List instances = - this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), - RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE); - return Iterables.getFirst(instances, null); - } - else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE)) { - List instances = - this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), - RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE); - return Iterables.getFirst(instances, null); - } - else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE)) { - List instances = - this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), - RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE); - final DataSchemaNode modules = Iterables.getFirst(instances, null); - instances = this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) modules), - RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE); - return Iterables.getFirst(instances, null); - } - else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE)) { - List instances = - this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), - RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE); - return Iterables.getFirst(instances, null); - } - else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)) { - List instances = - this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), - RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE); - final DataSchemaNode modules = Iterables.getFirst(instances, null); - instances = this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) modules), - RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE); - return Iterables.getFirst(instances, null); - } - - return null; - } - @Override public Object getRoot() { return null; @@ -463,113 +387,187 @@ public class RestconfImpl implements RestconfService { @Override public StructuredData invokeRpc(final String identifier, final CompositeNode payload) { - final RpcDefinition rpc = this.resolveIdentifierInInvokeRpc(identifier); - if (Objects.equal(rpc.getQName().getNamespace().toString(), SAL_REMOTE_NAMESPACE) && - Objects.equal(rpc.getQName().getLocalName(), SAL_REMOTE_RPC_SUBSRCIBE)) { + final RpcExecutor rpc = this.resolveIdentifierInInvokeRpc(identifier); + QName rpcName = rpc.getRpcDefinition().getQName(); + 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()); + } - final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null); - final SimpleNode pathNode = value == null ? null : - value.getFirstSimpleByName( QName.create(rpc.getQName(), "path") ); - final Object pathValue = pathNode == null ? null : pathNode.getValue(); + validateInput( rpc.getRpcDefinition().getInput(), payload ); - if (!(pathValue instanceof InstanceIdentifier)) { - throw new ResponseException(Status.INTERNAL_SERVER_ERROR, - "Instance identifier was not normalized correctly."); - } + return callRpc(rpc, payload); + } - final InstanceIdentifier pathIdentifier = ((InstanceIdentifier) pathValue); - String streamName = null; - if (!Iterables.isEmpty(pathIdentifier.getPath())) { - String fullRestconfIdentifier = this.controllerContext.toFullRestconfIdentifier(pathIdentifier); - streamName = Notificator.createStreamNameFromUri(fullRestconfIdentifier); - } + 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. + //} + } - if (Strings.isNullOrEmpty(streamName)) { - throw new ResponseException(Status.BAD_REQUEST, - "Path is empty or contains data node which is not Container or List build-in type."); - } + private StructuredData invokeSalRemoteRpcSubscribeRPC(final CompositeNode payload, + final RpcDefinition rpc) { + final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null); + final SimpleNode pathNode = value == null ? null : + value.getFirstSimpleByName( QName.create(rpc.getQName(), "path") ); + final Object pathValue = pathNode == null ? null : pathNode.getValue(); - final SimpleNode streamNameNode = NodeFactory.createImmutableSimpleNode( - QName.create(rpc.getOutput().getQName(), "stream-name"), null, streamName); - final List> output = new ArrayList>(); - output.add(streamNameNode); + if (!(pathValue instanceof InstanceIdentifier)) { + throw new RestconfDocumentedException( + "Instance identifier was not normalized correctly.", + ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED ); + } - final MutableCompositeNode responseData = NodeFactory.createMutableCompositeNode( - rpc.getOutput().getQName(), null, output, null, null); + final InstanceIdentifier pathIdentifier = ((InstanceIdentifier) pathValue); + String streamName = null; + if (!Iterables.isEmpty(pathIdentifier.getPath())) { + String fullRestconfIdentifier = this.controllerContext.toFullRestconfIdentifier(pathIdentifier); + streamName = Notificator.createStreamNameFromUri(fullRestconfIdentifier); + } - if (!Notificator.existListenerFor(pathIdentifier)) { - Notificator.createListener(pathIdentifier, streamName); - } + if (Strings.isNullOrEmpty(streamName)) { + throw new RestconfDocumentedException( + "Path is empty or contains data node which is not Container or List build-in type.", + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); + } - return new StructuredData(responseData, rpc.getOutput(), null); + final SimpleNode streamNameNode = NodeFactory.createImmutableSimpleNode( + QName.create(rpc.getOutput().getQName(), "stream-name"), null, streamName); + final List> output = new ArrayList>(); + output.add(streamNameNode); + + final MutableCompositeNode responseData = NodeFactory.createMutableCompositeNode( + rpc.getOutput().getQName(), null, output, null, null); + + if (!Notificator.existListenerFor(pathIdentifier)) { + Notificator.createListener(pathIdentifier, streamName); } - RpcDefinition rpcDefinition = this.controllerContext.getRpcDefinition(identifier); - return this.callRpc(rpcDefinition, payload); + return new StructuredData(responseData, rpc.getOutput(), null); } @Override public StructuredData invokeRpc(final String identifier, final String noPayload) { - if (!Strings.isNullOrEmpty(noPayload)) { - throw new ResponseException(Status.UNSUPPORTED_MEDIA_TYPE, - "Content-Type contains unsupported Media Type."); + if (StringUtils.isNotBlank(noPayload)) { + throw new RestconfDocumentedException( + "Content must be empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } - - final RpcDefinition rpc = this.resolveIdentifierInInvokeRpc(identifier); - return this.callRpc(rpc, null); + return invokeRpc( identifier, (CompositeNode)null ); } - private RpcDefinition resolveIdentifierInInvokeRpc(final String identifier) { - if (identifier.indexOf("/") < 0) { - final String identifierDecoded = this.controllerContext.urlPathArgDecode(identifier); - final RpcDefinition rpc = this.controllerContext.getRpcDefinition(identifierDecoded); - if (rpc != null) { - return rpc; - } + private RpcExecutor resolveIdentifierInInvokeRpc(final String identifier) { + String identifierEncoded = null; + MountInstance mountPoint = null; + if (identifier.contains(ControllerContext.MOUNT)) { + // mounted RPC call - look up mount instance. + InstanceIdWithSchemaNode mountPointId = controllerContext + .toMountPointIdentifier(identifier); + mountPoint = mountPointId.getMountPoint(); + + int startOfRemoteRpcName = identifier.lastIndexOf(ControllerContext.MOUNT) + + ControllerContext.MOUNT.length() + 1; + String remoteRpcName = identifier.substring(startOfRemoteRpcName); + identifierEncoded = remoteRpcName; + + } else if (identifier.indexOf("/") != CHAR_NOT_FOUND) { + final String slashErrorMsg = String + .format("Identifier %n%s%ncan\'t contain slash " + + "character (/).%nIf slash is part of identifier name then use %%2F placeholder.", + identifier); + throw new RestconfDocumentedException( + slashErrorMsg, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); + } else { + identifierEncoded = identifier; + } + + final String identifierDecoded = controllerContext.urlPathArgDecode(identifierEncoded); + RpcDefinition rpc = controllerContext.getRpcDefinition(identifierDecoded); - throw new ResponseException(Status.NOT_FOUND, "RPC does not exist."); + if (rpc == null) { + throw new RestconfDocumentedException( + "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT ); } - final String slashErrorMsg = String.format( - "Identifier %n%s%ncan\'t contain slash character (/).%nIf slash is part of identifier name then use %%2F placeholder.", - identifier); + if (mountPoint == null) { + return new BrokerRpcExecutor(rpc, broker); + } else { + return new MountPointRpcExecutor(rpc, mountPoint); + } - throw new ResponseException(Status.NOT_FOUND, slashErrorMsg); } - private StructuredData callRpc(final RpcDefinition rpc, final CompositeNode payload) { - if (rpc == null) { - throw new ResponseException(Status.NOT_FOUND, "RPC does not exist."); + private StructuredData callRpc(final RpcExecutor rpcExecutor, final CompositeNode payload) { + if (rpcExecutor == null) { + throw new RestconfDocumentedException( + "RPC does not exist.", ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT ); } CompositeNode rpcRequest = null; + RpcDefinition rpc = rpcExecutor.getRpcDefinition(); + QName rpcName = rpc.getQName(); + if (payload == null) { - rpcRequest = NodeFactory.createMutableCompositeNode(rpc.getQName(), null, null, null, null); - } - else { + rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, null, null, null); + } else { final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null); - final List> input = new ArrayList>(); - input.add(value); - - rpcRequest = NodeFactory.createMutableCompositeNode(rpc.getQName(), null, input, null, null); + List> input = Collections.> singletonList(value); + rpcRequest = NodeFactory.createMutableCompositeNode(rpcName, null, input, null, null); } - final RpcResult rpcResult = broker.invokeRpc(rpc.getQName(), rpcRequest); + RpcResult rpcResult = rpcExecutor.invokeRpc(rpcRequest); - if (!rpcResult.isSuccessful()) { - throw new ResponseException(Status.INTERNAL_SERVER_ERROR, "Operation failed"); - } + checkRpcSuccessAndThrowException(rpcResult); - CompositeNode result = rpcResult.getResult(); - if (result == null) { + if (rpcResult.getResult() == null) { return null; } - return new StructuredData(result, rpc.getOutput(), null); + if( rpc.getOutput() == null ) + { + return null; //no output, nothing to send back. + } + + return new StructuredData(rpcResult.getResult(), rpc.getOutput(), null); + } + + private void checkRpcSuccessAndThrowException(RpcResult rpcResult) { + if (rpcResult.isSuccessful() == false) { + + Collection 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 errorList = Lists.newArrayList(); + for( RpcError rpcError: rpcErrors ) { + errorList.add( new RestconfError( rpcError ) ); + } + + throw new RestconfDocumentedException( errorList ); + } } @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(); @@ -580,11 +578,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 pruneDataAtDepth( T node, Integer depth ) { + if( depth == null ) { + return node; + } + + if( node instanceof CompositeNode ) { + ImmutableList.Builder> newChildNodes = ImmutableList.> 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(); @@ -595,12 +639,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 status = null; @@ -614,7 +662,7 @@ public class RestconfImpl implements RestconfService { } } catch( Exception e ) { - throw new ResponseException( e, "Error updating data" ); + throw new RestconfDocumentedException( "Error updating data", e ); } if( status.getResult() == TransactionStatus.COMMITED ) @@ -625,10 +673,17 @@ 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 ResponseException(Status.BAD_REQUEST, - "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)"); + throw new RestconfDocumentedException( + "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)", + ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE ); } InstanceIdWithSchemaNode iiWithData = null; @@ -637,9 +692,10 @@ public class RestconfImpl implements RestconfService { // payload represents mount point data and URI represents path to the mount point if (this.endsWithMountPoint(identifier)) { - throw new ResponseException(Status.BAD_REQUEST, - "URI has bad format. URI should be without \"" + ControllerContext.MOUNT + - "\" for POST operation."); + throw new RestconfDocumentedException( + "URI has bad format. URI should be without \"" + ControllerContext.MOUNT + + "\" for POST operation.", + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } final String completeIdentifier = this.addMountPointIdentifier(identifier); @@ -654,8 +710,9 @@ public class RestconfImpl implements RestconfService { MountInstance mountPoint = incompleteInstIdWithData.getMountPoint(); final Module module = this.findModule(mountPoint, payload); if (module == null) { - throw new ResponseException(Status.BAD_REQUEST, - "Module was not found for \"" + payloadNS + "\""); + throw new RestconfDocumentedException( + "Module was not found for \"" + payloadNS + "\"", + ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT ); } String payloadName = this.getName(payload); @@ -682,7 +739,7 @@ public class RestconfImpl implements RestconfService { } } catch( Exception e ) { - throw new ResponseException( e, "Error creating data" ); + throw new RestconfDocumentedException( "Error creating data", e ); } if (status == null) { @@ -697,16 +754,24 @@ 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 ResponseException(Status.BAD_REQUEST, - "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)"); + throw new RestconfDocumentedException( + "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)", + ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE ); } final Module module = this.findModule(null, payload); if (module == null) { - throw new ResponseException(Status.BAD_REQUEST, - "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)"); + throw new RestconfDocumentedException( + "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)", + ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE ); } String payloadName = this.getName(payload); @@ -731,7 +796,7 @@ public class RestconfImpl implements RestconfService { } } catch( Exception e ) { - throw new ResponseException( e, "Error creating data" ); + throw new RestconfDocumentedException( "Error creating data", e ); } if (status == null) { @@ -760,7 +825,7 @@ public class RestconfImpl implements RestconfService { } } catch( Exception e ) { - throw new ResponseException( e, "Error creating data" ); + throw new RestconfDocumentedException( "Error creating data", e ); } if( status.getResult() == TransactionStatus.COMMITED ) @@ -773,12 +838,14 @@ public class RestconfImpl implements RestconfService { public Response subscribeToStream(final String identifier, final UriInfo uriInfo) { final String streamName = Notificator.createStreamNameFromUri(identifier); if (Strings.isNullOrEmpty(streamName)) { - throw new ResponseException(Status.BAD_REQUEST, "Stream name is empty."); + throw new RestconfDocumentedException( + "Stream name is empty.", ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } final ListenerAdapter listener = Notificator.getListenerFor(streamName); if (listener == null) { - throw new ResponseException(Status.BAD_REQUEST, "Stream was not found."); + throw new RestconfDocumentedException( + "Stream was not found.", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT ); } broker.registerToListenDataChanges(listener); @@ -882,9 +949,10 @@ public class RestconfImpl implements RestconfService { } if (dataNodeKeyValueObject == null) { - throw new ResponseException(Status.BAD_REQUEST, - "Data contains list \"" + dataNode.getNodeType().getLocalName() + - "\" which does not contain key: \"" + key.getLocalName() + "\""); + 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); @@ -919,14 +987,16 @@ public class RestconfImpl implements RestconfService { if (schema == null) { QName nodeType = node == null ? null : node.getNodeType(); String localName = nodeType == null ? null : nodeType.getLocalName(); - String _plus = ("Data schema node was not found for " + localName); - throw new ResponseException(Status.INTERNAL_SERVER_ERROR, - "Data schema node was not found for " + localName ); + + throw new RestconfDocumentedException( + "Data schema node was not found for " + localName, + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } if (!(schema instanceof DataNodeContainer)) { - throw new ResponseException(Status.BAD_REQUEST, - "Root element has to be container or list yang datatype."); + throw new RestconfDocumentedException( + "Root element has to be container or list yang datatype.", + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } if ((node instanceof CompositeNodeWrapper)) { @@ -935,8 +1005,9 @@ public class RestconfImpl implements RestconfService { try { this.normalizeNode(((CompositeNodeWrapper) node), schema, null, mountPoint); } - catch (NumberFormatException e) { - throw new ResponseException(Status.BAD_REQUEST, e.getMessage()); + catch (IllegalArgumentException e) { + throw new RestconfDocumentedException( + e.getMessage(), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } } @@ -950,9 +1021,10 @@ public class RestconfImpl implements RestconfService { final DataSchemaNode schema, final QName previousAugment, final MountInstance mountPoint) { if (schema == null) { - throw new ResponseException(Status.BAD_REQUEST, - "Data has bad format.\n\"" + nodeBuilder.getLocalName() + - "\" does not exist in yang schema."); + throw new RestconfDocumentedException( + "Data has bad format.\n\"" + nodeBuilder.getLocalName() + + "\" does not exist in yang schema.", + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } QName currentAugment = null; @@ -962,104 +1034,138 @@ public class RestconfImpl implements RestconfService { else { currentAugment = this.normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint); if (nodeBuilder.getQname() == null) { - throw new ResponseException(Status.BAD_REQUEST, + throw new RestconfDocumentedException( "Data has bad format.\nIf data is in XML format then namespace for \"" + nodeBuilder.getLocalName() + "\" should be \"" + schema.getQName().getNamespace() + "\".\n" + "If data is in JSON format then module name for \"" + nodeBuilder.getLocalName() + "\" should be corresponding to namespace \"" + - schema.getQName().getNamespace() + "\"."); + schema.getQName().getNamespace() + "\".", + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } } - if ((nodeBuilder instanceof CompositeNodeWrapper)) { - final List> children = ((CompositeNodeWrapper) nodeBuilder).getValues(); - for (final NodeWrapper child : children) { - final List potentialSchemaNodes = - this.controllerContext.findInstanceDataChildrenByName( - ((DataNodeContainer) schema), child.getLocalName()); + if ( nodeBuilder instanceof CompositeNodeWrapper ) { + if( schema instanceof DataNodeContainer ) { + normalizeCompositeNode( (CompositeNodeWrapper)nodeBuilder, (DataNodeContainer)schema, + mountPoint, currentAugment ); + } + else if( schema instanceof AnyXmlSchemaNode ) { + normalizeAnyXmlNode( (CompositeNodeWrapper)nodeBuilder, (AnyXmlSchemaNode)schema ); + } + } + else if ( nodeBuilder instanceof SimpleNodeWrapper ) { + normalizeSimpleNode( (SimpleNodeWrapper) nodeBuilder, schema, mountPoint ); + } + else if ((nodeBuilder instanceof EmptyNodeWrapper)) { + normalizeEmptyNode( (EmptyNodeWrapper) nodeBuilder, schema ); + } + } - if (potentialSchemaNodes.size() > 1 && child.getNamespace() == null) { - StringBuilder builder = new StringBuilder(); - for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) { - builder.append(" ").append(potentialSchemaNode.getQName().getNamespace().toString()) - .append("\n"); - } + private void normalizeAnyXmlNode( CompositeNodeWrapper compositeNode, AnyXmlSchemaNode schema ) { + List> children = compositeNode.getValues(); + for( NodeWrapper child : children ) { + child.setNamespace( schema.getQName().getNamespace() ); + if( child instanceof CompositeNodeWrapper ) { + normalizeAnyXmlNode( (CompositeNodeWrapper)child, schema ); + } + } + } - throw new ResponseException(Status.BAD_REQUEST, - "Node \"" + child.getLocalName() + - "\" is added as augment from more than one module. " + - "Therefore node must have namespace (XML format) or module name (JSON format)." + - "\nThe node is added as augment from modules with namespaces:\n" + builder); - } + private void normalizeEmptyNode( EmptyNodeWrapper emptyNodeBuilder, DataSchemaNode schema ) { + if ((schema instanceof LeafSchemaNode)) { + emptyNodeBuilder.setComposite(false); + } + else { + if ((schema instanceof ContainerSchemaNode)) { + // FIXME: Add presence check + emptyNodeBuilder.setComposite(true); + } + } + } + + private void normalizeSimpleNode( SimpleNodeWrapper simpleNode, DataSchemaNode schema, + MountInstance mountPoint ) { + final Object value = simpleNode.getValue(); + Object inputValue = value; + TypeDefinition typeDefinition = this.typeDefinition(schema); + if ((typeDefinition instanceof IdentityrefTypeDefinition)) { + if ((value instanceof String)) { + inputValue = new IdentityValuesDTO( simpleNode.getNamespace().toString(), + (String) value, null, (String) value ); + } // else value is already instance of IdentityValuesDTO + } + + Object outputValue = inputValue; + + if( typeDefinition != null ) { + Codec codec = RestCodec.from(typeDefinition, mountPoint); + outputValue = codec == null ? null : codec.deserialize(inputValue); + } - boolean rightNodeSchemaFound = false; + simpleNode.setValue(outputValue); + } + + private void normalizeCompositeNode( CompositeNodeWrapper compositeNodeBuilder, + DataNodeContainer schema, MountInstance mountPoint, + QName currentAugment ) { + final List> children = compositeNodeBuilder.getValues(); + for (final NodeWrapper child : children) { + final List potentialSchemaNodes = + this.controllerContext.findInstanceDataChildrenByName( + schema, child.getLocalName()); + + if (potentialSchemaNodes.size() > 1 && child.getNamespace() == null) { + StringBuilder builder = new StringBuilder(); for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) { - if (!rightNodeSchemaFound) { - final QName potentialCurrentAugment = - this.normalizeNodeName(child, potentialSchemaNode, currentAugment, mountPoint); - if (child.getQname() != null ) { - this.normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint); - rightNodeSchemaFound = true; - } - } + builder.append(" ").append(potentialSchemaNode.getQName().getNamespace().toString()) + .append("\n"); } - if (!rightNodeSchemaFound) { - throw new ResponseException(Status.BAD_REQUEST, - "Schema node \"" + child.getLocalName() + "\" was not found in module."); - } + throw new RestconfDocumentedException( + "Node \"" + child.getLocalName() + + "\" is added as augment from more than one module. " + + "Therefore node must have namespace (XML format) or module name (JSON format)." + + "\nThe node is added as augment from modules with namespaces:\n" + builder, + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); } - if ((schema instanceof ListSchemaNode)) { - final List listKeys = ((ListSchemaNode) schema).getKeyDefinition(); - for (final QName listKey : listKeys) { - boolean foundKey = false; - for (final NodeWrapper child : children) { - if (Objects.equal(child.unwrap().getNodeType().getLocalName(), listKey.getLocalName())) { - foundKey = true; - } - } - - if (!foundKey) { - throw new ResponseException(Status.BAD_REQUEST, - "Missing key in URI \"" + listKey.getLocalName() + - "\" of list \"" + schema.getQName().getLocalName() + "\""); + boolean rightNodeSchemaFound = false; + for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) { + if (!rightNodeSchemaFound) { + final QName potentialCurrentAugment = + this.normalizeNodeName(child, potentialSchemaNode, currentAugment, mountPoint); + if (child.getQname() != null ) { + this.normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint); + rightNodeSchemaFound = true; } } } - } - else { - if ((nodeBuilder instanceof SimpleNodeWrapper)) { - final SimpleNodeWrapper simpleNode = ((SimpleNodeWrapper) nodeBuilder); - final Object value = simpleNode.getValue(); - Object inputValue = value; - TypeDefinition typeDefinition = this.typeDefinition(schema); - if ((typeDefinition instanceof IdentityrefTypeDefinition)) { - if ((value instanceof String)) { - inputValue = new IdentityValuesDTO( nodeBuilder.getNamespace().toString(), - (String) value, null, (String) value ); - } // else value is already instance of IdentityValuesDTO - } - - Codec codec = RestCodec.from(typeDefinition, mountPoint); - Object outputValue = codec == null ? null : codec.deserialize(inputValue); - simpleNode.setValue(outputValue); + if (!rightNodeSchemaFound) { + throw new RestconfDocumentedException( + "Schema node \"" + child.getLocalName() + "\" was not found in module.", + ErrorType.APPLICATION, ErrorTag.UNKNOWN_ELEMENT ); } - else { - if ((nodeBuilder instanceof EmptyNodeWrapper)) { - final EmptyNodeWrapper emptyNodeBuilder = ((EmptyNodeWrapper) nodeBuilder); - if ((schema instanceof LeafSchemaNode)) { - emptyNodeBuilder.setComposite(false); - } - else { - if ((schema instanceof ContainerSchemaNode)) { - // FIXME: Add presence check - emptyNodeBuilder.setComposite(true); - } + } + + if ((schema instanceof ListSchemaNode)) { + ListSchemaNode listSchemaNode = (ListSchemaNode)schema; + final List listKeys = listSchemaNode.getKeyDefinition(); + for (final QName listKey : listKeys) { + boolean foundKey = false; + for (final NodeWrapper child : children) { + if (Objects.equal(child.unwrap().getNodeType().getLocalName(), listKey.getLocalName())) { + foundKey = true; } } + + if (!foundKey) { + throw new RestconfDocumentedException( + "Missing key in URI \"" + listKey.getLocalName() + + "\" of list \"" + listSchemaNode.getQName().getLocalName() + "\"", + ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE ); + } } } } @@ -1162,6 +1268,9 @@ public class RestconfImpl implements RestconfService { else if (node instanceof LeafSchemaNode) { return _typeDefinition((LeafSchemaNode)node); } + else if (node instanceof AnyXmlSchemaNode) { + return null; + } else { throw new IllegalArgumentException("Unhandled parameter types: " + Arrays.asList(node).toString());