/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (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/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.web;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import javax.management.MBeanServer;

import javax.naming.NamingException;

import org.apache.catalina.Container;
import org.apache.catalina.Connector;
import org.apache.catalina.startup.Embedded;
import org.apache.catalina.Engine;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.coyote.tomcat5.CoyoteConnector;

import com.sun.enterprise.server.Constants;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigBean;
import com.sun.enterprise.config.ConfigBeansFactory;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.serverbeans.AccessLog;
import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.config.serverbeans.ConnectionPool;
import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.config.serverbeans.Applications;
import com.sun.enterprise.config.serverbeans.J2eeApplication;
import com.sun.enterprise.config.serverbeans.LogService;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.config.serverbeans.WebModule;
import com.sun.enterprise.config.serverbeans.HttpFileCache;
import com.sun.enterprise.config.serverbeans.HttpService;
import com.sun.enterprise.config.serverbeans.HttpProtocol;
import com.sun.enterprise.config.serverbeans.KeepAlive;
import com.sun.enterprise.config.serverbeans.HttpListener;
import com.sun.enterprise.config.serverbeans.ElementProperty; 
import com.sun.enterprise.config.serverbeans.RequestProcessing;
import com.sun.enterprise.config.serverbeans.Ssl;
//import com.sun.enterprise.config.serverbeans.Mime;
import com.sun.enterprise.config.ConfigContext;

import com.sun.enterprise.instance.InstanceEnvironment;
import com.sun.enterprise.server.ApplicationRegistry;
import com.sun.enterprise.server.ServerContext;
import com.sun.enterprise.admin.common.InitConfFileBean;
import com.sun.enterprise.admin.common.PasswordConfReader;
import com.sun.enterprise.server.pluggable.WebContainerFeatureFactory;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.util.logging.IASLevel;
import com.sun.enterprise.util.StringUtils;
import com.sun.enterprise.web.logger.IASLogger;
import com.sun.enterprise.web.session.PersistenceType;
import com.sun.enterprise.web.session.SessionCookieConfig;
import com.sun.enterprise.web.connector.coyote.PECoyoteConnector;

import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.WebBundleDescriptor;
import com.sun.enterprise.Switch;
import com.sun.appserv.server.ServerLifecycleException;
import com.sun.appserv.ProxyHandler;
import com.sun.logging.LogDomains;
import com.sun.web.security.RealmAdapter;

//dynamic reconfiguration
import com.sun.enterprise.admin.event.AdminEventListenerException;
import com.sun.enterprise.admin.event.AdminEventListenerRegistry;
import com.sun.enterprise.admin.event.http.HSHttpListenerEvent;
import com.sun.enterprise.admin.event.http.HSSslEvent;
import com.sun.enterprise.admin.event.http.HSVirtualServerEvent;
import com.sun.enterprise.admin.event.http.HSHttpAccessLogEvent;
import com.sun.enterprise.admin.event.http.HSAccessLogEvent;

// monitoring imports
import com.sun.enterprise.admin.monitor.stats.HTTPListenerStats;
import com.sun.enterprise.admin.monitor.registry.MonitoringRegistry;
import com.sun.enterprise.admin.monitor.registry.MonitoringRegistrationException;
import com.sun.enterprise.admin.monitor.registry.MonitoringLevelListener;

import com.sun.enterprise.web.reconfig.HttpListenerReconfig;
import com.sun.enterprise.web.reconfig.VirtualServerReconfig;

import com.sun.enterprise.web.stats.HTTPListenerStatsImpl;
import com.sun.enterprise.web.stats.PWCFileCacheStatsImpl;
import com.sun.enterprise.web.stats.PWCKeepAliveStatsImpl;
import com.sun.enterprise.web.stats.PWCThreadPoolStatsImpl;
import com.sun.enterprise.web.stats.PWCVirtualServerStatsImpl;
import com.sun.enterprise.web.stats.PWCConnectionQueueStatsImpl;
import com.sun.enterprise.web.stats.PWCRequestStatsImpl;

import com.sun.enterprise.security.CipherInfo;

/**
 * Represents the web container for PE and EE (since 9.0)
 */
public class PEWebContainer extends WebContainer
    implements MonitoringLevelListener {

    /**
     * Maps mime element's id to parsed contents of mime file
     */
    private HashMap mimeMap;

    /**
     * Maps http-listener id to Tomcat Connector
     */
    private HashMap<String,PECoyoteConnector> connectorMap;
    
   
    /**
     * Allow disabling accessLog mechanism
     */
    private boolean installAccessLogValve = true;
    
    
   /**
    * AccessLog buffer size for storing logs.
    */
   private String accessLogBufferSize = null;
   
   
   /**
    * AccessLog interval before the valve flush its buffer to the disk.
    */
   private String accessLogWriteInterval = null;  
   
   
    /**
     * The default-redirect port.
     */
    protected int defaultRedirectPort = -1;

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

    /**
     * This creates the embedded Catalina/Jasper container 
     * and sets the config properties on the container.
     */
    protected PEWebContainer(String id, ServerContext context) {

        super(id, context);
    }

    /**
     * Initialize Tomcat internal objects.
     */
    protected void init(ServerContext context){ 

        connectorMap = new HashMap<String,PECoyoteConnector>();

        Config config = null;
        ConfigContext configContext = context.getConfigContext();

        try {
            config = ServerBeansFactory.getConfigBean(configContext);
        } catch (ConfigException e) {
            String msg = _rb.getString("vs.appsConfigError");
                Object[] params = { };
                msg = MessageFormat.format(msg, params);
            _logger.log(Level.SEVERE, msg, e);
        }
         // Create Tomcat Engine
        createEngine();

        HttpService httpService = config.getHttpService();
        configureNotSupported(httpService);

        createConnectors(httpService);

        createHosts(httpService, installAccessLogValve);
        createJKConnector();
        
        registerReconfigListeners();
       
    }


    /**
     * Starts the AJP connector that will listen to call from Apache using
     * mod_jk, mod_jk2 or mod_ajp.
     *
     */
    private void createJKConnector() {

        int port = 8009;
        int defaultRedirectPort = -1;

        String portString = System.getProperty("com.sun.enterprise.web.connector.enableJK");
        if (portString == null) {
            // do not create JK Connector if property is not set
            return;
        } else {
            try {
                port = Integer.parseInt(portString);
            } catch (NumberFormatException ex) {
                // use default port 8009
                port = 8009;
            }
            PECoyoteConnector connector = (PECoyoteConnector)
                ((Embedded)_embedded).createConnector("0.0.0.0", port, "ajp");

            String defaultHost = "server";
            Container[] virtualServers = 
                    _embedded.getEngines()[0].findChildren();
            // Find a virtual-server on which we can attach the mod_jk
            // listener.
            for (int i=0; i < virtualServers.length; i++){
                if ( !virtualServers[i].getName().equalsIgnoreCase(ADMIN_VS) ){
                    defaultHost = virtualServers[i].getName();
                    
                    int[] ports = ((VirtualServer)virtualServers[i]).findPorts();
                    int[] newPorts = new int[ports.length + 1];
                    System.arraycopy(ports,0,newPorts,0,ports.length);
                    newPorts[ports.length] = port;
                    ((VirtualServer)virtualServers[i]).setPorts(newPorts);
                    break;
                }
            }
            connector.setDefaultHost(defaultHost);        
            connector.setDomain(_serverContext.getDefaultDomainName());
            connector.setLogger(_logger); 
            connector.setName("httpd-listener");
        
            _logger.log(Level.INFO, "Apache mod_jk/jk2 attached to virtual-server "
                                + defaultHost + " listening on port: "
                                + portString);

            _embedded.addConnector(connector);       
        }
    }

    
    /**
     * Enumerates the http-listener subelements of the given http-service
     * element, and creates a corresponding Tomcat Connector for each.
     *
     * @param httpService The http-service element
     */
    public void createConnectors(HttpService httpService) {
        HttpListener[] httpListeners = httpService.getHttpListener();
        
        // Attach http-listeners to Engine
        if (httpListeners != null){
            for (int i=0; i< httpListeners.length; i++) {
                createConnector(httpListeners[i],httpService);               
            }
        }
        setDefaultRedirectPort(defaultRedirectPort);
    }

    
    /**
     * Use an http-listener subelements and creates a corresponding 
     * Tomcat Connector for each.
     *
     * @param httpService The http-service element
     * @param httpListener the configuration element.
     */       
    public PECoyoteConnector createConnector(HttpListener httpListener,
                                             HttpService httpService){
                                 
        int port = 8080;
        PECoyoteConnector connector;
        
        checkHostnameUniqueness(httpListener.getId(), httpService);

        try {
            port = Integer.parseInt(httpListener.getPort());
        } catch (NumberFormatException nfe) {
            String msg = _rb.getString("pewebcontainer.http_listener.invalid_port");
            msg = MessageFormat.format(msg,
                                       new Object[] {httpListener.getPort(),
                                       httpListener.getId() });
            throw new IllegalArgumentException(msg);
        }

        /*
         * Create Connector. Connector is SSL-enabled if
         * 'security-enabled' attribute in <http-listener>
         * element is set to TRUE.
         */
        boolean isSecure = httpListener.isSecurityEnabled();
        if (isSecure && defaultRedirectPort == -1) {
            defaultRedirectPort = port;
        }
        String address = httpListener.getAddress();
        if ("any".equals(address) || "ANY".equals(address)
                || "INADDR_ANY".equals(address)) {
            address = null;
            /*
             * Setting 'address' to NULL will cause Tomcat to pass a
             * NULL InetAddress argument to the java.net.ServerSocket
             * constructor, meaning that the server socket will accept
             * connections on any/all local addresses.
             */
        }

        connector = 
            (PECoyoteConnector)_embedded.createConnector(address,port,
                                                         isSecure);
        connector.setName(httpListener.getId());

        configureConnector(connector,httpListener,isSecure,httpService);

        if ( _logger.isLoggable(Level.FINE)){
            _logger.log(Level.FINE, "create.listenerport",
                new Object[] {new Integer(port), connector});
        }

        _embedded.addConnector(connector);

        connectorMap.put(httpListener.getId(), connector);       

        // If we already know the redirect port, then set it now
        // This situation will occurs when dynamic reconfiguration occurs
        if ( defaultRedirectPort != -1 ){
            connector.setRedirectPort(defaultRedirectPort);
        }

        return connector;
    }
    

    /**
     * Assigns the given redirect port to each Connector whose corresponding
     * http-listener element in domain.xml does not specify its own
     * redirect-port attribute.
     *
     * The given defaultRedirectPort corresponds to the port number of the 
     * first security-enabled http-listener in domain.xml.
     *
     * This method does nothing if none of the http-listener elements is
     * security-enabled, in which case Tomcat's default redirect port (443) 
     * will be used.
     *
     * @param defaultRedirectPort The redirect port to be assigned to any
     * Connector object that doesn't specify its own
     */
    private void setDefaultRedirectPort(int defaultRedirectPort) {
        if (defaultRedirectPort != -1) {
            Connector[] connectors = _embedded.getConnectors();
            for (int i=0; i<connectors.length; i++) {
                if (connectors[i].getRedirectPort() == -1) {
                    connectors[i].setRedirectPort(defaultRedirectPort);
                }
            } 
        }
    }


    /*
     * Creates Tomcat Engine.
     */
    protected void createEngine(){

        String engineName = "com.sun.appserv";

        Engine engine = _embedded.createEngine();
        _embedded.addEngine(engine);

        ((StandardEngine)engine).setName(engineName);
        if (isTomcatUsingDefaultDomain()) {
            ((StandardEngine)engine).setDomain(
                        _serverContext.getDefaultDomainName());
        } else {
            ((StandardEngine)engine).setDomain(engineName);
        }
        _logger.log(Level.FINE, "Creating Engine " + engineName);
    }
    
    
    /**
     * Enumerates the virtual-server subelements of the given http-service
     * element, and creates a corresponding Tomcat Host for each.
     *
     * @param httpService The http-service element
     */
    public void createHost(
                    com.sun.enterprise.config.serverbeans.VirtualServer vse,
                    ConfigContext configContext,
                    boolean enableMonitoring) throws ConfigException{
                        
         Config config = ServerBeansFactory.getConfigBean(configContext);
         
         VirtualServer vs = createHost(config.getHttpService(),
                                       vse, 
                                       installAccessLogValve);
         
         loadDefaultWebModule(vs,
                              ServerBeansFactory.getServerBean(configContext));
         
         if ( enableMonitoring ){             
             enableVirtualServerMonitoring(vs);
         }
    }  
    
    
    /**
     * Enumerates the virtual-server subelements of the given http-service
     * element, and creates a corresponding Tomcat Host for each.
     *
     * @param httpService The http-service element
     * @param configureAccessLog Indicates whether access log needs to be
     * added
     */
    protected void createHosts(HttpService httpService,
                               boolean configureAccessLog) {

        com.sun.enterprise.config.serverbeans.VirtualServer[] vses
            = httpService.getVirtualServer();
        for (int j = 0; j < vses.length; j++) {
            createHost(httpService,vses[j], configureAccessLog);
        }
    }
          
    
    /**
     * Creates a corresponding Tomcat Host from a virtual-server config bean.
     *
     * Note: if you add supports for a new property, you MUST also add the
     *       logic in updateHostProperties method.
     *
     * @param httpService The http-service element
     * @param vse The virtual-server configuration bean.
     * @param configureAccessLog Indicates whether access log needs to be
     * added
     */            
    protected VirtualServer createHost(
                       HttpService httpService,
                       com.sun.enterprise.config.serverbeans.VirtualServer vse,
                       boolean configureAccessLog){
            
        Engine[] engines = _embedded.getEngines();
        String docroot = null;
        MimeMap mm = null;
        String vs_id = vse.getId();

        ElementProperty element = vse.getElementPropertyByName("docroot");
        if ( element != null){
            docroot = element.getValue();
        }
        
        validateDocroot(docroot,
                        vs_id,
                        vse.getDefaultWebModule());

        VirtualServer vs = createVS(vs_id, vse, docroot,
                                    vse.getLogFile(), mm,
                                    httpService.getHttpProtocol());

        // cache control
        ElementProperty cacheProp = vse.getElementPropertyByName(
                                                    "setCacheControl");
        if ( cacheProp != null ){
            configureCacheControl(cacheProp.getValue(), vs);   
        }

        if (configureAccessLog){
            setAccessLog(vs, vs_id, vse, httpService);
        }
        if (_logger.isLoggable(Level.FINEST)) {
            _logger.log(Level.FINEST, "Created virtual server " + vs_id);
        }

        /*
         * We must configure the Host with its associated port numbers and
         * alias names before adding it as an engine child and thereby
         * starting it, because a MapperListener, which is associated with
         * an HTTP listener and receives notifications about Host
         * registrations, relies on these Host properties in order to determine
         * whether a new Host needs to be added to the HTTP listener's Mapper.
         */
        configureHost(vs, httpService);

        // Add Host to Engine
        engines[0].addChild(vs);

        return vs;
    }

    
    /**
     * Configure cache control property of a virtual-server
     */
    protected void configureCacheControl(String cacheControl, VirtualServer vs){       
        if (cacheControl != null) {
            List values = StringUtils.parseStringList(cacheControl,
                                                      ",");
            if (values != null && !values.isEmpty()) {
                String[] cacheControls = new String[values.size()];
                vs.setCacheControls((String[]) values.toArray(
                                                    cacheControls));
            }
        }
    }
    
    
    /**
     * Validate the docroot properties of a virtual-server.
     */
    protected boolean validateDocroot(String docroot, String vs_id, 
                                      String defaultWebModule){
        
        // docroot vs default-web-module
        if (docroot != null ) {
            // If the docroot is invalid and there is no default module,
            // stop the process.
            boolean isValid = new File(docroot).exists();
            if ( !isValid && defaultWebModule == null){

                String msg = 
                    _rb.getString("pewebcontainer.virtual_server.invalid_docroot");
                msg = MessageFormat.format(msg, 
                                        new Object[] { vs_id , docroot});
                throw new IllegalArgumentException(msg);
            } else if (!isValid) {

                _logger.log(Level.WARNING, "virtual-server " + vs_id 
                            + " has an invalid docroot: " + docroot );
            }
        } else if (defaultWebModule == null) {
            String msg = _rb.getString("pewebcontainer.virtual_server.missing_docroot");
            msg = MessageFormat.format(msg, new Object[] { vs_id });
            throw new IllegalArgumentException(msg);
        }
        return true;
    }
    

    /**
     * Configures the given virtual server.
     *
     * @param vs The virtual server to be configured
     * @param httpService The http-service element of which the given
     * virtual server is a subelement
     */
    protected void configureHost(VirtualServer vs, HttpService httpService) {
       com.sun.enterprise.config.serverbeans.VirtualServer vsBean
                                                                 = vs.getBean();
       
        configureHostAlias(vs);

        // Set the ports with which this virtual server is associated
        List<String> listeners = StringUtils.parseStringList(
                                                vsBean.getHttpListeners(), ",");
        if (listeners == null) {
            return;
        }
        
        HttpListener[] httpListeners = new HttpListener[listeners.size()];
        for (int i=0; i < listeners.size(); i++){
            httpListeners[i] = httpService.getHttpListenerById(listeners.get(i));
        }
                
        configureHostListener(vs, httpListeners);
    }
        
    
    /**
     * Configure virtual-server alias attribute.
     * @param vs the virtual-server.
     */    
    protected void configureHostAlias(VirtualServer vs){
       com.sun.enterprise.config.serverbeans.VirtualServer vsBean
            = vs.getBean();

        // Add each host name from the 'hosts' attribute as an alias
        List hosts = StringUtils.parseStringList(vsBean.getHosts(), ",");
        String alias;
        for (int i=0; i < hosts.size(); i++ ){
            alias = hosts.get(i).toString();
            if ( !alias.equalsIgnoreCase("localhost.localdomain")){
                vs.addAlias( alias );
            }
        }
    }
    
    
    /**
     * Configure virtual-server's http-listener
     * @param vs the virtual-server
     * @param httpListeners the http-listener associated with the virtual-server
     */
    protected void configureHostListener(VirtualServer vs,
                                         HttpListener[] httpListeners){
       
       String listener;
       int[] ports = new int[httpListeners.length];
       PECoyoteConnector conn;
       int j=0;
       
       for (int i=0; i < httpListeners.length; i++){
            listener = httpListeners[i].getId();
                                
            conn = connectorMap.get(listener);
            ports[j++] = conn.getPort();
            if (!httpListeners[i].isEnabled()){
                if ( ( vs.getName().equalsIgnoreCase(ADMIN_VS) ) ){
                    throw new java.lang.IllegalArgumentException(
                        "http-listener " 
                        + ADMIN_VS + " enabled property cannot be modified");
                }
            }            
        }
        vs.setPorts(ports);
    }
    
    
    /**
     * Log any unsupported domain.xml attribute or element.
     */
    private final void configureNotSupported(HttpService httpService){

        if (!_logger.isLoggable(Level.FINE)) {
            return;
        }
        
        if ( httpService.getHttpFileCache() != null ) {        
            if ( httpService.getHttpFileCache().getHashInitSize() != null) {
                _logger.log(Level.FINE,
                           "pewebcontainer.unsupportedAttribute.hash-init-size");
            }

            HttpListener[] httpListeners = httpService.getHttpListener();
            for ( HttpListener httpListener: httpListeners){
                if ( httpListener.getFamily() != null ){
                    _logger.log(Level.FINE,
                            "pewebcontainer.unsupportedAttribute.family");            
                } 

                if ( httpListener.getAttributeValue("blocking-enabled") != null){
                    _logger.log(Level.FINE,
                         "pewebcontainer.unsupportedAttribute.blocking-enabled");
                }

                if ( httpListener.getExternalPort() != null ){
                    _logger.log(Level.FINE,
                            "pewebcontainer.unsupportedAttribute.external-port");
                }
            }
        }
        
        if ( httpService.getHttpProtocol() != null ){
            if ( httpService.getHttpProtocol().getVersion() != null) {
                _logger.log(Level.FINE,
                            "pewebcontainer.unsupportedAttribute.version");
            }

            if ( httpService.getHttpProtocol().getAttributeValue("ssl-enabled") 
                    != null) {
                _logger.log(Level.FINE,
                            "pewebcontainer.unsupportedAttribute.ssl-enabled");
            }   
        }
    }
    
    
    

    /**
     * Create a PEWebContainer object 
     */
    public static WebContainer createInstance(ServerContext context) {
        // Create the web container associated with this configuration
        webContainer = new PEWebContainer(new String("0"), context);
        _logger.log(Level.FINE, "Creating new instance of PEWebContainer.");
        
        // Initialize the Tomcat internal's object.
        ((PEWebContainer)webContainer).init(context);
        _logger.log(Level.FINE, "Initialization of PEWebContainer.");	
        return webContainer;
    }


    /**
     * Start the web container
     */
    public void startInstance() throws ServerLifecycleException {
        _logger.log(Level.INFO, "pewebcontainer.start");
        try {
            super.start();
         } catch (LifecycleException le) {

             // check if there is an embedded exception, if so, throw that
             Throwable ex = le.getThrowable();
             if (ex == null)
                 ex = le;

             String msg = _rb.getString("webcontainer.startError");
             throw new ServerLifecycleException(msg, ex);
        }

        // the server has started up, now enable monitoring.
        enableVirtualServerMonitoring();
        enableHttpMonitoring();
    }


    /**
     * Stop the web container 
     */
    public void stopInstance() throws ServerLifecycleException {
        _logger.log(Level.INFO, "pewebcontainer.stop");
        try {
            super.stop();
        } catch (LifecycleException le) {
            // check if there is an embedded exception, if so, throw that
            Throwable ex = le.getThrowable();
            if (ex == null)
                ex = le;

            String msg = _rb.getString("webcontainer.stopError");
            throw new ServerLifecycleException(msg, ex);
        }
    }


    public static PEWebContainer getPEWebContainer() {         
        return (PEWebContainer)webContainer;
    }
 

    /*
     * Configures the given virtual server with the access log specified in 
     * the domain.xml.
     *
     * @param vs The virtual server instance
     * @param vsId The virtual server id
     * @param vsBean The virtual server bean containing info about the access
     * log
     */
    protected void setAccessLog(
                StandardHost vs,
                String vsId,
                com.sun.enterprise.config.serverbeans.VirtualServer vsBean,
                HttpService httpService) {

        AccessLog accessLogConfig = httpService.getAccessLog();

        /*
         * Determine the virtual server's access log directory, which may be
         * specified in two places:
         *
         * 1.  <virtual-server>
         *       <http-access-log log-directory="..."/>
         *     </virtual-server>
         *
         * 2.  <virtual-server>
         *       <property name="accesslog" value="..."/>
         *     </virtual-server>
         *
         * If both have been specified, the latter takes precedence.
         */
        String accessLog = "access";
        if ( vsBean.getElementPropertyByName("accesslog") != null) {
            accessLog = vsBean.getElementPropertyByName("accesslog").getValue();
        } else if (vsBean.getHttpAccessLog() != null) {
            accessLog = vsBean.getHttpAccessLog().getLogDirectory();
        }
        if (accessLog == null) {
            return;
        }

        File dir = new File(accessLog);
        if (!dir.isAbsolute()) {
            /*
             * If accesslog is relative, turn it into an absolute path by
             * prepending log-root of domain element
             */
            String logRoot = domain.getLogRoot();
            if (logRoot != null) {
                dir = new File(logRoot, accessLog);
            } else {
                dir = new File(instance.getInstancesRoot(), accessLog);
            }
        }
            
        if (_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE,
                        "Setting accesslog directory for virtual "
                        + "server '" + vsId + "' to "
                        + dir.getAbsolutePath());
	}

        WebContainerFeatureFactory fac = _serverContext.
            getPluggableFeatureFactory().getWebContainerFeatureFactory();
        PEAccessLogValve accessLogValve = new PEAccessLogValve();
        accessLogValve.setDirectory(dir.getAbsolutePath());
        accessLogValve.setPrefix(vsId + fac.getDefaultAccessLogPrefix());
        accessLogValve.setPattern("common");

        HttpProtocol httpProtocol = httpService.getHttpProtocol();
        if (httpProtocol != null) {
            accessLogValve.setResolveHosts(httpProtocol.isDnsLookupEnabled());
        } else {
            accessLogValve.setResolveHosts(false);
        }

        // access-log format
        String format = null;
        if (accessLogConfig != null) {
            format = accessLogConfig.getFormat();
        } else {
	    format = AccessLog.getDefaultFormat();
        }
        accessLogValve.setPattern(format);
                       
        // rotation-enabled
        if (accessLogConfig != null) {
            accessLogValve.setRotatable(accessLogConfig.isRotationEnabled());
        } else {
	    accessLogValve.setRotatable(
                Boolean.valueOf(AccessLog.getDefaultRotationEnabled()).booleanValue());
        }

        // rotation-interval
        int rotationInterval = 0;
        if (accessLogConfig != null) {
            String s = accessLogConfig.getRotationIntervalInMinutes();
            rotationInterval = Integer.parseInt(s) * 60;
        } else if (vsBean.getElementPropertyByName("accessLogWriteInterval") != null
                    || accessLogWriteInterval != null) {
            if (vsBean.getElementPropertyByName("accessLogWriteInterval") != null) {
                accessLogWriteInterval = vsBean.getElementPropertyByName(
                    "accessLogWriteInterval").getValue();
            }
            if (accessLogWriteInterval != null) {
                rotationInterval = Integer.parseInt(accessLogWriteInterval);
            }
        } else {
            rotationInterval = fac.getDefaultRotationIntervalInMinutes() * 60;
        }
        accessLogValve.setWriterInterval(rotationInterval);
         
        // buffer-size       
        if ( vsBean.getElementPropertyByName("accessLogBufferSize") != null){
            accessLogBufferSize =  vsBean.getElementPropertyByName
                ("accessLogBufferSize").getValue();
        }
        if (accessLogBufferSize != null){
            accessLogValve.setBufferSize(
                Integer.parseInt(accessLogBufferSize)); 
        }

        // rotation-datestamp
        String rotationDateStamp = null;
        if (accessLogConfig != null) {
            rotationDateStamp = accessLogConfig.getRotationSuffix();
        } else {
            rotationDateStamp = fac.getDefaultAccessLogDateStampPattern();
        }
        if ("%YYYY;%MM;%DD;-%hh;h%mm;m%ss;s".equals(rotationDateStamp)) {
            /*
             * Modify the default rotation suffix pattern specified in the
             * sun-domain DTD in such a way that it is accepted by
             * java.text.SimpleDateFormat. We support only those patterns
             * accepted by java.text.SimpleDateFormat.
             */
            rotationDateStamp = "yyyyMMdd-HH'h'mm'm'ss's'";
        }
        accessLogValve.setFileDateFormat(rotationDateStamp);

        // rotation-suffix
        accessLogValve.setSuffix(fac.getDefaultAccessLogSuffix());

        accessLogValve.setAddDateStampToFirstAccessLogFile(
            fac.getAddDateStampToFirstAccessLogFile());

        vs.addValve(accessLogValve);
    }


    /*
     * Configures the given connector.
     *
     * @param connector The connector to configure
     * @param httpListener The http-listener that corresponds to the given
     * connector
     * @param isSecure true if the connector is security-enabled, false
     * otherwise
     * @param httpServiceProps The http-service properties
     */
    private void configureConnector(PECoyoteConnector connector,
                                    HttpListener httpListener,
                                    boolean isSecure,
                                    HttpService httpService) {

        configureConnectionPool(connector, httpService.getConnectionPool());

        WebContainerFeatureFactory wcFeatureFactory = _serverContext.
            getPluggableFeatureFactory().getWebContainerFeatureFactory();
        String sslImplementationName = 
            wcFeatureFactory.getSSLImplementationName();
        
        if (sslImplementationName != null) {
            connector.setProperty("sSLImplementation",sslImplementationName);
        }
        
        connector.setDomain(_serverContext.getDefaultDomainName());
        connector.setLogger(_logger);         
        
        configureSSL(connector, httpListener);
        configureKeepAlive(connector, httpService.getKeepAlive());
        configureHttpProtocol(connector, httpService.getHttpProtocol());     
        configureRequestProcessing(httpService.getRequestProcessing(),connector);
        configureFileCache(connector, httpService.getHttpFileCache());
        
        // default-virtual-server
        connector.setDefaultHost(httpListener.getDefaultVirtualServer());

        // xpoweredBy
        connector.setXpoweredBy(httpListener.isXpoweredBy());

        // enabled/disabled
        connector.setIsEnabled(httpListener.isEnabled());
        
        // Application root
        connector.setWebAppRootPath(getModulesRoot());
        
        // server-name (may contain scheme and colon-separated port number)
        String serverName = httpListener.getServerName();
        if (serverName != null && serverName.length() > 0) {
            // Ignore scheme, which was required for webcore issued redirects
            // in 8.x EE
            if (serverName.startsWith("http://")) {
                serverName = serverName.substring("http://".length());
            } else if (serverName.startsWith("https://")) {
                serverName = serverName.substring("https://".length());
            }
            int index = serverName.indexOf(':');
            if (index != -1) {
                connector.setProxyName(serverName.substring(0, index).trim());
                String serverPort = serverName.substring(index+1).trim();
                if (serverPort.length() > 0) {
                    try {
                        connector.setProxyPort(Integer.parseInt(serverPort));
                    } catch (NumberFormatException nfe) {
                        _logger.log(Level.SEVERE,
                            "pewebcontainer.invalid_proxy_port",
                            new Object[] { serverPort, httpListener.getId() });
		    }
                }
            } else {
                connector.setProxyName(serverName);
            }
        }

        // redirect-port
        String redirectPort = httpListener.getRedirectPort();
        if (redirectPort != null && !redirectPort.equals("")) {
            try {
                connector.setRedirectPort(Integer.parseInt(redirectPort));
            } catch (NumberFormatException nfe) {
                _logger.log(Level.WARNING,
                    "pewebcontainer.invalid_redirect_port",
                    new Object[] {
                        redirectPort,
                        httpListener.getId(),
                        Integer.toString(connector.getRedirectPort()) });
            }  
        } else {
            connector.setRedirectPort(-1);
        }

        // acceptor-threads
        String acceptorThreads = httpListener.getAcceptorThreads();
        if (acceptorThreads != null) {
            try {
                connector.setSelectorReadThreadsCount
                    (Integer.parseInt(acceptorThreads));
            } catch (NumberFormatException nfe) {
                _logger.log(Level.WARNING,
                    "pewebcontainer.invalid_acceptor_threads",
                    new Object[] {
                        acceptorThreads,
                        httpListener.getId(),
                        Integer.toString(connector.getMaxProcessors()) });
            }  
        }
        
        // Configure Connector with keystore password and location
        if (isSecure) {

            // Get keystore location
            String ksFile = System.getProperty("javax.net.ssl.keyStore");
            if (ksFile != null) {
                connector.setKeystoreFile(ksFile);
            }

            /*
             * Get keystore password from password.conf file.
             * Notice that JSSE, the underlying SSL implementation,
             * currently does not support individual key entry passwords
             * that are different from the keystore password.
             */
            String ksPasswd = null;
            try {
                ksPasswd = PasswordConfReader.getKeyStorePassword();
            } catch (IOException ioe) {
                // Ignore
            }

            // Get keystore password from system property
            if (ksPasswd == null) {
                ksPasswd = System.getProperty(
                    "javax.net.ssl.keyStorePassword");
            }

            if (ksPasswd != null) {
                try {
                    connector.setKeystorePass(ksPasswd);
                } catch (Exception e) {
                    _logger.log(Level.SEVERE,
                        "pewebcontainer.http_listener_keystore_password_exception",
                        e);
                }
            }
        }

        // Configure Connector with <http-listener> properties
        // START S1AS8 4861933
        ElementProperty prop = httpListener.getElementPropertyByName(
                                                        "chunkingDisabled");
        if (prop == null) {
            prop = httpListener.getElementPropertyByName("chunking-disabled");
        }
        if (prop != null) {
            connector.setChunkingDisabled(
                ConfigBean.toBoolean(prop.getValue()));
        }
        // END S1AS8 4861933
        
        configureHttpServiceProperties(httpService,connector);      
        // Override http-service property if defined.
        configureHttpListenerProperties(httpListener,connector);

    }

    
    /**
     * Configure http-listener properties
     */
    public void configureHttpListenerProperties(HttpListener httpListener,
                                                PECoyoteConnector connector){
        // Configure Connector with <http-service> properties
        ElementProperty[] httpListenerProps = httpListener.getElementProperty();
        if (httpListenerProps != null) {
            for (int i=0; i< httpListenerProps.length; i++) {
                String propName = httpListenerProps[i].getName();
                String propValue = httpListenerProps[i].getValue();
                if (!configureHttpListenerProperty(propName,
                                                   propValue,
                                                   connector)){
                    _logger.log(Level.WARNING,
                        "pewebcontainer.invalid_http_listener_property",
                        propName);                    
                }
            }
        }    
    }        
       
    
    /**
     * Configure http-listener property.
     * return true if the property exists and has been set.
     */
    protected boolean configureHttpListenerProperty(
                                            String propName, 
                                            String propValue,
                                            PECoyoteConnector connector){
                                                        
        if ("bufferSize".equals(propName)) {
            connector.setBufferSize(Integer.parseInt(propValue)); 
            return true; 
        } else if ("recycle-objects".equals(propName)) {
            connector
                .setRecycleObjects(ConfigBean.toBoolean(propValue));
            return true;
        } else if ("reader-threads".equals(propName)) {
            connector
                .setMaxReadWorkerThreads(Integer.parseInt(propValue));
            return true;            
        } else if ("acceptor-queue-length".equals(propName)) {
            connector
                .setMinAcceptQueueLength(Integer.parseInt(propValue));
            return true;            
        } else if ("reader-queue-length".equals(propName)) {
            connector
                .setMinReadQueueLength(Integer.parseInt(propValue));
            return true;            
        } else if ("use-nio-direct-bytebuffer".equals(propName)) {
            connector
                .setUseDirectByteBuffer(ConfigBean.toBoolean(propValue));
            return true;   
        } else if ("maxKeepAliveRequests".equals(propName)) {
            connector
                .setMaxKeepAliveRequests(Integer.parseInt(propValue));
            return true;           
        } else if ("reader-selectors".equals(propName)) {
            connector
                .setSelectorReadThreadsCount(Integer.parseInt(propValue));
            return true;
        } else if ("authPassthroughEnabled".equals(propName)) {
            connector.setAuthPassthroughEnabled(
                                        ConfigBean.toBoolean(propValue));
            return true;
        } else if ("proxyHandler".equals(propName)) {
            setProxyHandler(connector, propValue);
            return true;
        } else {
            return false;
        }   
    }   
    

    /**
     * Configure http-service properties.
     */
    public void configureHttpServiceProperties(HttpService httpService,
                                               PECoyoteConnector connector){
        // Configure Connector with <http-service> properties
        ElementProperty[] httpServiceProps = httpService.getElementProperty();

        // Set default ProxyHandler impl, may be overriden by
        // proxyHandler property
        connector.setProxyHandler(new ProxyHandlerImpl());

        if (httpServiceProps != null) {
            for (int i=0; i<httpServiceProps.length; i++) {
                String propName = httpServiceProps[i].getName();
                String propValue = httpServiceProps[i].getValue();
                               
                if (configureHttpListenerProperty(propName,
                                                  propValue, 
                                                  connector)){
                    continue;
                }
                
                if ("connectionTimeout".equals(propName)) {
                    connector.setConnectionTimeout(
                                                Integer.parseInt(propValue));
                } else if ("tcpNoDelay".equals(propName)) {
                    connector.setTcpNoDelay(ConfigBean.toBoolean(propValue));
                } else if ("traceEnabled".equals(propName)) {
                    connector.setAllowTrace(ConfigBean.toBoolean(propValue));
                } else if ("accessLoggingEnabled".equals(propName)) {
                    installAccessLogValve = ConfigBean.toBoolean(propValue);
                } else if ("accessLogWriteInterval".equals(propName)) {
                    accessLogWriteInterval = propValue;
                } else if ("accessLogBufferSize".equals(propName)) {
                    accessLogBufferSize = propValue;
                } else if ("authPassthroughEnabled".equals(propName)) {
                    connector.setAuthPassthroughEnabled(
                                    ConfigBean.toBoolean(propValue));
                } else if ("ssl-session-timeout".equals(propName)) {
                    connector.setSSLSessionTimeout(propValue);
                } else if ("ssl3-session-timeout".equals(propName)) {
                    connector.setSSL3SessionTimeout(propValue);
                } else if ("ssl-cache-entries".equals(propName)) {
                    connector.setSSLSessionCacheSize(propValue);
                } else if ("proxyHandler".equals(propName)) {
                    setProxyHandler(connector, propValue);
                } else {
                    _logger.log(Level.WARNING,
                        "pewebcontainer.invalid_http_service_property",
                        httpServiceProps[i].getName());
                }
            }
        }    
    }


    /*
     * Parses the given comma-separated string of cipher suite names,
     * converts each cipher suite that is enabled (i.e., not preceded by a
     * '-') to the corresponding JSSE cipher suite name, and returns a string
     * of comma-separated JSSE cipher suite names.
     *
     * @param sslCiphers String of SSL ciphers to parse
     *
     * @return String of comma-separated JSSE cipher suite names, or null if
     * none of the cipher suites in the given string are enabled or can be
     * mapped to corresponding JSSE cipher suite names
     */
    private String getJSSECiphers(String ciphers) {

        String cipher = null;
        StringBuffer enabledCiphers = null;
        boolean first = true;

        int index = ciphers.indexOf(',');
        if (index != -1) {
            int fromIndex = 0;
            while (index != -1) {
                cipher = ciphers.substring(fromIndex, index).trim();
                if (cipher.length() > 0 && !cipher.startsWith("-")) {
                    if (cipher.startsWith("+")) {
                        cipher = cipher.substring(1);
		    }
                    String jsseCipher = getJSSECipher(cipher);
                    if (jsseCipher == null) {
                        _logger.log(Level.WARNING,
                            "pewebcontainer.unrecognized_cipher", cipher);
                    } else {
                        if (enabledCiphers == null) {
                            enabledCiphers = new StringBuffer();
                        }
                        if (!first) {
                            enabledCiphers.append(", ");
                        } else {
                            first = false;
                        }
                        enabledCiphers.append(jsseCipher);
                    }
                }
                fromIndex = index + 1;
                index = ciphers.indexOf(',', fromIndex);
            }
            cipher = ciphers.substring(fromIndex);
        } else {
            cipher = ciphers;
        }

        if (cipher != null) {
            cipher = cipher.trim();
            if (cipher.length() > 0 && !cipher.startsWith("-")) {
                if (cipher.startsWith("+")) {
                    cipher = cipher.substring(1);
                }
                String jsseCipher = getJSSECipher(cipher);
                if (jsseCipher == null) {
                    _logger.log(Level.WARNING,
                                "pewebcontainer.unrecognized_cipher", cipher);
                } else {
                    if (enabledCiphers == null) {
                        enabledCiphers = new StringBuffer();
                    }
                    if (!first) {
                        enabledCiphers.append(", ");
                    } else {
                        first = false;
                    }
                    enabledCiphers.append(jsseCipher);
                }
            }
        }

        return (enabledCiphers == null ? null : enabledCiphers.toString());
    }


    /*
     * Converts the given cipher suite name to the corresponding JSSE cipher.
     *
     * @param cipher The cipher suite name to convert
     *
     * @return The corresponding JSSE cipher suite name, or null if the given
     * cipher suite name can not be mapped
     */
    private String getJSSECipher(String cipher) {

        String jsseCipher = null;

        CipherInfo ci = CipherInfo.getCipherInfo(cipher);
        if( ci != null ) {
            jsseCipher = ci.getCipherName();
        }

        return jsseCipher;
    }
    

    /**
     * Registers various HTTP related monitoring stats.
     */
    private void enableHttpMonitoring(){

        String vsId;
        int port;
        HttpService httpService;
        
        ServerContext sc = getServerContext();
        ConfigContext cc = sc.getConfigContext();
        MonitoringRegistry mReg = sc.getMonitoringRegistry();
        try {
            httpService = ServerBeansFactory.getHttpServiceBean(cc);
        } catch(ConfigException ce) {
            _logger.log(Level.WARNING,
                        "Unable to find HttpServiceBean in config",
                        ce);
            return;
        }

        // keep-alive
        try {
            mReg.registerPWCKeepAliveStats(
                new PWCKeepAliveStatsImpl(sc.getDefaultDomainName()),
                null);
        } catch (MonitoringRegistrationException mre) {
            String msg = _logger.getResourceBundle().getString(
                                            "web.monitoringRegistrationError");
            msg = MessageFormat.format(msg,
                                       new Object[] { "PWCKeepAliveStats" });
            _logger.log(Level.WARNING, msg, mre);
        }
        
        // file-cache
        try {
            mReg.registerPWCFileCacheStats(
                new PWCFileCacheStatsImpl(sc.getDefaultDomainName()),null);
        } catch (MonitoringRegistrationException mre) {
            String msg = _logger.getResourceBundle().getString(
                                            "web.monitoringRegistrationError");
            msg = MessageFormat.format(msg,
                                       new Object[] { "PWCFileCacheStats" });
            _logger.log(Level.WARNING, msg, mre);
        }

        // pwc-thread-pool
        try {
            mReg.registerPWCThreadPoolStats(
                new PWCThreadPoolStatsImpl(sc.getDefaultDomainName()),
                null);
        } catch (MonitoringRegistrationException mre) {
            String msg = _logger.getResourceBundle().getString(
                                            "web.monitoringRegistrationError");
            msg = MessageFormat.format(msg,
                                       new Object[] { "PWCThreadPoolStats" });
            _logger.log(Level.WARNING, msg, mre);
        }

        // connection-queue
        try {
            mReg.registerPWCConnectionQueueStats(
                new PWCConnectionQueueStatsImpl(sc.getDefaultDomainName()),
                null);
        } catch (MonitoringRegistrationException mre) {
            String msg = _logger.getResourceBundle().getString(
                                            "web.monitoringRegistrationError");
            msg = MessageFormat.format(msg,
                                       new Object[] { "PWCConnectionQueueStats" });
            _logger.log(Level.WARNING, msg, mre);
        }

        com.sun.enterprise.config.serverbeans.VirtualServer[] vs
                                            = httpService.getVirtualServer();

        HttpListener currentListener; 
        for(int i = 0; i<vs.length ; i++) {				
            vsId = vs[i].getId();
            if(!vsId.equalsIgnoreCase(ADMIN_VS)) {
               
                VirtualServer virtualServer = 
                        (VirtualServer)getEngines()[0].findChild(vsId);

                if (virtualServer == null){
                    _logger.log(Level.WARNING,
                                "Invalid virtual-server: " + vsId);
                    continue;
                }
                List listeners = StringUtils.parseStringList(
                                            vs[i].getHttpListeners(), ",");
                if(listeners != null) {
                    ListIterator iter = listeners.listIterator();
                    while(iter.hasNext()) {
                       
                        currentListener = httpService.getHttpListenerById
                                                        (iter.next().toString());

			if (currentListener == null)
			    continue;
                        
                        enableHttpListenerMonitoring(virtualServer, 
                                Integer.parseInt(currentListener.getPort()),
                                currentListener.getId());
                    }
                }
            }
        }
    }
 
    
    /**
     * Register http-listener monitoring statistics.
     */
    protected void enableHttpListenerMonitoring(VirtualServer virtualServer,
            int port, String httpListenerId){
            
        PWCRequestStatsImpl pwcRequestStatsImpl = 
                virtualServer.getPWCRequestStatsImpl();
        
        if ( pwcRequestStatsImpl == null ){
            pwcRequestStatsImpl = new PWCRequestStatsImpl(
                    getServerContext().getDefaultDomainName());
            virtualServer.setPWCRequestStatsImpl(pwcRequestStatsImpl);
        }
 
        HTTPListenerStatsImpl httpStats;
        MonitoringRegistry mReg = getServerContext().getMonitoringRegistry();
        String vsId = virtualServer.getID();
        
        if (isTomcatUsingDefaultDomain()) {
            httpStats = new HTTPListenerStatsImpl(
                    getServerContext().getDefaultDomainName(),port);
        } else {
            httpStats = new HTTPListenerStatsImpl(vsId,port);
        }

        try {
            mReg.registerHttpListenerStats(httpStats, httpListenerId, vsId, null);
            pwcRequestStatsImpl.addHttpListenerStats(httpStats);
        } catch (MonitoringRegistrationException mre) {
            String msg =
                _logger.getResourceBundle().getString(
                    "web.monitoringRegistrationError");
            msg = MessageFormat.format(
                    msg,
                    new Object[] { "HTTPListenerStats" });
            _logger.log(Level.WARNING, msg, mre);
        }        
    }
    

    /*
     * Ensures that the host names of all virtual servers associated with the
     * HTTP listener with the given listener id are unique.
     *
     * @param listenerId The id of the HTTP listener whose associated virtual
     * servers are checked for uniqueness of host names
     * @param httpService The http-service element whose virtual servers are
     * checked
     */
    private void checkHostnameUniqueness(String listenerId,
                                         HttpService httpService) {

        ArrayList listenerVses = null;

        com.sun.enterprise.config.serverbeans.VirtualServer[] vses
            = httpService.getVirtualServer();
        if (vses == null) {
            return;
        }

        // Determine all the virtual servers associated with the given listener
        for (int i=0; i<vses.length; i++) {
            List vsListeners =
                StringUtils.parseStringList(vses[i].getHttpListeners(), ",");
            for (int j=0; vsListeners!=null && j<vsListeners.size(); j++) {
                if (listenerId.equals((String)vsListeners.get(j))) {
                    if (listenerVses == null) {
                        listenerVses = new ArrayList();
                    }
                    listenerVses.add(vses[i]);
                    break;
                }
            }
        }
        if (listenerVses == null) {
            return;
        }

        for (int i=0; i<listenerVses.size(); i++) {
            com.sun.enterprise.config.serverbeans.VirtualServer vs
                = (com.sun.enterprise.config.serverbeans.VirtualServer) listenerVses.get(i);
            List hosts = StringUtils.parseStringList(vs.getHosts(), ",");
            for (int j=0; hosts!=null && j<hosts.size(); j++) {
                String host = (String) hosts.get(j);
                for (int k=0; k<listenerVses.size(); k++) {
                    if (k <= i) {
                        continue;
                    }
                    com.sun.enterprise.config.serverbeans.VirtualServer otherVs
                        = (com.sun.enterprise.config.serverbeans.VirtualServer)
                            listenerVses.get(k);
                    List otherHosts = StringUtils.parseStringList(otherVs.getHosts(), ",");
                    for (int l=0; otherHosts!=null && l<otherHosts.size(); l++) {
                        if (host.equals((String) otherHosts.get(l))) {
                            _logger.log(Level.SEVERE,
                                        "pewebcontainer.duplicate_host_name",
                                        new Object[] { host, vs.getId(),
                                                       otherVs.getId(),
                                                       listenerId });
                        }
                    }
		}    
            }        
        }            
    }


    /**
     * is Tomcat using default domain name as its domain
     */
    protected boolean isTomcatUsingDefaultDomain() {
        // need to be careful and make sure tomcat jmx mapping works
        // since setting this to true might result in undeployment problems
        return true;
    }


    /**
     * Overrides the implementation of this method in the WebContainer.java
     * superclass by doing nothing.
     *
     * This is to prevent web modules that are bundled in EARs from being
     * loaded twice during startup, since they are already being loaded by
     * com.sun.enterprise.server.TomcatApplicationLoader.load(). See 4925655.
     */
    protected void loadAllJ2EEApplicationWebModules() {
        // Do nothing
    }
    
    public static WebContainer getInstance(){
        return webContainer;
    }


    /*
     * Enables monitoring of all virtual servers.
     */
    private void enableVirtualServerMonitoring() {
        Engine[] engines = _embedded.getEngines();
        for (int j = 0; j < engines.length; j++) {
            Container[] hostArray = engines[j].findChildren();
            for (int i = 0; i < hostArray.length; i++) {
                VirtualServer vs = (VirtualServer) hostArray[i];
                enableVirtualServerMonitoring(vs);            
            }
        }
    }

    
    /*
     * Enables monitoring of all virtual servers.
     */
    private void enableVirtualServerMonitoring(VirtualServer vs){        
        ServerContext sc = getServerContext();
        ConfigContext cc = sc.getConfigContext();
        MonitoringRegistry monitoringRegistry = sc.getMonitoringRegistry();
        
        PWCVirtualServerStatsImpl vsStats = new PWCVirtualServerStatsImpl(vs);
        try {
            monitoringRegistry.registerPWCVirtualServerStats(vsStats,
                                                             vs.getID(),
                                                             null);
        } catch (Exception e) {
            _logger.log(Level.WARNING,
                        "Unable to register PWCVirtualServerStats for "
                        + vs.getID(), e);
        }

        PWCRequestStatsImpl pwcRequestStatsImpl = 
                new PWCRequestStatsImpl(sc.getDefaultDomainName());  
        vs.setPWCRequestStatsImpl(pwcRequestStatsImpl);
        
        try {
            monitoringRegistry.registerPWCRequestStats(pwcRequestStatsImpl,
                        vs.getID(),
                        null);
        } catch (MonitoringRegistrationException mre) {
            String msg = _logger.getResourceBundle().getString(
                            "web.monitoringRegistrationError");
            msg = MessageFormat.format(
                            msg,
                            new Object[] { "PWCRequestStats" });
            _logger.log(Level.WARNING, msg, mre);
        }
    }
    
    /*
     * Configures the SSL properties on the given PECoyoteConnector from the
     * SSL config of the given HTTP listener.
     *
     * @param connector PECoyoteConnector to configure
     * @param httpListener HTTP listener whose SSL config to use
     */
    private void configureSSL(PECoyoteConnector connector,
                              HttpListener httpListener) {

        Ssl sslConfig = httpListener.getSsl();
        if (sslConfig == null) {
            return;
        }

        // client-auth
        if (sslConfig.isClientAuthEnabled()) {
            connector.setClientAuth(true);
        }

        // ssl protocol variants
        StringBuffer sslProtocolsBuf = new StringBuffer();
        boolean needComma = false;
        if (sslConfig.isSsl2Enabled()) {
            sslProtocolsBuf.append("SSLv2");
            needComma = true;
        }
        if (sslConfig.isSsl3Enabled()) {
            if (needComma) {
                sslProtocolsBuf.append(", ");
            } else {
                needComma = true;
            }
            sslProtocolsBuf.append("SSLv3");
        }
        if (sslConfig.isTlsEnabled()) {
            if (needComma) {
                sslProtocolsBuf.append(", ");
            }
            sslProtocolsBuf.append("TLSv1");
        }
        if (sslConfig.isSsl3Enabled() || sslConfig.isTlsEnabled()) {
            sslProtocolsBuf.append(", SSLv2Hello");
        }

        if (sslProtocolsBuf.length() == 0) {
            _logger.log(Level.WARNING,
                        "pewebcontainer.all_ssl_protocols_disabled",
                        httpListener.getId());
        } else {
            connector.setSslProtocols(sslProtocolsBuf.toString());
        }

        // cert-nickname
        String certNickname = sslConfig.getCertNickname();
        if (certNickname != null && certNickname.length() > 0) {
            connector.setKeyAlias(sslConfig.getCertNickname());
        }

        // ssl3-tls-ciphers
        String ciphers = sslConfig.getSsl3TlsCiphers();
        if (ciphers != null) {
            String jsseCiphers = getJSSECiphers(ciphers);
            if (jsseCiphers == null) {
                _logger.log(Level.WARNING,
                            "pewebcontainer.all_ciphers_disabled",
                            httpListener.getId());
            } else {
                connector.setCiphers(jsseCiphers);
            }
        }            
    }


    /*
     * Configures the keep-alive properties on the given PECoyoteConnector
     * from the given keep-alive config.
     *
     * @param connector PECoyoteConnector to configure
     * @param keepAlive Keep-alive config to use
     */
    private void configureKeepAlive(PECoyoteConnector connector,
                                    KeepAlive keepAlive) {

        // timeout-in-seconds, default is 60 as per sun-domain_1_1.dtd
        int timeoutInSeconds = 60;

        // max-connections, default is 256 as per sun-domain_1_1.dtd
        int maxConnections = 256;

        // thread-count, default is 1 as per sun-domain_1_1.dtd
        int threadCount = 1;

        if (keepAlive != null) {
            // timeout-in-seconds
            try {
	        timeoutInSeconds = Integer.parseInt(
                                keepAlive.getTimeoutInSeconds());
            } catch (NumberFormatException ex) {
                String msg = _rb.getString(
                    "pewebcontainer.invalidKeepAliveTimeout");
                msg = MessageFormat.format(
                    msg,
                    new Object[] { keepAlive.getTimeoutInSeconds(),
                                   Integer.toString(timeoutInSeconds)});
                _logger.log(Level.WARNING, msg, ex);
            }

            // max-connections
            try {
	        maxConnections = Integer.parseInt(
                                keepAlive.getMaxConnections());
            } catch (NumberFormatException ex) {
                String msg = _rb.getString(
                    "pewebcontainer.invalidKeepAliveMaxConnections");
                msg = MessageFormat.format(
                    msg,
                    new Object[] { keepAlive.getMaxConnections(),
                                   Integer.toString(maxConnections)});
                _logger.log(Level.WARNING, msg, ex);
            }

            // thread-count
            try {
	        threadCount = Integer.parseInt(keepAlive.getThreadCount());
            } catch (NumberFormatException ex) {
                String msg = _rb.getString(
                    "pewebcontainer.invalidKeepAliveThreadCount");
                msg = MessageFormat.format(
                    msg,
                    new Object[] { keepAlive.getThreadCount(),
                                   Integer.toString(threadCount)});
                _logger.log(Level.WARNING, msg, ex);
            }
        }
        
        connector.setKeepAliveTimeoutInSeconds(timeoutInSeconds);
        connector.setMaxKeepAliveRequests(maxConnections);
        connector.setKeepAliveThreadCount(threadCount);
    }
    

    /*
     * Configures the given HTTP connector with connection-pool related info.
     */
    private void configureConnectionPool(PECoyoteConnector connector,
                                         ConnectionPool cp) {
        if (cp == null) {
            return;
        }
            
        try{
            int queueSizeInBytes = 
                    Integer.parseInt(cp.getQueueSizeInBytes());
            if (queueSizeInBytes <= -1){
                _logger.log(
                    Level.WARNING,
                    "pewebcontainer.invalidQueueSizeInBytes",
                    new Object[] 
                        { cp.getQueueSizeInBytes(),
                          Integer.toString(
                                  connector.getQueueSizeInBytes())});
            } else {
                connector.setQueueSizeInBytes(queueSizeInBytes);
            }
        } catch (NumberFormatException ex){
            String msg = _rb.getString("pewebcontainer.invalidQueueSizeInBytes");
            msg = MessageFormat.format(
                msg, new Object[] 
                    { cp.getDefaultQueueSizeInBytes(),
                      Integer.toString(connector.getQueueSizeInBytes())});
            _logger.log(Level.WARNING, msg, ex);
        }
        
        
        try{
            int ssBackLog = Integer.parseInt(cp.getMaxPendingCount());
            if (ssBackLog <= 0){
                _logger.log(
                    Level.WARNING,
                    "pewebcontainer.invalidMaxPendingCount",
                    new Object[] 
                        { cp.getMaxPendingCount(),
                          Integer.toString(connector.getSocketServerBacklog())});
            } else {
                connector.setSocketServerBacklog(ssBackLog);
            }
        } catch (NumberFormatException ex){
            String msg = _rb.getString("pewebcontainer.invalidMaxPendingCount");
            msg = MessageFormat.format(
                msg, new Object[] 
                    { cp.getMaxPendingCount(),
                      Integer.toString(connector.getSocketServerBacklog())});
            _logger.log(Level.WARNING, msg, ex);
        }
        
        
        try{
            int bufferSize = 
                        Integer.parseInt(cp.getReceiveBufferSizeInBytes());
            if ( bufferSize <= 0 ){
                _logger.log(
                    Level.WARNING,
                    "pewebcontainer.invalidBufferSize",
                    new Object[] 
                        { cp.getReceiveBufferSizeInBytes(),
                          Integer.toString(connector.getBufferSize())});
            } else {
                connector.setBufferSize(bufferSize);
            }
        } catch (NumberFormatException ex) {
            String msg = _rb.getString("pewebcontainer.invalidBufferSize");
            msg = MessageFormat.format(
                msg, new Object[] 
                    { cp.getReceiveBufferSizeInBytes(),
                      Integer.toString(connector.getBufferSize())});
            _logger.log(Level.WARNING, msg, ex);
        }

        try{
            int maxHttpHeaderSize = 
                          Integer.parseInt(cp.getSendBufferSizeInBytes());
            if ( maxHttpHeaderSize <= 0 ){
                _logger.log(
                    Level.WARNING,
                    "pewebcontainer.invalidMaxHttpHeaderSize",
                    new Object[] 
                        { cp.getSendBufferSizeInBytes(),
                          Integer.toString(connector.getMaxHttpHeaderSize())});
            } else {
                connector.setMaxHttpHeaderSize(maxHttpHeaderSize);
            }
        } catch (NumberFormatException ex){
            String msg = _rb.getString(
                "pewebcontainer.invalidMaxHttpHeaderSize");
            msg = MessageFormat.format(
                msg, new Object[] 
                    { cp.getSendBufferSizeInBytes(),
                      Integer.toString(connector.getMaxHttpHeaderSize())});
            _logger.log(Level.WARNING, msg, ex);
        }
    }


    /** 
     * Registers listeners for dynamic reconfiguration with the
     * AdminEventListenerRegistry
     */
    public static void registerReconfigListeners(){
        AdminEventListenerRegistry.addEventListener(
                                    HSVirtualServerEvent.eventType, 
                                    new VirtualServerReconfig()); 
        AdminEventListenerRegistry.addEventListener(
                                    HSHttpListenerEvent.eventType, 
                                    new HttpListenerReconfig());  
    }
    
    
    /** 
     * Delete virtual-server.
     * @param httpService element which contains the configuration info.
     */
    public void deleteHost(HttpService httpService) throws LifecycleException{
    
        Engine[] engines = _embedded.getEngines();
        VirtualServer virtualServer;
        com.sun.enterprise.config.serverbeans.VirtualServer[] vses
            = httpService.getVirtualServer();        
        // First we need to find which virtual-server was deleted. In
        // reconfig/VirtualServerReconfig, it is impossible to lookup
        // the vsBean because the element is removed from domain.xml
        // before handleDelete is invoked.
        Container[] virtualServers = engines[0].findChildren();
        for (int i=0;i < virtualServers.length; i++){
            for (int j=0; j < vses.length; j++){
                if ( virtualServers[i].getName().equals(vses[j].getId())){
                    virtualServers[i] = null;
                    break;
                }
            }
        }       
        
        for (int i=0;i < virtualServers.length; i++){
            virtualServer = (VirtualServer)virtualServers[i];
            
            if (virtualServer != null ){           
                if (virtualServer.getID().equals(ADMIN_VS)) {
                    throw new 
                      LifecycleException("Cannot delete admin virtual-server.");
                }     

                Container[] webModules = virtualServer.findChildren();
                for (int j=0; j < webModules.length; j++){
                    unloadWebModule(webModules[j].getName(),
                                    webModules[j].getName(), 
                                    virtualServer.getID(),null);
                }
                virtualServer.stop();
                engines[0].removeChild(virtualServer);
            }
        }
    }
    
    
    /**
     * Updates a virtual-server element.
     *
     * @param vsBean the virtual-server config bean.
     * @param httpService element which contains the configuration info.
     */
    public void updateHost(
                    com.sun.enterprise.config.serverbeans.VirtualServer vsBean,
                    HttpService httpService,
                    Server serverBean) 
                    throws LifecycleException{

        Engine[] engines = _embedded.getEngines();
        VirtualServer virtualServer = 
                            (VirtualServer)engines[0].findChild(vsBean.getId());

        _embedded.setLogFile(virtualServer,vsBean.getLogFile());
        configureVirtualServerState(virtualServer,vsBean);
        
        virtualServer.clearAliases();
        configureHostAlias(virtualServer);

        // Set the ports with which this virtual server is associated
        List<String> listeners = StringUtils.parseStringList(
                                                vsBean.getHttpListeners(), ",");
        
        if (listeners != null) {
            HttpListener[] httpListeners = new HttpListener[listeners.size()];
            for (int i=0; i < listeners.size(); i++){
                httpListeners[i] = 
                            httpService.getHttpListenerById(listeners.get(i));
            }

            // Update listeners
            configureHostListener(virtualServer,httpListeners);       
       
            Connector[] connectors = _embedded.findConnectors();
            // If new connector where added, make sure it is started.
            PECoyoteConnector connector;
            for (int i = 0; i < connectors.length; i++) {
                connector = (PECoyoteConnector)connectors[i];
                // Already started.
                if (connector.isAvailable()){
                    continue;
                } 

                connector.start();
                enableHttpListenerMonitoring(virtualServer,
                        connector.getPort(),connector.getName());   
            }
        }
        
        String defaultWebModule = 
            virtualServer.getDefaultWebModuleID() == null 
                                   ? "": virtualServer.getDefaultWebModuleID();
        
        String configWebModule = vsBean.getDefaultWebModule();
        if ( configWebModule == null ){
            configWebModule = "";
        }
        
        if ( !defaultWebModule.equals(configWebModule)) {
            unloadWebModule("","", virtualServer.getID(),null);
            virtualServer.setBean(vsBean);  
            if (configWebModule != "" ) {
                loadDefaultWebModule(virtualServer, serverBean);
            } else {
                loadStandaloneWebModule(virtualServer,
                      virtualServer.createSystemDefaultWebModuleIfNecessary()); 
            }
        } 
    }
    
    
    /**
     * Update virtual-server properties.
     */
    public void updateHostProperties(
                    com.sun.enterprise.config.serverbeans.VirtualServer vsBean,
                    String name, 
                    String value,
                    HttpService httpService){
                        
        Engine[] engines = _embedded.getEngines();
        VirtualServer vs = (VirtualServer)engines[0].findChild(vsBean.getId());
        
        if ("docroot".equals(name)) {
            boolean isValid = validateDocroot(value,
                                              vsBean.getId(),
                                              vsBean.getDefaultWebModule());
            if ( isValid ) {
                vs.setAppBase(value);
            }
        } else if ("setCacheControl".equals(name)){
            configureCacheControl(value, vs);
        } else if ("accesslog".equals(name)){
            setAccessLog(vs, vsBean.getId(), vsBean, httpService);
        } else if ("allowRemoteHost".equals(name)
                || "denyRemoteHost".equals(name)) {
            configureRemoteHostFilterValve(vs, vsBean,
                                           httpService.getHttpProtocol());
        } else if ("allowRemoteAddress".equals(name)
                || "denyRemoteAddress".equals(name)) {
            configureRemoteAddressFilterValve(vs, vsBean);
        }       
    }
    
    
    /**
     * Update an http-listener property
     * @param httpListener the configuration bean.
     * @param propName the property name
     * @param propValue the property value
     */
    public void updateConnectorProperty(HttpListener httpListener,
                                        String propName,
                                        String propValue) 
        throws LifecycleException{
       
        PECoyoteConnector connector = connectorMap.get(httpListener.getId());
        
        configureHttpListenerProperty(propName,propValue,connector);
    }
    
    
    
    /**
     * Update an http-listener
     * @param httpService the configuration bean.
     */
    public void updateConnector(HttpListener httpListener,
                                HttpService httpService) throws LifecycleException{
       
        PECoyoteConnector connector = connectorMap.get(httpListener.getId());
        
        if ( connector == null ){
            throw new LifecycleException("Invalid http-listener:" 
                                                        + httpListener.getId());
        }
 
        int previousPort = connector.getPort();
        _embedded.removeConnector(connector);
        connectorMap.remove(httpListener.getId());
        
        connector = createConnector(httpListener, httpService);
        
        String virtualServerName = httpListener.getDefaultVirtualServer();
        VirtualServer virtualServer = (VirtualServer)
                     _embedded.getEngines()[0].findChild(virtualServerName);
        
        int[] ports = virtualServer.getPorts();
        for (int i=0; i < ports.length; i++){
            if (ports[i] == previousPort){
                ports[i] = connector.getPort();
            }
        }
        
        virtualServer.setPorts(ports);
        connector.start();
    }
    
    
    /**
     * Stop and delete the selected http-listener.
     * @param httpService the configuration bean.
     */
    public void deleteConnector(HttpService httpService) 
                                                    throws LifecycleException{
        HttpListener[] httpListeners = httpService.getHttpListener();
        HttpListener httpListener;
        Connector[] connectors = (Connector[])_embedded.findConnectors().clone();
                   
        for (int i=0; i < connectors.length; i++){        
            for (int j = 0; j < httpListeners.length; j++) {
                httpListener = (HttpListener)httpListeners[j];            
                if ( ((PECoyoteConnector)connectors[i]).getPort() 
                                  == Integer.parseInt(httpListener.getPort())){
                    connectors[i] = null;
                    break;
                }
            }            
        }
        
        for (int i=0; i < connectors.length; i++){
            if ( connectors[i] != null ){
                _embedded.removeConnector((PECoyoteConnector)connectors[i]);
            }
        }                  
    }  
    
   
    /*
     * Configures the given HTTP connector with the given http-protocol
     * config.
     *
     * @param connector HTTP connector to configure
     * @param httpProtocol http-protocol config to use
     */
    private void configureHttpProtocol(PECoyoteConnector connector,
                                       HttpProtocol httpProtocol) {
    
        if (httpProtocol == null) {
            return;
        }

        connector.setEnableLookups(httpProtocol.isDnsLookupEnabled());
        connector.setForcedResponseType(httpProtocol.getForcedResponseType());
        connector.setDefaultResponseType(httpProtocol.getDefaultResponseType());
    }
    
    
    /**
     * Configure the Grizzly FileCache mechanism
     */
    private void configureFileCache(PECoyoteConnector connector,
                                    HttpFileCache httpFileCache){
        if ( httpFileCache == null ) return;
        
        connector.setFileCacheEnabled(httpFileCache.isGloballyEnabled()); 
        connector.setLargeFileCacheEnabled(
              Boolean.parseBoolean(httpFileCache.getFileCachingEnabled()));
        connector.setSecondsMaxAge(
                Integer.parseInt(httpFileCache.getMaxAgeInSeconds()));
        connector.setMaxCacheEntries(
                Integer.parseInt(httpFileCache.getMaxFilesCount()));
        connector.setMinEntrySize(
            Integer.parseInt(httpFileCache.getSmallFileSizeLimitInBytes()));
        connector.setMaxEntrySize(
            Integer.parseInt(httpFileCache.getMediumFileSizeLimitInBytes()));
        connector.setMaxLargeCacheSize(
            Integer.parseInt(httpFileCache.getMediumFileSpaceInBytes()));
        connector.setMaxSmallCacheSize(
            Integer.parseInt(httpFileCache.getSmallFileSpaceInBytes())); 
    }
    
    
    /**
     * Configures all HTTP connector with the given request-processing
     * config.
     *
     * @param httpService http-service config to use
     */
    protected void configureRequestProcessing(HttpService httpService){

        RequestProcessing rp = httpService.getRequestProcessing();
        Connector[] connectors = (Connector[])_embedded.findConnectors();       
                    
        for (int i=0; i < connectors.length; i++){    
            configureRequestProcessing(rp,(PECoyoteConnector)connectors[i]);
        }
    }
    
    
    /**
     * Configures an HTTP connector with the given request-processing
     * config.
     *
     * @param RequestProcessing http-service config to use
     * @param connector the connector used.
     */
    protected void configureRequestProcessing(RequestProcessing rp, 
                                              PECoyoteConnector connector){
        if (rp == null) return;

        try{
            connector.setMaxProcessors(
                    Integer.parseInt(rp.getThreadCount()));
            connector.setMinProcessors(
                    Integer.parseInt(rp.getInitialThreadCount()));
            connector.setProcessorWorkerThreadsTimeout(
                    Integer.parseInt(rp.getRequestTimeoutInSeconds())); 
            connector.setProcessorWorkerThreadsIncrement(
                    Integer.parseInt(rp.getThreadIncrement()));
            connector.setMaxHttpHeaderSize(
                   Integer.parseInt(rp.getHeaderBufferLengthInBytes()));
        } catch (NumberFormatException ex){
            _logger.log(Level.WARNING, " Invalid request-processing attribute", 
                    ex);                      
        }             
    }


    /*
     * Loads and instantiates the ProxyHandler implementation
     * class with the specified name, and sets the instantiated 
     * ProxyHandler on the given connector.
     *
     * @param connector The HTTP connector to configure
     * @param className The ProxyHandler implementation class name
     */
    private void setProxyHandler(PECoyoteConnector connector,
                                 String className) {

        Object handler = null;
        try {
            Class handlerClass = Class.forName(className);
            handler = handlerClass.newInstance();
        } catch (Exception e) {
            String msg = _rb.getString(
                "pewebcontainer.proxyHandlerClassLoadError");
            msg = MessageFormat.format(msg, new Object[] { className });
            _logger.log(Level.SEVERE, msg, e);
        }
        if (handler != null) {
            if (!(handler instanceof ProxyHandler)) {
                _logger.log(
                    Level.SEVERE,
                    "pewebcontainer.proxyHandlerClassInvalid",
                    className);
            } else {
                connector.setProxyHandler((ProxyHandler) handler);
            }                
        } 
    }
}

