Added serializer/deserializer for JSON/XML 63/2363/5
authormsunal <msunal@cisco.com>
Tue, 5 Nov 2013 14:45:55 +0000 (15:45 +0100)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 5 Nov 2013 16:12:14 +0000 (16:12 +0000)
- exceptions are translated to rest call response with state code 400 or 500
- imporoved singleton pattern
- tests
- added providers for serialization/deserialization of JSON/XML
- supported rest methods: readData, invokeRpc

Change-Id: I3a888fc1ad9c0ae2364d050e5c67875f9e075337
Signed-off-by: Tony Tkacik <ttkacik@cisco.com>
Signed-off-by: Martin Sunal <msunal@cisco.com>
16 files changed:
opendaylight/distribution/opendaylight/pom.xml
opendaylight/md-sal/sal-rest-connector/pom.xml
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfService.java [moved from opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfService.java with 81% similarity]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfApplication.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/StructuredDataToJsonProvider.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/StructuredDataToXmlProvider.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/StructuredData.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ControllerContextTest.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JsonMapperTest.java [deleted file]
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfImplTest.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/XmlProvidersTest.java [new file with mode: 0644]

index 790b9b34c0dd30ebe106d4e1ed527112d706140e..4482df07805cd852b73f1a9130f30e1a9cde9c9b 100644 (file)
           <groupId>org.opendaylight.yangtools</groupId>
           <artifactId>yang-data-api</artifactId>
          </dependency>
+         <dependency>
+          <groupId>org.opendaylight.yangtools</groupId>
+          <artifactId>yang-data-impl</artifactId>
+          <version>0.5.9-SNAPSHOT</version>
+         </dependency>
          <dependency>
           <groupId>org.opendaylight.yangtools</groupId>
           <artifactId>yang-data-util</artifactId>
     <dependency>
       <groupId>com.google.code.gson</groupId>
       <artifactId>gson</artifactId>
+      <version>2.2.4</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
index 24ac2467f04cdb418e0ff8cb8546295bb3c306fc..8cc46dda9f12b4156ef0662d280a99349e9cf0b1 100644 (file)
       <version>3.0.4.Final</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-data-impl</artifactId>
