annotate java/src/uk/ac/qmul/eecs/ccmi/network/ServerLockManager.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
rev   line source
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.nio.channels.SocketChannel;
f@0 23 import java.util.Enumeration;
f@0 24 import java.util.HashMap;
f@0 25 import java.util.Iterator;
f@0 26 import java.util.LinkedList;
f@0 27 import java.util.List;
f@0 28 import java.util.Map;
f@0 29
f@0 30 import javax.swing.tree.TreeNode;
f@0 31
f@0 32 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
f@0 33 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
f@0 34 import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceHolderMutableTreeNode;
f@0 35 import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode;
f@0 36 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode;
f@0 37 import uk.ac.qmul.eecs.ccmi.gui.Edge;
f@0 38 import uk.ac.qmul.eecs.ccmi.gui.Lock;
f@0 39 import uk.ac.qmul.eecs.ccmi.gui.Node;
f@0 40
f@0 41 /**
f@0 42 *
f@0 43 * This class keeps track of the objects currently locked by the users.
f@0 44 * Locking is done by inserting a lock entry into a list of entries. All lock types have one or more dependencies.
f@0 45 * A dependency is a lock on the same or another tree node, which prevent the lock from being acquired.
f@0 46 * The dependency lock can also be of another type of the lock we're trying to acquire.
f@0 47 * For example if I want to change the arrow head of edge E, before inserting a arrow-head lock in the list
f@0 48 * I must be sure nobody else has already entered a delete-lock for E, as it means that user is about to
f@0 49 * delete the edge I want to change the arrow of. Likewise if I want to delete a node I must be sure
f@0 50 * nobody else inserted a note-lock on a edge reference laying within that node in the tree as that edge reference
f@0 51 * will go after the node is deleted. If a user tries to acquire a lock and one or more dependencies
f@0 52 * have been already inserted in the list by other users then the lock won't be granted. I no dependency
f@0 53 * exist in the list the lock entry is created and inserted.
f@0 54 *
f@0 55 *
f@0 56 * Lock dependencies (unless differently specified the dependency lock is on the same tree node):
f@0 57 * DELETE : DELETE,ARROW_HEAD,END_LABEL,MOVE,MUST_EXIST,NAME,NOTES, BOOKMARK,PROPERTIES,
f@0 58 * DELETE on two ended edges if this is a node, NOTES and BOOKMARK on those tree node
f@0 59 * that will be deleted as a result of this action.
f@0 60 * EDGE_END : EDGE_END, DELETE
f@0 61 * MOVE : MOVE, DELETE
f@0 62 * MUST_EXIST : DELETE
f@0 63 * NAME : NAME, DELETE
f@0 64 * NOTES : NOTES, DELETE, DELETE on Objects whose deletion would entail the deletion of this tree node
f@0 65 * PROPERTIES if this is a subtree of a property node.
f@0 66 * BOOKMARK : DELETE
f@0 67 * PROPERTIES : PROPERTIES, DELETE
f@0 68 *
f@0 69 *
f@0 70 */
f@0 71 class ServerLockManager {
f@0 72 public ServerLockManager(){
f@0 73 locksMap = new HashMap<String,List<LockEntry>>();
f@0 74 }
f@0 75
f@0 76 /* check if the specified lock is present in the lock list for the specified tree node */
f@0 77 private boolean lockExists(DiagramTreeNode treeNode, Lock lock, List<LockEntry> locks, SocketChannel channel){
f@0 78 for(LockEntry lockEntry : locks){
f@0 79 if(lockEntry.treeNode.equals(treeNode) && lockEntry.lock == lock && !lockEntry.channel.equals(channel))
f@0 80 return true;
f@0 81 }
f@0 82 return false;
f@0 83 }
f@0 84
f@0 85 /* check if either specified locks is present in the lock list for the specified tree node */
f@0 86 private boolean lockExists(DiagramTreeNode treeNode, Lock lock1, Lock lock2, List<LockEntry> locks, SocketChannel channel){
f@0 87 for(LockEntry lockEntry : locks){
f@0 88 if(lockEntry.treeNode.equals(treeNode) && (lockEntry.lock == lock1 || lockEntry.lock == lock2) && !lockEntry.channel.equals(channel) )
f@0 89 return true;
f@0 90 }
f@0 91 return false;
f@0 92 }
f@0 93
f@0 94 /* Check whether the lock can be granted as it does */
f@0 95 /* not clash with other locks owned by other clients */
f@0 96 private boolean checkLockDependencies(DiagramTreeNode treeNode, Lock lock, SocketChannel channel, List<LockEntry> locks){
f@0 97 /* bookmarks are not shared, we only check them against delete-lock, as editing a */
f@0 98 /* bookmark on a tree node that has just been would lead to an inconsistent state */
f@0 99 if(lock != Lock.BOOKMARK)
f@0 100 for(LockEntry lockEntry : locks){
f@0 101 /* if the two elements are different, there is no possible clash. Go ahead */
f@0 102 if(!lockEntry.treeNode.equals(treeNode))
f@0 103 continue;
f@0 104 /* locks are reentrant, that is if a client requests a lock he's already got *
f@0 105 * then he's automatically granted for that lock */
f@0 106 if(lockEntry.channel.equals(channel)){
f@0 107 if(lock.equals(lockEntry.lock)){
f@0 108 return true;
f@0 109 }
f@0 110 continue; //if the clients has ad different lock then just go ahead
f@0 111 }
f@0 112 /* DELETE depends on all the other locks and all the other locks depend on it */
f@0 113 /* (bear in mind that at this point of the code lockEntry.treeNode == treeNode) */
f@0 114 if(lock == Lock.DELETE||lockEntry.lock == Lock.DELETE)
f@0 115 return false;
f@0 116 /* unless the lock is of type MUST_EXIST, if someone else has the desired lock, then the client */
f@0 117 /* cannot get it that is, only MUST_EXIST can be shared by different clients at the same time */
f@0 118 if(lock != Lock.MUST_EXIST && lock == lockEntry.lock)
f@0 119 return false;
f@0 120 }
f@0 121
f@0 122 /* delete-locks on nodes and edges involved with treeNode will prevent from acquiring the lock as treeNode might */
f@0 123 /* no longer exist after the editing. See the following comments to figure out what "involved" means in this context */
f@0 124 if(lock == Lock.NOTES || lock == Lock.BOOKMARK){
f@0 125 /* no diagram element along the path from root to this treeNode must be delete-locked or properties-locked */
f@0 126 /* (as editing a property might mean delete some tree nodes and, maybe, this tree node) */
f@0 127 for(TreeNode tn : treeNode.getPath()){
f@0 128 if(tn instanceof DiagramElement)
f@0 129 if(lockExists((DiagramElement)tn,Lock.DELETE,Lock.PROPERTIES,locks,channel))
f@0 130 return false;
f@0 131 }
f@0 132 /* if note-locking a reference tree node, the referred diagram element must not be delete-locked */
f@0 133 /* as the reference will go as well after the diagram element will be deleted */
f@0 134 if(treeNode instanceof NodeReferenceMutableTreeNode){
f@0 135 NodeReferenceMutableTreeNode referenceNode = (NodeReferenceMutableTreeNode)treeNode;
f@0 136 if(lockExists(referenceNode.getNode(),Lock.DELETE,locks,channel))
f@0 137 return false;
f@0 138 }
f@0 139 if(treeNode instanceof EdgeReferenceMutableTreeNode){
f@0 140 EdgeReferenceMutableTreeNode referenceNode = (EdgeReferenceMutableTreeNode)treeNode;
f@0 141 if(lockExists(referenceNode.getEdge(),Lock.DELETE,locks,channel))
f@0 142 return false;
f@0 143 }
f@0 144 /* if note locking an edge reference tree holder which has only one child, we cannot grant *
f@0 145 * the lock if the referred edge is delete-locked as the holder will be deleted as well after *
f@0 146 * the eventual deletion of the edge */
f@0 147 if(treeNode instanceof EdgeReferenceHolderMutableTreeNode && treeNode.getChildCount() == 1){
f@0 148 EdgeReferenceMutableTreeNode referenceNode = (EdgeReferenceMutableTreeNode)treeNode.getChildAt(0);
f@0 149 if(lockExists(referenceNode.getEdge(),Lock.DELETE,locks,channel))
f@0 150 return false;
f@0 151 }
f@0 152 }
f@0 153
f@0 154 if(lock == Lock.DELETE){
f@0 155 /* all the descendants of the element must be non notes-locked or bookmark-locked */
f@0 156 for(@SuppressWarnings("rawtypes")
f@0 157 Enumeration enumeration = treeNode.breadthFirstEnumeration(); enumeration.hasMoreElements();){
f@0 158 if(lockExists((DiagramTreeNode)enumeration.nextElement(),Lock.NOTES,Lock.BOOKMARK,locks,channel))
f@0 159 return false;
f@0 160 }
f@0 161
f@0 162 if(treeNode instanceof Node){
f@0 163 Node n = (Node)treeNode;
f@0 164 /* if we want to delete a Node we must get the lock on each attached *
f@0 165 * edge as they will be deleted as well */
f@0 166 for(int i =0; i< n.getEdgesNum();i++){
f@0 167 Edge e = n.getEdgeAt(i);
f@0 168 if(lockExists(e,Lock.DELETE,locks,channel))
f@0 169 return false;
f@0 170 /* In order to delete-lock a Node, no referee must be bookmark/notes-locked. The referees *
f@0 171 * are the NodeReferenceTreeNode's pointing to this Node */
f@0 172 for(int j=0;j<e.getChildCount();j++){
f@0 173 NodeReferenceMutableTreeNode nodeRef = (NodeReferenceMutableTreeNode)e.getChildAt(j);
f@0 174 if(nodeRef.getNode().equals(n))
f@0 175 if(lockExists(nodeRef,Lock.NOTES,Lock.BOOKMARK,locks,channel))
f@0 176 return false;
f@0 177 }
f@0 178 }
f@0 179 }
f@0 180
f@0 181 if(treeNode instanceof Edge){
f@0 182 /* for each node check whether the reference to this edge is notes-locked */
f@0 183 Edge e = (Edge)treeNode;
f@0 184 for(int i=0;i<e.getNodesNum();i++){
f@0 185 Node n = e.getNodeAt(i);
f@0 186 for(int j=0;j<n.getChildCount();j++){
f@0 187 if(n.getChildAt(j) instanceof EdgeReferenceHolderMutableTreeNode){
f@0 188 EdgeReferenceHolderMutableTreeNode refHolder = (EdgeReferenceHolderMutableTreeNode)n.getChildAt(j);
f@0 189 /* someone else is editing notes on the reference holder and it has only one child */
f@0 190 /* which means it will be deleted after the edge deletion, the lock cannot be granted then */
f@0 191 if((refHolder.getChildCount() == 1) && lockExists(refHolder,Lock.NOTES,Lock.BOOKMARK,locks,channel))
f@0 192 return false;
f@0 193 /* if a reference tree node pointing to this edge is notes-locked, the edge can't be deleted */
f@0 194 for(int k=0;k<refHolder.getChildCount();k++){
f@0 195 EdgeReferenceMutableTreeNode edgeRef = (EdgeReferenceMutableTreeNode)refHolder.getChildAt(k);
f@0 196 if(lockExists(edgeRef,Lock.NOTES,Lock.BOOKMARK,locks,channel))
f@0 197 return false;
f@0 198 }
f@0 199 }
f@0 200 }
f@0 201 }
f@0 202 }
f@0 203 }
f@0 204
f@0 205 /* all the checks have been passed, the client definitely deserves the lock now */
f@0 206 return true;
f@0 207 }
f@0 208
f@0 209 /**
f@0 210 * Request an editing lock for a tree node
f@0 211 *
f@0 212 * @param treeNode the treeNode the caller is trying to lock
f@0 213 * @param lock the type of lock requested
f@0 214 * @param channel the channel works as a unique identifier for the clients
f@0 215 * @param diagramName the name of the diagram the lock is requested on
f@0 216 * @return true if the lock is successfully granted, or false otherwise (because of another client
f@0 217 * holding a lock which clashes with this request)
f@0 218 */
f@0 219 public boolean requestLock(DiagramTreeNode treeNode, Lock lock, SocketChannel channel,String diagramName){
f@0 220 // System.out.println("lock before request:"+lockStatusDescription(diagramName)+"\n----");
f@0 221 List<LockEntry> locks = locksMap.get(diagramName);
f@0 222 if(locks == null){
f@0 223 /* if no object in the diagram has ever been locked */
f@0 224 /* there is no entry in the map and one must be created */
f@0 225 locks = new LinkedList<LockEntry>();
f@0 226 locksMap.put(diagramName,locks);
f@0 227 }
f@0 228 /* deleting a node will cause all the attached two-ended edges to *
f@0 229 * be deleted, therefore we need to lock all those edges too, before */
f@0 230 if(lock == Lock.DELETE && treeNode instanceof Node){
f@0 231 Node n = (Node)treeNode;
f@0 232 for(int i=0; i<n.getEdgesNum();i++){
f@0 233 if(n.getEdgeAt(i).getNodesNum() > 2)
f@0 234 continue;
f@0 235 boolean succeeded = requestLock(n.getEdgeAt(i),Lock.DELETE,channel,diagramName);
f@0 236 if(!succeeded){
f@0 237 /* release the previously acquired locks and return a failure */
f@0 238 for(int j=0;j<i;j++){
f@0 239 if(n.getEdgeAt(j).getNodesNum() == 2)
f@0 240 releaseLock(n.getEdgeAt(j),Lock.DELETE,channel,diagramName);
f@0 241 }
f@0 242 return false;
f@0 243 }
f@0 244 }
f@0 245 }
f@0 246
f@0 247 if(!checkLockDependencies(treeNode,lock,channel,locks)){
f@0 248 if(lock == Lock.DELETE && treeNode instanceof Node){
f@0 249 Node n = (Node)treeNode;
f@0 250 for(int j=0;j<n.getEdgesNum();j++){
f@0 251 if(n.getEdgeAt(j).getNodesNum() == 2)
f@0 252 releaseLock(n.getEdgeAt(j),Lock.DELETE,channel,diagramName);
f@0 253 }
f@0 254 }
f@0 255 return false;
f@0 256 }
f@0 257
f@0 258 /* adds the lock only if it doesn't already exist */
f@0 259 boolean add = true;
f@0 260 for(LockEntry l : locks){
f@0 261 if(l.channel.equals(channel) && l.lock.equals(lock) && l.treeNode.equals(treeNode)){
f@0 262 add = false;
f@0 263 break;
f@0 264 }
f@0 265 }
f@0 266 if(add)
f@0 267 locks.add(new LockEntry(treeNode,lock,channel));
f@0 268 // System.out.println("lock after request:"+lockStatusDescription(diagramName)+"\n----");
f@0 269 return true;
f@0 270 }
f@0 271 /**
f@0 272 * Release a lock previously acquired
f@0 273 * @param treeNode the tree node, whose lock is getting released
f@0 274 * @param lock the lock type
f@0 275 * @param channel the channel of the client releasing the lock
f@0 276 * @param diagramName the diagram whose tree node is affected by this call
f@0 277 *
f@0 278 * @return true if a lock was really yielded as a result of the call
f@0 279 */
f@0 280 public boolean releaseLock(DiagramTreeNode treeNode, Lock lock, SocketChannel channel, String diagramName){
f@0 281 List<LockEntry> locks = locksMap.get(diagramName);
f@0 282 Iterator<LockEntry> iterator = locks.iterator();
f@0 283 boolean lockReleased = false;
f@0 284 while(iterator.hasNext()){
f@0 285 LockEntry entry = iterator.next();
f@0 286 if(entry.treeNode.equals(treeNode) && entry.lock == lock && entry.channel == channel){
f@0 287 iterator.remove();
f@0 288 lockReleased = true;
f@0 289 if(lock == Lock.DELETE && treeNode instanceof Node)
f@0 290 continue; // we have to check for attached edges which must be unlocked too
f@0 291 else
f@0 292 break;// if ain't a delete lock, we found what looking for and we can stop
f@0 293 }
f@0 294 /* if a delete lock, we have to check for attached edges which must be unlocked too */
f@0 295 if(lock == Lock.DELETE && entry.treeNode instanceof Edge && treeNode instanceof Node){
f@0 296 Edge e = (Edge)entry.treeNode;
f@0 297 if(e.getNodesNum() == 2){
f@0 298 if(e.getNodeAt(0).equals(treeNode) || e.getNodeAt(1).equals(treeNode)){
f@0 299 iterator.remove();
f@0 300 }
f@0 301 }
f@0 302 }
f@0 303 }
f@0 304 // System.out.println("lock release:"+lockStatusDescription(diagramName)+"\n----");
f@0 305 return lockReleased;
f@0 306 }
f@0 307
f@0 308 /**
f@0 309 * Removes all the locks related to a diagram element, to be called when a diagram element is deleted.
f@0 310 * If the diagram element is a Node all the locks of the two-ended edges attached to it will be
f@0 311 * removed as well.
f@0 312 * @param element the diagram element whose locks must be removed
f@0 313 * @param diagramName the diagram the element has to be removed from
f@0 314 */
f@0 315 public void removeLocks(DiagramElement element, String diagramName){
f@0 316 List<LockEntry> locks = locksMap.get(diagramName);
f@0 317 if(locks == null)
f@0 318 return;
f@0 319 Iterator<LockEntry> iterator = locks.iterator();
f@0 320 boolean isNode = (element instanceof Node);
f@0 321 while(iterator.hasNext()){
f@0 322 LockEntry entry = iterator.next();
f@0 323 if(entry.treeNode.equals(element)){
f@0 324 iterator.remove();
f@0 325 }
f@0 326 /* remove the lock id it's a two ended edges locks attached to this Node */
f@0 327 if(isNode && entry.treeNode instanceof Edge){
f@0 328 Edge e = (Edge)entry.treeNode;
f@0 329 if(e.getNodesNum() > 2)
f@0 330 continue;
f@0 331 if(e.getNodeAt(0).equals(element) || e.getNodeAt(1).equals(element)){
f@0 332 iterator.remove();
f@0 333 }
f@0 334 }
f@0 335 }
f@0 336 }
f@0 337
f@0 338 /**
f@0 339 * remove all the locks acquired by a client, this is normally called when a client disconnects
f@0 340 * @param channel the channel uniquely identifying the client
f@0 341 * @param diagramName the name of the diagram the channel is related to
f@0 342 */
f@0 343 public void removeLocks(SocketChannel channel, String diagramName){
f@0 344 if(!locksMap.containsKey(diagramName))
f@0 345 /* this can happen if a client connects and downloads the diagram list *
f@0 346 * and then disconnects without opening any diagram */
f@0 347 return;
f@0 348 Iterator<LockEntry> itr = locksMap.get(diagramName).iterator();
f@0 349 while(itr.hasNext())
f@0 350 if(itr.next().channel.equals(channel))
f@0 351 itr.remove();
f@0 352 }
f@0 353
f@0 354 public String lockStatusDescription(String diagramName){
f@0 355 StringBuilder builder = new StringBuilder();
f@0 356 if(locksMap.containsKey(diagramName)){
f@0 357 List<LockEntry> locks = locksMap.get(diagramName);
f@0 358 builder.append(diagramName).append('\n');
f@0 359 for(LockEntry entry : locks){
f@0 360 builder.append(entry.channel.socket().getInetAddress().getHostAddress()).append(' ').
f@0 361 append(entry.lock).append(' ').
f@0 362 append(entry.treeNode.getName()).append('\n');
f@0 363 }
f@0 364 }
f@0 365 return builder.toString();
f@0 366 }
f@0 367
f@0 368 public void clearAllLocks(){ // should not be used unless in a debugging session
f@0 369 locksMap = new HashMap<String,List<LockEntry>>();
f@0 370 }
f@0 371
f@0 372 private Map<String,List<LockEntry>> locksMap;
f@0 373
f@0 374 private static class LockEntry {
f@0 375 public LockEntry(DiagramTreeNode treeNode,Lock lock,SocketChannel channel) {
f@0 376 this.channel = channel;
f@0 377 this.lock = lock;
f@0 378 this.treeNode = treeNode;
f@0 379 }
f@0 380
f@0 381 public SocketChannel channel;
f@0 382 public Lock lock;
f@0 383 public DiagramTreeNode treeNode;
f@0 384 }
f@0 385 }