Package org.jdesktop.wonderland.modules.darkstar.server

Source Code of org.jdesktop.wonderland.modules.darkstar.server.DarkstarRunnerImpl$DarkstarOutputReader

/**
* Open Wonderland
*
* Copyright (c) 2010 - 2012, Open Wonderland Foundation, 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.
*
* The Open Wonderland Foundation designates this particular file as
* subject to the "Classpath" exception as provided by the Open Wonderland
* Foundation in the License file that accompanied this code.
*/

/**
* Project Wonderland
*
* Copyright (c) 2004-2010, 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.darkstar.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import javax.xml.bind.JAXBException;
import org.jdesktop.wonderland.modules.darkstar.api.weblib.DarkstarRunner;
import org.jdesktop.wonderland.modules.service.ModuleManager;
import org.jdesktop.wonderland.runner.BaseRunner;
import org.jdesktop.wonderland.runner.RunManager;
import org.jdesktop.wonderland.runner.RunnerChecksum;
import org.jdesktop.wonderland.runner.RunnerChecksums;
import org.jdesktop.wonderland.runner.RunnerConfigurationException;
import org.jdesktop.wonderland.runner.RunnerException;
import org.jdesktop.wonderland.runner.RunnerInfo;
import org.jdesktop.wonderland.runner.StatusWaiter;
import org.jdesktop.wonderland.utils.Constants;
import org.jdesktop.wonderland.utils.RunUtil;

/**
* An extension of <code>BaseRunner</code> to launch the Darkstar server.
* @author jkaplan
*/
public class DarkstarRunnerImpl extends BaseRunner implements DarkstarRunner {
    /** the default name if none is specified */
    private static final String DEFAULT_NAME = "Darkstar Server";

    /** the default port to run on */
    static final int DEFAULT_PORT = 1139;

    /** the URL for listing available files */
    private static final String CHECKSUM_URL =
                                  "darkstar/darkstarserver/services/checksums";

    /** property names in the runner info object */
    static final String HOSTNAME_PROP = "hostname";
    static final String HOSTNAME_INTERNAL_PROP = "hostnameInternal";
    static final String PORT_PROP = "port";
    static final String PORT_INTERNAL_PROP = "portInternal";
    static final String WFSNAME_PROP = "wfsname";

    /** the files to save WFS URLs in */
    private static final String SELECTED_WFS_FILE = "selectedwfs";
    private static final String LAST_WFS_FILE = "lastwfs";
    private static final String COLDSTART_FILE = ".coldstart";

    /** the name of the default world, if the file doesn't exist */
    private static final String DEFAULT_WORLD_PROP = "wonderland.sgs.wfs.default";
    private static final String DEFAULT_WORLD = "worlds/gardenarches-wfs";

    /** the property to check for persistence options */
    private static final String PERSISTENCE_TYPE_PROP = "wonderland.sgs.persistence";
    private PersistenceType PERSISTENCE_TYPE_DEFAULT = PersistenceType.FALLBACK;
    protected enum PersistenceType {
        NONE, FALLBACK, ALWAYS;

        public static PersistenceType parsePersistenceType(String str) {
            for (PersistenceType pt : PersistenceType.values()) {
                if (str.trim().equalsIgnoreCase(pt.name())) {
                    return pt;
                }
            }

            throw new IllegalArgumentException("No such type: " + str);
        }
    }

    /** the property to check for a public hostname */
    private static final String PUBLIC_ADDRESS_PROP = "darkstar.host.public";

    /** the property to check for a public port */
    private static final String PUBLIC_PORT_PROP = "darkstar.port.public";
   
    /** the logger */
    private static final Logger logger =
            Logger.getLogger(DarkstarRunnerImpl.class.getName());

    /** the webserver URL to link back to */
    private String webserverURL;

    /** the sgs port.  Only valid when starting up or running */
    private int currentPort;

    /** the internal port. Only valid when starting up or running */
    private int currentInternalPort;
   
    /** managers and services found as we deployed files */
    private List<String> managers;
    private List<String> services;

    /** the name of the WFS to load from, or null to load an empty world */
    private String wfsName;

