usr/src/java/vpanels/client/org/opensolaris/os/vp/client/swing/AppInstance.java
author Stephen Talley <stephen.talley@oracle.com>
Wed, 10 Nov 2010 10:03:10 -0500
changeset 601 9d4e3e0ee603
parent 600 c16a7e34499d
child 603 71a20acea802
permissions -rw-r--r--
7947 vp should fail if unable to connect to requested host

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */

package org.opensolaris.os.vp.client.swing;

import java.awt.event.*;
import java.awt.Window;
import java.net.URL;
import java.security.*;
import java.util.*;
import java.util.logging.*;
import javax.help.*;
import javax.swing.*;
import org.opensolaris.os.vp.client.common.*;
import org.opensolaris.os.vp.panel.common.*;
import org.opensolaris.os.vp.panel.common.action.*;
import org.opensolaris.os.vp.panel.common.control.*;
import org.opensolaris.os.vp.panel.common.control.InvalidParameterException;
import org.opensolaris.os.vp.panel.common.view.*;
import org.opensolaris.os.vp.panel.swing.control.SwingNavigator;

@SuppressWarnings({"serial"})
public class AppInstance implements ClientContext, ConnectionListener {
    static {
	// Force early load of runtime properties
	AppProperties a = AppProperties.singleton;

	// Set look and feel unless user has a preference
	String gtk = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";

	if (System.getProperty("swing.defaultlaf") == null) {
	    String lafName = UIManager.getSystemLookAndFeelClassName();
	    // Refuse to be a victim of Solaris's Motif default
	    if (lafName.contains("MotifLookAndFeel"))
		lafName = gtk;
	    try {
		UIManager.setLookAndFeel(lafName);
	    } catch (Exception ignore) {
	    }
	}

	LookAndFeel laf = UIManager.getLookAndFeel();

	// Handle Gnome-specific bugs
	if (laf != null && laf.getClass().getName().equals(gtk)) {
	    // Gnome L&F doesn't set this correctly
	    UIManager.put("ToolTip.background", new JToolTip().getBackground());

	    // Gnome L&F is the only L&F that does this
	    UIManager.put("Slider.paintValue", Boolean.FALSE);
	}
    }

    //
    // Instance data
    //

    private App app;
    private LoginHistory loginHistory;
    private BusyIndicator busy;
    private ConnectionInfo info;
    private HelpBroker helpBroker;
    private SwingNavigator navigator;
    private Properties hints;

    private AppLoginManager loginManager;
    private ActionListener showHelpAction;
    private ConnectionListeners cListeners = new ConnectionListeners();

    //
    // Constructors
    //

    private AppInstance(App app, Properties hints) {
	this.app = app;
	this.hints = hints;

	createNavigator();
	createHelp();

        loginManager = new AppLoginManager(app.getConnectionManager(),
	    new HasWindow() {
		@Override
		public Window getComponent() {
		    return SwingNavigator.getLastWindow(getNavigator());
		}
	    });

        // Initialize login history.
        loginHistory = AppLoginHistory.getInstance(
            app.getConnectionManager());
    }

    public AppInstance(App app, Properties hints, ConnectionInfo info) {
	this(app, hints);
	setConnectionInfo(info);
	app.instanceCreated(this);
    }

    public AppInstance(App app, Properties hints, LoginRequest request)
	throws ActionAbortedException, ActionFailedException {

	this(app, hints);

	// Don't prompt user for acknowledgement for default local connection
        ConnectionInfo current = new ConnectionInfo(RadLoginManager.LOCAL_HOST,
	    RadLoginManager.LOCAL_USER, null, null);

	ConnectionInfo[] infos = loginManager.getConnectionInfo(
	    request, current);

	if (infos[1] == null) {
	    setConnectionInfo(infos[0]);
	} else {
	    setConnectionInfo(infos[1]);
	    app.getConnectionManager().add(infos[0], null);
	}
	app.instanceCreated(this);
    }

