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