    /** the current wfs name.  Only valid when starting up */
    private String currentWFSName;

    /** the public hostname, or null to use the default hostname */
    private String publicAddress;

    /**
     * The current list of modules, implemented as a thread-local variable
     * that is only valid during a single call to start()
     */
    private static ThreadLocal<RunnerChecksums> moduleChecksums =
        new ThreadLocal<RunnerChecksums>();

    private DarkstarSnapshotRunner snapshot = null;
    /**
     * Configure this runner.  This method sets values to the default for the
     * Darkstar server.
     *
     * @param props the properties to deploy with
     * @throws RunnerConfigurationException if there is an error configuring
     * the runner
     */
    @Override
    public void configure(Properties props)
            throws RunnerConfigurationException
    {
        super.configure(props);
   
        // if the name wasn't configured, do that now
        if (!props.containsKey("runner.name")) {
            setName(DEFAULT_NAME);
        }

        // record the webserver URL
        webserverURL = props.getProperty("wonderland.web.server.url");

        // attempt to restore the WFS URL or use the default name
        try {
            File wfsFile = new File(getBaseDir(), SELECTED_WFS_FILE);
            if (wfsFile.exists()) {
                wfsName = restoreWFSName(SELECTED_WFS_FILE);
            } else {
                wfsName = System.getProperty(DEFAULT_WORLD_PROP, DEFAULT_WORLD);
            }
        } catch (IOException ioe) {
            logger.log(Level.WARNING, "Error reading WFS file", ioe);
        }
    }
    /**
     * Add the Darkstar distribution file
     * @return a list containing the core distribution file as well
     * as the Darkstar distribution file
     */
    @Override
    public Collection<String> getDeployFiles() {
        // add all the files from the superclass
        Collection<String> files = super.getDeployFiles();

        // and the Darkstar server jar
        files.add("wonderland-server-dist.zip");

        // now add each module
        try {
            for (String module : getModuleChecksums().getChecksums().keySet()) {
                files.add(module);
            }
        } catch (IOException ioe) {
            logger.log(Level.WARNING, "Error reading module checksums", ioe);
        }

        return files;
    }

    /** Default Darkstar properties */
    @Override
    public Properties getDefaultProperties() {
        Properties props = new Properties();
        props.setProperty("sgs.port", String.valueOf(DEFAULT_PORT));
        props.setProperty("wonderland.web.server.url", webserverURL);
        return props;
    }

    @Override
    public synchronized void start(Properties props) throws RunnerException {
        if(snapshot!=null) {
            throw new IllegalStateException("Snapshot in progress");
        }
        publicAddress = props.getProperty(PUBLIC_ADDRESS_PROP);
       
        try {
            super.start(props);
        } finally {
            // reset the module checksums
            moduleChecksums.remove();
        }
    }
  
    /**
     * Deploy files to the Darkstar server with the given properties.
     * This method first manages deploying any modules specified by the module
     * manager, and also detects any Darkstar runners or services we need
     * to install
     *
     * @param props the properties to run with
     * @throws IOException if there is an error deploying files
     */
    @Override
    protected void deployFiles(Properties props) throws IOException {
        ModuleManager mm = ModuleManager.getModuleManager();
       
        // first tell the module manager to remove any modules scheduled for
        // removal
        mm.uninstallAll();

        // next tell the module manager to install any pending modules
        mm.installAll();

        // then call the super class's deployFiles() method, which will
        // call the other methods in this class
        super.deployFiles(props);

        // set the relevant properties before super.start() is called
        setDarkstarProperties(props);
    }

