import com.google.common.base.Optional;
public class WriterParameters {
+ private final String content;
private final Optional<Integer> depth;
private final boolean prettyPrint;
private WriterParameters(final WriterParametersBuilder builder) {
- this.prettyPrint = builder.prettyPrint;
+ this.content = builder.content;
this.depth = builder.depth;
+ this.prettyPrint = builder.prettyPrint;
+ }
+
+ public String getContent() {
+ return content;
}
public Optional<Integer> getDepth() {
}
public static class WriterParametersBuilder {
+ private String content;
private Optional<Integer> depth = Optional.absent();
private boolean prettyPrint;
- public WriterParametersBuilder() {
- }
+ public WriterParametersBuilder() {}
- public Optional<Integer> getDepth() {
- return depth;
+ public WriterParametersBuilder setContent(final String content) {
+ this.content = content;
+ return this;
}
public WriterParametersBuilder setDepth(final int depth) {
return this;
}
- public boolean isPrettyPrint() {
- return prettyPrint;
- }
-
public WriterParametersBuilder setPrettyPrint(final boolean prettyPrint) {
this.prettyPrint = prettyPrint;
return this;
}
}
}
-
import org.opendaylight.netconf.sal.restconf.impl.PATCHStatusContext;
import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.netconf.sal.restconf.impl.WriterParameters;
import org.opendaylight.restconf.RestConnectorProvider;
import org.opendaylight.restconf.common.references.SchemaContextRef;
import org.opendaylight.restconf.handlers.DOMMountPointServiceHandler;
@Override
public Response readData(final String identifier, final UriInfo uriInfo) {
Preconditions.checkNotNull(identifier);
+
+ final WriterParameters parameters = ReadDataTransactionUtil.parseUriParameters(uriInfo);
final SchemaContextRef schemaContextRef = new SchemaContextRef(this.schemaContextHandler.get());
final InstanceIdentifierContext<?> instanceIdentifier = ParserIdentifier.toInstanceIdentifier(
identifier, schemaContextRef.get(), Optional.of(this.mountPointServiceHandler.get()));
final DOMMountPoint mountPoint = instanceIdentifier.getMountPoint();
- final String value = uriInfo.getQueryParameters().getFirst(RestconfDataServiceConstant.CONTENT);
final DOMTransactionChain transactionChain;
if (mountPoint == null) {
transactionChain = transactionChainOfMountPoint(mountPoint);
}
- final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(instanceIdentifier, mountPoint,
- transactionChain);
- final NormalizedNode<?, ?> node = ReadDataTransactionUtil.readData(value, transactionNode);
+ final TransactionVarsWrapper transactionNode = new TransactionVarsWrapper(
+ instanceIdentifier, mountPoint, transactionChain);
+ final NormalizedNode<?, ?> node = ReadDataTransactionUtil.readData(parameters.getContent(), transactionNode);
if (node == null) {
throw new RestconfDocumentedException(
"Request could not be completed because the relevant data model content does not exist",
+ node.getNodeType().getLocalName() + '"';
final Response resp;
- if ((value == null) || value.contains(RestconfDataServiceConstant.ReadData.CONFIG)) {
- resp = Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node)).header("ETag", etag)
- .header("Last-Modified", dateFormatGmt.format(new Date())).build();
+ if ((parameters.getContent().equals(RestconfDataServiceConstant.ReadData.ALL))
+ || parameters.getContent().equals(RestconfDataServiceConstant.ReadData.CONFIG)) {
+ resp = Response.status(200)
+ .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters))
+ .header("ETag", etag)
+ .header("Last-Modified", dateFormatGmt.format(new Date()))
+ .build();
} else {
- resp = Response.status(200).entity(new NormalizedNodeContext(instanceIdentifier, node)).build();
+ resp = Response.status(200)
+ .entity(new NormalizedNodeContext(instanceIdentifier, node, parameters))
+ .build();
}
+
return resp;
}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.restconf.restful.utils;
+
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+
+class ParametersUtil {
+
+ private ParametersUtil() {
+ throw new UnsupportedOperationException("Util class.");
+ }
+
+ /**
+ * Check if URI does not contain not allowed parameters for specified operation
+ *
+ * @param operationType
+ * - type of operation (READ, POST, PUT, DELETE...)
+ * @param usedParameters
+ * - parameters used in URI request
+ * @param allowedParameters
+ * - allowed parameters for operation
+ */
+ static void checkParametersTypes(@Nonnull final String operationType,
+ @Nonnull final Set<String> usedParameters,
+ @Nonnull final String... allowedParameters) {
+ final Set<String> notAllowedParameters = Sets.newHashSet(usedParameters);
+ notAllowedParameters.removeAll(Sets.newHashSet(allowedParameters));
+
+ if (!notAllowedParameters.isEmpty()) {
+ throw new RestconfDocumentedException(
+ "Not allowed parameters for " + operationType + " operation: " + notAllowedParameters,
+ RestconfError.ErrorType.PROTOCOL,
+ RestconfError.ErrorTag.INVALID_VALUE);
+ }
+ }
+
+ /**
+ * Check if URI does not contain value for the same parameter more than once
+ *
+ * @param parameterValues
+ * - URI parameter values
+ * @param parameterName
+ * - URI parameter name
+ */
+ static void checkParameterCount(@Nonnull final List<String> parameterValues, @Nonnull final String parameterName) {
+ if (parameterValues.size() > 1) {
+ throw new RestconfDocumentedException(
+ "Parameter " + parameterName + " can appear at most once in request URI",
+ ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE);
+ }
+ }
+}
package org.opendaylight.restconf.restful.utils;
import com.google.common.base.Optional;
+import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.CheckedFuture;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
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.RestconfDocumentedException;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
-import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+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.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
throw new UnsupportedOperationException("Util class.");
}
+ /**
+ * Parse parameters from URI request and check their types and values.
+ *
+ * @param uriInfo
+ * - URI info
+ * @return {@link WriterParameters}
+ */
+ @Nonnull public static WriterParameters parseUriParameters(@Nullable final UriInfo uriInfo) {
+ final WriterParametersBuilder builder = new WriterParametersBuilder();
+
+ if (uriInfo == null) {
+ return builder.build();
+ }
+
+ // check only allowed parameters
+ ParametersUtil.checkParametersTypes(
+ RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+ uriInfo.getQueryParameters().keySet(),
+ RestconfDataServiceConstant.ReadData.CONTENT,
+ RestconfDataServiceConstant.ReadData.DEPTH);
+
+ // read parameters from URI or set default values
+ final List<String> content = uriInfo.getQueryParameters().getOrDefault(
+ RestconfDataServiceConstant.ReadData.CONTENT,
+ Collections.singletonList(RestconfDataServiceConstant.ReadData.ALL));
+ final List<String> depth = uriInfo.getQueryParameters().getOrDefault(
+ RestconfDataServiceConstant.ReadData.DEPTH,
+ Collections.singletonList(RestconfDataServiceConstant.ReadData.UNBOUNDED));
+
+ // parameter can be in URI at most once
+ ParametersUtil.checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
+ ParametersUtil.checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
+
+ // check and set content
+ final String contentValue = content.get(0);
+ if (!contentValue.equals(RestconfDataServiceConstant.ReadData.ALL)) {
+ if (!contentValue.equals(RestconfDataServiceConstant.ReadData.CONFIG)
+ && !contentValue.equals(RestconfDataServiceConstant.ReadData.NONCONFIG)) {
+ throw new RestconfDocumentedException(
+ new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+ "Invalid content parameter: " + contentValue, null,
+ "The content parameter value must be either config, nonconfig or all (default)"));
+ }
+ }
+
+ builder.setContent(content.get(0));
+
+ // check and set depth
+ 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))) {
+ throw new RestconfDocumentedException(
+ new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+ "Invalid depth parameter: " + depth, null,
+ "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
+ } else {
+ builder.setDepth(value);
+ }
+ }
+
+ return builder.build();
+ }
+
/**
* Read specific type of data from data store via transaction.
*
* - {@link TransactionVarsWrapper} - wrapper for variables
* @return {@link NormalizedNode}
*/
- public static @Nullable NormalizedNode<?, ?> readData(@Nullable final String valueOfContent,
+ public static @Nullable NormalizedNode<?, ?> readData(@Nonnull final String valueOfContent,
@Nonnull final TransactionVarsWrapper transactionNode) {
- final NormalizedNode<?, ?> data;
- if (valueOfContent != null) {
- switch (valueOfContent) {
- case RestconfDataServiceConstant.ReadData.CONFIG:
- transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
- data = readDataViaTransaction(transactionNode);
- break;
- case RestconfDataServiceConstant.ReadData.NONCONFIG:
- transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
- data = readDataViaTransaction(transactionNode);
- break;
- case RestconfDataServiceConstant.ReadData.ALL:
- data = readAllData(transactionNode);
- break;
- default:
- throw new RestconfDocumentedException("Bad query parameter for content.", ErrorType.APPLICATION,
- ErrorTag.INVALID_VALUE);
- }
- } else {
- data = readAllData(transactionNode);
+ switch (valueOfContent) {
+ case RestconfDataServiceConstant.ReadData.CONFIG:
+ transactionNode.setLogicalDatastoreType(LogicalDatastoreType.CONFIGURATION);
+ return readDataViaTransaction(transactionNode);
+
+ case RestconfDataServiceConstant.ReadData.NONCONFIG:
+ transactionNode.setLogicalDatastoreType(LogicalDatastoreType.OPERATIONAL);
+ return readDataViaTransaction(transactionNode);
+
+ case RestconfDataServiceConstant.ReadData.ALL:
+ return readAllData(transactionNode);
+
+ default:
+ throw new RestconfDocumentedException(
+ new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
+ "Invalid content parameter: " + valueOfContent, null,
+ "The content parameter value must be either config, nonconfig or all (default)"));
}
-
- return data;
}
/**
*/
public final class RestconfDataServiceConstant {
- public static final String CONTENT = "content";
public static final QName NETCONF_BASE_QNAME;
static {
try {
*
*/
public final class ReadData {
+ // URI parameters
+ public static final String CONTENT = "content";
+ public static final String DEPTH = "depth";
+ // content values
public static final String CONFIG = "config";
- public static final String NONCONFIG = "nonconfig";
public static final String ALL = "all";
+ public static final String NONCONFIG = "nonconfig";
+
+ // depth values
+ public static final String UNBOUNDED = "unbounded";
+ public static final int MIN_DEPTH = 1;
+ public static final int MAX_DEPTH = 65535;
+
public static final String READ_TYPE_TX = "READ";
private ReadData() {
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.rest.impl;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+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.NodeWithValue;
+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;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+
+public class DepthAwareNormalizedNodeWriterTest {
+
+ @Mock
+ private NormalizedNodeStreamWriter writer;
+ @Mock
+ private ContainerNode containerNodeData;
+ @Mock
+ private MapNode mapNodeData;
+ @Mock
+ private MapEntryNode mapEntryNodeData;
+ @Mock
+ private LeafSetNode<String> leafSetNodeData;
+ @Mock
+ private LeafSetEntryNode<String> leafSetEntryNodeData;
+ @Mock
+ private LeafNode<String> keyLeafNodeData;
+ @Mock
+ private LeafNode<String> anotherLeafNodeData;
+
+ private NodeIdentifier containerNodeIdentifier;
+ private NodeIdentifier mapNodeIdentifier;
+ private NodeIdentifierWithPredicates mapEntryNodeIdentifier;
+ private NodeIdentifier leafSetNodeIdentifier;
+ private NodeWithValue<?> leafSetEntryNodeIdentifier;
+ private NodeIdentifier keyLeafNodeIdentifier;
+ private NodeIdentifier anotherLeafNodeIdentifier;
+
+ private Collection<DataContainerChild<?, ?>> containerNodeValue;
+ private Collection<MapEntryNode> mapNodeValue;
+ private Collection<DataContainerChild<?, ?>> mapEntryNodeValue;
+ private Collection<LeafSetEntryNode<String>> leafSetNodeValue;
+ private String leafSetEntryNodeValue;
+ private String keyLeafNodeValue;
+ private String anotherLeafNodeValue;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ // identifiers
+ containerNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "container"));
+ Mockito.when(containerNodeData.getIdentifier()).thenReturn(containerNodeIdentifier);
+
+ mapNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "list"));
+ Mockito.when(mapNodeData.getIdentifier()).thenReturn(mapNodeIdentifier);
+
+ final QName leafSetEntryNodeQName = QName.create("namespace", "leaf-set-entry");
+ leafSetEntryNodeValue = "leaf-set-value";
+ leafSetEntryNodeIdentifier = new NodeWithValue<>(leafSetEntryNodeQName, leafSetEntryNodeValue);
+ Mockito.when(leafSetEntryNodeData.getIdentifier()).thenReturn(leafSetEntryNodeIdentifier);
+ Mockito.when(leafSetEntryNodeData.getNodeType()).thenReturn(leafSetEntryNodeQName);
+
+ leafSetNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "leaf-set"));
+ Mockito.when(leafSetNodeData.getIdentifier()).thenReturn(leafSetNodeIdentifier);
+
+ final QName mapEntryNodeKey = QName.create("namespace", "key-field");
+ keyLeafNodeIdentifier = NodeIdentifier.create(mapEntryNodeKey);
+ keyLeafNodeValue = "key-value";
+
+ mapEntryNodeIdentifier = new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+ QName.create("namespace", "list-entry"), Collections.singletonMap(mapEntryNodeKey, keyLeafNodeValue));
+ Mockito.when(mapEntryNodeData.getIdentifier()).thenReturn(mapEntryNodeIdentifier);
+ Mockito.when(mapEntryNodeData.getChild(keyLeafNodeIdentifier)).thenReturn(Optional.of(keyLeafNodeData));
+
+ Mockito.when(keyLeafNodeData.getValue()).thenReturn(keyLeafNodeValue);
+ Mockito.when(keyLeafNodeData.getIdentifier()).thenReturn(keyLeafNodeIdentifier);
+
+ anotherLeafNodeIdentifier = NodeIdentifier.create(QName.create("namespace", "another-field"));
+ anotherLeafNodeValue = "another-value";
+
+ Mockito.when(anotherLeafNodeData.getValue()).thenReturn(anotherLeafNodeValue);
+ Mockito.when(anotherLeafNodeData.getIdentifier()).thenReturn(anotherLeafNodeIdentifier);
+
+ // values
+ Mockito.when(leafSetEntryNodeData.getValue()).thenReturn(leafSetEntryNodeValue);
+
+ leafSetNodeValue = Collections.singletonList(leafSetEntryNodeData);
+ Mockito.when(leafSetNodeData.getValue()).thenReturn(leafSetNodeValue);
+
+ containerNodeValue = Collections.singleton(leafSetNodeData);
+ Mockito.when(containerNodeData.getValue()).thenReturn(containerNodeValue);
+
+ mapEntryNodeValue = Sets.newHashSet(keyLeafNodeData, anotherLeafNodeData);
+ Mockito.when(mapEntryNodeData.getValue()).thenReturn(mapEntryNodeValue);
+
+ mapNodeValue = Collections.singleton(mapEntryNodeData);
+ Mockito.when(mapNodeData.getValue()).thenReturn(mapNodeValue);
+ }
+
+ /**
+ * Test write {@link ContainerNode} with children but write data only to depth 1 (children will not be written).
+ */
+ @Test
+ public void writeContainerWithoutChildrenTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(writer, 1);
+
+ depthWriter.write(containerNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startContainerNode(containerNodeIdentifier, containerNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write {@link ContainerNode} with children and write also all its children.
+ */
+ @Test
+ public void writeContainerWithChildrenTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, Integer.MAX_VALUE);
+
+ depthWriter.write(containerNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startContainerNode(containerNodeIdentifier, containerNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).leafSetEntryNode(
+ leafSetEntryNodeIdentifier.getNodeType(), leafSetEntryNodeValue);
+ inOrder.verify(writer, Mockito.times(2)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write with {@link MapNode} with children but write data only to depth 1 (children will not be written).
+ */
+ @Test
+ public void writeMapNodeWithoutChildrenTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(writer, 1);
+
+ depthWriter.write(mapNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startMapNode(mapNodeIdentifier, mapNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+ inOrder.verify(writer, Mockito.times(2)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write {@link MapNode} with children and write also all its children.
+ *
+ * FIXME
+ * Although ordered writer is used leaves are not written in expected order.
+ *
+ */
+ @Ignore
+ @Test
+ public void writeMapNodeWithChildrenTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, Integer.MAX_VALUE);
+
+ depthWriter.write(mapNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startMapNode(mapNodeIdentifier, mapNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+ inOrder.verify(writer, Mockito.times(2)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+ // FIXME this assertion is not working because leaves are not written in expected order
+ inOrder.verify(writer, Mockito.times(1)).leafNode(anotherLeafNodeIdentifier, anotherLeafNodeValue);
+ inOrder.verify(writer, Mockito.times(2)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write with {@link LeafSetNode} with depth 1 (children will not be written).
+ */
+ @Test
+ public void writeLeafSetNodeWithoutChildrenTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, 1);
+
+ depthWriter.write(leafSetNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write with {@link LeafSetNode} when all its children will be written.
+ */
+ @Test
+ public void writeLeafSetNodeWithChildrenTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, Integer.MAX_VALUE);
+
+ depthWriter.write(leafSetNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startLeafSet(leafSetNodeIdentifier, leafSetNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).leafSetEntryNode(
+ leafSetEntryNodeIdentifier.getNodeType(), leafSetEntryNodeValue);
+ inOrder.verify(writer, Mockito.times(1)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write with {@link LeafSetEntryNode}.
+ */
+ @Test
+ public void writeLeafSetEntryNodeTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, Integer.MAX_VALUE);
+
+ depthWriter.write(leafSetEntryNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).leafSetEntryNode(
+ leafSetEntryNodeIdentifier.getNodeType(), leafSetEntryNodeValue);
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write with {@link MapEntryNode} unordered to depth 1 to write only keys.
+ */
+ @Test
+ public void writeMapEntryNodeUnorderedOnlyKeysTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, false, 1);
+
+ depthWriter.write(mapEntryNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+ // write only the key
+ inOrder.verify(writer, Mockito.times(1)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+ inOrder.verify(writer, Mockito.times(1)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write with {@link MapEntryNode} unordered with full depth.
+ */
+ @Test
+ public void writeMapEntryNodeUnorderedTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, false, Integer.MAX_VALUE);
+
+ depthWriter.write(mapEntryNodeData);
+
+ // unordered
+ Mockito.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+ Mockito.verify(writer, Mockito.times(1)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+ Mockito.verify(writer, Mockito.times(1)).leafNode(anotherLeafNodeIdentifier, anotherLeafNodeValue);
+ Mockito.verify(writer, Mockito.times(1)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write with {@link MapEntryNode} ordered with depth 1 (children will not be written).
+ */
+ @Test
+ public void writeMapEntryNodeOrderedWithoutChildrenTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, true, 1);
+
+ depthWriter.write(mapEntryNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+ inOrder.verify(writer, Mockito.times(1)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+ inOrder.verify(writer, Mockito.times(1)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+
+ /**
+ * Test write with {@link MapEntryNode} ordered and write also all its children.
+ *
+ * FIXME
+ * Although ordered writer is used leaves are not written in expected order.
+ *
+ */
+ @Ignore
+ @Test
+ public void writeMapEntryNodeOrderedTest() throws Exception {
+ final DepthAwareNormalizedNodeWriter depthWriter = DepthAwareNormalizedNodeWriter.forStreamWriter(
+ writer, true, Integer.MAX_VALUE);
+
+ depthWriter.write(mapEntryNodeData);
+
+ final InOrder inOrder = Mockito.inOrder(writer);
+ inOrder.verify(writer, Mockito.times(1)).startMapEntryNode(mapEntryNodeIdentifier, mapEntryNodeValue.size());
+ inOrder.verify(writer, Mockito.times(2)).leafNode(keyLeafNodeIdentifier, keyLeafNodeValue);
+ // FIXME this assertion is not working because leaves are not written in expected order
+ inOrder.verify(writer, Mockito.times(1)).leafNode(anotherLeafNodeIdentifier, anotherLeafNodeValue);
+ inOrder.verify(writer, Mockito.times(1)).endNode();
+ Mockito.verifyNoMoreInteractions(writer);
+ }
+}
\ No newline at end of file
import com.google.common.util.concurrent.Futures;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.Response;
dataService.readData("example-jukebox:jukebox", uriInfo);
}
+ /**
+ * Read data from config datastore according to content parameter
+ */
+ @Test
+ public void testReadDataConfigTest() {
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+ parameters.put("content", Collections.singletonList("config"));
+
+ doReturn(parameters).when(uriInfo).getQueryParameters();
+ doReturn(Futures.immediateCheckedFuture(Optional.of(buildBaseContConfig))).when(read)
+ .read(LogicalDatastoreType.CONFIGURATION, iidBase);
+ doReturn(Futures.immediateCheckedFuture(Optional.of(buildBaseContOperational))).when(read)
+ .read(LogicalDatastoreType.OPERATIONAL, iidBase);
+
+ final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
+
+ assertNotNull(response);
+ assertEquals(200, response.getStatus());
+
+ // response must contain only config data
+ final NormalizedNode<?, ?> data = ((NormalizedNodeContext) response.getEntity()).getData();
+
+ // config data present
+ assertTrue(((ContainerNode) data).getChild(buildPlayerCont.getIdentifier()).isPresent());
+ assertTrue(((ContainerNode) data).getChild(buildLibraryCont.getIdentifier()).isPresent());
+
+ // state data absent
+ assertFalse(((ContainerNode) data).getChild(buildPlaylistList.getIdentifier()).isPresent());
+ }
+
+ /**
+ * Read data from operational datastore according to content parameter
+ */
+ @Test
+ public void testReadDataOperationalTest() {
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+ parameters.put("content", Collections.singletonList("nonconfig"));
+
+ doReturn(parameters).when(uriInfo).getQueryParameters();
+ doReturn(Futures.immediateCheckedFuture(Optional.of(buildBaseContConfig))).when(read)
+ .read(LogicalDatastoreType.CONFIGURATION, iidBase);
+ doReturn(Futures.immediateCheckedFuture(Optional.of(buildBaseContOperational))).when(read)
+ .read(LogicalDatastoreType.OPERATIONAL, iidBase);
+
+ final Response response = dataService.readData("example-jukebox:jukebox", uriInfo);
+
+ assertNotNull(response);
+ assertEquals(200, response.getStatus());
+
+ // response must contain only operational data
+ final NormalizedNode<?, ?> data = ((NormalizedNodeContext) response.getEntity()).getData();
+
+ // state data present
+ assertTrue(((ContainerNode) data).getChild(buildPlayerCont.getIdentifier()).isPresent());
+ assertTrue(((ContainerNode) data).getChild(buildPlaylistList.getIdentifier()).isPresent());
+
+ // config data absent
+ assertFalse(((ContainerNode) data).getChild(buildLibraryCont.getIdentifier()).isPresent());
+ }
+
@Test
public void testPutData() {
final InstanceIdentifierContext<DataSchemaNode> iidContext = new InstanceIdentifierContext<>(iidBase, schemaNode, null, contextRef.get());
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.restconf.restful.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+
+/**
+ * Unit test for {@link ParametersUtil}
+ */
+public class ParametersUtilTest {
+
+ /**
+ * Test when all parameters are allowed
+ */
+ @Test
+ public void checkParametersTypesTest() {
+ ParametersUtil.checkParametersTypes(
+ RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+ Sets.newHashSet("content"),
+ RestconfDataServiceConstant.ReadData.CONTENT, RestconfDataServiceConstant.ReadData.DEPTH);
+ }
+
+ /**
+ * Test when not allowed parameter type is used
+ */
+ @Test
+ public void checkParametersTypesNegativeTest() {
+ try {
+ ParametersUtil.checkParametersTypes(
+ RestconfDataServiceConstant.ReadData.READ_TYPE_TX,
+ Sets.newHashSet("not-allowed-parameter"),
+ RestconfDataServiceConstant.ReadData.CONTENT, RestconfDataServiceConstant.ReadData.DEPTH);
+
+ Assert.fail("Test expected to fail due to not allowed parameter used with operation");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Test when parameter is present at most once
+ */
+ @Test
+ public void checkParameterCountTest() {
+ ParametersUtil.checkParameterCount(Lists.newArrayList("all"), RestconfDataServiceConstant.ReadData.CONTENT);
+ }
+
+ /**
+ * Test when parameter is present more than once
+ */
+ @Test
+ public void checkParameterCountNegativeTest() {
+ try {
+ ParametersUtil.checkParameterCount(Lists.newArrayList("config", "nonconfig", "all"),
+ RestconfDataServiceConstant.ReadData.CONTENT);
+
+ Assert.fail("Test expected to fail due to multiple values of the same parameter");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+}
\ No newline at end of file
package org.opendaylight.restconf.restful.utils;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.Futures;
+import java.util.Collections;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.UriInfo;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
+import org.opendaylight.netconf.sal.restconf.impl.WriterParameters;
import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
doReturn(Futures.immediateCheckedFuture(Optional.of(data.data4))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, data.path);
doReturn(data.path).when(context).getInstanceIdentifier();
- final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(null, wrapper);
+ final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(
+ RestconfDataServiceConstant.ReadData.ALL, wrapper);
final ContainerNode checkingData = Builders
.containerBuilder()
.withNodeIdentifier(nodeIdentifier)
final NormalizedNode<?, ?> normalizedNode = ReadDataTransactionUtil.readData(valueOfContent, null);
assertNull(normalizedNode);
}
+
+ /**
+ * Test of parsing default parameters from URI request
+ */
+ @Test
+ public void parseUriParametersDefaultTest() {
+ final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+ // no parameters, default values should be used
+ when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+ final WriterParameters parsedParameters = ReadDataTransactionUtil.parseUriParameters(uriInfo);
+
+ assertEquals("Not correctly parsed URI parameter",
+ RestconfDataServiceConstant.ReadData.ALL, parsedParameters.getContent());
+ assertFalse("Not correctly parsed URI parameter",
+ parsedParameters.getDepth().isPresent());
+ }
+
+ /**
+ * Test of parsing user defined parameters from URI request
+ */
+ @Test
+ public void parseUriParametersUserDefinedTest() {
+ final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+ final String content = "config";
+ final String depth = "10";
+
+ parameters.put("content", Collections.singletonList(content));
+ parameters.put("depth", Collections.singletonList(depth));
+
+ when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+ final WriterParameters parsedParameters = ReadDataTransactionUtil.parseUriParameters(uriInfo);
+
+ assertEquals("Not correctly parsed URI parameter",
+ content, parsedParameters.getContent());
+ assertTrue("Not correctly parsed URI parameter",
+ parsedParameters.getDepth().isPresent());
+ assertEquals("Not correctly parsed URI parameter",
+ depth, parsedParameters.getDepth().get().toString());
+ }
+
+ /**
+ * Negative test of parsing request URI parameters when content parameter has not allowed value.
+ */
+ @Test
+ public void parseUriParametersContentParameterNegativeTest() {
+ final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+ parameters.put("content", Collections.singletonList("not-allowed-parameter-value"));
+ when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+ try {
+ ReadDataTransactionUtil.parseUriParameters(uriInfo);
+ fail("Test expected to fail due to not allowed parameter value");
+ } catch (final RestconfDocumentedException e) {
+ // Bad request
+ assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test of parsing request URI parameters when depth parameter has not allowed value.
+ */
+ @Test
+ public void parseUriParametersDepthParameterNegativeTest() {
+ final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+ // inserted value is not allowed
+ parameters.put("depth", Collections.singletonList("bounded"));
+ when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+ try {
+ ReadDataTransactionUtil.parseUriParameters(uriInfo);
+ fail("Test expected to fail due to not allowed parameter value");
+ } catch (final RestconfDocumentedException e) {
+ // Bad request
+ assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test of parsing request URI parameters when depth parameter has not allowed value (less than minimum).
+ */
+ @Test
+ public void parseUriParametersDepthMinimalParameterNegativeTest() {
+ final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+ // inserted value is too low
+ parameters.put(
+ "depth", Collections.singletonList(String.valueOf(RestconfDataServiceConstant.ReadData.MIN_DEPTH - 1)));
+ when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+ try {
+ ReadDataTransactionUtil.parseUriParameters(uriInfo);
+ fail("Test expected to fail due to not allowed parameter value");
+ } catch (final RestconfDocumentedException e) {
+ // Bad request
+ assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test of parsing request URI parameters when depth parameter has not allowed value (more than maximum).
+ */
+ @Test
+ public void parseUriParametersDepthMaximalParameterNegativeTest() {
+ final UriInfo uriInfo = Mockito.mock(UriInfo.class);
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+
+ // inserted value is too high
+ parameters.put(
+ "depth", Collections.singletonList(String.valueOf(RestconfDataServiceConstant.ReadData.MAX_DEPTH + 1)));
+ when(uriInfo.getQueryParameters()).thenReturn(parameters);
+
+ try {
+ ReadDataTransactionUtil.parseUriParameters(uriInfo);
+ fail("Test expected to fail due to not allowed parameter value");
+ } catch (final RestconfDocumentedException e) {
+ // Bad request
+ assertEquals("Error type is not correct", ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct", 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
}