FileDocCategorySizeDatePackage
VirtualServer.javaAPI DocGlassfish v2 API59241Wed Jul 25 17:58:04 BST 2007com.sun.enterprise.web

VirtualServer.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.web;

import com.sun.enterprise.web.stats.PWCRequestStatsImpl;
import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.catalina.Container;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.Context;
import org.apache.catalina.InstanceListener;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.deploy.ErrorPage;
import org.apache.catalina.valves.RemoteAddrValve;
import org.apache.catalina.valves.RemoteHostValve;

import com.sun.enterprise.config.ConfigBean;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.serverbeans.Applications;
import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.config.serverbeans.J2eeApplication;
import com.sun.enterprise.config.serverbeans.HttpProtocol;
import com.sun.enterprise.config.serverbeans.HttpService;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.config.serverbeans.ApplicationRef;
import com.sun.enterprise.config.serverbeans.ElementProperty;
//import com.sun.enterprise.config.serverbeans.VirtualServerClass;
import com.sun.enterprise.config.serverbeans.WebModule;
import com.sun.enterprise.config.serverbeans.SecurityService;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.instance.InstanceEnvironment;
import com.sun.enterprise.instance.WebModulesManager;
import com.sun.enterprise.security.web.SingleSignOn;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.server.ApplicationRegistry;
import com.sun.enterprise.server.pluggable.WebContainerFeatureFactory;
import com.sun.enterprise.util.StringUtils;
import com.sun.enterprise.util.io.FileUtils;

import com.sun.enterprise.Switch;
import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.WebBundleDescriptor;
import com.sun.enterprise.deployment.util.DOLLoadingContextFactory;
import com.sun.enterprise.deployment.backend.DeploymentUtils;
import com.sun.logging.LogDomains;
import com.sun.enterprise.config.serverbeans.ElementProperty;

/**
 * Standard implementation of a virtual server (aka virtual host) in
 * the iPlanet Application Server.
 */

public class VirtualServer extends StandardHost {

    public static final String ADMIN_VS = "__asadmin";

    static final String STATE = "state";
    static final String SSO_MAX_IDLE="sso-max-inactive-seconds";
    static final String SSO_REAP_INTERVAL="sso-reap-interval-seconds";
    static final String DISABLED = "disabled";
    static final String OFF = "off";
    static final String ON = "on";

    // ------------------------------------------------------------ Constructor

    /**
     * Default constructor that simply gets a handle to the web container 
     * subsystem's logger.
     */
    public VirtualServer() {

        super();

        origPipeline = pipeline;
        vsPipeline = new VirtualServerPipeline(this);
        accessLogValve = new PEAccessLogValve();

        _debug = _logger.isLoggable(Level.FINE);
    }

    // ----------------------------------------------------- Instance Variables

    /*
     * The custom pipeline of this VirtualServer, which implements the 
     * following virtual server features:
     *
     * - state (disabled/off)
     * - redirects
     */
    private VirtualServerPipeline vsPipeline;

    /*
     * The original (standard) pipeline of this VirtualServer.
     *
     * Only one (custom or original) pipeline may be active at any given time.
     * Any updates (such as adding or removing valves) to the currently
     * active pipeline are propagated to the other.
     */
    private Pipeline origPipeline;

    /**
     * The id of this virtual server as specified in the configuration.
     */
    private String _id = null;

    /**
     * The logger to use for logging ALL web container related messages.
     */
    protected static final Logger _logger
        = LogDomains.getLogger(LogDomains.WEB_LOGGER);

    /**
     * The resource bundle containing the message strings for _logger.
     */
    protected static final ResourceBundle _rb = _logger.getResourceBundle();

    /**
     * Indicates whether the logger level is set to any one of 
     * FINE/FINER/FINEST.
     *
     * This flag is used to avoid incurring a perf penalty by making
     * logging calls for debug messages when the logger level is
     * INFO or higher.
     */
    protected boolean _debug = false;

    /**
     * The descriptive information about this implementation.
     */
    private static final String _info =
        "com.sun.enterprise.web.VirtualServer/1.0";

    /**
     * The config bean associated with this VirtualServer
     */
    private com.sun.enterprise.config.serverbeans.VirtualServer vsBean;

    /**
     * The mime mapping associated with this VirtualServer
     */
    private MimeMap mimeMap;

    /*
     * Indicates whether symbolic links from this virtual server's docroot
     * are followed. This setting is inherited by all web modules deployed on
     * this virtual server, unless overridden by a web modules allowLinking
     * property in sun-web.xml.
     */
    private boolean allowLinking = false;


     /*
     * default context.xml location
     */
    private String defaultContextXmlLocation;
    

    /*
     * default-web.xml location 
     */
    private String defaultWebXmlLocation;


    private String[] cacheControls;


    // Is this virtual server active?
    private boolean isActive;

    
    /**
     * The Stats holder used by this virtual-server.
     */
    private PWCRequestStatsImpl pwcRequestStatsImpl;


    private String authRealmName;


    /*
     * The accesslog valve of this VirtualServer.
     *
     * This valve is activated, that is, added to this virtual server's
     * pipeline, only when access logging has been enabled. When acess logging
     * has been disabled, this valve is removed from this virtual server's
     * pipeline.
     */
    private PEAccessLogValve accessLogValve;


    // ------------------------------------------------------------- Properties

    /**
     * Return the virtual server identifier.
     */
    public String getID() {
        return _id;
    }

    /**
     * Set the virtual server identifier string.
     *
     * @param id New identifier for this virtual server
     */
    public void setID(String id) {
        _id = id;
    }

    /**
     * @return true if this virtual server is active, false otherwise
     */
    public boolean isActive() {
        return isActive;
    }

