f@0: /* f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool f@0: f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: f@0: package uk.ac.qmul.eecs.ccmi.network; f@0: f@0: import java.nio.channels.SocketChannel; f@0: import java.util.Enumeration; f@0: import java.util.HashMap; f@0: import java.util.Iterator; f@0: import java.util.LinkedList; f@0: import java.util.List; f@0: import java.util.Map; f@0: f@0: import javax.swing.tree.TreeNode; f@0: f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceHolderMutableTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.gui.Edge; f@0: import uk.ac.qmul.eecs.ccmi.gui.Lock; f@0: import uk.ac.qmul.eecs.ccmi.gui.Node; f@0: f@0: /** f@0: * f@0: * This class keeps track of the objects currently locked by the users. f@0: * Locking is done by inserting a lock entry into a list of entries. All lock types have one or more dependencies. f@0: * A dependency is a lock on the same or another tree node, which prevent the lock from being acquired. f@0: * The dependency lock can also be of another type of the lock we're trying to acquire. f@0: * For example if I want to change the arrow head of edge E, before inserting a arrow-head lock in the list f@0: * I must be sure nobody else has already entered a delete-lock for E, as it means that user is about to f@0: * delete the edge I want to change the arrow of. Likewise if I want to delete a node I must be sure f@0: * nobody else inserted a note-lock on a edge reference laying within that node in the tree as that edge reference f@0: * will go after the node is deleted. If a user tries to acquire a lock and one or more dependencies f@0: * have been already inserted in the list by other users then the lock won't be granted. I no dependency f@0: * exist in the list the lock entry is created and inserted. f@0: * f@0: * f@0: * Lock dependencies (unless differently specified the dependency lock is on the same tree node): f@0: * DELETE : DELETE,ARROW_HEAD,END_LABEL,MOVE,MUST_EXIST,NAME,NOTES, BOOKMARK,PROPERTIES, f@0: * DELETE on two ended edges if this is a node, NOTES and BOOKMARK on those tree node f@0: * that will be deleted as a result of this action. f@0: * EDGE_END : EDGE_END, DELETE f@0: * MOVE : MOVE, DELETE f@0: * MUST_EXIST : DELETE f@0: * NAME : NAME, DELETE f@0: * NOTES : NOTES, DELETE, DELETE on Objects whose deletion would entail the deletion of this tree node f@0: * PROPERTIES if this is a subtree of a property node. f@0: * BOOKMARK : DELETE f@0: * PROPERTIES : PROPERTIES, DELETE f@0: * f@0: * f@0: */ f@0: class ServerLockManager { f@0: public ServerLockManager(){ f@0: locksMap = new HashMap>(); f@0: } f@0: f@0: /* check if the specified lock is present in the lock list for the specified tree node */ f@0: private boolean lockExists(DiagramTreeNode treeNode, Lock lock, List locks, SocketChannel channel){ f@0: for(LockEntry lockEntry : locks){ f@0: if(lockEntry.treeNode.equals(treeNode) && lockEntry.lock == lock && !lockEntry.channel.equals(channel)) f@0: return true; f@0: } f@0: return false; f@0: } f@0: f@0: /* check if either specified locks is present in the lock list for the specified tree node */ f@0: private boolean lockExists(DiagramTreeNode treeNode, Lock lock1, Lock lock2, List locks, SocketChannel channel){ f@0: for(LockEntry lockEntry : locks){ f@0: if(lockEntry.treeNode.equals(treeNode) && (lockEntry.lock == lock1 || lockEntry.lock == lock2) && !lockEntry.channel.equals(channel) ) f@0: return true; f@0: } f@0: return false; f@0: } f@0: f@0: /* Check whether the lock can be granted as it does */ f@0: /* not clash with other locks owned by other clients */ f@0: private boolean checkLockDependencies(DiagramTreeNode treeNode, Lock lock, SocketChannel channel, List locks){ f@0: /* bookmarks are not shared, we only check them against delete-lock, as editing a */ f@0: /* bookmark on a tree node that has just been would lead to an inconsistent state */ f@0: if(lock != Lock.BOOKMARK) f@0: for(LockEntry lockEntry : locks){ f@0: /* if the two elements are different, there is no possible clash. Go ahead */ f@0: if(!lockEntry.treeNode.equals(treeNode)) f@0: continue; f@0: /* locks are reentrant, that is if a client requests a lock he's already got * f@0: * then he's automatically granted for that lock */ f@0: if(lockEntry.channel.equals(channel)){ f@0: if(lock.equals(lockEntry.lock)){ f@0: return true; f@0: } f@0: continue; //if the clients has ad different lock then just go ahead f@0: } f@0: /* DELETE depends on all the other locks and all the other locks depend on it */ f@0: /* (bear in mind that at this point of the code lockEntry.treeNode == treeNode) */ f@0: if(lock == Lock.DELETE||lockEntry.lock == Lock.DELETE) f@0: return false; f@0: /* unless the lock is of type MUST_EXIST, if someone else has the desired lock, then the client */ f@0: /* cannot get it that is, only MUST_EXIST can be shared by different clients at the same time */ f@0: if(lock != Lock.MUST_EXIST && lock == lockEntry.lock) f@0: return false; f@0: } f@0: f@0: /* delete-locks on nodes and edges involved with treeNode will prevent from acquiring the lock as treeNode might */ f@0: /* no longer exist after the editing. See the following comments to figure out what "involved" means in this context */ f@0: if(lock == Lock.NOTES || lock == Lock.BOOKMARK){ f@0: /* no diagram element along the path from root to this treeNode must be delete-locked or properties-locked */ f@0: /* (as editing a property might mean delete some tree nodes and, maybe, this tree node) */ f@0: for(TreeNode tn : treeNode.getPath()){ f@0: if(tn instanceof DiagramElement) f@0: if(lockExists((DiagramElement)tn,Lock.DELETE,Lock.PROPERTIES,locks,channel)) f@0: return false; f@0: } f@0: /* if note-locking a reference tree node, the referred diagram element must not be delete-locked */ f@0: /* as the reference will go as well after the diagram element will be deleted */ f@0: if(treeNode instanceof NodeReferenceMutableTreeNode){ f@0: NodeReferenceMutableTreeNode referenceNode = (NodeReferenceMutableTreeNode)treeNode; f@0: if(lockExists(referenceNode.getNode(),Lock.DELETE,locks,channel)) f@0: return false; f@0: } f@0: if(treeNode instanceof EdgeReferenceMutableTreeNode){ f@0: EdgeReferenceMutableTreeNode referenceNode = (EdgeReferenceMutableTreeNode)treeNode; f@0: if(lockExists(referenceNode.getEdge(),Lock.DELETE,locks,channel)) f@0: return false; f@0: } f@0: /* if note locking an edge reference tree holder which has only one child, we cannot grant * f@0: * the lock if the referred edge is delete-locked as the holder will be deleted as well after * f@0: * the eventual deletion of the edge */ f@0: if(treeNode instanceof EdgeReferenceHolderMutableTreeNode && treeNode.getChildCount() == 1){ f@0: EdgeReferenceMutableTreeNode referenceNode = (EdgeReferenceMutableTreeNode)treeNode.getChildAt(0); f@0: if(lockExists(referenceNode.getEdge(),Lock.DELETE,locks,channel)) f@0: return false; f@0: } f@0: } f@0: f@0: if(lock == Lock.DELETE){ f@0: /* all the descendants of the element must be non notes-locked or bookmark-locked */ f@0: for(@SuppressWarnings("rawtypes") f@0: Enumeration enumeration = treeNode.breadthFirstEnumeration(); enumeration.hasMoreElements();){ f@0: if(lockExists((DiagramTreeNode)enumeration.nextElement(),Lock.NOTES,Lock.BOOKMARK,locks,channel)) f@0: return false; f@0: } f@0: f@0: if(treeNode instanceof Node){ f@0: Node n = (Node)treeNode; f@0: /* if we want to delete a Node we must get the lock on each attached * f@0: * edge as they will be deleted as well */ f@0: for(int i =0; i< n.getEdgesNum();i++){ f@0: Edge e = n.getEdgeAt(i); f@0: if(lockExists(e,Lock.DELETE,locks,channel)) f@0: return false; f@0: /* In order to delete-lock a Node, no referee must be bookmark/notes-locked. The referees * f@0: * are the NodeReferenceTreeNode's pointing to this Node */ f@0: for(int j=0;j locks = locksMap.get(diagramName); f@0: if(locks == null){ f@0: /* if no object in the diagram has ever been locked */ f@0: /* there is no entry in the map and one must be created */ f@0: locks = new LinkedList(); f@0: locksMap.put(diagramName,locks); f@0: } f@0: /* deleting a node will cause all the attached two-ended edges to * f@0: * be deleted, therefore we need to lock all those edges too, before */ f@0: if(lock == Lock.DELETE && treeNode instanceof Node){ f@0: Node n = (Node)treeNode; f@0: for(int i=0; i 2) f@0: continue; f@0: boolean succeeded = requestLock(n.getEdgeAt(i),Lock.DELETE,channel,diagramName); f@0: if(!succeeded){ f@0: /* release the previously acquired locks and return a failure */ f@0: for(int j=0;j locks = locksMap.get(diagramName); f@0: Iterator iterator = locks.iterator(); f@0: boolean lockReleased = false; f@0: while(iterator.hasNext()){ f@0: LockEntry entry = iterator.next(); f@0: if(entry.treeNode.equals(treeNode) && entry.lock == lock && entry.channel == channel){ f@0: iterator.remove(); f@0: lockReleased = true; f@0: if(lock == Lock.DELETE && treeNode instanceof Node) f@0: continue; // we have to check for attached edges which must be unlocked too f@0: else f@0: break;// if ain't a delete lock, we found what looking for and we can stop f@0: } f@0: /* if a delete lock, we have to check for attached edges which must be unlocked too */ f@0: if(lock == Lock.DELETE && entry.treeNode instanceof Edge && treeNode instanceof Node){ f@0: Edge e = (Edge)entry.treeNode; f@0: if(e.getNodesNum() == 2){ f@0: if(e.getNodeAt(0).equals(treeNode) || e.getNodeAt(1).equals(treeNode)){ f@0: iterator.remove(); f@0: } f@0: } f@0: } f@0: } f@0: // System.out.println("lock release:"+lockStatusDescription(diagramName)+"\n----"); f@0: return lockReleased; f@0: } f@0: f@0: /** f@0: * Removes all the locks related to a diagram element, to be called when a diagram element is deleted. f@0: * If the diagram element is a Node all the locks of the two-ended edges attached to it will be f@0: * removed as well. f@0: * @param element the diagram element whose locks must be removed f@0: * @param diagramName the diagram the element has to be removed from f@0: */ f@0: public void removeLocks(DiagramElement element, String diagramName){ f@0: List locks = locksMap.get(diagramName); f@0: if(locks == null) f@0: return; f@0: Iterator iterator = locks.iterator(); f@0: boolean isNode = (element instanceof Node); f@0: while(iterator.hasNext()){ f@0: LockEntry entry = iterator.next(); f@0: if(entry.treeNode.equals(element)){ f@0: iterator.remove(); f@0: } f@0: /* remove the lock id it's a two ended edges locks attached to this Node */ f@0: if(isNode && entry.treeNode instanceof Edge){ f@0: Edge e = (Edge)entry.treeNode; f@0: if(e.getNodesNum() > 2) f@0: continue; f@0: if(e.getNodeAt(0).equals(element) || e.getNodeAt(1).equals(element)){ f@0: iterator.remove(); f@0: } f@0: } f@0: } f@0: } f@0: f@0: /** f@0: * remove all the locks acquired by a client, this is normally called when a client disconnects f@0: * @param channel the channel uniquely identifying the client f@0: * @param diagramName the name of the diagram the channel is related to f@0: */ f@0: public void removeLocks(SocketChannel channel, String diagramName){ f@0: if(!locksMap.containsKey(diagramName)) f@0: /* this can happen if a client connects and downloads the diagram list * f@0: * and then disconnects without opening any diagram */ f@0: return; f@0: Iterator itr = locksMap.get(diagramName).iterator(); f@0: while(itr.hasNext()) f@0: if(itr.next().channel.equals(channel)) f@0: itr.remove(); f@0: } f@0: f@0: public String lockStatusDescription(String diagramName){ f@0: StringBuilder builder = new StringBuilder(); f@0: if(locksMap.containsKey(diagramName)){ f@0: List locks = locksMap.get(diagramName); f@0: builder.append(diagramName).append('\n'); f@0: for(LockEntry entry : locks){ f@0: builder.append(entry.channel.socket().getInetAddress().getHostAddress()).append(' '). f@0: append(entry.lock).append(' '). f@0: append(entry.treeNode.getName()).append('\n'); f@0: } f@0: } f@0: return builder.toString(); f@0: } f@0: f@0: public void clearAllLocks(){ // should not be used unless in a debugging session f@0: locksMap = new HashMap>(); f@0: } f@0: f@0: private Map> locksMap; f@0: f@0: private static class LockEntry { f@0: public LockEntry(DiagramTreeNode treeNode,Lock lock,SocketChannel channel) { f@0: this.channel = channel; f@0: this.lock = lock; f@0: this.treeNode = treeNode; f@0: } f@0: f@0: public SocketChannel channel; f@0: public Lock lock; f@0: public DiagramTreeNode treeNode; f@0: } f@0: }