change initialization order.
authorymh <ymh.work@gmail.com>
Mon, 11 Feb 2013 10:34:23 +0100
changeset 54 43c35d6ca3f0
parent 51 3247fccfbd3f
child 55 17ff0be41a5b
change initialization order. separate user list initialization data comes from coweb moderator
.classpath
.settings/.jsdtscope
server/pom.xml
server/src/main/java/org/iri_research/renkan/coweb/RenkanSessionModerator.java
server/src/main/java/org/iri_research/renkan/coweb/event/AbstractSyncEventManager.java
server/src/main/java/org/iri_research/renkan/coweb/event/EdgeSyncEventManager.java
server/src/main/java/org/iri_research/renkan/coweb/event/IPersistedSyncEventManager.java
server/src/main/java/org/iri_research/renkan/coweb/event/ISyncEventManager.java
server/src/main/java/org/iri_research/renkan/coweb/event/NodeSyncEventManager.java
server/src/main/java/org/iri_research/renkan/coweb/event/ProjectSyncEventManager.java
server/src/main/java/org/iri_research/renkan/coweb/event/RosterSyncEventManager.java
server/src/main/java/org/iri_research/renkan/coweb/event/UserSyncEventManager.java
server/src/main/java/org/iri_research/renkan/models/RenkanSessionModeratorState.java
server/src/main/java/org/iri_research/renkan/models/RosterUser.java
server/src/main/resources/log4j.xml
server/src/main/webapp/WEB-INF/templates/renkanProjectEdit.html
server/src/main/webapp/static/js/config.js
server/src/main/webapp/static/js/corenkan-json.js
server/src/main/webapp/static/js/corenkan.js
server/src/main/webapp/static/js/ldtjson-bin.js
server/src/main/webapp/static/js/models.js
server/src/main/webapp/static/js/twitter-bin.js
server/src/main/webapp/static/js/wikipedia-bin.js
--- a/.classpath	Mon Jan 14 16:00:07 2013 +0100
+++ b/.classpath	Mon Feb 11 10:34:23 2013 +0100
@@ -46,5 +46,7 @@
 			<attribute name="owner.project.facets" value="java"/>
 		</attributes>
 	</classpathentry>
+	<classpathentry kind="var" path="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.1.2/jackson-core-2.1.2.jar"/>
+	<classpathentry kind="var" path="M2_REPO/com/fasterxml/jackson/core/jackson-databind/2.1.2/jackson-databind-2.1.2.jar"/>
 	<classpathentry kind="output" path="server/target/classes"/>
 </classpath>
--- a/.settings/.jsdtscope	Mon Jan 14 16:00:07 2013 +0100
+++ b/.settings/.jsdtscope	Mon Feb 11 10:34:23 2013 +0100
@@ -8,5 +8,6 @@
 		</attributes>
 	</classpathentry>
 	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
+	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.FireFoxBrowserLibrary"/>
 	<classpathentry kind="output" path=""/>
 </classpath>
--- a/server/pom.xml	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/pom.xml	Mon Feb 11 10:34:23 2013 +0100
@@ -7,11 +7,13 @@
     <version>0.1</version>
     <packaging>war</packaging>
 
-    <properties>
+    <properties>       
         <coweb-version>1.0</coweb-version>
         <cowebx-version>0.8.4</cowebx-version>
         <jersey-version>1.16</jersey-version>
         <spring-version>3.1.3.RELEASE</spring-version>
+        <!--jetty-version>9.0.0.M4</jetty-version-->
+        <jetty-version>8.1.8.v20121106</jetty-version>
         <junit-version>4.10</junit-version>
         <thymeleaf-version>2.0.14</thymeleaf-version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -33,7 +35,7 @@
             <plugin>
                 <groupId>org.mortbay.jetty</groupId>
                 <artifactId>jetty-maven-plugin</artifactId>
-                <version>8.1.8.v20121106</version>
+                <version>${jetty-version}</version>
                 <configuration>
                     <scanIntervalSeconds>10</scanIntervalSeconds>
                     <webAppConfig>
--- a/server/src/main/java/org/iri_research/renkan/coweb/RenkanSessionModerator.java	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/coweb/RenkanSessionModerator.java	Mon Feb 11 10:34:23 2013 +0100
@@ -1,25 +1,143 @@
 package org.iri_research.renkan.coweb;
 
-//import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
+import javax.inject.Inject;
 import javax.inject.Named;
 
 import org.coweb.DefaultSessionModerator;
 import org.iri_research.renkan.coweb.event.ISyncEventManager;
