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