+      <version>${yang.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+      <version>2.2.4</version>
+    </dependency>
     
     <!-- Testing Dependencies -->
     <dependency>
@@ -62,9 +72,9 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.opendaylight.yangtools</groupId>
-      <artifactId>yang-data-impl</artifactId>
-      <version>${yang.version}</version>
+      <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+      <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+      <version>2.4</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
@@ -5,7 +5,7 @@
  * 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.controller.sal.restconf.impl;
+package org.opendaylight.controller.sal.rest.api;
 
 import static org.opendaylight.controller.sal.restconf.impl.MediaTypes.API;
 
@@ -16,6 +16,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 
+import org.opendaylight.controller.sal.restconf.impl.StructuredData;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 
 /**
@@ -59,26 +60,26 @@ public interface RestconfService {
 
     @GET
     @Path("/datastore/{identifier}")
-    @Produces({API+XML})
-    public Object readData(@PathParam("identifier") String identifier);
+    @Produces({API+JSON,API+XML})
+    public StructuredData readData(@PathParam("identifier") String identifier);
 
     @PUT
     @Path("/datastore/{identifier}")
-    @Produces({API+XML})
+    @Produces({API+JSON,API+XML})
     public Object createConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload);
 
     @POST
     @Path("/datastore/{identifier}")
-    @Produces({API+XML})
+    @Produces({API+JSON,API+XML})
     public Object updateConfigurationData(@PathParam("identifier") String identifier, CompositeNode payload);
 
     @GET
     @Path("/modules")
-    @Produces(API+XML)
+    @Produces({API+JSON,API+XML})
     public Object getModules();
 
     @POST
     @Path("/operations/{identifier}")
-    @Produces(API+XML)
-    public Object invokeRpc(@PathParam("identifier") String identifier, CompositeNode payload);
+    @Produces({API+JSON,API+XML})
+    public StructuredData invokeRpc(@PathParam("identifier") String identifier, CompositeNode payload);
 }
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
new file mode 100644 (file)
index 0000000..97ad3ef
--- /dev/null
@@ -0,0 +1,39 @@
+package org.opendaylight.controller.sal.rest.impl;
+
+import static org.opendaylight.controller.sal.restconf.impl.MediaTypes.API;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+
+import org.opendaylight.controller.sal.rest.api.RestconfService;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+
+@Provider
+@Consumes({API+RestconfService.JSON})
+public enum JsonToCompositeNodeProvider implements MessageBodyReader<CompositeNode> {
+    INSTANCE;
+
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public CompositeNode readFrom(Class<CompositeNode> type, Type genericType, Annotation[] annotations,
+            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
+            throws IOException, WebApplicationException {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfApplication.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfApplication.java
new file mode 100644 (file)
index 0000000..6dc7484
--- /dev/null
@@ -0,0 +1,30 @@
+package org.opendaylight.controller.sal.rest.impl;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
+import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
+import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
+
+public class RestconfApplication extends Application {
+
+    @Override
+    public Set<Object> getSingletons() {
+        Set<Object> singletons = new HashSet<>();
+        ControllerContext controllerContext = ControllerContext.getInstance();
+        BrokerFacade brokerFacade = BrokerFacade.getInstance();
+        RestconfImpl restconfImpl = RestconfImpl.getInstance();
+        restconfImpl.setBroker(brokerFacade);
+        restconfImpl.setControllerContext(controllerContext);
+        singletons.add(controllerContext);
+        singletons.add(brokerFacade);
+        singletons.add(restconfImpl);
+        singletons.add(XmlToCompositeNodeProvider.INSTANCE);
+        singletons.add(StructuredDataToXmlProvider.INSTANCE);
+        return singletons;
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/StructuredDataToJsonProvider.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/StructuredDataToJsonProvider.java
new file mode 100644 (file)
index 0000000..608cdcd
--- /dev/null
@@ -0,0 +1,136 @@
+package org.opendaylight.controller.sal.rest.impl;
+
+import static org.opendaylight.controller.sal.restconf.impl.MediaTypes.API;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.opendaylight.controller.sal.rest.api.RestconfService;
+import org.opendaylight.controller.sal.restconf.impl.StructuredData;
+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.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
+
+import com.google.gson.stream.JsonWriter;
+
+@Provider
+@Produces({ API + RestconfService.JSON })
+public enum StructuredDataToJsonProvider implements MessageBodyWriter<StructuredData> {
+    INSTANCE;
+    
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    @Override
+    public long getSize(StructuredData t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(StructuredData t, Class<?> type, Type genericType, Annotation[] annotations,
+            MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
+            throws IOException, WebApplicationException {
+        JsonWriter writer = new JsonWriter(new OutputStreamWriter(entityStream, "UTF-8"));
+        writer.setIndent("    ");
+        writer.beginObject();
+        convertNodeToJsonAccordingToSchema(writer, t.getData(), t.getSchema());
+        writer.endObject();
+    }
+
+    private void convertNodeToJsonAccordingToSchema(JsonWriter writer, Node<?> node, DataSchemaNode dataSchemaNode) throws IOException {
+        if (node instanceof CompositeNode) {
+            if (!(dataSchemaNode instanceof DataNodeContainer)) {
+                throw new IllegalStateException("CompositeNode should be represented as DataNodeContainer");
+            }
+            if (dataSchemaNode instanceof ContainerSchemaNode) {
+                writer.name(node.getNodeType().getLocalName());
+                writer.beginObject();
+                String listName = "";
+                for (Node<?> n : ((CompositeNode) node).getChildren()) {
+                    DataSchemaNode foundDataSchemaNode = findSchemaForNode(n, ((DataNodeContainer) dataSchemaNode).getChildNodes());
+                    if (foundDataSchemaNode instanceof ListSchemaNode) {
+                        if (listName.equals(n.getNodeType().getLocalName())) {
+                            continue;
+                        }
+                        listName = n.getNodeType().getLocalName();
+                    }
+                    convertNodeToJsonAccordingToSchema(writer, n, foundDataSchemaNode);
+                }
+                writer.endObject();
+            } else if (dataSchemaNode instanceof ListSchemaNode) {
+                writer.name(node.getNodeType().getLocalName());
+                writer.beginArray();
+                List<Node<?>> nodeSiblings = node.getParent().getChildren();
+                for (Node<?> nodeSibling : nodeSiblings) {
+                    if (nodeSibling.getNodeType().getLocalName().equals(node.getNodeType().getLocalName())) {
+                        DataSchemaNode schemaForNodeSibling = findSchemaForNode(nodeSibling,
+                                ((DataNodeContainer) dataSchemaNode.getParent()).getChildNodes());
+                        writer.beginObject();
+                        for (Node<?> child : ((CompositeNode) nodeSibling).getChildren()) {
+                            DataSchemaNode schemaForChild = findSchemaForNode(child,
+                                    ((DataNodeContainer) schemaForNodeSibling).getChildNodes());
+                            convertNodeToJsonAccordingToSchema(writer, child, schemaForChild);
+                        }
+                        writer.endObject();
+                    }
+                }
+                writer.endArray();
+            }
+        } else if (node instanceof SimpleNode<?>) {
+            if (!(dataSchemaNode instanceof LeafSchemaNode)) {
+                throw new IllegalStateException("SimpleNode should should be represented as LeafSchemaNode");
+            }
+            writeLeaf(writer, (LeafSchemaNode) dataSchemaNode, (SimpleNode<?>) node);
+        }
+    }
+
+    private DataSchemaNode findSchemaForNode(Node<?> node, Set<DataSchemaNode> dataSchemaNode) {
+        for (DataSchemaNode dsn : dataSchemaNode) {
+            if (node.getNodeType().getLocalName().equals(dsn.getQName().getLocalName())) {
+                return dsn;
+            }
+        }
+        return null;
+    }
+
+    private void writeLeaf(JsonWriter writer, LeafSchemaNode leafSchemaNode, SimpleNode<?> data) throws IOException {
+        TypeDefinition<?> type = leafSchemaNode.getType();
+
+        writer.name(data.getNodeType().getLocalName());
+
+        if (type instanceof DecimalTypeDefinition) {
+            writer.value((Double.valueOf((String) data.getValue())).doubleValue());
+        } else if (type instanceof IntegerTypeDefinition) {
+            writer.value((Integer.valueOf((String) data.getValue())).intValue());
+        } else if (type instanceof EmptyTypeDefinition) {
+            writer.value("[null]");
+        } else {
+            writer.value(String.valueOf(data.getValue()));
+        }
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/StructuredDataToXmlProvider.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/StructuredDataToXmlProvider.java
new file mode 100644 (file)
index 0000000..0bce2e2
--- /dev/null
@@ -0,0 +1,71 @@
+package org.opendaylight.controller.sal.rest.impl;
+
+import static org.opendaylight.controller.sal.restconf.impl.MediaTypes.API;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.opendaylight.controller.sal.rest.api.RestconfService;
+import org.opendaylight.controller.sal.restconf.impl.StructuredData;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.impl.NodeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+
+@Provider
+@Produces({API+RestconfService.XML})
+public enum StructuredDataToXmlProvider implements MessageBodyWriter<StructuredData> {
+    INSTANCE;
+    
+    private final static Logger logger = LoggerFactory.getLogger(StructuredDataToXmlProvider.class);
+
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return true;
+    }
+
+    @Override
+    public long getSize(StructuredData t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(StructuredData t, Class<?> type, Type genericType, Annotation[] annotations,
+            MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
+            throws IOException, WebApplicationException {
+        CompositeNode data = t.getData();
+        Document domTree = NodeUtils.buildShadowDomTree(data);
+        
+        try {
+            TransformerFactory tf = TransformerFactory.newInstance();
+            Transformer transformer = tf.newTransformer();
+            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+            transformer.transform(new DOMSource(domTree), new StreamResult(entityStream));
+        } catch (TransformerException e) {
+            logger.error("Error during translation of Document to OutputStream", e);
+            new WebApplicationException(Response.status(Response.Status.INTERNAL_SERVER_ERROR).build());
+        }
+    }
+
+}
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
new file mode 100644 (file)
index 0000000..09733f5
--- /dev/null
@@ -0,0 +1,58 @@
+package org.opendaylight.controller.sal.rest.impl;
+
+import static org.opendaylight.controller.sal.restconf.impl.MediaTypes.API;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
+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;
+    }
+
+    @Override
+    public CompositeNode readFrom(Class<CompositeNode> type, Type genericType, Annotation[] annotations,
+            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
+            throws IOException, WebApplicationException {
+        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());
+            throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST)
+                    .entity(e.getMessage()).build());
+        }
+    }
+
+}
index 07cd4a846b211d68a1643081da667768a81e3a93..8e18661db6ecdfcc65d822dff5bf1e4c115c0b96 100644 (file)
@@ -7,14 +7,27 @@ import org.opendaylight.yangtools.yang.common.QName
 import org.opendaylight.yangtools.yang.common.RpcResult
 import org.opendaylight.yangtools.yang.data.api.CompositeNode
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
+import static org.opendaylight.controller.sal.restconf.impl.BrokerFacade.*
 
 class BrokerFacade implements DataReader<InstanceIdentifier, CompositeNode> {
 
+    val static BrokerFacade INSTANCE = new BrokerFacade
+
     @Property
     private ConsumerSession context;
 
     @Property
     private DataBrokerService dataService;
+    
+    private new() {
+        if (INSTANCE != null) {
+            throw new IllegalStateException("Already instantiated");
+        }
+    }
+    
+    def static BrokerFacade getInstance() {
+        return INSTANCE
+    }
 
     override readConfigurationData(InstanceIdentifier path) {
         return dataService.readConfigurationData(path);
@@ -40,5 +53,5 @@ class BrokerFacade implements DataReader<InstanceIdentifier, CompositeNode> {
         transaction.putConfigurationData(path, payload);
         return transaction.commit()
     }
-
+    
 }
index 2218023cafe16ff1b53ad6131c21b390f87882b8..5057413a7a0710b8261e359340fca5b0820e4f2f 100644 (file)
@@ -27,14 +27,26 @@ import static com.google.common.base.Preconditions.*
 
 class ControllerContext {
     
+    val static ControllerContext INSTANCE = new ControllerContext
+    
     val static NULL_VALUE = "null"
-
+    
     @Property
     SchemaContext schemas;
 
     private val BiMap<URI, String> uriToModuleName = HashBiMap.create();
     private val Map<String, URI> moduleNameToUri = uriToModuleName.inverse();
 
+    private new() {
+        if (INSTANCE != null) {
+            throw new IllegalStateException("Already instantiated");
+        }
+    }
+
+    static def getInstance() {
+        return INSTANCE
+    }
+
     public def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) {
         val ret = InstanceIdentifier.builder();
         val pathArgs = restconfInstance.split("/");
index 0b0ebc592259b5c89a8fb29ff9a2c9026b9d95a9..fc0dd1017fea6fbb9e77cd5f3b5a84d616b9e58f 100644 (file)
@@ -1,29 +1,31 @@
 package org.opendaylight.controller.sal.restconf.impl
 
-import org.opendaylight.controller.sal.core.api.model.SchemaService
 import org.opendaylight.yangtools.yang.data.api.CompositeNode
-
-import static com.google.common.base.Preconditions.*
+import org.opendaylight.controller.sal.rest.api.RestconfService
 
 class RestconfImpl implements RestconfService {
+    
+    val static RestconfImpl INSTANCE = new RestconfImpl
 
     @Property
     BrokerFacade broker
 
     @Property
     extension ControllerContext controllerContext
-
-    val JsonMapper jsonMapper = new JsonMapper;
-
-    def init(SchemaService schemaService) {
-        checkState(broker !== null)
-        checkState(controllerContext !== null)
-        checkState(schemaService !== null)
-        controllerContext.schemas = schemaService.globalContext
+    
+    private new() {
+        if (INSTANCE != null) {
+            throw new IllegalStateException("Already instantiated");
+        }
+    }
+    
+    static def getInstance() {
+        return INSTANCE
     }
 
     override readAllData() {
-        return broker.readOperationalData("".removePrefixes.toInstanceIdentifier.getInstanceIdentifier);
+//        return broker.readOperationalData("".toInstanceIdentifier.getInstanceIdentifier);
+        throw new UnsupportedOperationException("TODO: auto-generated method stub")
     }
 
     override getModules() {
@@ -36,26 +38,24 @@ class RestconfImpl implements RestconfService {
     }
 
     override readData(String identifier) {
-        val instanceIdentifierWithSchemaNode = identifier.removePrefixes.toInstanceIdentifier
+        val instanceIdentifierWithSchemaNode = identifier.toInstanceIdentifier
         val data = broker.readOperationalData(instanceIdentifierWithSchemaNode.getInstanceIdentifier);
-        jsonMapper.convert(instanceIdentifierWithSchemaNode.getSchemaNode, data)
+        return new StructuredData(data, instanceIdentifierWithSchemaNode.schemaNode)
     }
 
     override createConfigurationData(String identifier, CompositeNode payload) {
-        return broker.commitConfigurationDataCreate(identifier.removePrefixes.toInstanceIdentifier.getInstanceIdentifier, payload);
+//        return broker.commitConfigurationDataCreate(identifier.toInstanceIdentifier.getInstanceIdentifier, payload);
+        throw new UnsupportedOperationException("TODO: auto-generated method stub")
     }
 
     override updateConfigurationData(String identifier, CompositeNode payload) {
-        return broker.commitConfigurationDataCreate(identifier.removePrefixes.toInstanceIdentifier.getInstanceIdentifier, payload);
+//        return broker.commitConfigurationDataCreate(identifier.toInstanceIdentifier.getInstanceIdentifier, payload);
+        throw new UnsupportedOperationException("TODO: auto-generated method stub")
     }
 
     override invokeRpc(String identifier, CompositeNode payload) {
-        val rpcResult = broker.invokeRpc(identifier.removePrefixes.toRpcQName, payload);
-        jsonMapper.convert(identifier.removePrefixes.toInstanceIdentifier.getSchemaNode, rpcResult.result);
-    }
-
-    private def String removePrefixes(String path) {
-        return path;
+        val rpcResult = broker.invokeRpc(identifier.toRpcQName, payload);
+        return new StructuredData(rpcResult.result, identifier.toInstanceIdentifier.getSchemaNode)
     }
 
 }
diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/StructuredData.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/StructuredData.java
new file mode 100644 (file)
index 0000000..12f33d4
--- /dev/null
@@ -0,0 +1,24 @@
+package org.opendaylight.controller.sal.restconf.impl;
+
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+
+public class StructuredData {
+    
+    private final CompositeNode data;
+    private final DataSchemaNode schema;
+    
+    public StructuredData(CompositeNode data, DataSchemaNode schema) {
+        this.data = data;
+        this.schema = schema;
+    }
+
+    public CompositeNode getData() {
+        return data;
+    }
+
+    public DataSchemaNode getSchema() {
+        return schema;
+    }
+    
+}
index 8460140101d87d6ca9db06fa934dfa970b657f88..39c0d3b34f67159e7de8a1c1fc4a73809ae648b8 100644 (file)
@@ -15,7 +15,7 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 public class ControllerContextTest {
 
-    private static final ControllerContext controllerContext = new ControllerContext();
+    private static final ControllerContext controllerContext = ControllerContext.getInstance();
 
     @BeforeClass
     public static void init() throws FileNotFoundException {
@@ -56,7 +56,7 @@ public class ControllerContextTest {
         assertTrue(instanceIdentifier.getSchemaNode() instanceof ContainerSchemaNode);
         assertEquals(2, ((ContainerSchemaNode)instanceIdentifier.getSchemaNode()).getChildNodes().size());
     }
-    
+
     @Test
     public void testToInstanceIdentifierChoice() throws FileNotFoundException {
         InstanceIdWithSchemaNode instanceIdentifier = controllerContext.toInstanceIdentifier("simple-nodes:food/beer");
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JsonMapperTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/JsonMapperTest.java
deleted file mode 100644 (file)
index 18a5d7a..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.opendaylight.controller.sal.restconf.impl.test;
-
-import static org.junit.Assert.*;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.Set;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
-import org.opendaylight.controller.sal.restconf.impl.JsonMapper;
-import org.opendaylight.yangtools.yang.data.api.CompositeNode;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-
-public class JsonMapperTest {
-
-    private static final ControllerContext controllerContext = new ControllerContext();
-
-    @BeforeClass
-    public static void init() throws FileNotFoundException {
-        Set<Module> allModules = TestUtils.loadModules(JsonMapperTest.class.getResource("/full-versions/yangs").getPath());
-        SchemaContext schemaContext = TestUtils.loadSchemaContext(allModules);
-        controllerContext.setSchemas(schemaContext);
-    }
-
-    @Test
-    public void test() throws FileNotFoundException {
-        InputStream xmlStream = JsonMapperTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml");
-        CompositeNode loadedCompositeNode = TestUtils.loadCompositeNode(xmlStream);
-        DataSchemaNode loadedSchemaNode = controllerContext.toInstanceIdentifier("ietf-interfaces:interfaces/interface/eth0").getSchemaNode();
-        JsonMapper jsonMapper = new JsonMapper();
-        String json = jsonMapper.convert(loadedSchemaNode, loadedCompositeNode);
-    }
-
-}
index 292dfd8e2836174f7d661b45dc44b462b583a209..c36de6e4eabc9abc11c10d20cb3eab353e73d096 100644 (file)
@@ -19,13 +19,13 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 public class RestconfImplTest {
 
-    private static final RestconfImpl restconfImpl = new RestconfImpl();
+    private static final RestconfImpl restconfImpl = RestconfImpl.getInstance();
 
     @BeforeClass
     public static void init() throws FileNotFoundException {
         Set<Module> allModules = TestUtils.loadModules(RestconfImplTest.class.getResource("/full-versions/yangs").getPath());
         SchemaContext schemaContext = TestUtils.loadSchemaContext(allModules);
-        ControllerContext controllerContext = new ControllerContext();
+        ControllerContext controllerContext = ControllerContext.getInstance();
         controllerContext.setSchemas(schemaContext);
         restconfImpl.setControllerContext(controllerContext);
     }
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
new file mode 100644 (file)
index 0000000..3020824
--- /dev/null
@@ -0,0 +1,167 @@
+package org.opendaylight.controller.sal.restconf.impl.test;
+
+import static org.mockito.Mockito.*;
+import static org.junit.Assert.*;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.controller.sal.rest.api.RestconfService;
+import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
+import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
+import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
+import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
+import org.opendaylight.controller.sal.restconf.impl.MediaTypes;
+import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import com.google.common.base.Charsets;
+
+public class XmlProvidersTest extends JerseyTest {
+
+    private static ControllerContext controllerContext;
+    private static BrokerFacade brokerFacade;
+    private static RestconfImpl restconfImpl;
+
+    @BeforeClass
+    public static void init() {
+        Set<Module> allModules = null;
+        try {
+            allModules = TestUtils.loadModules(RestconfImplTest.class.getResource("/full-versions/yangs").getPath());
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+        SchemaContext schemaContext = TestUtils.loadSchemaContext(allModules);
+        controllerContext = ControllerContext.getInstance();
+        controllerContext.setSchemas(schemaContext);
+        brokerFacade = mock(BrokerFacade.class);
+        restconfImpl = RestconfImpl.getInstance();
+        restconfImpl.setBroker(brokerFacade);
+        restconfImpl.setControllerContext(controllerContext);
+    }
+
+//    @Before
+    public void logs() {
+        List<LogRecord> loggedRecords = getLoggedRecords();
+        for (LogRecord l : loggedRecords) {
+            System.out.println(l.getMessage());
+        }
+    }
+
+    @Test
+    public void testStructuredDataToXmlProvider() throws FileNotFoundException {
+        URI uri = null;
+        try {
+            uri = new URI("/restconf/datastore/" + 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");
+        CompositeNode loadedCompositeNode = TestUtils.loadCompositeNode(xmlStream);
+        when(brokerFacade.readOperationalData(any(InstanceIdentifier.class))).thenReturn(loadedCompositeNode);
+        
+        Response response = target(uri.toASCIIString()).request(MediaTypes.API+RestconfService.XML).get();
+        assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testXmlToCompositeNodeProvider() throws ParserConfigurationException, SAXException, IOException {
+        URI uri = null;
+        try {
+            uri = new URI("/restconf/operations/" + 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>() {
+            
+            @Override
+            public boolean isSuccessful() {
+                return true;
+            }
+            
+            @Override
+            public CompositeNode getResult() {
+                return loadedCompositeNode;
+            }
+            
+            @Override
+            public Collection<RpcError> getErrors() {
+                return null;
+            }
+        });
+        
+        DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
+        DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
+        xmlStream = RestconfImplTest.class.getResourceAsStream("/parts/ietf-interfaces_interfaces.xml");
+        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());
+    }
+    
+    @Test
+    public void testXmlToCompositeNodeProviderExceptions() {
+        URI uri = null;
+        try {
+            uri = new URI("/restconf/operations/" + URLEncoder.encode("ietf-interfaces:interfaces/interface/eth0", Charsets.US_ASCII.name()).toString());
+        } catch (UnsupportedEncodingException | URISyntaxException e) {
+            e.printStackTrace();
+        }
+        
+        Response response = target(uri.toASCIIString()).request(MediaTypes.API + RestconfService.XML).post(
+                Entity.entity("<SimpleNode/>", new MediaType("application", "vnd.yang.api+xml")));
+        assertEquals(400, response.getStatus());
+        
+        response = target(uri.toASCIIString()).request(MediaTypes.API + RestconfService.XML).post(
+                Entity.entity("<SimpleNode>", new MediaType("application", "vnd.yang.api+xml")));
+        assertEquals(400, response.getStatus());
+    }
+
+    @Override
+    protected Application configure() {
+        enable(TestProperties.LOG_TRAFFIC);
+        enable(TestProperties.DUMP_ENTITY);
+        enable(TestProperties.RECORD_LOG_LEVEL);
+        set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue());
+        
+        ResourceConfig resourceConfig = new ResourceConfig();
+        resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE);
+        return resourceConfig;
+    }
+
+}