| /********************************************************************** |
| * Copyright (c) 2007, 2010 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Igor Fedorenko & Fabrizio Giustina - Initial API and implementation |
| * Matteo TURRA - Support for multiple web resource paths |
| **********************************************************************/ |
| package org.eclipse.jst.server.tomcat.core.internal; |
| |
| import java.io.File; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jst.server.tomcat.core.internal.wst.IModuleVisitor; |
| import org.eclipse.jst.server.tomcat.core.internal.xml.Factory; |
| import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Context; |
| import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Loader; |
| import org.eclipse.jst.server.tomcat.core.internal.xml.server40.ServerInstance; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.wst.common.componentcore.resources.IVirtualComponent; |
| import org.eclipse.wst.common.componentcore.resources.IVirtualFile; |
| import org.eclipse.wst.common.componentcore.resources.IVirtualResource; |
| import org.eclipse.wst.server.core.IModule; |
| import org.eclipse.wst.server.core.ServerUtil; |
| |
| /** |
| * Handles "publishing" for servers that can load classes and resources directly |
| * from the workspace. Instead of creating and deploying jars to the webapp this |
| * simply update the virtual classpath in the context xml file. |
| */ |
| public class TomcatPublishModuleVisitor implements IModuleVisitor { |
| |
| /** |
| * Server base path (Catalina base). |
| */ |
| protected final IPath baseDir; |
| |
| /** |
| * Server instance in which to modify the context |
| */ |
| protected final ServerInstance serverInstance; |
| |
| /** |
| * Catalina.properties loader to add global classpath entries |
| */ |
| protected final String sharedLoader; |
| |
| /** |
| * |
| */ |
| protected final boolean enableMetaInfResources; |
| |
| /** |
| * Classpath entries added by ear configurations. |
| */ |
| protected final List earCommonResources = new ArrayList(); |
| |
| /** |
| * List of classpath elements that will be used by the custom tomcat loader. |
| * This set should include any class dir from referenced project. |
| */ |
| protected Set virtualClassClasspathElements = new LinkedHashSet(); |
| protected Set virtualJarClasspathElements = new LinkedHashSet(); |
| |
| /** |
| * Map of resources found in "META-INF/resources" folder of dependent projects |
| */ |
| protected Map virtualDependentResources = new LinkedHashMap(); |
| |
| /** |
| * Instantiate a new TomcatPublishModuleVisitor |
| * |
| * @param catalinaBase catalina base path |
| */ |
| TomcatPublishModuleVisitor(IPath catalinaBase, ServerInstance serverInstance, String sharedLoader, boolean enableMetaInfResources) { |
| this.baseDir = catalinaBase; |
| this.serverInstance = serverInstance; |
| this.sharedLoader = sharedLoader; |
| this.enableMetaInfResources = enableMetaInfResources; |
| } |
| |
| /** |
| * @see IModuleVisitor#visitWebComponent(IVirtualComponent) |
| */ |
| public void visitWebComponent(IVirtualComponent component) |
| throws CoreException { |
| // nothing to do, everything is done in endVisitWebComponent |
| } |
| |
| /** |
| * @see IModuleVisitor#visitArchiveComponent(IPath, IPath) |
| */ |
| public void visitArchiveComponent(IPath runtimePath, IPath workspacePath) { |
| addVirtualJarResource(runtimePath, workspacePath); |
| } |
| |
| /** |
| * @see IModuleVisitor#visitDependentComponent(IPath, IPath) |
| */ |
| public void visitDependentComponent(IPath runtimePath, IPath workspacePath) { |
| addVirtualJarResource(runtimePath, workspacePath); |
| } |
| |
| /** |
| * @see IModuleVisitor#visitWebResource(IPath, IPath) |
| */ |
| public void visitWebResource(IPath runtimePath, IPath workspacePath) { |
| addVirtualClassResource(runtimePath, workspacePath); |
| } |
| |
| /** |
| * @see IModuleVisitor#visitDependentContentResource(IPath, IPath) |
| */ |
| public void visitDependentContentResource(IPath runtimePath, IPath workspacePath) { |
| // Currently, only handle "META-INF/resources" folders if supported |
| if (enableMetaInfResources) { |
| addContentResource(runtimePath, workspacePath); |
| } |
| } |
| |
| /** |
| * @see IModuleVisitor#visitEarResource(IPath, IPath) |
| */ |
| public void visitEarResource(IPath runtimePath, IPath workspacePath) { |
| earCommonResources.add(workspacePath.toOSString()); |
| } |
| |
| /** |
| * @see IModuleVisitor#endVisitEarComponent(IVirtualComponent) |
| */ |
| public void endVisitEarComponent(IVirtualComponent component) |
| throws CoreException { |
| if (earCommonResources.size() > 0) { |
| try { |
| CatalinaPropertiesUtil.addGlobalClasspath(baseDir.append( |
| "conf/catalina.properties").toFile(), sharedLoader, |
| (String[]) earCommonResources.toArray(new String[earCommonResources.size()])); |
| } catch (IOException e) { |
| Trace.trace(Trace.WARNING, "Unable to add ear path entries to catalina.properties", e); |
| } finally { |
| earCommonResources.clear(); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void endVisitWebComponent(IVirtualComponent component) |
| throws CoreException { |
| |
| // track context changes, don't rewrite if not needed |
| boolean dirty = false; |
| |
| IModule module = ServerUtil.getModule(component.getProject()); |
| |
| // we need this for the user-specified context path |
| Context context = findContext(module); |
| if (context == null) { |
| String name = module != null ? module.getName() : component.getName(); |
| Trace.trace(Trace.SEVERE, "Could not find context for module " + name); |
| throw new CoreException(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, |
| NLS.bind(Messages.errorPublishContextNotFound, name), null)); |
| } |
| |
| String contextName = null; |
| boolean reloadable = true; |
| |
| contextName = context.getPath(); |
| reloadable = Boolean.valueOf(context.getReloadable()).booleanValue(); |
| |
| // now strip initial / |
| if (contextName.startsWith("/")) { |
| contextName = contextName.substring(1); |
| } |
| |
| // root context is deployed with the "ROOT" name in tomcat |
| if ("".equals(contextName)) { |
| contextName = "ROOT"; |
| } |
| |
| // handle project context.xml |
| Context projectContext = getProjectContextXml(component); |
| |
| if (projectContext != null) { |
| // copy configuration to server context |
| projectContext.copyChildrenTo(context); |
| |
| Map attrs = projectContext.getAttributes(); |
| Iterator iter = attrs.keySet().iterator(); |
| while (iter.hasNext()) { |
| String name = (String) iter.next(); |
| if (!name.equalsIgnoreCase("path") |
| && !name.equalsIgnoreCase("docBase") |
| && !name.equalsIgnoreCase("source")) { |
| String value = (String) attrs.get(name); |
| String actualValue = context.getAttributeValue(name); |
| if (!value.equals(actualValue)) { |
| context.setAttributeValue(name, value); |
| dirty = true; |
| } |
| } |
| } |
| } |
| |
| // handle changes in docBase |
| String docBase = component.getRootFolder().getUnderlyingFolder() |
| .getLocation().toOSString(); |
| if (!docBase.equals(context.getDocBase())) { |
| dirty = true; |
| context.setDocBase(docBase); |
| } |
| |
| // handle changes in reloadable flag |
| if (reloadable != (Boolean.valueOf((context.getReloadable())) |
| .booleanValue())) { |
| dirty = true; |
| context.setReloadable(Boolean.toString(reloadable)); |
| } |
| |
| String path = (contextName.equals("ROOT") ? "" : "/" + contextName); |
| // handle changes in the path |
| // PATH is required for tomcat 5.0, but ignored in tomcat 5.5 |
| if (!path.equals(context.getPath())) { |
| dirty = true; |
| context.setPath(path); |
| } |
| |
| context.getResources().setClassName( |
| "org.eclipse.jst.server.tomcat.loader.WtpDirContext"); |
| |
| Loader loader = context.getLoader(); |
| |
| loader.setClassName("org.eclipse.jst.server.tomcat.loader.WtpWebappLoader"); |
| |
| // required for tomcat 5.5.20 due to the change in |
| // http://issues.apache.org/bugzilla/show_bug.cgi?id=39704 |
| loader.setUseSystemClassLoaderAsParent(Boolean.FALSE.toString()); |
| |
| // Build the virtual classPath setting |
| StringBuffer vcBuffer = new StringBuffer(); |
| // Build list of additional resource paths and check for additional jars |
| StringBuffer rpBuffer = new StringBuffer(); |
| |
| // Add WEB-INF/classes elements to both settings |
| for (Iterator iterator = virtualClassClasspathElements.iterator(); |
| iterator.hasNext();) { |
| Object element = iterator.next(); |
| if (vcBuffer.length() > 0) { |
| vcBuffer.append(";"); |
| rpBuffer.append(";"); |
| } |
| vcBuffer.append(element); |
| // Add to resource paths too, so resource artifacts can be found |
| rpBuffer.append("/WEB-INF/classes").append("|").append(element); |
| } |
| if (vcBuffer.length() > 0 && virtualJarClasspathElements.size() > 0) { |
| vcBuffer.append(";"); |
| } |
| for (Iterator iterator = virtualJarClasspathElements.iterator(); |
| iterator.hasNext();) { |
| vcBuffer.append(iterator.next()); |
| if (iterator.hasNext()) { |
| vcBuffer.append(";"); |
| } |
| } |
| virtualClassClasspathElements.clear(); |
| virtualJarClasspathElements.clear(); |
| |
| Set rtPathsProcessed = new HashSet(); |
| Set locationsIncluded = new HashSet(); |
| locationsIncluded.add(docBase); |
| Map retryLocations = new HashMap(); |
| IVirtualResource [] virtualResources = component.getRootFolder().getResources(""); |
| // Loop over the module's resources |
| for (int i = 0; i < virtualResources.length; i++) { |
| String rtPath = virtualResources[i].getRuntimePath().toString(); |
| // Note: The virtual resources returned only know their runtime path. |
| // Asking for the project path for this resource performs a lookup |
| // that will only return the path for the first mapping for the |
| // runtime path. Thus use of getUnderlyingResources() is necessary. |
| // However, this returns matching resources from all mappings so |
| // we have to try to keep only those that are mapped directly |
| // to the runtime path in the .components file. |
| |
| // If this runtime path has not yet been processed |
| if (!rtPathsProcessed.contains(rtPath)) { |
| // If not a Java related resource |
| if (!"/WEB-INF/classes".equals(rtPath)) { |
| // Get all resources for this runtime path |
| IResource[] underlyingResources = virtualResources[i].getUnderlyingResources(); |
| // If resource is mapped to "/", then we know it corresponds directly |
| // to a mapping in the .components file |
| if ("/".equals(rtPath)) { |
| for (int j = 0; j < underlyingResources.length; j++) { |
| IPath resLoc = underlyingResources[j].getLocation(); |
| String location = resLoc.toOSString(); |
| if (!location.equals(docBase)) { |
| if (rpBuffer.length() != 0) { |
| rpBuffer.append(";"); |
| } |
| // Add this location to extra paths setting |
| rpBuffer.append(location); |
| // Add to the set of locations included |
| locationsIncluded.add(location); |
| // Check if this extra content location contains jars |
| File webInfLib = resLoc.append("WEB-INF/lib").toFile(); |
| // If this "WEB-INF/lib" exists and is a directory, add |
| // its jars to the virtual classpath |
| if (webInfLib.exists() && webInfLib.isDirectory()) { |
| String [] jars = webInfLib.list(new FilenameFilter() { |
| public boolean accept(File dir, String name) { |
| File f = new File(dir, name); |
| return f.isFile() && name.endsWith(".jar"); |
| } |
| }); |
| for (int k = 0; k < jars.length; k++) { |
| if (vcBuffer.length() != 0) { |
| vcBuffer.append(";"); |
| } |
| vcBuffer.append(webInfLib.getPath() + File.separator + jars[k]); |
| } |
| } |
| } |
| } |
| } |
| // Else this runtime path is something other than "/" |
| else { |
| int idx = rtPath.lastIndexOf('/'); |
| // If a "normal" runtime path |
| if (idx >= 0) { |
| // Get the name of the last segment in the runtime path |
| String lastSegment = rtPath.substring(idx + 1); |
| // Check the underlying resources to determine which correspond to mappings |
| for (int j = 0; j < underlyingResources.length; j++) { |
| IPath resLoc = underlyingResources[j].getLocation(); |
| String location = resLoc.toOSString(); |
| // If the last segment of the runtime path doesn't match the |
| // the last segment of the location, then we have a direct mapping |
| // from the .contents file. |
| if (!lastSegment.equals(resLoc.lastSegment())) { |
| if (rpBuffer.length() != 0) { |
| rpBuffer.append(";"); |
| } |
| // Add this location to extra paths setting |
| rpBuffer.append(rtPath).append("|").append(location); |
| // Add to the set of locations included |
| locationsIncluded.add(location); |
| // Check if this extra content location contains jars |
| File webInfLib = null; |
| if ("/WEB-INF".equals(rtPath)) { |
| webInfLib = resLoc.append("lib").toFile(); |
| } |
| else if ("/WEB-INF/lib".equals(rtPath)) { |
| webInfLib = resLoc.toFile(); |
| } |
| // If this "WEB-INF/lib" exists and is a directory, add |
| // its jars to the virtual classpath |
| if (webInfLib != null && webInfLib.exists() && webInfLib.isDirectory()) { |
| String [] jars = webInfLib.list(new FilenameFilter() { |
| public boolean accept(File dir, String name) { |
| File f = new File(dir, name); |
| return f.isFile() && name.endsWith(".jar"); |
| } |
| }); |
| for (int k = 0; k < jars.length; k++) { |
| if (vcBuffer.length() != 0) { |
| vcBuffer.append(";"); |
| } |
| vcBuffer.append(webInfLib.getPath() + File.separator + jars[k]); |
| } |
| } |
| } |
| // Else last segment of runtime path did match the last segment |
| // of the location. We likely have a subfolder of a mapping |
| // that matches a portion of the runtime path. |
| else { |
| // Since we can't be sure, save so it can be check again later |
| retryLocations.put(location, rtPath); |
| } |
| } |
| } |
| } |
| } |
| // Add the runtime path to those already processed |
| rtPathsProcessed.add(rtPath); |
| } |
| } |
| // If there are locations to retry, add any not yet included in extra paths setting |
| if (!retryLocations.isEmpty()) { |
| // Remove retry locations already included in the extra paths |
| for (Iterator iterator = retryLocations.keySet().iterator(); iterator.hasNext();) { |
| String location = (String)iterator.next(); |
| for (Iterator iterator2 = locationsIncluded.iterator(); iterator2.hasNext();) { |
| String includedLocation = (String)iterator2.next(); |
| if (location.equals(includedLocation) || location.startsWith(includedLocation + File.separator)) { |
| iterator.remove(); |
| break; |
| } |
| } |
| } |
| // If any entries are left, include them in the extra paths |
| if (!retryLocations.isEmpty()) { |
| for (Iterator iterator = retryLocations.entrySet().iterator(); iterator.hasNext();) { |
| Map.Entry entry = (Map.Entry)iterator.next(); |
| String location = (String)entry.getKey(); |
| String rtPath = (String)entry.getValue(); |
| if (rpBuffer.length() != 0) { |
| rpBuffer.append(";"); |
| } |
| rpBuffer.append(rtPath).append("|").append(location); |
| // Check if this extra content location contains jars |
| File webInfLib = null; |
| if ("/WEB-INF".equals(rtPath)) { |
| webInfLib = new File(location, "lib"); |
| } |
| else if ("/WEB-INF/lib".equals(rtPath)) { |
| webInfLib = new File(location); |
| } |
| // If this "WEB-INF/lib" exists and is a directory, add |
| // its jars to the virtual classpath |
| if (webInfLib != null && webInfLib.exists() && webInfLib.isDirectory()) { |
| String [] jars = webInfLib.list(new FilenameFilter() { |
| public boolean accept(File dir, String name) { |
| File f = new File(dir, name); |
| return f.isFile() && name.endsWith(".jar"); |
| } |
| }); |
| for (int k = 0; k < jars.length; k++) { |
| if (vcBuffer.length() != 0) { |
| vcBuffer.append(";"); |
| } |
| vcBuffer.append(webInfLib.getPath() + File.separator + jars[k]); |
| } |
| } |
| } |
| } |
| } |
| if (!virtualDependentResources.isEmpty()) { |
| for (Iterator iterator = virtualDependentResources.entrySet().iterator(); iterator.hasNext();) { |
| Map.Entry entry = (Map.Entry)iterator.next(); |
| String rtPath = (String)entry.getKey(); |
| List locations = (List)entry.getValue(); |
| for (Iterator iterator2 = locations.iterator(); iterator2.hasNext();) { |
| String location = (String)iterator2.next(); |
| if (rpBuffer.length() != 0) { |
| rpBuffer.append(";"); |
| } |
| if (rtPath.length() > 0) { |
| rpBuffer.append(entry.getKey()).append("|").append(location); |
| } |
| else { |
| rpBuffer.append(location); |
| } |
| } |
| } |
| } |
| |
| String vcp = vcBuffer.toString(); |
| String oldVcp = loader.getVirtualClasspath(); |
| if (!vcp.equals(oldVcp)) { |
| // save only if needed |
| dirty = true; |
| loader.setVirtualClasspath(vcp); |
| context.getResources().setVirtualClasspath(vcp); |
| } |
| |
| String resPaths = rpBuffer.toString(); |
| String oldResPaths = context.getResources().getExtraResourcePaths(); |
| if (!resPaths.equals(oldResPaths)) { |
| dirty = true; |
| context.getResources().setExtraResourcePaths(resPaths); |
| } |
| |
| if (enableMetaInfResources) { |
| context.findElement("JarScanner").setAttributeValue("scanAllDirectories", "true"); |
| } |
| |
| if (dirty) { |
| //TODO If writing to separate context XML files, save "dirty" status for later use |
| } |
| } |
| |
| private void addVirtualClassResource(IPath runtimePath, IPath workspacePath) { |
| virtualClassClasspathElements.add(workspacePath.toOSString()); |
| } |
| |
| private void addVirtualJarResource(IPath runtimePath, IPath workspacePath) { |
| virtualJarClasspathElements.add(workspacePath.toOSString()); |
| } |
| |
| private void addContentResource(IPath runtimePath, IPath workspacePath) { |
| String rtPath = runtimePath.toString(); |
| List locations = (List)virtualDependentResources.get(rtPath); |
| if (locations == null) { |
| locations = new ArrayList(); |
| virtualDependentResources.put(rtPath, locations); |
| } |
| locations.add(workspacePath.toOSString()); |
| } |
| |
| /** |
| * Load a META-INF/context.xml file from project, if available |
| * |
| * @param component web component containing the context.xml |
| * @return context element containing the context.xml |
| * @throws CoreException |
| */ |
| protected Context getProjectContextXml(IVirtualComponent component) |
| throws CoreException { |
| |
| // load or create module's context.xml document |
| IVirtualFile contextFile = (IVirtualFile) component.getRootFolder() |
| .findMember("META-INF/context.xml"); |
| |
| Context contextElement = null; |
| |
| if (contextFile != null && contextFile.exists()) { |
| |
| Factory factory = new Factory(); |
| factory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40"); |
| |
| InputStream fis = null; |
| try { |
| fis = contextFile.getUnderlyingFile().getContents(); |
| contextElement = (Context) factory.loadDocument(fis); |
| } catch (Exception e) { |
| Trace.trace(Trace.SEVERE, "Exception reading " + contextFile, e); |
| } finally { |
| try { |
| fis.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| return contextElement; |
| } |
| |
| /** |
| * Returns the given module from the config. |
| * |
| * @param module a web module |
| * @return a web module |
| */ |
| protected Context findContext(IModule module) { |
| if (module == null) { |
| return null; |
| } |
| |
| String source = module.getId(); |
| |
| Context [] contexts = serverInstance.getContexts(); |
| for (int i = 0; i < contexts.length; i++) { |
| if (source.equals(contexts[i].getSource())) |
| return contexts[i]; |
| } |
| return null; |
| } |
| } |