add export link on project
authorymh <ymh.work@gmail.com>
Thu, 12 Jun 2014 16:27:35 +0200
changeset 304 8ad1734d9d8a
parent 303 af4a54a78ba6
child 305 4dc484119b4c
add export link on project
server/pom.xml
server/src/main/java/org/iri_research/renkan/controller/RenkanController.java
server/src/main/java/org/iri_research/renkan/rest/RestApplication.java
server/src/main/webapp/WEB-INF/i18n/messages.properties
server/src/main/webapp/WEB-INF/i18n/messages_en.properties
server/src/main/webapp/WEB-INF/i18n/messages_fr.properties
server/src/main/webapp/WEB-INF/templates/projectIndex.html
server/src/main/webapp/WEB-INF/templates/renkanIndex.html
server/src/main/webapp/WEB-INF/web.xml
server/src/test/java/org/iri_research/renkan/test/controller/RenkanControllerTest.java
--- a/server/pom.xml	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/pom.xml	Thu Jun 12 16:27:35 2014 +0200
@@ -41,6 +41,7 @@
         <bson4jackson-version>2.2.3</bson4jackson-version>
         <fasterxml-java-uuid-generator-version>3.1.3</fasterxml-java-uuid-generator-version>
         <guava-version>17.0</guava-version>
+        <json-path-version>0.9.1</json-path-version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
 
@@ -447,6 +448,12 @@
             <artifactId>guava</artifactId>
             <version>${guava-version}</version>
         </dependency>