    /**
     * Sets the state of this virtual server.
     *
     * @param isActive true if this virtual server is active, false otherwise
     */
    public void setIsActive(boolean isActive) {
        this.isActive = isActive;
        if (isActive) {
            vsPipeline.setIsDisabled(false);
            vsPipeline.setIsOff(false);
            if (pipeline == vsPipeline && !vsPipeline.hasRedirects()) {
                // Restore original pipeline
                setPipeline(origPipeline);
            }
        }
    }

    /**
     * Gets the default-context.xml location of web modules deployed on this
     * virtual server.
     *
     * @return default-context.xml location of web modules deployed on this
     * virtual server
     */
    public String getDefaultContextXmlLocation() {
        return defaultContextXmlLocation;
    }

    /**
     * Sets the default-context.xml location for web modules deployed on this
     * virtual server.
     *
     * @param defaultWebXmlLocation default-context.xml location for web modules
     * deployed on this virtual server
     */
    public void setDefaultContextXmlLocation(String defaultContextXmlLocation) {
        this.defaultContextXmlLocation = defaultContextXmlLocation;
    }

    /**
     * Gets the default-web.xml location of web modules deployed on this
     * virtual server.
     *
     * @return default-web.xml location of web modules deployed on this
     * virtual server
     */
    public String getDefaultWebXmlLocation() {
        return defaultWebXmlLocation;
    }

    /**
     * Sets the default-web.xml location for web modules deployed on this
     * virtual server.
     *
     * @param defaultWebXmlLocation default-web.xml location for web modules
     * deployed on this virtual server
     */
    public void setDefaultWebXmlLocation(String defaultWebXmlLocation) {
        this.defaultWebXmlLocation = defaultWebXmlLocation;
    }

    /**
     * Gets the value of the allowLinking property of this virtual server.
     *
     * @return true if symbolic links from this virtual server's docroot (as
     * well as symbolic links from archives of web modules deployed on this
     * virtual server) are followed, false otherwise
     */
    public boolean getAllowLinking() {
        return allowLinking;
    }

    /**
     * Sets the allowLinking property of this virtual server, which determines
     * whether symblic links from this virtual server's docroot are followed.
     *
     * This property is inherited by all web modules deployed on this virtual
     * server, unless overridden by the allowLinking property in a web module's
     * sun-web.xml.
     *
     * @param allowLinking Value of allowLinking property
     */
    public void setAllowLinking(boolean allowLinking) {
        this.allowLinking = allowLinking;
    }

    /**
     * Gets the config bean associated with this VirtualServer.
     */
    public com.sun.enterprise.config.serverbeans.VirtualServer getBean(){
        return vsBean;
    }

    /**
     * Sets the config bean for this VirtualServer
     */
     public void setBean(com.sun.enterprise.config.serverbeans.VirtualServer vsBean){
        this.vsBean = vsBean;
     }

    /**
     * Gets the mime map associated with this VirtualServer.
     */
    public MimeMap getMimeMap(){
        return mimeMap;
    }

    /**
     * Sets the mime map for this VirtualServer
     */
    public void setMimeMap(MimeMap mimeMap){
        this.mimeMap = mimeMap;
    }

    /**
     * Gets the Cache-Control configuration of this VirtualServer.
     *
     * @return Cache-Control configuration of this VirtualServer, or null if
     * no such configuration exists for this VirtualServer
     */
    public String[] getCacheControls() {
        return cacheControls;
    }

    /**
     * Sets the Cache-Control configuration for this VirtualServer
     *
     * @param cacheControls Cache-Control configuration settings for this
     * VirtualServer
     */
    public void setCacheControls(String[] cacheControls) {
        this.cacheControls = cacheControls;
    }


    // --------------------------------------------------------- Public Methods

    /**
     * Return descriptive information about this Container implementation and
     * the corresponding version number, in the format
     * <code><description>/<version></code>.
     */
    public String getInfo() {
        return _info;
    }


    // ------------------------------------------------------ Lifecycle Methods

    /**
     * Gracefully shut down active use of the public methods of this Component.
     *
     * @exception IllegalStateException if this component has not been started
     * @exception LifecycleException if this component detects a fatal error
     *  that needs to be reported
     */
    public synchronized void stop() throws LifecycleException {

        super.stop();

        // Remove the descriptor bindings for all the web applications
        // in this virtual server
        Switch sw = Switch.getSwitch();
        Container children[] = findChildren();
        if (children != null) {
            for (int i = 0; i < children.length; i++) {
                sw.removeDescriptorFor(children[i]);
            }
        }
    }


    /**
     * Adds the given valve to the currently active pipeline, keeping the
     * valve that is not currently active in sync.
     */
    public synchronized void addValve(Valve valve) {
        super.addValve(valve);
        if (pipeline == vsPipeline) {
            origPipeline.addValve(valve);
        } else {
            vsPipeline.addValve(valve);
        }
    }


    /**
     * Removes the given valve from the currently active pipeline, keeping the
     * valve that is not currently active in sync.
     */
    public synchronized void removeValve(Valve valve) {
        super.removeValve(valve);
        if (pipeline == vsPipeline) {
            origPipeline.removeValve(valve);
        } else {
            vsPipeline.removeValve(valve);
        }
    }


    // ------------------------------------------------------ Protected Methods

