/******************************************************************************* * Copyright (c) 2005 RadRails.org and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.radrails.server.core; import java.io.BufferedReader; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IDebugEventSetListener; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationType; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.IStreamListener; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.IStreamMonitor; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.Display; import org.radrails.db.core.IDatabaseConstants; import org.radrails.rails.core.RailsLog; import org.radrails.rails.internal.core.RailsPlugin; import org.radrails.rails.internal.core.RailsRuntime; import org.rubypeople.rdt.core.SocketUtil; import org.rubypeople.rdt.debug.ui.RdtDebugUiConstants; import org.rubypeople.rdt.launching.IRubyLaunchConfigurationConstants; import org.rubypeople.rdt.launching.IVMInstall; import org.rubypeople.rdt.launching.RubyRuntime; import com.aptana.rdt.core.gems.Gem; import com.aptana.rdt.core.gems.IGemManager; /** * This class represents a web server that is capable of running Rails applications. It is an observable object, and is * observed by the ServersView class. Instead of traditional <code>setXXX</code> methods, <code>updateXXX</code> methods * are provided. These update methods will set the attribute and then notify all observers of the change. * * @author mkent */ public class Server extends Observable { private static final String CONSOLE_ENCODING = "UTF-8"; private static final int THIRTY_SECONDS = 30 * 1000; public static final String DEFAULT_RAILS_HOST = "0.0.0.0"; public static final String DEFAULT_RADRAILS_HOST = "127.0.0.1"; private IProject project; private String name; private String type; private String port; private String host = DEFAULT_RADRAILS_HOST; private String environment; private String runMode; private String status; private IProcess serverProcess; private int pid = -1; private boolean keepStarting; private Set<Observer> observers = new HashSet<Observer>(); /** * Constructor. Status is set to <code>IServerConstants.STOPPED</code> by default. * * @param project * the IProject the server is associated with * @param name * the name of the server * @param type * the type of the server (use {@link IServerConstants TYPE_XXX}) * @param host * the host/IP of the server * @param portNumber * the number of the port the server will operate on * @param environment * the runtime environment of the server (use {@link IDatabaseConstants ENV_XXX}) */ public Server(IProject project, String name, String type, String host, String portNumber, String environment) { Assert.isNotNull(project, "Server must have a non-null project"); this.project = project; this.name = name; this.type = type; if (this.type == null || this.type.trim().length() == 0) { this.type = IServerConstants.TYPE_WEBRICK; } this.host = host; if (this.host == null) { this.host = DEFAULT_RADRAILS_HOST; } this.port = portNumber; if (this.port == null || this.port.trim().length() == 0) { this.port = ServerManager.getInstance().getNextAvailablePort(); } else { try { // try to force to integer Integer.parseInt(this.port); } catch (NumberFormatException nfe) { this.port = ServerManager.getInstance().getNextAvailablePort(); } } this.environment = environment; if (this.environment == null || this.environment.trim().length() == 0) { this.environment = IDatabaseConstants.ENV_DEVELOPMENT; } this.status = IServerConstants.STOPPED; this.runMode = ILaunchManager.RUN_MODE; } /** * Constructor. Name defaults to projectNameServer, port defaults to next available, and environment defaults to * development. * * @param project * the location of the project the server is associated with * @param type * the type of server (Mongrel, WEBrick, Lighttpd) */ public Server(IProject project, String type) { this(project, project.getName() + "Server", type, DEFAULT_RADRAILS_HOST, ServerManager.getInstance() .getNextAvailablePort(), IDatabaseConstants.ENV_DEVELOPMENT); } public Server(IProject project, String serverName, String serverType, String port) { this(project, serverName, serverType, DEFAULT_RADRAILS_HOST, port, IDatabaseConstants.ENV_DEVELOPMENT); } /** * @return the location of the project the server is associated with */ public File getProjectFile() { return project.getLocation().toFile(); } /** * @return the name of the server */ public String getName() { return name; } /** * @return the port number that the server is available on */ public String getPort() { return port; } public int getPortInt() { try { return Integer.parseInt(port); } catch (Exception e) { return 3000; } } /** * @return the type of the server */ public String getType() { return type; } /** * @return the runtime environment of the server */ public String getEnvironment() { return environment; } /** * @return the status of the server */ public String getStatus() { return status; } /** * Sets the port number of the server and notifies observers of the change. * * @param port * the number of the port the server operates on */ public void updatePort(String port) { this.port = port; setChanged(); notifyServerObservers(IServerConstants.UPDATE); } /** * Sets the name of the server and notifies observers of the change. * * @param name * the name of the server */ public void updateName(String name) { this.name = name; setChanged(); notifyServerObservers(IServerConstants.UPDATE); } /** * Sets the environment of the server and notifies observers of the change. Use {@link IDatabaseConstants ENV_XXX} * * @param environment * the environment of the server */ public void updateEnvironment(String environment) { this.environment = environment; setChanged(); notifyServerObservers(IServerConstants.UPDATE); } /** * Sets the run mode of the server and notifies observers of the change. Use {@link ILaunchManager XXX_MODE} * * @param runMode * the run mode of the server */ public void updateRunMode(String runMode) { this.runMode = runMode; setChanged(); notifyServerObservers(IServerConstants.UPDATE); } /** * Sets the status of the server and notifies observers of the change. Use <code>IServerConstants.STARTED</code> or * <code>IServerConstants.STOPPED</code> * * @param status * the status of the server */ public void updateStatus(String status) { this.status = status; setChanged(); notifyServerObservers(IServerConstants.UPDATE); } public void updateHost(String newHost) { if (newHost == null) { newHost = DEFAULT_RAILS_HOST; } this.host = newHost; setChanged(); notifyServerObservers(IServerConstants.UPDATE); } /** * Updates the server status in a separate thread. */ protected void syncUpdateStatus(final String status) { Display.getDefault().syncExec(new Runnable() { public void run() { updateStatus(status); } }); } /** * Sets the changed flag. Clients should call this method before calling the <code>notifyObservers()</code> method * explicitly. */ public void touch() { setChanged(); } /** * @return true if the server's status is <code>IServerConstants.STARTED</code>, false otherwise. */ public boolean isStarted() { return status.equals(IServerConstants.STARTED); } /** * @return true if the server's status is <code>IServerConstants.STARTING</code>, false otherwise. */ public boolean isStarting() { return status.equals(IServerConstants.STARTING); } /** * @return true if the server's status is <code>IServerConstants.STOPPED</code>, false otherwise. */ public boolean isStopped() { return !isStarted() && !isStarting() && !isStopping(); } /** * @return true if the server's status is <code>IServerConstants.STOPPING</code>, false otherwise. */ public boolean isStopping() { return status.equals(IServerConstants.STOPPING); } /** * Starts the server. Subclass implementations must call {@link #setChanged() setChanged} and * {@link #notifyObservers() notifyObservers} after the server is started. Pass { * <code>IServerConstants.UPDATE</code> as the second argument to {@link #notifyObservers() notifyObservers}. */ public void start() { start(true); } private boolean isMongrel() { return getType().equals(IServerConstants.TYPE_MONGREL); } /** * Stops the server. Subclass implementations must call {@link #setChanged() setChanged} and * {@link #notifyObservers() notifyObservers} after the server is started. Pass <code>IServerConstants.UPDATE</code> * as the second argument to {@link #notifyObservers() notifyObservers}. */ public void stop() { if (serverProcess != null || pid != -1) { // Make sure to stop the start job, just in case the user is // stopping the server while it's still starting keepStarting = false; Job j = new Job("Stopping server") { protected IStatus run(IProgressMonitor monitor) { stopServer(); return Status.OK_STATUS; } }; j.schedule(); } } /** * Restart the WEBrick Server. * * @see org.radrails.server.core.Server#stop() */ public void restart() { Job j = new Job("Restarting server") { protected IStatus run(IProgressMonitor monitor) { keepStarting = false; stopServer(); startServer(false); return Status.OK_STATUS; } }; j.schedule(); } /** * Internal implementation of stopping the server. Attempts to terminate the Process object. */ private void stopServer() { syncUpdateStatus(IServerConstants.STOPPING); if (getRunMode().equals(ILaunchManager.DEBUG_MODE)) { // FIXME We need to properly kill the debugger! IDebugTarget debugtarget = serverProcess.getLaunch().getDebugTarget(); try { debugtarget.terminate(); } catch (DebugException e) { RailsLog.log(e); syncUpdateStatus(IServerConstants.STARTED); return; } } else { // try to kill the process if (serverProcess != null) { int tries = 0; while (!serverProcess.isTerminated() && tries < 10) { try { serverProcess.terminate(); Thread.sleep(10); tries++; } catch (DebugException e) { ServerLog.logError("Error terminating server process", e); } catch (InterruptedException e) { ServerLog.logError("Error sleeping", e); } } } if (!Platform.getOS().equals(Platform.OS_WIN32)) { if (pid < 0) { grabPid(); } try { if (pid > 0 && killProcess()) { pid = -1; syncUpdateStatus(IServerConstants.STOPPED); return; } } catch (CoreException e) { // ignore } catch (InterruptedException e) { // ignore } catch (IllegalThreadStateException e) { // ignore } } } if (serverProcess != null && serverProcess.isTerminated()) { pid = -1; } // Notify the view that the server has stopped syncUpdateStatus(IServerConstants.STOPPED); } private void grabPid() { BufferedReader reader = null; try { String[] allCmds = new String[] { "ps", "wwwx" }; Process p = DebugPlugin.exec(allCmds, null); reader = new BufferedReader(new InputStreamReader(p.getInputStream())); String line = null; String args = getProgramArguments(); while ((line = reader.readLine()) != null) { if (line.indexOf(args) != -1) { String modified = line.trim(); int index = modified.indexOf(' '); if (index == -1) continue; String pid = modified.substring(0, index); try { this.pid = Integer.parseInt(pid); break; } catch (NumberFormatException e) { // ignore } } } } catch (CoreException e) { ServerLog.log(e); } catch (IOException e) { ServerLog.log(e); } finally { try { if (reader != null) reader.close(); } catch (IOException e) { // ignore } } } private boolean killProcess() throws CoreException, InterruptedException { List<String> killCommands = new ArrayList<String>(); killCommands.add(getRubyScriptPath("kill.rb")); killCommands.add("INT"); killCommands.add("" + pid + ""); Process p = RailsRuntime.rubyExec((String[]) killCommands.toArray(new String[] {}), getProjectFile(), false); return 0 == p.exitValue(); } private boolean isWebrick() { return getType().equals(IServerConstants.TYPE_WEBRICK); } public void started(IProcess process) { try { serverProcess = process; IStreamMonitor stdOut = null; IStreamMonitor stdErr = null; // Prepare the stream listeners if (serverProcess.getStreamsProxy() != null) { stdOut = serverProcess.getStreamsProxy().getOutputStreamMonitor(); stdErr = serverProcess.getStreamsProxy().getErrorStreamMonitor(); } // Now add a listener for process being terminated, because user // could terminate from console view. DebugPlugin.getDefault().addDebugEventListener(new IDebugEventSetListener() { public void handleDebugEvents(DebugEvent[] events) { if (events == null) return; for (int i = 0; i < events.length; i++) { if (events[i].getKind() == DebugEvent.TERMINATE && serverProcess.equals(events[i].getSource())) { pid = -1; syncUpdateStatus(IServerConstants.STOPPED); } } } }); keepStarting = true; addPIDListener(stdOut); addServerStartedListeners(stdOut, stdErr); // Create a new console page for the server process SimpleDateFormat dateFormat = new SimpleDateFormat("MMM d, yyyy h:mm:ss aa"); String date = dateFormat.format(new Date()); serverProcess.setAttribute(IProcess.ATTR_PROCESS_LABEL, project.getName() + " - " + getDisplayName() + " Server - (" + date + ")"); // Wait for the server to start long start = System.currentTimeMillis(); while (keepStarting && !serverProcess.isTerminated()) { Thread.yield(); if ((start + THIRTY_SECONDS) < System.currentTimeMillis()) break; // Safety valve to exit loop in 30 seconds } keepStarting = false; if (serverProcess.isTerminated()) syncUpdateStatus(IServerConstants.STOPPED); if (this.status.equals(IServerConstants.STARTING)) syncUpdateStatus(IServerConstants.STARTED); } catch (Exception e) { ServerLog.logError("Error running the server command", e); syncUpdateStatus(e.getLocalizedMessage() + " See the log for details."); } } private void addServerStartedListeners(IStreamMonitor stdOut, IStreamMonitor stdErr) { if (isMongrel()) { addMongrelStartedListener(stdOut, stdErr); } else if (isWebrick()) { addServerStartedListener(stdErr, "WEBrick::HTTPServer#start"); } } private void addMongrelStartedListener(IStreamMonitor stdOut, IStreamMonitor stdErr) { addServerStartedListener(stdOut, "=> Ctrl-C to shutdown server"); addServerStartedListener(stdErr, "available at"); } private void addServerStartedListener(IStreamMonitor monitor, final String string) { if (monitor == null) return; monitor.addListener(new IStreamListener() { public void streamAppended(String text, IStreamMonitor monitor) { if (text == null) return; if (!keepStarting) { // Server has started monitor.removeListener(this); return; } String[] lines = text.split("\n"); for (int i = 0; i < lines.length; i++) { if (lines[i].indexOf(string) != -1) { keepStarting = false; monitor.removeListener(this); return; } } } }); } private void addPIDListener(IStreamMonitor stdOut) { if (stdOut == null) return; stdOut.addListener(new IStreamListener() { public void streamAppended(String text, IStreamMonitor monitor) { if (pid != -1) return; if (text == null) return; String[] lines = text.split("\n"); String num = lines[0].trim(); if (num.startsWith("\"")) { num = num.substring(1); } if (num.endsWith("\"")) { num = num.substring(0, num.length() - 1); } try { pid = Integer.parseInt(num.trim()); } catch (NumberFormatException e) { pid = -2; } monitor.removeListener(this); } }); } /** * Internal implementation of starting the server. Runs the server command, opens a console window, and attaches the * output streams to the console. * * @param publicLaunchConfig */ private void startServer(boolean publicLaunchConfig) { if (!SocketUtil.portFree(Integer.parseInt(getPort()))) { Display.getDefault().asyncExec(new Runnable() { public void run() { MessageDialog .openError( Display.getDefault().getActiveShell(), "Port not free", "The selected port for this server is not free. Perhaps the server is already running, or another server has the same port?"); } }); return; } try { ILaunchConfiguration config = findOrCreateLaunchConfiguration(publicLaunchConfig); int port = Integer.parseInt(getPort()); if (port < 1024) { ILaunchConfigurationWorkingCopy wc = config.getWorkingCopy(); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_IS_SUDO, true); config = wc.doSave(); } config.launch(runMode, null); } catch (CoreException e) { ServerLog.logError("Error running the server command", e); syncUpdateStatus(e.getLocalizedMessage() + " See the log for details."); } } /** * The type of server displayed in console name. * * @return */ protected String getDisplayName() { return getType(); } protected ILaunchConfiguration findOrCreateLaunchConfiguration(boolean publicLaunchConfig) throws CoreException { ILaunchConfigurationType configType = getRubyApplicationConfigType(); ILaunchConfiguration[] configs = getLaunchManager().getLaunchConfigurations(configType); List<ILaunchConfiguration> candidateConfigs = new ArrayList<ILaunchConfiguration>(configs.length); for (int i = 0; i < configs.length; i++) { ILaunchConfiguration config = configs[i]; boolean projectsEqual = config.getAttribute(IRubyLaunchConfigurationConstants.ATTR_PROJECT_NAME, "") .equals(getProject().getName()); if (projectsEqual) { boolean argumentsSame = config.getAttribute(IRubyLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, "").equals(getProgramArguments()); if (argumentsSame) { String vmName = config.getAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, ""); String vmType = config.getAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, ""); IVMInstall vm = RubyRuntime.getDefaultVMInstall(); // VMs // must // match // ? if (vmName.equals(vm.getName()) && vm.getVMInstallType().getId().equals(vmType)) candidateConfigs.add(config); } } } switch (candidateConfigs.size()) { case 0: return createServerLaunchConfiguration(publicLaunchConfig); default: return (ILaunchConfiguration) candidateConfigs.get(0); } } private ILaunchConfiguration createServerLaunchConfiguration(boolean publicLaunchConfig) { IPath railsRoot = RailsPlugin.findRailsRoot(project); if (railsRoot == null || railsRoot.segmentCount() == 0) { railsRoot = project.getLocation(); } else { railsRoot = project.getLocation().append(railsRoot); } String fileName = null; if (isMongrel() && isOldRails()) { fileName = getMongrelRailsScript(); } else { IPath serverScript = railsRoot.append("script").append("server"); if (project.getLocation().isPrefixOf(serverScript)) { fileName = serverScript.toPortableString().substring( project.getLocation().toPortableString().length() + 1); } else { fileName = serverScript.toPortableString(); } } ILaunchConfiguration config = null; try { ILaunchConfigurationType configType = getRubyApplicationConfigType(); ILaunchConfigurationWorkingCopy wc = configType.newInstance(null, RubyRuntime .generateUniqueLaunchConfigurationNameFrom(getName())); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_PROJECT_NAME, getProject().getName()); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_FILE_NAME, fileName); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, railsRoot.toFile() .getAbsolutePath()); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, RubyRuntime.getDefaultVMInstall() .getName()); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, RubyRuntime.getDefaultVMInstall() .getVMInstallType().getId()); wc.setAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, RdtDebugUiConstants.RUBY_SOURCE_LOCATOR); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, getProgramArguments()); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, "-e p(Process.pid.to_s) -e load(ARGV.shift)"); wc.setAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING, CONSOLE_ENCODING); Map<String, String> map = new HashMap<String, String>(); map.put(IRubyLaunchConfigurationConstants.ATTR_RUBY_COMMAND, "ruby"); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE_SPECIFIC_ATTRS_MAP, map); wc.setAttribute(IDebugUIConstants.ATTR_PRIVATE, !publicLaunchConfig); wc.setAttribute(IDebugUIConstants.ATTR_LAUNCH_IN_BACKGROUND, true); config = wc.doSave(); } catch (CoreException ce) { // ignore for now } return config; } private boolean isOldRails() { return doIsOldRails(RailsPlugin.getRailsVersion(project)); } private boolean doIsOldRails(String version) { // Assume we're newer than 1.1.3 by default now. Rails is way newer for most people try { if (version == null) return false; String[] numbers = version.split("\\."); if (numbers == null || numbers.length == 0) return true; if (numbers[0].equals("0")) return true; // if major version is less than 1, we're too old if (!numbers[0].equals("1")) return false; // if we're greater than 1, we're new int second = Integer.parseInt(numbers[1]); if (second > 1) return false; // if minor version is greater than 1, we're new if (second == 0) return true; // if minor version is 0, we're too old // if version so far is 1.1 ... if (numbers.length == 2) return true; // if there's no bugfix version, we're too old int third = Integer.parseInt(numbers[2]); if (third == 6) return true; if (third >= 3) return false; // if we're 1.1.3 or higher, we're new } catch (Exception e) { ServerLog.log(e); } return false; } private String getMongrelRailsScript() { return RailsPlugin.getInstance().getMongrelPath(); } private ILaunchConfigurationType getRubyApplicationConfigType() { return getLaunchManager().getLaunchConfigurationType(IRubyLaunchConfigurationConstants.ID_RUBY_APPLICATION); } private ILaunchManager getLaunchManager() { return DebugPlugin.getDefault().getLaunchManager(); } /** * Construct the arguments to script\server * * @return */ public String getProgramArguments() { StringBuffer args = new StringBuffer(); if (isMongrel() && isOldRails()) { args.append("start"); args.append(" -l "); args.append(getMongrelLogPath()); } else { args.append(getServerArg()); } args.append(" --port="); args.append(getPort()); args.append(" --environment="); args.append(getEnvironment()); if (!getHost().equals(DEFAULT_RAILS_HOST)) { // If user is running mongrel_rails, it doesn't like --binding if (args.toString().startsWith("start")) { args.append(" --address="); args.append(host); } else { args.append(" --binding="); args.append(host); } } return args.toString(); } private String getMongrelLogPath() { IPath path = getProject().getLocation().append(RailsPlugin.findRailsRoot(getProject())); path = path.append("log").append("mongrel.log"); return '"' + path.toOSString() + '"'; } /** * The argument to script\server which tells the type of server to launch. * * @return */ protected String getServerArg() { if (isWebrick()) { return "webrick"; } else if (isMongrel()) { return "mongrel"; } else if (isLighttpd()) { return "lighttpd"; } throw new IllegalStateException("Server has a type that isn't Mongrel, WEBrick or Lighttpd!"); } private boolean isLighttpd() { return getType().equals(IServerConstants.TYPE_LIGHTTPD); } /** * Writes a script from plugin jar to plugin metadata folder within the workspace. * * @param rubyFile * - The file to place on the filesystem * @return Absolute path to specified script file */ protected String getRubyScriptPath(String rubyFile) { String directoryFile = ServerPlugin.getInstance().getStateLocation().toOSString() + File.separator + rubyFile; File pluginDirFile = new File(directoryFile); if (!pluginDirFile.exists()) { BufferedReader input = null; FileWriter output = null; try { pluginDirFile.createNewFile(); URL u = ServerPlugin.getInstance().getBundle().getEntry("/ruby/" + rubyFile); input = new BufferedReader(new InputStreamReader(u.openStream())); output = new FileWriter(pluginDirFile); String line; while ((line = input.readLine()) != null) { output.write(line); output.write('\n'); } output.flush(); } catch (IOException e) { ServerLog.logError("Error copying script file from jar to metadata", e); } finally { if (output != null) { try { output.close(); } catch (IOException e) { // ignore } } if (input != null) { try { input.close(); } catch (IOException e) { // ignore } } } } String path = ""; try { path = pluginDirFile.getCanonicalPath(); } catch (IOException e) { ServerLog.logError("Error getting script file path", e); } return path; } public String getRunMode() { return runMode; } @Override public boolean equals(Object arg0) { if (!(arg0 instanceof Server)) return false; Server other = (Server) arg0; return getType().equals(other.getType()) && getPort().equals(other.getPort()) && getProject().equals(other.getProject()) && getEnvironment().equals(other.getEnvironment()); } public void updateType(String newType) { type = newType; setChanged(); notifyServerObservers(IServerConstants.UPDATE); } public void addServerObserver(Observer ob) { synchronized (observers) { observers.add(ob); } } public void notifyServerObservers(Object arg) { Set<Observer> copy; synchronized (observers) { copy = new HashSet<Observer>(observers); } for (Observer observer : copy) { observer.update(this, arg); } } public IProject getProject() { return project; } public IProcess getProcess() { return serverProcess; } public void deleteServerObserver(Observer ob) { synchronized (observers) { observers.remove(ob); } } public String getHost() { if (host == null) { return DEFAULT_RADRAILS_HOST; } return host; } public String getBrowserHost() { String host = getHost(); if (Platform.getOS().equals(Platform.OS_WIN32) && host.equals(DEFAULT_RAILS_HOST)) { return DEFAULT_RADRAILS_HOST; } return host; } public void start(final boolean publicLaunchConfig) { if (isMongrel()) { final IGemManager gemManager = RailsPlugin.getInstance().getGemManager(); if (gemManager != null && gemManager.isInitialized()) { if (!gemManager.gemInstalled("mongrel") && RailsPlugin.getInstance().getMongrelPath() == null) { // FIXME Pull out strings for translation if (MessageDialog .openQuestion(Display.getDefault().getActiveShell(), "Mongrel is not installed", "It appears that you do not have the mongrel gem installed. Would you like us to install it?")) { Job job = new Job("Installing mongrel") { @Override protected IStatus run(IProgressMonitor monitor) { return gemManager.installGem(new Gem("mongrel", Gem.ANY_VERSION, null), monitor); } }; job.setUser(true); job.schedule(); return; } } } } Job j = new Job("Starting server") { protected IStatus run(IProgressMonitor monitor) { startServer(publicLaunchConfig); return Status.OK_STATUS; } }; j.schedule(); } public boolean isLocalhost() { return getHost() != null && (getHost().equals(DEFAULT_RADRAILS_HOST) || getHost().equals(DEFAULT_RAILS_HOST) || getHost() .equals("localhost")); } /** * Special method to see if there's a process that matches this server already running... */ void checkIfLeftHanging() { if (Platform.getOS().equals(Platform.OS_WIN32)) return; grabPid(); if (this.pid != -1) { this.status = IServerConstants.STARTED; } } }