| /********************************************************************** |
| * Copyright (c) 2007 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 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.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; |
| |
| /** |
| * 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>"; |
| |
| /** |
| * Reads the from the specified InputStream and returns |
| * the result as a String. Each line is terminated by |
| * "\n". 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, 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=\"" + installPath.append("common").append("endorsed").toOSString() + "\""); |
| |
| 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 = 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(); |
| } |
| 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, 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); |
| 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; |
| } |
| } |