    /**
     * Return the list of enabled web-modules configured for this
     * virtual server.
     *
     * @return     The list of WebModuleConfig objects for all enabled
     *             web-modules hosted under the specified virtual server.
     */
    protected List getWebModules(Server serverBean, String modulesRoot) {

        List modules = new Vector();
        
        //ROB: config changes
        //Applications appsBean = serverBean.getApplications();
        Applications appsBean = null;
        try {
            appsBean = ServerBeansFactory.getApplicationsBean(serverBean.getConfigContext());
        } catch (ConfigException e) {
            String msg = _rb.getString("vs.appsConfigError");
            Object[] params = { getID() };
            msg = MessageFormat.format(msg, params);
            _logger.log(Level.SEVERE, msg, e);
        }

        if (appsBean != null) {
            WebModule[] wmBeans = appsBean.getWebModule();
            if (wmBeans != null && wmBeans.length > 0) {
                for (int i = 0; i < wmBeans.length; i++) {
                    WebModule wm = wmBeans[i];
                    if (isActive(wm)) {                      
                        // skips if the web module is not referenced by 
                        // this server
                        ApplicationRef ref = 
                            serverBean.getApplicationRefByRef(wm.getName());
                        if (ref == null) {
                            continue;
                        }                       

                        String location = wm.getLocation();
                        // If module root is relative then prefix it with the 
                        // location of where all the standalone modules for 
                        // this server instance are deployed
                        File moduleBase = new File(location);
                        if (!moduleBase.isAbsolute()) {
                            location = modulesRoot+File.separator+location;
                            wm.setLocation(location);
                        }
                        WebModuleConfig wmInfo = loadWebModuleConfig(wm);
                        if (wmInfo != null)
                            modules.add(wmInfo);
                    } else {
                        if (_debug) {
                            _logger.finer("Web Module [" + wm.getName() + 
                                          "] is not applicable for virtual " +
                                          " server [" + getID() + "]");
                        }
                    }
                }
            }
        }
        return modules;
    }

    /**
     * Gets the context root of the web module that the user/configuration
     * has designated as the default-web-module for this virtual server.
     *
     * The default-web-module for a virtual server is specified via the
     * 'default-web-module' attribute of the 'virtual-server' element in
     * server.xml. This is an optional attribute and if the configuration
     * does not specify another web module (standalone or part of a 
     * j2ee-application) that is configured at a context-root="", then
     * a default web module will be created and loaded. The value for this
     * attribute is either "${standalone-web-module-name}" or 
     * "${j2ee-app-name}:${web-module-uri}".
     *
     * @return null if the default-web-module has not been specified or
     *              if the web module specified either could not be found or
     *              is disabled or does not specify this virtual server (if
     *              it specifies a value for the virtual-servers attribute) or
     *              if there was an error loading its deployment descriptors.
     */
    protected String getDefaultContextPath(Server serverBean) {

        String contextRoot = null;
        Applications appsBean = null;

        try{
            appsBean = ServerBeansFactory.getApplicationsBean(
                serverBean.getConfigContext());
        } catch (ConfigException e) {
            String msg = _rb.getString("vs.appsConfigError");
            Object[] params = { getID() };
            msg = MessageFormat.format(msg, params);
            _logger.log(Level.SEVERE, msg, e);
        }

        String wmID = getDefaultWebModuleID();
        if (wmID != null) {
            // Check if the default-web-module is part of a
            // j2ee-application
            WebModuleConfig wmInfo = findWebModuleInJ2eeApp(appsBean, wmID);

            // Look up the list of standalone web modules
            if (wmInfo == null) {
                WebModule wm = appsBean.getWebModuleByName(wmID);
                if (wm != null) {
                    if (isActive(wm, false)) {
                        // Create a copy as we need to change the name
                        // and context root to indicate that this web module
                        // is to be loaded as the 'default' web module for
                        // the virtual server
                        // WebModule wmCopy = (WebModule) wm.clone();
                        contextRoot = wm.getContextRoot();
                    } else {
                        Object[] params = { wmID, getID() };
                        _logger.log(Level.SEVERE, "vs.defaultWebModuleDisabled",
                                    params);
                    }
                }
            } else {
                WebModule wm = wmInfo.getBean();
                contextRoot = wm.getContextRoot();
            }

            if (contextRoot == null) {
                Object[] params = { wmID, getID() };
                _logger.log(Level.SEVERE, "vs.defaultWebModuleNotFound",
                            params);
            }
        }

        return contextRoot;
    }

    /**
     * If a default web module has not yet been configured and added to this
     * virtual server's list of web modules then return the configuration
     * information needed in order to create a default web module for this
     * virtual server.
     *
     * This method should be invoked only after all the standalone modules
     * and the modules within j2ee-application elements have been added to
     * this virtual server's list of modules (only then will one know whether
     * the user has already configured a default web module or not).
     */
    protected WebModuleConfig createSystemDefaultWebModuleIfNecessary() {

        WebModuleConfig wmInfo = null;
        //
        // Add a default context only if one hasn't already been loaded
        // and then too only if docroot is not null
        //
        String docroot = getAppBase();
        if (getDefaultWebModuleID() == null && (findChild("") == null)
                && (docroot != null)) {
            wmInfo = new WebModuleConfig();
            WebModule wm = new WebModule();
            wm.setName(Constants.DEFAULT_WEB_MODULE_NAME);
            wm.setContextRoot("");
            wm.setLocation(docroot);
            wmInfo.setBean(wm);
            
            wmInfo.setDescriptor(DOLLoadingContextFactory.getDefaultWebBundleDescriptor());
 
            WebBundleDescriptor wbd = wmInfo.getDescriptor();
            if ( wbd.getApplication() == null ) {
                Application application = new Application();
                application.setVirtual(true);
                application.setName(Constants.DEFAULT_WEB_MODULE_NAME);
                wbd.setApplication(application);
            }
        }

        return wmInfo;
    }


    /**
     * Creates and returns an object that contains information about
     * the web module's configuration such as the information specified
     * in server.xml, the deployment descriptor objects etc.
     *
     * @return null if an error occured while reading/parsing the 
     *              deployment descriptors.
     */
    protected WebModuleConfig loadWebModuleConfig(WebModule wm) {

        WebModuleConfig wmInfo = new WebModuleConfig();
        wmInfo.setBean(wm);
        String wmID = wm.getName();
        String location = wm.getLocation();
        try {
            WebModulesManager webModulesManager = new WebModulesManager(
                ApplicationServer.getServerContext().getInstanceEnvironment());
	    Application app = webModulesManager.getDescriptor(wmID, location);	    
            WebBundleDescriptor wbd = (WebBundleDescriptor) app.getStandaloneBundleDescriptor();
            wmInfo.setDescriptor(wbd);
        } catch (ConfigException ce) {
            wmInfo = null;
            String msg = _rb.getString("vs.moduleConfigError");
            Object[] params = { wmID, getID() };
            msg = MessageFormat.format(msg, params);
            _logger.log(Level.SEVERE, msg, ce);
        }
        return wmInfo;
    }

