--- 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());
+ }
+
+}