    //
    // ConnectionListener methods
    //

    @Override
    public void connectionChanged(ConnectionEvent event) {
        // This method should only called by the ConnectionManager when a failed
        // connection has been restored
	assert info == event.getOldConnectionInfo();

	setConnectionInfo(event.getConnectionInfo());
    }

    @Override
    public void connectionFailed(ConnectionEvent event) {
	cListeners.connectionFailed(
	    new ConnectionEvent(this, event.getConnectionInfo()));
    }

    //
    // ClientContext methods
    //

    @Override
    public void addConnectionListener(ConnectionListener listener) {
	cListeners.add(listener);
    }

    @Override
    public void closeInstance(final boolean isCancel)
	throws ActionAbortedException {

	// Using PrivilegedExceptionAction offers no compile-time checking of
	// thrown Exceptions, so use this mildly awkward method instead

	final ActionAbortedException[] err = new ActionAbortedException[1];

	AccessController.doPrivileged(
	    new PrivilegedAction<Object>() {
		@Override
		public Object run() {
		    try {
			closeInstanceImp(isCancel);
		    } catch (ActionAbortedException e) {
			err[0] = e;
		    }
		    return null;
		}
	    });

	if (err[0] != null) {
	    throw err[0];
	}
    }

    @Override
    public BusyIndicator getBusyIndicator() {
	List<Control> controls = navigator.getPath();
	for (int i = controls.size() - 1; i >= 0; i--) {
	    Control control = controls.get(i);
	    if (control instanceof HasBusyIndicator) {
		BusyIndicator busy =
		    ((HasBusyIndicator)control).getBusyIndicator();

		if (busy != null) {
		    return busy;
		}
	    }
	}

	if (busy == null) {
	    busy = new SimpleBusyIndicator();
	}

	return busy;
    }

    @Override
    public ConnectionInfo getConnectionInfo() {
	return info;
    }

    @Override
    public HelpBroker getHelpBroker() {
	return helpBroker;
    }

    @Override
    public Navigator getNavigator() {
	return navigator;
    }

    @Override
    public Properties getRuntimeHints() {
	return hints;
    }

    @Override
    public ClientContext login(final LoginRequest request,
        final boolean forceNewContext) throws ActionAbortedException,
        ActionFailedException {

	// Using PrivilegedExceptionAction offers no compile-time checking of
	// thrown Exceptions, so use this mildly awkward method instead

	final ActionAbortedException[] abort = new ActionAbortedException[1];
	final ActionFailedException[] fail = new ActionFailedException[1];

	ClientContext context = AccessController.doPrivileged(
	    new PrivilegedAction<ClientContext>() {
		@Override
		public ClientContext run() {
		    try {
			return loginImp(request, forceNewContext);
		    } catch (ActionAbortedException e) {
			abort[0] = e;
		    } catch (ActionFailedException e) {
			fail[0] = e;
		    }
		    return null;
		}
	    });

	if (abort[0] != null) {
	    throw abort[0];
	}

	if (fail[0] != null) {
	    throw fail[0];
	}

	return context;
    }

    @Override
    public boolean removeConnectionListener(ConnectionListener listener) {
	return cListeners.remove(listener);
    }

    @Override
    public void showHelp() {
	Window window = navigator.getLastWindow();

	if (window == null) {
            // Absurdly, the Swing help viewer needs a non-null window to show,
            // even if that window is not visible
	    window = new Window(null);
	}

	ActionEvent event = new ActionEvent(
	    window, ActionEvent.ACTION_PERFORMED, "showHelp");

	showHelpAction.actionPerformed(event);
    }

    @Override
    public LoginHistory getLoginHistory() {
        return loginHistory;
    }

    //
    // AppInstance methods
    //

    /**
     * Closes this {@code AppInstance} unconditionally.
     */
    public void close() {
	ConnectionInfo info = getConnectionInfo();
	if (info != null) {
	    app.getConnectionManager().remove(info, this);
	}
	app.instanceClosed(this);
    }