    /**
     * Determines whether the specified web module is "active" under this
     * virtual server.
     */
    private boolean isActive(WebModule wm) {
        return isActive(wm, true);
    }

    /**
     * Determines whether the specified web module is "active" under this
     * virtual server.
     *
     * A web module is active if it meets ALL of the following conditions:
     *   - wm is not null
     *   - the enabled attribute of the web-module element is true
     * If matchVSID is true then the following additional condition must be
     * satisfied.
     *   - the virtual-servers attribute of the web-module element is either
     *     empty/not-specified or if specified then this virtual server's
     *     name/ID is one of the virtual servers specified in the list
     *
     * @param wm        The bean containing the web module configuration
     *                  information as specified in server.xml
     * @param matchVSID This is set to false when testing to see if a
     *                  web module that has been configured to be the
     *                  default-web-module for a VS is active or not. In this
     *                  case the only test is to check if the web module
     *                  is enabled or not. When set to true, the virtual
     *                  server list is examined to ensure that the web
     *                  module has been configured to run on this virtual
     *                  server.
     * @return     <code>true</code> if all the criteria are satisfied and
     *             <code>false</code> otherwise.
     */
    protected boolean isActive(WebModule wm, boolean matchVSID) {

        String vsID = getID();

        boolean active = ((vsID != null) && (vsID.length() > 0));
        active &= (wm != null);

        if (active) {
            // Check if the web module is enabled
            active &= wm.isEnabled();

            //
            // Check if vsID is one of the virtual servers specified
            // in the list of virtual servers that the web module is to
            // be loaded on. If the virtual-servers attribute of the
            // <web-module> element is missing or empty then the implied
            // behaviour is that the web module is active on every virtual
            // server.
            //
            String vsIDs = getVirtualServers(wm.getName());

            //
            // fix for bug# 4913636
            // so that for PE if the vsList is null and the virtual server is
            // admin-vs then return false because we don't want to load user apps
            // on admin-vs
            //
            if (getID().equals(ADMIN_VS) && matchVSID
                    && ((vsIDs == null) || (vsIDs.length() == 0 ))) {
                return false;
            } 


            if (matchVSID && (vsIDs != null) && (vsIDs.length() > 0)) {
                List vsList = StringUtils.parseStringList(vsIDs, " ,");
                if (vsList != null)
                    active &= vsList.contains(vsID.trim());
                else
                    active &= true;
            } else
                active &= true;
        }
        return active;
    }

    /**
     * Returns the id of the default web module for this virtual server
     * as specified in the 'default-web-module' attribute of the 
     * 'virtual-server' element.
     *
     * This is an optional attribute.
     */
    protected String getDefaultWebModuleID() {
        String wmID = null;
        
        if (vsBean != null) {
            wmID = vsBean.getDefaultWebModule();
            if (wmID != null && _debug) {
                Object[] params = { wmID, _id };
                _logger.log(Level.FINE, "vs.defaultWebModule", params);
            }
        } else {
            _logger.log(Level.SEVERE, "vs.configError", _id);
        }

        return wmID;
    }

    /**
     * Finds and returns information about a web module embedded within a
     * J2EE application, which is identified by a string of the form
     * <code>a:b</code> or <code>a#b</code>, where <code>a</code> is the name
     * of the J2EE application and <code>b</code> is the name of the embedded
     * web module.
     *
     * @return null if <code>id</code> does not identify a web module embedded
     * within a J2EE application.
     */
    protected WebModuleConfig findWebModuleInJ2eeApp(Applications appsBean,
                                                   String id) {
        WebModuleConfig wmInfo = null;

        int length = id.length();
        // Check for ':' separator
        int separatorIndex = id.indexOf(Constants.NAME_SEPARATOR);
        if (separatorIndex == -1) {
            // Check for '#' separator
            separatorIndex = id.indexOf('#');
        }
        if (separatorIndex != -1) {
            String appID = id.substring(0, separatorIndex);
            String moduleID = id.substring(separatorIndex + 1);
            J2eeApplication j2eeApp = appsBean.getJ2eeApplicationByName(appID);
            if ((j2eeApp != null) && j2eeApp.isEnabled()) {
                String location = j2eeApp.getLocation();
                String moduleDir = 
                    DeploymentUtils.getRelativeEmbeddedModulePath(
                        location, moduleID);
              
                ApplicationRegistry registry =
                    ApplicationRegistry.getInstance();
                ClassLoader appLoader =
                    registry.getClassLoaderForApplication(appID);
                if (appLoader != null) {
                    Application appDesc = registry.getApplication(appLoader);
                    if (appDesc != null) {
                        Set wbds = appDesc.getWebBundleDescriptors();
                        WebBundleDescriptor wbd = null;
                        for (Iterator itr = wbds.iterator(); itr.hasNext(); ) {
                            wbd = (WebBundleDescriptor) itr.next();
                            String webUri = wbd.getModuleDescriptor().getArchiveUri();
                            if (moduleID.equals(webUri)) {
                                StringBuffer dir = new StringBuffer(location);
                                dir.append(File.separator);
                                dir.append(moduleDir);
                                WebModule wm = new WebModule();
                                wm.setName(moduleID);
                                wm.setContextRoot(wbd.getContextRoot());
                                wm.setLocation(dir.toString());
                                wm.setEnabled(true);
                                String vsList = getVirtualServers(j2eeApp.getName());
                                wmInfo = new WebModuleConfig();
                                wmInfo.setBean(wm);
                                wmInfo.setDescriptor(wbd);
                                wmInfo.setParentLoader(appLoader);
                                wmInfo.setVirtualServers(vsList);
                                break;
                            }
                        }
                    }
                }
            } else {
                Object[] params = { id, getID() };
                _logger.log(Level.SEVERE, "vs.defaultWebModuleDisabled",
                            params);
            }
        }
        return wmInfo;
    }
    
