Package org.jdesktop.wonderland.modules.xremwin.client

Source Code of org.jdesktop.wonderland.modules.xremwin.client.AppXrwMaster$ExitListener

/**
* Project Wonderland
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied
* this code.
*/
package org.jdesktop.wonderland.modules.xremwin.client;

import com.jme.math.Vector2f;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import java.util.LinkedList;
import org.jdesktop.wonderland.client.comms.WonderlandSession;
import org.jdesktop.wonderland.client.utils.SmallIntegerAllocator;
import org.jdesktop.wonderland.common.InternalAPI;
import org.jdesktop.wonderland.common.cell.CellID;
import org.jdesktop.wonderland.modules.appbase.client.utils.net.NetworkAddress;
import org.jdesktop.wonderland.modules.appbase.client.MonitoredProcess;
import org.jdesktop.wonderland.modules.appbase.client.ProcessReporter;
import org.jdesktop.wonderland.modules.xremwin.client.wm.X11WindowManager;

/**
* A Master Xremwin app. This is the AppXrw subclass used on the client machine
* which is executing the app.
*
* @author deronj
*/
@InternalAPI
public class AppXrwMaster
        extends AppXrw
        implements X11WindowManager.WindowTitleListener, ClientXrwMaster.ExitListener {

    /* An allocator for instance numbers */
    private static HashMap<String, SmallIntegerAllocator> instanceAllocators =
            new HashMap<String, SmallIntegerAllocator>();
    /**  The app instance number (this distinguishes between multiple apps with the same name */
    private int appInstance;
    /**  The name of the app along with its instance number, in the format "<appName> <appInstance>" */
    private String appInstanceName;
    /** The window system (X11 server and window manager) for this app. */
    private WindowSystemXrw winSys;
    /** The main process of the application */
    private MonitoredProcess appProcess;
    /** The master/slave peer-to-peer socket */
    private ServerSocket serverSocket;
    /** The information that slaves use to connect to the server socket */
    private AppXrwConnectionInfo connectionInfo;
    /** List of master apps created by this client. */
    private static final LinkedList<AppXrwMaster> masterApps = new LinkedList<AppXrwMaster>();
    /** The process exit value. */
    private int exitValue = -2;

    /** Defines a listener which is called when the app exits. */
    public interface ExitListener {
        public void appExitted(AppXrwMaster app);
    }

    /** A listener which is called when the app exits. */
    private ExitListener exitListener;

    // Register the X11 appbase shutdown hook
    static {
        Runtime.getRuntime().addShutdownHook(new Thread("X11 App Base Master Shutdown Hook") {
            @Override
            public void run() { AppXrwMaster.shutdownAllApps(); }
        });
    }

    /**
     * Create a new instance of AppXrwMaster in a user client.
     *
     * @param appName The name of the application.
     * @param command The operating system command to execute to start app program.
     * @param cellID The id of the cell this app is associated with.
     * @param pixelScale The size of the window pixels.
     * @param processReporter Report output and exit status to this
     * @param session This app's Wonderland session.
     * @throws InstantiationException Could not launch app
     */
    public AppXrwMaster(String appName, String command, CellID cellID, Vector2f pixelScale,
                        ProcessReporter reporter, WonderlandSession session)
            throws InstantiationException {
        this(appName, command, cellID, pixelScale, reporter, session, false);
    }

    /**
     * Create a new instance of AppXrwMaster.
     *
     * @param appName The name of the application.
     * @param command The operating system command to execute to start app program.
     * @param cellID The id of the cell this app is associated with.
     * @param pixelScale The size of the window pixels.
     * @param processReporter Report output and exit status to this
     * @param session This app's Wonderland session.
     * @param sas Whether this app is being launched by a SAS provider
     * @throws InstantiationException Could not launch app
     */
    public AppXrwMaster(String appName, String command, CellID cellID, Vector2f pixelScale,
                        ProcessReporter reporter, WonderlandSession session, boolean sas)
            throws InstantiationException {

        super(appName, (sas ? new ControlArbNull() : new ControlArbXrw()), pixelScale);
        AppXrw.logger.info("appName = " + appName);
        controlArb.setApp(this);

        AppXrw.logger.info("Created AppXrwMaster with command = " + command);

        appInstance = allocAppInstance(appName);
        appInstanceName = appName + " " + appInstance;
        AppXrw.logger.info("appInstanceName = " + appInstanceName);

        // Note: we must be be careful to initialize components in the
        // following order

        // Start the window system for this app
        winSys = WindowSystemXrw.create(appInstanceName, this);
        if (winSys == null) {
            AppXrw.logger.warning("Cannot launch " + appInstanceName + ": Cannot create window system");
            cleanup();
            throw new InstantiationException();
        }

        // Create the peer-to-peer server socket
        // TODO: change name of this property?
        String masterHost = NetworkAddress.getDefaultHostAddress();
        String publicMasterHost = System.getProperty("wonderland.appshare.hostName", masterHost);
        InetAddress inetAddr = NetworkAddress.getDefaultInetAddress();
        try {
            serverSocket = createServerSocket(inetAddr);
        } catch (IOException ex) {
            AppXrw.logger.warning("Cannot create server socket for master for app " + appName);
            cleanup();
            throw new InstantiationException();
        }
        int portNum = serverSocket.getLocalPort();
        connectionInfo = new AppXrwConnectionInfo(publicMasterHost, portNum);

        // Create the Xremwin protocol client.
        client = null;
        try {
            client = new ClientXrwMaster(this, controlArb, session, cellID, masterHost, serverSocket,
                    winSys, reporter);
            ((ClientXrwMaster)client).setExitListener(this);
        } catch (InstantiationException ex) {
            AppXrw.logger.warning("Cannot launch " + appInstanceName + ": Cannot create Xremwin protocol client");
            cleanup();
            throw new InstantiationException();
        }

        // Launch the app (this must be done after the Xremwin protocol client
        // has been started).
        String displayName = winSys.getDisplayName();
        HashMap<String, String> env = new HashMap<String, String>();
        env.put("DISPLAY", displayName);
        appProcess = new MonitoredProcess(appInstanceName, command, env, reporter);
        if (!appProcess.start()) {
            AppXrw.logger.warning("Cannot launch " + appInstanceName + ": Cannot start application process: " +
                    command);
            cleanup();
            throw new InstantiationException();
        }
        synchronized (masterApps) {
            masterApps.add(this);
        }
    }

    /**
     * Specify a listener which is called when the app exits.
     */
    public void setExitListener (ExitListener exitListener) {
        this.exitListener = exitListener;
    }

    /**
     * Find a valid server socket with the range specified by the
     * wl.appshare.minPort and wl.appshare.maxPort values.
     * @param inetAddr the address to open a socket on
     * @return a server socket bound to an open port with the given
     * port range
     * @throws IOException if no ports are available in the given range
     */
    protected ServerSocket createServerSocket(InetAddress addr)
            throws IOException {
        // read properties
        String minPortStr = System.getProperty("wonderland.appshare.minPort");
        String maxPortStr = System.getProperty("wonderland.appshare.maxPort");

        if (minPortStr == null || maxPortStr == null) {
            // no values specified
            return new ServerSocket(0, 0, addr);
        }

        logger.fine("Searching for port between " + minPortStr + " and " +
                maxPortStr);

        // parse ports into a range
        int minPort = Integer.parseInt(minPortStr);
        int maxPort = Integer.parseInt(maxPortStr);

        // Find a valid socket in the range.  We choose a random port in
        // the range, and record the ones we hit that are bad.  This is
        // better than just going in order for smaller numbers of connections,
        // but might take a long time for longer collections.
        int rangeSize = maxPort - minPort;
        Set<Integer> tried = new TreeSet<Integer>();
        while (tried.size() < rangeSize) {
            // generate a random number in the given range
            int port = minPort + (int) (Math.random() * rangeSize);

            // check if we have already tried it
            if (!tried.contains(new Integer(port))) {
                try {
                    return new ServerSocket(port, 0, addr);
                } catch (SocketException se) {
                    // port in use, just record it and go on
                    tried.add(new Integer(port));
                }
            }
        }

        // no ports available -- throw an exception
        throw new IOException("No ports available in range " + minPort +
                " - " + maxPort + ".");
    }

    /**
     * Clean up resources.
     */
    @Override
    public void cleanup() {
        super.cleanup();

        client = null;
        winSys = null;

        if (appProcess != null) {
            exitValue = appProcess.getExitValue();
            appProcess.cleanup();
            appProcess = null;
        }

        deallocAppInstance(getName(), appInstance);

        synchronized (masterApps) {
            masterApps.remove(this);
        }

        logger.info("AppXrwMaster stopped.");

        if (exitListener != null) {
            exitListener.appExitted(this);
        }
    }

    /**
     * Returns the connection info.
     */
    public AppXrwConnectionInfo getConnectionInfo() {
        return connectionInfo;
    }

    /**
     * This method is called when the master client has exitted.
     */
    @Override
    public void clientExitted(ClientXrwMaster client) {
        logger.severe("Master client exitted");
        cleanup();
    }

    /**
     * Allocate the next available unique instance number for an app name.
     * (Instance numbers start at 1).
     *
     * @param appName The name of the app.
     * @return A number which is unique among apps with the same name.
     */
    private static int allocAppInstance(String appName) {
        SmallIntegerAllocator allocator = instanceAllocators.get(appName);
        if (allocator == null) {
            // First time allocation for app name
            allocator = new SmallIntegerAllocator(1);
            instanceAllocators.put(appName, allocator);
        }
        return allocator.allocate();
    }

    /**
     * Return the given instance number to the app name's pool of instances.
     *
     * @param appName The name of the app.
     * @param appInstance An app instance previous allocated by allocAppInstance.
     */
    private static void deallocAppInstance(String appName, int appInstance) {
        SmallIntegerAllocator allocator = instanceAllocators.get(appName);
        if (allocator == null) {
            throw new RuntimeException("Deallocation when app instance is not allocated = " +
                    appInstance);
        }

        allocator.free(appInstance);

        if (allocator.getNumAllocated() == 0) {
            instanceAllocators.remove(appName);
        }
    }

    /**
     * The window title of Master window has changed.
     *
     * @param wid The X window ID of the window.
     * @param String The new window title.
     */
    @Override
    public void setWindowTitle(int wid, String windowTitle) {
        // Relay window title to master client
        ((ClientXrwMaster) client).setWindowTitle(wid, windowTitle);
    }

    /** {@inheritDoc} */
    public boolean isMaster () {
        return true;
    }

    /**
     * Inform all slaves that the given popup has the specified parent.
     */
    public void setPopupParentForSlaves (WindowXrw popup, WindowXrw parent) {
        ((ClientXrwMaster)client).setPopupParent(popup, parent);
    }

    /** Executed by the JVM shutdown process. */
    private static void shutdownAllApps () {
        if (masterApps.size() > 0) {
            logger.warning("Shutting down X11 app base master apps...");

            // TODO: low: workaround for bug 205. This is draconian. Is there something else better?
            try {
                Runtime.getRuntime().exec("pkill -9 Xvfb-xremwin");
            } catch (Exception e) {}
        }
    }

    /**
     * Returns the exit value of the app process. A return value >= 0 indicates that the process
     * has exitted and returned an exit value. The return value is this exit value.
     * <br><br>
     * A return value of -1 indicates that the process is still running, so no exit value is available.
     * <br>
     * A return value of -2 indicates that the process is no longer running, but for some reason
     * its exit value is not available.
     */
    public int getExitValue () {
        if (appProcess != null) {
            return appProcess.getExitValue();
        }
        return exitValue;
    }
}
TOP

Related Classes of org.jdesktop.wonderland.modules.xremwin.client.AppXrwMaster$ExitListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.