[124914] Implement remaining portion of experimental Tomcat plug-in by saving contexts to separate XML files
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatServer.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatServer.java
index 4e09d34..03e2194 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatServer.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatServer.java
@@ -38,6 +38,12 @@
 	 * publishing.
 	 */
 	public static final String PROPERTY_SERVE_MODULES_WITHOUT_PUBLISH = "serveModulesWithoutPublish";
+	
+	/**
+	 * Property which specifies contexts in the server.xml file should
+	 * be saved to separate context files.
+	 */
+	public static final String PROPERTY_SAVE_SEPARATE_CONTEXT_FILES = "saveSeparateContextFiles";
 
 	/**
 	 * Returns true if this is a test (publish and run code out of the
@@ -72,4 +78,12 @@
 	 * @return true if modules should not be published but served directly
 	 */
 	public boolean isServeModulesWithoutPublish();
+	
+	/**
+	 * Returns true if contexts should be saved to separate context
+	 * files instead of being kept within server.xml when the server
+	 * is published.
+	 * @return true if contexts should be saved to separate files
+	 */
+	public boolean isSaveSeparateContextFiles();
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatServerWorkingCopy.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatServerWorkingCopy.java
index b6dc882..30ac7be 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatServerWorkingCopy.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatServerWorkingCopy.java
@@ -54,4 +54,12 @@
 	 * @param b true if modules should be served without publishing
 	 */
 	public void setServeModulesWithoutPublish(boolean b);
+	
+	/**
+	 * Set this server to save contexts to separate context
+	 * files when publishing the server.
+	 * @param b true if contexts should be saved to separate files
+	 * when publishing the server
+	 */
+	public void setSaveSeparateContextFiles(boolean b);
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatVersionHandler.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatVersionHandler.java
index 9cea32d..e0e7ceb 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatVersionHandler.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/ITomcatVersionHandler.java
@@ -161,4 +161,11 @@
 	 * @return true if debug argument is supported
 	 */
 	public boolean supportsDebugArgument();
+	
+	/**
+	 * Returns true if this server supports separate context files.
+	 * 
+	 * @return true if this server supports separate context files
+	 */
+	public boolean supportsSeparateContextFiles();
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.java
index 732211f..bc3f02b 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.java
@@ -83,6 +83,7 @@
 	public static String errorXMLContextNotFoundPath;
 	public static String errorXMLContextMangerNotFound;
 	public static String errorXMLContextNotFoundPath32;
+	public static String errorXMLNullContextArg;
 	public static String errorNoPublishNotSupported;
 	public static String errorPublishContextNotFound;
 	public static String errorPublishCouldNotRemoveModule;
@@ -104,6 +105,7 @@
 	public static String serverEditorActionSetServerDirectory;
 	public static String serverEditorActionSetDeployDirectory;
 	public static String serverEditorActionSetServeWithoutPublish;
+	public static String serverEidtorActionSetSeparateContextFiles;
 	public static String fixModuleContextRootDescription;
 	public static String fixModuleContextRoot;
 
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.properties b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.properties
index 407bc59..10ede7c 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.properties
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Messages.properties
@@ -100,6 +100,7 @@
 errorXMLContextNotFoundPath=Context with path \"{0}\" was not found under Service \"{1}\", Engine \"{2}\", and Host \"{3}\".
 errorXMLContextMangerNotFound=ContextManager element was not found.
 errorXMLContextNotFoundPath32=Context with path \"{0}\" was not found under ContextManager.
+errorXMLNullContextArg=Context argument may not be null.
 errorNoPublishNotSupported=Serving modules without publishing is not supported by this server.
 errorPublishContextNotFound=Context not found for module {0}.
 errorPublishCouldNotRemoveModule=Could not remove module {0}.
@@ -113,7 +114,8 @@
 
 # Actions (used in undo/redo menus)
 serverEditorActionSetSecure=Tomcat Security Option Change
-serverEditorActionSetDebugMode=Tomcat Debug Option Change
+serverEditorActionSetDebugMode=Tomcat Debug Logging Option Change
 serverEditorActionSetServerDirectory=Tomcat Server Path Change
 serverEditorActionSetDeployDirectory=Tomcat Deploy Path Change
-serverEditorActionSetServeWithoutPublish=Serve Modules Without Publish Change
+serverEditorActionSetServeWithoutPublish=Serve Modules Without Publish Option Change
+serverEidtorActionSetSeparateContextFiles=Publish Contexts To Separate Files Option Change
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat32Handler.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat32Handler.java
index 2e0d348..87de94e 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat32Handler.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat32Handler.java
@@ -179,4 +179,11 @@
 	public boolean supportsDebugArgument() {
 		return true;
 	}
