/** * Aptana Studio * Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.radrails.rails.core; import java.io.File; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.Set; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; 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.Path; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; 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.ILaunchesListener2; import org.eclipse.debug.core.model.IProcess; import com.aptana.core.epl.IMemento; import com.aptana.core.logging.IdeLog; import com.aptana.core.util.CollectionsUtil; import com.aptana.core.util.StringUtil; import com.aptana.ruby.debug.core.launching.IRubyLaunchConfigurationConstants; import com.aptana.ruby.debug.core.launching.InterruptingProcessFactory; import com.aptana.webserver.core.AbstractWebServer; import com.aptana.webserver.core.URLtoURIMapper; public class RailsServer extends AbstractWebServer { public static final String TYPE_ID = "org.radrails.rails.railsServer"; //$NON-NLS-1$ /** * Default values for IP/binding and port. */ public static final String DEFAULT_BINDING = "0.0.0.0"; //$NON-NLS-1$ public static final int DEFAULT_PORT = 3000; private static final String RAILS = "rails"; //$NON-NLS-1$ private static final String SERVER = "server"; //$NON-NLS-1$ private static final String SCRIPT = "script"; //$NON-NLS-1$ /** * Properties to persist that user can change */ private static final String ELEMENT_PORT = "port"; //$NON-NLS-1$ private static final String ELEMENT_HOST = "host"; //$NON-NLS-1$ private static final String ELEMENT_PROJECT = "project"; //$NON-NLS-1$ private IProject fProject; private String fMode; private ILaunch fLaunch; private Integer fPort; private String fHost; public RailsServer() { super(); this.fPort = DEFAULT_PORT; this.fHost = DEFAULT_BINDING; } public IStatus stop(boolean force, IProgressMonitor monitor) { // if there is no launch or it is terminated already, just make sure we're marked as stopped and return OK. if (getLaunch() == null || getLaunch().isTerminated()) { updateState(State.STOPPED); return Status.OK_STATUS; } updateState(State.STOPPING); try { getLaunch().terminate(); updateState(State.STOPPED); } catch (DebugException e) { updateState(State.STARTED); return new Status(IStatus.ERROR, RailsCorePlugin.PLUGIN_ID, Messages.RailsServer_StopFailedErrorMsg, e); } return Status.OK_STATUS; } public IStatus start(String mode, IProgressMonitor monitor) { // TODO Check to make sure this isn't already started? updateState(State.STARTING); try { ILaunchConfiguration config = findOrCreateLaunchConfiguration(fProject); if (config != null) { this.fMode = mode; this.fLaunch = config.launch(mode, monitor); DebugPlugin.getDefault().getLaunchManager().addLaunchListener(new ILaunchesListener2() { public void launchesRemoved(ILaunch[] launches) { // do nothing } public void launchesChanged(ILaunch[] launches) { // do nothing } public void launchesAdded(ILaunch[] launches) { // do nothing } public void launchesTerminated(ILaunch[] launches) { for (ILaunch launch : launches) { if (launch.equals(fLaunch)) { // TODO Remove ourselves as a listener? updateState(State.STOPPED); break; } } } }); updateState(State.STARTED); } } catch (CoreException e) { updateState(State.STOPPED); return e.getStatus(); } // We may throw a DebuggerNotFoundException which is a RuntimeException... catch (Exception e) { updateState(State.STOPPED); return new Status(IStatus.ERROR, RailsCorePlugin.PLUGIN_ID, e.getMessage(), e); } return Status.OK_STATUS; } public String getMode() { return this.fMode; } public ILaunch getLaunch() { return fLaunch; } public IProcess[] getProcesses() { return getLaunch().getProcesses(); } public String getHostname() { return this.fHost; } public int getPort() { return this.fPort; } public URI getDocumentRoot() { if (fProject == null) { return null; } IPath projectLocation = fProject.getLocation(); if (projectLocation == null) { return null; } File file = projectLocation.append("public").toFile(); //$NON-NLS-1$ if (file == null) { return null; } return file.toURI(); } public URL getBaseURL() { try { return new URL("http", getHostname(), getPort(), StringUtil.EMPTY); //$NON-NLS-1$ } catch (MalformedURLException e) { IdeLog.logError(RailsCorePlugin.getDefault(), e); return null; } } public URI resolve(IFileStore file) { return new URLtoURIMapper(getBaseURL(), getDocumentRoot()).resolve(file); } public IFileStore resolve(URI uri) { // TODO We need to implement a mapper that understands rails routing. Even a mapper that assumes // controller/action/id would be a good start... return new URLtoURIMapper(getBaseURL(), getDocumentRoot()).resolve(uri); } @Override public void loadState(IMemento memento) { super.loadState(memento); Integer port = memento.getInteger(ELEMENT_PORT); if (port != null) { this.fPort = port; } this.fHost = memento.getString(ELEMENT_HOST); String location = memento.getString(ELEMENT_PROJECT); if (location != null) { this.fProject = (IProject) ResourcesPlugin.getWorkspace().getRoot() .getContainerForLocation(Path.fromPortableString(location)); } // TODO Sniff to see if server is actually already started? } @Override public void saveState(IMemento memento) { super.saveState(memento); memento.putInteger(ELEMENT_PORT, this.fPort); memento.putString(ELEMENT_HOST, this.fHost); if (this.fProject != null && this.fProject.getLocation() != null) { memento.putString(ELEMENT_PROJECT, this.fProject.getLocation().toPortableString()); } } protected ILaunchConfiguration findOrCreateLaunchConfiguration(IProject railsProject) throws CoreException { StringBuilder args = new StringBuilder(); String filename = StringUtil.EMPTY; if (scriptServerExists(railsProject)) { IFile file = railsProject.getFile(new Path(SCRIPT).append(SERVER)); filename = file.getLocation().toOSString(); } else { IFile file = railsProject.getFile(new Path(SCRIPT).append(RAILS)); filename = file.getLocation().toOSString(); args.append(SERVER); } args.append(" --binding=").append(getHostname()); //$NON-NLS-1$ args.append(" --port=").append(getPort()); //$NON-NLS-1$ // Always generate a new launch config return createConfiguration(railsProject, filename, args.toString()); } private ILaunchConfiguration createConfiguration(IProject project, String rubyFile, String args) throws CoreException { ILaunchConfigurationType configType = getRubyLaunchConfigType(); ILaunchConfigurationWorkingCopy wc = configType.newInstance(null, getLaunchManager() .generateLaunchConfigurationName(project.getName())); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_FILE_NAME, rubyFile); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, project.getLocation().toOSString()); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, args); wc.setAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, IRubyLaunchConfigurationConstants.ID_RUBY_SOURCE_LOCATOR); wc.setAttribute(ILaunchManager.ATTR_PRIVATE, true); wc.setAttribute("org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND", false); //$NON-NLS-1$ wc.setAttribute(DebugPlugin.ATTR_PROCESS_FACTORY_ID, InterruptingProcessFactory.ID); return wc.doSave(); } protected ILaunchConfigurationType getRubyLaunchConfigType() { return getLaunchManager().getLaunchConfigurationType(IRubyLaunchConfigurationConstants.ID_RUBY_APPLICATION); } protected ILaunchManager getLaunchManager() { return DebugPlugin.getDefault().getLaunchManager(); } protected boolean scriptServerExists(IProject railsProject) { IFile scriptServer = railsProject.getFile(new Path(SCRIPT).append(SERVER)); return scriptServer != null && scriptServer.exists(); } public void setPort(int port) { this.fPort = port; } public void setHost(String host) { this.fHost = host; } public void setProject(IProject project) { this.fProject = project; } public IProject getProject() { return this.fProject; } public Set<String> getAvailableModes() { return CollectionsUtil.newSet(ILaunchManager.RUN_MODE, ILaunchManager.DEBUG_MODE); } }