added own XML to Composite node translation 25/2825/2
authormsunal <msunal@cisco.com>
Mon, 18 Nov 2013 13:55:07 +0000 (14:55 +0100)
committermsunal <msunal@cisco.com>
Mon, 18 Nov 2013 15:56:00 +0000 (16:56 +0100)
- 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 <msunal@cisco.com>
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/StructuredDataToJsonProvider.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/UnsupportedFormatException.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/UnsupportedJsonFormatException.java [deleted file]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java

index 3115994e01575a57661db4bbc73bc81927e9713d..a0acaf156facb47a74f9e672f1514277f78fc4a4 100644 (file)
@@ -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<Entry<String, JsonElement>> 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<String, JsonElement> 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.");
         }
     }
     
index daaedd92b8d6c85f05f26365b90f6bbe68f7d5d6..27ebebabd7f7fe97c7dcc24f20c1a5de761f1284 100644 (file)
@@ -35,7 +35,7 @@ public enum JsonToCompositeNodeProvider implements MessageBodyReader<CompositeNo
         JsonReader jsonReader = new JsonReader();
         try {
             return jsonReader.read(entityStream);
-        } catch (UnsupportedJsonFormatException e) {
+        } catch (UnsupportedFormatException e) {
             throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST)
                     .entity(e.getMessage()).build());
         }
index 4a851a3f6ca531f6042b9d20df7f4d9ba3cd3517..90e6d2affc5cde925d4adc941b24d42005aad117 100644 (file)
@@ -24,7 +24,7 @@ import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import com.google.gson.stream.JsonWriter;
 
 @Provider
-@Produces({ API + RestconfService.JSON })
+@Produces({API+RestconfService.JSON})
 public enum StructuredDataToJsonProvider implements MessageBodyWriter<StructuredData> {
     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 (file)
index 0000000..615f209
--- /dev/null
@@ -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 (file)
index dccf29b..0000000
+++ /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 (file)
index 0000000..9f31eb4
--- /dev/null
@@ -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<NodeWrapper<?>> 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);
+    }
+    
+}
index 09733f5e907faa533167be5211b39cf2839b6345..07b30327f07d7c8d8723e79268d213702bc189b4 100644 (file)
@@ -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<CompositeNode> {
     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<CompositeNod
     public CompositeNode readFrom(Class<CompositeNode> type, Type genericType, Annotation[] annotations,
             MediaType mediaType, MultivaluedMap<String, String> 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());
         }
index ea3a4fbcfcbae90e848c39e505008dd701613b26..a41a48287df87f887dd37c12b0fd88f4539f7597 100644 (file)
@@ -113,7 +113,7 @@ class RestconfImpl implements RestconfService {
             val List<NodeWrapper<?>> 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)])
             }
         }
     }
index 9d004362d29d0b64b9a8636f51430ccfa46d8ca3..baf226712ffbb255550948c3c5f36160d4c30262 100644 (file)
@@ -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<LogRecord> 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<CompositeNode>() {
-            
+        when(brokerFacade.commitConfigurationDataPut(any(InstanceIdentifier.class), any(CompositeNode.class))).thenReturn(new Future<RpcResult<TransactionStatus>>() {
+            @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<RpcError> getErrors() {
+            public RpcResult<TransactionStatus> get() throws InterruptedException, ExecutionException {
+                return null;
+            }
+            @Override
+            public RpcResult<TransactionStatus> 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