    /**
     * Virtual servers are maintained in the reference contained 
     * in Server element. First, we need to find the server
     * and then get the virtual server from the correct reference
     *
     * @param appName Name of the app to get vs
     *
     * @return virtual servers as a string (separated by space or comma)
     */
    private String getVirtualServers(String appName) {
        String ret = null;
        try {
            ConfigContext ctx = 
                ApplicationServer.getServerContext().getConfigContext();
            ret = ServerBeansFactory
                    .getVirtualServersByAppName(ctx, appName);
        } catch (ConfigException ce) {
            _logger.log(Level.FINE, "Cannot get virtual server for " + appName, ce);
        }
        
        return ret;
    }
    
    
    /**
     * Delete all aliases.
     */
    public void clearAliases(){
        aliases = new String[0];
    }


    private void setIsDisabled(boolean isDisabled) {
        vsPipeline.setIsDisabled(isDisabled);
        vsPipeline.setIsOff(false);
        if (isDisabled && pipeline != vsPipeline) {
            // Enable custom pipeline
            setPipeline(vsPipeline);
        } 
    }


    private void setIsOff(boolean isOff) {
        vsPipeline.setIsOff(isOff);
        vsPipeline.setIsDisabled(false);
        if (isOff && pipeline != vsPipeline) {
            // Enable custom pipeline
            setPipeline(vsPipeline);
        } 
    }


    /**
     * @return The properties of this virtual server, or null
     */
    ElementProperty[] getProperties() {
        if (vsBean != null) {
            return vsBean.getElementProperty();
        } else {
            return null;
        }
    }


    /**
     * Set the Stat holder.
     */
    public void setPWCRequestStatsImpl(PWCRequestStatsImpl pwcRequestStatsImpl){
        this.pwcRequestStatsImpl = pwcRequestStatsImpl;
    }

    
    /**
     * Get the Stat holder.
     */
    public PWCRequestStatsImpl getPWCRequestStatsImpl(){
        return pwcRequestStatsImpl;
    }

    
    /**
     * Configures the valve_ and listener_ properties of this VirtualServer.
     */
    protected void configureCatalinaProperties(){

        ElementProperty[] props = vsBean.getElementProperty();
        if (props == null) {
            return;
        }

        for (int i=0; i<props.length; i++) {

            String propName = props[i].getName();
            String propValue = props[i].getValue();
            if (propName == null || propValue == null) {
                _logger.log(Level.WARNING,
                            "webcontainer.nullWebModuleProperty",
                            getName());
            }

            if (propName.startsWith("valve_")) {
                addValve(propValue);            
            } else if (propName.startsWith("listener_")) {
                addListener(propValue);   
            } else if (propName.equals("securePagesWithPragma")){
                setSecurePagesWithPragma(Boolean.valueOf(propValue));
            }
        }
    }
    

    /**
     * Configure virtual-server alias attribute.
     */    
    void configureAliases() {

        // Add each host name from the 'hosts' attribute as an alias
        List hosts = StringUtils.parseStringList(vsBean.getHosts(), ",");
        for (int i=0; i < hosts.size(); i++ ){
            String alias = hosts.get(i).toString();
            if ( !alias.equalsIgnoreCase("localhost.localdomain")){
                addAlias(alias);
            }
        }
    }

 
    /**
     * Configures this virtual server with its authentication realm.
     *
     * Checks if this virtual server specifies any authRealm property, and
     * if so, ensures that its value identifies a valid realm.
     *
     * @param securityService The security-service element from domain.xml
     */
    void configureAuthRealm(SecurityService securityService) {
        ElementProperty prop = vsBean.getElementPropertyByName("authRealm");
        if (prop != null && prop.getValue() != null) {
            if (securityService.getAuthRealmByName(prop.getValue()) != null) {
                authRealmName = prop.getValue();
            } else {
                _logger.log(Level.SEVERE, "vs.invalidAuthRealm",
                            new Object[] { getID(), prop.getValue() });
            }
        }
    }
     

    /**
     * Gets the value of the authRealm property of this virtual server.
     *
     * @return The value of the authRealm property of this virtual server,
     * or null of this virtual server does not have any such property
     */
    String getAuthRealmName() {
        return authRealmName;
    }


    /**
     * Adds the <code>Valve</code> with the given class name to this
     * VirtualServer.
     *
     * @param valveName The valve's fully qualified class name
     */
    protected void addValve(String valveName) {
        Valve valve = (Valve)loadInstance(valveName);  
        
        if (valve == null) return;
        
        super.addValve(valve); 
    }    
    
    
    /**
     * Adds the Catalina listener with the given class name to this
     * VirtualServer.
     * 
     * @param listenerName The fully qualified class name of the listener. 
     */
    protected void addListener(String listenerName) {
        Object listener = loadInstance(listenerName);
        
        if ( listener == null ) return;

        if (listener instanceof ContainerListener) {
            addContainerListener((ContainerListener)listener);
        } else if (listener instanceof LifecycleListener){
            addLifecycleListener((LifecycleListener)listener);            
        } else {
            _logger.log(Level.SEVERE,"webcontainer.invalidListener"
                    + listenerName);
        }     
    }
    
   
    private Object loadInstance(String className){
        try{
            Class clazz = Class.forName(className);
            return clazz.newInstance();
        } catch (Throwable ex){
            _logger.log(Level.SEVERE,"webcontainer.unableToLoadExtension",ex);        
        }
        return null;
    }