    /**
     * Update the properties associated with this deployer before running.
     * This will search for all managers and services declared by any
     * module jar files, and also set the current port for Darkstar
     */
    protected void setDarkstarProperties(Properties props)
        throws IOException
    {
        // go through al the module jars looking for any Darkstar managers and
        // services
        managers = new ArrayList<String>();
        services = new ArrayList<String>();

        File[] moduleFiles = getModuleDir().listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar");
            }
        });

        if (moduleFiles != null) {
            for (File moduleFile : moduleFiles) {
                checkForServices(moduleFile, managers, services);
            }
        }

        // turn the captured manager and service names into properties
        if (managers.size() > 0) {
            StringBuffer sb = new StringBuffer();
            for (String manager : managers) {
               sb.append(":" + manager);
            }
            props.put("sgs.managers", sb.toString());
        }

        if (services.size() > 0) {
            StringBuffer sb = new StringBuffer();
            for (String service : services) {
               sb.append(":" + service);
            }
            props.put("sgs.services", sb.toString());
        }

        // set the current port & WFS URL to the ones we are now running with.
        // This will stay valid until the runner is stopped.
        currentPort = getPort(props);
        currentInternalPort = getInternalPort(props);
        currentWFSName = getWFSName();

        // see if we need to force a coldstart
        if (checkColdstart(props)) {
            props.put("sgs.coldstart", "true");
        }

        // set the WFS URL to load.  This will only be used in the case of
        // a cold start
        if (currentWFSName != null) {
            props.put("sgs.wfs.root", currentWFSName);
        }
    }

    /**
     * Deploy zip files as normal using the superclass.  Copy any
     * .jar files (which are assumed to be modules) into the modules
     * directory
     * @param deploy the file to deploy
     * @throws IOException
     */
    @Override
    protected void deployFile(File deploy) throws IOException {
        if (deploy.getName().endsWith(".jar")) {
            File out = new File(getModuleDir(), deploy.getName());
            RunUtil.writeToFile(new FileInputStream(deploy), out);
        } else {
            super.deployFile(deploy);
        }
    }

    @Override
    protected RunnerChecksums getServerChecksums() throws IOException {
        // get the server checksums
        RunnerChecksums serverChecksums = super.getServerChecksums();

        // now add in the checksums for the modules
        Map<String, RunnerChecksum> checksums = serverChecksums.getChecksums();
        checksums.putAll(getModuleChecksums().getChecksums());
        serverChecksums.setChecksums(checksums);
        return serverChecksums;
    }

    /**
     * Get the module checksums from the thread-local variable.  This is only
     * valid during the method calls within a single invocation of start()
     */
    protected synchronized RunnerChecksums getModuleChecksums()
        throws IOException
    {
        RunnerChecksums out = moduleChecksums.get();
        if (out == null) {
            // read in the new checksums from the server
            URL checksumURL = new URL(webserverURL + CHECKSUM_URL);
            try {
                Reader in = new InputStreamReader(checksumURL.openStream());
                out = RunnerChecksums.decode(in);

                moduleChecksums.set(out);
            } catch (JAXBException je) {
                IOException ioe = new IOException("Error reading checksums " +
                                                  "from " + checksumURL);
                ioe.initCause(je);
                throw ioe;
            }
        }

        return out;
    }

    /**
     * Force a coldstart
     */
    public void forceColdstart() {
        File coldstartFile = new File(getRunDir(), COLDSTART_FILE);
        try {
            coldstartFile.createNewFile();
        } catch (IOException ioe) {
            logger.log(Level.WARNING, "Error creating " + coldstartFile, ioe);
        }
    }

    /**
     * Check whether we need to force a cold start.  Right now, this just
     * checks the global persistence type, and if the WFS URL has changed since
     * the last time we ran.
     * @param props the properties we are starting with
     * @return true if a coldstart is required, or false if a warm start is OK
     */
    protected boolean checkColdstart(Properties props) {
        if (getPersistenceType(props) == PersistenceType.NONE) {
            System.out.println("[Coldstart] Persistence type: NONE");
            // forece a coldstart
            return true;
        }

        // see if a coldstart file exists
        File coldstartFile = new File(getRunDir(), COLDSTART_FILE);
        if (coldstartFile.exists()) {
            // remove the file, since we are doing a coldstart now
            coldstartFile.delete();
            return true;
        }

        // see if the WFS URL has changed since the last time we ran
        String oldWFSName = null;
        try {
            oldWFSName = restoreWFSName(LAST_WFS_FILE);
        } catch (IOException ioe) {
            logger.log(Level.WARNING, "Error restoring WFS URL", ioe);
        }

        if (oldWFSName == null) {
            return (currentWFSName != null);
        } else {
            return !oldWFSName.equals(currentWFSName);
        }
    }

    /**
     * Get a runner info for this runner
     * @return a runner info, including hostname and port
     */
    @Override
    public RunnerInfo getRunnerInfo() {
        // get the runner info
        RunnerInfo info = super.getRunnerInfo();

        // add our properties
        info.setProperty(HOSTNAME_PROP, getHostname());
        info.setProperty(HOSTNAME_INTERNAL_PROP, getInternalHostname());
        info.setProperty(PORT_PROP, String.valueOf(getPort()));
        info.setProperty(PORT_INTERNAL_PROP, String.valueOf(getInternalPort()));
        info.setProperty(WFSNAME_PROP, getWFSName());
        return info;

    }

    /**
     * Get the Darkstar server name for clients to connect to.
     * @return the external hostname of the Darkstar server
     */
    public String getHostname() {
        // first, see if the Darkstar server has a public hostname we should
        // be giving out instead of the internal one
        String hostname = publicAddress;
        if (hostname == null) {
            // fall back to the internal address, which is guaranteed to be set
            hostname = System.getProperty(Constants.WEBSERVER_HOST_PROP);
        }

        return hostname;
    }

    /**
     * Get the Darkstar internal hostname for clients to connect to.
     * @return the internal hostname of the Darkstar server
     */
    public String getInternalHostname() {
        String hostname = System.getProperty(Constants.WEBSERVER_HOST_INTERNAL_PROP);
        if (hostname == null) {
            return System.getProperty(Constants.WEBSERVER_HOST_PROP);
        }

        return hostname;
    }

    /**
     * Get the Darkstar server port for clients to connect to.  This method
     * returns the port of the currently running server.  If the server
     * is not running, it returns what the port will be the next time the
     * server starts up.
     * @return the port
     */
    public synchronized int getPort() {
        // if the server is running, us the current port variable
        if (getStatus() == Status.RUNNING ||
                getStatus() == Status.STARTING_UP)
        {
            return currentPort;
        } else {
            return getPort(RunManager.getInstance().getStartProperties(this));
        }
    }
   
    /**
     * Get the internal Darkstar server port for clients to connect to.  This
     * method returns the port of the currently running server.  If the server
     * is not running, it returns what the port will be the next time the
     * server starts up.
     * @return the port
     */
    public synchronized int getInternalPort() {
        // if the server is running, us the current port variable
        if (getStatus() == Status.RUNNING ||
                getStatus() == Status.STARTING_UP)
        {
            return currentInternalPort;
        } else {
            return getInternalPort(RunManager.getInstance().getStartProperties(this));
        }
    }
   
    /**
     * Get the port to run on from a set of properties
     * @param properties the properties to look at
     * @return the port to run on
     */
    private int getPort(Properties props) {
        // determine the current port
        String portProp = props.getProperty(PUBLIC_PORT_PROP);
        if (portProp != null) {
            return Integer.parseInt(portProp);
        } else {
            return getInternalPort(props);
        }
    }
   
    /**
     * Get the port to run on from a set of properties
     * @param properties the properties to look at
     * @return the port to run on
     */
    private int getInternalPort(Properties props) {
        // determine the current port
        String portProp = props.getProperty("sgs.port");
        if (portProp != null) {
            return Integer.parseInt(portProp);
        } else {
            return DEFAULT_PORT;
        }
    }

    /**
     * Get the current WFS name
     * @return the current WFS name
     */
    public String getWFSName() {
        return wfsName;
    }

    /**
     * Set the current WFS name
     * @param name the name of the WFS to load the world from, or null to load
     * an empty world
     */
    public void setWFSName(String wfsName) {
        this.wfsName = wfsName;

        try {
            saveWFSName(wfsName, SELECTED_WFS_FILE);
        } catch (IOException ioe) {
            logger.log(Level.WARNING, "Error saving WFS name", ioe);
        }
    }

    /**
     * Save the current WFS name to disk
     * @param wfsName the name to save
     * @param fileName the file name to save to
     */
    protected void saveWFSName(String wfsName, String fileName) throws IOException {
        File wfsFile = new File(getBaseDir(), fileName);
        PrintWriter out = new PrintWriter(new FileWriter(wfsFile));
      
        if (wfsName != null) {
            out.println(wfsName);
        }

        out.close();
    }

    /**
     * Restore a WFS URL from disk
     * @param fileName the file name to read from
     * @return the current URL, or null if no URL is set
     */
    protected String restoreWFSName(String fileName) throws IOException {
        File wfsFile = new File(getBaseDir(), fileName);
        if (!wfsFile.exists()) {
            return null;
        }

        BufferedReader br = new BufferedReader(new FileReader(wfsFile));
        return br.readLine();
    }

    /**
     * Create a snapshot with the given name.  The runner must be stopped
     * to call this method.
     * @param name the snapshot name to create
     * @return the snapshot that was created
     * @throws RunnerException if there is an error creating the snapshot
     */
    public void createSnapshot(String name) throws RunnerException {
        if (getStatus() != Status.NOT_RUNNING) {
            throw new IllegalStateException("Snapshots require server to " +
                                            "  be stopped");
        }
        synchronized(this) {
            if(snapshot != null) {
                throw new IllegalStateException("Snapshot in progress");
            }
           
            snapshot = new DarkstarSnapshotRunner(name);
        }       
        try {
            // run the snapshot runner using the RunManager
            RunManager.getInstance().start(snapshot, false);

            // wait for the snapshot runner to exit, which it should do
            // as soon as it finishes starting up
            StatusWaiter waiter = new StatusWaiter(snapshot, Status.NOT_RUNNING);
            waiter.waitFor();
        } catch (InterruptedException ie) {
            // not much we can do here...
        } finally {
            synchronized(this) {
                snapshot = null;
            }
        }
    }

    /**
     * Get the Darkstar server module directory
     * @return the server module directory
     */
    protected File getModuleDir() {
        File moduleDir = new File(getRunDir(), "modules");
        moduleDir.mkdirs();
        return moduleDir;
    }

    /**
     * Check a .jar file for any Darkstar managers and services.
     * @param f the file to check
     * @param managers the list of Darkstar managers to add to
     * @param services the list of Darkstar services to add to
     */
    private void checkForServices(File f, List<String> managers,
                                  List<String> services)
        throws IOException
    {
        JarFile jf = new JarFile(f);

        // look for services
        ZipEntry ze = jf.getEntry("META-INF/services/com.sun.sgs.service.Service");
        if (ze != null) {
            addAll(jf.getInputStream(ze), services);
        }

        // loog for managers
        ze = jf.getEntry("META-INF/services/com.sun.sgs.service.Manager");
        if (ze != null) {
            addAll(jf.getInputStream(ze), managers);
        }
    }

    /**
     * Add all services in a file to a list
     * @param is the InputStream containing the list of files to add
     * @param list the list to add entries to
     */
    private void addAll(InputStream is, List<String> list) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(is));
        String line;
        while ((line = in.readLine()) != null) {
            if (line.trim().length() > 0) {
                list.add(line.trim());
            }
        }
    }

    /**
     * Get the persistence type from a property.  The given properties will
     * be checked fist, followed by the system properties.  If neither of
     * those has a value, the default value will be returned
     * @param props the properties to check first
     * @return the current persistence type
     */
    protected PersistenceType getPersistenceType(Properties props) {
        String persistStr = props.getProperty(PERSISTENCE_TYPE_PROP);
        if (persistStr == null) {
            persistStr = System.getProperty(PERSISTENCE_TYPE_PROP);
        }

        if (persistStr != null) {
            return PersistenceType.parsePersistenceType(persistStr);
        } else {
            return PERSISTENCE_TYPE_DEFAULT;
        }
    }

    /**
     * Override the setStatus() method to ignore the RUNNING status.  Instead,
     * we notify other processes that Darkstar is RUNNING when the output
     * reader gets the startup line successfully.
     * @param status the status to set
     */
    @Override
    protected void setStatus(Status status) {
        if (status == Status.RUNNING) {
            return;
        }
       
        super.setStatus(status);
    }
   
    /**
     * Override the createOutputReader method to return a
     * DarkstarOutputReader that will notify us when Darkstar is really up
     */
    @Override
    protected DarkstarOutputReader createOutputReader(InputStream in,
                                                      Logger out)
    {
        return new DarkstarOutputReader(in, out, new DarkstarStartup() {
            public void darkstarStarted() {
                DarkstarRunnerImpl.this.darkstarStarted();
            }
        });
    }
   
    /**
     * Called when Darkstar starts up
     */
    protected void darkstarStarted() {
        // save the current WFS URL
        try {
            saveWFSName(currentWFSName, LAST_WFS_FILE);
        } catch (IOException ioe) {
            logger.log(Level.WARNING, "Error saving WFS URL", ioe);
        }

        super.setStatus(Status.RUNNING);
    }
   
    /**
     * Wait for Darkstar to fully startup
     */
    protected class DarkstarOutputReader extends BaseRunner.ProcessOutputReader {
        private static final String DARKSTAR_STARTUP =
                "Wonderland: application is ready";

        private DarkstarStartup runner;

        protected DarkstarOutputReader(InputStream in, Logger out,
                                       DarkstarStartup runner)
        {
            super (in, out);
            this.runner = runner;
        }
       
        @Override
        protected void handleLine(String line) {
            // see if this is a Darkstar startup message
            if (isStartupLine(line)) {
                runner.darkstarStarted();
            }
           
            super.handleLine(line);
        }

        /**
         * Determine when we have started up
         * @return true if startup is complete, or false if not
         */
        protected boolean isStartupLine(String line) {
            return line.contains(DARKSTAR_STARTUP);
        }
    }

    /**
     * Create a world snapshot
     */
    protected class DarkstarSnapshotRunner extends BaseRunner
        implements DarkstarStartup
    {
        private String snapshotName;

        protected DarkstarSnapshotRunner(String snapshotName) {
            super();

            this.snapshotName = snapshotName;
        }

        @Override
        public String getName() {
            return DarkstarRunnerImpl.this.getName() + " Snapshot";
        }

        @Override
        public Properties getDefaultProperties() {
            return RunManager.getInstance().getStartProperties(DarkstarRunnerImpl.this);
        }

        @Override
        public synchronized void start(Properties props) throws RunnerException {
            props.put("org.jdesktop.wonderland.server.wfs" +
                      ".exporter.CellExportService.export.on.startup",
                      snapshotName);

            // setup the other Darkstar properties
            try {
                setDarkstarProperties(props);
            } catch (IOException ioe) {
                throw new RunnerException(ioe);
            }
           
            // make sure coldstart is false, otherwise we'll overwrite
            // the database before taking the snapshot
            props.remove("sgs.coldstart");

            // now run
            super.start(props);
        }

        @Override
        protected void deployFiles(Properties props) throws IOException {
            // do nothing
        }

        @Override
        protected synchronized File getBaseDir() {
            return DarkstarRunnerImpl.this.getBaseDir();
        }

        @Override
        protected ProcessOutputReader createOutputReader(InputStream in,
                                                         Logger out)
        {
            return new SnapshotOutputReader(in, out, this);
        }

        public void darkstarStarted() {
            // once the startup is complete, the snapshot has been written
            // and we can exit
            this.stop();
        }

        class SnapshotOutputReader extends DarkstarOutputReader {
            private Pattern success = Pattern.compile("Exported \\d+ cells.");
            private Pattern failure = Pattern.compile("Error creating snapshot");

            protected SnapshotOutputReader(InputStream in, Logger out,
                                           DarkstarStartup runner)
            {
                super (in, out, runner);
            }

            @Override
            protected boolean isStartupLine(String line) {
                Matcher m = success.matcher(line);
                if (m.find()) {
                    return true;
                }

                m = failure.matcher(line);
                if (m.matches()) {
                    return true;
                }

                return false;
            }
        }
    }

    protected static interface DarkstarStartup {
        void darkstarStarted();
    }
}
TOP

Related Classes of org.jdesktop.wonderland.modules.darkstar.server.DarkstarRunnerImpl$DarkstarOutputReader

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.