    public App getApp() {
	return app;
    }

    /**
     * Sets the current connection, notifying any registered {@link
     * ConnectionListener}s.
     *
     * @param	    info
     *		    a {@link ConnectionInfo}
     */
    protected void setConnectionInfo(ConnectionInfo info) {
	ConnectionInfo oldInfo = getConnectionInfo();
	if (oldInfo != info) {
	    ConnectionManager connManager = app.getConnectionManager();
	    if (oldInfo != null) {
		connManager.remove(oldInfo, this);
	    }

	    this.info = info;

	    connManager.add(info, this);

	    // Set and notify any listeners (ie, panels) of change
	    cListeners.connectionChanged(
		new ConnectionEvent(this, info, oldInfo));
	}
    }

    //
    // Private methods
    //

    private void closeInstanceImp(boolean isCancel)
	throws ActionAbortedException {

	JDialog dialog = loginManager.getDialog();
	if (dialog != null && dialog.isVisible()) {
	    throw new ActionAbortedException();
	}

	try {
	    // Unwind navigation stack to force user to handle pending changes
	    navigator.goToAsyncAndWait(isCancel, null);

	} catch (NavigationAbortedException e) {
	    throw new ActionAbortedException(e);

	} catch (InvalidAddressException e) {
	    // Should not be possible

	} catch (InvalidParameterException e) {
	    // Should not be possible

	} catch (EmptyNavigationStackException e) {
	    // Should not be possible

	} catch (RootNavigableNotControlException e) {
	    // Should not be possible
	}

	close();
    }

    private void createHelp() {
	String helpSetName =
	    getClass().getPackage().getName().replaceAll("\\.", "/") +
	    "/help/app";

	HelpSet helpSet;
	try {
	    ClassLoader loader = getClass().getClassLoader();
	    URL url = HelpSet.findHelpSet(loader, helpSetName);
	    helpSet = new HelpSet(loader, url);
	} catch (HelpSetException e) {
	    String message = String.format(
		"could not load helpset: %s", helpSetName);

	    Logger.getLogger(getClass().getName()).log(
		Level.WARNING, message, e);
	    helpSet = new HelpSet();
	}

	helpBroker = helpSet.createHelpBroker();
	showHelpAction = new CSH.DisplayHelpFromSource(helpBroker);
    }

    private void createNavigator() {
	AppRootControl root = new AppRootControl(this);

	try {
	    navigator = new SwingNavigator();

	    // Re-navigate to most appropriate path on error
	    navigator.addNavigationListener(new NavigationErrorHandler());

	    navigator.goToAsyncAndWait(false, null, root);
	} catch (NavigationException unlikely) {
	}
    }

    private ClientContext loginImp(LoginRequest request,
        boolean forceNewContext) throws ActionAbortedException,
        ActionFailedException {

	if (request == null) {
	    StringLoginProperty host =
		new StringLoginProperty(info.getHost(), false);
	    StringLoginProperty user =
		new StringLoginProperty(info.getUser(), false);
	    StringLoginProperty role =
		new StringLoginProperty(info.getRole(), false);
	    request = new LoginRequest(host, user, role);
	}

	ConnectionInfo[] infos = loginManager.getConnectionInfo(
	    request, this.info);
	ConnectionInfo info;
	ConnectionInfo userInfo = null;

	if (infos[1] == null) {
	    info = infos[0];
	} else {
	    info = infos[1];
	    userInfo = infos[0];
	}

	ClientContext context;

	if (forceNewContext || (this.info != null &&
	    !this.info.matchesHost(info.getHost()))) {

	    context = new AppInstance(app, hints, info);
	} else {
	    setConnectionInfo(info);
	    context = this;
	}

	if (userInfo != null) {
	    // Add user-based connection to ConnectionManager in case it isn't
	    // already there
	    app.getConnectionManager().add(userInfo, null);
	}

	return context;
    }
}