add the count of project by spaces, add pagination, update libraries and add some more unit tests.
authorymh <ymh.work@gmail.com>
Wed, 13 Mar 2013 15:47:08 +0100
changeset 76 523f0647513e
parent 75 7adef9ce92aa
child 77 4f2511a70880
add the count of project by spaces, add pagination, update libraries and add some more unit tests.
.classpath
server/pom.xml
server/src/main/java/org/iri_research/renkan/Constants.java
server/src/main/java/org/iri_research/renkan/controller/RenkanRootController.java
server/src/main/java/org/iri_research/renkan/repositories/IRenkanRepository.java
server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepository.java
server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepositoryCustom.java
server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepositoryImpl.java
server/src/main/resources/log4j.xml
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/mongo-config.xml
server/src/main/webapp/WEB-INF/spring-servlet.xml
server/src/main/webapp/WEB-INF/templates/fragment/paginationFragment.html
server/src/main/webapp/WEB-INF/templates/projectIndex.html
server/src/main/webapp/WEB-INF/templates/renkanIndex.html
server/src/test/java/org/iri_research/renkan/test/repositories/ProjectsRepositoryTest.java
server/src/test/java/org/iri_research/renkan/test/repositories/SpacesRepositoryTest.java
server/src/test/resources/log4j.xml
--- a/.classpath	Wed Mar 13 15:37:31 2013 +0100
+++ b/.classpath	Wed Mar 13 15:47:08 2013 +0100
@@ -20,8 +20,8 @@
 	<classpathentry kind="var" path="M2_REPO"/>
 	<classpathentry kind="var" path="M2_REPO/org/springframework/spring-beans/3.2.1.RELEASE/spring-beans-3.2.1.RELEASE.jar"/>
 	<classpathentry kind="var" path="M2_REPO/javax/persistence/persistence-api/1.0.2/persistence-api-1.0.2.jar"/>
-	<classpathentry kind="var" path="M2_REPO/org/springframework/data/spring-data-mongodb/1.1.1.RELEASE/spring-data-mongodb-1.1.1.RELEASE.jar"/>
-	<classpathentry kind="var" path="M2_REPO/org/springframework/data/spring-data-commons-core/1.4.1.RELEASE/spring-data-commons-core-1.4.1.RELEASE.jar"/>
+	<classpathentry kind="var" path="M2_REPO/org/springframework/data/spring-data-mongodb/1.2.0.RELEASE/spring-data-mongodb-1.2.0.RELEASE.jar"/>
+	<classpathentry kind="var" path="M2_REPO/org/springframework/data/spring-data-commons/1.5.0.RELEASE/spring-data-commons-1.5.0.RELEASE.jar"/>
 	<classpathentry kind="var" path="M2_REPO/org/springframework/data/spring-data-jpa/1.3.0.RELEASE/spring-data-jpa-1.3.0.RELEASE.jar"/>
 	<classpathentry kind="var" path="M2_REPO/javax/inject/javax.inject/1/javax.inject-1.jar"/>
 	<classpathentry kind="var" path="M2_REPO/org/cometd/java/cometd-java-annotations/2.5.0/cometd-java-annotations-2.5.0.jar"/>
--- a/server/pom.xml	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/pom.xml	Wed Mar 13 15:47:08 2013 +0100
@@ -7,7 +7,8 @@
     <version>0.1</version>
     <packaging>war</packaging>
 
-    <properties>       
+    <properties>
+        <surefire-version>2.14</surefire-version>
         <coweb-version>1.0</coweb-version>
         <cowebx-version>1.0</cowebx-version>
         <jersey-version>1.17</jersey-version>
@@ -16,7 +17,7 @@
         <!--jetty-version>9.0.0.RC0</jetty-version-->
         <jetty-version>8.1.9.v20130131</jetty-version>
         <junit-version>4.10</junit-version>
-        <thymeleaf-version>2.0.15</thymeleaf-version>
+        <thymeleaf-version>2.0.16</thymeleaf-version>
         <cometd-version>2.5.1</cometd-version>
         <jackson-version>2.1.4</jackson-version>
         <joda-version>2.1</joda-version>
@@ -100,6 +101,11 @@
                     </webResources>
                 </configuration>
             </plugin>
+	        <plugin>
+	          <groupId>org.apache.maven.plugins</groupId>
+	          <artifactId>maven-surefire-plugin</artifactId>
+	          <version>${surefire-version}</version>
+	        </plugin>
         </plugins>
     </build>
     <repositories>
