package org.radrails.rails.internal.ui.wizards; import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IWorkspace; 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.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IDebugEventSetListener; 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.model.IProcess; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.progress.UIJob; import org.radrails.db.core.DatabasePlugin; import org.radrails.rails.core.RailsLog; import org.radrails.rails.internal.core.RailsPlugin; import org.radrails.rails.internal.ui.RailsInstallDialog; import org.radrails.rails.internal.ui.console.IRailsShellConstants; import org.radrails.rails.internal.ui.wizards.pages.WizardNewRailsProjectPage; import org.radrails.rails.ui.RailsUILog; import org.radrails.rails.ui.RailsUIPlugin; import org.radrails.server.core.IServerConstants; import org.radrails.server.core.Server; import org.radrails.server.core.ServerManager; import org.radrails.server.core.launching.IRailsAppLaunchConfigurationConstants; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.launching.IRubyLaunchConfigurationConstants; import org.rubypeople.rdt.launching.RubyRuntime; import com.aptana.ide.core.builder.UnifiedProjectBuilder; import com.aptana.ide.editors.UnifiedEditorsPlugin; import com.aptana.ide.editors.profiles.Profile; import com.aptana.rdt.AptanaRDTPlugin; import com.aptana.rdt.core.gems.IGemManager; import com.aptana.rdt.core.gems.Version; public class RailsProjectCreator implements IRunnableWithProgress { private static final String RAILS_VERSION_NOT_SUPPORTING_DATABASE_SWITCH = "1.0.0"; private IProject newProject; private IPath newPath; private boolean generateSkeleton; private boolean forceTerminalCommand = false; private String args; private String fRunMode = ILaunchManager.RUN_MODE; private String dbType; private String railsVersion; private boolean fStartServer; private RailsProjectCreator(IProject project, IPath path, boolean generateSkeleton, String dbType, String railsVersion) { this.newProject = project; this.newPath = path; this.generateSkeleton = generateSkeleton; this.dbType = dbType; this.railsVersion = railsVersion; } public RailsProjectCreator(String runMode, String args) { this(null, null, true, "", ""); this.fRunMode = runMode; // Grab project name, create a project StringTokenizer tokenizer = new StringTokenizer(args); String projectName = tokenizer.nextToken(); while (projectName.startsWith("--") || (projectName.startsWith("_") && projectName.endsWith("_"))) { projectName = tokenizer.nextToken(); } this.newProject = ResourcesPlugin.getWorkspace().getRoot() .getProject(projectName); this.args = args; Pattern p = Pattern.compile("(_\\d\\.\\d\\.\\d_ )?.*"); Matcher m = p.matcher(args); if (m.find()) { String raw = m.group(1); if (raw != null && raw.trim().length() > 0) { this.railsVersion = raw.trim(); this.railsVersion = this.railsVersion.substring(1, this.railsVersion.length() - 1); } } } public RailsProjectCreator(WizardNewRailsProjectPage page) { this(page.getProjectHandle(), page.getLocationPath(), page .getGenerateButtonSelection(), page.getDatabaseType(), page .getRailsVersion()); this.forceTerminalCommand = true; this.fStartServer = page.startServer(); } public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { if (generateSkeleton) { String railsPath = RailsPlugin.getInstance().getRailsPath(); if (railsPath == null || railsPath.trim().length() == 0) { UIJob dialog = new RailsInstallDialog(RailsUIPlugin .getInstance().getGemManager()); dialog.schedule(); return; } } monitor.beginTask("Creating project...", 40); // Create the project description IWorkspace workspace = ResourcesPlugin.getWorkspace(); IProjectDescription description = createProjectDescription(workspace); // Set the location path of the project IPath oldPath = Platform.getLocation(); if (newPath != null && !oldPath.equals(newPath)) { oldPath = newPath; description.setLocation(newPath); } // Create and open the project try { if (!newProject.exists()) { newProject.create(description, new SubProgressMonitor(monitor, 10)); } if (!newProject.isOpen()) { newProject.open(new SubProgressMonitor(monitor, 10)); } // Add the Rails nature, which will add Ruby nature if it needs to RailsPlugin.addRailsNature(newProject, new SubProgressMonitor( monitor, 10)); // Generate the Rails skeleton if requested if (generateSkeleton) { // Run the rails command to create the project files ILaunch launch = runRailsCommand(newProject.getLocation() .append("../").toFile(), newProject.getName(), newProject.getLocation().toFile().getName(), new SubProgressMonitor(monitor, 10)); if (launch != null) { final IProcess process = launch.getProcesses()[0]; 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 && process.equals(events[i] .getSource())) { finishCreatingProject(new NullProgressMonitor()); DebugPlugin.getDefault() .removeDebugEventListener( this); return; } } } }); } else { finishCreatingProject(monitor); } } } catch (CoreException e) { throw new InvocationTargetException(e); } // Generate a server if (mongrelNotInstalled()) { generateServer(IServerConstants.TYPE_WEBRICK); } else { generateServer(IServerConstants.TYPE_MONGREL); } monitor.done(); } private IProjectDescription createProjectDescription(IWorkspace workspace) { IProjectDescription description = workspace .newProjectDescription(newProject.getName()); description .setNatureIds(new String[] { "com.aptana.ide.project.nature.web" }); //$NON-NLS-1$ ICommand command = description.newCommand(); command.setBuilderName(UnifiedProjectBuilder.BUILDER_ID); description.setBuildSpec(new ICommand[] { command }); return description; } private boolean mongrelNotInstalled() { IGemManager gemManager = RailsUIPlugin.getInstance().getGemManager(); return gemManager != null && gemManager.isInitialized() && !gemManager.gemInstalled("mongrel"); } protected void finishCreatingProject(IProgressMonitor monitor) { IRubyProject rubyProject = RubyCore.create(newProject); createCodeAssistProfile(rubyProject); if (dbType.equals("derby")) { replaceDatabaseYML(); } replaceIndex(); try { RailsUIPlugin.addDefaultRailsLoadpaths(rubyProject, monitor); } catch (RubyModelException e) { RailsUILog.log(e); } overrideDocumentRoot(); String serverName = getServerName(); ILaunchConfiguration config = null; try { ILaunchConfigurationType configType = getLaunchManager() .getLaunchConfigurationType( IRailsAppLaunchConfigurationConstants.LAUNCH_TYPE_ID); ILaunchConfigurationWorkingCopy wc = configType.newInstance( null, getLaunchManager() .generateUniqueLaunchConfigurationNameFrom( newProject.getName())); wc.setAttribute(IRailsAppLaunchConfigurationConstants.PROJECT_NAME, newProject.getName()); wc.setAttribute(IRailsAppLaunchConfigurationConstants.SERVER_NAME, serverName); wc.setAttribute( IRailsAppLaunchConfigurationConstants.LAUNCH_BROWSER, true); wc.setAttribute( IRailsAppLaunchConfigurationConstants.USE_INTERNAL_BROWSER, true); config = wc.doSave(); } catch (CoreException e) { RailsUILog.log(e); } if (fStartServer && config != null) { startServer(config); } } private void overrideDocumentRoot() { try { RailsUIPlugin.overrideDocumentRoot(newProject); } catch (CoreException e) { RailsUILog.log(e); } } private void startServer(final ILaunchConfiguration c) { Job job = new Job("Starting rails server") { @Override protected IStatus run(IProgressMonitor monitor) { try { c.launch(ILaunchManager.RUN_MODE, monitor); } catch (CoreException e) { RailsUILog.log(e); return e.getStatus(); } return Status.OK_STATUS; } }; job.schedule(); } private String getServerName() { Collection<Server> servers = ServerManager.getInstance() .getServersForProject(newProject); String serverName = null; if (servers.size() > 0) { Server server = servers.iterator().next(); serverName = server.getName(); } return serverName; } private void replaceIndex() { Job job = new Job("Replace project index file") { protected IStatus run(IProgressMonitor monitor) { IFile sample = newProject.getFile("public/index.html"); try { if (sample.exists()) { sample.delete(true, null); } Version theVersion = getRailsVersion(); if (theVersion != null && theVersion.isLessThan("2.0.0")) { sample.create( this.getClass().getResourceAsStream( "/resources/index_1.2.html"), true, null); } else { sample.create( this.getClass().getResourceAsStream( "/resources/index.html"), true, null); } } catch (CoreException e) { return e.getStatus(); } return Status.OK_STATUS; } }; job.schedule(); } protected Version getRailsVersion() { try { if (railsVersion == null || railsVersion.trim().length() == 0) { List<Version> availableVersions = AptanaRDTPlugin.getDefault() .getGemManager().getVersions("rails"); if (availableVersions == null || availableVersions.isEmpty()) return null; Collections.sort(availableVersions); return availableVersions.get(availableVersions.size() - 1); } return new Version(railsVersion); } catch (Exception e) { return null; } } private void replaceDatabaseYML() { Job job = new Job("Create database.yml for derby") { @Override protected IStatus run(IProgressMonitor monitor) { DatabasePlugin.startDerby(); String pathToFile = newProject.getLocation() .append("config/database.yml").toFile().toString(); String projectName = newProject.getName(); projectName = projectName.replace(' ', '_'); String newContents = "development:\n" + " adapter: jdbc\n" + " driver: org.apache.derby.jdbc.ClientDriver\n" + " url: jdbc:derby://localhost/" + projectName + "_development;create=true\n" + " username: app\n" + " password: app\n" + " \n" + "# Warning: The database defined as 'test' will be erased and\n" + "# re-generated from your development database when you run 'rake'.\n" + "# Do not set this db to the same as development or production.\n" + "test:\n" + " adapter: jdbc\n" + " driver: org.apache.derby.jdbc.ClientDriver\n" + " url: jdbc:derby://localhost/" + projectName + "_test;create=true\n" + " username: app\n" + " password: app\n" + " \n" + "production:\n" + " adapter: jdbc\n" + " driver: org.apache.derby.jdbc.ClientDriver\n" + " url: jdbc:derby://localhost/" + projectName + "_production;create=true\n" + " username: app\n" + " password: app\n"; boolean success = writeNewContents(pathToFile, newContents); if (!success) { return new Status( IStatus.ERROR, RailsUIPlugin.getPluginIdentifier(), -1, "Was unable to overwrite database.yml file with configuration set to use derby.", null); } DatabasePlugin.startDerby(); return Status.OK_STATUS; } private boolean writeNewContents(final String pathToFile, String newContents) { PrintWriter writer = null; try { writer = new PrintWriter(new FileWriter(pathToFile)); writer.println(newContents); } catch (FileNotFoundException fnfe) { UIJob job = new UIJob("Display error") { public IStatus runInUIThread(IProgressMonitor monitor) { MessageDialog.openError(Display.getDefault() .getActiveShell(), "Error creating derby database.yml", "File " + pathToFile + " not found"); return Status.OK_STATUS; } }; job.schedule(); return false; } catch (final IOException ioe) { UIJob job = new UIJob("Display error") { public IStatus runInUIThread(IProgressMonitor monitor) { MessageDialog.openError(Display.getDefault() .getActiveShell(), "Error creating derby database.yml", ioe .getLocalizedMessage()); return Status.OK_STATUS; } }; job.schedule(); return false; } finally { if (writer != null) writer.close(); } return true; } }; job.schedule(); } private void generateServer(String type) { Server s = new Server(newProject, newProject.getName(), type, Server.DEFAULT_RAILS_HOST, null, null); ServerManager.getInstance().addServer(s); } /** * Create a profile for the included prototype/scriptaculous * * @param rubyProject */ private void createCodeAssistProfile(final IRubyProject rubyProject) { Display.getDefault().asyncExec(new Runnable() { public void run() { String profilePath = rubyProject.getProject().getLocation() .toFile().toURI().toString(); if (profilePath.endsWith("/") || profilePath.endsWith("\\")) { profilePath = profilePath.substring(0, profilePath.length() - 1); } Profile profile = UnifiedEditorsPlugin .getDefault() .getProfileManager() .createProfile(rubyProject.getElementName(), profilePath); IPath path = rubyProject.getProject().getLocation() .append("public").append("javascripts"); String[] uris = new String[5]; uris[0] = path.append("prototype.js").toFile().toURI() .toString(); uris[1] = path.append("effects.js").toFile().toURI().toString(); uris[2] = path.append("dragdrop.js").toFile().toURI() .toString(); uris[3] = path.append("controls.js").toFile().toURI() .toString(); uris[4] = path.append("application.js").toFile().toURI() .toString(); profile.addURIs(uris); UnifiedEditorsPlugin.getDefault().getProfileManager() .addProfile(profile); UnifiedEditorsPlugin.getDefault().getProfileManager() .setCurrentProfile(profile); } }); } private ILaunchConfigurationType getRubyApplicationConfigType() { return getLaunchManager().getLaunchConfigurationType( IRubyLaunchConfigurationConstants.ID_RUBY_APPLICATION); } private ILaunchManager getLaunchManager() { return DebugPlugin.getDefault().getLaunchManager(); } /** * Runs the rails command to create a new project. * * @param location * the full path to the project root * @param args * the arguments to the rails command * @param monitor * the progress monitor for the operation */ private ILaunch runRailsCommand(File location, String projName, String appName, IProgressMonitor monitor) { if (appName.indexOf(" ") != -1 && appName.charAt(0) != '"') { // surround with quotes appName = '"' + appName + '"'; } String rails = RailsPlugin.getInstance().getRailsPath(); try { ILaunchConfigurationType configType = getRubyApplicationConfigType(); ILaunchConfigurationWorkingCopy wc = configType.newInstance(null, RubyRuntime .generateUniqueLaunchConfigurationNameFrom("rails_" + appName)); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_FILE_NAME, rails); wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, RubyRuntime.getDefaultVMInstall().getName()); wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, RubyRuntime.getDefaultVMInstall().getVMInstallType() .getId()); wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, getArgs(appName)); wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, ""); if (forceTerminalCommand) wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_TERMINAL_COMMAND, "rails " + getArgs(appName)); wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_PROJECT_NAME, projName); wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, location.toString()); wc.setAttribute(IRubyLaunchConfigurationConstants.ATTR_IS_SUDO, false); wc.setAttribute(IDebugUIConstants.ATTR_PRIVATE, true); wc.setAttribute(IDebugUIConstants.ATTR_LAUNCH_IN_BACKGROUND, false); wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_REQUIRES_REFRESH, true); wc.setAttribute( IRubyLaunchConfigurationConstants.ATTR_USE_TERMINAL, IRailsShellConstants.TERMINAL_ID); ILaunchConfiguration config = wc.doSave(); return config.launch(fRunMode, monitor); } catch (CoreException e) { RailsLog.logError("Error running rake task", e); } return null; } public String getArgs(String appName) { if (this.args != null) return args; String generatedArgs = ""; if (railsVersion != null) generatedArgs += "_" + railsVersion + "_ "; Version railsVersion = getRailsVersion(); // Handle rails 3 needing "new" arg if (railsVersion != null) { boolean newArg = false; if (railsVersion.getMajor() > 3) { newArg = true; } else if (railsVersion.getMajor() == 3) { if (railsVersion.getMinor() == 0 && railsVersion.getMicro() == 0 && railsVersion.getQualifier().length() != 0) { if ("beta4".equals(railsVersion.getQualifier())) { newArg = true; } } else { newArg = true; } } if (newArg) { generatedArgs += "new "; } } generatedArgs += appName; if (railsVersion != null && railsVersion .isLessThanOrEqualTo(RAILS_VERSION_NOT_SUPPORTING_DATABASE_SWITCH)) { return generatedArgs; } String dbTypeToUse = dbType; if (dbType.equals("derby")) { dbTypeToUse = "sqlite3"; } generatedArgs += " -d " + dbTypeToUse; return generatedArgs; } public IProject getProject() { return newProject; } }