    /**
     * Configures this VirtualServer with its send-error properties.
     */
    void configureErrorPage() {

        ErrorPage errorPage = null;

        if (vsBean == null) {
            return;
        }

        ElementProperty[] props = vsBean.getElementProperty();
        if (props == null) {
            return;
        }

        for (int i=0; i<props.length; i++) {

            String propName = props[i].getName();
            String propValue = props[i].getValue();
            if (propName == null || propValue == null) {
                _logger.log(Level.WARNING,
                            "webcontainer.nullVirtualServerProperty",
                            getID());
                continue;
            }

            if (!propName.startsWith("send-error_")) {
                continue;
            }

            /* 
             * Validate the prop value
             */
            String path = null;
            String reason = null;
            String status = null;

            String[] errorParams = propValue.split(" ");
            for (int j=0; j<errorParams.length; j++) {

                if (errorParams[j].startsWith("path=")) {
                    if (path != null) {
                        _logger.log(Level.WARNING,
                                    "webcontainer.sendErrorMultipleElement",
                                    new Object[] { propValue, "path" });
                    }
                    path = errorParams[j].substring("path=".length());
                }

                if (errorParams[j].startsWith("reason=")) {
                    if (reason != null) {
                        _logger.log(Level.WARNING,
                                    "webcontainer.sendErrorMultipleElement",
                                    new Object[] { propValue, "reason" });
                    }
                    reason = errorParams[j].substring("reason=".length());
                }

                if (errorParams[j].startsWith("code=")) {
                    if (status != null) {
                        _logger.log(Level.WARNING,
                                    "webcontainer.sendErrorMultipleElement",
                                    new Object[] { propValue, "code" });
                    }
                    status = errorParams[j].substring("code=".length());
                }
            }

            if (path == null || path.length() == 0) {
                _logger.log(Level.WARNING,
                            "webcontainer.sendErrorMissingPath",
                            propValue);
            }

            errorPage = new ErrorPage();
            errorPage.setLocation(path);
            errorPage.setErrorCode(status);
            errorPage.setReason(reason);

            addErrorPage(errorPage);
        }

    }


    /**
     * Configures this VirtualServer with its redirect properties.
     */
    void configureRedirect() {

        vsPipeline.clearRedirects();

        if (vsBean == null) {
            return;
        }

        ElementProperty[] props = vsBean.getElementProperty();
        if (props == null) {
            return;
        }

        for (int i=0; i<props.length; i++) {

            String propName = props[i].getName();
            String propValue = props[i].getValue();
            if (propName == null || propValue == null) {
                _logger.log(Level.WARNING,
                            "webcontainer.nullVirtualServerProperty",
                            getID());
                continue;
            }

            if (!propName.startsWith("redirect_")) {
                continue;
            }

            /* 
             * Validate the prop value
             */
            String from = null;
            String url = null;
            String urlPrefix = null;
            String escape = null;

            String[] redirectParams = propValue.split(" ");
            for (int j=0; j<redirectParams.length; j++) {

                if (redirectParams[j].startsWith("from=")) {
                    if (from != null) {
                        _logger.log(Level.WARNING,
                                    "webcontainer.redirectMultipleElement",
                                    new Object[] { propValue, "from" });
                    }
                    from = redirectParams[j].substring("from=".length());
                }

                if (redirectParams[j].startsWith("url=")) {
                    if (url != null) {
                        _logger.log(Level.WARNING,
                                    "webcontainer.redirectMultipleElement",
                                    new Object[] { propValue, "url" });
                    }
                    url = redirectParams[j].substring("url=".length());
                }

                if (redirectParams[j].startsWith("url-prefix=")) {
                    if (urlPrefix != null) {
                        _logger.log(Level.WARNING,
                                    "webcontainer.redirectMultipleElement",
                                    new Object[] { propValue, "url-prefix" });
                    }
                    urlPrefix = redirectParams[j].substring(
                                                    "url-prefix=".length());
                }

                if (redirectParams[j].startsWith("escape=")) {
                    if (escape != null) {
                        _logger.log(Level.WARNING,
                                    "webcontainer.redirectMultipleElement",
                                    new Object[] { propValue, "escape" });
                    }
                    escape = redirectParams[j].substring("escape=".length());
                }
            }

            if (from == null || from.length() == 0) {
                _logger.log(Level.WARNING,
                            "webcontainer.redirectMissingFrom",
                            propValue);
            }

            // Either url or url-prefix (but not both!) must be present
            if ((url == null || url.length() == 0)
                    && (urlPrefix == null || urlPrefix.length() == 0)) {
                _logger.log(Level.WARNING,
                            "webcontainer.redirectMissingUrlOrUrlPrefix",
                            propValue);
            }
            if (url != null && url.length() > 0
                    && urlPrefix != null && urlPrefix.length() > 0) {
                _logger.log(Level.WARNING,
                            "webcontainer.redirectBothUrlAndUrlPrefix",
                            propValue);
            }

            boolean escapeURI = true;
            if (escape != null) {
                if ("yes".equalsIgnoreCase(escape)) {
                    escapeURI = true;
                } else if ("no".equalsIgnoreCase(escape)) {
                    escapeURI = false;
                } else {
                    _logger.log(Level.WARNING,
                                "webcontainer.redirectInvalidEscape",
                                propValue);
                }
            }

            vsPipeline.addRedirect(from, url, urlPrefix, escapeURI);
        }

        if (vsPipeline.hasRedirects()) {
            if (pipeline != vsPipeline) {
                // Enable custom pipeline
                setPipeline(vsPipeline);
            }
        } else if (isActive && pipeline != origPipeline) {
            setPipeline(origPipeline);
        }
    }


