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: }