|
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, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.client.swing; |
|
27 |
|
28 import java.awt.event.*; |
|
29 import java.awt.Window; |
|
30 import java.net.URL; |
|
31 import java.security.*; |
|
32 import java.util.*; |
|
33 import java.util.logging.*; |
|
34 import javax.help.*; |
|
35 import javax.swing.*; |
|
36 import com.oracle.solaris.vp.client.common.*; |
|
37 import com.oracle.solaris.vp.panel.common.*; |
|
38 import com.oracle.solaris.vp.panel.common.action.*; |
|
39 import com.oracle.solaris.vp.panel.common.control.*; |
|
40 import com.oracle.solaris.vp.panel.common.control.InvalidParameterException; |
|
41 import com.oracle.solaris.vp.panel.common.view.*; |
|
42 import com.oracle.solaris.vp.panel.swing.control.SwingNavigator; |
|
43 |
|
44 @SuppressWarnings({"serial"}) |
|
45 public class AppInstance implements ClientContext, ConnectionListener { |
|
46 static { |
|
47 // Force early load of runtime properties |
|
48 AppProperties a = AppProperties.singleton; |
|
49 |
|
50 // Set look and feel unless user has a preference |
|
51 String gtk = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; |
|
52 |
|
53 if (System.getProperty("swing.defaultlaf") == null) { |
|
54 String lafName = UIManager.getSystemLookAndFeelClassName(); |
|
55 // Refuse to be a victim of Solaris's Motif default |
|
56 if (lafName.contains("MotifLookAndFeel")) |
|
57 lafName = gtk; |
|
58 try { |
|
59 UIManager.setLookAndFeel(lafName); |
|
60 } catch (Exception ignore) { |
|
61 } |
|
62 } |
|
63 |
|
64 LookAndFeel laf = UIManager.getLookAndFeel(); |
|
65 |
|
66 // Handle Gnome-specific bugs |
|
67 if (laf != null && laf.getClass().getName().equals(gtk)) { |
|
68 // Gnome L&F doesn't set this correctly |
|
69 UIManager.put("ToolTip.background", new JToolTip().getBackground()); |
|
70 |
|
71 // Gnome L&F is the only L&F that does this |
|
72 UIManager.put("Slider.paintValue", Boolean.FALSE); |
|
73 } |
|
74 } |
|
75 |
|
76 // |
|
77 // Instance data |
|
78 // |
|
79 |
|
80 private App app; |
|
81 private BusyIndicator busy; |
|
82 private ConnectionInfo info; |
|
83 private HelpBroker helpBroker; |
|
84 private SwingNavigator navigator; |
|
85 private Properties hints; |
|
86 |
|
87 private AppLoginManager loginManager; |
|
88 private ActionListener showHelpAction; |
|
89 private ConnectionListeners cListeners = new ConnectionListeners(); |
|
90 |
|
91 // |
|
92 // Constructors |
|
93 // |
|
94 |
|
95 private AppInstance(App app, Properties hints, |
|
96 List<ConnectionInfo> depChain) { |
|
97 |
|
98 init(app, hints); |
|
99 setDepChain(depChain); |
|
100 initNavigator(); |
|
101 app.instanceCreated(this); |
|
102 } |
|
103 |
|
104 public AppInstance(App app, Properties hints, LoginRequest request) |
|
105 throws ActionAbortedException, ActionFailedException { |
|
106 |
|
107 init(app, hints); |
|
108 |
|
109 // Don't prompt user for acknowledgement for default local connection |
|
110 ConnectionInfo current = new ConnectionInfo(RadLoginManager.LOCAL_HOST, |
|
111 RadLoginManager.LOCAL_USER, null, null); |
|
112 |
|
113 List<ConnectionInfo> depChain = loginManager.getConnectionInfo( |
|
114 request, current); |
|
115 |
|
116 setDepChain(depChain); |
|
117 initNavigator(); |
|
118 app.instanceCreated(this); |
|
119 } |
|
120 |
|
121 // |
|
122 // ConnectionListener methods |
|
123 // |
|
124 |
|
125 @Override |
|
126 public void connectionChanged(ConnectionEvent event) { |
|
127 // This method should only called by the ConnectionManager when a failed |
|
128 // connection has been restored |
|
129 assert info == event.getOldConnectionInfo(); |
|
130 |
|
131 setConnectionInfo(event.getConnectionInfo()); |
|
132 } |
|
133 |
|
134 @Override |
|
135 public void connectionFailed(ConnectionEvent event) { |
|
136 cListeners.connectionFailed( |
|
137 new ConnectionEvent(this, event.getConnectionInfo())); |
|
138 } |
|
139 |
|
140 // |
|
141 // ClientContext methods |
|
142 // |
|
143 |
|
144 @Override |
|
145 public void addConnectionListener(ConnectionListener listener) { |
|
146 cListeners.add(listener); |
|
147 } |
|
148 |
|
149 @Override |
|
150 public void closeInstance(final boolean isCancel) |
|
151 throws ActionAbortedException { |
|
152 |
|
153 // Using PrivilegedExceptionAction offers no compile-time checking of |
|
154 // thrown Exceptions, so use this mildly awkward method instead |
|
155 |
|
156 final ActionAbortedException[] err = new ActionAbortedException[1]; |
|
157 |
|
158 AccessController.doPrivileged( |
|
159 new PrivilegedAction<Object>() { |
|
160 @Override |
|
161 public Object run() { |
|
162 try { |
|
163 closeInstanceImp(isCancel); |
|
164 } catch (ActionAbortedException e) { |
|
165 err[0] = e; |
|
166 } |
|
167 return null; |
|
168 } |
|
169 }); |
|
170 |
|
171 if (err[0] != null) { |
|
172 throw err[0]; |
|
173 } |
|
174 } |
|
175 |
|
176 @Override |
|
177 public BusyIndicator getBusyIndicator() { |
|
178 List<Control> controls = navigator.getPath(); |
|
179 for (int i = controls.size() - 1; i >= 0; i--) { |
|
180 Control control = controls.get(i); |
|
181 if (control instanceof HasBusyIndicator) { |
|
182 BusyIndicator busy = |
|
183 ((HasBusyIndicator)control).getBusyIndicator(); |
|
184 |
|
185 if (busy != null) { |
|
186 return busy; |
|
187 } |
|
188 } |
|
189 } |
|
190 |
|
191 if (busy == null) { |
|
192 busy = new SimpleBusyIndicator(); |
|
193 } |
|
194 |
|
195 return busy; |
|
196 } |
|
197 |
|
198 @Override |
|
199 public ConnectionInfo getConnectionInfo() { |
|
200 return info; |
|
201 } |
|
202 |
|
203 @Override |
|
204 public HelpBroker getHelpBroker() { |
|
205 return helpBroker; |
|
206 } |
|
207 |
|
208 @Override |
|
209 public Navigator getNavigator() { |
|
210 return navigator; |
|
211 } |
|
212 |
|
213 @Override |
|
214 public Properties getRuntimeHints() { |
|
215 return hints; |
|
216 } |
|
217 |
|
218 @Override |
|
219 public ClientContext login(final LoginRequest request, |
|
220 final boolean forceNewContext) throws ActionAbortedException, |
|
221 ActionFailedException { |
|
222 |
|
223 // Using PrivilegedExceptionAction offers no compile-time checking of |
|
224 // thrown Exceptions, so use this mildly awkward method instead |
|
225 |
|
226 final ActionAbortedException[] abort = new ActionAbortedException[1]; |
|
227 final ActionFailedException[] fail = new ActionFailedException[1]; |
|
228 |
|
229 ClientContext context = AccessController.doPrivileged( |
|
230 new PrivilegedAction<ClientContext>() { |
|
231 @Override |
|
232 public ClientContext run() { |
|
233 try { |
|
234 return loginImp(request, forceNewContext); |
|
235 } catch (ActionAbortedException e) { |
|
236 abort[0] = e; |
|
237 } catch (ActionFailedException e) { |
|
238 fail[0] = e; |
|
239 } |
|
240 return null; |
|
241 } |
|
242 }); |
|
243 |
|
244 if (abort[0] != null) { |
|
245 throw abort[0]; |
|
246 } |
|
247 |
|
248 if (fail[0] != null) { |
|
249 throw fail[0]; |
|
250 } |
|
251 |
|
252 return context; |
|
253 } |
|
254 |
|
255 @Override |
|
256 public boolean removeConnectionListener(ConnectionListener listener) { |
|
257 return cListeners.remove(listener); |
|
258 } |
|
259 |
|
260 @Override |
|
261 public void showHelp() { |
|
262 Window window = navigator.getLastWindow(); |
|
263 |
|
264 if (window == null) { |
|
265 // Absurdly, the Swing help viewer needs a non-null window to show, |
|
266 // even if that window is not visible |
|
267 window = new Window(null); |
|
268 } |
|
269 |
|
270 ActionEvent event = new ActionEvent( |
|
271 window, ActionEvent.ACTION_PERFORMED, "showHelp"); |
|
272 |
|
273 showHelpAction.actionPerformed(event); |
|
274 } |
|
275 |
|
276 @Override |
|
277 public LoginHistory getLoginHistory() { |
|
278 return app.getLoginHistoryManager(); |
|
279 } |
|
280 |
|
281 // |
|
282 // AppInstance methods |
|
283 // |
|
284 |
|
285 /** |
|
286 * Closes this {@code AppInstance} unconditionally. |
|
287 */ |
|
288 public void close() { |
|
289 ConnectionInfo info = getConnectionInfo(); |
|
290 if (info != null) { |
|
291 app.getConnectionManager().remove(this, info); |
|
292 } |
|
293 app.instanceClosed(this); |
|
294 } |
|
295 |
|
296 public App getApp() { |
|
297 return app; |
|
298 } |
|
299 |
|
300 /** |
|
301 * Sets the dependency chain of {@link ConnectionListener}s derived from the |
|
302 * login process. |
|
303 * |
|
304 * @param depChain |
|
305 * a dependency chain of {@link ConnectionInfo}s |
|
306 */ |
|
307 protected void setDepChain(List<ConnectionInfo> depChain) { |
|
308 ConnectionInfo oldInfo = getConnectionInfo(); |
|
309 ConnectionInfo info = depChain.get(0); |
|
310 |
|
311 if (oldInfo != info) { |
|
312 ConnectionManager connManager = app.getConnectionManager(); |
|
313 if (oldInfo != null) { |
|
314 connManager.remove(this, oldInfo); |
|
315 } |
|
316 |
|
317 connManager.add(this, depChain); |
|
318 setConnectionInfo(info); |
|
319 } |
|
320 } |
|
321 |
|
322 /** |
|
323 * Sets the current connection, notifying any registered {@link |
|
324 * ConnectionListener}s. |
|
325 * |
|
326 * @param info |
|
327 * a {@link ConnectionInfo} |
|
328 */ |
|
329 protected void setConnectionInfo(ConnectionInfo info) { |
|
330 ConnectionInfo oldInfo = getConnectionInfo(); |
|
331 |
|
332 if (oldInfo != info) { |
|
333 this.info = info; |
|
334 |
|
335 // Set and notify any listeners (ie, panels) of change |
|
336 cListeners.connectionChanged( |
|
337 new ConnectionEvent(this, info, oldInfo)); |
|
338 } |
|
339 } |
|
340 |
|
341 // |
|
342 // Private methods |
|
343 // |
|
344 |
|
345 private void closeInstanceImp(boolean isCancel) |
|
346 throws ActionAbortedException { |
|
347 |
|
348 JDialog dialog = loginManager.getDialog(); |
|
349 if (dialog != null && dialog.isVisible()) { |
|
350 throw new ActionAbortedException(); |
|
351 } |
|
352 |
|
353 try { |
|
354 // Unwind navigation stack to force user to handle pending changes |
|
355 navigator.goToAsyncAndWait(isCancel, null); |
|
356 |
|
357 } catch (NavigationAbortedException e) { |
|
358 throw new ActionAbortedException(e); |
|
359 |
|
360 } catch (NavigationFailedException e) { |
|
361 // Should not be possible |
|
362 |
|
363 } catch (InvalidAddressException e) { |
|
364 // Should not be possible |
|
365 |
|
366 } catch (InvalidParameterException e) { |
|
367 // Should not be possible |
|
368 |
|
369 } catch (EmptyNavigationStackException e) { |
|
370 // Should not be possible |
|
371 |
|
372 } catch (RootNavigableNotControlException e) { |
|
373 // Should not be possible |
|
374 } |
|
375 |
|
376 close(); |
|
377 } |
|
378 |
|
379 private void createHelp() { |
|
380 String helpSetName = |
|
381 getClass().getPackage().getName().replaceAll("\\.", "/") + |
|
382 "/help/app"; |
|
383 |
|
384 HelpSet helpSet; |
|
385 try { |
|
386 ClassLoader loader = getClass().getClassLoader(); |
|
387 URL url = HelpSet.findHelpSet(loader, helpSetName); |
|
388 helpSet = new HelpSet(loader, url); |
|
389 } catch (HelpSetException e) { |
|
390 String message = String.format( |
|
391 "could not load helpset: %s", helpSetName); |
|
392 |
|
393 Logger.getLogger(getClass().getName()).log( |
|
394 Level.WARNING, message, e); |
|
395 helpSet = new HelpSet(); |
|
396 } |
|
397 |
|
398 helpBroker = helpSet.createHelpBroker(); |
|
399 showHelpAction = new CSH.DisplayHelpFromSource(helpBroker); |
|
400 } |
|
401 |
|
402 private void createNavigator() { |
|
403 navigator = new SwingNavigator(); |
|
404 |
|
405 // Re-navigate to most appropriate path on error |
|
406 navigator.addNavigationListener(new NavigationErrorHandler()); |
|
407 } |
|
408 |
|
409 private void init(App app, Properties hints) { |
|
410 this.app = app; |
|
411 this.hints = hints; |
|
412 |
|
413 createNavigator(); |
|
414 createHelp(); |
|
415 |
|
416 loginManager = new AppLoginManager(app.getConnectionManager(), |
|
417 new HasWindow() { |
|
418 @Override |
|
419 public Window getComponent() { |
|
420 return SwingNavigator.getLastWindow(getNavigator()); |
|
421 } |
|
422 }); |
|
423 } |
|
424 |
|
425 private void initNavigator() { |
|
426 AppRootControl root = new AppRootControl(this); |
|
427 try { |
|
428 navigator.goToAsyncAndWait(false, null, root); |
|
429 } catch (NavigationException unlikely) { |
|
430 } |
|
431 } |
|
432 |
|
433 private ClientContext loginImp(LoginRequest request, |
|
434 boolean forceNewContext) throws ActionAbortedException, |
|
435 ActionFailedException { |
|
436 |
|
437 if (request == null) { |
|
438 LoginProperty<String> host = |
|
439 new LoginProperty<String>(info.getHost(), false); |
|
440 |
|
441 LoginProperty<String> user = |
|
442 new LoginProperty<String>(info.getUser(), false); |
|
443 |
|
444 LoginProperty<String> role = |
|
445 new LoginProperty<String>(info.getRole(), false); |
|
446 |
|
447 LoginProperty<String> zone = |
|
448 new LoginProperty<String>(info.getZone(), false); |
|
449 |
|
450 LoginProperty<String> zoneUser = |
|
451 new LoginProperty<String>(info.getZoneUser(), false); |
|
452 |
|
453 LoginProperty<String> zoneRole = |
|
454 new LoginProperty<String>(info.getZoneRole(), false); |
|
455 |
|
456 boolean zonePromptVal = zone.getValue() != null || |
|
457 zoneUser.getValue() != null || zoneRole.getValue() != null; |
|
458 LoginProperty<Boolean> zonePrompt = |
|
459 new LoginProperty<Boolean>(zonePromptVal, false); |
|
460 |
|
461 request = new LoginRequest(host, user, role, zonePrompt, zone, |
|
462 zoneUser, zoneRole); |
|
463 } |
|
464 |
|
465 List<ConnectionInfo> depChain = loginManager.getConnectionInfo( |
|
466 request, getConnectionInfo()); |
|
467 |
|
468 ConnectionInfo info = depChain.get(0); |
|
469 |
|
470 ClientContext context; |
|
471 |
|
472 // Open new instance (window) if changing host/zone |
|
473 if (forceNewContext || !this.info.matchesHost(info.getHost()) || |
|
474 !this.info.matchesZone(info.getZone())) { |
|
475 context = new AppInstance(app, hints, depChain); |
|
476 } else { |
|
477 setDepChain(depChain); |
|
478 context = this; |
|
479 } |
|
480 |
|
481 return context; |
|
482 } |
|
483 } |