From: Jakub Toth Date: Wed, 16 Nov 2016 13:47:33 +0000 (+0100) Subject: Bug 6951 - Implement Query parameters - with-defaults X-Git-Tag: release/carbon~123 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F07%2F48407%2F2;p=netconf.git Bug 6951 - Implement Query parameters - with-defaults * fixed tests Change-Id: Idae6981b7fd0df3e690965b063b7bd3975d049c5 Signed-off-by: Jakub Toth --- diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/api/JSONRestconfService.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/api/JSONRestconfService.java index 1d08d89db5..d3f9187419 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/api/JSONRestconfService.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/api/JSONRestconfService.java @@ -61,7 +61,8 @@ public interface JSONRestconfService { * @return an Optional containing the data in JSON format if present. * @throws OperationFailedException if the request fails. */ - Optional get(String uriPath, LogicalDatastoreType datastoreType) throws OperationFailedException; + Optional get(String uriPath, LogicalDatastoreType datastoreType, UriInfo uriInfo) + throws OperationFailedException; /** * Invokes a yang-defined RPC. diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java index f0fa715f0f..9409320251 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/BrokerFacade.java @@ -43,8 +43,14 @@ import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType; import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter; import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter; import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +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.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; @@ -52,11 +58,17 @@ import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.OrderedLeafSetNode; import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder; import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode; import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaPath; @@ -96,28 +108,89 @@ public class BrokerFacade { } } - // READ configuration + /** + * Read config data by path + * + * @param path + * - path of data + * @return read date + */ public NormalizedNode readConfigurationData(final YangInstanceIdentifier path) { + return readConfigurationData(path, null); + } + + /** + * Read config data by path + * + * @param path + * - path of data + * @param withDefa + * - value of with-defaults parameter + * @return read date + */ + public NormalizedNode readConfigurationData(final YangInstanceIdentifier path, final String withDefa) { checkPreconditions(); - return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path); + return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path, withDefa); } - public NormalizedNode readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) { + /** + * Read config data from mount point by path. + * + * @param mountPoint + * - mount point for reading data + * @param path + * - path of data + * @return read data + */ + public NormalizedNode readConfigurationData(final DOMMountPoint mountPoint, + final YangInstanceIdentifier path) { + return readConfigurationData(mountPoint, path, null); + } + + /** + * Read config data from mount point by path. + * + * @param mountPoint + * - mount point for reading data + * @param path + * - path of data + * @param withDefa + * - value of with-defaults parameter + * @return read data + */ + public NormalizedNode readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path, + final String withDefa) { final Optional domDataBrokerService = mountPoint.getService(DOMDataBroker.class); if (domDataBrokerService.isPresent()) { - return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), CONFIGURATION, path); + return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), CONFIGURATION, path, + withDefa); } final String errMsg = "DOM data broker service isn't available for mount point " + path; LOG.warn(errMsg); throw new RestconfDocumentedException(errMsg); } - // READ operational + /** + * Read operational data by path. + * + * @param path + * - path of data + * @return read data + */ public NormalizedNode readOperationalData(final YangInstanceIdentifier path) { checkPreconditions(); return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path); } + /** + * Read operational data from mount point by path. + * + * @param mountPoint + * - mount point for reading data + * @param path + * - path of data + * @return read data + */ public NormalizedNode readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) { final Optional domDataBrokerService = mountPoint.getService(DOMDataBroker.class); if (domDataBrokerService.isPresent()) { @@ -440,6 +513,11 @@ public class BrokerFacade { private NormalizedNode readDataViaTransaction(final DOMDataReadTransaction transaction, final LogicalDatastoreType datastore, final YangInstanceIdentifier path) { + return readDataViaTransaction(transaction, datastore, path, null); + } + + private NormalizedNode readDataViaTransaction(final DOMDataReadTransaction transaction, + final LogicalDatastoreType datastore, final YangInstanceIdentifier path, final String withDefa) { LOG.trace("Read {} via Restconf: {}", datastore.name(), path); final ListenableFuture>> listenableFuture = transaction.read(datastore, path); final ReadDataResult> readData = new ReadDataResult<>(); @@ -467,7 +545,135 @@ public class BrokerFacade { LOG.warn(msg); throw new RestconfDocumentedException(msg, e); } - return readData.getResult(); + if (withDefa == null) { + return readData.getResult(); + } else { + return prepareDataByParamWithDef(readData.getResult(), path, withDefa); + } + + } + + private NormalizedNode prepareDataByParamWithDef(final NormalizedNode result, + final YangInstanceIdentifier path, final String withDefa) { + boolean trim; + switch (withDefa) { + case "trim": + trim = true; + break; + case "explicit": + trim = false; + break; + default: + throw new RestconfDocumentedException("Bad value used with with-defaults parameter : " + withDefa); + } + + final SchemaContext ctx = ControllerContext.getInstance().getGlobalSchema(); + final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx); + final DataSchemaNode baseSchemaNode = baseSchemaCtxTree.getChild(path).getDataSchemaNode(); + if (result instanceof ContainerNode) { + final DataContainerNodeAttrBuilder builder = + Builders.containerBuilder((ContainerSchemaNode) baseSchemaNode); + buildCont(builder, (ContainerNode) result, baseSchemaCtxTree, path, trim); + return builder.build(); + } else { + final DataContainerNodeAttrBuilder builder = + Builders.mapEntryBuilder((ListSchemaNode) baseSchemaNode); + buildMapEntryBuilder(builder, (MapEntryNode) result, baseSchemaCtxTree, path, trim, + ((ListSchemaNode) baseSchemaNode).getKeyDefinition()); + return builder.build(); + } + } + + private void buildMapEntryBuilder(final DataContainerNodeAttrBuilder builder, + final MapEntryNode result, final DataSchemaContextTree baseSchemaCtxTree, + final YangInstanceIdentifier actualPath, final boolean trim, final List keys) { + for (final DataContainerChild child : result.getValue()) { + final YangInstanceIdentifier path = actualPath.node(child.getIdentifier()); + final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(path).getDataSchemaNode(); + if (child instanceof ContainerNode) { + final DataContainerNodeAttrBuilder childBuilder = + Builders.containerBuilder((ContainerSchemaNode) childSchema); + buildCont(childBuilder, (ContainerNode) child, baseSchemaCtxTree, path, trim); + builder.withChild(childBuilder.build()); + } else if (child instanceof MapNode) { + final CollectionNodeBuilder childBuilder = + Builders.mapBuilder((ListSchemaNode) childSchema); + buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim, + ((ListSchemaNode) childSchema).getKeyDefinition()); + builder.withChild(childBuilder.build()); + } else if (child instanceof LeafNode) { + final String defaultVal = ((LeafSchemaNode) childSchema).getDefault(); + final String nodeVal = ((LeafNode) child).getValue(); + final NormalizedNodeAttrBuilder> leafBuilder = + Builders.leafBuilder((LeafSchemaNode) childSchema); + if (keys.contains(child.getNodeType())) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } else { + if (trim) { + if ((defaultVal == null) || !defaultVal.equals(nodeVal)) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } + } else { + if ((defaultVal != null) && defaultVal.equals(nodeVal)) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } + } + } + } + } + } + + private void buildList(final CollectionNodeBuilder builder, final MapNode result, + final DataSchemaContextTree baseSchemaCtxTree, final YangInstanceIdentifier path, final boolean trim, + final List keys) { + for (final MapEntryNode mapEntryNode : result.getValue()) { + final YangInstanceIdentifier actualNode = path.node(mapEntryNode.getIdentifier()); + final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(actualNode).getDataSchemaNode(); + final DataContainerNodeAttrBuilder mapEntryBuilder = + Builders.mapEntryBuilder((ListSchemaNode) childSchema); + buildMapEntryBuilder(mapEntryBuilder, mapEntryNode, baseSchemaCtxTree, actualNode, trim, keys); + builder.withChild(mapEntryBuilder.build()); + } + } + + private void buildCont(final DataContainerNodeAttrBuilder builder, + final ContainerNode result, final DataSchemaContextTree baseSchemaCtxTree, + final YangInstanceIdentifier actualPath, final boolean trim) { + for (final DataContainerChild child : result.getValue()) { + final YangInstanceIdentifier path = actualPath.node(child.getIdentifier()); + final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(path).getDataSchemaNode(); + if(child instanceof ContainerNode){ + final DataContainerNodeAttrBuilder builderChild = + Builders.containerBuilder((ContainerSchemaNode) childSchema); + buildCont(builderChild, result, baseSchemaCtxTree, actualPath, trim); + builder.withChild(builderChild.build()); + } else if (child instanceof MapNode) { + final CollectionNodeBuilder childBuilder = + Builders.mapBuilder((ListSchemaNode) childSchema); + buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim, + ((ListSchemaNode) childSchema).getKeyDefinition()); + builder.withChild(childBuilder.build()); + } else if (child instanceof LeafNode) { + final String defaultVal = ((LeafSchemaNode) childSchema).getDefault(); + final String nodeVal = ((LeafNode) child).getValue(); + final NormalizedNodeAttrBuilder> leafBuilder = + Builders.leafBuilder((LeafSchemaNode) childSchema); + if (trim) { + if ((defaultVal == null) || !defaultVal.equals(nodeVal)) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } + } else { + if ((defaultVal != null) && defaultVal.equals(nodeVal)) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } + } + } + } } /** diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java index 57457519f6..9c9084fc2f 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/JSONRestconfServiceImpl.java @@ -92,15 +92,16 @@ public class JSONRestconfServiceImpl implements JSONRestconfService, AutoCloseab } @Override - public Optional get(final String uriPath, final LogicalDatastoreType datastoreType) throws OperationFailedException { + public Optional get(final String uriPath, final LogicalDatastoreType datastoreType, final UriInfo uriInfo) + throws OperationFailedException { LOG.debug("get: uriPath: {}", uriPath); try { NormalizedNodeContext readData; if(datastoreType == LogicalDatastoreType.CONFIGURATION) { - readData = RestconfImpl.getInstance().readConfigurationData(uriPath, null); + readData = RestconfImpl.getInstance().readConfigurationData(uriPath, uriInfo); } else { - readData = RestconfImpl.getInstance().readOperationalData(uriPath, null); + readData = RestconfImpl.getInstance().readOperationalData(uriPath, uriInfo); } final Optional result = Optional.of(toJson(readData)); diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java index 082ba18e81..70d9280c42 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/QueryParametersParser.java @@ -24,12 +24,17 @@ public class QueryParametersParser { @Override public String toString() { - return uriParameterName; + return this.uriParameterName; } } public static WriterParameters parseWriterParameters(final UriInfo info) { - WriterParameters.WriterParametersBuilder wpBuilder = new WriterParameters.WriterParametersBuilder(); + return parseParams(info, false); + } + + private static WriterParameters parseParams(final UriInfo info, final boolean tagged) { + final WriterParameters.WriterParametersBuilder wpBuilder = new WriterParameters.WriterParametersBuilder(); + wpBuilder.setTagged(tagged); if(info == null) { return wpBuilder.build(); } @@ -55,4 +60,8 @@ public class QueryParametersParser { return wpBuilder.build(); } + public static WriterParameters parseWriterParameters(final UriInfo uriInfo, final boolean tagged) { + return parseParams(uriInfo, tagged); + } + } diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java index 2f724ec26a..637d345dc7 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/RestconfImpl.java @@ -674,21 +674,48 @@ public class RestconfImpl implements RestconfService { @Override public NormalizedNodeContext readConfigurationData(final String identifier, final UriInfo uriInfo) { + boolean withDefa_used = false; + String withDefa = null; + + for (final Entry> entry : uriInfo.getQueryParameters().entrySet()) { + switch (entry.getKey()) { + case "with-defaults": + if (!withDefa_used) { + withDefa_used = true; + withDefa = entry.getValue().iterator().next(); + } else { + throw new RestconfDocumentedException("With-defaults parameter can be used only once."); + } + break; + } + } + boolean tagged = false; + if (withDefa_used) { + if (withDefa.equals("report-all-tagged")) { + tagged = true; + withDefa = null; + } + if (withDefa.equals("report-all")) { + withDefa = null; + } + } + final InstanceIdentifierContext iiWithData = this.controllerContext.toInstanceIdentifier(identifier); final DOMMountPoint mountPoint = iiWithData.getMountPoint(); NormalizedNode data = null; final YangInstanceIdentifier normalizedII = iiWithData.getInstanceIdentifier(); if (mountPoint != null) { - data = this.broker.readConfigurationData(mountPoint, normalizedII); + data = this.broker.readConfigurationData(mountPoint, normalizedII, withDefa); } else { - data = this.broker.readConfigurationData(normalizedII); + data = this.broker.readConfigurationData(normalizedII, withDefa); } if(data == null) { final String errMsg = "Request could not be completed because the relevant data model content does not exist "; LOG.debug(errMsg + identifier); throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.DATA_MISSING); } - return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo)); + return new NormalizedNodeContext(iiWithData, data, + QueryParametersParser.parseWriterParameters(uriInfo, tagged)); } @Override @@ -739,7 +766,7 @@ public class RestconfImpl implements RestconfService { default: throw new RestconfDocumentedException("Bad parameter for post: " + entry.getKey()); } - } + } if (point_used && !insert_used) { throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter."); diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/WriterParameters.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/WriterParameters.java index 996f6d9d0d..9d576c466f 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/WriterParameters.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/restconf/impl/WriterParameters.java @@ -17,28 +17,34 @@ public class WriterParameters { private final Integer depth; private final List> fields; private final boolean prettyPrint; + private final boolean tagged; private WriterParameters(final WriterParametersBuilder builder) { this.content = builder.content; this.depth = builder.depth; this.fields = builder.fields; this.prettyPrint = builder.prettyPrint; + this.tagged = builder.tagged; } public String getContent() { - return content; + return this.content; } public Integer getDepth() { - return depth; + return this.depth; } public List> getFields() { - return fields; + return this.fields; } public boolean isPrettyPrint() { - return prettyPrint; + return this.prettyPrint; + } + + public boolean isTagged() { + return this.tagged; } public static class WriterParametersBuilder { @@ -46,6 +52,7 @@ public class WriterParameters { private Integer depth; private List> fields; private boolean prettyPrint; + private boolean tagged; public WriterParametersBuilder() {} @@ -72,5 +79,9 @@ public class WriterParameters { public WriterParameters build() { return new WriterParameters(this); } + + public void setTagged(final boolean tagged) { + this.tagged = tagged; + } } } diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImpl.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImpl.java index bb42fbb9d5..a4d63a23ed 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImpl.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/services/impl/RestconfDataServiceImpl.java @@ -76,8 +76,34 @@ public class RestconfDataServiceImpl implements RestconfDataService { final InstanceIdentifierContext instanceIdentifier = ParserIdentifier.toInstanceIdentifier( identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get())); + boolean withDefa_used = false; + String withDefa = null; + + for (final Entry> entry : uriInfo.getQueryParameters().entrySet()) { + switch (entry.getKey()) { + case "with-defaults": + if (!withDefa_used) { + withDefa_used = true; + withDefa = entry.getValue().iterator().next(); + } else { + throw new RestconfDocumentedException("With-defaults parameter can be used only once."); + } + break; + } + } + boolean tagged = false; + if (withDefa_used) { + if (withDefa.equals("report-all-tagged")) { + tagged = true; + withDefa = null; + } + if (withDefa.equals("report-all")) { + withDefa = null; + } + } + final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters( - instanceIdentifier, uriInfo); + instanceIdentifier, uriInfo, tagged); final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint(); final DOMTransactionChain transactionChain; @@ -89,7 +115,8 @@ public class RestconfDataServiceImpl implements RestconfDataService { final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper( instanceIdentifier, mountPoint, transactionChain); - final NormalizedNode node = ReadDataTransactionUtil.readData(parameters.getContent(), transactionNode); + final NormalizedNode node = + ReadDataTransactionUtil.readData(parameters.getContent(), transactionNode, withDefa); if (node == null) { throw new RestconfDocumentedException( "Request could not be completed because the relevant data model content does not exist", diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ReadDataTransactionUtil.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ReadDataTransactionUtil.java index e18ea09613..56cb546278 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ReadDataTransactionUtil.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ReadDataTransactionUtil.java @@ -21,6 +21,7 @@ import javax.annotation.Nullable; import javax.ws.rs.core.UriInfo; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.netconf.sal.restconf.impl.ControllerContext; import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext; import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException; import org.opendaylight.netconf.sal.restconf.impl.RestconfError; @@ -28,7 +29,9 @@ import org.opendaylight.netconf.sal.restconf.impl.WriterParameters; import org.opendaylight.netconf.sal.restconf.impl.WriterParameters.WriterParametersBuilder; import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper; import org.opendaylight.restconf.utils.parser.ParserFieldsParameter; +import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; @@ -36,6 +39,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgum import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; @@ -45,8 +49,15 @@ import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeAttrBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** * Util class for read data from data store via transaction. @@ -63,6 +74,23 @@ public final class ReadDataTransactionUtil { throw new UnsupportedOperationException("Util class."); } + /** + * Parse parameters from URI request and check their types and values. + * + * + * @param identifier + * - {@link InstanceIdentifierContext} + * @param uriInfo + * - URI info + * @param tagged + * - set tagged for {@link WriterParameters} + * @return {@link WriterParameters} + */ + public static @Nonnull WriterParameters parseUriParameters(@Nonnull final InstanceIdentifierContext identifier, + @Nullable final UriInfo uriInfo, final boolean tagged) { + return parseParams(identifier, uriInfo, tagged); + } + /** * Parse parameters from URI request and check their types and values. * @@ -75,7 +103,13 @@ public final class ReadDataTransactionUtil { */ public static @Nonnull WriterParameters parseUriParameters(@Nonnull final InstanceIdentifierContext identifier, @Nullable final UriInfo uriInfo) { + return parseParams(identifier, uriInfo, false); + } + + private static WriterParameters parseParams(final InstanceIdentifierContext identifier, final UriInfo uriInfo, + final boolean tagged) { final WriterParametersBuilder builder = new WriterParametersBuilder(); + builder.setTagged(tagged); if (uriInfo == null) { return builder.build(); @@ -87,7 +121,7 @@ public final class ReadDataTransactionUtil { uriInfo.getQueryParameters().keySet(), RestconfDataServiceConstant.ReadData.CONTENT, RestconfDataServiceConstant.ReadData.DEPTH, - RestconfDataServiceConstant.ReadData.FIELDS); + RestconfDataServiceConstant.ReadData.FIELDS, RestconfDataServiceConstant.ReadData.WITH_DEFAULTS); // read parameters from URI or set default values final List content = uriInfo.getQueryParameters().getOrDefault( @@ -124,9 +158,9 @@ public final class ReadDataTransactionUtil { if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) { final Integer value = Ints.tryParse(depth.get(0)); - if (value == null - || (!(value >= RestconfDataServiceConstant.ReadData.MIN_DEPTH - && value <= RestconfDataServiceConstant.ReadData.MAX_DEPTH))) { + if ((value == null) + || (!((value >= RestconfDataServiceConstant.ReadData.MIN_DEPTH) + && (value <= RestconfDataServiceConstant.ReadData.MAX_DEPTH)))) { throw new RestconfDocumentedException( new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depth, null, @@ -154,18 +188,38 @@ public final class ReadDataTransactionUtil { * @return {@link NormalizedNode} */ public static @Nullable NormalizedNode readData(@Nonnull final String valueOfContent, - @Nonnull final TransactionVarsWrapper transactionNode) { + @Nonnull final TransactionVarsWrapper transactionNode) { + return readData(valueOfContent, transactionNode, null); + } + + /** + * Read specific type of data from data store via transaction. + * + * @param valueOfContent + * - type of data to read (config, state, all) + * @param transactionNode + * - {@link TransactionVarsWrapper} - wrapper for variables + * @param withDefa + * - vaule of with-defaults parameter + * @return {@link NormalizedNode} + */ + public static @Nullable NormalizedNode readData(@Nonnull final String valueOfContent, + @Nonnull final TransactionVarsWrapper transactionNode, final String withDefa) { switch (valueOfContent) { case RestconfDataServiceConstant.ReadData.CONFIG: transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION); - return readDataViaTransaction(transactionNode); - + if (withDefa == null) { + return readDataViaTransaction(transactionNode); + } else { + return prepareDataByParamWithDef(readDataViaTransaction(transactionNode), + transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa); + } case RestconfDataServiceConstant.ReadData.NONCONFIG: transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL); return readDataViaTransaction(transactionNode); case RestconfDataServiceConstant.ReadData.ALL: - return readAllData(transactionNode); + return readAllData(transactionNode, withDefa); default: throw new RestconfDocumentedException( @@ -175,6 +229,130 @@ public final class ReadDataTransactionUtil { } } + private static NormalizedNode prepareDataByParamWithDef(final NormalizedNode result, + final YangInstanceIdentifier path, final String withDefa) { + boolean trim; + switch (withDefa) { + case "trim": + trim = true; + break; + case "explicit": + trim = false; + break; + default: + throw new RestconfDocumentedException(""); + } + + final SchemaContext ctx = ControllerContext.getInstance().getGlobalSchema(); + final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx); + final DataSchemaNode baseSchemaNode = baseSchemaCtxTree.getChild(path).getDataSchemaNode(); + if (result instanceof ContainerNode) { + final DataContainerNodeAttrBuilder builder = + Builders.containerBuilder((ContainerSchemaNode) baseSchemaNode); + buildCont(builder, (ContainerNode) result, baseSchemaCtxTree, path, trim); + return builder.build(); + } else { + final DataContainerNodeAttrBuilder builder = + Builders.mapEntryBuilder((ListSchemaNode) baseSchemaNode); + buildMapEntryBuilder(builder, (MapEntryNode) result, baseSchemaCtxTree, path, trim, + ((ListSchemaNode) baseSchemaNode).getKeyDefinition()); + return builder.build(); + } + } + + private static void buildMapEntryBuilder( + final DataContainerNodeAttrBuilder builder, + final MapEntryNode result, final DataSchemaContextTree baseSchemaCtxTree, + final YangInstanceIdentifier actualPath, final boolean trim, final List keys) { + for (final DataContainerChild child : result.getValue()) { + final YangInstanceIdentifier path = actualPath.node(child.getIdentifier()); + final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(path).getDataSchemaNode(); + if (child instanceof ContainerNode) { + final DataContainerNodeAttrBuilder childBuilder = + Builders.containerBuilder((ContainerSchemaNode) childSchema); + buildCont(childBuilder, (ContainerNode) child, baseSchemaCtxTree, path, trim); + builder.withChild(childBuilder.build()); + } else if (child instanceof MapNode) { + final CollectionNodeBuilder childBuilder = + Builders.mapBuilder((ListSchemaNode) childSchema); + buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim, + ((ListSchemaNode) childSchema).getKeyDefinition()); + builder.withChild(childBuilder.build()); + } else if (child instanceof LeafNode) { + final String defaultVal = ((LeafSchemaNode) childSchema).getDefault(); + final String nodeVal = ((LeafNode) child).getValue(); + final NormalizedNodeAttrBuilder> leafBuilder = + Builders.leafBuilder((LeafSchemaNode) childSchema); + if (keys.contains(child.getNodeType())) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } else { + if (trim) { + if ((defaultVal == null) || !defaultVal.equals(nodeVal)) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } + } else { + if ((defaultVal != null) && defaultVal.equals(nodeVal)) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } + } + } + } + } + } + + private static void buildList(final CollectionNodeBuilder builder, final MapNode result, + final DataSchemaContextTree baseSchemaCtxTree, final YangInstanceIdentifier path, final boolean trim, + final List keys) { + for (final MapEntryNode mapEntryNode : result.getValue()) { + final YangInstanceIdentifier actualNode = path.node(mapEntryNode.getIdentifier()); + final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(actualNode).getDataSchemaNode(); + final DataContainerNodeAttrBuilder mapEntryBuilder = + Builders.mapEntryBuilder((ListSchemaNode) childSchema); + buildMapEntryBuilder(mapEntryBuilder, mapEntryNode, baseSchemaCtxTree, actualNode, trim, keys); + builder.withChild(mapEntryBuilder.build()); + } + } + + private static void buildCont(final DataContainerNodeAttrBuilder builder, + final ContainerNode result, final DataSchemaContextTree baseSchemaCtxTree, + final YangInstanceIdentifier actualPath, final boolean trim) { + for (final DataContainerChild child : result.getValue()) { + final YangInstanceIdentifier path = actualPath.node(child.getIdentifier()); + final DataSchemaNode childSchema = baseSchemaCtxTree.getChild(path).getDataSchemaNode(); + if (child instanceof ContainerNode) { + final DataContainerNodeAttrBuilder builderChild = + Builders.containerBuilder((ContainerSchemaNode) childSchema); + buildCont(builderChild, result, baseSchemaCtxTree, actualPath, trim); + builder.withChild(builderChild.build()); + } else if (child instanceof MapNode) { + final CollectionNodeBuilder childBuilder = + Builders.mapBuilder((ListSchemaNode) childSchema); + buildList(childBuilder, (MapNode) child, baseSchemaCtxTree, path, trim, + ((ListSchemaNode) childSchema).getKeyDefinition()); + builder.withChild(childBuilder.build()); + } else if (child instanceof LeafNode) { + final String defaultVal = ((LeafSchemaNode) childSchema).getDefault(); + final String nodeVal = ((LeafNode) child).getValue(); + final NormalizedNodeAttrBuilder> leafBuilder = + Builders.leafBuilder((LeafSchemaNode) childSchema); + if (trim) { + if ((defaultVal == null) || !defaultVal.equals(nodeVal)) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } + } else { + if ((defaultVal != null) && defaultVal.equals(nodeVal)) { + leafBuilder.withValue(((LeafNode) child).getValue()); + builder.withChild(leafBuilder.build()); + } + } + } + } + } + /** * If is set specific {@link LogicalDatastoreType} in * {@link TransactionVarsWrapper}, then read this type of data from DS. If @@ -200,16 +378,24 @@ public final class ReadDataTransactionUtil { * * @param transactionNode * - {@link TransactionVarsWrapper} - wrapper for variables + * @param withDefa * @return {@link NormalizedNode} */ - private static @Nullable NormalizedNode readAllData(@Nonnull final TransactionVarsWrapper transactionNode) { + private static @Nullable NormalizedNode readAllData(@Nonnull final TransactionVarsWrapper transactionNode, + final String withDefa) { // PREPARE STATE DATA NODE transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL); final NormalizedNode stateDataNode = readDataViaTransaction(transactionNode); // PREPARE CONFIG DATA NODE transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION); - final NormalizedNode configDataNode = readDataViaTransaction(transactionNode); + final NormalizedNode configDataNode; + if (withDefa == null) { + configDataNode = readDataViaTransaction(transactionNode); + } else { + configDataNode = prepareDataByParamWithDef(readDataViaTransaction(transactionNode), + transactionNode.getInstanceIdentifier().getInstanceIdentifier(), withDefa); + } // if no data exists if ((stateDataNode == null) && (configDataNode == null)) { diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfDataServiceConstant.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfDataServiceConstant.java index e8d1b32791..a2ef70b9a8 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfDataServiceConstant.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/RestconfDataServiceConstant.java @@ -58,6 +58,7 @@ public final class RestconfDataServiceConstant { public static final int MAX_DEPTH = 65535; public static final String READ_TYPE_TX = "READ"; + public static final String WITH_DEFAULTS = "with-defaults"; private ReadData() { throw new UnsupportedOperationException("Util class."); diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JSONRestconfServiceImplTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JSONRestconfServiceImplTest.java index 49ddf217b3..6729ea1b01 100644 --- a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JSONRestconfServiceImplTest.java +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JSONRestconfServiceImplTest.java @@ -346,16 +346,24 @@ public class JSONRestconfServiceImplTest { @Test public void testGetWithNoData() throws OperationFailedException { - doReturn(null).when(brokerFacade).readConfigurationData(notNull(YangInstanceIdentifier.class)); + doReturn(null).when(brokerFacade).readConfigurationData(notNull(YangInstanceIdentifier.class), + Mockito.anyString()); final String uriPath = "ietf-interfaces:interfaces"; - this.service.get(uriPath, LogicalDatastoreType.CONFIGURATION); + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + final MultivaluedMap value = Mockito.mock(MultivaluedMap.class); + Mockito.when(value.entrySet()).thenReturn(new HashSet<>()); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(value); + this.service.get(uriPath, LogicalDatastoreType.CONFIGURATION, uriInfo); } @Test(expected=OperationFailedException.class) public void testGetFailure() throws Exception { final String invalidUriPath = "/ietf-interfaces:interfaces/invalid"; - - this.service.get(invalidUriPath, LogicalDatastoreType.CONFIGURATION); + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + final MultivaluedMap value = Mockito.mock(MultivaluedMap.class); + Mockito.when(value.entrySet()).thenReturn(new HashSet<>()); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(value); + this.service.get(invalidUriPath, LogicalDatastoreType.CONFIGURATION, uriInfo); } @SuppressWarnings("rawtypes") @@ -443,14 +451,21 @@ public class JSONRestconfServiceImplTest { .build(); if(datastoreType == LogicalDatastoreType.CONFIGURATION) { - doReturn(entryNode).when(brokerFacade).readConfigurationData(notNull(YangInstanceIdentifier.class)); + doReturn(entryNode).when(brokerFacade).readConfigurationData(notNull(YangInstanceIdentifier.class), + Mockito.anyString()); } else { doReturn(entryNode).when(brokerFacade).readOperationalData(notNull(YangInstanceIdentifier.class)); } final String uriPath = "/ietf-interfaces:interfaces/interface/eth0"; + final UriInfo uriInfo = Mockito.mock(UriInfo.class); + final MultivaluedMap value = Mockito.mock(MultivaluedMap.class); + Mockito.when(value.entrySet()).thenReturn(new HashSet<>()); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(value); + Mockito.when(uriInfo.getQueryParameters(false)).thenReturn(value); + Mockito.when(value.getFirst("depth")).thenReturn(""); - final Optional optionalResp = this.service.get(uriPath, datastoreType); + final Optional optionalResp = this.service.get(uriPath, datastoreType, uriInfo); assertEquals("Response present", true, optionalResp.isPresent()); final String jsonResp = optionalResp.get(); @@ -462,7 +477,7 @@ public class JSONRestconfServiceImplTest { final ArgumentCaptor capturedPath = ArgumentCaptor.forClass(YangInstanceIdentifier.class); if (datastoreType == LogicalDatastoreType.CONFIGURATION) { - verify(brokerFacade).readConfigurationData(capturedPath.capture()); + verify(brokerFacade).readConfigurationData(capturedPath.capture(), Mockito.anyString()); } else { verify(brokerFacade).readOperationalData(capturedPath.capture()); } diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetOperationTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetOperationTest.java index d5fd89777b..1bcfa8fae0 100644 --- a/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetOperationTest.java +++ b/restconf/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetOperationTest.java @@ -46,6 +46,7 @@ import org.glassfish.jersey.test.JerseyTest; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; +import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint; @@ -172,7 +173,8 @@ public class RestGetOperationTest extends JerseyTest { @SuppressWarnings("unchecked") @Test public void getDataWithUrlMountPoint() throws UnsupportedEncodingException, URISyntaxException, ParseException { - when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), any(YangInstanceIdentifier.class))).thenReturn( + when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), any(YangInstanceIdentifier.class), + Mockito.anyString())).thenReturn( prepareCnDataForMountPointTest(false)); final DOMMountPoint mountInstance = mock(DOMMountPoint.class); when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule); @@ -202,8 +204,8 @@ public class RestGetOperationTest extends JerseyTest { @Test public void getDataWithSlashesBehindMountPoint() throws Exception { final YangInstanceIdentifier awaitedInstanceIdentifier = prepareInstanceIdentifierForList(); - when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), eq(awaitedInstanceIdentifier))).thenReturn( - prepareCnDataForSlashesBehindMountPointTest()); + when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), eq(awaitedInstanceIdentifier), + Mockito.anyString())).thenReturn(prepareCnDataForSlashesBehindMountPointTest()); final DOMMountPoint mountInstance = mock(DOMMountPoint.class); when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule); final DOMMountPointService mockMountService = mock(DOMMountPointService.class); @@ -238,7 +240,8 @@ public class RestGetOperationTest extends JerseyTest { @Test public void getDataMountPointIntoHighestElement() throws UnsupportedEncodingException, URISyntaxException, ParseException { - when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), any(YangInstanceIdentifier.class))).thenReturn( + when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), any(YangInstanceIdentifier.class), + Mockito.anyString())).thenReturn( prepareCnDataForMountPointTest(true)); final DOMMountPoint mountInstance = mock(DOMMountPoint.class); when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule); @@ -270,7 +273,7 @@ public class RestGetOperationTest extends JerseyTest { .withChild(ImmutableNodes.leafNode(newTestModuleQName("type"), newTestModuleQName("test-identity"))) .withChild(ImmutableNodes.leafNode(newTestModuleQName("name"), "foo")) .withChild(ImmutableNodes.leafNode(newTestModuleQName("data"), "bar")).build()).build(); - when(brokerFacade.readConfigurationData(iid)).thenReturn(data); + when(brokerFacade.readConfigurationData(iid, null)).thenReturn(data); final String uri = "/config/test-module:modules/module/test-module:test-identity/foo"; assertEquals(200, get(uri, MediaType.APPLICATION_XML)); @@ -670,7 +673,8 @@ public class RestGetOperationTest extends JerseyTest { @SuppressWarnings("unchecked") private void mockReadConfigurationDataMethod() { - when(brokerFacade.readConfigurationData(any(YangInstanceIdentifier.class))).thenReturn(answerFromGet); + when(brokerFacade.readConfigurationData(any(YangInstanceIdentifier.class), Mockito.anyString())) + .thenReturn(answerFromGet); } @SuppressWarnings("rawtypes")