    /**
     * Configures the SSO valve of this VirtualServer.
     */
    void configureSSOValve(
            boolean globalSSOEnabled,
            WebContainerFeatureFactory webContainerFeatureFactory) {

        if (vsBean == null) {
            return;
        }

        if (!isSSOEnabled(globalSSOEnabled)) {
            /*
             * Disable SSO
             */
            Object[] params = {  getID() };
            _logger.log(Level.INFO, "webcontainer.ssodisabled", params);

            // Remove existing SSO valve (if any)
            Valve[] valves = getValves();
            for (int i=0; valves!=null && i<valves.length; i++) {
                if (valves[i] instanceof SingleSignOn) {
                    removeValve(valves[i]);
                    break;
                }
            }

        } else {
            /*
             * Enable SSO
             */
            try {
                SSOFactory ssoFactory = webContainerFeatureFactory.getSSOFactory();
                //SingleSignOn sso = ssoFactory.createSingleSignOnValve();
                String vsName = this.getName();
                SingleSignOn sso = ssoFactory.createSingleSignOnValve(vsName);
                
                // set max idle time if given
                ElementProperty idle =
                    vsBean.getElementPropertyByName(SSO_MAX_IDLE);
                if (idle != null && idle.getValue() != null) {
                    _logger.fine("SSO entry max idle time set to: " +
                                 idle.getValue());
                    int i = Integer.parseInt(idle.getValue());
                    sso.setMaxInactive(i);
                }

                // set expirer thread sleep time if given
                ElementProperty expireTime =
                    vsBean.getElementPropertyByName(SSO_REAP_INTERVAL);
                if (expireTime !=null && expireTime.getValue() != null) {
                    _logger.fine("SSO expire thread interval set to : " +
                                 expireTime.getValue());
                    int i = Integer.parseInt(expireTime.getValue());
                    sso.setReapInterval(i);
                }

                // Remove existing SSO valve (if any), in case of a reconfig
                Valve[] valves = getValves();
                for (int i=0; valves!=null && i<valves.length; i++) {
                    if (valves[i] instanceof SingleSignOn) {
                        removeValve(valves[i]);
                        break;
                    }
                }

                addValve(sso);

            } catch (Exception e) {
                _logger.log(Level.WARNING, "webcontainer.ssobadconfig", e);
                _logger.log(Level.WARNING, "webcontainer.ssodisabled",
                            getID());
            }
        }
    }


    /**
     * Configures this VirtualServer with its state (on | off | disabled).
     */
    void configureVirtualServerState(){

        String stateValue = ON;
        if (vsBean != null){
            stateValue = vsBean.getState();
        }

        if ( ( !stateValue.equalsIgnoreCase(ON) )
            && (getName().equalsIgnoreCase(ADMIN_VS) ) ){
            throw new java.lang.IllegalArgumentException(
                "virtual-server " 
                + ADMIN_VS + " state property cannot be modified");
        }
        
        if ( stateValue.equalsIgnoreCase(DISABLED) ) {
            // state="disabled"
            setIsDisabled(true);
        } else if ( !ConfigBean.toBoolean( stateValue ) ) {
            // state="off"
            setIsOff(true);
        } else {
            setIsActive(true);
        }
    }


    /**
     * Configures the Remote Address Filter valve of this VirtualServer.
     *
     * This valve enforces request accpetance/denial based on the string
     * representation of the remote client's IP address.
     */
    void configureRemoteAddressFilterValve() {

        RemoteAddrValve remoteAddrValve = null;

        if (vsBean == null) {
            return;
        }

        ElementProperty allow = vsBean.getElementPropertyByName(
            "allowRemoteAddress");
        ElementProperty deny = vsBean.getElementPropertyByName(
            "denyRemoteAddress");
        if ((allow != null && allow.getValue() != null)
                || (deny != null && deny.getValue() != null))  {
            remoteAddrValve = new RemoteAddrValve();
        }

        if (allow != null && allow.getValue() != null) {
            _logger.fine("Allowing access to " + getID()+ " from " +
                         allow.getValue());
            remoteAddrValve.setAllow(allow.getValue());
        }

        if (deny != null && deny.getValue() != null) {
            _logger.fine("Denying access to " + getID()+ " from " +
                         deny.getValue());
            remoteAddrValve.setDeny(deny.getValue());
        }

        if (remoteAddrValve != null) {
            // Remove existing RemoteAddrValve (if any), in case of a reconfig
            Valve[] valves = getValves();
            for (int i=0; valves!=null && i<valves.length; i++) {
                if (valves[i] instanceof RemoteAddrValve) {
                    removeValve(valves[i]);
                    break;
                }
            }
            addValve(remoteAddrValve);
        }
    }


    /**
     * Configures the Remote Host Filter valve of this VirtualServer.
     * 
     * This valve enforces request acceptance/denial based on the name of the
     * remote host from where the request originated.
     */
    void configureRemoteHostFilterValve(HttpProtocol httpProtocol) {

        RemoteHostValve remoteHostValve = null;

        if (vsBean == null) {
            return;
        }

        ElementProperty allow = vsBean.getElementPropertyByName(
            "allowRemoteHost");
        ElementProperty deny = vsBean.getElementPropertyByName(
            "denyRemoteHost");
        if ((allow != null && allow.getValue() != null)
                || (deny != null && deny.getValue() != null))  {
            remoteHostValve = new RemoteHostValve();
        }

        if (allow != null && allow.getValue() != null) {
            _logger.fine("Allowing access to " + getID()+ " from " +
                         allow.getValue());
            if (httpProtocol == null || !httpProtocol.isDnsLookupEnabled()) {
                _logger.log(Level.WARNING,
                            "webcontainer.allowRemoteHost.dnsLookupDisabled",
                            getID());
            }
            remoteHostValve.setAllow(allow.getValue());
        }

        if (deny != null && deny.getValue() != null) {
            _logger.fine("Denying access to " + getID()+ " from " +
                         deny.getValue());
            if (httpProtocol == null || !httpProtocol.isDnsLookupEnabled()) {
                _logger.log(Level.WARNING,
                            "webcontainer.denyRemoteHost.dnsLookupDisabled",
                            getID());
            }
            remoteHostValve.setDeny(deny.getValue());
        }

        if (remoteHostValve != null) {
            // Remove existing RemoteHostValve (if any), in case of a reconfig
            Valve[] valves = getValves();
            for (int i=0; valves!=null && i<valves.length; i++) {
                if (valves[i] instanceof RemoteHostValve) {
                    removeValve(valves[i]);
                    break;
                }
            }
            addValve(remoteHostValve);
        }
    }