+
+	/**
+	 * @see ITomcatVersionHandler#supportsSeparateContextFiles()
+	 */
+	public boolean supportsSeparateContextFiles() {
+		return false;
+	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat40Configuration.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat40Configuration.java
index 5508438..a9d0021 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat40Configuration.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat40Configuration.java
@@ -561,9 +561,9 @@
 	 * @param monitor a progress monitor or null
 	 * @return MultiStatus containing results of the cleanup operation
 	 */
-	protected IStatus cleanupServer(IPath baseDir, IPath installDir, IProgressMonitor monitor) {
+	protected IStatus cleanupServer(IPath baseDir, IPath installDir, boolean removeKeptContextFiles, IProgressMonitor monitor) {
 		List modules = getWebModules();
-		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, modules, monitor);
+		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, removeKeptContextFiles, modules, monitor);
 	}
 
 	/**
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat40Handler.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat40Handler.java
index 36eed35..dfb24c7 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat40Handler.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat40Handler.java
@@ -174,4 +174,11 @@
 	public boolean supportsDebugArgument() {
 		return true;
 	}
+
+	/**
+	 * @see ITomcatVersionHandler#supportsSeparateContextFiles()
+	 */
+	public boolean supportsSeparateContextFiles() {
+		return false;
+	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat41Configuration.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat41Configuration.java
index 3c3481d..2c43081 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat41Configuration.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat41Configuration.java
@@ -600,9 +600,9 @@
 	 * @param monitor a progress monitor or null
 	 * @return MultiStatus containing results of the cleanup operation
 	 */
-	protected IStatus cleanupServer(IPath baseDir, IPath installDir, IProgressMonitor monitor) {
+	protected IStatus cleanupServer(IPath baseDir, IPath installDir, boolean removeKeptContextFiles, IProgressMonitor monitor) {
 		List modules = getWebModules();
-		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, modules, monitor);
+		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, removeKeptContextFiles, modules, monitor);
 	}
 
 	/**
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat41Handler.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat41Handler.java
index 8b9ad2d..6d49cf2 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat41Handler.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat41Handler.java
@@ -160,4 +160,15 @@
 	public boolean supportsDebugArgument() {
 		return true;
 	}
+
+	/**
+	 * While Tomcat 4.1.x does implement a form of separate
+	 * context files, these file are found outside of the
+	 * "conf" directory and is not practical to
+	 * support.
+	 * @see ITomcatVersionHandler#supportsSeparateContextFiles()
+	 */
+	public boolean supportsSeparateContextFiles() {
+		return false;
+	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat50Configuration.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat50Configuration.java
index 8385035..fc3aae2 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat50Configuration.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat50Configuration.java
@@ -17,7 +17,6 @@
 import java.io.FileWriter;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
@@ -27,9 +26,7 @@
 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.Status;
-import org.eclipse.jst.server.core.PublishUtil;
 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.Connector;
@@ -625,7 +622,7 @@
 	/**
 	 * Cleanup the server instance.  This consists of deleting the work
 	 * directory associated with Contexts that are going away in the
-	 * up coming publish.  Also, Context XML files which may have been
+	 * up coming publish.  In addition, Context XML files which may have been
 	 * created for these Contexts are also deleted.
 	 * 
 	 * @param baseDir path to server instance directory, i.e. catalina.base
@@ -633,96 +630,9 @@
 	 * @param monitor a progress monitor or null
 	 * @return MultiStatus containing results of the cleanup operation
 	 */
-	protected IStatus cleanupServer(IPath baseDir, IPath installDir, IProgressMonitor monitor) {
-		MultiStatus ms = new MultiStatus(TomcatPlugin.PLUGIN_ID, 0, Messages.cleanupServerTask, null);
-		monitor = ProgressUtil.getMonitorFor(monitor);
-		monitor.beginTask(Messages.cleanupServerTask, 200);
-
-		try {
-			monitor.subTask(Messages.detectingRemovedProjects);
-
-			IPath serverXml = baseDir.append("conf").append("server.xml");
-			ServerInstance oldInstance = TomcatVersionHelper.getCatalinaServerInstance(serverXml, null, null);
-			if (oldInstance != null) {
-				List modules = getWebModules();
-				Collection oldPaths = TomcatVersionHelper.getRemovedCatalinaContexts(oldInstance, modules);
-				monitor.worked(100);
-				if (oldPaths != null && oldPaths.size() > 0) {
-					// Begin building path to context directory
-					IPath contextXmlDir = oldInstance.getContextXmlDirectory(baseDir.append("conf"));
-
-					// Delete context files and work directories for managed web modules that have gone away
-					if (oldPaths.size() > 0 ) {
-						IProgressMonitor subMonitor = ProgressUtil.getSubMonitorFor(monitor, 100);
-						subMonitor.beginTask(Messages.deletingContextFilesTask, oldPaths.size() * 200);
-						
-						Iterator iter = oldPaths.iterator();
-						while (iter.hasNext()) {
-							String oldPath = (String)iter.next();
-							// Derive the context file name from the path + ".xml", minus the leading '/'
-							String fileName;
-							if (oldPath.length() > 0)
-								fileName = oldPath.substring(1) + ".xml";
-							else
-								fileName = "ROOT.xml";
-							IPath contextPath = contextXmlDir.append(fileName);
-							File contextFile = contextPath.toFile();
-							if (contextFile.exists()) {
-								subMonitor.subTask(NLS.bind(Messages.deletingContextFile, fileName));
-								if (contextFile.delete()) {
-									if (Trace.isTraceEnabled())
-										Trace.trace(Trace.FINER, "Leftover context file " + fileName + " deleted.");
-									ms.add(new Status(IStatus.OK, TomcatPlugin.PLUGIN_ID, 0,
-											NLS.bind(Messages.deletedContextFile, fileName), null));
-								} else {
-									Trace.trace(Trace.SEVERE, "Could not delete obsolete context file " + contextPath.toOSString());
-									ms.add(new Status(IStatus.ERROR, TomcatPlugin.PLUGIN_ID, 0,
-											NLS.bind(Messages.errorCouldNotDeleteContextFile, contextPath.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.
-							Context ctx = oldInstance.getContext(oldPath);
-							IPath ctxWorkPath = oldInstance.getContextWorkDirectory(baseDir, ctx);
-							if (baseDir.isPrefixOf(ctxWorkPath)) {
-								File ctxWorkDir = ctxWorkPath.toFile();
-								if (ctxWorkDir.exists() && ctxWorkDir.isDirectory()) {
-									IStatus [] results = PublishUtil.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
-									monitor.worked(100);
-							}
-							else
-								monitor.worked(100);
-						}
-						subMonitor.done();
-					} else {
-						monitor.worked(100);
-					}
-				}
-			}
-			// 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));
-		}
-		
-		monitor.done();
-		return ms;
+	protected IStatus cleanupServer(IPath baseDir, IPath installDir, boolean removeKeptContextFiles, IProgressMonitor monitor) {
+		List modules = getWebModules();
+		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, removeKeptContextFiles, modules, monitor);
 	}
 
 	/**
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat50Handler.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat50Handler.java
index fc60147..7b7d198 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat50Handler.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat50Handler.java
@@ -177,4 +177,11 @@
 	public boolean supportsDebugArgument() {
 		return false;
 	}
+
+	/**
+	 * @see ITomcatVersionHandler#supportsSeparateContextFiles()
+	 */
+	public boolean supportsSeparateContextFiles() {
+		return true;
+	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat55Configuration.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat55Configuration.java
index 61d5433..89ac151 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat55Configuration.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat55Configuration.java
@@ -628,9 +628,9 @@
 	 * @param monitor a progress monitor or null
 	 * @return MultiStatus containing results of the cleanup operation
 	 */
-	protected IStatus cleanupServer(IPath baseDir, IPath installDir, IProgressMonitor monitor) {
+	protected IStatus cleanupServer(IPath baseDir, IPath installDir, boolean removeKeptContextFiles, IProgressMonitor monitor) {
 		List modules = getWebModules();
-		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, modules, monitor);
+		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, removeKeptContextFiles, modules, monitor);
 	}
 
 	/**
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat60Configuration.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat60Configuration.java
index 2e07671..b0f706e 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat60Configuration.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat60Configuration.java
@@ -630,9 +630,9 @@
 	 * @param monitor a progress monitor or null
 	 * @return MultiStatus containing results of the cleanup operation
 	 */
-	protected IStatus cleanupServer(IPath baseDir, IPath installDir, IProgressMonitor monitor) {
+	protected IStatus cleanupServer(IPath baseDir, IPath installDir, boolean removeKeptContextFiles, IProgressMonitor monitor) {
 		List modules = getWebModules();
-		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, modules, monitor);
+		return TomcatVersionHelper.cleanupCatalinaServer(baseDir, installDir, removeKeptContextFiles, modules, monitor);
 	}
 
 	/**
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat60Handler.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat60Handler.java
index 7ac82a2..640d106 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat60Handler.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/Tomcat60Handler.java
@@ -171,4 +171,11 @@
 	public boolean supportsDebugArgument() {
 		return false;
 	}
+
+	/**
+	 * @see ITomcatVersionHandler#supportsSeparateContextFiles()
+	 */
+	public boolean supportsSeparateContextFiles() {
+		return true;
+	}
 }
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatConfiguration.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatConfiguration.java
index 15f636e..ded24bf 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatConfiguration.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatConfiguration.java
@@ -185,7 +185,7 @@
 		return Status.OK_STATUS;
 	}
 	
-	protected IStatus cleanupServer(IPath confDir, IPath installDir, IProgressMonitor monitor) {
+	protected IStatus cleanupServer(IPath confDir, IPath installDir, boolean removeKeptContextFiles, IProgressMonitor monitor) {
 		// Default implementation assumes nothing to clean
 		return Status.OK_STATUS;
 	}
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServer.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServer.java
index 6106f14..648d465 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServer.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServer.java
@@ -233,6 +233,20 @@
 	}
 	
 	/**
+	 * Returns true if contexts should be saved in separate files
+	 * during server publish.
+	 * 
+	 * @return boolean
+	 */
+	public boolean isSaveSeparateContextFiles() {
+		// If feature is supported, return current setting
+		if (versionHandler.supportsSeparateContextFiles())
+			return getAttribute(PROPERTY_SAVE_SEPARATE_CONTEXT_FILES, false);
+		return false;
+	}
+
+	
+	/**
 	 * Gets the base directory where the server instance runs.  This
 	 * path can vary depending on the configuration. Null may be returned
 	 * if a runtime hasn't been specified for the server.
@@ -423,6 +437,13 @@
 	}
 
 	/**
+	 * @see ITomcatServerWorkingCopy#setSaveSeparateContextFiles(boolean)
+	 */
+	public void setSaveSeparateContextFiles(boolean b) {
+		setAttribute(PROPERTY_SAVE_SEPARATE_CONTEXT_FILES, b);
+	}
+
+	/**
 	 * @see ServerDelegate#modifyModules(IModule[], IModule[], IProgressMonitor)
 	 */
 	public void modifyModules(IModule[] add, IModule[] remove, IProgressMonitor monitor) throws CoreException {
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServerBehaviour.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServerBehaviour.java
index 11f8c41..881e51d 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServerBehaviour.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatServerBehaviour.java
@@ -218,7 +218,8 @@
 		monitor = ProgressUtil.getMonitorFor(monitor);
 		monitor.beginTask(Messages.publishServerTask, 600);
 		
-		status = getTomcatConfiguration().cleanupServer(confDir, installDir, ProgressUtil.getSubMonitorFor(monitor, 100));
+		status = getTomcatConfiguration().cleanupServer(confDir, installDir,
+				!getTomcatServer().isSaveSeparateContextFiles(), ProgressUtil.getSubMonitorFor(monitor, 100));
 		if (status != null && !status.isOK())
 			throw new CoreException(status);
 		
@@ -357,12 +358,13 @@
 	protected void publishFinish(IProgressMonitor monitor) throws CoreException {
 		IStatus status;
 		IPath baseDir = getRuntimeBaseDirectory();
+		TomcatServer ts = getTomcatServer();
 		ITomcatVersionHandler tvh = getTomcatVersionHandler();
 		// Include or remove loader jar depending on state of serving directly 
 		status = tvh.prepareForServingDirectly(baseDir, getTomcatServer());
 		if (status.isOK()) {
 			// If serving modules directly, update server.xml accordingly (includes project context.xmls)
-			if (getTomcatServer().isServeModulesWithoutPublish()) {
+			if (ts.isServeModulesWithoutPublish()) {
 				status = getTomcatConfiguration().updateContextsToServeDirectly(
 						baseDir, tvh.getSharedLoader(baseDir), monitor);
 			}
@@ -372,6 +374,13 @@
 				status = getTomcatConfiguration().publishContextConfig(
 						baseDir, getServerDeployDirectory(), monitor);
 			}
+			if (status.isOK() && ts.isSaveSeparateContextFiles()) {
+				// Determine if context's path attribute should be removed
+				String id = getServer().getServerType().getId();
+				boolean noPath = id.indexOf("55") > 0 || id.indexOf("60") > 0;
+				// TODO Add a monitor
+				TomcatVersionHelper.moveContextsToSeparateFiles(baseDir, noPath, null);
+			}
 		}
 		if (!status.isOK())
 			throw new CoreException(status);
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatVersionHelper.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatVersionHelper.java
index 1d733de..19dd7a2 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatVersionHelper.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/TomcatVersionHelper.java
@@ -15,17 +15,19 @@
 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.HashSet;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilder;
 
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.FileLocator;
@@ -38,12 +40,15 @@
 import org.eclipse.jst.server.core.PublishUtil;
 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.w3c.dom.Document;
 import org.xml.sax.SAXException;
 
 /**
@@ -191,6 +196,23 @@
 		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;
 	}
@@ -203,18 +225,18 @@
 	 * 
 	 * @param oldServerInstance for server.xml from previous server publish
 	 * @param modules list of currently added modules
-	 * @return collection of Context paths that are not present in current modules
+	 * @param removedContextsMap Map to receive removed contexts mapped by path
+	 * @param keptContextsMap Map to receive kept contexts mapped by path
 	 */
-	public static Collection getRemovedCatalinaContexts(ServerInstance oldServerInstance, List modules) {
-		// Determine which contexts are going away
-		Set removedContextPaths = new HashSet();
+	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 )	{
-					removedContextPaths.add(contexts[i].getPath());
+					removedContextsMap.put(contexts[i].getPath(), contexts[i]);
 				}
 			}
 		}
@@ -223,27 +245,29 @@
 		int size = modules.size();
 		for (int i = 0; i < size; i++) {
 			WebModule module = (WebModule) modules.get(i);
-			removedContextPaths.remove(module.getPath());
+			Context context = (Context)removedContextsMap.remove(module.getPath());
+			if (context != null)
+				keptContextsMap.put(context.getPath(), context);
 		}
-		return removedContextPaths;
 	}
-
+	
 	/**
 	 * 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.<br>
-	 * <br>
-	 * Note: This method is not used by Tomcat 5.0, because it may create
-	 * Context XML files under &quot;conf/Catalina/localhost&quot; for Contexts
-	 * in server.xml which requires additional cleanup.
+	 * 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, List modules, IProgressMonitor monitor) {
+	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);
@@ -253,43 +277,98 @@
 			IPath serverXml = baseDir.append("conf").append("server.xml");
 			ServerInstance oldInstance = TomcatVersionHelper.getCatalinaServerInstance(serverXml, null, null);
 			if (oldInstance != null) {
-				Collection oldPaths = TomcatVersionHelper.getRemovedCatalinaContexts(oldInstance, modules);
+				Map removedContextsMap = new HashMap();
+				Map keptContextsMap = new HashMap();
+				TomcatVersionHelper.getRemovedKeptCatalinaContexts(oldInstance, modules, removedContextsMap, keptContextsMap);
 				monitor.worked(100);
-				if (oldPaths != null && oldPaths.size() > 0) {
-					// Delete work directories for managed web modules that have gone away
-					if (oldPaths.size() > 0 ) {
-						IProgressMonitor subMonitor = ProgressUtil.getSubMonitorFor(monitor, 100);
-						subMonitor.beginTask(Messages.deletingContextFilesTask, oldPaths.size() * 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);
 						
-						Iterator iter = oldPaths.iterator();
-						while (iter.hasNext()) {
-							String oldPath = (String)iter.next();
-							
-							// 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.
-							Context ctx = oldInstance.getContext(oldPath);
-							IPath ctxWorkPath = oldInstance.getContextWorkDirectory(baseDir, ctx);
-							if (baseDir.isPrefixOf(ctxWorkPath)) {
-								File ctxWorkDir = ctxWorkPath.toFile();
-								if (ctxWorkDir.exists() && ctxWorkDir.isDirectory()) {
-									IStatus [] results = PublishUtil.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]);
-										}
+						// 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 = PublishUtil.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();
+						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 {
@@ -731,4 +810,134 @@
 		}
 		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 monitor a progress monitor
+	 * @return result of operation
+	 */
+	public static IStatus moveContextsToSeparateFiles(IPath baseDir, boolean noPath, 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 Hande 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");
+					
+					// If requested, remove path attribute
+					if (noPath)
+						context.removeAttribute("path");
+					
+					File contextFile = new File(contextDir, name + ".xml");
+					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];
+
+			FileInputStream fis = null;
+			Context context = null;
+			try {
+				fis = new FileInputStream(ctx);
+				context = (Context) factory.loadDocument(fis);
+			} catch (Exception e) {
+				// may be a spurious xml file in the host dir?
+				Trace.trace(Trace.FINER, "Unable to read context "
+						+ ctx.getAbsolutePath());
+			} finally {
+				try {
+					fis.close();
+				} catch (IOException e) {
+					// ignore
+				}
+			}
+			if (context != null) {
+				// TODO Handle non-project contexts when their removal can be addressed
+				String memento = context.getSource();
+				if (memento != null) {
+					String path = context.getPath();
+					// If path attribute is not set, derive from file name
+					if (path == null) {
+						String fileName = ctx.getName();
+						path = fileName.substring(0, fileName.length() - ".xml".length());
+						if ("ROOT".equals(path))
+							path = "";
+						context.setPath(path);
+					}
+					projectContexts.put(ctx, context);
+				}
+			}
+		}
+	}
 }
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/command/SetSaveSeparateContextFilesCommand.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/command/SetSaveSeparateContextFilesCommand.java
new file mode 100644
index 0000000..2c67a05
--- /dev/null
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/command/SetSaveSeparateContextFilesCommand.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2007 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:
+ *     IBM Corporation - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jst.server.tomcat.core.internal.command;
+
+import org.eclipse.jst.server.tomcat.core.internal.ITomcatServerWorkingCopy;
+import org.eclipse.jst.server.tomcat.core.internal.Messages;
+/**
+ * Command to enable or disable serving modules without publishing
+ */
+public class SetSaveSeparateContextFilesCommand extends ServerCommand {
+	protected boolean sscf;
+	protected boolean oldSscf;
+
+	/**
+	 * SetSeparateContextFilesCommand constructor comment.
+	 * 
+	 * @param server a Tomcat server
+	 * @param sscf <code>true</code> to enable saving separate context XML
+	 * files. Otherwise contexts are kept in server.xml when published.
+	 */
+	public SetSaveSeparateContextFilesCommand(ITomcatServerWorkingCopy server, boolean sscf) {
+		super(server, Messages.serverEidtorActionSetSeparateContextFiles);
+		this.sscf = sscf;
+	}
+
+	/**
+	 * Execute the command.
+	 */
+	public void execute() {
+		oldSscf = server.isSaveSeparateContextFiles();
+		server.setSaveSeparateContextFiles(sscf);
+	}
+
+	/**
+	 * Undo the command.
+	 */
+	public void undo() {
+		server.setSaveSeparateContextFiles(oldSscf);
+	}
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/command/SetServeModulesWithoutPublishCommand.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/command/SetServeModulesWithoutPublishCommand.java
index 9eff331..a9e0e21 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/command/SetServeModulesWithoutPublishCommand.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/command/SetServeModulesWithoutPublishCommand.java
@@ -20,7 +20,7 @@
 	protected boolean oldSmwp;
 
 	/**
-	 * SetTestEnvironmentCommand constructor comment.
+	 * SetServeModulesWithoutPublishCommand constructor comment.
 	 * 
 	 * @param server a Tomcat server
 	 * @param smwp <code>true</code> to enable serving modules without
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/XMLElement.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/XMLElement.java
index b14c15f..e576548 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/XMLElement.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/XMLElement.java
@@ -24,6 +24,10 @@
 	public XMLElement() {
 		// do nothing
 	}
+	
+	public Element getElementNode() {
+		return xmlElement;
+	}
 
 	public Attr addAttribute(String s, String s1) {
 		Attr attr = factory.createAttribute(s, xmlElement);
@@ -243,7 +247,7 @@
 		}
 	}
 	
-	void importNode(Node node, boolean deep) {
+	public void importNode(Node node, boolean deep) {
 		xmlElement.appendChild(xmlElement.getOwnerDocument().importNode(node, deep));
 	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/XMLUtil.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/XMLUtil.java
index 76d96f6..5c6d59a 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/XMLUtil.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/XMLUtil.java
@@ -345,6 +345,23 @@
 		}
 	}
 
+	public static void save(String filename, Node node) throws IOException {
+		PrintStream out = null;
+		try {
+			out = new PrintStream(new BufferedOutputStream(new FileOutputStream(filename)), true, "UTF-8");
+			print(out, node);
+		} catch (Exception ex) {
+			throw new IOException(ex.getLocalizedMessage());
+		} finally {
+			if (out != null)
+				try {
+					out.close();
+				} catch (Exception e) {
+					// ignore
+				}
+		}
+	}
+
 	/*
 	 * Set the value of the subnode
 	 *
diff --git a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/server40/ServerInstance.java b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/server40/ServerInstance.java
index efe9a27..4e9a550 100644
--- a/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/server40/ServerInstance.java
+++ b/plugins/org.eclipse.jst.server.tomcat.core/tomcatcore/org/eclipse/jst/server/tomcat/core/internal/xml/server40/ServerInstance.java
@@ -413,6 +413,44 @@
 		return confDir.append(engineName).append(hostName);
 	}
 	
+	
+	/**
+	 * Gets the path for the context XML file that would be used
+	 * if this context were written to a separate files. This
+	 * method will return <b>null</b> if the selected Host is
+	 * not found in the server configuration. This method does
+	 * not verify if the specified Context currently exists
+	 * within the selected Host.
+	 * 
+	 * @param baseDir Path to the base directory for the server.
+	 * @param context Context whose context XML file path to return.
+	 * @return Returns the path to the context XML file for the specifed
+	 * Context. Returns <b>null</b> if the selected Host can not be
+	 * found or the context has no path attribute.
+	 */
+	public IPath getContextFilePath(IPath baseDir, Context context) {
+		if (context == null)
+			throw new IllegalArgumentException(Messages.errorXMLNullContextArg);
+		status = Status.OK_STATUS;
+		if (host == null && getHost() == null)
+			return null;
+
+		IPath contextFilePath = null;
+		IPath contextDir = getContextXmlDirectory(baseDir.append("conf"));
+		String name = context.getPath();
+		if (name != null) {
+			if (name.startsWith("/"))
+				name = name.substring(1);
+			if (name.length() == 0)
+				name = "ROOT";
+			contextFilePath = contextDir.append(name + ".xml");
+		}
+		else {
+			// TODO Set error status
+		}
+		return contextFilePath;
+	}
+	
 	/**
 	 * Gets the work directory associated with the specified 
 	 * Context. If the work directory obtained is relative,
@@ -430,7 +468,7 @@
 	 */
 	public IPath getContextWorkDirectory(IPath basePath, Context context) {
 		if (context == null)
-			throw new IllegalArgumentException("Context argument may not be null.");
+			throw new IllegalArgumentException(Messages.errorXMLNullContextArg);
 		status = Status.OK_STATUS;
 		if (host == null && getHost() == null)
 			return null;
diff --git a/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/Messages.java b/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/Messages.java
index 2c7fd41..e442d7b 100644
--- a/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/Messages.java
+++ b/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/Messages.java
@@ -70,12 +70,13 @@
 	public static String serverEditorDeployDir;
 	public static String serverEditorTestEnvironment;
 	public static String serverEditorNoPublish;
+	public static String serverEditorSeparateContextFiles;
 	public static String serverEditorSecure;
 	public static String serverEditorDebugMode;
 	public static String serverEditorNotSupported;
 	public static String serverEditorDoesNotModify;
 	public static String serverEditorTakesControl;
-	public static String errorNoPublishServerMustBeStopped;
+	public static String errorServerMustBeStopped;
 	public static String errorServerDirIsRoot;
 	public static String errorServerDirUnderRoot;
 	public static String errorServerDirCustomNotMetadata;
diff --git a/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/Messages.properties b/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/Messages.properties
index 4a3a954..b531a07 100644
--- a/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/Messages.properties
+++ b/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/Messages.properties
@@ -86,7 +86,7 @@
 serverEditorSetDefaultDeployDirLink2=Set deploy path to the default value (currently set)
 
 # Fields
-# Note: The argument for the following two strings will be the string for
+# Note: The argument for the following three strings will be the string for
 #       one of serverEditorDoesNotModify or serverEditorTakesControl or and empty string.
 serverEditorServerDirMetadata=Use workspace metadata {0}
 serverEditorServerDirInstall=Use Tomcat installation {0}
@@ -94,10 +94,12 @@
 serverEditorServerDir=Server path:
 serverEditorDeployDir=Deploy path:
 serverEditorSecure=Enable security
-serverEditorDebugMode=Enable Tomcat debug mode (v4.x and above only)
+# Note: The argument for the following string will be the serverEditorNotSupported string or an empty string
+serverEditorDebugMode=Enable Tomcat debug logging {0}
 serverEditorTestEnvironment=Run modules directly from the workspace (do not modify the Tomcat installation)
 # Note: The argument for the following string will be the serverEditorNotSupported string or an empty string
 serverEditorNoPublish=Serve modules without publishing {0}
+serverEditorSeparateContextFiles=Publish module contexts to separate XML files {0}
 errorServerDirIsRoot=The server path may not be set to the the root of your workspace.
 errorServerDirUnderRoot=The server path may not be under the \"{0}\" folder of your workspace unless it is the workspace metadata location.
 # Note: The argument for the following string will be the string for serverEditorServerDirMetadata with a blank string for its argument
@@ -108,7 +110,7 @@
 serverEditorNotSupported=(not supported by this Tomcat version)
 serverEditorDoesNotModify=(does not modify Tomcat installation)
 serverEditorTakesControl=(takes control of Tomcat installation)
-errorNoPublishServerMustBeStopped=The server must be stopped before a change to the \"{0}\" setting can be saved.
+errorServerMustBeStopped=The server must be stopped before a change to the \"{0}\" setting can be saved.
 
 # Browse for Server dialog
 serverEditorBrowseServerMessage=Select a server directory.
diff --git a/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/editor/ServerGeneralEditorSection.java b/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/editor/ServerGeneralEditorSection.java
index cc62e0f..24ae773 100644
--- a/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/editor/ServerGeneralEditorSection.java
+++ b/plugins/org.eclipse.jst.server.tomcat.ui/tomcatui/org/eclipse/jst/server/tomcat/ui/internal/editor/ServerGeneralEditorSection.java
@@ -16,9 +16,11 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jst.server.tomcat.core.internal.ITomcatServer;
+import org.eclipse.jst.server.tomcat.core.internal.ITomcatVersionHandler;
 import org.eclipse.jst.server.tomcat.core.internal.TomcatServer;
 import org.eclipse.jst.server.tomcat.core.internal.command.SetDebugModeCommand;
 import org.eclipse.jst.server.tomcat.core.internal.command.SetSecureCommand;
+import org.eclipse.jst.server.tomcat.core.internal.command.SetSaveSeparateContextFilesCommand;
 import org.eclipse.jst.server.tomcat.core.internal.command.SetServeModulesWithoutPublishCommand;
 import org.eclipse.jst.server.tomcat.ui.internal.ContextIds;
 import org.eclipse.jst.server.tomcat.ui.internal.Messages;
@@ -50,11 +52,13 @@
 	protected Button secure;
 	protected Button debug;
 	protected Button noPublish;
+	protected Button separateContextFiles;
 	protected boolean updating;
 
 	protected PropertyChangeListener listener;
 	
 	protected boolean noPublishChanged;
+	protected boolean separateContextFilesChanged;
 
 	/**
 	 * ServerGeneralEditorPart constructor comment.
@@ -83,6 +87,11 @@
 					ServerGeneralEditorSection.this.noPublish.setSelection(b.booleanValue());
 					// Indicate this setting has changed
 					noPublishChanged = true;
+				} else if (ITomcatServer.PROPERTY_SAVE_SEPARATE_CONTEXT_FILES.equals(event.getPropertyName())) {
+					Boolean b = (Boolean) event.getNewValue();
+					ServerGeneralEditorSection.this.separateContextFiles.setSelection(b.booleanValue());
+					// Indicate this setting has changed
+					separateContextFilesChanged = true;
 				}
 				updating = false;
 			}
@@ -138,6 +147,25 @@
 		// TODO Address help
 //		whs.setHelp(noPublish, ContextIds.SERVER_EDITOR_SECURE);
 
+		// save separate context XML files
+		separateContextFiles = toolkit.createButton(composite, NLS.bind(Messages.serverEditorSeparateContextFiles, ""), SWT.CHECK);
+		data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
+		data.horizontalSpan = 3;
+		separateContextFiles.setLayoutData(data);
+		separateContextFiles.addSelectionListener(new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent se) {
+				if (updating)
+					return;
+				updating = true;
+				execute(new SetSaveSeparateContextFilesCommand(tomcatServer, separateContextFiles.getSelection()));
+				// Indicate this setting has changed
+				separateContextFilesChanged = true;
+				updating = false;
+			}
+		});
+		// TODO Address help
+//		whs.setHelp(separateContextFiles, ContextIds.SERVER_EDITOR_SECURE);
+		
 		// security
 		secure = toolkit.createButton(composite, Messages.serverEditorSecure, SWT.CHECK);
 		data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
@@ -155,7 +183,7 @@
 		whs.setHelp(secure, ContextIds.SERVER_EDITOR_SECURE);
 	
 		// debug mode
-		debug = toolkit.createButton(composite, Messages.serverEditorDebugMode, SWT.CHECK);
+		debug = toolkit.createButton(composite, NLS.bind(Messages.serverEditorDebugMode, ""), SWT.CHECK);
 		data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
 		data.horizontalSpan = 3;
 		debug.setLayoutData(data);
@@ -201,19 +229,35 @@
 		if (secure == null || tomcatServer == null)
 			return;
 		updating = true;
+		ITomcatVersionHandler tvh = tomcatServer.getTomcatVersionHandler();
 		
-		boolean supported = tomcatServer.getTomcatVersionHandler().supportsServeModulesWithoutPublish();
-		String noPublishLabel = NLS.bind(Messages.serverEditorNoPublish,
+		boolean supported = tvh.supportsServeModulesWithoutPublish();
+		String label = NLS.bind(Messages.serverEditorNoPublish,
 				supported ? "" : Messages.serverEditorNotSupported);
-		noPublish.setText(noPublishLabel);
+		noPublish.setText(label);
 		noPublish.setSelection(tomcatServer.isServeModulesWithoutPublish());
 		if (readOnly || !supported)
 			noPublish.setEnabled(false);
 		else
 			noPublish.setEnabled(true);
 
+		supported = tvh.supportsSeparateContextFiles();
+		label = NLS.bind(Messages.serverEditorSeparateContextFiles,
+				supported ? "" : Messages.serverEditorNotSupported);
+		separateContextFiles.setText(label);
+		separateContextFiles.setSelection(tomcatServer.isSaveSeparateContextFiles());
+		if (readOnly || !supported)
+			separateContextFiles.setEnabled(false);
+		else
+			separateContextFiles.setEnabled(true);
+
 		secure.setSelection(tomcatServer.isSecure());
-		if (server.getRuntime() != null && server.getRuntime().getRuntimeType().getId().indexOf("32") >= 0 || readOnly)
+		
+		supported = tvh.supportsDebugArgument();
+		label = NLS.bind(Messages.serverEditorDebugMode,
+				supported ? "" : Messages.serverEditorNotSupported);
+		debug.setText(label);
+		if (readOnly || !supported)
 			debug.setEnabled(false);
 		else {
 			debug.setEnabled(true);
@@ -238,7 +282,7 @@
 			if (tomcatServer.getServer().getServerState() != IServer.STATE_STOPPED) {
 				return new IStatus [] {
 						new Status(IStatus.ERROR, TomcatUIPlugin.PLUGIN_ID,
-								NLS.bind(Messages.errorNoPublishServerMustBeStopped,
+								NLS.bind(Messages.errorServerMustBeStopped,
 										NLS.bind(Messages.serverEditorNoPublish, "").trim()))
 				};
 			}
@@ -247,6 +291,16 @@
 			publishJob.schedule();
 			noPublishChanged = false;
 		}
+		if (separateContextFilesChanged) {
+			// If server is running, abort the save since contexts will be moving
+			if (tomcatServer.getServer().getServerState() != IServer.STATE_STOPPED) {
+				return new IStatus [] {
+						new Status(IStatus.ERROR, TomcatUIPlugin.PLUGIN_ID,
+								NLS.bind(Messages.errorServerMustBeStopped,
+										NLS.bind(Messages.serverEditorSeparateContextFiles, "").trim()))
+				};
+			}
+		}
 		// use default implementation to return success
 		return super.getSaveStatus();
 	}