import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
+import com.google.gson.stream.JsonWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
+import javax.xml.XMLConstants;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
import org.opendaylight.controller.sal.restconf.impl.RestconfError;
import org.opendaylight.yangtools.yang.common.QName;
+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;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
errNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) errMsgSchemaNode)
.withValue(error.getErrorMessage()).build());
- // TODO : find how could we add possible "error-path" and "error-info"
+ if(error.getErrorInfo() != null) {
+ // Oddly, error-info is defined as an empty container in the restconf yang. Apparently the
+ // intention is for implementors to define their own data content so we'll just treat it as a leaf
+ // with string data.
+ errNodeValues.withChild(ImmutableNodes.leafNode(Draft02.RestConfModule.ERROR_INFO_QNAME,
+ error.getErrorInfo()));
+ }
+
+ // TODO : find how could we add possible "error-path"
return errNodeValues.build();
}
if(!schema.isAugmenting() && !(schema instanceof SchemaContext)) {
initialNs = schema.getQName().getNamespace();
}
- final NormalizedNodeStreamWriter jsonWriter = JSONNormalizedNodeStreamWriter.create(context.getSchemaContext(),path,initialNs,outputWriter);
- final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(jsonWriter);
+
+ final JsonWriter jsonWriter = JsonWriterFactory.createJsonWriter(outputWriter);
+ final NormalizedNodeStreamWriter jsonStreamWriter = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
+ JSONCodecFactory.create(context.getSchemaContext()), path, initialNs, jsonWriter);
+
+ // We create a delegating writer to special-case error-info as error-info is defined as an empty
+ // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
+ // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but
+ // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
+ // for error-info.
+ final NormalizedNodeStreamWriter streamWriter = new DelegatingNormalizedNodeStreamWriter(jsonStreamWriter) {
+ @Override
+ public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
+ if(name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+ jsonWriter.name(Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName());
+ jsonWriter.value(value.toString());
+ } else {
+ super.leafNode(name, value);
+ }
+ }
+ };
+
+ final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter);
try {
if(isDataRoot) {
writeDataRoot(outputWriter,nnWriter,(ContainerNode) data);
final InstanceIdentifierContext<?> pathContext = errorsNode.getInstanceIdentifierContext();
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- XMLStreamWriter xmlWriter;
+ final XMLStreamWriter xmlWriter;
try {
xmlWriter = XML_FACTORY.createXMLStreamWriter(outStream, "UTF-8");
} catch (final XMLStreamException e) {
schemaPath = schemaPath.getParent();
}
- final NormalizedNodeStreamWriter streamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
+ final NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
pathContext.getSchemaContext(), schemaPath);
+
+ // We create a delegating writer to special-case error-info as error-info is defined as an empty
+ // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
+ // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but
+ // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
+ // for error-info.
+ final NormalizedNodeStreamWriter streamWriter = new DelegatingNormalizedNodeStreamWriter(xmlStreamWriter) {
+ @Override
+ public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
+ if(name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+ String ns = Draft02.RestConfModule.ERROR_INFO_QNAME.getNamespace().toString();
+ try {
+ xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX,
+ Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName(), ns);
+ xmlWriter.writeCharacters(value.toString());
+ xmlWriter.writeEndElement();
+ } catch (XMLStreamException e) {
+ throw new IOException("Error writing error-info", e);
+ }
+ } else {
+ super.leafNode(name, value);
+ }
+ }
+ };
+
final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter);
try {
if (isDataRoot) {
nnWriter.flush();
}
}
+
+ private static class DelegatingNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter {
+ private final NormalizedNodeStreamWriter delegate;
+
+ DelegatingNormalizedNodeStreamWriter(NormalizedNodeStreamWriter delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void leafNode(NodeIdentifier name, Object value) throws IOException, IllegalArgumentException {
+ delegate.leafNode(name, value);
+ }
+
+ @Override
+ public void startLeafSet(NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
+ delegate.startLeafSet(name, childSizeHint);
+ }
+
+ @Override
+ public void leafSetEntryNode(Object value) throws IOException, IllegalArgumentException {
+ delegate.leafSetEntryNode(value);
+ }
+
+ @Override
+ public void startContainerNode(NodeIdentifier name, int childSizeHint) throws IOException,
+ IllegalArgumentException {
+ delegate.startContainerNode(name, childSizeHint);
+ }
+
+ @Override
+ public void startUnkeyedList(NodeIdentifier name, int childSizeHint) throws IOException,
+ IllegalArgumentException {
+ delegate.startUnkeyedList(name, childSizeHint);
+ }
+
+ @Override
+ public void startUnkeyedListItem(NodeIdentifier name, int childSizeHint) throws IOException,
+ IllegalStateException {
+ delegate.startUnkeyedListItem(name, childSizeHint);
+ }
+
+ @Override
+ public void startMapNode(NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
+ delegate.startMapNode(name, childSizeHint);
+ }
+
+ @Override
+ public void startMapEntryNode(NodeIdentifierWithPredicates identifier, int childSizeHint) throws IOException,
+ IllegalArgumentException {
+ delegate.startMapEntryNode(identifier, childSizeHint);
+ }
+
+ @Override
+ public void startOrderedMapNode(NodeIdentifier name, int childSizeHint) throws IOException,
+ IllegalArgumentException {
+ delegate.startOrderedMapNode(name, childSizeHint);
+ }
+
+ @Override
+ public void startChoiceNode(NodeIdentifier name, int childSizeHint) throws IOException,
+ IllegalArgumentException {
+ delegate.startChoiceNode(name, childSizeHint);
+ }
+
+ @Override
+ public void startAugmentationNode(AugmentationIdentifier identifier) throws IOException,
+ IllegalArgumentException {
+ delegate.startAugmentationNode(identifier);
+ }
+
+ @Override
+ public void anyxmlNode(NodeIdentifier name, Object value) throws IOException, IllegalArgumentException {
+ delegate.anyxmlNode(name, value);
+ }
+
+ @Override
+ public void endNode() throws IOException, IllegalStateException {
+ delegate.endNode();
+ }
+
+ @Override
+ public void close() throws IOException {
+ delegate.close();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ delegate.flush();
+ }
+ }
}
LOG.debug("Update ConfigDataStore fail " + identifier, e);
throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
}
+ } catch (Exception e) {
+ final String errMsg = "Error updating data ";
+ LOG.debug(errMsg + identifier, e);
+ throw new RestconfDocumentedException(errMsg, e);
}
}
throw e;
} catch (final Exception e) {
final String errMsg = "Error creating data ";
- LOG.info(errMsg + uriInfo.getPath(), e);
+ LOG.info(errMsg + (uriInfo != null ? uriInfo.getPath() : ""), e);
throw new RestconfDocumentedException(errMsg, e);
}
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
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.NormalizedNode;
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.impl.ImmutableLeafNodeBuilder;
import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
* @throws ParseException
*/
@Test
- public void getDataWithSlashesBehindMountPoint() throws UnsupportedEncodingException, URISyntaxException,
- ParseException {
+ public void getDataWithSlashesBehindMountPoint() throws Exception {
final YangInstanceIdentifier awaitedInstanceIdentifier = prepareInstanceIdentifierForList();
when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), eq(awaitedInstanceIdentifier))).thenReturn(
prepareCnDataForSlashesBehindMountPointTest());
assertEquals(200, get(uri, MediaType.APPLICATION_XML));
}
- private YangInstanceIdentifier prepareInstanceIdentifierForList() throws URISyntaxException, ParseException {
+ private YangInstanceIdentifier prepareInstanceIdentifierForList() throws Exception {
final List<PathArgument> parameters = new ArrayList<>();
- final Date revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-01-09");
- final URI uri = new URI("test:module");
- final QName qNameCont = QName.create(uri, revision, "cont");
- final QName qNameList = QName.create(uri, revision, "lst1");
- final QName qNameKeyList = QName.create(uri, revision, "lf11");
+ final QName qNameCont = newTestModuleQName("cont");
+ final QName qNameList = newTestModuleQName("lst1");
+ final QName qNameKeyList = newTestModuleQName("lf11");
parameters.add(new YangInstanceIdentifier.NodeIdentifier(qNameCont));
parameters.add(new YangInstanceIdentifier.NodeIdentifier(qNameList));
return YangInstanceIdentifier.create(parameters);
}
+ private QName newTestModuleQName(String localPart) throws Exception {
+ final Date revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-01-09");
+ final URI uri = new URI("test:module");
+ return QName.create(uri, revision, localPart);
+ }
+
@Test
public void getDataMountPointIntoHighestElement() throws UnsupportedEncodingException, URISyntaxException,
ParseException {
assertEquals(200, get(uri, MediaType.APPLICATION_XML));
}
+ @SuppressWarnings("unchecked")
+ @Test
+ public void getDataWithIdentityrefInURL() throws Exception {
+ setControllerContext(schemaContextTestModule);
+
+ QName moduleQN = newTestModuleQName("module");
+ ImmutableMap<QName, Object> keyMap = ImmutableMap.<QName, Object>builder()
+ .put(newTestModuleQName("type"), newTestModuleQName("test-identity"))
+ .put(newTestModuleQName("name"), "foo").build();
+ YangInstanceIdentifier iid = YangInstanceIdentifier.builder().node(newTestModuleQName("modules"))
+ .node(moduleQN).nodeWithKey(moduleQN, keyMap).build();
+ @SuppressWarnings("rawtypes")
+ NormalizedNode data = ImmutableMapNodeBuilder.create().withNodeIdentifier(
+ new NodeIdentifier(moduleQN)).withChild(ImmutableNodes.mapEntryBuilder()
+ .withNodeIdentifier(new NodeIdentifierWithPredicates(moduleQN, keyMap))
+ .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);
+
+ String uri = "/config/test-module:modules/module/test-module:test-identity/foo";
+ assertEquals(200, get(uri, MediaType.APPLICATION_XML));
+ }
+
// /modules
@Test
public void getModulesTest() throws UnsupportedEncodingException, FileNotFoundException {
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
import com.google.gson.JsonArray;
import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
import org.w3c.dom.Document;
-import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
void verifyJson(JsonElement errorInfoElement);
}
- static class ComplexErrorInfoVerifier implements ErrorInfoVerifier {
-
- Map<String, String> expErrorInfo;
-
- public ComplexErrorInfoVerifier(final Map<String, String> expErrorInfo) {
- this.expErrorInfo = expErrorInfo;
- }
-
- @Override
- public void verifyXML(final Node errorInfoNode) {
-
- final Map<String, String> mutableExpMap = Maps.newHashMap(expErrorInfo);
- final NodeList childNodes = errorInfoNode.getChildNodes();
- for (int i = 0; i < childNodes.getLength(); i++) {
- final Node child = childNodes.item(i);
- if (child instanceof Element) {
- final String expValue = mutableExpMap.remove(child.getNodeName());
- assertNotNull("Found unexpected \"error-info\" child node: " + child.getNodeName(), expValue);
- assertEquals("Text content for \"error-info\" child node " + child.getNodeName(), expValue,
- child.getTextContent());
- }
- }
-
- if (!mutableExpMap.isEmpty()) {
- fail("Missing \"error-info\" child nodes: " + mutableExpMap);
- }
- }
-
- @Override
- public void verifyJson(final JsonElement errorInfoElement) {
-
- assertTrue("\"error-info\" Json element is not an Object", errorInfoElement.isJsonObject());
-
- final Map<String, String> actualErrorInfo = Maps.newHashMap();
- for (final Entry<String, JsonElement> entry : errorInfoElement.getAsJsonObject().entrySet()) {
- final String leafName = entry.getKey();
- final JsonElement leafElement = entry.getValue();
- actualErrorInfo.put(leafName, leafElement.getAsString());
- }
-
- final Map<String, String> mutableExpMap = Maps.newHashMap(expErrorInfo);
- for (final Entry<String, String> actual : actualErrorInfo.entrySet()) {
- final String expValue = mutableExpMap.remove(actual.getKey());
- assertNotNull("Found unexpected \"error-info\" child node: " + actual.getKey(), expValue);
- assertEquals("Text content for \"error-info\" child node " + actual.getKey(), expValue,
- actual.getValue());
- }
-
- if (!mutableExpMap.isEmpty()) {
- fail("Missing \"error-info\" child nodes: " + mutableExpMap);
- }
- }
- }
-
static class SimpleErrorInfoVerifier implements ErrorInfoVerifier {
String expTextContent;
}
@Test
- @Ignore // TODO : we are not supported "error-info" element yet
public void testToJsonResponseWithErrorInfo() throws Exception {
final String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
testJsonResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo)), Status.BAD_REQUEST,
ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
- new ComplexErrorInfoVerifier(ImmutableMap.of("session-id", "123", "address", "1.2.3.4")));
+ new SimpleErrorInfoVerifier(errorInfo));
}
@Test
- @Ignore //TODO : we are not supporting "error-info" yet
public void testToJsonResponseWithExceptionCause() throws Exception {
final Exception cause = new Exception("mock exception cause");
}
@Test
- @Ignore // TODO : we are not supporting "error-info" node yet
public void testToXMLResponseWithErrorInfo() throws Exception {
final String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
testXMLResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo)), Status.BAD_REQUEST,
ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
- new ComplexErrorInfoVerifier(ImmutableMap.of("session-id", "123", "address", "1.2.3.4")));
+ new SimpleErrorInfoVerifier(errorInfo));
}
@Test
- @Ignore // TODO : we are not supporting "error-info" node yet
public void testToXMLResponseWithExceptionCause() throws Exception {
final Exception cause = new Exception("mock exception cause");
revision 2014-01-09 {
}
+ identity module-type {
+ }
+
+ identity test-identity {
+ }
+
container interfaces {
container class {
leaf name {
}
}
}
-
+
+ container modules {
+ list module {
+ key "type name";
+ leaf name {
+ type string;
+ mandatory true;
+ }
+
+ leaf type {
+ type identityref {
+ base module-type;
+ }
+ mandatory true;
+ }
+
+ leaf data {
+ type string;
+ }
+ }
+ }
+
list lst-with-composite-key {
key "key1 key2";
leaf key1 {