f@0
|
1 /*
|
f@0
|
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
|
f@0
|
3
|
f@0
|
4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
|
f@0
|
5
|
f@0
|
6 This program is free software: you can redistribute it and/or modify
|
f@0
|
7 it under the terms of the GNU General Public License as published by
|
f@0
|
8 the Free Software Foundation, either version 3 of the License, or
|
f@0
|
9 (at your option) any later version.
|
f@0
|
10
|
f@0
|
11 This program is distributed in the hope that it will be useful,
|
f@0
|
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
f@0
|
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
f@0
|
14 GNU General Public License for more details.
|
f@0
|
15
|
f@0
|
16 You should have received a copy of the GNU General Public License
|
f@0
|
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
f@0
|
18 */
|
f@0
|
19
|
f@0
|
20 package uk.ac.qmul.eecs.ccmi.network;
|
f@0
|
21
|
f@0
|
22 import java.awt.geom.Point2D;
|
f@0
|
23 import java.awt.geom.Rectangle2D;
|
f@0
|
24 import java.io.BufferedOutputStream;
|
f@0
|
25 import java.io.ByteArrayOutputStream;
|
f@0
|
26 import java.io.IOException;
|
f@0
|
27 import java.nio.channels.SocketChannel;
|
f@0
|
28 import java.util.HashMap;
|
f@0
|
29 import java.util.HashSet;
|
f@0
|
30 import java.util.LinkedList;
|
f@0
|
31 import java.util.List;
|
f@0
|
32 import java.util.Map;
|
f@0
|
33 import java.util.Queue;
|
f@0
|
34 import java.util.ResourceBundle;
|
f@0
|
35 import java.util.Set;
|
f@0
|
36 import java.util.concurrent.ConcurrentHashMap;
|
f@0
|
37 import java.util.concurrent.locks.ReentrantLock;
|
f@0
|
38 import java.util.logging.Logger;
|
f@0
|
39
|
f@0
|
40 import javax.swing.SwingUtilities;
|
f@0
|
41
|
f@0
|
42 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
|
f@0
|
43 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
|
f@0
|
44 import uk.ac.qmul.eecs.ccmi.gui.Diagram;
|
f@0
|
45 import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
|
f@0
|
46 import uk.ac.qmul.eecs.ccmi.gui.Edge;
|
f@0
|
47 import uk.ac.qmul.eecs.ccmi.gui.Finder;
|
f@0
|
48 import uk.ac.qmul.eecs.ccmi.gui.Lock;
|
f@0
|
49 import uk.ac.qmul.eecs.ccmi.gui.Node;
|
f@0
|
50 import uk.ac.qmul.eecs.ccmi.gui.awareness.AwarenessPanelEditor;
|
f@0
|
51 import uk.ac.qmul.eecs.ccmi.gui.awareness.BroadcastFilter;
|
f@0
|
52 import uk.ac.qmul.eecs.ccmi.gui.awareness.DisplayFilter;
|
f@0
|
53 import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
|
f@0
|
54 import uk.ac.qmul.eecs.ccmi.pdsupport.PdDiagram;
|
f@0
|
55 import uk.ac.qmul.eecs.ccmi.pdsupport.PdPersistenceManager;
|
f@0
|
56 import uk.ac.qmul.eecs.ccmi.speech.Narrator;
|
f@0
|
57 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
|
f@0
|
58
|
f@0
|
59 /* This class manages the different sessions with the clients. whereas the
|
f@0
|
60 * Server class just listens for connections, this class manages all what's going on about the
|
f@0
|
61 * diagram editing: e.g. command processing consistency check, client updates etc.
|
f@0
|
62 */
|
f@0
|
63 class ServerConnectionManager {
|
f@0
|
64 ServerConnectionManager( Map<String,Diagram> diagrams, AwarenessPanelEditor panelEditor) {
|
f@0
|
65 this.diagrams = diagrams;
|
f@0
|
66 awarenessPanelEditor = panelEditor;
|
f@0
|
67 diagramChannelAllocations = new HashMap<Diagram,Set<UserNameSocketChannel>>();
|
f@0
|
68 localhostDiagramElementQueue = new ConcurrentHashMap<String,Queue<DiagramElement>>();
|
f@0
|
69 protocol = ProtocolFactory.newInstance();
|
f@0
|
70 lockManager = new ServerLockManager();
|
f@0
|
71 broadcastFilter = BroadcastFilter.getInstance();
|
f@0
|
72 }
|
f@0
|
73
|
f@0
|
74 /**
|
f@0
|
75 * Removes the channel and the locks related to it from the inner data structures.
|
f@0
|
76 *
|
f@0
|
77 * @param channel the channel to remove
|
f@0
|
78 */
|
f@0
|
79 void removeChannel(SocketChannel channel) throws IOException{
|
f@0
|
80 String diagramName = null;
|
f@0
|
81 /* looks for the Set containing this channel */
|
f@0
|
82 for(Map.Entry<Diagram,Set<UserNameSocketChannel>> entry : diagramChannelAllocations.entrySet()){
|
f@0
|
83 UserNameSocketChannel unsc = null;
|
f@0
|
84 for(UserNameSocketChannel userNameSocketChannel : entry.getValue())
|
f@0
|
85 if(userNameSocketChannel.channel.equals(channel)){
|
f@0
|
86 unsc = userNameSocketChannel;
|
f@0
|
87 break;
|
f@0
|
88 }
|
f@0
|
89 /* remove the channel from this set of channels */
|
f@0
|
90 if(entry.getValue().remove(unsc) && !unsc.userName.isEmpty()){
|
f@0
|
91 diagramName = entry.getKey().getName();
|
f@0
|
92 awarenessPanelEditor.removeUserName(diagramName, unsc.userName);
|
f@0
|
93 /* notify the other clients the user has disconnected */
|
f@0
|
94 AwarenessMessage awMsg = new AwarenessMessage(
|
f@0
|
95 AwarenessMessage.Name.USERNAME_A,
|
f@0
|
96 diagramName,
|
f@0
|
97 ""+AwarenessMessage.USERNAMES_SEPARATOR+unsc.userName
|
f@0
|
98 );
|
f@0
|
99 for(UserNameSocketChannel userNameSocketChannel : entry.getValue()){
|
f@0
|
100 /* don't send aw msg to the local channel */
|
f@0
|
101 if(!userNameSocketChannel.channel.equals(localChannel))
|
f@0
|
102 protocol.send(userNameSocketChannel.channel, awMsg);
|
f@0
|
103 }
|
f@0
|
104 break;
|
f@0
|
105 }
|
f@0
|
106
|
f@0
|
107 }
|
f@0
|
108 /* all locks held by disconnected user are released */
|
f@0
|
109 lockManager.removeLocks(channel, diagramName);
|
f@0
|
110 }
|
f@0
|
111
|
f@0
|
112 void handleMessage(SocketChannel channel) throws IOException{
|
f@0
|
113 Message message = protocol.receiveMessage(channel);
|
f@0
|
114 if(message instanceof Command){
|
f@0
|
115 handleCommand((Command)message, channel);
|
f@0
|
116 }else if(message instanceof LockMessage) {
|
f@0
|
117 handleLockMessage((LockMessage)message,channel);
|
f@0
|
118 }else { // awareness message - broadcast the message
|
f@0
|
119 handleAwarenessMessage((AwarenessMessage)message,channel,null);
|
f@0
|
120 }
|
f@0
|
121 }
|
f@0
|
122
|
f@0
|
123 private void handleLockMessage(LockMessage lockMessage, SocketChannel channel) throws IOException{
|
f@0
|
124 Lock lock = LockMessageConverter.getLockFromMessageName((LockMessage.Name)lockMessage.getName());
|
f@0
|
125 String name = lockMessage.getName().toString();
|
f@0
|
126 String diagramName = lockMessage.getDiagram();
|
f@0
|
127
|
f@0
|
128 Diagram diagram = null;
|
f@0
|
129 synchronized(diagrams){
|
f@0
|
130 diagram = diagrams.get(diagramName);
|
f@0
|
131 }
|
f@0
|
132 if(diagram == null){
|
f@0
|
133 replyLockMessage(channel,diagramName,false);
|
f@0
|
134 return;
|
f@0
|
135 }
|
f@0
|
136
|
f@0
|
137 /* spot the tree node the message refers to */
|
f@0
|
138 int[] path = new int[lockMessage.getArgNum()];
|
f@0
|
139 for(int i = 0; i< path.length;i++){
|
f@0
|
140 path[i] = (Integer)lockMessage.getArgAt(i);
|
f@0
|
141 }
|
f@0
|
142
|
f@0
|
143 DiagramTreeNode treeNode = null;
|
f@0
|
144 /* synchronize with the event dispatching thread */
|
f@0
|
145 ReentrantLock monitor = diagram.getCollectionModel().getMonitor();
|
f@0
|
146 monitor.lock();
|
f@0
|
147
|
f@0
|
148 treeNode = Finder.findTreeNode(path, (DiagramTreeNode)diagram.getTreeModel().getRoot());
|
f@0
|
149 /* the tree node has been deleted, lock cannot be granted */
|
f@0
|
150 if(treeNode == null){
|
f@0
|
151 monitor.unlock();
|
f@0
|
152 replyLockMessage(channel,diagramName,false);
|
f@0
|
153 return;
|
f@0
|
154 }
|
f@0
|
155 //System.out.println("Lock message received: " + name +" diagram:"+diagramName+" treenode:"+treeNode.getName() );
|
f@0
|
156
|
f@0
|
157 /* check whether it's a GET or YIELD message and act accordingly */
|
f@0
|
158 if(name.startsWith(LockMessage.GET_LOCK_PREFIX)){
|
f@0
|
159 // System.out.println("get lock source:"+ lockMessage.getSource());
|
f@0
|
160 boolean succeeded;
|
f@0
|
161 succeeded = lockManager.requestLock(treeNode, lock, channel, diagramName);
|
f@0
|
162 monitor.unlock();
|
f@0
|
163 /* send the response */
|
f@0
|
164 replyLockMessage(channel,diagramName,succeeded);
|
f@0
|
165 if(succeeded && broadcastFilter.accept(lockMessage.getSource())){
|
f@0
|
166 DiagramEventActionSource processedSource = broadcastFilter.process(lockMessage.getSource()); // changes according to configuration;
|
f@0
|
167
|
f@0
|
168 //select node is a temporary record, therefore it doesn't need to be stored
|
f@0
|
169 if(processedSource.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION){
|
f@0
|
170 Set<UserNameSocketChannel> userNames = diagramChannelAllocations.get(diagram);
|
f@0
|
171 /* saves the diagramEventActionSource for when the lock is yielded */
|
f@0
|
172 if(userNames != null){
|
f@0
|
173 for(UserNameSocketChannel userName : userNames){
|
f@0
|
174 if(userName.channel.equals(channel)){
|
f@0
|
175 userName.lockAwarenessSources.add(processedSource);
|
f@0
|
176 }
|
f@0
|
177 }
|
f@0
|
178 }
|
f@0
|
179 }
|
f@0
|
180 /* handle the awareness message piggybacked in the lock message */
|
f@0
|
181 AwarenessMessage awarMsg = new AwarenessMessage(
|
f@0
|
182 AwarenessMessage.Name.START_A,
|
f@0
|
183 diagramName,
|
f@0
|
184 processedSource
|
f@0
|
185 );
|
f@0
|
186 handleAwarenessMessage(awarMsg,channel,diagram);
|
f@0
|
187 }
|
f@0
|
188 }else{ // yield lock
|
f@0
|
189 boolean released = lockManager.releaseLock(treeNode, lock, channel,diagramName);
|
f@0
|
190 monitor.unlock();
|
f@0
|
191 DiagramEventActionSource source = lockMessage.getSource();
|
f@0
|
192 /* 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
|
193 if(released && source != DiagramEventActionSource.NULL && source.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION){
|
f@0
|
194
|
f@0
|
195 if(source.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION && broadcastFilter.accept(source)){
|
f@0
|
196 /* unselect node for edge creation is treated differently because it doesn't *
|
f@0
|
197 * clear the text panel but adds another record, which is temporary */
|
f@0
|
198 handleAwarenessMessage(new AwarenessMessage(
|
f@0
|
199 AwarenessMessage.Name.STOP_A,
|
f@0
|
200 diagramName,
|
f@0
|
201 source),
|
f@0
|
202 channel,
|
f@0
|
203 diagram);
|
f@0
|
204 return;
|
f@0
|
205 }
|
f@0
|
206
|
f@0
|
207 /* retrieves the diagramEventActionSource: when the lock was gotten, the source was stored in *
|
f@0
|
208 * userName.lockAwarenessSource. This is done because the broadcast filter configuration might *
|
f@0
|
209 * have changed in the meanwhile but we still need to send the aw msg with the original source *
|
f@0
|
210 * or the clients won't be able to pick out the string to delete */
|
f@0
|
211 DiagramEventActionSource savedSource = removeEventActionSource(channel,source.getSaveID(),diagram);
|
f@0
|
212
|
f@0
|
213 /* saved source = null means the broadcast filter didn't let the get_lock message
|
f@0
|
214 * this yield_lock message is referring to. Move on */
|
f@0
|
215 if(savedSource == null){
|
f@0
|
216 return;
|
f@0
|
217 }
|
f@0
|
218
|
f@0
|
219 AwarenessMessage awMsg = new AwarenessMessage(
|
f@0
|
220 AwarenessMessage.Name.STOP_A,
|
f@0
|
221 diagramName,
|
f@0
|
222 savedSource
|
f@0
|
223 );
|
f@0
|
224 handleAwarenessMessage(awMsg,channel,diagram);
|
f@0
|
225 }
|
f@0
|
226 }
|
f@0
|
227 }
|
f@0
|
228
|
f@0
|
229 private void handleCommand(final Command cmd, SocketChannel channel) throws IOException{
|
f@0
|
230 /* init some variables we're gonna use in (nearly) every branch of the switch */
|
f@0
|
231 final String diagramName = cmd.getDiagram();
|
f@0
|
232 Diagram diagram = null;
|
f@0
|
233
|
f@0
|
234 if(cmd.getName() != Command.Name.LIST){
|
f@0
|
235 synchronized(diagrams){
|
f@0
|
236 diagram = diagrams.get(diagramName);
|
f@0
|
237 }
|
f@0
|
238 if(diagram == null)
|
f@0
|
239 protocol.send(channel, new Reply(Reply.Name.ERROR_R,diagramName,"Diagram "+diagramName+" does not exists",cmd.getSource()));
|
f@0
|
240 }
|
f@0
|
241 Node node = null;
|
f@0
|
242 Edge edge = null;
|
f@0
|
243 boolean broadcast = true;
|
f@0
|
244
|
f@0
|
245 DiagramEventSource source = cmd.getSource();
|
f@0
|
246 if(channel == localChannel)
|
f@0
|
247 source = source.getLocalSource();
|
f@0
|
248 /* set the diagram id so the haptic will update the diagram specified by the command and not the active tab's */
|
f@0
|
249 if(diagram != null)
|
f@0
|
250 source.setDiagramName(diagram.getName());
|
f@0
|
251 /* log the command through the interaction logger, if any */
|
f@0
|
252 Command.log(cmd,(channel == localChannel) ? "local command received" : "remote command received");
|
f@0
|
253 //System.out.println("ServerConnectionManager: received command "+cmd.getName());
|
f@0
|
254 switch(cmd.getName()){
|
f@0
|
255 case LOCAL : // the local socket makes itself known to the server
|
f@0
|
256 localChannel = channel;
|
f@0
|
257 Set<UserNameSocketChannel> list = new HashSet<UserNameSocketChannel>();
|
f@0
|
258 list.add(new UserNameSocketChannel(localChannel,AwarenessMessage.getDefaultUserName()));
|
f@0
|
259 diagramChannelAllocations.put(diagram, list);
|
f@0
|
260 broadcast = false;
|
f@0
|
261 break;
|
f@0
|
262 case LIST : // ask for the list of available diagrams on the server
|
f@0
|
263 StringBuilder names = new StringBuilder("");
|
f@0
|
264 synchronized(diagrams){
|
f@0
|
265 for(String s : diagrams.keySet()){
|
f@0
|
266 names.append(s).append('\n');
|
f@0
|
267 }
|
f@0
|
268 }
|
f@0
|
269 protocol.send(channel, new Reply(Reply.Name.LIST_R,"",names.toString(),DiagramEventSource.NONE));
|
f@0
|
270 broadcast = false;
|
f@0
|
271 break;
|
f@0
|
272 case GET : // ask for a diagram xml
|
f@0
|
273 try{
|
f@0
|
274 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
275 Set<UserNameSocketChannel> userNames = diagramChannelAllocations.get(diagram);
|
f@0
|
276 ByteArrayOutputStream out = new ByteArrayOutputStream();
|
f@0
|
277 if(diagram instanceof PdDiagram){
|
f@0
|
278 PdPersistenceManager.getInstance().encodeDiagramInstance(diagram, new BufferedOutputStream(out));
|
f@0
|
279 }else{
|
f@0
|
280 PersistenceManager.encodeDiagramInstance(diagram, new BufferedOutputStream(out));
|
f@0
|
281 }
|
f@0
|
282 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
283 try{
|
f@0
|
284 protocol.send(channel, new Reply(Reply.Name.GET_R,diagramName,out.toString("UTF-8"),DiagramEventSource.NONE));
|
f@0
|
285 for(UserNameSocketChannel sc: userNames){
|
f@0
|
286 protocol.send(channel, new AwarenessMessage(AwarenessMessage.Name.USERNAME_A,diagramName,sc.userName));
|
f@0
|
287 }
|
f@0
|
288 }catch(IOException ioe){
|
f@0
|
289 throw ioe;
|
f@0
|
290 }
|
f@0
|
291 userNames.add(new UserNameSocketChannel(channel));
|
f@0
|
292 broadcast = false;
|
f@0
|
293 }catch(Exception e){
|
f@0
|
294 // close the socket, log and discard the packet
|
f@0
|
295 try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
|
f@0
|
296 Server.logger.severe(e.getMessage());
|
f@0
|
297 }
|
f@0
|
298 break;
|
f@0
|
299 case INSERT_NODE :
|
f@0
|
300 if(channel == localChannel){
|
f@0
|
301 /* if the command is coming from the local user then there is *
|
f@0
|
302 * a diagram element queued, which is the one the user wanted to insert */
|
f@0
|
303 node = (Node)localhostDiagramElementQueue.get(diagramName).poll();
|
f@0
|
304 }else{
|
f@0
|
305 double dx = (Double)cmd.getArgAt(1);
|
f@0
|
306 double dy = (Double)cmd.getArgAt(2);
|
f@0
|
307 node = Finder.findNode((String)cmd.getArgAt(0),diagram.getNodePrototypes());
|
f@0
|
308 node = (Node)node.clone();
|
f@0
|
309 /* Place the top left corner of the bounds at the origin. It might be different from *
|
f@0
|
310 * the origin, as it depends on how the clonation is implemented internally. These calls *
|
f@0
|
311 * to translate are not notified to any listener as the node is not in the model yet */
|
f@0
|
312 Rectangle2D bounds = node.getBounds();
|
f@0
|
313 node.translate(new Point2D.Double(),-bounds.getX(),-bounds.getY(),DiagramEventSource.NONE);
|
f@0
|
314 /* perform the actual translation from the origin */
|
f@0
|
315 node.translate(new Point2D.Double(), dx, dy, DiagramEventSource.NONE);
|
f@0
|
316 }
|
f@0
|
317 /* wait for the node to be inserted in the model so that it gets an id, which is then *
|
f@0
|
318 * used in the awareness message to notify other users that the node has been inserted */
|
f@0
|
319 try {
|
f@0
|
320 SwingUtilities.invokeAndWait(new CommandExecutor.Insert(diagram.getCollectionModel(),node,null,source));
|
f@0
|
321 } catch (Exception exception) {
|
f@0
|
322 throw new RuntimeException(exception); // must never happen
|
f@0
|
323 }
|
f@0
|
324 /* send the reply to the client which issued the command */
|
f@0
|
325 if(channel != localChannel)
|
f@0
|
326 protocol.send(channel, new Reply(Reply.Name.INSERT_NODE_R,diagramName,"Insert new Node",source.getLocalSource()));
|
f@0
|
327
|
f@0
|
328 DiagramEventActionSource actionSource = new DiagramEventActionSource(source,
|
f@0
|
329 Command.Name.INSERT_NODE,node.getId(),node.getName());
|
f@0
|
330 if(broadcastFilter.accept(actionSource)){
|
f@0
|
331 /* must set the username to the one of the client who sent the command *
|
f@0
|
332 * otherwise the local username is automatically assigned in the constructor */
|
f@0
|
333 for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
|
f@0
|
334 if(sc.channel.equals(channel))
|
f@0
|
335 actionSource.setUserName(sc.userName);
|
f@0
|
336 }
|
f@0
|
337 /* process on the broadcast filter */
|
f@0
|
338 DiagramEventActionSource processedSource = broadcastFilter.process(actionSource);
|
f@0
|
339 /* since no lock is used we must send an awareness message without piggybacking */
|
f@0
|
340 AwarenessMessage awMsg = new AwarenessMessage(
|
f@0
|
341 AwarenessMessage.Name.START_A,
|
f@0
|
342 diagramName,
|
f@0
|
343 processedSource
|
f@0
|
344 );
|
f@0
|
345 if(channel != localChannel){
|
f@0
|
346 /* update the local awareness panel and speech */
|
f@0
|
347 awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), DisplayFilter.getInstance().processForText(processedSource));
|
f@0
|
348 NarratorFactory.getInstance().speakWholeText(DisplayFilter.getInstance().processForSpeech(processedSource), Narrator.SECOND_VOICE);
|
f@0
|
349 }
|
f@0
|
350 /* broadcast the awareness message to all the clients but one which sent the *
|
f@0
|
351 * command and the local one to inform them the action has started */
|
f@0
|
352 for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
|
f@0
|
353 if(!sc.channel.equals(channel) && !sc.channel.equals(localChannel)){
|
f@0
|
354 protocol.send(sc.channel, awMsg);
|
f@0
|
355 }
|
f@0
|
356 }
|
f@0
|
357 }
|
f@0
|
358 break;
|
f@0
|
359 case REMOVE_NODE :
|
f@0
|
360 if(channel == localChannel){
|
f@0
|
361 /* if the command is coming from the local user then there is *
|
f@0
|
362 * a diagram element queued, which is the one the user wants to remove */
|
f@0
|
363 node = (Node)localhostDiagramElementQueue.get(diagramName).poll();
|
f@0
|
364 lockManager.removeLocks(node, diagramName);
|
f@0
|
365 }else{
|
f@0
|
366 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
367 node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
368 lockManager.removeLocks(node, diagramName);
|
f@0
|
369 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
370 }
|
f@0
|
371 /* remove the action source, like when a lock is yielded, for this node */
|
f@0
|
372 removeEventActionSource(channel,node.getId(),diagram);
|
f@0
|
373 /* wait for the Event Dispatching Thread to delete the edge, in order to avoid collisions *
|
f@0
|
374 * with other locks, e.g. locking again an edge before the EDT deletes it */
|
f@0
|
375 try {
|
f@0
|
376 SwingUtilities.invokeAndWait(new CommandExecutor.Remove(diagram.getCollectionModel(),node,source));
|
f@0
|
377 } catch (Exception exception) {
|
f@0
|
378 throw new RuntimeException(exception); // must never happen
|
f@0
|
379 }
|
f@0
|
380 /* send the reply to the client which issued the command */
|
f@0
|
381 if(channel != localChannel)
|
f@0
|
382 protocol.send(channel, new Reply(Reply.Name.REMOVE_NODE_R,diagramName,"Node with id "+ node.getId() +" removed",source.getLocalSource()));
|
f@0
|
383 break;
|
f@0
|
384 case INSERT_EDGE :
|
f@0
|
385 long[] nodesToConnect = null;
|
f@0
|
386 if(channel == localChannel){
|
f@0
|
387 /* if the command is coming from the local user then there is a diagram *
|
f@0
|
388 * element queued, which is the one the user wanted to insert */
|
f@0
|
389 edge = (Edge)localhostDiagramElementQueue.get(diagramName).poll();
|
f@0
|
390 }else{
|
f@0
|
391 edge = Finder.findEdge((String)cmd.getArgAt(0),diagram.getEdgePrototypes());
|
f@0
|
392 edge = (Edge)edge.clone();
|
f@0
|
393 nodesToConnect = new long[cmd.getArgNum()-1];
|
f@0
|
394 for(int i=0;i<nodesToConnect.length;i++){
|
f@0
|
395 nodesToConnect[i] = (Long)cmd.getArgAt(i+1);
|
f@0
|
396 }
|
f@0
|
397 }
|
f@0
|
398 /* wait for the edge to be inserted in the model so that it gets an id, which is then *
|
f@0
|
399 * used in the awareness message to notify other users that the node has been inserted */
|
f@0
|
400 try {
|
f@0
|
401 SwingUtilities.invokeAndWait(new CommandExecutor.Insert(diagram.getCollectionModel(), edge, nodesToConnect, source));
|
f@0
|
402 } catch (Exception exception) {
|
f@0
|
403 throw new RuntimeException(exception);
|
f@0
|
404 }
|
f@0
|
405 /* send the reply to the client which issued the command */
|
f@0
|
406 if(channel != localChannel)
|
f@0
|
407 protocol.send(channel, new Reply(Reply.Name.INSERT_EDGE_R,diagramName,"Insert new Edge", source.getLocalSource()));
|
f@0
|
408
|
f@0
|
409 /* send the awareness message for edge insertion */
|
f@0
|
410 DiagramEventActionSource actSource = new DiagramEventActionSource(source,
|
f@0
|
411 Command.Name.INSERT_EDGE,edge.getId(),edge.getName());
|
f@0
|
412 if(broadcastFilter.accept(actSource)){
|
f@0
|
413 /* must set the username to the one of the client who sent the command *
|
f@0
|
414 * otherwise the local username is automatically assigned in the constructor */
|
f@0
|
415 for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
|
f@0
|
416 if(sc.channel.equals(channel))
|
f@0
|
417 actSource.setUserName(sc.userName);
|
f@0
|
418 }
|
f@0
|
419
|
f@0
|
420 /* process it with the broadcast filter */
|
f@0
|
421 DiagramEventActionSource processedSource = broadcastFilter.process(actSource);
|
f@0
|
422
|
f@0
|
423 /* since no lock is used we must send an awareness message without piggybacking */
|
f@0
|
424 AwarenessMessage awMsg = new AwarenessMessage(
|
f@0
|
425 AwarenessMessage.Name.START_A,
|
f@0
|
426 diagramName,
|
f@0
|
427 processedSource
|
f@0
|
428 );
|
f@0
|
429
|
f@0
|
430 if(channel != localChannel){
|
f@0
|
431 /* update the local awareness panel and speech */
|
f@0
|
432 awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), DisplayFilter.getInstance().processForText(processedSource));
|
f@0
|
433 NarratorFactory.getInstance().speakWholeText(
|
f@0
|
434 DisplayFilter.getInstance().processForSpeech(processedSource), Narrator.SECOND_VOICE);
|
f@0
|
435 }
|
f@0
|
436 /* broadcast the awareness message to all the clients but one which sent the *
|
f@0
|
437 * command and the local one to inform them the action has started */
|
f@0
|
438 for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
|
f@0
|
439 if(!sc.channel.equals(channel) && !sc.channel.equals(localChannel)){
|
f@0
|
440 protocol.send(sc.channel, awMsg);
|
f@0
|
441 }
|
f@0
|
442 }
|
f@0
|
443 }
|
f@0
|
444 break;
|
f@0
|
445 case REMOVE_EDGE :
|
f@0
|
446 if(channel == localChannel){
|
f@0
|
447 /* if the command is coming from the local user then there is a diagram */
|
f@0
|
448 /* element queued, which is the one the user wanted to insert */
|
f@0
|
449 edge = (Edge)localhostDiagramElementQueue.get(diagramName).poll();
|
f@0
|
450 }else{
|
f@0
|
451 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
452 edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
|
f@0
|
453 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
454 }
|
f@0
|
455 /* wait for the Event Dispatching Thread to delete the edge, in order to avoid collisions *
|
f@0
|
456 * with other locks, e.g. locking again an edge before the EDT deletes it */
|
f@0
|
457 try {
|
f@0
|
458 lockManager.removeLocks(edge, diagramName);
|
f@0
|
459 SwingUtilities.invokeAndWait(new CommandExecutor.Remove(diagram.getCollectionModel(), edge, source));
|
f@0
|
460 } catch (Exception e) {
|
f@0
|
461 throw new RuntimeException(e); // must never happen
|
f@0
|
462 }
|
f@0
|
463 /* remove the action source, like when a lock is yielded, for this node */
|
f@0
|
464 removeEventActionSource(channel,edge.getId(),diagram);
|
f@0
|
465 /* send the reply to the client which issued the command */
|
f@0
|
466 if(channel != localChannel)
|
f@0
|
467 protocol.send(channel, new Reply(Reply.Name.REMOVE_EDGE_R,diagramName,"Edge with id "+ edge.getId() +" removed", source.getLocalSource()));
|
f@0
|
468 break;
|
f@0
|
469 case SET_EDGE_NAME : {
|
f@0
|
470 DiagramElement de = null;
|
f@0
|
471 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
472 de = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
|
f@0
|
473 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
474 SwingUtilities.invokeLater(new CommandExecutor.SetName(de,((String)cmd.getArgAt(1)),source));
|
f@0
|
475 if(channel != localChannel)
|
f@0
|
476 protocol.send(channel, new Reply(Reply.Name.SET_EDGE_NAME_R,diagramName,"Name set to "+ cmd.getArgAt(1),source.getLocalSource()));
|
f@0
|
477 }break;
|
f@0
|
478 case SET_NODE_NAME : {
|
f@0
|
479 DiagramElement de = null;
|
f@0
|
480 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
481 de = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
482 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
483 SwingUtilities.invokeLater(new CommandExecutor.SetName(de,((String)cmd.getArgAt(1)),source));
|
f@0
|
484 if(channel != localChannel)
|
f@0
|
485 protocol.send(channel, new Reply(Reply.Name.SET_NODE_NAME_R,diagramName,"Name set to "+ cmd.getArgAt(1),source.getLocalSource()));
|
f@0
|
486 }break;
|
f@0
|
487 case SET_PROPERTY :
|
f@0
|
488 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
489 node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
490 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
491 SwingUtilities.invokeLater(new CommandExecutor.SetProperty(
|
f@0
|
492 node,
|
f@0
|
493 (String)cmd.getArgAt(1),
|
f@0
|
494 (Integer)cmd.getArgAt(2),
|
f@0
|
495 (String)cmd.getArgAt(3),
|
f@0
|
496 source
|
f@0
|
497 ));
|
f@0
|
498 if(channel != localChannel)
|
f@0
|
499 protocol.send(channel, new Reply(Reply.Name.SET_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(2)+ " set to "+ cmd.getArgAt(3),source.getLocalSource()));
|
f@0
|
500 break;
|
f@0
|
501 case SET_PROPERTIES :
|
f@0
|
502 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
503 node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
504 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
505
|
f@0
|
506 SwingUtilities.invokeLater(new CommandExecutor.SetProperties(
|
f@0
|
507 node,
|
f@0
|
508 (String)cmd.getArgAt(1),
|
f@0
|
509 source
|
f@0
|
510 ));
|
f@0
|
511 if(channel != localChannel)
|
f@0
|
512 protocol.send(channel, new Reply(Reply.Name.SET_PROPERTIES_R,diagramName,"Properties for " + node.getName()+ " set to "+ cmd.getArgAt(1),source.getLocalSource()));
|
f@0
|
513 break;
|
f@0
|
514 case CLEAR_PROPERTIES :
|
f@0
|
515 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
516 node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
517 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
518 SwingUtilities.invokeLater(new CommandExecutor.ClearProperties(node,source));
|
f@0
|
519 if(channel != localChannel)
|
f@0
|
520 protocol.send(channel, new Reply(Reply.Name.CLEAR_PROPERTIES_R,diagramName,"Propertis of Node "+ node.getName() +" cleared",source.getLocalSource()));
|
f@0
|
521 break;
|
f@0
|
522 case SET_NOTES :{
|
f@0
|
523 DiagramTreeNode treeNode = null;
|
f@0
|
524 int[] path = new int[cmd.getArgNum()-1];
|
f@0
|
525 for(int i = 0; i< cmd.getArgNum()-1;i++){
|
f@0
|
526 path[i] = (Integer)cmd.getArgAt(i);
|
f@0
|
527 }
|
f@0
|
528 final String notes = (String)cmd.getArgAt(cmd.getArgNum()-1);
|
f@0
|
529 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
530 treeNode = Finder.findTreeNode(path, (DiagramTreeNode)diagram.getTreeModel().getRoot());
|
f@0
|
531 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
532 SwingUtilities.invokeLater(new CommandExecutor.SetNotes(diagram.getTreeModel(),treeNode,notes,source));
|
f@0
|
533 if(channel != localChannel)
|
f@0
|
534 protocol.send(channel, new Reply(Reply.Name.SET_NOTES_R,diagramName,"Notes for " + treeNode.getName() + " successfully updated",source.getLocalSource()));
|
f@0
|
535 }break;
|
f@0
|
536 case ADD_PROPERTY :
|
f@0
|
537 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
538 node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
539 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
540 SwingUtilities.invokeLater(new CommandExecutor.AddProperty(
|
f@0
|
541 node,
|
f@0
|
542 (String)cmd.getArgAt(1),
|
f@0
|
543 (String)cmd.getArgAt(2),
|
f@0
|
544 source
|
f@0
|
545 ));
|
f@0
|
546 if(channel != localChannel)
|
f@0
|
547 protocol.send(channel, new Reply(Reply.Name.ADD_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(1)+ " added to "+ cmd.getArgAt(1),source.getLocalSource()));
|
f@0
|
548 break;
|
f@0
|
549 case REMOVE_PROPERTY :
|
f@0
|
550 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
551 node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
552 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
553 SwingUtilities.invokeLater(new CommandExecutor.RemoveProperty(
|
f@0
|
554 node,
|
f@0
|
555 (String)cmd.getArgAt(1),
|
f@0
|
556 (Integer)cmd.getArgAt(2),
|
f@0
|
557 source));
|
f@0
|
558 if(channel != localChannel)
|
f@0
|
559 protocol.send(channel, new Reply(Reply.Name.REMOVE_PROPERTY_R,diagramName,"Property " + cmd.getArgAt(1)+ " of type "+ cmd.getArgAt(1)+" removed",source.getLocalSource()));
|
f@0
|
560 break;
|
f@0
|
561 case SET_MODIFIERS :
|
f@0
|
562 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
563 node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
564 indexes = new HashSet<Integer>(cmd.getArgNum()-3);
|
f@0
|
565 for(int i=3;i<cmd.getArgNum();i++){
|
f@0
|
566 indexes.add((Integer)cmd.getArgAt(i));
|
f@0
|
567 }
|
f@0
|
568 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
569 SwingUtilities.invokeLater(new CommandExecutor.SetModifiers(
|
f@0
|
570 node,
|
f@0
|
571 (String)cmd.getArgAt(1),
|
f@0
|
572 (Integer)cmd.getArgAt(2),
|
f@0
|
573 indexes,
|
f@0
|
574 source));
|
f@0
|
575 if(channel != localChannel)
|
f@0
|
576 protocol.send(channel, new Reply(Reply.Name.SET_MODIFIERS_R,diagramName,"Modifiers for " + cmd.getArgAt(1)+ " successfully set",source.getLocalSource()));
|
f@0
|
577 break;
|
f@0
|
578 case SET_ENDLABEL :
|
f@0
|
579 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
580 edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
|
f@0
|
581 node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
|
f@0
|
582 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
583 SwingUtilities.invokeLater(new CommandExecutor.SetEndLabel(edge,node,(String)cmd.getArgAt(2),source));
|
f@0
|
584 if(channel != localChannel)
|
f@0
|
585 protocol.send(channel, new Reply(Reply.Name.SET_ENDLABEL_R,diagramName,"Endlabel set to "+ cmd.getArgAt(2) +" for edge "+edge.getName(),source.getLocalSource()));
|
f@0
|
586 break;
|
f@0
|
587 case SET_ENDDESCRIPTION :
|
f@0
|
588 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
589 edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
|
f@0
|
590 node = Finder.findNode((Long)cmd.getArgAt(1),diagram.getCollectionModel().getNodes());
|
f@0
|
591 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
592 SwingUtilities.invokeLater(new CommandExecutor.SetEndDescription(
|
f@0
|
593 edge,
|
f@0
|
594 node,
|
f@0
|
595 (Integer)cmd.getArgAt(2),
|
f@0
|
596 source
|
f@0
|
597 ));
|
f@0
|
598 if(channel != localChannel)
|
f@0
|
599 protocol.send(channel, new Reply(Reply.Name.SET_ENDDESCRIPTION_R,diagramName,"End description set to "
|
f@0
|
600 +(cmd.getArgNum() == 3 ? cmd.getArgAt(2) : Edge.NO_ENDDESCRIPTION_STRING)
|
f@0
|
601 + " for edge",source.getLocalSource()));
|
f@0
|
602 break;
|
f@0
|
603 case TRANSLATE_NODE :
|
f@0
|
604 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
605 node = Finder.findNode((Long)cmd.getArgAt(0),diagram.getCollectionModel().getNodes());
|
f@0
|
606 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
607 SwingUtilities.invokeLater(new CommandExecutor.Translate(
|
f@0
|
608 node,
|
f@0
|
609 new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
|
f@0
|
610 (Double)cmd.getArgAt(3),
|
f@0
|
611 (Double)cmd.getArgAt(4),
|
f@0
|
612 source
|
f@0
|
613 ));
|
f@0
|
614 if(channel != localChannel)
|
f@0
|
615 protocol.send(channel, new Reply(Reply.Name.TRANSLATE_NODE_R,diagramName,"Translate. Delta=("+(Double)cmd.getArgAt(3)+","+(Double)cmd.getArgAt(4)+")",source.getLocalSource()));
|
f@0
|
616 break;
|
f@0
|
617 case TRANSLATE_EDGE :
|
f@0
|
618 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
619 edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
|
f@0
|
620 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
621 SwingUtilities.invokeLater(new CommandExecutor.Translate(
|
f@0
|
622 edge,
|
f@0
|
623 new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
|
f@0
|
624 (Double)cmd.getArgAt(3),
|
f@0
|
625 (Double)cmd.getArgAt(4),
|
f@0
|
626 source
|
f@0
|
627 ));
|
f@0
|
628 if(channel != localChannel)
|
f@0
|
629 protocol.send(channel, new Reply(Reply.Name.TRANSLATE_EDGE_R,
|
f@0
|
630 diagramName,
|
f@0
|
631 "Translate. Delta=("+(Double)cmd.getArgAt(3)+","+(Double)cmd.getArgAt(4)+")",
|
f@0
|
632 source.getLocalSource())
|
f@0
|
633 );
|
f@0
|
634 break;
|
f@0
|
635 case BEND :
|
f@0
|
636 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
637 edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
|
f@0
|
638 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
639 Point2D bendStart = null;
|
f@0
|
640 if(cmd.getArgNum() == 5){
|
f@0
|
641 bendStart = new Point2D.Double((Double)cmd.getArgAt(3),(Double)cmd.getArgAt(4));
|
f@0
|
642 }
|
f@0
|
643 SwingUtilities.invokeLater(new CommandExecutor.Bend(
|
f@0
|
644 edge,
|
f@0
|
645 new Point2D.Double((Double)cmd.getArgAt(1),(Double)cmd.getArgAt(2)),
|
f@0
|
646 bendStart,
|
f@0
|
647 source
|
f@0
|
648 ));
|
f@0
|
649 if(channel != localChannel)
|
f@0
|
650 protocol.send(channel, new Reply(Reply.Name.BEND_R,diagramName,"Bend at point: ("+(Double)cmd.getArgAt(1)+","+(Double)cmd.getArgAt(1)+")",cmd.getSource().getLocalSource()));
|
f@0
|
651 break;
|
f@0
|
652 case STOP_EDGE_MOVE :
|
f@0
|
653 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
654 edge = Finder.findEdge((Long)cmd.getArgAt(0),diagram.getCollectionModel().getEdges());
|
f@0
|
655 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
656 SwingUtilities.invokeLater(new CommandExecutor.StopMove(edge,source));
|
f@0
|
657 if(channel != localChannel)
|
f@0
|
658 protocol.send(channel, new Reply(Reply.Name.STOP_EDGE_MOVE_R,diagramName,"Undo straight bends",source.getLocalSource()));
|
f@0
|
659 break;
|
f@0
|
660 case STOP_NODE_MOVE :
|
f@0
|
661 diagram.getCollectionModel().getMonitor().lock();
|
f@0
|
662 node = Finder.findNode((Long)cmd.getArgAt(0), diagram.getCollectionModel().getNodes());
|
f@0
|
663 diagram.getCollectionModel().getMonitor().unlock();
|
f@0
|
664 SwingUtilities.invokeLater(new CommandExecutor.StopMove(node,source));
|
f@0
|
665 if(channel != localChannel){
|
f@0
|
666 protocol.send(channel, new Reply(Reply.Name.STOP_NODE_MOVE_R,diagramName,"Stop node move",source.getLocalSource()));
|
f@0
|
667 }
|
f@0
|
668 break;
|
f@0
|
669 default : throw new RuntimeException(cmd.getName().toString()+ " command not recognized");
|
f@0
|
670 }
|
f@0
|
671 if(broadcast){
|
f@0
|
672 /* 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)*/
|
f@0
|
673 for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
|
f@0
|
674 if(sc.channel != localChannel && !sc.channel.equals(channel)){
|
f@0
|
675 protocol.send(sc.channel, cmd);
|
f@0
|
676 }
|
f@0
|
677 }
|
f@0
|
678 }
|
f@0
|
679 }
|
f@0
|
680
|
f@0
|
681 private void handleAwarenessMessage(AwarenessMessage awMsg,SocketChannel channel, Diagram diagram) throws IOException {
|
f@0
|
682 if(diagram == null)
|
f@0
|
683 synchronized(diagrams){
|
f@0
|
684 diagram = diagrams.get(awMsg.getDiagram());
|
f@0
|
685 }
|
f@0
|
686
|
f@0
|
687 if(awMsg.getName() == AwarenessMessage.Name.ERROR_A){
|
f@0
|
688 Logger.getLogger(Server.class.getCanonicalName()).info((String)awMsg.getSource());
|
f@0
|
689 return;
|
f@0
|
690 }
|
f@0
|
691
|
f@0
|
692 /* for username aw msg checks whether the chosen name is not already used by another client. *
|
f@0
|
693 * If not changes the source from "newName", sent by the client, into "newName<SEPARATOR>oldName" *
|
f@0
|
694 * in order to broadcast it to the other client and make them replace the new name with the old one */
|
f@0
|
695 String oldName = "";
|
f@0
|
696 if(awMsg.getName() == AwarenessMessage.Name.USERNAME_A){
|
f@0
|
697 String userName = (String)awMsg.getSource();
|
f@0
|
698 UserNameSocketChannel userNameChannel = null;
|
f@0
|
699 for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
|
f@0
|
700 if(sc.channel.equals(channel)){
|
f@0
|
701 userNameChannel = sc;
|
f@0
|
702 oldName = userNameChannel.userName;
|
f@0
|
703
|
f@0
|
704 }
|
f@0
|
705 /* if another user already has the name then prevent from getting it */
|
f@0
|
706 if(sc.userName.equals(userName)){
|
f@0
|
707 /* user name already in use, send a reply and return */
|
f@0
|
708 protocol.send(channel, new AwarenessMessage(
|
f@0
|
709 AwarenessMessage.Name.ERROR_A,
|
f@0
|
710 awMsg.getDiagram(),
|
f@0
|
711 ResourceBundle.getBundle(Server.class.getName()).getString("awareness.msg.user_already_exists")
|
f@0
|
712 ));
|
f@0
|
713 return;
|
f@0
|
714 }
|
f@0
|
715 }
|
f@0
|
716 userNameChannel.userName = userName;
|
f@0
|
717 /* set the source of the msg for the clients, which don't hold the channel-username association *
|
f@0
|
718 * and therefore need a message of the form "newName<SEPARATOR>oldName" in order to do the replacement */
|
f@0
|
719 awMsg.setSource((String)awMsg.getSource()+AwarenessMessage.USERNAMES_SEPARATOR+oldName);
|
f@0
|
720 }
|
f@0
|
721
|
f@0
|
722 /* update the local GUI to make the local user aware of the actions */
|
f@0
|
723 DisplayFilter filter = DisplayFilter.getInstance();
|
f@0
|
724 if(channel != localChannel && filter != null){
|
f@0
|
725 if(awMsg.getName() == AwarenessMessage.Name.USERNAME_A){
|
f@0
|
726 awarenessPanelEditor.replaceUserName(awMsg.getDiagram(), (String)awMsg.getSource());
|
f@0
|
727 }else{
|
f@0
|
728 DiagramEventActionSource processedSource = broadcastFilter.process((DiagramEventActionSource)awMsg.getSource()); // changes according to configuration;
|
f@0
|
729 if(filter.configurationHasChanged()){
|
f@0
|
730 for(Diagram d : diagramChannelAllocations.keySet())
|
f@0
|
731 awarenessPanelEditor.clearRecords(d.getName());
|
f@0
|
732 }
|
f@0
|
733
|
f@0
|
734 /* select and unselect are announced and written (temporary) on the panel, regardless START_A and STOP_A */
|
f@0
|
735 if(processedSource.getCmd() == Command.Name.SELECT_NODE_FOR_EDGE_CREATION || processedSource.getCmd() == Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION){
|
f@0
|
736 awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), filter.processForText(processedSource));
|
f@0
|
737 NarratorFactory.getInstance().speakWholeText(filter.processForSpeech(processedSource), Narrator.SECOND_VOICE);
|
f@0
|
738 }else if(awMsg.getName() == AwarenessMessage.Name.START_A){
|
f@0
|
739 awarenessPanelEditor.addRecord(awMsg.getDiagram(), filter.processForText(processedSource));
|
f@0
|
740 /* announce the just received awareness message via the second voice */
|
f@0
|
741 NarratorFactory.getInstance().speakWholeText(filter.processForSpeech(processedSource), Narrator.SECOND_VOICE);
|
f@0
|
742 }else{ // STOP_A
|
f@0
|
743 /* selection is a timedRecord, therefore no need to remove the record on STOP_A */
|
f@0
|
744 if(processedSource.getCmd() != Command.Name.SELECT_NODE_FOR_EDGE_CREATION && processedSource.getCmd() != Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION)
|
f@0
|
745 awarenessPanelEditor.removeRecord(awMsg.getDiagram(), filter.processForText(processedSource));
|
f@0
|
746 }
|
f@0
|
747 }
|
f@0
|
748 }
|
f@0
|
749
|
f@0
|
750 /* broadcast the awareness message to all the clients but the local and *
|
f@0
|
751 * one which sent it, to inform them the action has started */
|
f@0
|
752 for(UserNameSocketChannel sc : diagramChannelAllocations.get(diagram)){
|
f@0
|
753 if(sc.channel != localChannel && !sc.channel.equals(channel))
|
f@0
|
754 protocol.send(sc.channel, awMsg);
|
f@0
|
755 }
|
f@0
|
756 }
|
f@0
|
757
|
f@0
|
758 Map<String, Queue<DiagramElement>> getLocalhostMap() {
|
f@0
|
759 return localhostDiagramElementQueue;
|
f@0
|
760 }
|
f@0
|
761
|
f@0
|
762 private void replyLockMessage(SocketChannel channel, String diagramName,boolean yes) throws IOException{
|
f@0
|
763 protocol.send(channel, new LockMessage(
|
f@0
|
764 yes ? LockMessage.Name.YES_L : LockMessage.Name.NO_L,
|
f@0
|
765 diagramName,
|
f@0
|
766 -1,
|
f@0
|
767 DiagramEventActionSource.NULL
|
f@0
|
768 ));
|
f@0
|
769 }
|
f@0
|
770
|
f@0
|
771 private DiagramEventActionSource removeEventActionSource(SocketChannel channel, long saveID, Diagram diagram){
|
f@0
|
772 Set<UserNameSocketChannel> userNames = diagramChannelAllocations.get(diagram);
|
f@0
|
773 if(userNames != null){
|
f@0
|
774 for(UserNameSocketChannel userName : userNames){
|
f@0
|
775 if(userName.channel.equals(channel)){
|
f@0
|
776 for(DiagramEventActionSource s : userName.lockAwarenessSources){
|
f@0
|
777 if(s.getSaveID() == saveID){
|
f@0
|
778 userName.lockAwarenessSources.remove(s);
|
f@0
|
779 return s;
|
f@0
|
780 }
|
f@0
|
781 }
|
f@0
|
782 }
|
f@0
|
783 }
|
f@0
|
784 }
|
f@0
|
785 return null;
|
f@0
|
786 }
|
f@0
|
787
|
f@0
|
788 private Set<Integer> indexes;
|
f@0
|
789 /* the String key is the name of diagram, this collection is shared with the class Server *
|
f@0
|
790 * and it's used to retrieve the diagrams out of commands */
|
f@0
|
791 private Map<String,Diagram> diagrams;
|
f@0
|
792 /* unique localChannel for all the digrams */
|
f@0
|
793 private SocketChannel localChannel;
|
f@0
|
794 /* this map contains all the channels bound to a diagram, so if a change *
|
f@0
|
795 * is made to a diagram all its channels are broadcasted through this map */
|
f@0
|
796 private Map<Diagram,Set<UserNameSocketChannel>> diagramChannelAllocations;
|
f@0
|
797 /* this map is used to pass the reference to elements created by the local client *
|
f@0
|
798 * (we don't create a new object as well as we do for the nodes created by remote clients */
|
f@0
|
799 private Map<String, Queue<DiagramElement>> localhostDiagramElementQueue;
|
f@0
|
800 private Protocol protocol;
|
f@0
|
801 private ServerLockManager lockManager;
|
f@0
|
802 private BroadcastFilter broadcastFilter;
|
f@0
|
803 private AwarenessPanelEditor awarenessPanelEditor;
|
f@0
|
804
|
f@0
|
805 /* this class holds for each socketChannel the username associated to it
|
f@0
|
806 * and the last received awareness message source,*/
|
f@0
|
807 private static class UserNameSocketChannel {
|
f@0
|
808 UserNameSocketChannel(SocketChannel channel){
|
f@0
|
809 this(channel,"");
|
f@0
|
810 }
|
f@0
|
811
|
f@0
|
812 UserNameSocketChannel(SocketChannel channel, String userName){
|
f@0
|
813 this.channel = channel;
|
f@0
|
814 this.userName = userName;
|
f@0
|
815 lockAwarenessSources = new LinkedList<DiagramEventActionSource>();
|
f@0
|
816 }
|
f@0
|
817
|
f@0
|
818 SocketChannel channel;
|
f@0
|
819 String userName;
|
f@0
|
820 List<DiagramEventActionSource> lockAwarenessSources;
|
f@0
|
821 }
|
f@0
|
822 }
|