@@ -257,18 +263,18 @@
         </dependency>        
         <dependency>
             <groupId>org.springframework.data</groupId>
-            <artifactId>spring-data-commons-core</artifactId>
-            <version>1.4.0.RELEASE</version>
+            <artifactId>spring-data-commons</artifactId>
+            <version>1.5.0.RELEASE</version>
         </dependency>
         <dependency>
             <groupId>org.springframework.data</groupId>
             <artifactId>spring-data-jpa</artifactId>
-            <version>1.2.0.RELEASE</version>
+            <version>1.3.0.RELEASE</version>
         </dependency> 
         <dependency>
             <groupId>org.springframework.data</groupId>
             <artifactId>spring-data-mongodb</artifactId>
-            <version>1.1.1.RELEASE</version>
+            <version>1.2.0.RELEASE</version>
         </dependency>
         <dependency>
             <groupId>javax.persistence</groupId>
--- a/server/src/main/java/org/iri_research/renkan/Constants.java	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/Constants.java	Wed Mar 13 15:47:08 2013 +0100
@@ -38,5 +38,6 @@
 	
 	public final static String KEYHEX = "f2338d2299ac28ef64f82956fde37337b87a2b9e8fc03e28fa0768cac37d838113c7d0fc78c60fce1e23b1b3e03ac7db4676b3189c267f26baaab10f72544441";
 	public final static int SALT_LENGTH = 24;
+	public final static int PAGINATION_SIZE = 10;
 
 }
--- a/server/src/main/java/org/iri_research/renkan/controller/RenkanRootController.java	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/controller/RenkanRootController.java	Wed Mar 13 15:47:08 2013 +0100
@@ -1,9 +1,12 @@
 package org.iri_research.renkan.controller;
 
+import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
+import javax.servlet.http.HttpServletRequest;
+
+import org.iri_research.renkan.Constants;
 import org.iri_research.renkan.models.Project;
 import org.iri_research.renkan.models.Space;
 import org.iri_research.renkan.repositories.ProjectsRepository;
@@ -11,8 +14,13 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.web.PageableDefaults;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -31,21 +39,48 @@
 	
 	@Autowired
 	private SpacesRepository spacesRepository;
+	
+	private String buildBaseUrl(HttpServletRequest request) {
+		StringBuffer baseUrl = request.getRequestURL();
+		boolean firstParam = true;
+		Enumeration<?> namesEnum = request.getParameterNames();
+		while(namesEnum.hasMoreElements()) {
+			String paramName = 
+					(String)namesEnum.nextElement();
+			
+			if("p.page".equals(paramName)) {
+				continue;
+			}
+			for(String val:request.getParameterValues(paramName)) {
+				if(firstParam) {
+					baseUrl.append('?');
+					firstParam = false;		
+				}
+				else {
+					baseUrl.append('&');
+				}
+				baseUrl.append(paramName).append('=').append(val);
+			}			
+		}
+		return baseUrl.toString();
+	}
 		
 	@RequestMapping(value="", method = RequestMethod.GET, produces={"text/html;charset=UTF-8"})
