You can declare an alias in you context entry tag.
@avalon.context key="urn:avalon:home" type="java.io.File" alias="app.home"
Confirmed this works. Thanks. Here's the patch to JettyWebServer.java....
/* ============================================================================ The Apache Software License, Version 1.1 ============================================================================ Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved. Redistribution and use in source and binary forms, with or without modifica- tion, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by the Apache Software Foundation (http://www.apache.org/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear. 4. The names "Jakarta", "Apache Avalon", "Avalon Framework" and "Apache Software Foundation" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact [EMAIL PROTECTED] 5. Products derived from this software may not be called "Apache", nor may "Apache" appear in their name, without prior written permission of the Apache Software Foundation. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software consists of voluntary contributions made by many individuals on behalf of the Apache Software Foundation. For more information on the Apache Software Foundation, please see <http://www.apache.org/>. */
package org.apache.avalon.merlin.http; import java.io.File; import java.util.HashMap; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.activity.Startable; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.context.Context; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.context.Contextualizable; import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.logger.Logger; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.avalon.merlin.http.WebServer; import org.mortbay.http.HttpContext; import org.mortbay.http.HttpHandler; import org.mortbay.http.HttpListener; import org.mortbay.http.HttpServer; import org.mortbay.http.SocketListener; import org.mortbay.http.SunJsseListener; import org.mortbay.http.ajp.AJP13Listener; import org.mortbay.http.handler.ResourceHandler; import org.mortbay.jetty.servlet.ServletHandler; import org.mortbay.jetty.servlet.SessionManager; import org.mortbay.jetty.servlet.WebApplicationContext; import org.mortbay.util.MultiException; /** * Implements a web server/servlet container service using the Jetty web server. * * The configuration options for this implementation are defined in * the [EMAIL PROTECTED] JettyWebServer#configure configure} method. * * @author Howard Henson * @author <a href="mailto:[EMAIL PROTECTED]">Timothy Bennett</a> * @avalon.component name="avalon-jetty" version="1.3" lifestyle="singleton" * @avalon.service type="org.apache.avalon.merlin.http.WebServer" version="1.2" * @see JettyWebServer#configure * @see org.apache.avalon.merlin.http.WebServer */ public class JettyWebServer extends AbstractLogEnabled implements Contextualizable, Configurable, Initializable, Serviceable, Startable, Disposable, WebServer { /************************************************/ protected Context m_context; protected Configuration m_config; protected Logger m_logger; protected HttpServer m_httpServer; protected ServiceManager m_serviceManager; /************************************************/ //protected File m_appHome = null; protected HashMap m_listeners = new HashMap(); protected String m_basedir = System.getProperty("merlin.home", System.getProperty("user.dir")); /************************************************/ protected static final String CFG_LISTENER = "Listener"; protected static final String CFG_LISTENER_NAME = "name"; protected static final String CFG_LISTENER_PORT = "port"; protected static final String CFG_LISTENER_HOST = "host"; protected static final String CFG_LISTENER_TYPE = "type"; protected static final String CFG_LISTENER_AUTO = "auto-start"; protected static final String CFG_LISTENER_TYPE_SOCKET = "socket"; protected static final String CFG_LISTENER_TYPE_AJP = "ajp"; protected static final String CFG_LISTENER_TYPE_JSSE = "jsse"; protected static final String CFG_LISTENER_TIMEOUT = "timeout"; protected static final String CFG_CONTEXT = "Context"; protected static final String CFG_CONTEXT_PATH = "path"; protected static final String CFG_CONTEXT_NAME = "name"; protected static final String CFG_SERVLET = "Servlet"; protected static final String CFG_SERVLET_NAME = "name"; protected static final String CFG_SERVLET_PATH = "path"; protected static final String CFG_SERVLET_CLASSNAME = "classname"; protected static final String CFG_WEB_CONTEXT = "WebContext"; /** * Contextualizes the component using an Avalon context. Specifically looks * for a home or working directory with the key "app.home" or "urn:avalon:home", * and sets this as the component's base directory. Otherwise, the component's * base directory will default to either the MERLIN_HOME environment setting * or the application's current working directory. * * @param context an Avalon Context object * @throws <code>ContextException</code> if an error occurs while retrieving * the avalon home directory from the context * * @avalon.entry key="urn:avalon:home" type="java.io.File" alias="app.home" */ public void contextualize(Context context) throws ContextException { m_context = context; // cache the component's logger m_logger = getLogger(); if (m_logger.isInfoEnabled()) m_logger.info("Contextualizing..."); // attempt to find out the "home" directory of this component // if we find it, then set the basedir to this directory try { File home = null; // First, we'll try 'app.home'; if that doesn't exist, then 'urn:avalon:home' try { home = (File) context.get("app.home"); } catch (ContextException ce) { if (m_logger.isWarnEnabled()) m_logger.warn("'app.home' key not found in context; trying 'urn:avalon:home'"); try { home = (File) context.get("urn:avalon:home"); } catch (ContextException cex) { if (m_logger.isWarnEnabled()) m_logger.warn("'urn:avalon:home' key not found in context; using default"); } } if (home != null) { m_basedir = home.getAbsolutePath(); } if (m_logger.isDebugEnabled()) m_logger.debug("Setting home directory to: " + m_basedir); } catch (Exception e) { if (m_logger.isWarnEnabled()) m_logger.warn("Error attempting to establish home directory. Using default.", e); } } /** * Servicing of the component by the container during which service dependencies * that are declared can be resolved using the supplied service manager. This * component depends upon the following component services: * <p> * TODO: list dependent services... * <br> * * @param manager the service manager * @throws <code>ServiceException</code> if an error occurs while servicing * this component * * @avalon.dependency type="org.mortbay.jetty.servlet.SessionManager" * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager) */ public void service(ServiceManager manager) throws ServiceException { if (m_logger.isInfoEnabled()) m_logger.info("Servicing..."); m_serviceManager = manager; } /** * Configuration of the component by the container. * <p> * A general configuration takes the form of:<br> * <br><code> * <configuration><br> * <Listener name="my-listener" port="8080" host="localhost" type="socket"/><br> * <Context name="myCtx" path="/mypath/*"><br> * <Servlet name="MyServlet" path="/servlets/MyServlet" classname="package.MyServlet" /><br> * ...<br> * </Context><br> * <WebContext name="myContext" path="/myContext"/><br> * <WebContext name="myWarContext" path="/myWarFile.war"/><br> * </configuration><br> * </code><br> * One or more listeners can be defined to load and start-up with the server. * The port is the port the listener will listen on, and the host * is the IP address the web server will be bound to. It is an optional attribute, * and defaults to 0.0.0.0, which is all IP addresses defined on the machine. * The context name is the directory within the sar * file that the resources for this context can be found. The path is the url path to bind the context to. * <p> * The specific configuration options are:<br> * <ul> * <li><b>Listener</b> - The listener defines the nature of the connections that the web server will * support and provide a service over. The listener supports multiple types connections, via the <code>type</code> * attribute, these include:<br> * <ul> * <li><b>socket</b> - Uses the SocketListener class. This is a standard socket and should work * fine for all JDK versions supported by Jetty. This is the default type and provides a stock HTTP protocol * listener.</li> * <li><b>ajp</b> - Uses the AJP13Listener. This provides a standard implementation of the AJP13 protocol. This is useful * for a service that will be accessed from an external web server, for example an external apache web server.</li> * <li><b>jsse</b> - Uses the SunJsseListener. This provides a Sun JSSE SSL protocol listener.</li> * </ul> * The listener can also have the port on which it will listen to defined via the <code>port</code> attribute, by default the * HTTP ports are set to 8080, SSL set to 8433, and the AJP port is set to 2345. * </li> * <br> * <li><b>Context</b> - This tag represents the context you wish to bind. The <code>path</code> represents the name * of the context (should be the path on the URL) and the <code>name</code> attribute is the relative directory within * the application that the files (non-servlet, e.g. html, etc.) can be found. The context also contains servlet definitions, * these definitions are as follows:<br> * <ul> * <li><b>Servlet</b> - The servlet definition describes the servlets that are to be made accessable. The attributes are: * <ul> * <li>name - The name of the serlvet</li> * <li>path - The path to access the servlet, this is what the url will look like</li> * <li>classname - The name of the servlet class.</li> * </ul> * There can be many serlvet definitions within a context. * </li> * </ul> * There can be many context definitions within a configuration. * </li> * </ul> * </p> * <p>The new tag is the <code><b>WebContext</b></code> which will treat the contents of the directory within the * application (descibed by the <b><i>path</i></b> attribute) as a web application and bind it to the context path * labled with the <b><i>name attribute</i></b>. * You don't need to provide any of the old Context tags and can use the WebContext to load the application. This * relies on the file WEB-INF/web.xml to configure the context. The file is be within the directory provided for the * application, or if this is a war file the file is extracted and used as the directory. * </p> * * @param config the configuration object * @throws <code>ConfigurationException</code> if an error occurs while configuring * this component * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration) */ public void configure(Configuration config) throws ConfigurationException { if (m_logger.isInfoEnabled()) m_logger.info("Configuring..."); m_config = config; } /** * Initialization of the component by the container. * <p> * Initialises the Jetty web server. It is at this time that the configuration information * is actually used. Any defined listeners, contexts, and web applications * are loaded at this time. * * @throws <code>Exception</code> if an error occurs during initialization of * this component * @see org.apache.avalon.framework.activity.Initializable#initialize() */ public void initialize() throws Exception { if (m_logger.isInfoEnabled()) m_logger.info("Initializing..."); m_httpServer = new HttpServer(); loadListeners(); loadContexts(); loadWebApplications(); } /** * Component is started by the container. * <p> * Starts the Jetty web server. * * @throws <code>Exception</code> if an error occurs during startup of * this component * @see org.apache.avalon.framework.activity.Startable#start() */ public void start() throws Exception { if (m_logger.isInfoEnabled()) m_logger.info("Starting..."); m_httpServer.start(); } /** * Component is stopped by the container. * <p> * Stops the Jetty web server. * * @throws <code>Exception</code> if an error occurs during shutdown of * this component * @see org.apache.avalon.framework.activity.Startable#stop() */ public void stop() throws Exception { if (m_logger.isInfoEnabled()) m_logger.info("Shutting down..."); m_httpServer.stop(true); } /** * Component disposal by the container. * <p> * Cleans up resources allocated by the component. * * @see org.apache.avalon.framework.activity.Disposable#dispose() */ public void dispose() { m_context = null; m_serviceManager = null; m_httpServer = null; m_listeners = null; } /** * @see org.apache.avalon.merlin.http.WebServer#deployWebApplication(java.lang.String, java.lang.String) */ public void deployWebApplication(String context, String appPath) { loadWebApplicationContext(context, appPath); } /** * @see org.apache.avalon.merlin.http.WebServer#removeWebApplication(java.lang.String) */ public boolean removeWebApplication(String context) { try { m_httpServer.stop(); HttpContext ctx = m_httpServer.getContext(context); if (ctx != null) { m_httpServer.removeContext(ctx); } else { m_logger.error("Could not find application to remove: " + context); return false; } m_httpServer.start(); } catch (InterruptedException e) { m_logger.error("Unable to remove web application due to: " + e.getMessage()); return false; } catch (MultiException e) { m_logger.error("Unable to re-start web application due to: " + e.getMessage()); return false; } return true; } /** * Allows dynamic starting of a Jetty listener. * * @param name the alias of the listener that has been defined in configuration file */ public void startListener(String name) throws Exception { boolean found = false; boolean started = false; m_logger.debug("Attempting to start listener " + name); if (m_listeners.containsKey(name)) { m_logger.debug("Retrieving listener " + name + " from lookup table..."); HttpListener listener = (HttpListener) m_listeners.get(name); m_logger.debug("Listener " + name + " retrieved"); // listener must be stopped before we can start it if (listener == null) { m_logger.debug("Listener " + name + " has not yet been created/started"); // get the configuration for this listener... Configuration[] listeners = m_config.getChildren(CFG_LISTENER); m_logger.debug("Searching for listener " + name + "'s configuration..."); for (int i = 0; i < listeners.length; i++) { Configuration conf = listeners[i]; String alias = conf.getAttribute(CFG_LISTENER_NAME); if (alias.equals(name)) { found = true; m_logger.debug("Found configuration for listener " + name); m_logger.debug("Loading listener " + name + "..."); listener = loadListener(conf); if (listener != null) { m_logger.debug("Listener " + name + " loaded"); listener.setHttpServer(m_httpServer); m_logger.debug("Registered the listener with Jetty..."); if (!listener.isStarted()) { m_logger.debug("Starting listener " + name); listener.start(); m_logger.debug("Listener " + name + " started"); started = true; m_listeners.put(name, listener); m_logger.debug("Listener " + name + " stored in lookup table"); } } break; } } // did we find the listener in our lookup table? if (!found) { String errmsg = "Listener name [" + name + "] cannot be started because configuration not found"; m_logger.warn(errmsg); throw new Exception(errmsg); } else if (!started) { String errmsg = "Listener name [" + name + "] cannot be started because of unknown listener name"; m_logger.warn(errmsg); throw new Exception(errmsg); } } else { String errmsg = "Listener name [" + name + "] cannot be started because it is already running"; m_logger.warn(errmsg); throw new Exception(errmsg); } } else { String errmsg = "Listener name [" + name + "] cannot be started because of unknown listener name"; m_logger.warn(errmsg); throw new Exception(errmsg); } } /** * Allows the dynamic stopping of a Jetty listener. * * @param name the alias of the listener that has been defined in configuration file */ public void stopListener(String name) throws Exception { if (m_listeners.containsKey(name)) { HttpListener listener = (HttpListener) m_listeners.get(name); // listener must be started before we can stop it if (listener != null) { if (listener.isStarted()) { listener.stop(); //httpServer.removeListener(listener); m_listeners.put(name, null); } } } } /** * Load a individual listener defined in the component configuration. This * method may be overloaded in a subclass in order to load a custom Jetty * listener. Subclasses should still invoke <code>super()</code> from the * overloaded method so that standard Jetty listeners will continue to be * loaded. */ protected HttpListener loadListener(Configuration conf) throws Exception { HttpListener listener = null; String listenerType = conf.getAttribute(CFG_LISTENER_TYPE, CFG_LISTENER_TYPE_SOCKET); if (listenerType.equals(CFG_LISTENER_TYPE_AJP)) { listener = createAJP13Listener(conf); /* Need to set the correct default value here */ listener.setPort(conf.getAttributeAsInteger(CFG_LISTENER_PORT, 2345)); listener.setHost(conf.getAttribute(CFG_LISTENER_HOST, "0.0.0.0")); } else if (listenerType.equals(CFG_LISTENER_TYPE_JSSE)) { listener = createSunJsseListener(conf); listener.setPort(conf.getAttributeAsInteger(CFG_LISTENER_PORT, 8443)); listener.setHost(conf.getAttribute(CFG_LISTENER_HOST, "0.0.0.0")); } else { listener = createSocketListener(conf); listener.setPort(conf.getAttributeAsInteger(CFG_LISTENER_PORT, 8080)); listener.setHost(conf.getAttribute(CFG_LISTENER_HOST, "0.0.0.0")); } m_httpServer.addListener(listener); m_logger.info("Loaded listener: " + listener.getClass().getName() + " on " + listener.getHost() + ":" + listener.getPort()); return listener; } /** * Loads all the listeners defined in the component configuration. This method * invokes the <code>loadListener</code> method, which may be overloaded in * a subclass in order to load custom Jetty listeners. */ private void loadListeners() throws Exception { Configuration[] listeners = m_config.getChildren(CFG_LISTENER); for (int i = 0; i < listeners.length; i++) { Configuration conf = listeners[i]; // Get the listener's alias String name = conf.getAttribute(CFG_LISTENER_NAME, null); // Add a NULL listener to our lookup table. if (name != null) m_listeners.put(name, null); // Do we auto-start this listener? defaults to true boolean isAutoStart = conf.getAttributeAsBoolean(CFG_LISTENER_AUTO, true); if (isAutoStart) { try { // load/start the listener HttpListener listener = loadListener(conf); // indicate the listener has been started if (name != null) m_listeners.put(name, listener); } catch (Exception e) { m_logger.error("Could not create listner", e); } } else { m_logger.error("Listener " + name + " will not be started"); } } } /** * Loads all of the contexts defined in the component configuration */ private void loadContexts() { Configuration[] contexts = m_config.getChildren(CFG_CONTEXT); for (int i = 0; i < contexts.length; i++) { try { Configuration conf = contexts[i]; HttpContext context = new HttpContext(); context.setContextPath(conf.getAttribute(CFG_CONTEXT_PATH, "/*")); m_httpServer.addContext(context); ServletHandler servletHandler = setSessionManager(context); Configuration[] servlets = conf.getChildren(CFG_SERVLET); for (int j = 0; j < servlets.length; j++) { Configuration servletCfg = servlets[j]; servletHandler.addServlet( servletCfg.getAttribute(CFG_SERVLET_NAME, servletCfg.getAttribute(CFG_SERVLET_CLASSNAME)), servletCfg.getAttribute(CFG_SERVLET_PATH, "/servlets/*"), servletCfg.getAttribute(CFG_SERVLET_CLASSNAME) ); } context.setResourceBase(m_basedir + "/" + conf.getAttribute(CFG_CONTEXT_NAME)); context.addHandler(new ResourceHandler()); } catch (Exception e) { m_logger.error("Could not construct context", e); } } } /** * Loads all the web application defined in the component configuration. */ private void loadWebApplications() { Configuration[] confArr = m_config.getChildren(CFG_WEB_CONTEXT); for (int c = 0; c < confArr.length; c++) { Configuration conf = confArr[c]; loadWebApplicationContext( conf.getAttribute(CFG_CONTEXT_NAME, ""), new File(m_basedir, conf.getAttribute(CFG_CONTEXT_PATH, "")).toString() ); } } /** * Adds the Avalon SessionManager to the servlet handler. If no servlet handler * exists within the context, then a servlet manager is added to the context. * * @param the HttpContext * @return the created ServletHandler * @throws ServiceException */ private ServletHandler setSessionManager(HttpContext context) throws ServiceException { HttpHandler[] handlers = context.getHandlers(); ServletHandler servletHandler = null; if (handlers == null || handlers.length == 0) { servletHandler = new ServletHandler(); context.addHandler(servletHandler); } else { for (int c = 0; c < handlers.length; c++) { if (handlers[c] instanceof ServletHandler) { servletHandler = (ServletHandler) handlers[c]; c = handlers.length; } } if (servletHandler == null) { servletHandler = new ServletHandler(); context.addHandler(servletHandler); } } // switched from hhtp.SessionManager to jetty variant // due to class cast exception - some rethingking needed here String clazzName = SessionManager.class.getName(); SessionManager sessionManager = (SessionManager) m_serviceManager.lookup(clazzName) ; servletHandler.setSessionManager( sessionManager ); return servletHandler; } /** * Loads a web application from the appURL, the application must use * the correct directory structure for a web application, i.e WEB-INF/web.xml * must exist within the appURL. * * @param contextName The name to bind for the context (should not contain a leading / or trailing /*. * @param appURL The directory path to load as an application. */ private void loadWebApplicationContext(String contextName, String appURL) { WebApplicationContext wac; try { wac = new WebApplicationContext(appURL); // Check to see if this is a WAR file, if so extract the WAR. File app = new File(appURL); if (app.isFile()) { String filename = app.getName(); int index = filename.lastIndexOf('.'); if (index != -1) { String ext = filename.substring(index + 1); if (ext.equalsIgnoreCase("war")) { wac.setExtractWAR(true); } } } wac.setContextPath("/" + contextName + "/*"); String clazzName = org.apache.avalon.merlin.http.SessionManager.class.getName(); wac.getServletHandler().setSessionManager((SessionManager) m_serviceManager.lookup(clazzName)); } catch (Exception e) { m_logger.error("Failed to load context: " + contextName); return; } m_httpServer.addContext(wac); } /** * Creates a new AJP13 Listener */ private HttpListener createAJP13Listener(Configuration config) { AJP13Listener listener = new AJP13Listener(); listener.setMaxIdleTimeMs(config.getAttributeAsInteger(CFG_LISTENER_TIMEOUT, 60000)); return listener; } /** * Creates a new Socket Listener */ private HttpListener createSocketListener(Configuration config) { SocketListener listener = new SocketListener(); listener.setMaxIdleTimeMs(config.getAttributeAsInteger(CFG_LISTENER_TIMEOUT, 60000)); return listener; } /** * Creates a default sun jsse listener. * Contributed by Timothy Bennett [EMAIL PROTECTED] * @param configuration * @return The newly created sun jsse listener * @throws Exception */ private HttpListener createSunJsseListener(Configuration configuration) throws Exception { SunJsseListener listener = new SunJsseListener(); listener.setMaxIdleTimeMs(configuration.getAttributeAsInteger(CFG_LISTENER_TIMEOUT, 60000)); Configuration ksconfig = configuration.getChild("keystore"); String fileName = ksconfig.getChild("file").getValue("conf/.keystore"); File configuredFile = new File(fileName); if (!configuredFile.isAbsolute()) { listener.setKeystore(new File(m_basedir, fileName).getAbsolutePath()); } else { listener.setKeystore(configuredFile.getAbsolutePath()); } listener.setPassword(ksconfig.getChild("password").getValue(null)); listener.setKeyPassword(ksconfig.getChild("key-password").getValue(null)); return listener; } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]