blob: 4e161cf2bab678e72488b646cffb2424c012fbf1 [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2007, 2011 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.jdt.core.IJavaProject;
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;
/**
* Tomcat version (from "server.info" property in org.apache.catalina.util.ServerInfo.properties).
*/
protected final String tomcatVersion;
/**
* 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<String> earCommonResources = new ArrayList<String>();
/**
* 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<String> virtualClassClasspathElements = new LinkedHashSet<String>();
protected Set<String> virtualJarClasspathElements = new LinkedHashSet<String>();
/**
* Map of resources found in "META-INF/resources" folder of dependent projects
*/
protected Map<String, List<String>> virtualDependentResources = new LinkedHashMap<String, List<String>>();
/**
* Instantiate a new TomcatPublishModuleVisitor
*
* @param baseDir catalina base path
* @param tomcatVersion tomcat version
* @param serverInstance ServerInstance containing server.xml contents
* @param sharedLoader string value for shared.loader catalina configuration property
* @param enableMetaInfResources flag to indicate if Servlet 3.0 "META-INF/resources" feature should be supported
*/
TomcatPublishModuleVisitor(IPath baseDir, String tomcatVersion, ServerInstance serverInstance, String sharedLoader, boolean enableMetaInfResources) {
this.baseDir = baseDir;
this.tomcatVersion = tomcatVersion;
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#visitDependentJavaProject(IJavaProject javaProject)
*/
public void visitDependentJavaProject(IJavaProject javaProject) {
// Useful for finding source folders, so do nothing.
}
/**
* @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,
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();
boolean isTomcat7 = tomcatVersion.startsWith("7.");
// 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(";");
}
vcBuffer.append(element);
if (isTomcat7) {
if (rpBuffer.length() > 0) {
rpBuffer.append(";");
}
// 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<String> rtPathsProcessed = new HashSet<String>();
Set<String> locationsIncluded = new HashSet<String>();
locationsIncluded.add(docBase);
Map<String, String> retryLocations = new HashMap<String, String>();
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<String> locations = virtualDependentResources.get(rtPath);
if (locations == null) {
locations = new ArrayList<String>();
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;
}
}