-	public ModelAndView renkanIndex() {
-				
-		Map<String, Object> model = new HashMap<String, Object>();
+	public String renkanIndex(Model model, @PageableDefaults(sort={"created"}, sortDir=Direction.DESC, value=Constants.PAGINATION_SIZE) Pageable p, HttpServletRequest request) {
+
+		Page<Space> page = this.spacesRepository.findAll(p);
+		//Iterable<Space> spaces = this.spacesRepository.findAll();
 		
-		Iterable<Space> spaces = this.spacesRepository.findAll();
+				
+		model.addAttribute("page", page);
+		model.addAttribute("baseUrl", this.buildBaseUrl(request));
+		model.addAttribute("projectsCount", this.projectsRepository.getCountBySpace());
 		
-		model.put("spaces", spaces);
-		
-		return new ModelAndView("renkanIndex", model);
+		return "renkanIndex";
 	}
 
 	@RequestMapping(value="/s/{space_id}", method = RequestMethod.GET, produces={"text/html;charset=UTF-8"})
-	public ModelAndView spaceIndex(@PathVariable("space_id") String spaceId) {
+	public ModelAndView spaceIndex(@PathVariable("space_id") String spaceId, @PageableDefaults(sort={"created"}, sortDir=Direction.DESC, value=Constants.PAGINATION_SIZE) Pageable p, HttpServletRequest request) {
 		
 		logger.debug("SpaceId : " + (spaceId== null ? "null" : spaceId));
 		
@@ -63,9 +98,10 @@
 		
 		model.put("space", space);
 		
-		List<Project> projects = this.projectsRepository.findBySpaceId(spaceId);
+		Page<Project> page = this.projectsRepository.findBySpaceId(spaceId, p);
 		
-		model.put("projects", projects);
+		model.put("page", page);
+		model.put("baseUrl", this.buildBaseUrl(request));
 		
 		return new ModelAndView("projectIndex", model);
 	}
--- a/server/src/main/java/org/iri_research/renkan/repositories/IRenkanRepository.java	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/repositories/IRenkanRepository.java	Wed Mar 13 15:47:08 2013 +0100
@@ -2,12 +2,12 @@
 
 import java.io.Serializable;
 
-import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.PagingAndSortingRepository;
 
 import com.mongodb.DBCollection;
 
 public interface IRenkanRepository<T, ID extends Serializable> extends
-		CrudRepository<T, ID> {
+		PagingAndSortingRepository<T, ID> {
 
 	public DBCollection getCollection();
 }
--- a/server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepository.java	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepository.java	Wed Mar 13 15:47:08 2013 +0100
@@ -3,9 +3,11 @@
 import java.util.List;
 
 import org.iri_research.renkan.models.Project;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 
 public interface ProjectsRepository extends IRenkanRepository<Project, String>, ProjectsRepositoryCustom {
 	
 	List<Project> findBySpaceId(String spaceId);
-
+	Page<Project> findBySpaceId(String spaceId, Pageable p);
 }
--- a/server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepositoryCustom.java	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepositoryCustom.java	Wed Mar 13 15:47:08 2013 +0100
@@ -1,5 +1,11 @@
 package org.iri_research.renkan.repositories;
 
+import java.util.Map;
+
 public interface ProjectsRepositoryCustom {
+	
 	public int getRevCounter(String projectId);
+	
+	public Map<String, Integer> getCountBySpace();
+	
 }
--- a/server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepositoryImpl.java	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/repositories/ProjectsRepositoryImpl.java	Wed Mar 13 15:47:08 2013 +0100
@@ -1,15 +1,26 @@
 package org.iri_research.renkan.repositories;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.iri_research.renkan.models.Project;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.mongodb.core.MongoTemplate;
 import org.springframework.stereotype.Component;
 import static org.springframework.data.mongodb.core.query.Query.query;
 import static org.springframework.data.mongodb.core.query.Criteria.where;
+
+import org.springframework.data.mongodb.core.mapreduce.GroupBy;
+import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
 import org.springframework.data.mongodb.core.query.Update;
 
 @Component
-public class ProjectsRepositoryImpl implements ProjectsRepositoryCustom {	
+public class ProjectsRepositoryImpl implements ProjectsRepositoryCustom {
+	
+	private class GroupResult {
+		public String space_id;
+		public int count;		
+	}
 	
 	@Autowired
 	private MongoTemplate mongoTemplate;
@@ -24,4 +35,21 @@
 		return p.getRevCounter();
 	}
 
+	@Override
+	public Map<String, Integer> getCountBySpace() {
+		
+		GroupByResults<GroupResult> groupResult = this.mongoTemplate.group(
+				this.mongoTemplate.getCollectionName(Project.class),
+				GroupBy.key("space_id").initialDocument("{ count: 0 }").reduceFunction("function(doc, prev) { prev.count += 1; }"),
+				GroupResult.class);
+		
+		HashMap<String, Integer> res = new HashMap<>();
+		for (GroupResult gr : groupResult) {
+			res.put(gr.space_id, new Integer(gr.count));
+		}
+		
+		return res;
+		
+	}
+
 }
--- a/server/src/main/resources/log4j.xml	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/resources/log4j.xml	Wed Mar 13 15:47:08 2013 +0100
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE log4j:configuration >
+<?xml version="1.0" encoding="UTF-8" standalone='no' ?>
+<!DOCTYPE log4j:configuration>
 
 <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
   <appender name="console" class="org.apache.log4j.ConsoleAppender"> 
--- a/server/src/main/webapp/WEB-INF/i18n/messages_en.properties	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/i18n/messages_en.properties	Wed Mar 13 15:47:08 2013 +0100
@@ -3,19 +3,21 @@
 
 renkanIndex.new_renkan = New Renkan
 renkanIndex.new_space = New Space
-renkanIndex.renkan_title = or create/open a Renkan with the title
+renkanIndex.renkan_exp = or create/open a Renkan with the title
 renkanIndex.project_list = Project list
 renkanIndex.project_name = Name
 renkanIndex.project_creation = Creation
 renkanIndex.project_edit = Edit
 renkanIndex.project_edit_link = Edit project
 
+renkanIndex.space_exp = or create/open a space with the title
 renkanIndex.space_list = Spaces list
 renkanIndex.space_name = Name
 renkanIndex.space_title = Title
 renkanIndex.space_creation = Creation
 renkanIndex.space_open = Open
 renkanIndex.space_open_link = Open space
+renkanIndex.space_proj_count = Proj. count
 
 
 renkanIndex.js.empty_name_error = Please enter a title
\ No newline at end of file
--- a/server/src/main/webapp/WEB-INF/i18n/messages_fr.properties	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/i18n/messages_fr.properties	Wed Mar 13 15:47:08 2013 +0100
@@ -3,7 +3,7 @@
 
 renkanIndex.new_renkan = Nouveau&nbsp;Renkan
 renkanIndex.new_space = Nouvel Espace
-renkanIndex.renkan_titre = ou bien créer un Renkan avec le titre
+renkanIndex.renkan_exp = ou bien créer un Renkan avec le titre
 
 renkanIndex.project_list = Liste des projets
 renkanIndex.project_title = Titre
@@ -11,13 +11,14 @@
 renkanIndex.project_edit = Edition
 renkanIndex.project_edit_link = Editer proj.
 
+renkanIndex.space_exp = ou bien créer un espace avec le titre
 renkanIndex.space_list = Liste des espace
 renkanIndex.space_name = Nom
 renkanIndex.space_title = Titre
 renkanIndex.space_creation = Creation
 renkanIndex.space_open = Ouvrir
 renkanIndex.space_open_link = Ouvrir esp.
-
+renkanIndex.space_proj_count = Nb. proj.
 
 renkanIndex.js.empty_name_error = Veuillez entrer un titre
 
--- a/server/src/main/webapp/WEB-INF/mongo-config.xml	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/mongo-config.xml	Wed Mar 13 15:47:08 2013 +0100
@@ -24,5 +24,5 @@
   	</bean>
   	
   	<mongo:repositories base-package="org.iri_research.renkan" factory-class="org.iri_research.renkan.repositories.RenkanRepositoryFactoryBean" />
-  	  	
+
 </beans>
--- a/server/src/main/webapp/WEB-INF/spring-servlet.xml	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/spring-servlet.xml	Wed Mar 13 15:47:08 2013 +0100
@@ -5,11 +5,11 @@
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:mvc="http://www.springframework.org/schema/mvc"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
-                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                         http://www.springframework.org/schema/context
-                        http://www.springframework.org/schema/context/spring-context-3.0.xsd
+                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                         http://www.springframework.org/schema/mvc
-                        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
+                        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
 
     <!-- **************************************************************** -->
     <!-- RESOURCE FOLDERS CONFIGURATION -->
@@ -23,7 +23,13 @@
     <!-- **************************************************************** -->
     <!-- SPRING ANNOTATION PROCESSING -->
     <!-- **************************************************************** -->
-    <mvc:annotation-driven />
+    <mvc:annotation-driven>
+	    <mvc:argument-resolvers>
+	        <bean class="org.springframework.data.web.PageableArgumentResolver">
+	           <property name="prefix" value="p"/>
+	        </bean>
+	    </mvc:argument-resolvers>
+    </mvc:annotation-driven>
     <context:component-scan
         base-package="org.iri_research.renkan.controller" />
          
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/webapp/WEB-INF/templates/fragment/paginationFragment.html	Wed Mar 13 15:47:08 2013 +0100
@@ -0,0 +1,23 @@
+<!doctype html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:th="http://www.thymeleaf.org">
+<head>
+<meta charset="utf-8" />
+<title>pagination fragment</title>
+</head>
+<body>
+ <div id="paginationFragment" th:fragment="paginationFragment" >
+ <div th:if="${page.totalPages>1}">
+ <a th:if="!${page.firstPage}" th:href="@{${baseUrl}(p.page=1)}">&lt;&lt;</a>
+ <a th:if="${page.hasPreviousPage()}" th:href="@{${baseUrl}(p.page=${page.number})}">&lt;</a>
+ <span th:if="${page.number-2} > 0">...</span>
+ <a th:each="i: ${#numbers.sequence(1,2)}" th:if="${page.number-2+i} > 0" th:href="@{${baseUrl}(p.page=${page.number-2+i})}" th:text="${page.number-2+i}"></a>
+ <span th:text="${page.number+1}"></span>
+ <a th:each="i: ${#numbers.sequence(1,2)}" th:if="${page.number+1+i} &lt; ${page.totalPages+1}" th:href="@{${baseUrl}(p.page=${page.number+1+i})}" th:text="${page.number+1+i}"></a>
+ <span th:if="${page.number+3} &lt; ${page.totalPages}">...</span>
+ <a th:if="${page.hasNextPage()}" th:href="@{${baseUrl}(p.page=${page.number+2})}">&gt;</a>
+ <a th:if="!${page.lastPage}" th:href="@{${baseUrl}(p.page=${page.totalPages})}">&gt;&gt;</a>
+ </div>
+ </div>
+</body>
+</html>
\ No newline at end of file
--- a/server/src/main/webapp/WEB-INF/templates/projectIndex.html	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/projectIndex.html	Wed Mar 13 15:47:08 2013 +0100
@@ -1,5 +1,6 @@
 <!doctype html>
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" >
+    <head>
         <title>Renkan</title>
 
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
@@ -123,17 +124,19 @@
         /*]]>*/
         </style>
         <link href="static/css/index.css" rel="stylesheet" th:href="@{/static/css/index.css}"/> 
-        
+    </head>
+    <body>
         <div id="wrapper">
             <div id="inner">
                 <div id="button" onclick="go2Random()" class="translate" th:utext="#{renkanIndex.new_renkan}">New Renkan</div>
-                <div id="label" class="translate" th:text="#{renkanIndex.renkan_title}">Create a Renkan with the title</div> 
+                <div id="label" class="translate" th:text="#{renkanIndex.renkan_exp}">Create a Renkan with the title</div> 
                 <form action="#" onsubmit="go2Title();return false;"> 
                     <input type="text" id="renkantitle" autofocus="autofocus" x-webkit-speech="x-webkit-speech"/> 
                     <button type="submit">OK</button>
                 </form>
             </div>
             <h2 th:text="#{renkanIndex.project_list}">Project list</h2>
+            <div th:include="fragment/paginationFragment :: paginationFragment"></div>
             <table>
               <thead>
                 <tr>
@@ -141,7 +144,7 @@
                 </tr>
               </thead>
               <tbody>
-                <tr th:each="project: ${projects}">
+                <tr th:each="project: ${page}">
                   <th th:text="${project.title}">title</th>
                   <td th:text="${#dates.format(project.created, #messages.msg('date.format'))}">date</td>
                   <td><a href="#" th:href="@{'/p/'+${project.id}(cowebkey=${project.key})}" th:text="#{renkanIndex.project_edit_link}">Edit projet</a></td>
@@ -184,5 +187,5 @@
  
         /*]]>*/
         </script>
-
+    </body>
 </html>
--- a/server/src/main/webapp/WEB-INF/templates/renkanIndex.html	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/renkanIndex.html	Wed Mar 13 15:47:08 2013 +0100
@@ -1,5 +1,6 @@
 <!doctype html>
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" >
+    <head>
         <title>Renkan</title>
 
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
@@ -123,27 +124,33 @@
         /*]]>*/
         </style>
         <link href="static/css/index.css" rel="stylesheet" th:href="@{/static/css/index.css}"/> 
-        
+    </head>
+    <body>
         <div id="wrapper">
             <div id="inner">
                 <div id="button" onclick="go2Random()" class="translate" th:utext="#{renkanIndex.new_space}">New Space</div>
-                <div id="label" class="translate" th:text="#{renkanIndex.renkan_title}">Create a Renkan with the title</div> 
+                <div id="label" class="translate" th:text="#{renkanIndex.space_exp}">Create a Space with the title</div> 
                 <form action="#" onsubmit="go2Title();return false;"> 
-                    <input type="text" id="renkantitle" autofocus="autofocus" x-webkit-speech="x-webkit-speech"/> 
+                    <input type="text" id="renkantitle" autofocus="autofocus"/> 
                     <button type="submit">OK</button>
                 </form>
             </div>
             <h2 th:text="#{renkanIndex.space_list}">Space list</h2>
+            <div th:include="fragment/paginationFragment :: paginationFragment"></div>
             <table>
               <thead>
                 <tr>
-                    <th th:text="#{renkanIndex.space_name}">Name</th><th th:text="#{renkanIndex.space_creation}">Creation</th><th th:text="#{renkanIndex.space_open}">Edit</th>
+                    <th th:text="#{renkanIndex.space_name}">Name</th>
+                    <th th:text="#{renkanIndex.space_creation}">Creation</th>
+                    <th th:text="#{renkanIndex.space_proj_count}">Proj. count</th>
+                    <th th:text="#{renkanIndex.space_open}">Edit</th>
                 </tr>
               </thead>
               <tbody>
-                <tr th:each="space: ${spaces}">
+                <tr th:each="space: ${page.content}">
                   <th th:text="${space.title}">title</th>
                   <td th:text="${#dates.format(space.created, #messages.msg('date.format'))}">date</td>
+                  <td th:text="${#maps.containsKey(projectsCount, space.id)}? ${projectsCount[space.id]} : 0">nb. proj</td>
                   <td><a href="#" th:href="@{'/s/'+${space.id}}" th:text="#{renkanIndex.space_open_link}">Open space</a></td>
                 </tr>
               </tbody>
@@ -161,7 +168,7 @@
                 	return false;
                 }
                 
-                new_renkan = {
+                new_space = {
                 	title: renkantitle,
                 	description: "(empty description)",
                 	uri: null
@@ -169,7 +176,7 @@
                 
                 var post_url = /*[[@{/rest/spaces/}]]*/"/rest/spaces/"; 
                 $.ajax(post_url, {
-                    data:JSON.stringify(new_renkan),
+                    data:JSON.stringify(new_space),
                     type: "POST",
                     dataType: "json",
                     contentType: "application/json; charset=UTF-8"                    
@@ -183,5 +190,5 @@
  
         /*]]>*/
         </script>
-
+    </body>
 </html>
--- a/server/src/test/java/org/iri_research/renkan/test/repositories/ProjectsRepositoryTest.java	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/test/java/org/iri_research/renkan/test/repositories/ProjectsRepositoryTest.java	Wed Mar 13 15:47:08 2013 +0100
@@ -3,6 +3,8 @@
 
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import org.iri_research.renkan.models.Project;
@@ -25,6 +27,8 @@
 @ContextConfiguration("repositories-context.xml")
 public class ProjectsRepositoryTest {
 
+	private final static int SPACE_NB = 3;
+	
 	private Logger logger = LoggerFactory.getLogger(ProjectsRepositoryTest.class);
 	
 	@Autowired
@@ -37,28 +41,36 @@
 	
 	private Date creationDate = new Date();
 	
+	private List<String> spaceIds = new ArrayList<>(); 
+	
 	public ProjectsRepositoryTest() {
 	}
 	
 	@Before
 	public void setup() {
-		
+
 		logger.debug("Setup");
-		Space testSpace = new Space(UUID.randomUUID().toString(), "test", "Test space", null, null, null, "test_user", null, this.creationDate);
-		testSpace = spacesRepository.save(testSpace);
 		ArrayList<Project> pl = new ArrayList<Project>();
-		pl.add(new Project(testSpace.getId(), UUID.randomUUID().toString(), "test1", "desc1", "http://localhost:8080/rest/projects/id1", this.creationDate));
-		pl.add(new Project(testSpace.getId(), UUID.randomUUID().toString(), "test2", "desc2", "http://localhost:8080/rest/projects/id2", this.creationDate));
-		logger.debug("Setup : new Project ");
+		Space testSpace = null;
+		for(int i=0; i<SPACE_NB; i++) {
+			spaceIds.add(UUID.randomUUID().toString());
+			testSpace = new Space(this.spaceIds.get(i), "test space " + i, "Test space " + i, null, null, null, "test_user", null, this.creationDate);
+			testSpace = spacesRepository.save(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), this.creationDate));
+			}
+		}
 		for(Project p: projectsRepository.save(pl)) {
 			this.testProjects.add(p);
 		}
+		
 	}
 	
 	@After
 	public void teardown() {
 		logger.debug("Teardown");
 		projectsRepository.deleteAll();
+		spacesRepository.deleteAll();
 	}
 	
 	@Test
@@ -126,5 +138,22 @@
 		} 
 	}
 	
+	@Test
+	public void testGetCountBySpace() {
+		
+		Map<String, Integer> groupRes = projectsRepository.getCountBySpace();
+		
+		Assert.assertNotNull("GroupRes not null", groupRes);
+		Assert.assertEquals("Group res size", SPACE_NB-1, groupRes.size());
+		
+		for(int i=0;i<(SPACE_NB-1);i++) {
+			Integer count = groupRes.get(this.spaceIds.get(i));
+			Assert.assertNotNull("count not null", count);
+			Assert.assertEquals("Nb of project/space", 2-i, count.intValue());
+		}
+		
+		Assert.assertNull("Last space id has no project i.e count is null", groupRes.get(this.spaceIds.get(SPACE_NB-1)));		
+	}
+	
 	
 }
--- a/server/src/test/java/org/iri_research/renkan/test/repositories/SpacesRepositoryTest.java	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/test/java/org/iri_research/renkan/test/repositories/SpacesRepositoryTest.java	Wed Mar 13 15:47:08 2013 +0100
@@ -1,7 +1,9 @@
 package org.iri_research.renkan.test.repositories;
 
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
@@ -15,6 +17,11 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
 import org.springframework.data.mongodb.core.MongoTemplate;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -26,6 +33,8 @@
 @ContextConfiguration("repositories-context.xml")
 public class SpacesRepositoryTest {
 	
+	private final static int SPACE_NB = 3;
+	
 	private Logger logger = LoggerFactory.getLogger(SpacesRepositoryTest.class);
 	
 	@Autowired
@@ -34,19 +43,27 @@
 	@Autowired
 	private MongoTemplate mongoTemplate;
 	
-	private Map<String, Space> spacesList = new HashMap<String, Space>();
-
+	private Map<String, Space> spacesList = new HashMap<String, Space>(SPACE_NB);
+	private List<String> spacesUuids = new ArrayList<>(SPACE_NB);	
 	
 	@Before
 	public void setup() {
 		
 		logger.debug("Setup");
 		spacesRepository.deleteAll();
-		Date creationDate = new Date();
-		String uuid = UUID.randomUUID().toString();
-		Space testSpace = new Space(uuid, "test", "Test space", "{}", "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 i=0;i<SPACE_NB;i++) {
+			Date creationDate = new Date();
+			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);
+			try {
+				Thread.sleep(1);
+			} catch (InterruptedException e) {
+				e.printStackTrace();
+			}
+		}
 	}
 	
 	@Test
@@ -86,7 +103,25 @@
 			Assert.assertTrue("mongo object must have created_by field", obj.containsField("created_by"));
 			Assert.assertEquals("created by must be the same", obj.get("created_by"), sp.getCreatedBy());			
 			
-		}
+		}		
+	}
+	
+	@Test
+	public void testPagination() {
+		
+		Sort s = new Sort(Direction.DESC, "created");
+		Pageable p = new PageRequest(0, 2, s);
+		
+		Page<Space> page = this.spacesRepository.findAll(p);
+		
+		Assert.assertEquals("page content length must be two", 2, page.getNumberOfElements());
+		Assert.assertEquals("id must be id of last created space", this.spacesUuids.get(SPACE_NB-1), page.getContent().get(0).getId());
+		Assert.assertEquals("id must be id of last created space", this.spacesUuids.get(SPACE_NB-2), page.getContent().get(1).getId());
+		
+		p = new PageRequest(1, 2, s);
+		page = this.spacesRepository.findAll(p);
+		Assert.assertEquals("page content length must be one", 1, page.getNumberOfElements());
+		Assert.assertEquals("id must be id of first created space", this.spacesUuids.get(0), page.getContent().get(0).getId());
 		
 	}
 	
--- a/server/src/test/resources/log4j.xml	Wed Mar 13 15:37:31 2013 +0100
+++ b/server/src/test/resources/log4j.xml	Wed Mar 13 15:47:08 2013 +0100
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8" ?>
+<?xml version="1.0" encoding="UTF-8" standalone='no' ?>
 <!DOCTYPE log4j:configuration >
 
 <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">