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