    /**
     * Reconfigures the access log of this VirtualServer with its
     * updated access log related properties.
     */
    void reconfigureAccessLog(String globalAccessLogBufferSize,
                              String globalAccessLogWriteInterval,
                              InstanceEnvironment instance,
                              Domain domain,
                              boolean globalAccessLoggingEnabled) {
        try {
            if (accessLogValve.isStarted()) {
                accessLogValve.stop();
            }
            boolean start = accessLogValve.updateVirtualServerProperties(
                vsBean.getId(), vsBean, domain, instance,
                globalAccessLogBufferSize, globalAccessLogWriteInterval);
            if (start && isAccessLoggingEnabled(globalAccessLoggingEnabled)) {
                enableAccessLogging();
            } else {
                disableAccessLogging();
            }
        } catch (LifecycleException le) {
            _logger.log(Level.SEVERE,
                        "pewebcontainer.accesslog.reconfigure",
                        le);
        }
    }


    /**
     * Reconfigures the access log of this VirtualServer with the
     * updated attributes of the access-log element from domain.xml.
     */
    void reconfigureAccessLog(
            HttpService httpService,
            WebContainerFeatureFactory webcontainerFeatureFactory) {

        try {
            boolean restart = false;
            if (accessLogValve.isStarted()) {
                accessLogValve.stop();
                restart = true;
            }
            accessLogValve.updateAccessLogAttributes(
                httpService,
                webcontainerFeatureFactory);
            if (restart) {
                accessLogValve.start();
            }
        } catch (LifecycleException le) {
            _logger.log(Level.SEVERE,
                        "pewebcontainer.accesslog.reconfigure",
                        le);
        }
    }


    /**
     * @return the accesslog valve of this virtual server
     */
    PEAccessLogValve getAccessLogValve() {
        return accessLogValve;
    }


    /**
     * Enables access logging for this virtual server, by adding its
     * accesslog valve to its pipeline, or starting its accesslog valve
     * if it is already present in the pipeline.
     */
    void enableAccessLogging() {
        if (!isAccessLogValveActivated()) {
            addValve(accessLogValve);
        } else {
            try {
                if (accessLogValve.isStarted()) {
                    accessLogValve.stop();
                }
                accessLogValve.start();
            } catch (LifecycleException le) {
                _logger.log(Level.SEVERE,
                            "pewebcontainer.accesslog.reconfigure",
                            le);
            }
        }
    }


    /**
     * Disables access logging for this virtual server, by removing its
     * accesslog valve from its pipeline.
     */
    void disableAccessLogging() {
        removeValve(accessLogValve);
    }


    /**
     * @return true if the accesslog valve of this virtual server has been
     * activated, that is, added to this virtual server's pipeline; false
     * otherwise
     */
    private boolean isAccessLogValveActivated() {

        Pipeline p = getPipeline();
        if (p != null) {
            Valve[] valves = p.getValves();
            if (valves != null) {
                for (int i=0; i<valves.length; i++) {
                    if (valves[i] instanceof PEAccessLogValve) {
                        return true;
                    }
                }
            }
        }

        return false;
    }


    /**
     * Configures the cache control of this VirtualServer
     */
    void configureCacheControl(String cacheControl){       
        if (cacheControl != null) {
            List values = StringUtils.parseStringList(cacheControl,
                                                      ",");
            if (values != null && !values.isEmpty()) {
                String[] cacheControls = new String[values.size()];
                setCacheControls((String[]) values.toArray(cacheControls));
            }
        }
    }


    /**
     * Checks if SSO is enabled for this VirtualServer.
     *
     * @return The value of the sso-enabled property for this VirtualServer
     */
    private boolean isSSOEnabled(boolean globalSSOEnabled)
    {
        ElementProperty ssoProperty  = 
            vsBean.getElementPropertyByName(Constants.SSO_ENABLED);

        if (ssoProperty == null || ssoProperty.getValue() == null) {
            return globalSSOEnabled;
        } else {
            return ConfigBean.toBoolean(ssoProperty.getValue());
        }
    }


    /**
     * Determines whether access logging is enabled for this virtual server.
     *
     * @param globalAccessLoggingEnabled The value of the 
     * accessLoggingEnabled property of the http-service element
     *
     * @return true if access logging is enabled for this virtual server,
     * false otherwise.
     */
    boolean isAccessLoggingEnabled(boolean globalAccessLoggingEnabled) {
        ElementProperty prop  = 
            vsBean.getElementPropertyByName(Constants.ACCESS_LOGGING_ENABLED);

        if (prop == null || prop.getValue() == null) {
            return globalAccessLoggingEnabled;
        } else {
            return ConfigBean.toBoolean(prop.getValue());
        }
    }

    /**
     * Starts the children (web contexts) of this virtual server
     * concurrently.
     */
    protected void startChildren() {

        ArrayList<LifecycleStarter> starters
            = new ArrayList<LifecycleStarter>();

        Container children[] = findChildren();
        for (int i = 0; i < children.length; i++) {
            if (children[i] instanceof Lifecycle) {
                LifecycleStarter starter =
                    new LifecycleStarter(((Lifecycle) children[i]));
                starters.add(starter);
                starter.submit();
            }
        }

        for (LifecycleStarter starter : starters) {
            Throwable t = starter.waitDone();
            if (t != null) {
                Lifecycle container = starter.getContainer();
                ((Context) container).setAvailable(false);
                String msg = _rb.getString("vs.startContextError");
                msg = MessageFormat.format(msg,
                                           new Object[] { container,
                                                          getID() });
                _logger.log(Level.SEVERE, msg, t);
            }
        }
    }
}