blob: a5ae929fa243f64f64340f1987e243ed1f486085 [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2007, 2010 SAS Institute, Inc 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:
* SAS Institute, Inc - Initial API and implementation
**********************************************************************/
package org.eclipse.jst.server.tomcat.core.internal;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.xml.parsers.DocumentBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jst.server.tomcat.core.internal.wst.ModuleTraverser;
import org.eclipse.jst.server.tomcat.core.internal.xml.Factory;
import org.eclipse.jst.server.tomcat.core.internal.xml.XMLUtil;
import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Context;
import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Host;
import org.eclipse.jst.server.tomcat.core.internal.xml.server40.Server;
import org.eclipse.jst.server.tomcat.core.internal.xml.server40.ServerInstance;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.ServerUtil;
import org.eclipse.wst.server.core.util.PublishHelper;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* Utility class for methods that are used by more that one version
* of Tomcat. Use of these methods makes it clear that more than
* one version will be impacted by changes.
*
*/
public class TomcatVersionHelper {
/**
* Sting containing contents for a default web.xml for Servlet 2.2.
*/
public static final String DEFAULT_WEBXML_SERVLET22 =
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
"<!DOCTYPE web-app PUBLIC \"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN\" \"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd\">\n" +
"<web-app>\n</web-app>";
/**
* Default web.xml contents for a Servlet 2.3 web application.
*/
public static final String DEFAULT_WEBXML_SERVLET23 =
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
"<!DOCTYPE web-app PUBLIC \"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN\" \"http://java.sun.com/dtd/web-app_2_3.dtd\">\n" +
"<web-app>\n</web-app>";
/**
* Default web.xml contents for a Servlet 2.4 web application.
*/
public static final String DEFAULT_WEBXML_SERVLET24 =
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
"<web-app xmlns=\"http://java.sun.com/xml/ns/j2ee\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd\" version=\"2.4\">\n" +
"</web-app>";
/**
* Default web.xml contents for a Servlet 2.5 web application.
*/
public static final String DEFAULT_WEBXML_SERVLET25 =
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
"<web-app xmlns=\"http://java.sun.com/xml/ns/j2ee\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd\" version=\"2.5\">\n" +
"</web-app>";
/**
* Map of server type ID to expected version string fragment for version checking.
*/
private static final Map versionStringMap = new HashMap();
static {
versionStringMap.put(TomcatPlugin.TOMCAT_41, "4.1.");
versionStringMap.put(TomcatPlugin.TOMCAT_50, "5.0.");
versionStringMap.put(TomcatPlugin.TOMCAT_55, "5.5.");
versionStringMap.put(TomcatPlugin.TOMCAT_60, "6.0.");
versionStringMap.put(TomcatPlugin.TOMCAT_70, "7.0.");
}
/**
* Reads the from the specified InputStream and returns
* the result as a String. Each line is terminated by
* &quot;\n&quot;. Returns whatever is read regardless
* of any errors that occurs while reading.
*
* @param stream InputStream for the contents to be read
* @return contents read
* @throws IOException if error occurs closing the stream
*/
public static String getFileContents(InputStream stream) throws IOException {
BufferedReader br = null;
StringBuffer sb = new StringBuffer();
try {
br = new BufferedReader(new InputStreamReader(stream));
String temp = br.readLine();
while (temp != null) {
sb.append(temp).append("\n");
temp = br.readLine();
}
} catch (Exception e) {
Trace.trace(Trace.WARNING, "Could not load file contents.", e);
} finally {
if (br != null)
br.close();
}
return sb.toString();
}
/**
* Gets the base directory for this server. This directory
* is used as the "base" property for the server.
*
* @param ts TomcatServer from which to derive the base directory
* directory. Only used to get the temp directory if needed.
* @return path to base directory
*/
public static IPath getStandardBaseDirectory(TomcatServer ts) {
if (ts.isTestEnvironment()) {
String baseDir = ts.getInstanceDirectory();
// If test mode and no instance directory specified, use temporary directory
if (baseDir == null) {
TomcatServerBehaviour tsb = (TomcatServerBehaviour)ts.getServer().loadAdapter(TomcatServerBehaviour.class, null);
return tsb.getTempDirectory();
}
IPath path = new Path(baseDir);
if (!path.isAbsolute()) {
IPath rootPath = ResourcesPlugin.getWorkspace().getRoot().getLocation();
path = rootPath.append(path);
}
// Return specified instance directory
return path;
}
// Return runtime path
return ts.getServer().getRuntime().getLocation();
}
/**
* Gets the startup VM arguments for the Catalina server.
*
* @param installPath installation path for the server
* @param instancePath instance path for the server
* @param deployPath deploy path for the server
* @param isTestEnv test environment flag
* @return array of strings containing VM arguments
*/
public static String[] getCatalinaVMArguments(IPath installPath, IPath instancePath, IPath deployPath, String endorsedDirs, boolean isTestEnv) {
List list = new ArrayList();
if (isTestEnv)
list.add("-Dcatalina.base=\"" + instancePath.toOSString() + "\"");
else
list.add("-Dcatalina.base=\"" + installPath.toOSString() + "\"");
list.add("-Dcatalina.home=\"" + installPath.toOSString() + "\"");
// Include a system property for the configurable deploy location
list.add("-Dwtp.deploy=\"" + deployPath.toOSString() + "\"");
list.add("-Djava.endorsed.dirs=\"" + endorsedDirs + "\"");
String[] s = new String[list.size()];
list.toArray(s);
return s;
}
/**
* Gets a ServerInstance for the specified server.xml, Service name,
* and Host name. Returns null if server.xml does not exist
* or an error occurs.
*
* @param serverXml path to previously published server.xml
* @param serviceName name of Service to be used by this instance or null
* @param hostName name of Host to be used by this instance or null
* @return ServerInstance for specified server.xml using specified
* Service and Host names. null if server.xml does not exist.
* @throws FileNotFoundException should not occur since existence is tested
* @throws IOException if there is an error reading server.xml
* @throws SAXException if there is a syntax error in server.xml
*/
public static ServerInstance getCatalinaServerInstance(IPath serverXml, String serviceName, String hostName) throws FileNotFoundException, IOException, SAXException {
ServerInstance serverInstance = null;
Factory factory = new Factory();
factory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
File serverFile = serverXml.toFile();
if (serverFile.exists()) {
Server server = (Server) factory.loadDocument(new FileInputStream(serverFile));
serverInstance = new ServerInstance(server, serviceName, hostName);
IPath contextPath = serverInstance.getContextXmlDirectory(serverXml.removeLastSegments(1));
File contextDir = contextPath.toFile();
if (contextDir.exists()) {
Map projectContexts = new HashMap();
loadSeparateContextFiles(contextPath.toFile(), factory, projectContexts);
// add any separately saved contexts
Host host = serverInstance.getHost();
Collection contexts = projectContexts.values();
Iterator iter = contexts.iterator();
while (iter.hasNext()) {
Context context = (Context)iter.next();
host.importNode(context.getElementNode(), true);
}
// TODO Add handling for non-project contexts when there removal can be addressed
}
}
return serverInstance;
}
/**
* Gets the paths for Contexts that are being removed in the
* next server publish. Reads the old server.xml to determine
* what Contexts were previously servered and returns those
* that are not included in the specified list of modules.
*
* @param oldServerInstance for server.xml from previous server publish
* @param modules list of currently added modules
* @param removedContextsMap Map to receive removed contexts mapped by path
* @param keptContextsMap Map to receive kept contexts mapped by path
*/
public static void getRemovedKeptCatalinaContexts(ServerInstance oldServerInstance,
List modules, Map removedContextsMap, Map keptContextsMap) {
// Collect paths of old web modules managed by WTP
Context [] contexts = oldServerInstance.getContexts();
if (contexts != null) {
for (int i = 0; i < contexts.length; i++) {
String source = contexts[i].getSource();
if (source != null && source.length() > 0 ) {
removedContextsMap.put(contexts[i].getPath(), contexts[i]);
}
}
}
// Remove paths for web modules that are staying around
int size = modules.size();
for (int i = 0; i < size; i++) {
WebModule module = (WebModule) modules.get(i);
Context context = (Context)removedContextsMap.remove(module.getPath());
if (context != null)
keptContextsMap.put(context.getPath(), context);
}
}
/**
* Cleanup server instance location in preparation for next server publish.
* This currently involves deleting work directories for currently
* existing Contexts which will not be included in the next publish.
* In addition, Context XML files which may have been created for these
* Contexts are also deleted. If requested, Context XML files for
* kept Contexts will be deleted since they will be kept in server.xml.
*
* @param baseDir path to server instance directory, i.e. catalina.base
* @param installDir path to server installation directory (not currently used)
* @param removeKeptContextFiles true if kept contexts should have a separate
* context XML file removed
* @param modules list of currently added modules
* @param monitor a progress monitor or null
* @return MultiStatus containing results of the cleanup operation
*/
public static IStatus cleanupCatalinaServer(IPath baseDir, IPath installDir, boolean removeKeptContextFiles, List modules, IProgressMonitor monitor) {
MultiStatus ms = new MultiStatus(TomcatPlugin.PLUGIN_ID, 0, Messages.cleanupServerTask, null);
try {
monitor = ProgressUtil.getMonitorFor(monitor);
monitor.beginTask(Messages.cleanupServerTask, 200);
monitor.subTask(Messages.detectingRemovedProjects);
IPath serverXml = baseDir.append("conf").append("server.xml");
ServerInstance oldInstance = TomcatVersionHelper.getCatalinaServerInstance(serverXml, null, null);
if (oldInstance != null) {
Map removedContextsMap = new HashMap();
Map keptContextsMap = new HashMap();
TomcatVersionHelper.getRemovedKeptCatalinaContexts(oldInstance, modules, removedContextsMap, keptContextsMap);
monitor.worked(100);
if (removedContextsMap.size() > 0) {
// Delete context files and work directories for managed web modules that have gone away
IProgressMonitor subMonitor = ProgressUtil.getSubMonitorFor(monitor, 100);
subMonitor.beginTask(Messages.deletingContextFilesTask, removedContextsMap.size() * 200);
Iterator iter = removedContextsMap.keySet().iterator();
while (iter.hasNext()) {
String oldPath = (String)iter.next();
Context ctx = (Context)removedContextsMap.get(oldPath);
// Delete the corresponding context file, if it exists
IPath ctxFilePath = oldInstance.getContextFilePath(baseDir, ctx);
if (ctxFilePath != null) {
File ctxFile = ctxFilePath.toFile();
if (ctxFile.exists()) {
subMonitor.subTask(NLS.bind(Messages.deletingContextFile, ctxFile.getName()));
if (ctxFile.delete()) {
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Leftover context file " + ctxFile.getName() + " deleted.");
ms.add(new Status(IStatus.OK, TomcatPlugin.PLUGIN_ID, 0,
NLS.bind(Messages.deletedContextFile, ctxFile.getName()), null));
} else {
Trace.trace(Trace.SEVERE, "Could not delete obsolete context file " + ctxFilePath.toOSString());
ms.add(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0,
NLS.bind(Messages.errorCouldNotDeleteContextFile, ctxFilePath.toOSString()), null));
}
}
}
subMonitor.worked(100);
// Delete work directory associated with the removed context if it is within confDir.
// If it is outside of confDir, assume user is going to manage it.
IPath ctxWorkPath = oldInstance.getContextWorkDirectory(baseDir, ctx);
if (baseDir.isPrefixOf(ctxWorkPath)) {
File ctxWorkDir = ctxWorkPath.toFile();
if (ctxWorkDir.exists() && ctxWorkDir.isDirectory()) {
IStatus [] results = PublishHelper.deleteDirectory(ctxWorkDir, ProgressUtil.getSubMonitorFor(monitor, 100));
if (results.length > 0) {
Trace.trace(Trace.SEVERE, "Could not delete work directory " + ctxWorkDir.getPath() + " for removed context " + oldPath);
for (int i = 0; i < results.length; i++) {
ms.add(results[i]);
}
}
}
else
subMonitor.worked(100);
}
else
subMonitor.worked(100);
}
subMonitor.done();
}
monitor.worked(100);
// If requested, remove any separate context XML files for contexts being kept
if (removeKeptContextFiles && keptContextsMap.size() > 0) {
// Delete context files and work directories for managed web modules that have gone away
IProgressMonitor subMonitor = ProgressUtil.getSubMonitorFor(monitor, 100);
// TODO Improve task name
subMonitor.beginTask(Messages.deletingContextFilesTask, keptContextsMap.size() * 100);
Iterator iter = keptContextsMap.keySet().iterator();
while (iter.hasNext()) {
String keptPath = (String)iter.next();
Context ctx = (Context)keptContextsMap.get(keptPath);
// Delete the corresponding context file, if it exists
IPath ctxFilePath = oldInstance.getContextFilePath(baseDir, ctx);
if (ctxFilePath != null) {
File ctxFile = ctxFilePath.toFile();
if (ctxFile.exists()) {
subMonitor.subTask(NLS.bind(Messages.deletingContextFile, ctxFile.getName()));
if (ctxFile.delete()) {
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Leftover context file " + ctxFile.getName() + " deleted.");
ms.add(new Status(IStatus.OK, TomcatPlugin.PLUGIN_ID, 0,
NLS.bind(Messages.deletedContextFile, ctxFile.getName()), null));
} else {
Trace.trace(Trace.SEVERE, "Could not delete obsolete context file " + ctxFilePath.toOSString());
ms.add(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0,
NLS.bind(Messages.errorCouldNotDeleteContextFile, ctxFilePath.toOSString()), null));
}
}
}
subMonitor.worked(100);
}
subMonitor.done();
}
}
// Else no server.xml. Assume first publish to new temp directory
else {
monitor.worked(200);
}
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Server cleaned");
} catch (Exception e) {
Trace.trace(Trace.SEVERE, "Could not cleanup server at " + baseDir.toOSString() + ": " + e.getMessage());
ms.add(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0,
NLS.bind(Messages.errorCleanupServer, new String[] {e.getLocalizedMessage()}), e));
}
finally {
monitor.done();
}
return ms;
}
/**
* Creates a Catalina instance directory at the specified
* path. This involves creating the set of subdirectories
* uses by a Catalina instance.
*
* @param baseDir directory at which to create Catalina instance
* directories.
* @return result status of the operation
*/
public static IStatus createCatalinaInstanceDirectory(IPath baseDir) {
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Creating runtime directory at " + baseDir.toOSString());
// TODO Add more error handling.
// Prepare a catalina.base directory structure
File temp = baseDir.append("conf").toFile();
if (!temp.exists())
temp.mkdirs();
temp = baseDir.append("logs").toFile();
if (!temp.exists())
temp.mkdirs();
temp = baseDir.append("temp").toFile();
if (!temp.exists())
temp.mkdirs();
temp = baseDir.append("webapps").toFile();
if (!temp.exists())
temp.mkdirs();
temp = baseDir.append("work").toFile();
if (!temp.exists())
temp.mkdirs();
return Status.OK_STATUS;
}
/**
* Creates the specified deployment directory if it does not already exist.
* It will include a default ROOT web application using the specified web.xml.
*
* @param deployDir path to deployment directory to create
* @param webxml web.xml context to use for the ROOT web application.
* @return result status of the operation
*/
public static IStatus createDeploymentDirectory(IPath deployDir, String webxml) {
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Creating deployment directory at " + deployDir.toOSString());
// TODO Add more error handling.
File temp = deployDir.toFile();
if (!temp.exists())
temp.mkdirs();
IPath tempPath = deployDir.append("ROOT/WEB-INF");
temp = tempPath.toFile();
if (!temp.exists())
temp.mkdirs();
temp = tempPath.append("web.xml").toFile();
if (!temp.exists()) {
FileWriter fw;
try {
fw = new FileWriter(temp);
fw.write(webxml);
fw.close();
} catch (IOException e) {
Trace.trace(Trace.WARNING, "Unable to create web.xml for ROOT context.", e);
}
}
return Status.OK_STATUS;
}
/**
* Add context configuration found in META-INF/context.xml files
* present in projects to published server.xml. Used by
* Tomcat 4.1, 5.0, and 5.5 which support use of META-INF/context.xml
* in some form.
*
* @param baseDir absolute path to catalina instance directory
* @param webappsDir absolute path to deployment directory
* @param monitor a progress monitor or null
* @return result of operation
*/
public static IStatus publishCatalinaContextConfig(IPath baseDir, IPath webappsDir, IProgressMonitor monitor) {
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Apply context configurations");
IPath confDir = baseDir.append("conf");
try {
monitor = ProgressUtil.getMonitorFor(monitor);
monitor.beginTask(Messages.publishConfigurationTask, 300);
monitor.subTask(Messages.publishContextConfigTask);
Factory factory = new Factory();
factory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
Server publishedServer = (Server) factory.loadDocument(new FileInputStream(confDir.append("server.xml").toFile()));
ServerInstance publishedInstance = new ServerInstance(publishedServer, null, null);
monitor.worked(100);
boolean modified = false;
MultiStatus ms = new MultiStatus(TomcatPlugin.PLUGIN_ID, 0, Messages.publishContextConfigTask, null);
Context [] contexts = publishedInstance.getContexts();
if (contexts != null) {
for (int i = 0; i < contexts.length; i++) {
Context context = contexts[i];
monitor.subTask(NLS.bind(Messages.checkingContextTask,
new String[] {context.getPath()}));
if (addCatalinaContextConfig(webappsDir, context, ms)) {
modified = true;
}
}
}
monitor.worked(100);
if (modified) {
monitor.subTask(Messages.savingContextConfigTask);
factory.save(confDir.append("server.xml").toOSString());
}
// If problem(s) occurred adding context configurations, return error status
if (ms.getChildren().length > 0) {
return ms;
}
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Server.xml updated with context.xml configurations");
return Status.OK_STATUS;
} catch (Exception e) {
Trace.trace(Trace.WARNING, "Could not apply context configurations to published Tomcat configuration from " + confDir.toOSString() + ": " + e.getMessage());
return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorPublishConfiguration, new String[] {e.getLocalizedMessage()}), e);
}
finally {
monitor.done();
}
}
/**
* If the specified Context is linked to a project, try to
* update it with any configuration from a META-INF/context.xml found
* relative to the specified web applications directory and context docBase.
*
* @param webappsDir Path to server's web applications directory.
* @param context Context object to receive context.xml contents.
* @param ms MultiStatus object to receive error status.
* @return Returns true if context is modified.
*/
private static boolean addCatalinaContextConfig(IPath webappsDir, Context context, MultiStatus ms) {
boolean modified = false;
String source = context.getSource();
if (source != null && source.length() > 0 )
{
File docBase = new File(context.getDocBase());
if (!docBase.isAbsolute())
docBase = new File(webappsDir.toOSString(), docBase.getPath());
try {
Context contextConfig = loadCatalinaContextConfig(docBase);
if (null != contextConfig) {
if (context.hasChildNodes())
context.removeChildren();
contextConfig.copyChildrenTo(context);
Map attrs = contextConfig.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);
context.setAttributeValue(name, value);
}
}
modified = true;
}
} catch (Exception e) {
String contextPath = context.getPath();
if (contextPath.startsWith("/")) {
contextPath = contextPath.substring(1);
}
Trace.trace(Trace.SEVERE, "Error reading context.xml file for " + contextPath, e);
IStatus s = new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0,
NLS.bind(Messages.errorCouldNotLoadContextXml, contextPath), e);
ms.add(s);
}
}
return modified;
}
/**
* Tries to read a META-INF/context.xml file relative to the
* specified web application path. If found, it creates a Context object
* containing the contexts of that file.
*
* @param docBase File with absolute path to the web application
* @return Context element created from context.xml, or null if not found.
* @throws SAXException If there is a error parsing the XML.
* @throws IOException If there is an error reading the file.
*/
private static Context loadCatalinaContextConfig(File docBase) throws IOException, SAXException {
File contextXML = new File(docBase, "META-INF" + File.separator + "context.xml");
if (contextXML.exists()) {
try {
InputStream is = new FileInputStream(contextXML);
Factory ctxFactory = new Factory();
ctxFactory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
Context ctx = (Context)ctxFactory.loadDocument(is);
is.close();
return ctx;
} catch (FileNotFoundException e) {
// Ignore, should never occur
}
}
return null;
}
/**
* If modules are not being deployed to the "webapps" directory, the
* context for the published modules is updated to contain the
* corrected docBase.
*
* @param baseDir runtime base directory for the server
* @param deployDir deployment directory for the server
* @param server server being localized
* @param monitor a progress monitor
* @return result of operation
*/
public static IStatus localizeConfiguration(IPath baseDir, IPath deployDir, TomcatServer server, IProgressMonitor monitor) {
try {
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Localizing configuration at " + baseDir);
monitor = ProgressUtil.getMonitorFor(monitor);
monitor.beginTask(Messages.publishConfigurationTask, 300);
IPath serverXml = baseDir.append("conf/server.xml");
Factory factory = new Factory();
factory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
Server publishedServer = (Server)factory.loadDocument(
new FileInputStream(serverXml.toFile()));
ServerInstance publishedInstance = new ServerInstance(publishedServer, null, null);
monitor.worked(100);
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
boolean modified = false;
// Only add root module if running in a test env (i.e. not on the installation)
boolean addRootWebapp = server.isTestEnvironment();
// If not deploying to "webapps", context docBase attributes need updating
// TODO Improve to compare with appBase value instead of hardcoded "webapps"
boolean deployingToAppBase = "webapps".equals(server.getDeployDirectory());
Map pathMap = new HashMap();
MultiStatus ms = new MultiStatus(TomcatPlugin.PLUGIN_ID, 0,
NLS.bind(Messages.errorPublishServer, server.getServer().getName()), null);
Context [] contexts = publishedInstance.getContexts();
if (contexts != null) {
for (int i = 0; i < contexts.length; i++) {
Context context = contexts[i];
// Normalize path and check for duplicates
String path = context.getPath();
if (path != null) {
// Save a copy of original in case it's "/"
String origPath = path;
// Normalize "/" to ""
if ("/".equals(path)) {
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Context path is being changed from \"/\" to \"\".");
path = "";
context.setPath(path);
modified = true;
}
// Context paths that are the same or differ only in case are not allowed
String lcPath = path.toLowerCase();
if (!pathMap.containsKey(lcPath)) {
pathMap.put(lcPath, origPath);
}
else {
String otherPath = (String)pathMap.get(lcPath);
IStatus s = new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID,
origPath.equals(otherPath) ? NLS.bind(Messages.errorPublishPathDup, origPath)
: NLS.bind(Messages.errorPublishPathConflict, origPath, otherPath));
ms.add(s);
}
}
else {
IStatus s = new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID,
Messages.errorPublishPathMissing);
ms.add(s);
}
// If default webapp has not been found, check this one
// TODO Need to add a root context if deploying to webapps but with auto-deploy off
if (addRootWebapp && "".equals(context.getPath())) {
// A default webapp is being deployed, don't add one
addRootWebapp = false;
}
// If not deploying to appBase, convert to absolute path under deploy dir
if (!deployingToAppBase) {
String source = context.getSource();
if (source != null && source.length() > 0 ) {
context.setDocBase(deployDir.append(context.getDocBase()).toOSString());
modified = true;
}
}
}
}
// If errors are present, return status
if (!ms.isOK())
return ms;
if (addRootWebapp) {
// Add a context for the default webapp
Context rootContext = publishedInstance.createContext(0);
rootContext.setPath("");
rootContext.setDocBase(deployDir.append("ROOT").toOSString());
rootContext.setReloadable("false");
modified = true;
}
monitor.worked(100);
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
if (modified) {
monitor.subTask(Messages.savingContextConfigTask);
factory.save(serverXml.toOSString());
}
monitor.worked(100);
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Context docBase settings updated in server.xml.");
}
catch (Exception e) {
Trace.trace(Trace.WARNING, "Could not localize server configuration published to " + baseDir.toOSString() + ": " + e.getMessage());
return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorPublishConfiguration, new String[] {e.getLocalizedMessage()}), e);
}
finally {
monitor.done();
}
return Status.OK_STATUS;
}
/**
* Copies the custom loader jar required to serve projects without
* publishing to the specified destination directory.
*
* @param destDir destination directory for the loader jar
* @param serverId ID of the server receiving the jar
* @return result of copy operation
*/
public static IStatus copyLoaderJar(IPath destDir, String serverId) {
String loaderJar = "/" + serverId + ".loader.jar";
URL installURL = TomcatPlugin.getInstance().getBundle().getEntry(loaderJar);
if (installURL == null) {
Trace.trace(Trace.SEVERE, "Loader jar not found for server ID " + serverId);
return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorPublishLoaderJarNotFound, serverId), null);
}
URL localURL;
try {
localURL = FileLocator.toFileURL(installURL);
} catch (IOException e) {
Trace.trace(Trace.SEVERE, "Could not convert " + installURL.toString() + " to file URL: " + e.getMessage());
return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorPublishURLConvert,
new String[] {installURL.toString(), e.getLocalizedMessage()}), e);
}
destDir.toFile().mkdirs();
IStatus status = FileUtil.copyFile(localURL, destDir.append(loaderJar).toString());
return status;
}
/**
* Tries to delete the custom loader jar added to support serving projects directly
* without publishing. Returns a warning if not successful.
*
* @param destDir destination directory containing the loader jar
* @param serverId ID of the server from which to delete the jar
* @return result of copy operation
*/
public static IStatus removeLoaderJar(IPath destDir, String serverId) {
String loaderJar = "/" + serverId + ".loader.jar";
File loaderFile = destDir.append(loaderJar).toFile();
// If loader jar exists but is not successfully deleted, return warning
if (loaderFile.exists() && !loaderFile.delete())
return new Status(IStatus.WARNING, TomcatPlugin.PLUGIN_ID, 0,
NLS.bind(Messages.errorPublishCantDeleteLoaderJar, loaderFile.getPath()), null);
return Status.OK_STATUS;
}
/**
* Updates the catalina.properties file to include a extra entry in the
* specified loader property to pickup the loader jar.
*
* @param baseDir directory where the Catalina instance is found
* @param jarLoc location of loader jar relative to baseDir
* @param loader loader in catalina.properties to use
* @return result of update operation
*/
public static IStatus updatePropertiesToServeDirectly(IPath baseDir, String jarLoc, String loader) {
File catalinaProperties = baseDir.append(
"conf/catalina.properties").toFile();
try {
CatalinaPropertiesUtil.addGlobalClasspath(catalinaProperties, loader,
new String[] { "${catalina.base}/" + jarLoc + "/*.jar" });
} catch (IOException e) {
return new Status(IStatus.ERROR,TomcatPlugin.PLUGIN_ID,
NLS.bind(Messages.errorPublishCatalinaProps, e.getLocalizedMessage()), e);
}
return Status.OK_STATUS;
}
/**
* Update Contexts to serve web projects directly.
*
* @param baseDir directory where the Catalina instance is found
* @param loader name of the catalina.properties loader to use for global
* classpath entries
* @param monitor a progress monitor
* @return result of update operation
*/
public static IStatus updateContextsToServeDirectly(IPath baseDir, String loader, boolean enableMetaInfResources, IProgressMonitor monitor) {
IPath confDir = baseDir.append("conf");
IPath serverXml = confDir.append("server.xml");
try {
monitor = ProgressUtil.getMonitorFor(monitor);
monitor.beginTask(Messages.publishConfigurationTask, 300);
monitor.subTask(Messages.publishContextConfigTask);
Factory factory = new Factory();
factory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
Server publishedServer = (Server) factory.loadDocument(new FileInputStream(serverXml.toFile()));
ServerInstance publishedInstance = new ServerInstance(publishedServer, null, null);
monitor.worked(100);
boolean modified = false;
// care about top-level modules only
TomcatPublishModuleVisitor visitor = new TomcatPublishModuleVisitor(
baseDir, publishedInstance, loader, enableMetaInfResources);
Context [] contexts = publishedInstance.getContexts();
for (int i = 0; i < contexts.length; i++) {
String moduleId = contexts[i].getSource();
if (moduleId != null && moduleId.length() > 0) {
IModule module = ServerUtil.getModule(moduleId);
ModuleTraverser.traverse(module, visitor, monitor);
modified = true;
}
}
if (modified) {
monitor.subTask(Messages.savingContextConfigTask);
factory.save(serverXml.toOSString());
}
monitor.worked(100);
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Context docBase settings updated in server.xml.");
} catch (Exception e) {
Trace.trace(Trace.SEVERE, "Could not modify context configurations to serve directly for Tomcat configuration " + confDir.toOSString() + ": " + e.getMessage());
return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorPublishConfiguration, new String[] {e.getLocalizedMessage()}), e);
}
finally {
monitor.done();
}
return Status.OK_STATUS;
}
/**
* Moves contexts out of current published server.xml and into individual
* context XML files.
*
* @param baseDir directory where the Catalina instance is found
* @param noPath true if path attribute should be removed from the context
* @param serverStopped true if the server is stopped
* @param monitor a progress monitor
* @return result of operation
*/
public static IStatus moveContextsToSeparateFiles(IPath baseDir, boolean noPath, boolean serverStopped, IProgressMonitor monitor) {
IPath confDir = baseDir.append("conf");
IPath serverXml = confDir.append("server.xml");
try {
monitor = ProgressUtil.getMonitorFor(monitor);
monitor.beginTask(Messages.publishConfigurationTask, 300);
monitor.subTask(Messages.publishContextConfigTask);
Factory factory = new Factory();
factory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
Server publishedServer = (Server) factory.loadDocument(new FileInputStream(serverXml.toFile()));
ServerInstance publishedInstance = new ServerInstance(publishedServer, null, null);
monitor.worked(100);
boolean modified = false;
Host host = publishedInstance.getHost();
Context[] wtpContexts = publishedInstance.getContexts();
if (wtpContexts != null && wtpContexts.length > 0) {
IPath contextPath = publishedInstance.getContextXmlDirectory(serverXml.removeLastSegments(1));
File contextDir = contextPath.toFile();
if (!contextDir.exists()) {
contextDir.mkdirs();
}
// Process in reverse order, since contexts may be removed
for (int i = wtpContexts.length - 1; i >= 0; i--) {
Context context = wtpContexts[i];
// TODO Handle non-project contexts when their removal can be addressed
if (context.getSource() == null)
continue;
String name = context.getPath();
if (name.startsWith("/")) {
name = name.substring(1);
}
// If the default context, adjust the file name
if (name.length() == 0) {
name = "ROOT";
}
// TODO Determine circumstances, if any, where setting antiResourceLocking true can cause the original docBase content to be deleted.
if (Boolean.valueOf(context.getAttributeValue("antiResourceLocking")).booleanValue())
context.setAttributeValue("antiResourceLocking", "false");
File contextFile = new File(contextDir, name + ".xml");
Context existingContext = loadContextFile(contextFile);
// If server is stopped or if contexts are not the equivalent, write the context file
if (serverStopped || !context.isEquivalent(existingContext)) {
// If requested, remove path attribute
if (noPath)
context.removeAttribute("path");
DocumentBuilder builder = XMLUtil.getDocumentBuilder();
Document contextDoc = builder.newDocument();
contextDoc.appendChild(contextDoc.importNode(context.getElementNode(), true));
XMLUtil.save(contextFile.getAbsolutePath(), contextDoc);
}
host.removeElement("Context", i);
modified = true;
}
}
monitor.worked(100);
if (modified) {
monitor.subTask(Messages.savingContextConfigTask);
factory.save(serverXml.toOSString());
}
monitor.worked(100);
if (Trace.isTraceEnabled())
Trace.trace(Trace.FINER, "Context docBase settings updated in server.xml.");
} catch (Exception e) {
Trace.trace(Trace.SEVERE, "Could not modify context configurations to serve directly for Tomcat configuration " + confDir.toOSString() + ": " + e.getMessage());
return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0, NLS.bind(Messages.errorPublishConfiguration, new String[] {e.getLocalizedMessage()}), e);
}
finally {
monitor.done();
}
return Status.OK_STATUS;
}
private static void loadSeparateContextFiles(File contextDir, Factory factory, Map projectContexts) {
File[] contextFiles = contextDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".xml");
}
});
for (int j = 0; j < contextFiles.length; j++) {
File ctx = contextFiles[j];
Context context = loadContextFile(ctx);
if (context != null) {
// TODO Handle non-project contexts when their removal can be addressed
String memento = context.getSource();
if (memento != null) {
projectContexts.put(ctx, context);
}
}
}
}
private static Context loadContextFile(File contextFile) {
FileInputStream fis = null;
Context context = null;
if (contextFile != null && contextFile.exists()) {
try {
Factory factory = new Factory();
factory.setPackageName("org.eclipse.jst.server.tomcat.core.internal.xml.server40");
fis = new FileInputStream(contextFile);
context = (Context)factory.loadDocument(fis);
if (context != null) {
String path = context.getPath();
// If path attribute is not set, derive from file name
if (path == null) {
String fileName = contextFile.getName();
path = fileName.substring(0, fileName.length() - ".xml".length());
if ("ROOT".equals(path))
path = "";
context.setPath("/" + path);
}
}
} catch (Exception e) {
// may be a spurious xml file in the host dir?
Trace.trace(Trace.FINER, "Unable to read context "
+ contextFile.getAbsolutePath());
} finally {
try {
fis.close();
} catch (IOException e) {
// ignore
}
}
}
return context;
}
private static Map catalinaJarVersion = new ConcurrentHashMap();
private static Map catalinaJarLastModified = new HashMap();
private static volatile long lastCheck = 0;
/**
* Checks if the version of Tomcat installed at the specified location matches
* the specified server type. The return status indicates if the version matches
* or not, or can't be determined.
*
* Because this can get called repeatedly for certain operations, some caching
* is provided. The first check for an installPath in the current Eclipse
* session will query the catalina.jar for its version. Any additional
* checks will compare the catalina.jar's time stamp and will use the previously
* cached version if it didn't change. Additional checks that occur within
* 2 seconds of the last check, regardless of Tomcat version, don't bother with
* checking the jar time stamp and just use the cached values.
*
* @param installPath Path to Tomcat installation
* @param serverType The server type ID for the desired version of Tomcat
* @return Returns Status.OK_Status if check succeeds, or an error status
* if the check fails. If the check can't determine if the version matches,
* Status.CANCEL_STATUS is returned.
*/
public static IStatus checkCatalinaVersion(IPath installPath, String serverType) {
String versionSubString = null;
IPath catalinaJarPath = null;
File jarFile = null;
if (TomcatPlugin.TOMCAT_60.equals(serverType) || TomcatPlugin.TOMCAT_70.equals(serverType)) {
catalinaJarPath = installPath.append("lib").append("catalina.jar");
jarFile = catalinaJarPath.toFile();
// If jar is not at expected location, try alternate location
if (!jarFile.exists()) {
catalinaJarPath = installPath.append("server/lib").append("catalina.jar");
jarFile = catalinaJarPath.toFile();
// If not here either, discard path
if (!jarFile.exists()) {
catalinaJarPath = null;
}
}
}
else if (TomcatPlugin.TOMCAT_50.equals(serverType) || TomcatPlugin.TOMCAT_55.equals(serverType)
|| TomcatPlugin.TOMCAT_41.equals(serverType)) {
catalinaJarPath = installPath.append("server/lib").append("catalina.jar");
jarFile = catalinaJarPath.toFile();
// If jar is not at expected location, try alternate location
if (!jarFile.exists()) {
catalinaJarPath = installPath.append("lib").append("catalina.jar");
jarFile = catalinaJarPath.toFile();
// If not here either, discard path
if (!jarFile.exists()) {
catalinaJarPath = null;
}
}
}
if (catalinaJarPath != null) {
versionSubString = (String)catalinaJarVersion.get(catalinaJarPath);
long checkTime = System.currentTimeMillis();
// Use some logic to try to determine if a cached value is stale
// If last check was more than a couple of seconds ago, check the jar time stamp
if (versionSubString != null && (checkTime - lastCheck > 2000)) {
long curLastModified = jarFile.lastModified();
Long oldLastModified = (Long)catalinaJarLastModified.get(catalinaJarPath);
// If jar time stamps differ, discard the cached version string
if (oldLastModified == null || curLastModified != oldLastModified.longValue()) {
versionSubString = null;
}
}
lastCheck = checkTime;
// If a version string needs to be acquired
if (versionSubString == null) {
InputStream is = null;
try {
// Read version string from catalina.jar
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry("org/apache/catalina/util/ServerInfo.properties");
if (entry != null) {
is = jar.getInputStream(entry);
if (is != null) {
Properties props = new Properties();
props.load(is);
String serverVersion = props.getProperty("server.info");
if (serverVersion != null) {
int index = serverVersion.indexOf("/");
if (index > 0) {
versionSubString = serverVersion.substring(index + 1);
catalinaJarVersion.put(catalinaJarPath, versionSubString);
catalinaJarLastModified.put(catalinaJarPath, new Long(jarFile.lastModified()));
}
else {
return Status.CANCEL_STATUS;
}
}
}
}
else {
return Status.CANCEL_STATUS;
}
} catch (IOException e) {
return Status.CANCEL_STATUS;
}
finally {
if (is != null) {
try {
is.close();
}
catch (IOException e) {
// Ignore
}
}
}
}
if (versionSubString != null) {
String versionTest = (String)versionStringMap.get(serverType);
if (versionTest != null && !versionSubString.startsWith(versionTest)) {
return new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID,
NLS.bind(Messages.errorInstallDirWrongVersion2,
versionSubString, versionTest.substring(0, versionTest.length() -1)));
}
}
}
// Else server type is not supported or jar doesn't exist
else {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
}