/* * $Id$ * $HeadURL$ * * ============================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package runjettyrun; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; 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.Status; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.sourcelookup.ISourceContainer; import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; import org.eclipse.debug.core.sourcelookup.containers.DefaultSourceContainer; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.internal.launching.JavaSourceLookupDirector; import org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate; import org.eclipse.jdt.launching.ExecutionArguments; import org.eclipse.jdt.launching.IRuntimeClasspathEntry; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.VMRunnerConfiguration; import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer; import runjettyrun.utils.ProjectUtil; import runjettyrun.utils.RunJettyRunClasspathResolver; import runjettyrun.utils.RunJettyRunClasspathUtil; import runjettyrun.utils.RunJettyRunLaunchConfigurationUtil; import runjettyrun.utils.RunJettyRunSourceLookupUtil; /** * Launch configuration type for Jetty. Based on * org.eclipse.jdt.launching.JavaLaunchDelegate. * * @author hillenius */ public class JettyLaunchConfigurationType extends AbstractJavaLaunchConfigurationDelegate { private static HashMap<String, ILaunch> launcher = new HashMap<String, ILaunch>(); private JettyLaunchConfigurationClassPathProvider provider = new JettyLaunchConfigurationClassPathProvider(); /** * Here's what the WebApp classpath. That means all the classpath here just * like WEB-INF/classes or WEB-INF/lib , which is only for the specific * webapp project and will not used for the Jetty Instance. * * @param configuration * @return * @throws CoreException */ private String getWebappClasspath(ILaunchConfiguration configuration) throws CoreException { String[] webAppClasspathArray = getProjectClasspath(configuration); String webAppClasspath = ""; StringBuilder sb = new StringBuilder(); for (int i = 0; i < webAppClasspathArray.length; i++) { String path = webAppClasspathArray[i]; if (sb.length() > 0) sb.append(File.pathSeparator); sb.append(path); } webAppClasspath = sb.toString(); /** * The smallest limit for windows XP is 2048 */ if (webAppClasspath.length() > 1024) { File f = prepareClasspathFile(configuration, webAppClasspath); webAppClasspath = "file://" + f.getAbsolutePath(); } return webAppClasspath; } private String getScanlist(ILaunchConfiguration configuration) throws CoreException{ Set<String> paths = provider.getAllScanPathList(configuration); Map<String,String> nonChecked = getAttributeFromLaunchConfiguration(configuration, Plugin.ATTR_SCAN_FOLDER_NON_CHECKED); StringBuffer scanList = new StringBuffer(); for(String path:paths){ if(nonChecked.containsKey(path) && nonChecked.get(path).equals("1")){ continue; } if(!nonChecked.containsKey(path) && path.endsWith("test-classes")){ continue; } if (scanList.length() > 0){ scanList.append(File.pathSeparator); } scanList.append(path); } File f = prepareConfigFile(configuration, scanList.toString(),".scanlist"); return "file://" + f.getAbsolutePath(); } /** * Get working directory's absolute folder path. * * @param configuration * @return return the path if exist, or return null. * @throws CoreException */ private String getWorkingDirectoryAbsolutePath( ILaunchConfiguration configuration) throws CoreException { File workingDir = verifyWorkingDirectory(configuration); String workingDirName = null; if (workingDir != null) workingDirName = workingDir.getAbsolutePath(); return workingDirName; } /** * I prefer to change the name to JettyClasspath to make it more clear. This * classpath means how the RunJettyRun to get the Jetty bundle. * * Note:it only used the USER_CLASSES classpaths , not including * BOOTSTRAP_CLASSES. If you change the classpath , that might means you are * changing the Jetty version or something on it. * * @param configuration * @return * @throws CoreException */ private String[] getJettyClasspath(ILaunchConfiguration configuration) throws CoreException { String[] paths = getResolvedJettyClasspath(configuration); Set<String> finalPaths = new HashSet<String>(); for (String path : paths) { finalPaths.add(path); } finalPaths.addAll(getJettyCustomClasspath(configuration)); Map<String,String> checked = getAttributeFromLaunchConfiguration(configuration, Plugin.ATTR_JETTY_CLASSPATH_NON_CHECKED); HashSet<String> results = new HashSet<String>(); for (String path : finalPaths) { if (!checked.containsKey(path) ||checked.get(path).equals("0")) { results.add(path); } } return results.toArray(new String[0]); } public String[] getResolvedJettyClasspath(ILaunchConfiguration configuration) throws CoreException { IRuntimeClasspathEntry[] entries = JavaRuntime .computeUnresolvedRuntimeClasspath(configuration); entries = JavaRuntime.resolveRuntimeClasspath(filterProejctEntries(entries), configuration); List<String> userEntries = new ArrayList<String>(entries.length); Set<String> set = new HashSet<String>(entries.length); for (int i = 0; i < entries.length; i++) { if (entries[i].getClasspathProperty() == IRuntimeClasspathEntry.USER_CLASSES) { String location = entries[i].getLocation(); if (location != null) { if (!set.contains(location)) { userEntries.add(location); set.add(location); } } } } return (String[]) userEntries.toArray(new String[userEntries.size()]); } private IRuntimeClasspathEntry[] filterProejctEntries(IRuntimeClasspathEntry[] entries){ if(entries == null) { return null; } List<IRuntimeClasspathEntry> items = new ArrayList<IRuntimeClasspathEntry>(); for(IRuntimeClasspathEntry entry:entries){ if(entry.getType() == IRuntimeClasspathEntry.PROJECT){ continue; } if(RunJettyRunClasspathUtil.isDefaultProjectClasspathEntry(entry)){ continue; } items.add(entry); } return items.toArray(new IRuntimeClasspathEntry[0]); } @SuppressWarnings("unchecked") private Map<String,String> getAttributeFromLaunchConfiguration( ILaunchConfiguration configuration, String attribute) { Map<String,String> checked = null; try { checked = (Map<String,String>) configuration.getAttribute(attribute, (Map<String,String>) null); } catch (CoreException e) { } if(checked == null){ checked = new HashMap<String,String>(); } return checked; } private Set<String> getCustomClasspath(ILaunchConfiguration configuration, String attribute) throws CoreException { IRuntimeClasspathEntry[] entries = provider .computeUnresolvedCustomClasspath(configuration, attribute); List<IRuntimeClasspathEntry> result = Arrays.asList(JavaRuntime.resolveRuntimeClasspath(entries, configuration)); Set<String> set = new HashSet<String>(result.size()); for (int i = 0; i < result.size(); i++) { String location = result.get(i).getLocation(); if (location != null) { if (!set.contains(location)) { set.add(location); } } } return set; } private Set<String> getJettyCustomClasspath( ILaunchConfiguration configuration) throws CoreException { return getCustomClasspath(configuration, Plugin.ATTR_JETTY_CUSTOM_CLASSPATH); } private Set<String> getWebappCustomClasspath( ILaunchConfiguration configuration) throws CoreException { return getCustomClasspath(configuration, Plugin.ATTR_WEB_CONTEXT_CUSTOM_CLASSPATH); } /** * get Runtime arguments , and prepare the webapp classpath for the program. * * @param configuration * @param oringinalVMArguments * @return * @throws CoreException */ private String[] getRuntimeArguments(ILaunchConfiguration configuration, String[] oringinalVMArguments,boolean debugMode) throws CoreException { List<String> runtimeVmArgs = getJettyArgs(configuration,debugMode); boolean maxperm = false; for (String str : oringinalVMArguments) { if (str != null && str.indexOf("-XX:MaxPermSize") != -1) maxperm = true; } if (!maxperm) { runtimeVmArgs.add("-XX:MaxPermSize=128m"); } // Here the classpath is really for web app. runtimeVmArgs.add("-Drjrclasspath=" + getWebappClasspath(configuration)); runtimeVmArgs.add("-Drjrscanlist=" + getScanlist(configuration)); runtimeVmArgs.add("-DrjrResourceMapping=" + getLinkedResourceMapping(configuration)); if (Plugin.getDefault().isListenerEnable()) { runtimeVmArgs.add("-DrjrEclipseListener=" + Plugin.getDefault().getListenerPort()); } runtimeVmArgs.addAll(Arrays.asList(oringinalVMArguments)); return runtimeVmArgs.toArray(new String[runtimeVmArgs.size()]); } private String getLinkedResourceInResource(IContainer root, IContainer folder) { StringBuffer sb = new StringBuffer(); try { for (IResource ir : folder.members()) { if (ir instanceof IFolder) { if (ir.isLinked()) { sb.append(ir.getProjectRelativePath().makeRelativeTo( root.getProjectRelativePath()) + "=" + ir.getLocation() + ";"); } sb.append(getLinkedResourceInResource(root, (IFolder) ir)); } } } catch (CoreException e) { e.printStackTrace(); } return sb.toString(); } private String getLinkedResourceMapping(ILaunchConfiguration conf) { try { IJavaProject proj = getJavaProject(conf); String webappPath = conf.getAttribute(Plugin.ATTR_WEBAPPDIR, ""); if (proj == null || "".equals(webappPath)) return null; IContainer webappDir = null; if ("/".equals(webappPath)){ webappDir = proj.getProject(); }else{ webappDir = proj.getProject().getFolder(webappPath); } if (webappDir.exists()){ return getLinkedResourceInResource(webappDir, webappDir); } } catch (CoreException e) { e.printStackTrace(); } return null; } /** * The launcher ! */ public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException { /* * for those terminate by our self . * * @see #terminateOldRJRLauncher */ if (!RunJettyRunLaunchConfigurationUtil.validation(configuration)) { throw new CoreException( new Status( IStatus.ERROR, Plugin.PLUGIN_ID, 01, " Invalid run configuration , please check the configuration ", null)); } addSourcesLookupProjectsFromMavenIfExist(configuration); if (monitor == null) { monitor = new NullProgressMonitor(); } monitor.beginTask( MessageFormat.format("{0}...", configuration.getName()), 3); //$NON-NLS-1$ // check for cancellation if (monitor.isCanceled()) return; try { monitor.subTask("verifying installation"); // Program & VM arguments ExecutionArguments execArgs = new ExecutionArguments( getVMArguments(configuration), getProgramArguments(configuration)); // Create VM configuration // here the classpath means for the Jetty Server , not for the // application! by TonyQ 2011/3/7 VMRunnerConfiguration runConfig = new VMRunnerConfiguration( Plugin.BOOTSTRAP_CLASS_NAME, getJettyClasspath(configuration)); // logger to list classpaths // for(String path:getJettyClasspath(configuration)){ // System.out.println("path:"+path); // } runConfig.setProgramArguments(execArgs.getProgramArgumentsArray()); // Environment variables runConfig.setEnvironment(getEnvironment(configuration)); boolean debug = ILaunchManager.DEBUG_MODE.equals(mode); // Here prepare the classpath is really for webapp in Runtime // Arguments , too. runConfig.setVMArguments(getRuntimeArguments(configuration, execArgs.getVMArgumentsArray(),debug)); runConfig .setWorkingDirectory(getWorkingDirectoryAbsolutePath(configuration)); runConfig .setVMSpecificAttributesMap(getVMSpecificAttributesMap(configuration)); // Boot path runConfig.setBootClassPath(getBootpath(configuration)); // check for cancellation if (monitor.isCanceled()) return; // stop in main prepareStopInMain(configuration); // done the verification phase monitor.worked(1); monitor.subTask("Creating source locator"); // set the default source locator if required setDefaultSourceLocator(launch, configuration); launch.getSourceLocator(); monitor.worked(1); synchronized (configuration) { terminateOldRJRLauncher(configuration, launch); // Launch the configuration - 1 unit of work getVMRunner(configuration, mode) .run(runConfig, launch, monitor); registerRJRLauncher(configuration, launch); } // check for cancellation if (monitor.isCanceled()) return; } finally { monitor.done(); } } /** * A private helper to prepare a classpath file to workspace metadata, we * use this to prevent classpath too long which was caused the problem for * reaching Windows command length limitation. * * @param configuration * @param classpath * @return */ private File prepareClasspathFile(ILaunchConfiguration configuration, String classpath) { return prepareConfigFile(configuration,classpath,".classpath"); } private File prepareConfigFile(ILaunchConfiguration configuration, String content ,String extension) { IPath path = Plugin.getDefault().getStateLocation() .append(configuration.getName() + extension); File f = path.toFile(); try { BufferedWriter out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(f, false), "UTF8")); out.write(content); out.close(); return f; } catch (IOException e) { return null; } } /** * Terminate old Run-Jetty-Run launcher which use same port if exist. * * @param configuration * @param launch * @throws CoreException */ private static void terminateOldRJRLauncher( ILaunchConfiguration configuration, ILaunch launch) throws CoreException { String port = configuration.getAttribute(Plugin.ATTR_PORT, ""); String sslPort = configuration.getAttribute(Plugin.ATTR_SSL_PORT, ""); boolean enableSSL = configuration.getAttribute(Plugin.ATTR_ENABLE_SSL, false); if (!"".equals(port) && launcher.containsKey(port)) { terminateLaunch(launcher.get(port)); launcher.remove(port); } if (enableSSL && !"".equals(sslPort) && launcher.containsKey(sslPort)) { terminateLaunch(launcher.get(sslPort)); launcher.remove(sslPort); } } private static void terminateLaunch(ILaunch launch) throws DebugException { if (!launch.isTerminated()) { IProcess[] processes = launch.getProcesses(); if (processes != null) { for (IProcess proc : processes) { if (proc != null) proc.terminate(); } } } } /** * register a port for RJR Launcher * * @param configuration * @param launch * @throws CoreException */ private static void registerRJRLauncher(ILaunchConfiguration configuration, ILaunch launch) throws CoreException { String port = configuration.getAttribute(Plugin.ATTR_PORT, ""); String sslPort = configuration.getAttribute(Plugin.ATTR_SSL_PORT, ""); boolean enableSSL = configuration.getAttribute(Plugin.ATTR_ENABLE_SSL, false); if (!"".equals(port)) launcher.put(port, launch); if (enableSSL && !"".equals(sslPort)) launcher.put(sslPort, launch); } private List<String> getJettyArgs(ILaunchConfiguration configuration,boolean debugMode) throws CoreException { List<String> runtimeVmArgs = new ArrayList<String>(); addOptionalAttr(configuration, runtimeVmArgs, Plugin.ATTR_CONTEXT, "context"); addWebappAttr(configuration, runtimeVmArgs, Plugin.ATTR_WEBAPPDIR); addOptionalAttr(configuration, runtimeVmArgs, Plugin.ATTR_PORT, "port"); addOptionalAttr(configuration, runtimeVmArgs, Plugin.ATTR_SSL_PORT, "sslport"); addOptionalAttr(configuration, runtimeVmArgs, Plugin.ATTR_KEYSTORE, "keystore"); addOptionalAttr(configuration, runtimeVmArgs, Plugin.ATTR_KEY_PWD, "keypassword"); addOptionalAttr(configuration, runtimeVmArgs, Plugin.ATTR_PWD, "password"); addOptionalAttr(configuration, runtimeVmArgs, Plugin.ATTR_JETTY_XML_PATH, "jettyXMLPath"); addOptionalAttr(configuration, runtimeVmArgs, Plugin.ATTR_SCANINTERVALSECONDS, "scanintervalseconds"); addOptionalAttrx(configuration, runtimeVmArgs, Plugin.ATTR_ENABLE_SCANNER, "enablescanner"); if(debugMode){ addOptionalAttrx(configuration, runtimeVmArgs, Plugin.ATTR_IGNORE_SCAN_CLASS_WHEN_DEBUG_MODE, "ignoreScanClassFile",true); } addOptionalAttrx(configuration, runtimeVmArgs, Plugin.ATTR_ENABLE_SSL, "enablessl"); addOptionalAttrx(configuration, runtimeVmArgs, Plugin.ATTR_ENABLE_NEED_CLIENT_AUTH, "needclientauth"); addOptionalAttrx(configuration, runtimeVmArgs, Plugin.ATTR_ENABLE_PARENT_LOADER_PRIORITY, "parentloaderpriority"); addOptionalAttrx(configuration, runtimeVmArgs, Plugin.ATTR_ENABLE_JNDI, "enbaleJNDI"); return runtimeVmArgs; } private void addWebappAttr(ILaunchConfiguration configuration, List<String> runtimeVmArgs, String cfgAttr) throws CoreException { IJavaProject proj = this.getJavaProject(configuration); if (proj == null) return; String value = configuration.getAttribute(cfgAttr, ""); if ("/".equals(value)) { IPath path = (IPath) proj.getResource().getLocation().clone(); path.makeAbsolute(); value = path.toOSString(); } else { if (proj.getProject().getFolder(value).getLocation() == null) { throw new IllegalStateException( "raw location shouldn't be null"); } value = proj.getProject().getFolder(value).getLocation() .toOSString(); } if (value.length() == 0) return; String arg = "-Drjr" + "webapp" + "=" + value + ""; runtimeVmArgs.add(arg); return; } private void addOptionalAttr(ILaunchConfiguration configuration, List<String> runtimeVmArgs, String cfgAttr, String argName) throws CoreException { String value = configuration.getAttribute(cfgAttr, ""); if (value.length() == 0) return; String arg = "-Drjr" + argName + "=" + value; runtimeVmArgs.add(arg); return; } private void addOptionalAttrx(ILaunchConfiguration configuration, List<String> runtimeVmArgs, String cfgAttr, String argName,boolean defVal) throws CoreException { Boolean value = configuration.getAttribute(cfgAttr, defVal); String arg = "-Drjr" + argName + "=" + value; runtimeVmArgs.add(arg); return; } private void addOptionalAttrx(ILaunchConfiguration configuration, List<String> runtimeVmArgs, String cfgAttr, String argName) throws CoreException { addOptionalAttrx(configuration,runtimeVmArgs,cfgAttr,argName,false); } /** * Returns the class path to be used by the web app context (not by Jetty, * or as JRE Bootstrap). * <p> * Added by James Synge. * * Copied from {@link AbstractJavaLaunchConfigurationDelegate} so that I can * eliminate everything that should be in WEB-INF/lib, but is not supposed * to be in the project's classpath. * * (non-Javadoc) * * @see org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate#getClasspath(org.eclipse.debug.core.ILaunchConfiguration) */ private String[] getProjectClasspath(ILaunchConfiguration configuration) throws CoreException { try { IRuntimeClasspathEntry[] entries = RunJettyRunClasspathUtil.getDefaultWebAppClasspaths(configuration); entries = RunJettyRunClasspathResolver.resolveClasspath(entries, configuration); // entries = JavaRuntime.resolveRuntimeClasspath(entries, // configuration); Set<String> locations = searchUserClass(entries); locations.addAll(getWebappCustomClasspath(configuration)); locations.addAll(RunJettyRunClasspathUtil.getWebInfLibLocations(configuration)); List<String> result = new ArrayList<String>(); Map<String,String> checked = getAttributeFromLaunchConfiguration(configuration, Plugin.ATTR_WEB_CONTEXT_CLASSPATH_NON_CHECKED); addResult(result,locations,checked,true); return (String[]) result.toArray(new String[0]); } catch (IllegalArgumentException e) { return new String[0]; } } private void addResult(List<String> result,Set<String> locations,Map<String,String> checked,boolean ignoreTestClassesByDefault){ for(String item:locations){ if(!checked.containsKey(item) ){ if(ignoreTestClassesByDefault && item.endsWith("test-classes")){ result.add("-n-"+item); }else{ result.add("-y-"+item); } }else if(checked.get(item).equals("0")){ result.add("-y-"+item); }else{ result.add("-n-"+item); } } } /** * add project sources into ILaunchConfiguration which are referenced by * maven * * Referenced from yanyilin224. * @param configuration */ private void addSourcesLookupProjectsFromMavenIfExist(ILaunchConfiguration configuration) { try { IJavaProject curProject = JavaRuntime.getJavaProject(configuration); boolean isMaven = ProjectUtil.isMavenProject(curProject.getProject()); if(!isMaven ) { return ; } List<IProject> projs = RunJettyRunSourceLookupUtil.findMavenRelatedProjects(configuration); if(projs.size() == 0 ){ return ; } ISourceLookupDirector sourceDir = new JavaSourceLookupDirector(); ILaunchConfigurationWorkingCopy workCopy = configuration.getWorkingCopy(); String initMemento = workCopy.getAttribute( ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, ""); if (initMemento != null && !initMemento.trim().equals("")) { sourceDir.initializeFromMemento(initMemento); } ISourceContainer[] existContainers = sourceDir .getSourceContainers(); List<ISourceContainer> realContainers = new ArrayList<ISourceContainer>(); //add exsits source containers for (ISourceContainer container : existContainers) { realContainers.add(container); } //check default source container ISourceContainer defaultContainer = new DefaultSourceContainer(); if (!contains(existContainers, defaultContainer)) { realContainers.add(defaultContainer); } for (IProject dependency : projs) { // handle projects in current workspace ISourceContainer newContainer = new JavaProjectSourceContainer( JavaCore.create((IProject) dependency)); if (!contains(existContainers, newContainer)) { realContainers.add(newContainer); } } sourceDir.setSourceContainers(realContainers .toArray(new ISourceContainer[realContainers.size()])); workCopy.setAttribute( ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, sourceDir.getMemento()); workCopy.doSave(); } catch (Exception e) { // something wrong, skip add sources } } /** * if containers contains target source container * * @param containers * exsited source containers * @param target * @return */ private boolean contains(ISourceContainer[] containers, ISourceContainer target) { String name = target.getName(); String type = target.getType().getId(); for (ISourceContainer container : containers) { if (name.equals(container.getName()) && type.equals(container.getType().getId())) { return true; } } return false; } private Set<String> searchUserClass(IRuntimeClasspathEntry[] entries) { Set<String> locations = new LinkedHashSet<String>(); for (int i = 0; i < entries.length; i++) { IRuntimeClasspathEntry entry = entries[i]; if (entry.getClasspathProperty() == IRuntimeClasspathEntry.USER_CLASSES) { String location = entry.getLocation(); if (location != null) { locations.add(location); } } } return locations; } }