+import org.iri_research.renkan.models.Project;
+import org.iri_research.renkan.models.RenkanSessionModeratorState;
+import org.iri_research.renkan.models.RosterUser;
+import org.iri_research.renkan.repositories.ProjectsRepository;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.ApplicationContext;
-//import javax.inject.Inject;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 @Named
 public class RenkanSessionModerator extends DefaultSessionModerator {
 	
 	private final Logger logger = LoggerFactory.getLogger(RenkanSessionModerator.class);
+	
+	@Inject
+	private ProjectsRepository projectsRepository;
+	
+	
+	public ProjectsRepository getProjectsRepository() {
+		if(this.projectsRepository == null) {
+			ApplicationContext context = SpringConfigurer.getInstance().getApplicationContext();
+			this.projectsRepository = (ProjectsRepository)context.getBean("projectsRepository");
+		}
+		return projectsRepository;
+	}
+
+	@Override
+	public synchronized boolean canClientJoinSession(String clientId, Map<String,Object> userDefined) {
 		
+		this.logger.debug("canClientJoinSession: " + clientId  + " , " + (userDefined==null?"null":userDefined.toString()));
+		
+		if(RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().containsKey(clientId)) {
+			this.logger.warn("Client id already declared in user activation map: " + clientId);
+		}
+		
+		String projectId = (String)userDefined.get("project_id");
+		
+		if(projectId == null) {
+			return false;
+		}
+		
+		RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().put(clientId, projectId);
+		return true;
+	}
+	
+	private synchronized void activateProject(String clientId) {
+		
+		if(!RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().containsKey(clientId)) {
+			this.logger.warn("Client id not declared in user activation map: " + clientId);
+			return;
+		}
+		String project_id = RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().get(clientId);
+		if(project_id == null) {
+			this.logger.warn("Null project id for client id in user activation map: " + clientId);
+			return;
+		}
+		
+		List<String> user_list = RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().get(project_id);
+		if(user_list == null) {
+			user_list = new ArrayList<String>();
+			RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().put(project_id, user_list);
+		}
+
+		if(!user_list.contains(clientId)) {
+			user_list.add(clientId);
+		}
+
+	}
+ 	
+	@Override
+	public synchronized void onClientJoinSession(String clientId) {
+		
+		this.logger.debug("onClientJoinSession: " + clientId);
+		
+		this.activateProject(clientId);
+		
+	}
+	
+	@Override
+	public synchronized void onClientLeaveSession(String clientId) {
+		
+		this.logger.debug("onClientLeaveSession: " + clientId);
+		
+		String project_id = RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().get(clientId);
+		
+		RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().remove(clientId);
+		if(project_id == null) {
+			this.logger.warn("Leaving client have no associated project: " + clientId);
+			return;
+		}
+		
+		List<String> user_list = RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().get(project_id);
+		if(user_list == null) {
+			this.logger.warn("Leaving client have associated project but no project list : " + clientId + ", " + project_id);
+			return;
+		}
+		
+		if(!user_list.remove(clientId)) {
+			this.logger.warn("Leaving client have associated project but not in project list : " + clientId + ", " + project_id);
+			return;
+		}
+		
+		if(user_list.isEmpty()) {
+			this.logger.debug("Leaving client, project list empty, removing from active projects: " + clientId + ", " + project_id);
+			RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().remove(project_id);
+		}
+		
+		List<RosterUser> r_user_list = RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().get(project_id);
+		if(r_user_list == null) {
+			this.logger.warn("Leaving client have associated project but no user list : " + clientId + ", " + project_id);
+			return;
+		}
+		for (RosterUser rosterUser : r_user_list) {
+			if( rosterUser.getClient_id().equals(clientId)) {
+				r_user_list.remove(rosterUser);
+			}
+		}
+		if(r_user_list.isEmpty()) {
+			this.logger.debug("Leaving client, user list empty, removing from active projects: " + clientId + ", " + project_id);
+			RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().remove(project_id);
+		}		
+		
+	}
+	
 	@SuppressWarnings("unchecked")
 	@Override
-	public void onSync(String clientId, Map<String, Object> data) {
+	public synchronized void onSync(String clientId, Map<String, Object> data) {
 				
 		this.logger.debug("Debugging onSync client id: " + clientId);
 		this.logger.debug("Debugging onSync: " + data.toString());
@@ -57,7 +175,7 @@
 		try {
 			ISyncEventManager<?, ?> eventManager = (ISyncEventManager<?, ?>)context.getBean(beanName);
 			logger.debug("Debugging on Sync : dispatch to " + beanName);
-			eventManager.dispatchEvent(data);
+			eventManager.dispatchEvent(clientId, data);
 		}
 		catch(Throwable e) {
 			this.logger.error(String.format("onSync EventManagerClass %s not found : error %s : %s", sync_type, e.toString(), e.getMessage()));
@@ -65,4 +183,44 @@
 		
 	}
 
+	@Override
+	public Map<String, Object> getLateJoinState() {
+		this.logger.debug("getLateJoinState");
+		
+		Map<String, Object> res = super.getLateJoinState();
+		ObjectMapper mapper = new ObjectMapper();
+		
+		for (String clientId : RenkanSessionModeratorState.INSTANCE.getUsersActivationMap().keySet()) {
+			this.activateProject(clientId);
+		}
+		
+		this.logger.debug("getLateJoinState : Project activated");
+		
+		for (String project_id : RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().keySet()) {
+
+			Project p = this.getProjectsRepository().findOne(project_id);
+			if (p != null) {
+				try {
+					res.put("renkan_"+project_id, mapper.writeValueAsString(p));
+				} catch (JsonProcessingException e) {
+					this.logger.error("Error when deserializing project " + project_id, e);
+				}
+			}
+			
+			String user_res = "[]";
+			if(RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().containsKey(project_id)) {
+				try {
+					user_res = mapper.writeValueAsString(RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().get(project_id));
+				} catch (JsonProcessingException e) {
+					this.logger.error("Error when deserializing user list " + project_id, e);
+				}
+			}
+			res.put("users_"+project_id, user_res);
+		}
+		
+		
+		this.logger.debug("getLateJoinState res : " + res.toString());
+		return res;
+	}
+
 }
--- a/server/src/main/java/org/iri_research/renkan/coweb/event/AbstractSyncEventManager.java	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/coweb/event/AbstractSyncEventManager.java	Mon Feb 11 10:34:23 2013 +0100
@@ -23,7 +23,7 @@
 import com.mongodb.WriteResult;
 
 
-public abstract class AbstractSyncEventManager<T extends IRenkanModel<ID>, ID extends Serializable> implements ISyncEventManager<T,ID> {
+public abstract class AbstractSyncEventManager<T extends IRenkanModel<ID>, ID extends Serializable> implements IPersistedSyncEventManager<T,ID> {
 
 	private final Logger logger = LoggerFactory.getLogger(AbstractSyncEventManager.class);
 	
@@ -42,23 +42,23 @@
 	public abstract IRenkanRepository<T, ID> getObjectRepository();
 		
 	@Override
-	public void dispatchEvent(Map<String, Object> data) {
+	public void dispatchEvent(String clientId, Map<String, Object> data) {
 		
 		this.saveSyncEvent(data);
 
 		String eventType = (String) data.get("type");
 		
 		if("null".equalsIgnoreCase(eventType)) {
-			this.nullOperation(data);
+			this.nullOperation(null, data);
 		}
 		else if ("update".equalsIgnoreCase(eventType)) {
-			this.update(data);
+			this.update(clientId, data);
 		}
 		else if ("insert".equalsIgnoreCase(eventType)) {
-			this.insert(data);
+			this.insert(clientId, data);
 		}
 		else if("delete".equalsIgnoreCase(eventType)) {
-			this.delete(data);
+			this.delete(clientId, data);
 		}
 		else {		
 			logger.warn(String.format("dispatchEvent : eventType unknown %s", eventType));
@@ -107,7 +107,7 @@
 	protected abstract List<T> getObjectList(Project project);
 
 	@Override
-	public void update(Map<String, Object> data) {
+	public void update(String clientId, Map<String, Object> data) {
 		
 		this.logger.debug("AbstractSyncEventManager: update " + this.getClass().getName());
 		
@@ -149,10 +149,10 @@
 	}
 
 	@Override
-	public abstract void insert(Map<String, Object> data);
+	public abstract void insert(String clientId, Map<String, Object> data);
 
 	@Override
-	public void delete(Map<String, Object> data) {
+	public void delete(String clientId, Map<String, Object> data) {
 		
 		@SuppressWarnings("unchecked")
 		Map<String, Object> values = (Map<String, Object>) data.get("value");
@@ -206,7 +206,7 @@
 	}
 
 	@Override
-	public abstract void nullOperation(Map<String, Object> data);
+	public abstract void nullOperation(String clientId, Map<String, Object> data);
 	
 	
 }
--- a/server/src/main/java/org/iri_research/renkan/coweb/event/EdgeSyncEventManager.java	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/coweb/event/EdgeSyncEventManager.java	Mon Feb 11 10:34:23 2013 +0100
@@ -52,7 +52,7 @@
 	
 	
 	@Override
-	public void insert(Map<String, Object> data) {
+	public void insert(String clientId, Map<String, Object> data) {
 	
 		// get project
 		this.logger.debug("EdgeSyncEventManager: insert Edge");
@@ -106,7 +106,7 @@
 	}
 	
 	@Override
-	public void nullOperation(Map<String, Object> data) {
+	public void nullOperation(String clientId, Map<String, Object> data) {
 		this.logger.debug("nullOperation: NOP");
 	}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/java/org/iri_research/renkan/coweb/event/IPersistedSyncEventManager.java	Mon Feb 11 10:34:23 2013 +0100
@@ -0,0 +1,13 @@
+package org.iri_research.renkan.coweb.event;
+
+import java.io.Serializable;
+
+import org.iri_research.renkan.repositories.IRenkanRepository;
+import org.iri_research.renkan.repositories.ProjectsRepository;
+
+public interface IPersistedSyncEventManager<T, ID extends Serializable>  extends ISyncEventManager<T, ID> {
+		
+	public ProjectsRepository getProjectsRepository();
+	public IRenkanRepository<T,ID> getObjectRepository();
+
+}
--- a/server/src/main/java/org/iri_research/renkan/coweb/event/ISyncEventManager.java	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/coweb/event/ISyncEventManager.java	Mon Feb 11 10:34:23 2013 +0100
@@ -3,19 +3,14 @@
 import java.io.Serializable;
 import java.util.Map;
 
-import org.iri_research.renkan.repositories.IRenkanRepository;
-import org.iri_research.renkan.repositories.ProjectsRepository;
-
 public interface ISyncEventManager<T, ID extends Serializable> {
 	
-	public void dispatchEvent(Map<String, Object> data);
+	public void dispatchEvent(String clientId, Map<String, Object> data);
 
-	public void update(Map<String, Object> data);
-	public void insert(Map<String, Object> data);
-	public void delete(Map<String, Object> data);
-	public void nullOperation(Map<String, Object> data);
+	public void update(String clientId, Map<String, Object> data);
+	public void insert(String clientId, Map<String, Object> data);
+	public void delete(String clientId, Map<String, Object> data);
+	public void nullOperation(String clientId, Map<String, Object> data);
 	
-	public ProjectsRepository getProjectsRepository();
-	public IRenkanRepository<T,ID> getObjectRepository();
 
 }
--- a/server/src/main/java/org/iri_research/renkan/coweb/event/NodeSyncEventManager.java	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/coweb/event/NodeSyncEventManager.java	Mon Feb 11 10:34:23 2013 +0100
@@ -43,7 +43,7 @@
 
 	
 	@Override
-	public void insert(Map<String, Object> data) {
+	public void insert(String clientId, Map<String, Object> data) {
 	
 		// get project
 		this.logger.debug("NodeSyncEventManager: insert Node");
@@ -89,7 +89,7 @@
 	}
 	
 	@Override
-	public void nullOperation(Map<String, Object> data) {
+	public void nullOperation(String clientId, Map<String, Object> data) {
 		this.logger.debug("nullOperation: NOP");
 	}
 
--- a/server/src/main/java/org/iri_research/renkan/coweb/event/ProjectSyncEventManager.java	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/coweb/event/ProjectSyncEventManager.java	Mon Feb 11 10:34:23 2013 +0100
@@ -31,7 +31,7 @@
 	 * @see org.iri_research.renkan.coweb.event.AbstractSyncEventManager#insert(java.util.Map)
 	 */
 	@Override
-	public void insert(Map<String, Object> data) {
+	public void insert(String clientId, Map<String, Object> data) {
 		this.logger.debug("Insert called, do nothing");
 		// do nothing
 	}
@@ -40,7 +40,7 @@
 	 * @see org.iri_research.renkan.coweb.event.AbstractSyncEventManager#delete(java.util.Map)
 	 */
 	@Override
-	public void delete(Map<String, Object> data) {
+	public void delete(String clientId, Map<String, Object> data) {
 		this.logger.debug("Delete called, do nothing");
 		// do nothing
 	}
@@ -49,7 +49,7 @@
 	 * @see org.iri_research.renkan.coweb.event.AbstractSyncEventManager#nullOperation(java.util.Map)
 	 */
 	@Override
-	public void nullOperation(Map<String, Object> data) {
+	public void nullOperation(String clientId, Map<String, Object> data) {
 		this.logger.debug("Null called, do nothing");
 		// do nothing
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/java/org/iri_research/renkan/coweb/event/RosterSyncEventManager.java	Mon Feb 11 10:34:23 2013 +0100
@@ -0,0 +1,196 @@
+package org.iri_research.renkan.coweb.event;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Named;
+
+import org.coweb.CowebException;
+import org.iri_research.renkan.models.RenkanSessionModeratorState;
+import org.iri_research.renkan.models.RosterUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+@Named
+public class RosterSyncEventManager implements ISyncEventManager<RosterUser, String> {
+
+	private final Logger logger = LoggerFactory.getLogger(RosterSyncEventManager.class);
+	
+
+	@Override
+	public void insert(String clientId, Map<String, Object> data) {
+		logger.debug("RosterUserSyncEventManager.insert " + data.toString());
+		
+		@SuppressWarnings("unchecked")
+		Map<String, Object> values = (Map<String, Object>) data.get("value");
+		String projectId = (String) values.get("_project_id");
+
+		List<RosterUser> usersList = null;
+		
+		if(RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().containsKey(projectId)) {
+			usersList = RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().get(projectId);
+		}
+		if(usersList == null) {
+			usersList = new ArrayList<RosterUser>();
+			RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().put(projectId, usersList);
+		}
+				
+		String id = (String)values.get("id");
+
+		RosterUser user = null;
+		
+		for (RosterUser rosterUser : usersList) {
+			if(rosterUser.getId().equals(id)) {
+				user = rosterUser;
+				break;
+			}
+		}
+		if(user != null) {
+			this.update(clientId, data);
+			return;
+		}
+		
+		String title = (String)values.get("title");
+		String description = (String)values.get("description");
+		String uri = (String)values.get("uri");
+		String color = (String)values.get("color");
+		Long site_id = (Long)values.get("site_id");
+		
+		user = new RosterUser(id, title, description, uri, color, projectId, site_id, clientId);
+		
+		Integer position = (Integer)data.get("position");
+		if(position == null) {
+			position = new Integer(0);
+		}
+		
+		usersList.add(position.intValue(), user);
+		
+		logger.debug("RosterUserSyncEventManager.insert in  " + projectId + " : " + RenkanSessionModeratorState.INSTANCE.getProjectsActivationMap().toString());
+		
+	}
+
+	@Override
+	public void nullOperation(String clientId, Map<String, Object> data) {
+		// do nothing
+		return;
+	}
+
+	@Override
+	public void update(String clientId, Map<String, Object> data) {
+		logger.debug("RosterUserSyncEventManager.update " + data.toString());
+
+		@SuppressWarnings("unchecked")
+		Map<String, Object> values = (Map<String, Object>) data.get("value");
+		String projectId = (String) values.get("_project_id");
+
+		List<RosterUser> usersList = null;
+		
+		if(RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().containsKey(projectId)) {
+			usersList = RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().get(projectId);
+		}
+		
+		if(usersList == null) {
+			logger.debug("RosterUserSyncEventManager.update : null user list");
+			return;
+		}
+		
+		String id = (String)values.get("id");
+
+		RosterUser user = null;
+		
+		for (RosterUser rosterUser : usersList) {
+			if(rosterUser.getId().equals(id)) {
+				user = rosterUser;
+				break;
+			}
+		}
+		
+		if(user == null) {
+			logger.debug("RosterUserSyncEventManager.update : user not found in list");
+			return;
+		}
+		
+		String title = (String)values.get("title");
+		String description = (String)values.get("description");
+		String uri = (String)values.get("uri");
+		String color = (String)values.get("color");
+		
+		if(title != null) {
+			user.setTitle(title);
+		}
+		if(description != null) {
+			user.setDescription(description);
+		}
+		if(uri != null) {
+			user.setUri(uri);
+		}
+		if(color != null) {
+			user.setColor(color);
+		}
+		
+		return;
+		
+	}
+
+	@Override
+	public void delete(String clientId, Map<String, Object> data) {
+		logger.debug("RosterUserSyncEventManager.delete " + data.toString());
+		
+		@SuppressWarnings("unchecked")
+		Map<String, Object> values = (Map<String, Object>) data.get("value");
+		String projectId = (String) values.get("_project_id");
+
+		List<RosterUser> usersList = null;
+		
+		if(RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().containsKey(projectId)) {
+			usersList = RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().get(projectId);
+		}
+		
+		if(usersList == null) {
+			logger.debug("RosterUserSyncEventManager.delete : null user list");
+			return;
+		}
+		
+		Integer position = (Integer)data.get("position");
+		
+		if(position == null || position < 0) {
+			throw new CowebException("object delete: bad delete position", String.format("Bad position %s not found", position==null?"null":position.toString()));
+		}
+		int index = position.intValue();
+		
+		usersList.remove(index);
+		
+		if(usersList.isEmpty()) {
+			RenkanSessionModeratorState.INSTANCE.getProjectsUsersList().remove(projectId);
+		}
+		
+		return;
+		
+	}
+	
+	@Override
+	public void dispatchEvent(String clientId, Map<String, Object> data) {
+		
+		String eventType = (String) data.get("type");
+		
+		if("null".equalsIgnoreCase(eventType)) {
+			this.nullOperation(clientId, data);
+		}
+		else if ("update".equalsIgnoreCase(eventType)) {
+			this.update(clientId, data);
+		}
+		else if ("insert".equalsIgnoreCase(eventType)) {
+			this.insert(clientId, data);
+		}
+		else if("delete".equalsIgnoreCase(eventType)) {
+			this.delete(clientId, data);
+		}
+		else {		
+			logger.warn(String.format("dispatchEvent : eventType unknown %s", eventType));
+		}
+	}
+
+
+}
--- a/server/src/main/java/org/iri_research/renkan/coweb/event/UserSyncEventManager.java	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/coweb/event/UserSyncEventManager.java	Mon Feb 11 10:34:23 2013 +0100
@@ -35,7 +35,7 @@
 
 
 	@Override
-	public void insert(Map<String, Object> data) {
+	public void insert(String clientId, Map<String, Object> data) {
 	
 		// get project
 		this.logger.debug("UserSyncEventManager: insert User");
@@ -76,7 +76,7 @@
 	}
 
 	@Override
-	public void nullOperation(Map<String, Object> data) {
+	public void nullOperation(String clientId, Map<String, Object> data) {
 		this.logger.debug("nullOperation: NOP");
 	}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/java/org/iri_research/renkan/models/RenkanSessionModeratorState.java	Mon Feb 11 10:34:23 2013 +0100
@@ -0,0 +1,27 @@
+package org.iri_research.renkan.models;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public enum RenkanSessionModeratorState {
+	INSTANCE;
+	
+	private Map<String, String> usersActivationMap = new HashMap<String, String>();
+	private Map<String, List<String>> projectsActivationMap = new HashMap<String, List<String>>();
+	private Map<String, List<RosterUser>> projectsUsersList = new HashMap<String, List<RosterUser>>();
+	
+	public Map<String, String> getUsersActivationMap() {
+		return usersActivationMap;
+	}
+
+	public Map<String, List<String>> getProjectsActivationMap() {
+		return projectsActivationMap;
+	}
+
+	public Map<String, List<RosterUser>> getProjectsUsersList() {
+		return projectsUsersList;
+	}	
+	
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/java/org/iri_research/renkan/models/RosterUser.java	Mon Feb 11 10:34:23 2013 +0100
@@ -0,0 +1,53 @@
+package org.iri_research.renkan.models;
+
+public class RosterUser extends AbstractRenkanModel<String> {
+
+	private String color;
+	
+	private String project_id;
+	private Long site_id;
+	private String client_id;
+
+	public RosterUser(String id, String title, String description, String uri, String color, String project_id, Long site_id, String client_id) {
+		super(id, title, description, uri);
+		
+		this.project_id = project_id;
+		this.site_id = site_id;
+		this.client_id = client_id;
+		this.color = color;
+	}
+	
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+	
+	public void setUri(String uri) {
+		this.uri = uri;
+	}
+	
+	public String getColor() {
+		return this.color;
+	}
+	
+	public void setColor(String color) {
+		this.color = color;
+	}	
+
+	public String getProject_id() {
+		return project_id;
+	}
+
+	public Long getSite_id() {
+		return site_id;
+	}
+	
+	public String getClient_id() {
+		return client_id;
+	}
+
+	
+}
--- a/server/src/main/resources/log4j.xml	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/resources/log4j.xml	Mon Feb 11 10:34:23 2013 +0100
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE log4j:configuration >
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 
 <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
   <appender name="console" class="org.apache.log4j.ConsoleAppender"> 
--- a/server/src/main/webapp/WEB-INF/templates/renkanProjectEdit.html	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/renkanProjectEdit.html	Mon Feb 11 10:34:23 2013 +0100
@@ -17,18 +17,15 @@
         <script src="js/i18n.js" th:src="@{/static/js/i18n.js}"></script>
         <script src="js/models.js" th:src="@{/static/js/models.js}"></script>
         <script src="js/corenkan-json.js" th:src="@{/static/js/corenkan-json.js}"></script>
-        <!--script src="js/random-data.js" th:src="@{/static/js/random-data.js}"></script-->
         <script src="js/twitter-bin.js" th:src="@{/static/js/twitter-bin.js}"></script>
         <script src="js/wikipedia-bin.js" th:src="@{/static/js/wikipedia-bin.js}"></script>
         <script src="js/ldtjson-bin.js" th:src="@{/static/js/ldtjson-bin.js}"></script>
         <script src="js/paper-renderer.js" th:src="@{/static/js/paper-renderer.js}"></script>
         <script type="text/javascript" src="js/config.js" th:src="@{/static/js/config.js}"></script>
-        <script type="text/javascript" src="lib/dojo/dojo.js" data-dojo-config="isDebug: true, parseOnLoad: true" th:src="@{/static/lib/dojo/dojo.js}"></script>        
+        <script type="text/javascript" src="lib/dojo/dojo.js" data-dojo-config="isDebug: true, parseOnLoad: true" th:src="@{/static/lib/dojo/dojo.js}"></script>                
         <script type="text/javascript" th:inline="javascript">
-            var _proj;
-            require(["corenkan"], function(corenkan) {            
-              $(function() {
-                _renkan = new Rkns.Renkan({
+            $(function() {
+                var _renkan = new Rkns.Renkan({
                     url: /*[[@{/rest/projects/}+${project.id}]]*/ "data/simple-persist.php",
                     static_url : /*[[@{/static/}]]*/ "",
                     bins: [
@@ -71,17 +68,24 @@
                         }
                     ],
                 });
-                corenkan.app.onStatusChange = function(status) {
-                	if(status == "ready") {
-                        Rkns.jsonIO(_renkan, {
-                            url: /*[[@{/rest/projects/}+${project.id}]]*/ "data/simple-persist.php",
-                            callback: function(_proj, _renkan) {
-                            	corenkan.app.setObjects(_renkan);
-                            }
-                        });
-                	}
-                };
-              });
+                var corenkanConfig = {
+        	        projectId: /*[[${project.id}]]*/"new_project",
+        	        renkan : _renkan
+        	    };
+
+                require({corenkanConfig: corenkanConfig}, ["corenkan"], function(corenkan) {            
+                    corenkan.app.onStatusChange = function(status) {
+                    	if(status == "ready") {
+                    		_renkan.renderer.autoScale();
+                            /*Rkns.jsonIO(_renkan, {*/
+                                /*url: *//*[[@{/rest/projects/}+${project.id}]]*/ /*"data/simple-persist.php",*/
+                                //callback: function(_proj, _renkan) {
+                               // 	corenkan.app.setObjects(_renkan);
+                              // }
+                            //});
+                    	}
+                    };
+                });
             });
         </script>
         <link rel="stylesheet" href="css/renkan.css" th:href="@{/static/css/renkan.css}"/>
--- a/server/src/main/webapp/static/js/config.js	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/webapp/static/js/config.js	Mon Feb 11 10:34:23 2013 +0100
@@ -19,7 +19,7 @@
 		name: 'dojo',
 		location:'static/lib/dojo',
 		main:'main'
-	}]
+	}],	
 	/*{
 		name: 'dijit',
 		location:'lib/dijit',
@@ -33,10 +33,9 @@
 };
 
 var cowebConfig = {
-		baseUrl: window.location.protocol + '//' +window.location.host,
-	    adminUrl : '/renkan/admin',
-	    debug: false,
-	    //useWebSockets: true
+    baseUrl: window.location.protocol + '//' +window.location.host,
+	adminUrl : '/renkan/admin',
+	debug: false,
+	//useWebSockets: true
 };
 
-
--- a/server/src/main/webapp/static/js/corenkan-json.js	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/webapp/static/js/corenkan-json.js	Mon Feb 11 10:34:23 2013 +0100
@@ -1,6 +1,6 @@
 
 Rkns.jsonIO = function(_renkan, _opts) {
-    var _proj = _renkan.project;
+    /*var _proj = _renkan.project;
     if (typeof _opts.http_method == "undefined") {
         _opts.http_method = 'PUT';
     }
@@ -15,5 +15,5 @@
             }
         });
     };
-    _load();
+    _load();*/
 };
--- a/server/src/main/webapp/static/js/corenkan.js	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/webapp/static/js/corenkan.js	Mon Feb 11 10:34:23 2013 +0100
@@ -1,27 +1,33 @@
-//TODO add underscore as requirement
+/**
+ * TODO: add js header
+ */
+
 define([
     "dojo",
     "dojo/cookie",
+    "dojo/json",
     "coweb/main",
     "rcolor",
-    "underscore"
-], function(dojo, cookie, coweb, RColor, underscore) {
+], function(dojo, cookie, json, coweb, RColor) {
 	
     var CoRenkan = function() {
     };
 
     var proto = CoRenkan.prototype;
-    
-    var _ = underscore;
-   
+       
     proto.init = function() {
-    	console.log("ready callback");
+    	console.log("ready callback", dojo.config.corenkanConfig);
     	
-    	//this.initCollab();
-    	var that = this;
+    	this.renkan = dojo.config.corenkanConfig.renkan;
+    	this.project = dojo.config.corenkanConfig.renkan.project;
+
+    	this.initCollab(dojo.config.corenkanConfig.projectId);
     	    	
     	var sess = coweb.initSession();
-    	
+    	this.session = sess;
+
+    	var that = this;    	
+
     	sess.onStatusChange = function(status) {
     	    console.log(status);
     	    if(typeof that.onStatusChange === "function") {
@@ -29,28 +35,41 @@
     	    }
     	};
     	    	
-    	sess.prepare().then(function(data) {
+    	sess.prepare({userDefined: {project_id:dojo.config.corenkanConfig.projectId}, collab: true}).then(function(data) {
     		console.log("Prepare ok : ", data);
+        	that.setObjects();
     	});
-    	this.project = null;
                 
     };
     
-    proto.initCollab = function(id) {    	
-    	this.collab = coweb.initCollab({id : id});
+    proto.initCollab = function(id) {
+    	
+    	this.users_collab = coweb.initCollab({id: "users_" + id});
+    	
+    	this.users_collab.subscribeStateResponse(this, "onUsersStateResponse");
+    	this.users_collab.subscribeReady(this, "onLocalJoin");        
+        this.users_collab.subscribeSiteJoin(this, 'onRemoteJoin');
+        this.users_collab.subscribeSiteLeave(this, 'onRemoteLeave');
+    	this.users_collab.subscribeSync("roster", this, "onRemoteRosterChange");
+
+    	
+    	this.collab = coweb.initCollab({id : "renkan_" + id});    	    	    	
+    	
+    	this.collab.subscribeStateResponse(this, "onStateResponse");
+    	
     	this.collab.subscribeSync("project", this, "onRemoteProjectChange");
     	this.collab.subscribeSync("user", this, "onRemoteUserChange");
     	this.collab.subscribeSync("node", this, "onRemoteNodeChange");
     	this.collab.subscribeSync("edge", this, "onRemoteEdgeChange");
-    	this.collab.subscribeSync("_roster", this, "onRemoteRosterChange");
     	
-        this.collab.subscribeReady(this, 'onLocalJoin');
-        this.collab.subscribeSiteJoin(this, 'onRemoteJoin');
-        this.collab.subscribeSiteLeave(this, 'onRemoteLeave');
     };
     
     proto.onLocalJoin = function(params) {
     	console.log("Local join", params);
+    	this.current_site = params.site;
+    	if(typeof this.renkan !== "undefined" && this.renkan != null && typeof this.renkan.current_user != "undefined") {
+    		this.renkan.current_user.set("site_id", params.site);
+    	}
     };
     
     
@@ -66,9 +85,28 @@
     	if(typeof this.renkan === "undefined" || this.renkan == null || typeof this.renkan.current_user_list === "undefined" || this.renkan.current_user_list == null) {
     		return;
     	}
+    	for ( var user in this.renkan.current_user_list.filter(function(u) { return u.get("site_id") == params.site; })) {
+			this.renkan.current_user_list.remove(user);
+		}
     	
     };
     
+    proto.onUsersStateResponse = function(state) {
+    	user_list = json.parse(state);
+    	console.log("Users State response", user_list);
+    	_.each(user_list, function(user, i, l) {
+    		user['_id'] = user['id'];
+    	});    	
+    	this.renkan.current_user_list.reset(user_list, {silent: true});
+    };
+    
+    proto.onStateResponse = function(state) {
+    	obj = json.parse(state);
+    	console.log("State response", obj);
+    	obj['_id'] = obj['id'];
+    	this.project.set(obj, {silent: true});    	
+    };
+    
     function prepareValues(obj,c) {
 		values = {};
 		for(var fieldname in c.changes) {
@@ -79,8 +117,12 @@
 		return values;
     }
     
-    proto.addObjectBind = function(type, obj, c, options) {
-		console.log("add " + type,obj, c, options, this.project.toJSON());
+    proto.addObjectBind = function(type, obj, c, options, collab) {
+		console.log("add " + type,obj, c, options);
+		if(this.project == null) {
+			console.log("null project exiting");
+			return;
+		}
 		var values = obj.toJSON();
 		var new_values = {
 			id: obj.id,
@@ -93,10 +135,10 @@
 			values[k] = new_values[k];
 		}
 		console.log("add values : ", values);
-		this.collab.sendSync(type, values, "insert", options.index);
+		collab.sendSync(type, values, "insert", options.index);
     };
     
-    proto.removeObjectBind = function(type, obj, c, options) {
+    proto.removeObjectBind = function(type, obj, c, options, collab) {
 		console.log("delete " + type,obj, c, options);
 		var values = {
     	    id: obj.id,
@@ -105,10 +147,10 @@
     	    _project_id : obj.get("project").id,
     	    _user_id : (this.project.current_user!=null)?this.project.current_user.id:null
     	};
-		this.collab.sendSync(type, values, "delete", options.index);    	
+		collab.sendSync(type, values, "delete", options.index);    	
     };
     
-    proto.updateObjectBind = function(type, obj, options) {
+    proto.updateObjectBind = function(type, obj, options, collab) {
 		console.log("change " + type,obj, options);
 		if(typeof obj != "undefined" && obj.hasChanged()) {
 			var values = {
@@ -118,7 +160,7 @@
 	    	    _user_id : (this.project.current_user!=null)?this.project.current_user.id:null
 	    	};
 			_.extend(values,obj.changed);		
-	    	this.collab.sendSync(type, values);
+	    	collab.sendSync(type, values);
 		}
     };
     
@@ -144,15 +186,14 @@
 	};
 
     
-    proto.setObjects = function(renkan) {
+    proto.setObjects = function() {
     	
     	console.log(cookie("BAYEUX_BROWSER"));
+    	var renkan = this.renkan; 
     	var project = renkan.project;
     	this.setProject(project);
     	this.setRenkan(renkan);
-    	
-    	this.initCollab("renkan_" + project.id);
-    	
+    	    	
     	this.setUser(renkan);
 
     };
@@ -164,13 +205,13 @@
     	var that = this;
     	
     	renkan.current_user_list.bind("add", function(obj, c, options) {
-    		that.addObjectBind("_roster", obj, c, options);
+    		that.addObjectBind("roster", obj, c, options, that.users_collab);
     	});
-    	renkan.current_user_list.bind("remove", function(obj, c, options) {
-    		that.removeObjectBind("_roster", obj, c, options);
-    	});
+    	//renkan.current_user_list.bind("remove", function(obj, c, options) {
+    	//	that.removeObjectBind("_roster", obj, c, options, that.users_collab);
+    	//});
     	renkan.current_user_list.bind("change", function(obj, options) {
-    		that.updateObjectBind("_roster", obj, options);
+    		that.updateObjectBind("roster", obj, options, that.users_collab);
     	});
     	
     	renkan.current_user_list.bind("change", function(obj, options) {
@@ -193,9 +234,7 @@
     			}
     		}
     	});
-    	
-    	this.renkan = renkan;
-    	
+    	    	
     };
     
     proto.setUser = function(renkan) {
@@ -216,7 +255,8 @@
     		    id: user_id,
                 title: "anonymous",
                 project: project,
-                color: color.get(true, 0.3, 0.99)
+                color: color.get(true, 0.3, 0.99),
+                site_id: this.current_site
             };
     	}
     	else {
@@ -268,43 +308,41 @@
     	
     	
     	project.get("nodes").bind("add", function(obj, c, options) {    		
-    		that.addObjectBind("node", obj, c, options);
+    		that.addObjectBind("node", obj, c, options, that.collab);
     	});
     	
     	project.get("nodes").bind("remove", function(obj, c, options) {
-    		that.removeObjectBind("node", obj, c, options);
+    		that.removeObjectBind("node", obj, c, options, that.collab);
     	});
     	
     	project.get("nodes").bind("change", function(obj, options) {
-    		that.updateObjectBind("node", obj, options);
+    		that.updateObjectBind("node", obj, options, that.collab);
     	});
 
     	project.get("users").bind("add", function(obj, c, options) {    		
-    		that.addObjectBind("user", obj, c, options);
+    		that.addObjectBind("user", obj, c, options, that.collab);
     	});
     	
     	project.get("users").bind("remove", function(obj, c, options) {
-    		that.removeObjectBind("user", obj, c, options);
+    		that.removeObjectBind("user", obj, c, options, that.collab);
     	});
     	
     	project.get("users").bind("change", function(obj, options) {
-    		that.updateObjectBind("user", obj, options);
+    		that.updateObjectBind("user", obj, options, that.collab);
     	});
 
     	project.get("edges").bind("add", function(obj, c, options) {    		
-    		that.addObjectBind("edge", obj, c, options);
+    		that.addObjectBind("edge", obj, c, options, that.collab);
     	});
     	
     	project.get("edges").bind("remove", function(obj, c, options) {
-    		that.removeObjectBind("edge", obj, c, options);
+    		that.removeObjectBind("edge", obj, c, options, that.collab);
     	});
     	
     	project.get("edges").bind("change", function(obj, options) {
-    		that.updateObjectBind("edge", obj, options);
+    		that.updateObjectBind("edge", obj, options, that.collab);
     	});
-
     	
-    	this.project = project;
     };
     
     
--- a/server/src/main/webapp/static/js/ldtjson-bin.js	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/webapp/static/js/ldtjson-bin.js	Mon Feb 11 10:34:23 2013 +0100
@@ -13,6 +13,7 @@
 );
 
 Rkns.Ldt.ProjectBin.prototype._init = function(_renkan, _opts) {
+	this.renkan = _renkan;
     this.proj_id = _opts.project_id;
     this.ldt_platform = _opts.ldt_platform || "http://ldt.iri.centrepompidou.fr/";
     this.title_$.html(_opts.title);
@@ -108,7 +109,7 @@
     } else {
         this.$.show();
     }
-    _renkan.resizeBins();
+    this.renkan.resizeBins();
 }
 
 Rkns.Ldt.ProjectBin.prototype.refresh = function() {
--- a/server/src/main/webapp/static/js/models.js	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/webapp/static/js/models.js	Mon Feb 11 10:34:23 2013 +0100
@@ -253,7 +253,8 @@
                 uri: this.get("uri"),
                 description: this.get("description"),
                 color: this.get("color"),
-                project: (this.get("project") != null)?this.get("project").get("id"):null
+                project: (this.get("project") != null)?this.get("project").get("id"):null,
+                site_id: this.get("site_id")
             };
         },
     });
--- a/server/src/main/webapp/static/js/twitter-bin.js	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/webapp/static/js/twitter-bin.js	Mon Feb 11 10:34:23 2013 +0100
@@ -38,6 +38,7 @@
 );
 
 Rkns.Twitter.Bin.prototype._init = function(_renkan, _opts) {
+	this.renkan = _renkan;
     this.search = _opts.search;
     this.title_icon_$.addClass('Rk-Twitter-Title-Icon');
     this.title_$.html(this.search).addClass("Rk-Twitter-Title");
@@ -133,7 +134,7 @@
     } else {
         this.$.show();
     }
-    _renkan.resizeBins();
+    this.renkan.resizeBins();
 }
 
 Rkns.Twitter.Bin.prototype.refresh = function() {
--- a/server/src/main/webapp/static/js/wikipedia-bin.js	Mon Jan 14 16:00:07 2013 +0100
+++ b/server/src/main/webapp/static/js/wikipedia-bin.js	Mon Feb 11 10:34:23 2013 +0100
@@ -33,6 +33,7 @@
 );
 
 Rkns.Wikipedia.Bin.prototype._init = function(_renkan, _opts) {
+	this.renkan = _renkan;
     this.search = _opts.search;
     this.lang = _opts.lang || "en";
     this.title_icon_$.addClass('Rk-Wikipedia-Title-Icon Rk-Wikipedia-Lang-' + this.lang);
@@ -79,7 +80,7 @@
     } else {
         this.$.show();
     }
-    _renkan.resizeBins();
+    this.renkan.resizeBins();
 }
     
 Rkns.Wikipedia.Bin.prototype.refresh = function() {