f@0: /*
f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
f@0:
f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
f@0:
f@0: This program is free software: you can redistribute it and/or modify
f@0: it under the terms of the GNU General Public License as published by
f@0: the Free Software Foundation, either version 3 of the License, or
f@0: (at your option) any later version.
f@0:
f@0: This program is distributed in the hope that it will be useful,
f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0: GNU General Public License for more details.
f@0:
f@0: You should have received a copy of the GNU General Public License
f@0: along with this program. If not, see .
f@0: */
f@0:
f@0: package uk.ac.qmul.eecs.ccmi.network;
f@0:
f@0: import java.awt.geom.Point2D;
f@0: import java.awt.geom.Rectangle2D;
f@0: import java.io.BufferedOutputStream;
f@0: import java.io.ByteArrayOutputStream;
f@0: import java.io.IOException;
f@0: import java.nio.channels.SocketChannel;
f@0: import java.util.HashMap;
f@0: import java.util.HashSet;
f@0: import java.util.LinkedList;
f@0: import java.util.List;
f@0: import java.util.Map;
f@0: import java.util.Queue;
f@0: import java.util.ResourceBundle;
f@0: import java.util.Set;
f@0: import java.util.concurrent.ConcurrentHashMap;
f@0: import java.util.concurrent.locks.ReentrantLock;
f@0: import java.util.logging.Logger;
f@0:
f@0: import javax.swing.SwingUtilities;
f@0:
f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
f@0: import uk.ac.qmul.eecs.ccmi.gui.Diagram;
f@0: import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
f@0: import uk.ac.qmul.eecs.ccmi.gui.Edge;
f@0: import uk.ac.qmul.eecs.ccmi.gui.Finder;
f@0: import uk.ac.qmul.eecs.ccmi.gui.Lock;
f@0: import uk.ac.qmul.eecs.ccmi.gui.Node;
f@0: import uk.ac.qmul.eecs.ccmi.gui.awareness.AwarenessPanelEditor;
f@0: import uk.ac.qmul.eecs.ccmi.gui.awareness.BroadcastFilter;
f@0: import uk.ac.qmul.eecs.ccmi.gui.awareness.DisplayFilter;
f@0: import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
f@0: import uk.ac.qmul.eecs.ccmi.pdsupport.PdDiagram;
f@0: import uk.ac.qmul.eecs.ccmi.pdsupport.PdPersistenceManager;
f@0: import uk.ac.qmul.eecs.ccmi.speech.Narrator;
f@0: import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
f@0:
f@0: /* This class manages the different sessions with the clients. whereas the
f@0: * Server class just listens for connections, this class manages all what's going on about the
f@0: * diagram editing: e.g. command processing consistency check, client updates etc.
f@0: */
f@0: class ServerConnectionManager {
f@0: ServerConnectionManager( Map diagrams, AwarenessPanelEditor panelEditor) {
f@0: this.diagrams = diagrams;
f@0: awarenessPanelEditor = panelEditor;
f@0: diagramChannelAllocations = new HashMap>();
f@0: localhostDiagramElementQueue = new ConcurrentHashMap>();
f@0: protocol = ProtocolFactory.newInstance();
f@0: lockManager = new ServerLockManager();
f@0: broadcastFilter = BroadcastFilter.getInstance();
f@0: }
f@0:
f@0: /**
f@0: * Removes the channel and the locks related to it from the inner data structures.
f@0: *
f@0: * @param channel the channel to remove
f@0: */
f@0: void removeChannel(SocketChannel channel) throws IOException{
f@0: String diagramName = null;
f@0: /* looks for the Set containing this channel */
f@0: for(Map.Entry> entry : diagramChannelAllocations.entrySet()){
f@0: UserNameSocketChannel unsc = null;
f@0: for(UserNameSocketChannel userNameSocketChannel : entry.getValue())
f@0: if(userNameSocketChannel.channel.equals(channel)){
f@0: unsc = userNameSocketChannel;
f@0: break;
f@0: }
f@0: /* remove the channel from this set of channels */
f@0: if(entry.getValue().remove(unsc) && !unsc.userName.isEmpty()){
f@0: diagramName = entry.getKey().getName();
f@0: awarenessPanelEditor.removeUserName(diagramName, unsc.userName);
f@0: /* notify the other clients the user has disconnected */
f@0: AwarenessMessage awMsg = new AwarenessMessage(
f@0: AwarenessMessage.Name.USERNAME_A,
f@0: diagramName,
f@0: ""+AwarenessMessage.USERNAMES_SEPARATOR+unsc.userName
f@0: );
f@0: for(UserNameSocketChannel userNameSocketChannel : entry.getValue()){
f@0: /* don't send aw msg to the local channel */
f@0: if(!userNameSocketChannel.channel.equals(localChannel))
f@0: protocol.send(userNameSocketChannel.channel, awMsg);
f@0: }
f@0: break;
f@0: }
f@0:
f@0: }
f@0: /* all locks held by disconnected user are released */
f@0: lockManager.removeLocks(channel, diagramName);
f@0: }
f@0:
f@0: void handleMessage(SocketChannel channel) throws IOException{
f@0: Message message = protocol.receiveMessage(channel);
f@0: if(message instanceof Command){
f@0: handleCommand((Command)message, channel);
f@0: }else if(message instanceof LockMessage) {
f@0: handleLockMessage((LockMessage)message,channel);
f@0: }else { // awareness message - broadcast the message
f@0: handleAwarenessMessage((AwarenessMessage)message,channel,null);
f@0: }
f@0: }
f@0:
f@0: private void handleLockMessage(LockMessage lockMessage, SocketChannel channel) throws IOException{
f@0: Lock lock = LockMessageConverter.getLockFromMessageName((LockMessage.Name)lockMessage.getName());
f@0: String name = lockMessage.getName().toString();
f@0: String diagramName = lockMessage.getDiagram();
f@0:
f@0: Diagram diagram = null;
f@0: synchronized(diagrams){
f@0: diagram = diagrams.get(diagramName);
f@0: }
f@0: if(diagram == null){
f@0: replyLockMessage(channel,diagramName,false);
f@0: return;
f@0: }
f@0:
f@0: /* spot the tree node the message refers to */
f@0: int[] path = new int[lockMessage.getArgNum()];
f@0: for(int i = 0; i< path.length;i++){
f@0: path[i] = (Integer)lockMessage.getArgAt(i);
f@0: }
f@0:
f@0: DiagramTreeNode treeNode = null;
f@0: /* synchronize with the event dispatching thread */
f@0: ReentrantLock monitor = diagram.getCollectionModel().getMonitor();
f@0: monitor.lock();
f@0:
f@0: treeNode = Finder.findTreeNode(path, (DiagramTreeNode)diagram.getTreeModel().getRoot());
f@0: /* the tree node has been deleted, lock cannot be granted */
f@0: if(treeNode == null){
f@0: monitor.unlock();
f@0: replyLockMessage(channel,diagramName,false);
f@0: return;
f@0: }
f@0: //System.out.println("Lock message received: " + name +" diagram:"+diagramName+" treenode:"+treeNode.getName() );
f@0:
f@0: /* check whether it's a GET or YIELD message and act accordingly */
f@0: if(name.startsWith(LockMessage.GET_LOCK_PREFIX)){
f@0: // System.out.println("get lock source:"+ lockMessage.getSource());
f@0: boolean succeeded;
f@0: succeeded = lockManager.requestLock(treeNode, lock, channel, diagramName);
f@0: monitor.unlock();
f@0: /* send the response */
f@0: replyLockMessage(channel,diagramName,succeeded);
f@0: if(succeeded && broadcastFilter.accept(lockMessage.getSource())){
f@0: DiagramEventActionSource processedSource = broadcastFilter.process(lockMessage.getSource()); // changes according to configuration;
f@0:
f@0: //select node is a temporary record, therefore it doesn't need to be stored
f@0: if(processedSource.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION){
f@0: Set userNames = diagramChannelAllocations.get(diagram);
f@0: /* saves the diagramEventActionSource for when the lock is yielded */
f@0: if(userNames != null){
f@0: for(UserNameSocketChannel userName : userNames){
f@0: if(userName.channel.equals(channel)){
f@0: userName.lockAwarenessSources.add(processedSource);
f@0: }
f@0: }
f@0: }
f@0: }
f@0: /* handle the awareness message piggybacked in the lock message */
f@0: AwarenessMessage awarMsg = new AwarenessMessage(
f@0: AwarenessMessage.Name.START_A,
f@0: diagramName,
f@0: processedSource
f@0: );
f@0: handleAwarenessMessage(awarMsg,channel,diagram);
f@0: }
f@0: }else{ // yield lock
f@0: boolean released = lockManager.releaseLock(treeNode, lock, channel,diagramName);
f@0: monitor.unlock();
f@0: DiagramEventActionSource source = lockMessage.getSource();
f@0: /* it's NULL for NOTES lock and SELECT_NODE_FOR_EDGE_CREATION must not clean the text panel, because its record is temporary */
f@0: if(released && source != DiagramEventActionSource.NULL && source.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION){
f@0:
f@0: if(source.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION && broadcastFilter.accept(source)){
f@0: /* unselect node for edge creation is treated differently because it doesn't *
f@0: * clear the text panel but adds another record, which is temporary */
f@0: handleAwarenessMessage(new AwarenessMessage(
f@0: AwarenessMessage.Name.STOP_A,
f@0: diagramName,
f@0: source),
f@0: channel,
f@0: diagram);
f@0: return;
f@0: }
f@0:
f@0: /* retrieves the diagramEventActionSource: when the lock was gotten, the source was stored in *
f@0: * userName.lockAwarenessSource. This is done because the broadcast filter configuration might *
f@0: * have changed in the meanwhile but we still need to send the aw msg with the original source *
f@0: * or the clients won't be able to pick out the string to delete */
f@0: DiagramEventActionSource savedSource = removeEventActionSource(channel,source.getSaveID(),diagram);
f@0:
f@0: /* saved source = null means the broadcast filter didn't let the get_lock message
f@0: * this yield_lock message is referring to. Move on */
f@0: if(savedSource == null){
f@0: return;
f@0: }
f@0:
f@0: AwarenessMessage awMsg = new AwarenessMessage(
f@0: AwarenessMessage.Name.STOP_A,
f@0: diagramName,
f@0: savedSource
f@0: );
f@0: handleAwarenessMessage(awMsg,channel,diagram);
f@0: }
f@0: }
f@0: }
f@0:
f@0: private void handleCommand(final Command cmd, SocketChannel channel) throws IOException{
f@0: /* init some variables we're gonna use in (nearly) every branch of the switch */
f@0: final String diagramName = cmd.getDiagram();
f@0: Diagram diagram = null;
f@0:
f@0: if(cmd.getName() != Command.Name.LIST){
f@0: synchronized(diagrams){
f@0: diagram = diagrams.get(diagramName);
f@0: }
f@0: if(diagram == null)
f@0: protocol.send(channel, new Reply(Reply.Name.ERROR_R,diagramName,"Diagram "+diagramName+" does not exists",cmd.getSource()));
f@0: }
f@0: Node node = null;
f@0: Edge edge = null;
f@0: boolean broadcast = true;
f@0:
f@0: DiagramEventSource source = cmd.getSource();
f@0: if(channel == localChannel)
f@0: source = source.getLocalSource();
f@0: /* set the diagram id so the haptic will update the diagram specified by the command and not the active tab's */
f@0: if(diagram != null)
f@0: source.setDiagramName(diagram.getName());
f@0: /* log the command through the interaction logger, if any */
f@0: Command.log(cmd,(channel == localChannel) ? "local command received" : "remote command received");
f@0: //System.out.println("ServerConnectionManager: received command "+cmd.getName());
f@0: switch(cmd.getName()){
f@0: case LOCAL : // the local socket makes itself known to the server
f@0: localChannel = channel;
f@0: Set list = new HashSet();
f@0: list.add(new UserNameSocketChannel(localChannel,AwarenessMessage.getDefaultUserName()));
f@0: diagramChannelAllocations.put(diagram, list);
f@0: broadcast = false;
f@0: break;
f@0: case LIST : // ask for the list of available diagrams on the server
f@0: StringBuilder names = new StringBuilder("");
f@0: synchronized(diagrams){
f@0: for(String s : diagrams.keySet()){
f@0: names.append(s).append('\n');
f@0: }
f@0: }
f@0: protocol.send(channel, new Reply(Reply.Name.LIST_R,"",names.toString(),DiagramEventSource.NONE));
f@0: broadcast = false;
f@0: break;
f@0: case GET : // ask for a diagram xml
f@0: try{
f@0: diagram.getCollectionModel().getMonitor().lock();
f@0: Set userNames = diagramChannelAllocations.get(diagram);
f@0: ByteArrayOutputStream out = new ByteArrayOutputStream();
f@0: if(diagram instanceof PdDiagram){
f@0: PdPersistenceManager.getInstance().encodeDiagramInstance(diagram, new BufferedOutputStream(out));
f@0: }else{
f@0: PersistenceManager.encodeDiagramInstance(diagram, new BufferedOutputStream(out));
f@0: }
f@0: diagram.getCollectionModel().getMonitor().unlock();
f@0: try{
f@0: protocol.send(channel, new Reply(Reply.Name.GET_R,diagramName,out.toString("UTF-8"),DiagramEventSource.NONE));
f@0: for(UserNameSocketChannel sc: userNames){
f@0: protocol.send(channel, new AwarenessMessage(AwarenessMessage.Name.USERNAME_A,diagramName,sc.userName));
f@0: }
f@0: }catch(IOException ioe){
f@0: throw ioe;
f@0: }
f@0: userNames.add(new UserNameSocketChannel(channel));
f@0: broadcast = false;
f@0: }catch(Exception e){
f@0: // close the socket, log and discard the packet
f@0: try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
f@0: Server.logger.severe(e.getMessage());
f@0: }
f@0: break;
f@0: case INSERT_NODE :
f@0: if(channel == localChannel){
f@0: /* if the command is coming from the local user then there is *
f@0: * a diagram element queued, which is the one the user wanted to insert */
f@0: node = (Node)localhostDiagramElementQueue.get(diagramName).poll();
f@0: }else{
f@0: double dx = (Double)cmd.getArgAt(1);
f@0: double dy = (Double)cmd.getArgAt(2);
f@0: node = Finder.findNode((String)cmd.getArgAt(0),diagram.getNodePrototypes());
f@0: node = (Node)node.clone();
f@0: /* Place the top left corner of the bounds at the origin. It might be different from *
f@0: * the origin, as it depends on how the clonation is implemented internally. These calls *
f@0: * to translate are not notified to any listener as the node is not in the model yet */
f@0: Rectangle2D bounds = node.getBounds();
f@0: node.translate(new Point2D.Double(),-bounds.getX(),-bounds.getY(),DiagramEventSource.NONE);
f@0: /* perform the actual translation from the origin */
f@0: node.translate(new Point2D.Double(), dx, dy, DiagramEventSource.NONE);
f@0: }
f@0: /* wait for the node to be inserted in the model so that it gets an id, which is then *
f@0: * used in the awareness message to notify other users that the node has been inserted */
f@0: try {
f@0: SwingUtilities.invokeAndWait(new CommandExecutor.Insert(diagram.getCollectionModel(),node,null,source));
f@0: } catch (Exception exception) {
f@0: throw new RuntimeException(exception); // must never happen
f@0: }
f@0: /* send the reply to the client which issued the command */
f@0: if(channel != localChannel)
f@0: protocol.send(channel, new Reply(Reply.Name.INSERT_NODE_R,diagramName,"Insert new Node",source.getLocalSource()));
f@0:
f@0: DiagramEventActionSource actionSource = new DiagramEventActionSource(source,
f@0: Command.Name.INSERT_NODE,node.getId(),node.getName());
f@0: if(broadcastFilter.accept(actionSource)){
f@0: /* must set the username to the one of the client who sent the command *
f@0: * otherwise the local username is automatically assigned in the constructor */
f@0: for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
f@0: if(sc.channel.equals(channel))
f@0: actionSource.setUserName(sc.userName);
f@0: }
f@0: /* process on the broadcast filter */
f@0: DiagramEventActionSource processedSource = broadcastFilter.process(actionSource);
f@0: /* since no lock is used we must send an awareness message without piggybacking */
f@0: AwarenessMessage awMsg = new AwarenessMessage(
f@0: AwarenessMessage.Name.START_A,
f@0: diagramName,
f@0: processedSource
f@0: );
f@0: if(channel != localChannel){
f@0: /* update the local awareness panel and speech */
f@0: awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), DisplayFilter.getInstance().processForText(processedSource));
f@0: NarratorFactory.getInstance().speakWholeText(DisplayFilter.getInstance().processForSpeech(processedSource), Narrator.SECOND_VOICE);
f@0: }
f@0: /* broadcast the awareness message to all the clients but one which sent the *
f@0: * command and the local one to inform them the action has started */
f@0: for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
f@0: if(!sc.channel.equals(channel) && !sc.channel.equals(localChannel)){
f@0: protocol.send(sc.channel, awMsg);
f@0: }
f@0: }
f@0: }
f@0: break;
f@0: case REMOVE_NODE :
f@0: if(channel == localChannel){
f@0: /* if the command is coming from the local user then there is *
f@0: * a diagram element queued, which is the one the user wants to remove */
f@0: node = (Node)localhostDiagramElementQueue.get(diagramName).poll();
f@0: lockManager.removeLocks(node, diagramName);
f@0: }else{
f@0: diagram.getCollectionModel().getMonitor().lock();
f@0: node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
f@0: lockManager.removeLocks(node, diagramName);
f@0: diagram.getCollectionModel().getMonitor().unlock();
f@0: }
f@0: /* remove the action source, like when a lock is yielded, for this node */
f@0: removeEventActionSource(channel,node.getId(),diagram);
f@0: /* wait for the Event Dispatching Thread to delete the edge, in order to avoid collisions *
f@0: * with other locks, e.g. locking again an edge before the EDT deletes it */
f@0: try {
f@0: SwingUtilities.invokeAndWait(new CommandExecutor.Remove(diagram.getCollectionModel(),node,source));
f@0: } catch (Exception exception) {
f@0: throw new RuntimeException(exception); // must never happen
f@0: }
f@0: /* send the reply to the client which issued the command */
f@0: if(channel != localChannel)
f@0: protocol.send(channel, new Reply(Reply.Name.REMOVE_NODE_R,diagramName,"Node with id "+ node.getId() +" removed",source.getLocalSource()));
f@0: break;
f@0: case INSERT_EDGE :
f@0: long[] nodesToConnect = null;
f@0: if(channel == localChannel){
f@0: /* if the command is coming from the local user then there is a diagram *
f@0: * element queued, which is the one the user wanted to insert */
f@0: edge = (Edge)localhostDiagramElementQueue.get(diagramName).poll();
f@0: }else{
f@0: edge = Finder.findEdge((String)cmd.getArgAt(0),diagram.getEdgePrototypes());
f@0: edge = (Edge)edge.clone();
f@0: nodesToConnect = new long[cmd.getArgNum()-1];
f@0: for(int i=0;i(cmd.getArgNum()-3);
f@0: for(int i=3;ioldName" *
f@0: * in order to broadcast it to the other client and make them replace the new name with the old one */
f@0: String oldName = "";
f@0: if(awMsg.getName() == AwarenessMessage.Name.USERNAME_A){
f@0: String userName = (String)awMsg.getSource();
f@0: UserNameSocketChannel userNameChannel = null;
f@0: for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
f@0: if(sc.channel.equals(channel)){
f@0: userNameChannel = sc;
f@0: oldName = userNameChannel.userName;
f@0:
f@0: }
f@0: /* if another user already has the name then prevent from getting it */
f@0: if(sc.userName.equals(userName)){
f@0: /* user name already in use, send a reply and return */
f@0: protocol.send(channel, new AwarenessMessage(
f@0: AwarenessMessage.Name.ERROR_A,
f@0: awMsg.getDiagram(),
f@0: ResourceBundle.getBundle(Server.class.getName()).getString("awareness.msg.user_already_exists")
f@0: ));
f@0: return;
f@0: }
f@0: }
f@0: userNameChannel.userName = userName;
f@0: /* set the source of the msg for the clients, which don't hold the channel-username association *
f@0: * and therefore need a message of the form "newNameoldName" in order to do the replacement */
f@0: awMsg.setSource((String)awMsg.getSource()+AwarenessMessage.USERNAMES_SEPARATOR+oldName);
f@0: }
f@0:
f@0: /* update the local GUI to make the local user aware of the actions */
f@0: DisplayFilter filter = DisplayFilter.getInstance();
f@0: if(channel != localChannel && filter != null){
f@0: if(awMsg.getName() == AwarenessMessage.Name.USERNAME_A){
f@0: awarenessPanelEditor.replaceUserName(awMsg.getDiagram(), (String)awMsg.getSource());
f@0: }else{
f@0: DiagramEventActionSource processedSource = broadcastFilter.process((DiagramEventActionSource)awMsg.getSource()); // changes according to configuration;
f@0: if(filter.configurationHasChanged()){
f@0: for(Diagram d : diagramChannelAllocations.keySet())
f@0: awarenessPanelEditor.clearRecords(d.getName());
f@0: }
f@0:
f@0: /* select and unselect are announced and written (temporary) on the panel, regardless START_A and STOP_A */
f@0: if(processedSource.getCmd() == Command.Name.SELECT_NODE_FOR_EDGE_CREATION || processedSource.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION){
f@0: awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), filter.processForText(processedSource));
f@0: NarratorFactory.getInstance().speakWholeText(filter.processForSpeech(processedSource), Narrator.SECOND_VOICE);
f@0: }else if(awMsg.getName() == AwarenessMessage.Name.START_A){
f@0: awarenessPanelEditor.addRecord(awMsg.getDiagram(), filter.processForText(processedSource));
f@0: /* announce the just received awareness message via the second voice */
f@0: NarratorFactory.getInstance().speakWholeText(filter.processForSpeech(processedSource), Narrator.SECOND_VOICE);
f@0: }else{ // STOP_A
f@0: /* selection is a timedRecord, therefore no need to remove the record on STOP_A */
f@0: if(processedSource.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION && processedSource.getCmd() != Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION)
f@0: awarenessPanelEditor.removeRecord(awMsg.getDiagram(), filter.processForText(processedSource));
f@0: }
f@0: }
f@0: }
f@0:
f@0: /* broadcast the awareness message to all the clients but the local and *
f@0: * one which sent it, to inform them the action has started */
f@0: for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
f@0: if(sc.channel != localChannel && !sc.channel.equals(channel))
f@0: protocol.send(sc.channel, awMsg);
f@0: }
f@0: }
f@0:
f@0: Map> getLocalhostMap() {
f@0: return localhostDiagramElementQueue;
f@0: }
f@0:
f@0: private void replyLockMessage(SocketChannel channel, String diagramName,boolean yes) throws IOException{
f@0: protocol.send(channel, new LockMessage(
f@0: yes ? LockMessage.Name.YES_L : LockMessage.Name.NO_L,
f@0: diagramName,
f@0: -1,
f@0: DiagramEventActionSource.NULL
f@0: ));
f@0: }
f@0:
f@0: private DiagramEventActionSource removeEventActionSource(SocketChannel channel, long saveID, Diagram diagram){
f@0: Set userNames = diagramChannelAllocations.get(diagram);
f@0: if(userNames != null){
f@0: for(UserNameSocketChannel userName : userNames){
f@0: if(userName.channel.equals(channel)){
f@0: for(DiagramEventActionSource s : userName.lockAwarenessSources){
f@0: if(s.getSaveID() == saveID){
f@0: userName.lockAwarenessSources.remove(s);
f@0: return s;
f@0: }
f@0: }
f@0: }
f@0: }
f@0: }
f@0: return null;
f@0: }
f@0:
f@0: private Set indexes;
f@0: /* the String key is the name of diagram, this collection is shared with the class Server *
f@0: * and it's used to retrieve the diagrams out of commands */
f@0: private Map diagrams;
f@0: /* unique localChannel for all the digrams */
f@0: private SocketChannel localChannel;
f@0: /* this map contains all the channels bound to a diagram, so if a change *
f@0: * is made to a diagram all its channels are broadcasted through this map */
f@0: private Map> diagramChannelAllocations;
f@0: /* this map is used to pass the reference to elements created by the local client *
f@0: * (we don't create a new object as well as we do for the nodes created by remote clients */
f@0: private Map> localhostDiagramElementQueue;
f@0: private Protocol protocol;
f@0: private ServerLockManager lockManager;
f@0: private BroadcastFilter broadcastFilter;
f@0: private AwarenessPanelEditor awarenessPanelEditor;
f@0:
f@0: /* this class holds for each socketChannel the username associated to it
f@0: * and the last received awareness message source,*/
f@0: private static class UserNameSocketChannel {
f@0: UserNameSocketChannel(SocketChannel channel){
f@0: this(channel,"");
f@0: }
f@0:
f@0: UserNameSocketChannel(SocketChannel channel, String userName){
f@0: this.channel = channel;
f@0: this.userName = userName;
f@0: lockAwarenessSources = new LinkedList();
f@0: }
f@0:
f@0: SocketChannel channel;
f@0: String userName;
f@0: List lockAwarenessSources;
f@0: }
f@0: }