components/visual-panels/core/src/java/vpanels/client/com/oracle/solaris/vp/client/common/ConnectionManager.java
changeset 3553 f1d133b09a8c
parent 3552 077ebe3d0d24
child 3554 ef58713bafc4
equal deleted inserted replaced
3552:077ebe3d0d24 3553:f1d133b09a8c
     1 /*
       
     2  * CDDL HEADER START
       
     3  *
       
     4  * The contents of this file are subject to the terms of the
       
     5  * Common Development and Distribution License (the "License").
       
     6  * You may not use this file except in compliance with the License.
       
     7  *
       
     8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
     9  * or http://www.opensolaris.org/os/licensing.
       
    10  * See the License for the specific language governing permissions
       
    11  * and limitations under the License.
       
    12  *
       
    13  * When distributing Covered Code, include this CDDL HEADER in each
       
    14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    15  * If applicable, add the following below this CDDL HEADER, with the
       
    16  * fields enclosed by brackets "[]" replaced with your own identifying
       
    17  * information: Portions Copyright [yyyy] [name of copyright owner]
       
    18  *
       
    19  * CDDL HEADER END
       
    20  */
       
    21 
       
    22 /*
       
    23  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
       
    24  */
       
    25 
       
    26 package com.oracle.solaris.vp.client.common;
       
    27 
       
    28 import java.awt.*;
       
    29 import java.util.*;
       
    30 import java.util.List;
       
    31 import javax.swing.tree.*;
       
    32 import javax.swing.WindowConstants;
       
    33 import com.oracle.solaris.vp.panel.common.*;
       
    34 import com.oracle.solaris.vp.util.misc.EnumerationIterator;
       
    35 import com.oracle.solaris.vp.util.swing.*;
       
    36 import com.oracle.solaris.vp.util.swing.tree.TypedTreeNode;
       
    37 
       
    38 /**
       
    39  * The {@code ConnectionManager} class maintains a dependency tree of {@link
       
    40  * ConnectionInfo}s and {@link ConnectionListener}s.
       
    41  * <p/>
       
    42  * Dependencies between connections could be hard (connection {@code b} actively
       
    43  * uses connection {@code a} to communicate) or soft ({@code a} was created in
       
    44  * the process of creating {@code b}, and {@code a} should be maintained while
       
    45  * {@code b} is active in case the user wants to return to {@code a} at some
       
    46  * point without having to re-authenticate).
       
    47  * <p/>
       
    48  * Each connection in the tree is managed until it has no dependants (other
       
    49  * connections or listeners), at which point it is removed from the dependency
       
    50  * tree.  If there are no other references to it outside of this class, it will
       
    51  * be garbage collected (and thus closed).
       
    52  */
       
    53 public class ConnectionManager {
       
    54     //
       
    55     // Inner classes
       
    56     //
       
    57 
       
    58     /**
       
    59      * Graphically display the dependency tree for debugging.
       
    60      */
       
    61     private class Visualizer {
       
    62 	//
       
    63 	// Instance data
       
    64 	//
       
    65 
       
    66 	private DefaultTreeModel model = new DefaultTreeModel(root);
       
    67 	private ExtTree tree = new ExtTree(model);
       
    68 
       
    69 	//
       
    70 	// Constructors
       
    71 	//
       
    72 
       
    73 	public Visualizer() {
       
    74 	    ExtFrame frame = new ExtFrame();
       
    75 	    frame.setAutoResizeEnabled(true);
       
    76 	    Container cont = frame.getContentPane();
       
    77 	    cont.setLayout(new BorderLayout());
       
    78 	    cont.add(tree, BorderLayout.CENTER);
       
    79 
       
    80 	    frame.setLocationByPlatform(true);
       
    81 	    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
       
    82 	    frame.pack();
       
    83 	    frame.setVisible(true);
       
    84 	}
       
    85 
       
    86 	//
       
    87 	// Visualizer methods
       
    88 	//
       
    89 
       
    90 	public void update() {
       
    91 	    EventQueue.invokeLater(
       
    92 		new Runnable() {
       
    93 		    @Override
       
    94 		    public void run() {
       
    95 			model.nodeStructureChanged(root);
       
    96 			tree.setExpandedRecursive(true);
       
    97 		    }
       
    98 		});
       
    99 	}
       
   100     }
       
   101 
       
   102     private class DependencyTreeNode<T> extends TypedTreeNode<T> {
       
   103 	//
       
   104 	// Constructors
       
   105 	//
       
   106 
       
   107 	public DependencyTreeNode(T userObject) {
       
   108 	    super(userObject);
       
   109 	}
       
   110 
       
   111 	//
       
   112 	// DefaultMutableTreeNode methods
       
   113 	//
       
   114 
       
   115 	@Override
       
   116 	public void add(MutableTreeNode child) {
       
   117 	    super.add(child);
       
   118 	    DependencyTreeNode node = (DependencyTreeNode)child;
       
   119 	    nodeMap.put(node.getUserObject(), node);
       
   120 	    if (visualizer != null) {
       
   121 		visualizer.update();
       
   122 	    }
       
   123 	}
       
   124 
       
   125 	@Override
       
   126 	public void remove(MutableTreeNode child) {
       
   127 	    super.remove(child);
       
   128 	    DependencyTreeNode node = (DependencyTreeNode)child;
       
   129 	    nodeMap.remove(node.getUserObject());
       
   130 	    if (visualizer != null) {
       
   131 		visualizer.update();
       
   132 	    }
       
   133 	}
       
   134 
       
   135 	//
       
   136 	// DependencyTreeNode methods
       
   137 	//
       
   138 
       
   139 	@SuppressWarnings({"unchecked"})
       
   140 	public Iterable<DependencyTreeNode> getChildren() {
       
   141 	    return new EnumerationIterator<DependencyTreeNode>(children());
       
   142 	}
       
   143     }
       
   144 
       
   145     private class ConnectionTreeNode
       
   146 	extends DependencyTreeNode<ConnectionInfo> {
       
   147 
       
   148 	//
       
   149 	// Instance data
       
   150 	//
       
   151 
       
   152 	private boolean healthy = true;
       
   153 
       
   154 	//
       
   155 	// Constructors
       
   156 	//
       
   157 
       
   158 	public ConnectionTreeNode(ConnectionInfo info) {
       
   159 	    super(info);
       
   160 	}
       
   161 
       
   162 	//
       
   163 	// Object methods
       
   164 	//
       
   165 
       
   166 	@Override
       
   167 	public String toString() {
       
   168 	    return String.format("%s: %s (%s)", getUserObject().hashCode(),
       
   169 		super.toString(), isHealthy() ? "healthy" : "unhealthy");
       
   170 	}
       
   171 
       
   172 	//
       
   173 	// ConnectionTreeNode methods
       
   174 	//
       
   175 
       
   176 	public boolean isHealthy() {
       
   177 	    return healthy;
       
   178 	}
       
   179 
       
   180 	public void setHealthy(boolean healthy) {
       
   181 	    this.healthy = healthy;
       
   182 	    if (visualizer != null) {
       
   183 		visualizer.update();
       
   184 	    }
       
   185 	}
       
   186     }
       
   187 
       
   188     private class ListenerTreeNode
       
   189 	extends DependencyTreeNode<ConnectionListener> {
       
   190 
       
   191 	//
       
   192 	// Constructors
       
   193 	//
       
   194 
       
   195 	public ListenerTreeNode(ConnectionListener listener) {
       
   196 	    super(listener);
       
   197 	}
       
   198     }
       
   199 
       
   200     //
       
   201     // Static data
       
   202     //
       
   203 
       
   204     //
       
   205     // Instance data
       
   206     //
       
   207 
       
   208     private DependencyTreeNode<?> root = new DependencyTreeNode<Object>(null);
       
   209 
       
   210     // Set to null unless debugging
       
   211     private Visualizer visualizer = null;
       
   212 
       
   213     private Map<Object, DependencyTreeNode> nodeMap =
       
   214 	new HashMap<Object, DependencyTreeNode>();
       
   215 
       
   216     private ConnectionListListeners listeners =
       
   217         new ConnectionListListeners();
       
   218 
       
   219     //
       
   220     // ConnectionManager methods
       
   221     //
       
   222 
       
   223     /**
       
   224      * Adds a dependency chain of {@link ConnectionInfo}s and a {@link
       
   225      * ConnectionListener} to the dependency tree.  The first element of {@code
       
   226      * depChain} is a dependency of {@code listener}.  Each additional element
       
   227      * is a dependency of the previous element.
       
   228      * <p/>
       
   229      * {@code listener} will be notified when the first {@code ConnectionInfo}
       
   230      * in the chain {@link ConnectionListener#connectionFailed fails} or {@link
       
   231      * ConnectionListener#connectionChanged is replaced}.
       
   232      * <p/>
       
   233      * {@code listener} and {@code depChain} are merged into the tree if any
       
   234      * element thereof already exists in the tree.
       
   235      *
       
   236      * @param	    listener
       
   237      *		    a {@link ConnectionListener} utilizing the first
       
   238      *		    {@code ConnectionInfo} in {@code depChain}
       
   239      *
       
   240      * @param	    depChain
       
   241      *		    the {@link ConnectionInfo} dependency chain
       
   242      *
       
   243      * @throws	    IllegalStateException
       
   244      *		    if the given dependency chain conflicts with the existing
       
   245      *		    dependency tree
       
   246      */
       
   247     public synchronized void add(ConnectionListener listener,
       
   248 	List<ConnectionInfo> depChain) {
       
   249 
       
   250 	if (depChain.isEmpty()) {
       
   251 	    return;
       
   252 	}
       
   253 
       
   254 	DependencyTreeNode parent = root;
       
   255 	for (int last = depChain.size() - 1, i = last; i >= 0; i--) {
       
   256 	    ConnectionInfo info = depChain.get(i);
       
   257 	    DependencyTreeNode node = getNode(info);
       
   258 
       
   259 	    if (node != null) {
       
   260                 DependencyTreeNode oldParent =
       
   261 		    (DependencyTreeNode)node.getParent();
       
   262 		if (oldParent != parent) {
       
   263 		    if (parent == root) {
       
   264 			parent = oldParent;
       
   265 		    } else if (oldParent != root) {
       
   266                         // If this connection already depends on a connection
       
   267                         // other than the next one in depChain...
       
   268 			throw new IllegalStateException(String.format(
       
   269 			    "\"%s\" should depend on \"%s\" but already " +
       
   270 			    "depends on \"%s\"", info,
       
   271 			    ((ConnectionTreeNode)parent).getUserObject(),
       
   272 			    ((ConnectionTreeNode)oldParent).getUserObject()));
       
   273 		    } else {
       
   274 			if (parent.isNodeAncestor(node)) {
       
   275 			    throw new IllegalStateException(String.format(
       
   276 				"\"%s\" should depend on \"%s\" but " +
       
   277 				"existing dependencies are reversed", info,
       
   278 				((ConnectionTreeNode)parent).
       
   279 				getUserObject()));
       
   280 			}
       
   281 
       
   282 			parent.add(node);
       
   283 		    }
       
   284 		}
       
   285 
       
   286 		parent = node;
       
   287 		continue;
       
   288 	    }
       
   289 
       
   290 	    node = new ConnectionTreeNode(info);
       
   291 
       
   292 	    parent.add(node);
       
   293 	    fireAddEvent(info);
       
   294 	    parent = node;
       
   295 
       
   296 	    // Does this new connection replace one or more failed connections?
       
   297 	    for (ConnectionTreeNode failed : getFailed()) {
       
   298 		if (failed.getUserObject().matches(info)) {
       
   299 		    ConnectionEvent event = null;
       
   300 
       
   301 		    // Adopt failed node's dependants and notify them of change
       
   302 		    for (TreeNode child : failed.getChildren()) {
       
   303 			node.add((MutableTreeNode)child);
       
   304 
       
   305 			if (child instanceof ListenerTreeNode) {
       
   306 			    if (event == null) {
       
   307 				event = new ConnectionEvent(
       
   308 				    this, info, failed.getUserObject());
       
   309 			    }
       
   310 
       
   311 			    ((ListenerTreeNode)child).getUserObject().
       
   312 				connectionChanged(event);
       
   313 			}
       
   314 		    }
       
   315 
       
   316 		    remove(failed);
       
   317 		}
       
   318 	    }
       
   319 	}
       
   320 
       
   321         // Listener could be at the end of multiple dependency chains.  Ensure
       
   322         // that it is listening to the first connection in depChain.
       
   323 	ListenerTreeNode node = getChild(parent, listener);
       
   324 	if (node == null) {
       
   325 	    node = new ListenerTreeNode(listener);
       
   326 	    parent.add(node);
       
   327 	}
       
   328 
       
   329 	fireSelectedEvent(depChain.get(0));
       
   330     }
       
   331 
       
   332     /**
       
   333      * Adds a {@code ConnectionListListener} to notification.
       
   334      */
       
   335     public synchronized void addConnectionListListener(
       
   336 	ConnectionListListener listener) {
       
   337 
       
   338 	listeners.add(listener);
       
   339     }
       
   340 
       
   341     /**
       
   342      * Gets a dependency chain of {@link ConnectionInfo}s, with the first
       
   343      * element healthy and matching the given connection parameters, or {@code
       
   344      * null} if no healthy matching {@code ConnectionInfo} exists.
       
   345      */
       
   346     public synchronized List<ConnectionInfo> getDepChain(String host,
       
   347         String user, String role, String zone, String zoneUser, String zoneRole)
       
   348     {
       
   349 	for (DependencyTreeNode node : nodeMap.values()) {
       
   350 	    if (node instanceof ConnectionTreeNode) {
       
   351 		ConnectionTreeNode cNode = (ConnectionTreeNode)node;
       
   352 		ConnectionInfo info = cNode.getUserObject();
       
   353                 if (cNode.isHealthy() && info.matches(host, user, role, zone,
       
   354 		    zoneUser, zoneRole)) {
       
   355 		    List<ConnectionInfo> depChain =
       
   356 			new LinkedList<ConnectionInfo>();
       
   357 
       
   358 		    for (TreeNode n = node; n != root; n = n.getParent()) {
       
   359 			depChain.add(((ConnectionTreeNode)n).getUserObject());
       
   360 		    }
       
   361 
       
   362 		    return depChain;
       
   363 		}
       
   364 	    }
       
   365 	}
       
   366 	return null;
       
   367     }
       
   368 
       
   369     /**
       
   370      * Removes {@code listener} as a listener to/dependency of {@code info}.
       
   371      * Any connection leaf nodes in the resulting dependency tree will be
       
   372      * removed from the tree.
       
   373      *
       
   374      * @param	    listener
       
   375      *		    a {@link ConnectionListener} that depends on {@code info}
       
   376      *
       
   377      * @param	    info
       
   378      *		    the {@link ConnectionInfo} in the dependency tree
       
   379      *
       
   380      * @throws	    IllegalArgumentException
       
   381      *		    if {@code listener} does not depend on {@code info}
       
   382      */
       
   383     public synchronized void remove(ConnectionListener listener,
       
   384 	ConnectionInfo info) {
       
   385 
       
   386 	DependencyTreeNode parent = getNode(info);
       
   387 	if (parent == null) {
       
   388 	    throw new IllegalArgumentException(String.format(
       
   389 		"listener does not depend on \"%s\"", info));
       
   390 	}
       
   391 
       
   392 	ListenerTreeNode node = getChild(parent, listener);
       
   393 	if (node == null) {
       
   394 	    throw new IllegalArgumentException(String.format(
       
   395 		"listener does not depend on \"%s\"", info));
       
   396 	}
       
   397 
       
   398 	remove(node);
       
   399     }
       
   400 
       
   401     /**
       
   402      * Removes a {@code ConnectionListListener} from notification.
       
   403      */
       
   404     public synchronized void removeConnectionListListener(
       
   405 	ConnectionListListener listener) {
       
   406 
       
   407 	listeners.remove(listener);
       
   408     }
       
   409 
       
   410     //
       
   411     // Private methods
       
   412     //
       
   413 
       
   414     private synchronized void fireAddEvent(ConnectionInfo info) {
       
   415         listeners.connectionAdded(new ConnectionEvent(this, info));
       
   416     }
       
   417 
       
   418     private synchronized void fireConnectionFailed(ConnectionTreeNode node) {
       
   419 	node.setHealthy(false);
       
   420 	ConnectionEvent event = null;
       
   421 
       
   422 	// Forward to any ConnectionListener dependants
       
   423 	for (TreeNode child : node.getChildren()) {
       
   424 	    if (child instanceof ListenerTreeNode) {
       
   425 		if (event == null) {
       
   426 		    event = new ConnectionEvent(this, node.getUserObject());
       
   427 		}
       
   428 		ConnectionListener listener =
       
   429 		    ((ListenerTreeNode)child).getUserObject();
       
   430 		listener.connectionFailed(event);
       
   431 	    }
       
   432 	}
       
   433     }
       
   434 
       
   435     private synchronized void fireRemoveEvent(ConnectionInfo info) {
       
   436 	listeners.connectionRemoved(new ConnectionEvent(this, info));
       
   437     }
       
   438 
       
   439     private synchronized void fireSelectedEvent(ConnectionInfo info) {
       
   440 	listeners.connectionSelected(new ConnectionEvent(this, info));
       
   441     }
       
   442 
       
   443     /**
       
   444      * Returns the dependent child {@code ListenerTreeNode} of {@code node} for
       
   445      * the given {@code listener}, {@code null} if none is found.
       
   446      */
       
   447     private ListenerTreeNode getChild(DependencyTreeNode<?> parent,
       
   448 	ConnectionListener listener) {
       
   449 
       
   450 	for (DependencyTreeNode child : parent.getChildren()) {
       
   451 	    if (child instanceof ListenerTreeNode &&
       
   452 		child.getUserObject() == listener) {
       
   453 		return (ListenerTreeNode)child;
       
   454 	    }
       
   455 	}
       
   456 
       
   457 	return null;
       
   458     }
       
   459 
       
   460     private synchronized List<ConnectionTreeNode> getFailed() {
       
   461 	List<ConnectionTreeNode> list = new LinkedList<ConnectionTreeNode>();
       
   462 	for (DependencyTreeNode node : nodeMap.values()) {
       
   463 	    if (node instanceof ConnectionTreeNode) {
       
   464 		ConnectionTreeNode cNode = (ConnectionTreeNode)node;
       
   465 		if (!cNode.isHealthy()) {
       
   466 		    list.add(cNode);
       
   467 		}
       
   468 	    }
       
   469 	}
       
   470 	return list;
       
   471     }
       
   472 
       
   473     private synchronized DependencyTreeNode getNode(Object userObj) {
       
   474 	for (Map.Entry<Object, DependencyTreeNode> entry : nodeMap.entrySet()) {
       
   475 	    Object key = entry.getKey();
       
   476 	    if (key == userObj) {
       
   477 		return entry.getValue();
       
   478 	    }
       
   479 	}
       
   480 	return null;
       
   481     }
       
   482 
       
   483     private synchronized void remove(DependencyTreeNode<?> node) {
       
   484 	for (DependencyTreeNode child : node.getChildren()) {
       
   485 	    remove(child);
       
   486 	}
       
   487 
       
   488 	if (node instanceof ConnectionTreeNode) {
       
   489 	    ConnectionInfo info = ((ConnectionTreeNode)node).getUserObject();
       
   490 	}
       
   491 
       
   492 	DependencyTreeNode<?> parent = (DependencyTreeNode)node.getParent();
       
   493 	parent.remove(node);
       
   494 
       
   495 	if (node instanceof ConnectionTreeNode) {
       
   496 	    ConnectionInfo info = ((ConnectionTreeNode)node).getUserObject();
       
   497 	    fireRemoveEvent(info);
       
   498 	}
       
   499 
       
   500 	if (parent != root && parent.getChildCount() == 0) {
       
   501 	    remove(parent);
       
   502 	}
       
   503     }
       
   504 }