+        <dependency>
+            <groupId>com.jayway.jsonpath</groupId>
+            <artifactId>json-path-assert</artifactId>
+            <version>${json-path-version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <organization>
         <name>IRI</name>
--- a/server/src/main/java/org/iri_research/renkan/controller/RenkanController.java	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/src/main/java/org/iri_research/renkan/controller/RenkanController.java	Thu Jun 12 16:27:35 2014 +0200
@@ -1,8 +1,11 @@
 package org.iri_research.renkan.controller;
 
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 
+import javax.servlet.http.HttpServletResponse;
+
 import org.iri_research.renkan.Constants;
 import org.iri_research.renkan.Constants.EditMode;
 import org.iri_research.renkan.RenkanException;
@@ -10,6 +13,7 @@
 import org.iri_research.renkan.models.Project;
 import org.iri_research.renkan.repositories.ProjectsRepository;
 import org.iri_research.renkan.repositories.SpacesRepository;
+import org.iri_research.renkan.rest.ObjectMapperProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +31,12 @@
 import org.springframework.web.client.HttpServerErrorException;
 import org.springframework.web.servlet.ModelAndView;
 
+import com.fasterxml.jackson.annotation.ObjectIdGenerators.UUIDGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
 @Controller
 @RequestMapping("/p")
 public class RenkanController {
@@ -39,6 +49,9 @@
 
     @Autowired
     private SpacesRepository spacesRepository;
+    
+    @Autowired
+    private ObjectMapperProvider mapperProvider;
 
     private void checkCowebkey(String cowebkey, Project project,
             Constants.EditMode editMode) {
@@ -117,17 +130,17 @@
     @RequestMapping(value = "/pub/{project_id}", method = RequestMethod.GET, produces = {
             "text/html;charset=UTF-8", "!image/*" })
     public String renkanPublishProject(Model model,
-            @PathVariable(value = "project_id") String project_id,
+            @PathVariable(value = "project_id") String projectId,
             @RequestParam(value = "cowebkey") String cowebkey) {
-        if (project_id == null || project_id.length() == 0) {
+        if (projectId == null || projectId.length() == 0) {
             throw new IllegalArgumentException(
                     "RenkanContoller.renkanProject: Project id is null or empty.");
         }
 
-        Project project = this.projectsRepository.findOne(project_id);
+        Project project = this.projectsRepository.findOne(projectId);
         if (project == null) {
             throw new HttpClientErrorException(HttpStatus.NOT_FOUND, "Project "
-                    + project_id + " not found.");
+                    + projectId + " not found.");
         }
 
         this.checkCowebkey(cowebkey, project, EditMode.READ_ONLY);
@@ -138,5 +151,52 @@
 
         return "renkanProjectPublish";
     }
+    
+    @RequestMapping(value = "/exp/{project_id}", method = RequestMethod.GET, produces = { "application/json;charset=UTF-8" })
+    public @ResponseBody String exportProject(@PathVariable(value = "project_id") String projectId, HttpServletResponse response) throws JsonProcessingException {
+        
+        ObjectMapper mapper = this.mapperProvider.getContext(ObjectMapper.class);
+        
+        Project project = this.projectsRepository.findOne(projectId);
+        
+        if (project == null) {
+            throw new HttpClientErrorException(HttpStatus.NOT_FOUND, "Project "
+                    + projectId + " not found.");
+        }
+        
+        ObjectNode jsonNode = mapper.valueToTree(project);
+        
+        jsonNode.remove("id");
+        
+        Iterator<JsonNode> nodes = jsonNode.get("nodes").elements();
+
+        HashMap<String, String> nodeIds = new HashMap<String, String>();
+        UUIDGenerator uuidgens = new UUIDGenerator();
+        
+        while(nodes.hasNext()) {
+            ObjectNode nodeNode = (ObjectNode) nodes.next();
+            String nodeId = nodeNode.get("id").asText();
+            String atId = uuidgens.generateId(nodeNode).toString();
+            nodeIds.put(nodeId, atId);
+            nodeNode.put("@id", atId);
+            nodeNode.remove("id");
+        }
+        
+        Iterator<JsonNode> edges = jsonNode.get("edges").elements();
+        while(edges.hasNext()) {
+            ObjectNode edgeNode = (ObjectNode) edges.next();
+            edgeNode.put("from", nodeIds.get(edgeNode.get("from").asText()));
+            edgeNode.put("to", nodeIds.get(edgeNode.get("to").asText()));
+            edgeNode.remove("id");
+        }
+
+        String res =  mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
+        
+        response.setContentType("application/force-download");
+        response.setHeader("Content-Transfer-Encoding", "binary");
+        response.setHeader("Content-Disposition", "attachment; filename=\""+ projectId +".json\"");
+        
+        return res;
+    }
 
 }
--- a/server/src/main/java/org/iri_research/renkan/rest/RestApplication.java	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/src/main/java/org/iri_research/renkan/rest/RestApplication.java	Thu Jun 12 16:27:35 2014 +0200
@@ -19,7 +19,7 @@
         
         ObjectMapper objectMapper = this.objectMapperProvider.getContext(ObjectMapper.class);
         
-        JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(objectMapper, JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS);        
+        JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(objectMapper, JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS);
         
         this.register(provider);
 
--- a/server/src/main/webapp/WEB-INF/i18n/messages.properties	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/src/main/webapp/WEB-INF/i18n/messages.properties	Thu Jun 12 16:27:35 2014 +0200
@@ -15,10 +15,12 @@
 renkanIndex.project_copy = Copy
 renkanIndex.project_delete = Delete
 renkanIndex.project_render = View
+renkanIndex.project_export = Export
 renkanIndex.project_edit_link = Edit renkan
 renkanIndex.project_copy_link = Copy renkan
 renkanIndex.project_delete_link = Delete renkan
 renkanIndex.project_render_link = View renkan
+renkanIndex.project_export_link = Export renkan
 renkanIndex.project_delete_confirm = Delete renkan "<%= title %>" ?
 renkanIndex.project_filter = Filter title
 
--- a/server/src/main/webapp/WEB-INF/i18n/messages_en.properties	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/src/main/webapp/WEB-INF/i18n/messages_en.properties	Thu Jun 12 16:27:35 2014 +0200
@@ -14,10 +14,12 @@
 renkanIndex.project_copy = Copy
 renkanIndex.project_delete = Delete
 renkanIndex.project_render = View
+renkanIndex.project_export = Export
 renkanIndex.project_edit_link = Edit renkan
 renkanIndex.project_copy_link = Copy renkan
 renkanIndex.project_delete_link = Delete renkan
 renkanIndex.project_render_link = View renkan
+renkanIndex.project_export_link = Export renkan
 renkanIndex.project_delete_confirm = Delete renkan "<%= title %>" ?
 renkanIndex.project_filter = Filter title
 
--- a/server/src/main/webapp/WEB-INF/i18n/messages_fr.properties	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/src/main/webapp/WEB-INF/i18n/messages_fr.properties	Thu Jun 12 16:27:35 2014 +0200
@@ -15,10 +15,12 @@
 renkanIndex.project_copy = Copier
 renkanIndex.project_delete = Eff.
 renkanIndex.project_render = Consult.
+renkanIndex.project_export = Export.
 renkanIndex.project_edit_link = Editer renkan
 renkanIndex.project_copy_link = Copier renkan
 renkanIndex.project_delete_link = Eff. renkan
 renkanIndex.project_render_link = Consult. renkan
+renkanIndex.project_export_link = Export. renkan
 renkanIndex.project_delete_confirm = Voulez-vous effacer le renkan "<%= title %>" ?
 renkanIndex.project_filter = Filtre titre
 
--- a/server/src/main/webapp/WEB-INF/templates/projectIndex.html	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/src/main/webapp/WEB-INF/templates/projectIndex.html	Thu Jun 12 16:27:35 2014 +0200
@@ -69,6 +69,7 @@
                       <th th:text="#{renkanIndex.project_copy}">Copy</th>
                       <th th:text="#{renkanIndex.project_delete}">Del.</th>
                       <th th:text="#{renkanIndex.project_render}">View</th>
+                      <th th:text="#{renkanIndex.project_export}">Export</th>
                   </tr>
                 </thead>
                 <tbody>
@@ -80,6 +81,7 @@
                     <td><a href="#" th:text="#{renkanIndex.project_copy_link}" th:attr="data-project_id=${project.id}" class="copy-project">Copy project</a></td>
                     <td><a href="#" th:text="#{renkanIndex.project_delete_link}" th:attr="data-project_id=${project.id},data-project_title=${project.title}" class="delete-project">Delete project</a></td>
                     <td><a href="#" th:href="@{'/p/pub/'+${project.id}(cowebkey=${project.getKey(1)})}" th:text="#{renkanIndex.project_render_link}">View project</a></td>
+                    <td><a href="#" th:href="@{'/p/exp/'+${project.id}}" th:text="#{renkanIndex.project_export_link}">Export project</a></td>
                   </tr>
                 </tbody>
               </table>
--- a/server/src/main/webapp/WEB-INF/templates/renkanIndex.html	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/src/main/webapp/WEB-INF/templates/renkanIndex.html	Thu Jun 12 16:27:35 2014 +0200
@@ -14,7 +14,6 @@
 
         <script src="../../lib/jquery.min.js" th:src="@{/static/lib/jquery.min.js}" ></script>
         <script src="../../lib/underscore-min.js" th:src="@{/static/lib/underscore-min.js}" ></script>
-        <script src="../../js/main.js" th:src="@{/static/js/main.js}" ></script>
 
         <link href="../../static/css/style.css" rel="stylesheet" th:href="@{/static/css/style.css}"/>
         <link href="../../static/css/index.css" rel="stylesheet" th:href="@{/static/css/index.css}"/> 
@@ -26,10 +25,12 @@
                 <h1 th:text="#{renkanIndex.renkan_spaces}">Renkan Spaces</h1>
             </header>
             <div id="inner">
-                <div id="label" class="translate" th:text="#{renkanIndex.space_exp}">Create a Space</div> 
+                <div id="label" class="translate" th:text="#{renkanIndex.space_exp}">Create a Space</div>                
                 <form action="#" onsubmit="go2Title();return false;">
-                    <input type="text" id="renkantitle" autofocus="autofocus"/> 
-                    <button type="submit">OK</button>
+                    <fieldset id="form-fields">
+                        <div id="title-field"><label th:text="#{renkanIndex.renkan_title}" for="renkantitle">title</label><input type="text" id="renkantitle" autofocus="autofocus" x-webkit-speech="x-webkit-speech"/></div>
+                    </fieldset>
+                    <div id="form-submit"><button type="submit">OK</button></div>
                 </form>
             </div>
             <h2 th:text="#{renkanIndex.space_list}">Space list</h2>
--- a/server/src/main/webapp/WEB-INF/web.xml	Thu Jun 05 18:15:52 2014 +0200
+++ b/server/src/main/webapp/WEB-INF/web.xml	Thu Jun 12 16:27:35 2014 +0200
@@ -41,21 +41,13 @@
     <init-param>
        <param-name>ws.bufferSize</param-name>
        <param-value>100000</param-value>
-    </init-param>        
+    </init-param>
     <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
     <servlet-name>cometd</servlet-name>
     <url-pattern>/cometd/*</url-pattern>
   </servlet-mapping>
-  <filter>
-    <filter-name>cross-origin</filter-name>
-    <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
-  </filter>
-  <filter-mapping>
-    <filter-name>cross-origin</filter-name>
-    <url-pattern>/cometd/*</url-pattern>
-  </filter-mapping>
   <servlet>
     <servlet-name>admin</servlet-name>
     <servlet-class>org.coweb.servlet.AdminServlet</servlet-class>
@@ -99,4 +91,13 @@
       <filter-name>springSecurityFilterChain</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>
+  <filter>
+    <filter-name>cross-origin</filter-name>
+    <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
+  </filter>
+  <filter-mapping>
+    <filter-name>cross-origin</filter-name>
+    <url-pattern>/cometd/*</url-pattern>
+    <url-pattern>/rest/*</url-pattern>
+  </filter-mapping>
 </web-app>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/test/java/org/iri_research/renkan/test/controller/RenkanControllerTest.java	Thu Jun 12 16:27:35 2014 +0200
@@ -0,0 +1,191 @@
+package org.iri_research.renkan.test.controller;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import org.iri_research.renkan.models.Edge;
+import org.iri_research.renkan.models.Node;
+import org.iri_research.renkan.models.Project;
+import org.iri_research.renkan.models.Space;
+import org.iri_research.renkan.repositories.EdgesRepository;
+import org.iri_research.renkan.repositories.NodesRepository;
+import org.iri_research.renkan.repositories.ProjectsRepository;
+import org.iri_research.renkan.repositories.SpacesRepository;
+import org.joda.time.DateTime;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.geo.Point;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ContextConfiguration(locations = { "controller-context.xml",
+        "file:src/main/webapp/WEB-INF/spring-servlet.xml" })
+public class RenkanControllerTest {
+
+    private final static int SPACE_NB = 3;
+
+    private Logger logger = LoggerFactory.getLogger(RenkanControllerTest.class);
+
+    @Autowired
+    private SpacesRepository spacesRepository;
+    @Autowired
+    private ProjectsRepository projectsRepository;
+    @Autowired
+    private NodesRepository nodesRepository;
+    @Autowired
+    private EdgesRepository edgesRepository;
+
+    private Map<String, Space> spacesList = new HashMap<String, Space>(SPACE_NB);
+    private List<String> spacesUuids = new ArrayList<>(SPACE_NB);
+
+    private ArrayList<Project> testProjects = new ArrayList<>();
+    private ArrayList<Node> testNodes = new ArrayList<>();
+    private ArrayList<Edge> testEdges = new ArrayList<>();
+
+    @Autowired
+    private WebApplicationContext context;
+    private MockMvc mvc;
+
+    private void clean() {
+        edgesRepository.deleteAll();
+        nodesRepository.deleteAll();
+        projectsRepository.deleteAll();
+        spacesRepository.deleteAll();
+    }
+    
+    @Before
+    public void setup() {
+
+        logger.debug("Setup");
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        this.clean();
+
+        ArrayList<Project> pl = new ArrayList<Project>();
+        for (int i = 0; i < SPACE_NB; i++) {
+            DateTime creationDate = new DateTime();
+            String uuid = UUID.randomUUID().toString();
+            spacesUuids.add(uuid);
+            Space testSpace = new Space(uuid, "test " + i, "Test space " + 1,
+                    "{}", "http://ldt.iri.centrepompidou.fr", "#ababab",
+                    "test_user", "http://ldt.iri.centrepompidou.fr",
+                    creationDate);
+            testSpace = spacesRepository.save(testSpace);
+            this.spacesList.put(uuid, testSpace);
+            for (int j = 0; j < SPACE_NB - 1 - i; j++) {
+                pl.add(new Project(testSpace.getId(), UUID.randomUUID()
+                        .toString(), "test" + ((SPACE_NB - 1) * i + j + 1),
+                        "desc" + ((SPACE_NB - 1) * i + j + 1),
+                        "http://localhost:8080/rest/projects/id"
+                                + ((SPACE_NB - 1) * i + j + 1), creationDate));
+            }
+            try {
+                Thread.sleep(1);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        
+        Project testProject = pl.get(0);
+        
+        for (int i = 0; i < 3; i++) {
+            Node node = new Node("Node" + i, "Node" + i, "Node " + i,
+                    "http://renkan.org/nodes/node" + i, "#ffff0" + i,
+                    "test_user", new Point(0, i),
+                    "http://renkan.org/images/node" + i, i, testProject.getId());
+            node = this.nodesRepository.save(node);
+            testProject.getNodes().add(node);
+            this.testNodes.add(node);
+        }
+
+        for (int i = 0; i < 3; i++) {
+            Edge edge = new Edge("Node" + i, "Node" + i, "Node " + i,
+                    "http://renkan.org/edges/edge" + i, "#ffff0" + i,
+                    this.testNodes.get((i + 2) % 3), this.testNodes.get(i),
+                    "test_user", testProject.getId());
+            edge = this.edgesRepository.save(edge);
+            testProject.getEdges().add(edge);
+            this.testEdges.add(edge);
+        }
+
+
+        for (Project p : projectsRepository.save(pl)) {
+            this.testProjects.add(p);
+        }
+
+        this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
+    }
+
+    
+    @After
+    public void teardown() {
+        this.clean();
+    }
+    
+    @Test
+    public void testExportProject() throws Exception {
+        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/p/exp/"+this.testProjects.get(0).getId());
+        MvcResult res = this.mvc.perform(get)
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+                .andReturn();
+        
+        logger.debug("testExportProject resp : "
+                + res.getResponse().getContentAsString());
+    }
+    
+    @Test
+    public void testExportProjectContent() throws Exception {
+        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/p/exp/"+this.testProjects.get(0).getId());
+        MvcResult res = this.mvc.perform(get)
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+                .andExpect(MockMvcResultMatchers.jsonPath("title").exists())
+                .andExpect(MockMvcResultMatchers.jsonPath("title").value("test1"))
+                .andExpect(MockMvcResultMatchers.jsonPath("description").exists())
+                .andExpect(MockMvcResultMatchers.jsonPath("description").value("desc1"))
+                .andExpect(MockMvcResultMatchers.jsonPath("nodes").isArray())
+                .andExpect(MockMvcResultMatchers.jsonPath("edges").isArray())
+                .andReturn();
+        
+        logger.debug("testExportProjectContent resp : "
+                + res.getResponse().getContentAsString());
+    }
+    
+    
+    @Test
+    public void testExportProjectExclude() throws Exception {
+        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/p/exp/"+this.testProjects.get(0).getId());
+        MvcResult res = this.mvc.perform(get)
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+                .andExpect(MockMvcResultMatchers.jsonPath("id").doesNotExist())
+                .andExpect(MockMvcResultMatchers.jsonPath("nodes[*].id").doesNotExist())
+                .andExpect(MockMvcResultMatchers.jsonPath("nodes[*].@id").exists())
+                .andExpect(MockMvcResultMatchers.jsonPath("edges[*].id").doesNotExist())
+                .andReturn();
+        
+        logger.debug("testExportProjectContentExclude resp : "
+                + res.getResponse().getContentAsString());
+    }
+
+}