From: msunal Date: Mon, 18 Nov 2013 13:55:07 +0000 (+0100) Subject: added own XML to Composite node translation X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~379^2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=6cfe2845817ee6c0036de61ba9e4e51e55954eb4 added own XML to Composite node translation - JsonReader is used for translation of XML to CompositeNode. It creates CompositeNode and its structure with CompositeNodeWrapper and SimpleNodeWrapper implementations. It allows to add namespaces after translation. Change-Id: Ib0d8fef54befe791f53901785688c2dc2bd5bb8a Signed-off-by: Martin Sunal --- diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java index 3115994e01..a0acaf156f 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java @@ -17,17 +17,17 @@ import com.google.gson.JsonPrimitive; class JsonReader { - public CompositeNodeWrapper read(InputStream entityStream) throws UnsupportedJsonFormatException { + public CompositeNodeWrapper read(InputStream entityStream) throws UnsupportedFormatException { JsonParser parser = new JsonParser(); JsonElement rootElement = parser.parse(new InputStreamReader(entityStream)); if (!rootElement.isJsonObject()) { - throw new UnsupportedJsonFormatException("Root element of Json has to be Object"); + throw new UnsupportedFormatException("Root element of Json has to be Object"); } Set> entrySetsOfRootJsonObject = rootElement.getAsJsonObject().entrySet(); if (entrySetsOfRootJsonObject.size() != 1) { - throw new UnsupportedJsonFormatException("Json Object should contain one element"); + throw new UnsupportedFormatException("Json Object should contain one element"); } else { Entry childEntry = Lists.newArrayList(entrySetsOfRootJsonObject).get(0); String firstElementName = childEntry.getKey(); @@ -41,10 +41,10 @@ class JsonReader { if (firstElementInArray.isJsonObject()) { return createStructureWithRoot(firstElementName, firstElementInArray.getAsJsonObject()); } - throw new UnsupportedJsonFormatException("Array as the first element in Json Object can have only Object element"); + throw new UnsupportedFormatException("Array as the first element in Json Object can have only Object element"); } } - throw new UnsupportedJsonFormatException("First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."); + throw new UnsupportedFormatException("First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."); } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java index daaedd92b8..27ebebabd7 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java @@ -35,7 +35,7 @@ public enum JsonToCompositeNodeProvider implements MessageBodyReader { INSTANCE; diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/UnsupportedFormatException.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/UnsupportedFormatException.java new file mode 100644 index 0000000000..615f209037 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/UnsupportedFormatException.java @@ -0,0 +1,23 @@ +package org.opendaylight.controller.sal.rest.impl; + +public class UnsupportedFormatException extends Exception { + + private static final long serialVersionUID = -1741388894406313402L; + + public UnsupportedFormatException() { + super(); + } + + public UnsupportedFormatException(String message, Throwable cause) { + super(message, cause); + } + + public UnsupportedFormatException(String message) { + super(message); + } + + public UnsupportedFormatException(Throwable cause) { + super(cause); + } + +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/UnsupportedJsonFormatException.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/UnsupportedJsonFormatException.java deleted file mode 100644 index dccf29b052..0000000000 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/UnsupportedJsonFormatException.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.opendaylight.controller.sal.rest.impl; - -public class UnsupportedJsonFormatException extends Exception { - - private static final long serialVersionUID = -1741388894406313402L; - - public UnsupportedJsonFormatException() { - super(); - } - - public UnsupportedJsonFormatException(String message, Throwable cause) { - super(message, cause); - } - - public UnsupportedJsonFormatException(String message) { - super(message); - } - - public UnsupportedJsonFormatException(Throwable cause) { - super(cause); - } - -} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java new file mode 100644 index 0000000000..9f31eb46d5 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java @@ -0,0 +1,157 @@ +package org.opendaylight.controller.sal.rest.impl; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.io.InputStream; +import java.net.URI; +import java.util.Stack; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper; +import org.opendaylight.controller.sal.restconf.impl.NodeWrapper; +import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper; + +public class XmlReader { + + private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + private XMLEventReader eventReader; + + public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException, UnsupportedFormatException { + eventReader = xmlInputFactory.createXMLEventReader(entityStream); + + if (eventReader.hasNext()) { + XMLEvent element = eventReader.peek(); + if (element.isStartDocument()) { + eventReader.nextEvent(); + } + } + + if (eventReader.hasNext() && !isCompositeNodeEvent(eventReader.peek())) { + throw new UnsupportedFormatException("Root element of XML has to be composite element."); + } + + final Stack> processingQueue = new Stack<>(); + CompositeNodeWrapper root = null; + NodeWrapper element = null; + while (eventReader.hasNext()) { + final XMLEvent event = eventReader.nextEvent(); + + if (event.isStartElement()) { + final StartElement startElement = event.asStartElement(); + CompositeNodeWrapper compParentNode = null; + if (!processingQueue.isEmpty() && processingQueue.peek() instanceof CompositeNodeWrapper) { + compParentNode = (CompositeNodeWrapper) processingQueue.peek(); + } + NodeWrapper newNode = null; + if (isCompositeNodeEvent(event)) { + if (root == null) { + root = resolveCompositeNodeFromStartElement(startElement); + newNode = root; + } else { + newNode = resolveCompositeNodeFromStartElement(startElement); + } + } else if (isSimpleNodeEvent(event)) { + if (root == null) { + throw new UnsupportedFormatException("Root element of XML has to be composite element."); + } + newNode = resolveSimpleNodeFromStartElement(startElement); + } + + if (newNode != null) { + processingQueue.push(newNode); + if (compParentNode != null) { + compParentNode.addValue(newNode); + } + } + } else if (event.isEndElement()) { + element = processingQueue.pop(); + } + } + + if (!root.getLocalName().equals(element.getLocalName())) { + throw new UnsupportedFormatException("XML should contain only one root element"); + } + + return root; + } + + private boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException { + checkArgument(event != null, "XML Event cannot be NULL!"); + if (event.isStartElement()) { + if (eventReader.hasNext()) { + final XMLEvent innerEvent; + innerEvent = eventReader.peek(); + if (innerEvent.isCharacters()) { + final Characters chars = innerEvent.asCharacters(); + if (!chars.isWhiteSpace()) { + return true; + } + } else if (innerEvent.isEndElement()) { + return true; + } + } + } + return false; + } + + private boolean isCompositeNodeEvent(final XMLEvent event) throws XMLStreamException { + checkArgument(event != null, "XML Event cannot be NULL!"); + if (event.isStartElement()) { + if (eventReader.hasNext()) { + XMLEvent innerEvent; + innerEvent = eventReader.peek(); + if (innerEvent.isCharacters()) { + Characters chars = innerEvent.asCharacters(); + if (chars.isWhiteSpace()) { + eventReader.nextEvent(); + innerEvent = eventReader.peek(); + } + } + if (innerEvent.isStartElement()) { + return true; + } + } + } + return false; + } + + private SimpleNodeWrapper resolveSimpleNodeFromStartElement(final StartElement startElement) throws XMLStreamException { + checkArgument(startElement != null, "Start Element cannot be NULL!"); + String data = null; + + if (eventReader.hasNext()) { + final XMLEvent innerEvent = eventReader.peek(); + if (innerEvent.isCharacters()) { + final Characters chars = innerEvent.asCharacters(); + if (!chars.isWhiteSpace()) { + data = innerEvent.asCharacters().getData(); + } + } else if (innerEvent.isEndElement()) { + data = ""; + } + } + + return new SimpleNodeWrapper(getNamespaceFrom(startElement), getLocalNameFrom(startElement), data); + } + + private CompositeNodeWrapper resolveCompositeNodeFromStartElement(final StartElement startElement) { + checkArgument(startElement != null, "Start Element cannot be NULL!"); + return new CompositeNodeWrapper(getNamespaceFrom(startElement), getLocalNameFrom(startElement)); + } + + private String getLocalNameFrom(StartElement startElement) { + return startElement.getName().getLocalPart(); + } + + private URI getNamespaceFrom(StartElement startElement) { + String namespaceURI = startElement.getName().getNamespaceURI(); + return namespaceURI.isEmpty() ? null : URI.create(namespaceURI); + } + +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java index 09733f5e90..07b30327f0 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java @@ -18,19 +18,12 @@ import javax.xml.stream.XMLStreamException; import org.opendaylight.controller.sal.rest.api.RestconfService; import org.opendaylight.yangtools.yang.data.api.CompositeNode; -import org.opendaylight.yangtools.yang.data.api.Node; -import org.opendaylight.yangtools.yang.data.api.SimpleNode; -import org.opendaylight.yangtools.yang.data.impl.XmlTreeBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Provider @Consumes({API+RestconfService.XML}) public enum XmlToCompositeNodeProvider implements MessageBodyReader { INSTANCE; - private final static Logger logger = LoggerFactory.getLogger(XmlToCompositeNodeProvider.class); - @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return true; @@ -40,16 +33,10 @@ public enum XmlToCompositeNodeProvider implements MessageBodyReader type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { + XmlReader xmlReader = new XmlReader(); try { - Node node = XmlTreeBuilder.buildDataTree(entityStream); - if (node instanceof SimpleNode) { - logger.info("Node is SimpleNode"); - throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) - .entity("XML should start with XML element that contains 1..N XML child elements.").build()); - } - return (CompositeNode) node; - } catch (XMLStreamException e) { - logger.info("Error during translation of InputStream to Node\n" + e.getMessage()); + return xmlReader.read(entityStream); + } catch (XMLStreamException | UnsupportedFormatException e) { throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) .entity(e.getMessage()).build()); } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend index ea3a4fbcfc..a41a48287d 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend @@ -113,7 +113,7 @@ class RestconfImpl implements RestconfService { val List> children = (nodeBuilder as CompositeNodeWrapper).getValues for (child : children) { addNamespaceToNodeFromSchemaRecursively(child, - (schema as DataNodeContainer).childNodes.findFirst[n|n.QName.localName === child.localName]) + (schema as DataNodeContainer).childNodes.findFirst[n|n.QName.localName.equals(child.localName)]) } } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java index 9d004362d2..baf226712f 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java @@ -13,6 +13,10 @@ import java.net.URLEncoder; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -31,6 +35,7 @@ import org.glassfish.jersey.test.TestProperties; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.rest.api.RestconfService; import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider; import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider; @@ -73,7 +78,7 @@ public class XmlProvidersTest extends JerseyTest { restconfImpl.setControllerContext(controllerContext); } -// @Before + @Before public void logs() { List loggedRecords = getLoggedRecords(); for (LogRecord l : loggedRecords) { @@ -102,26 +107,32 @@ public class XmlProvidersTest extends JerseyTest { public void testXmlToCompositeNodeProvider() throws ParserConfigurationException, SAXException, IOException { URI uri = null; try { - uri = new URI("/operations/" + URLEncoder.encode("ietf-interfaces:interfaces/interface/eth0", Charsets.US_ASCII.name()).toString()); + uri = new URI("/config/" + URLEncoder.encode("ietf-interfaces:interfaces/interface/eth0", Charsets.US_ASCII.name()).toString()); } catch (UnsupportedEncodingException | URISyntaxException e) { e.printStackTrace(); } InputStream xmlStream = RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml"); final CompositeNode loadedCompositeNode = TestUtils.loadCompositeNode(xmlStream); - when(brokerFacade.invokeRpc(any(QName.class), any(CompositeNode.class))).thenReturn(new RpcResult() { - + when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn(new Future>() { + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } @Override - public boolean isSuccessful() { - return true; + public boolean isCancelled() { + return false; } - @Override - public CompositeNode getResult() { - return loadedCompositeNode; + public boolean isDone() { + return false; } - @Override - public Collection getErrors() { + public RpcResult get() throws InterruptedException, ExecutionException { + return null; + } + @Override + public RpcResult get(long timeout, TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { return null; } }); @@ -132,7 +143,7 @@ public class XmlProvidersTest extends JerseyTest { Document doc = docBuilder.parse(xmlStream); Response response = target(uri.toASCIIString()).request(MediaTypes.API+RestconfService.XML).post(Entity.entity(TestUtils.getDocumentInPrintableForm(doc), new MediaType("application","vnd.yang.api+xml"))); - assertEquals(200, response.getStatus()); + assertEquals(204, response.getStatus()); } @Test