/*
 * 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.appclient.jws;

import com.sun.enterprise.appclient.AppClientInfo;
import com.sun.enterprise.appclient.Main;
import com.sun.enterprise.appclient.MainWithModuleSupport;
import com.sun.enterprise.config.ConfigBean;
import com.sun.enterprise.config.ConfigBeansFactory;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.serverbeans.AppclientModule;
import com.sun.enterprise.config.serverbeans.Applications;
import com.sun.enterprise.config.serverbeans.J2eeApplication;
import com.sun.enterprise.config.serverbeans.ServerHelper;
import com.sun.enterprise.config.serverbeans.ServerXPathHelper;
import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.ApplicationClientDescriptor;
import com.sun.enterprise.deployment.Descriptor;
import com.sun.enterprise.deployment.backend.DeployableObjectType;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchive;
import com.sun.enterprise.deployment.deploy.shared.ArchiveFactory;
import com.sun.enterprise.deployment.interfaces.DeploymentImplConstants;
import com.sun.enterprise.deployment.runtime.JavaWebStartAccessDescriptor;
import com.sun.enterprise.deployment.util.ModuleDescriptor;
import com.sun.enterprise.deployment.backend.DeploymentUtils;
import com.sun.enterprise.instance.AppclientModulesManager;
import com.sun.enterprise.instance.ApplicationEnvironment;
import com.sun.enterprise.instance.AppclientModulesManager;
import com.sun.enterprise.instance.AppsManager;
import com.sun.enterprise.instance.BaseManager;
import com.sun.enterprise.instance.InstanceEnvironment;
import com.sun.enterprise.instance.ModuleEnvironment;
import com.sun.enterprise.loader.EJBClassPathUtils;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.server.ServerContext;
import com.sun.enterprise.util.ExecException;
import com.sun.enterprise.util.ORBManager;
import com.sun.enterprise.util.ProcessExecutor;
import com.sun.enterprise.util.SystemPropertyConstants;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.util.i18n.StringManager;
import com.sun.enterprise.util.net.NetUtils;
import com.sun.enterprise.web.WebContainer;
import com.sun.logging.LogDomains;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.enterprise.deploy.shared.ModuleType;
import javax.servlet.http.HttpServletRequest;
import org.xml.sax.SAXParseException;

import org.omg.CORBA.ORB;
import com.sun.corba.ee.spi.folb.ClusterInstanceInfo;
import com.sun.corba.ee.spi.folb.GroupInfoService;
import com.sun.corba.ee.spi.folb.SocketInfo;
import com.sun.corba.ee.impl.orbutil.ORBConstants;
import org.omg.CORBA.ORBPackage.InvalidName;

/**
 *Records and provides access to information about app clients that need
 *Java Web Start support.
 *<p>
 *The basic purpose of this class is to map between the "virtual" location of
 *files as will be specified by incoming HTTP requests and the content to be returned
 *as the responses to those requests.  The 
 *content itself can be dynamic (created based on information about the
 *particular app client or the particular HTTP request) or static (a file
 *that resides somewhere in the file system).  
 *<p>
 *This information is updated as modules are loaded and unloaded in the
 *server instance and as the administrator enables or disables modules
 *for Java Web Start access to their app clients.  The information is used
 *by the system web application that responds to Java Web Start's requests
 *for content related to the app clients.
 *<p>
 *This class is a singleton.
 *
 * @author tjquinn
 */
public class AppclientJWSSupportInfo {

    /** MIME types for dynamic documents */
    private static final String JNLP_MIME_TYPE = "application/x-java-jnlp-file";
    private static final String HTML_MIME_TYPE = "text/html";
    private static final String XML_MIME_TYPE = "application/xml";

    /** the singleton instance for this class */
    private static AppclientJWSSupportInfo instance = null;

    /**
     * pattern string describing the format of the pathInfo of incoming requests;
     * includes capture of useful portions as separate groups 
     *
     *The format is:  <category>/<subcategory>/<rest-of-path>
     *
     *where category must be appclient, application, or appserver.  The
     *exact meaning of the subcategory can vary, depending on the category.  
     *The /<rest-of-path> part can be empty, and will be for requests for 
     *the main JNLP document.
     *
     *If the pattern is changed, then the int definitions just below may also
     *need to be changed to reflect the correct positions in the pattern.
     */
    private static final String REQUEST_PATH_INFO_PATTERN = "/(" + 
            NamingConventions.APPCLIENT_CATEGORY + "|" + 
            NamingConventions.APPLICATION_CATEGORY + "|" + 
            NamingConventions.APPSERVER_CATEGORY + "|" + 
            ")/(.*?)(?:/(.*(?:$|\\z)))?";
    
    /*
     *These group numbers are used in retrieving "captured" parts of matched
     *strings.
     */
    private static final int PATTERN_CATEGORY_GROUP_NUMBER = 1;
    private static final int PATTERN_REGNAME_GROUP_NUMBER = 2;
    private static final int PATTERN_RELATIVEPATH_GROUP_NUMBER = 3;
    
    /** pattern used for processing incoming request pathInfo strings */
    private static final Pattern requestPathInfoPattern = Pattern.compile(REQUEST_PATH_INFO_PATTERN);
    
    /*
     *Property settable on the server side to retain temp files on the client.
     *Use the same property name on both sides for clarity.
     */
    private static final String SERVER_RETAIN_TEMP_FILES_PROPERTYNAME = MainWithModuleSupport.APPCLIENT_RETAIN_TEMP_FILES_PROPERTYNAME;
    
    /*
     * Defines the type prefix that indicates a IIOP socket info object that should
     * NOT be included in the list of IIOP endpoints sent to the client for
     * failover.  Eventually the IIOP classes may provide this constant in
     * one place.
     */
    private static final String IIOP_ENDPOINT_TYPE_PREFIX_IGNORE = "SSL";
    
    /** Property name used to create request for elevated permissions in the JNLP document */
    private static final String APPCLIENT_JAR_SECURITY_ELEMENT_PROPERTYNAME = "appclient.jar.security.setting";
    
    /** local strings manager */
    private StringManager localStrings = StringManager.getManager(getClass());
    
    /*
     *Two key concepts in the implementation are "content" - information that
     *must be served back to Java Web Start on the requesting client - and 
     *"origin" of content - such as an app client, an application, or one of
     *several groupings of content from the app server itself.
     *
     *The following Maps relate each app client registration name, J2EE app reg. name,
     *and app server content grouping (lib, imq, etc.) to the corresponding "origin" of
     *the content.  In particular, by keeping separate collections for the signed
     *vs. unsigned appserver origins, it is easier to create the two JNLP documents
     *that list the files.  Each list of files must appear in a separate JNLP
     *document so each file can have different security settings.
     */
    
    private Map<String,ContentOrigin> appclients;
    private Map<String,ContentOrigin> applications;
    private Map<String,ContentOrigin> appserverOrigins;
    private Map<String,ContentOrigin> signedAppserverOrigins;
    private Map<String,ContentOrigin> extJarAppserverOrigins;
    
    /*
     *The following map relates strings representing pathInfo values to the
     *corresponding Content.  All content available from the Java Web Start
     *system servlet is entered in this map.
     */
    private Map<String,Content> contentMap;
    

    /** maps the category name to the particular map of key to content */
    private Map<String,Map<String,ContentOrigin>> originTypes;
    
    /*
     *Several app server-provided objects are used for looking up apps, 
     *app clients, and getting information about the server.
     */
    /** the app server instance server context */
    protected ServerContext appServerContext;
    
    /** the app server instance environment */
    protected InstanceEnvironment instEnv;
    
    /** the instance's J2EE applications manager */
    protected AppsManager appsManager;
    
    /** the instance's app client modules manager */
    protected AppclientModulesManager appclientModulesManager;
    
    private static final String lineSep = System.getProperty("line.separator");

    private Logger _logger=LogDomains.getLogger(NamingConventions.JWS_LOGGER);
    
    /** Cache for the templates, bundled and retrieved from the jar file that contains this class. */
    private TemplateCache templateCache;
    
    /** URI to the app server's installation root directory */
    private URI installRootURI;
    
    /** File pointing to the app server's root directory */
    private File installRootDir;
    
    /** URI to the derby root directory */
    private URI derbyRootURI;
    
    /** File pointing to the derby root directory */
    private File derbyRootDir;
    
    /** File pointing to the app server's installed lib directory */
    private File libRoot;
    
    /** File pointing to the temporary app client jar directory */
    private File tempJarDirectory;
    
    /** File pointing to the instance's j2ee-modules directory */
    private File j2eeModulesDir;
    
    /** File pointing to the instance's j2ee-apps directory */
    private File j2eeApplicationsDir;

    /** manages extension jar files */
    private ExtensionFileManager extensionFileManager;

    private String UNSIGNED_JWSACC_JARFILE_NAME = "appserv-jwsacc.jar";

    private String SIGNED_JWSACC_JARFILE_NAME = "appserv-jwsacc-signed.jar";
    
    /**
     *Returns the single instance of the class.
     *@return the singleton AppclientJWSSupportInfo
     */
    public static synchronized AppclientJWSSupportInfo getInstance() throws IOException, Exception {
        if (instance == null) {
            instance = new AppclientJWSSupportInfo();
            
            /*
             *Make sure the manager is instantiated so it can register for
             *events.  No reference to the object needs to be kept here; 
             *just make sure the manager has been set up.
             */
             AppclientJWSSupportManager.getInstance();
        }
        return instance;
    }    

    /**
     *Creates a new instance of AppclientJWSSupportInfo.
     *The constructor is private to prevent other objects from creating a new
     *instance except through getInstance().
     */
    private AppclientJWSSupportInfo() throws IOException, Exception {
        
        extensionFileManager = new ExtensionFileManager();
        
        /*
         *Create the collections of content origins (such as the app server itself,
         *app clients, and applications that contain embedded app clients.
         */
        appclients = Collections.synchronizedMap(new HashMap<String,ContentOrigin>());
        applications = Collections.synchronizedMap(new HashMap<String,ContentOrigin>());
        appserverOrigins = Collections.synchronizedMap(new HashMap<String,ContentOrigin>());
        signedAppserverOrigins = Collections.synchronizedMap(new HashMap<String,ContentOrigin>());
        extJarAppserverOrigins = Collections.synchronizedMap(new HashMap<String,ContentOrigin>());
        
        contentMap = Collections.synchronizedMap(new HashMap<String,Content>());
        
        /*
         *Add these maps to the map of maps (!).
         */
        originTypes = new HashMap<String,Map<String,ContentOrigin>>();
        originTypes.put(NamingConventions.APPCLIENT_CATEGORY, appclients);
        originTypes.put(NamingConventions.APPLICATION_CATEGORY, applications);
        originTypes.put(NamingConventions.APPSERVER_CATEGORY, appserverOrigins);
        originTypes.put(NamingConventions.APPSERVER_CATEGORY, signedAppserverOrigins);
        originTypes.put(NamingConventions.APPSERVER_EXTJAR_FILES, extJarAppserverOrigins);
        
        /*
         *Get references to useful app server objects.
         */
        findAppServerObjects();

        /*
         *On Windows, the user may have entered the installation path with
         *different case compared to how the directories are actually spelled.
         *(For example, the directory might be on the disk as MyDir but the
         *user could have entered mydir.)  Windows is okay with this, but
         *a File object using mydir will yield a different URI than one
         *with MyDir.  Using the canonical path for the installation directory
         *makes sure the installRootURI is formatted consistently with the
         *ones for which we construct relative paths later on.
         */
        String installRootDirSpec = System.getProperty(SystemPropertyConstants.INSTALL_ROOT_PROPERTY);
        installRootDir = new File(installRootDirSpec).getCanonicalFile();
        installRootURI = installRootDir.toURI();
        
        String derbyRootDirSpec = System.getProperty(SystemPropertyConstants.DERBY_ROOT_PROPERTY);
        derbyRootDir = new File(derbyRootDirSpec).getCanonicalFile();
        derbyRootURI = derbyRootDir.toURI();
        
        /*
         *Create the template cache.
         */
        templateCache = new TemplateCache();
        
        /*
         *Set up the File objects for the app server's lib directory.  It is
         *used in setting up the content objects for the app server jars served
         *back to the client.
         */
        String instanceRootDirSpec = instEnv.getInstancesRoot();
        libRoot = new File(installRootDir, "lib");

        /*
         *Load the map with app server-provided files.  Do this now, because these
         *are the same for all applications or app clients.
         */
        preloadAppserverContent();
    }
    
    /**
     *Starts Java Web Start services for a stand-alone app client module.
     *
     *@param the Application object for this app client
     *@param the ModuleDescriptor for the app client
     *@param the config context from the deploy event (null if the instance is restarting)
     */
    public void startJWSServicesForAppclient(Application application, ModuleDescriptor moduleDescr, ConfigContext configContext) 
        throws IOException, URISyntaxException, ConfigException, SAXParseException, ConfigException, Exception {
        String regName = application.getRegistrationName();
        AppClientConfig appClientConfig = new AppClientConfig(configContext, regName);
        if (appClientConfig == null) {
            throw new ConfigException("Cannot locate config information for app client " + regName);
        }

        /*
         *Make sure we don't already have services going for this app client.
         */
        String appclientMapKey = NamingConventions.TopLevelAppclient.actualContextRoot(application);
        AppclientContentOrigin origin = findAppclient(appclientMapKey);
        
        if (origin != null) {
            _logger.warning("Attempted to start Java Web Start services for stand-alone app client " + regName + " when they were already started; ignoring the duplicate request");
            return;
        }

        _logger.fine("Starting Java Web Start services for stand-alone app client " + application.getRegistrationName());
        
        /*
         *There is no pre-existing origin for this app client, so create one and
         *start Java Web Start services for it.
         */
        origin = prepareTopLevelAppclient(application, moduleDescr, 
                appClientConfig.getLocation(), appClientConfig.getGeneratedXMLLocation());
        if (origin != null) {
            startJWSServices(origin);

            appclients.put(appclientMapKey, origin);
            refreshConfigContextForManagers(configContext);

            String logOutput = (_logger.isLoggable(Level.FINE) ? origin.toLongString() : origin.toString());
            _logger.info("Java Web Start services started for stand-alone app client " + logOutput);
        }
        
    }
    
    /**
     *Ends Java Web Start support for a stand-alone app client that is being
     *unloaded from the server.
     *
     *@param the application for the app client
     *@param the module descriptor for the app client
     */
    public void endJWSServicesForAppclient(Application application, ModuleDescriptor moduleDescr, ConfigContext configContext) throws ConfigException {
        _logger.fine("Ending Java Web Start services for stand-alone app client " + application.getRegistrationName());
        
        String appclientMapKey = NamingConventions.TopLevelAppclient.actualContextRoot(application);
        AppclientContentOrigin origin = findAppclient(appclientMapKey);
        if (origin != null) {
            endJWSServices(origin);
            appclients.remove(appclientMapKey);

            _logger.info("Java Web Start services ended for stand-alone app client " + origin);
        }
        refreshConfigContextForManagers(configContext);
    }
    
    /**
     *Starts Java Web Start services for an application that contains nested 
     *app clients which need Java Web Start support.
     *@param the application's registration ID
     *@param the module descriptors for child app clients eligible for Java Web Start access
     *@param the config context from the deploy event (null if during an instance restart)
     *@exception IOException during any retrieval of template for dynamic content
     */
    public void startJWSServicesForApplication(Application application, ModuleDescriptor [] moduleDescriptors, ConfigContext configContext) 
            throws IOException, URISyntaxException, SAXParseException, ConfigException, Exception {
        String applicationMapKey = NamingConventions.TopLevelApplication.contextRoot(application);
        String regName = application.getRegistrationName();
        
        ApplicationConfig appConfig = new ApplicationConfig(configContext, regName);
        if (appConfig == null) {
            throw new ConfigException("Cannot locate config information for application " + regName);
        }
        _logger.fine("Starting Java Web Start services for application " + regName);

        /*
         *Make sure we don't have services running already for this application.
         */
        ApplicationContentOrigin origin = findApplication(applicationMapKey);
        if (origin != null) {
            _logger.warning("Attempt to start Java Web Start services for application " + regName + " when they were already started; ignoring the duplicate request");
            return;
        }
        
        /*
         *Preparing the application also includes preparing its child 
         *app clients, which is the real goal.
         */
        origin = prepareApplication(application, moduleDescriptors, 
                appConfig.getLocation(), appConfig.getGeneratedXMLLocation());

        startJWSServices(origin);

        /*
         *Add the origin to the collection.
         */
        applications.put(applicationMapKey, origin);

        refreshConfigContextForManagers(configContext);
        
        String logOutput = (_logger.isLoggable(Level.FINE) ? origin.toLongString() : origin.toString());
        _logger.info("Java Web Start services started for application " + logOutput);
    }
    
    /**
     *Ends Java Web Start services for an application that may contain embedded app clients.
     *
     *@param the application object for the application
     *@param the module descriptor for the application
     */
     public void endJWSServicesForApplication(Application application, ModuleDescriptor[] moduleDescrs, ConfigContext configContext) throws ConfigException {
         String applicationMapKey = NamingConventions.TopLevelApplication.contextRoot(application);
         ApplicationContentOrigin origin = findApplication(applicationMapKey);
         if (origin != null) {
             _logger.fine("Ending Java Web Start services for application " + origin.getTopLevelRegistrationName());
             endJWSServices(origin);
             _logger.info("Java Web Start services ended for application: " + origin);

             /*
              *Finally, remove the application's origin from the collection.
              */
             applications.remove(applicationMapKey);
         }
         refreshConfigContextForManagers(configContext);
     }

      /**
      *Returns whether the specified origin is currently enabled for Java Web
      *Start access or not.
      *@param origin the UserContentOrigin to check
      *@return boolean indicating whether the app client origin is enabled for JWS access
      */
      public boolean isEnabled(UserContentOrigin origin){
          boolean result = false;
          String regName = origin.getApplication().getRegistrationName();
          try {
              if (origin instanceof ApplicationContentOrigin) {
                  result = appsManager.isJavaWebStartEnabled(regName);
              } else {
                  result = appclientModulesManager.isJavaWebStartEnabled(regName);
              }
              return result;
          } catch (ConfigException ce) {
              _logger.log(Level.SEVERE, "Error checking if Java Web Start access enabled for app client " + origin.getApplication().getRegistrationName(), ce);
              return false;
          }
      }
       
      /**
      *Returns whether the specified origin is currently enabled for Java Web
      *Start access or not.
      *@param origin the ApplicationContentOrigin to check
      *@return boolean indicating whether the app client origin is enabled for JWS access
      */
      public boolean isEnabled(ApplicationContentOrigin origin) {
          try {
             return appsManager.isJavaWebStartEnabled(origin.getApplication().getRegistrationName());
          } catch (ConfigException ce) {
              _logger.log(Level.SEVERE, "Error checking if Java Web Start access enabled for application " + origin.getApplication().getRegistrationName(), ce);
              return false;
          }
      }
    /**
     *Returns the Content object corresponding to the path info in the HTTP
     *request.
     *@param the HTTP request asking for the content
     *@return Content object representing the desired content; returns null if no 
     *content matches the HTTP request's information
     */
    public Content getContent(HttpServletRequest request) {
        
        String pathInfo = request.getPathInfo();
        
        /*
         *Adjust the request's pathInfo - for instance, this removes the 
         *context-root of the web app if the web app is nested inside an ear.
         */
        String contentKey = NamingConventions.pathToContentKey(pathInfo);

        /*
         *Even though each origin tracks its own content as well, the single
         *contentMap allows a single look-up to find any content, regardless
         *of its origin.
         */
        Content result = contentMap.get(contentKey);

        /*
         *Make sure the origin for this content is currently enabled for Java 
         *Web Start access.
         */
         if (result != null) {
             ContentOrigin origin = result.getOrigin();
             if ( ! origin.isEnabled()) {
                 if (_logger.isLoggable(Level.FINE)) {
                     _logger.fine("Located requested document with content key " + contentKey + " but reporting 'not found' because the administrator has disabled Java Web Start access for the application or app client");
                 }
                 result = null;
             }
         } else if (_logger.isLoggable(Level.WARNING) && (result == null) ) {
             /*
              *Almost every request that reaches our system servlet results from
              *information we generated into the JNLP documents.  So we should not
              *receive requests for content we do not have.  If the look-up failed 
              *to find the content, report it.
              */
              _logger.warning("Attempt failed to find Java Web Start content at path " + pathInfo + "; content is available at these paths:" + contentMap.keySet());
          }
        
        return result;
    }

     /**
      *Register all deployed appclients with JWS service. This will be called by
      *OnDemand initialization framework, when webcontainer starts.
      */
     public void startJWSServicesForDeployedAppclients() {
         for (ContentOrigin origin : appclients.values()) {
             AppclientContentOrigin acOrigin = (AppclientContentOrigin) origin;
             if (acOrigin.isAdhocPathRegistered() == false) {
                 startJWSServices(acOrigin);
             }
         }

         for (ContentOrigin origin : applications.values()) {
             ApplicationContentOrigin aOrigin = (ApplicationContentOrigin) origin;
             if (aOrigin.isAdhocPathRegistered() == false) {
                 startJWSServices(aOrigin);
             }
         }
     }
    
     /**
      *Returns the AppclientContentOrigin corresponding to the app client with the
      *specified registration name.
      *@param the reg name of the app client 
      *@return the AppclientContentOrigin object for that app client; null if there is none
      */
     private AppclientContentOrigin findAppclient(String regName) {
         AppclientContentOrigin result = (AppclientContentOrigin) appclients.get(regName);
        return result;
     }
     
     /**
     *Returns the AppclientContentOrigin that corresponds to the specified Application and ModuleDescriptor.
     *@param the Application object for the app client
     *@param the ModuleDescriptor object for the app client
     *@return the corresponding AppclientOrigin; null if no matching AppclientOrigin is found
     */
    private AppclientContentOrigin findAppclient(Application application, ModuleDescriptor moduleDescr) {
        return findAppclient(application.getRegistrationName());
    }

    /**
     *Returns the ApplicationContentOrigin corresponding to the specified Application and ModuleDescriptor
     *@param the registration name for the application
     *@return the corresponding AppclientOrigin; null if no matching AppclientOrigin is found
     */
    private ApplicationContentOrigin findApplication(String regName) {
        ApplicationContentOrigin result = (ApplicationContentOrigin) applications.get(regName);
        return result;
    }
    /**
     *Returns the ApplicationContentOrigin corresponding to the specified Application and ModuleDescriptor
     *@param the Application object for the app client
     *@return the corresponding AppclientOrigin; null if no matching AppclientOrigin is found
     */
    private ApplicationContentOrigin findApplication(Application application) {
        return findApplication(application.getRegistrationName());
    }

    /**
     *Does the work to start Java Web Start services for an app client.
     *@param the appclientOrigin for which to begin JWS services
     */
    private void startJWSServices(AppclientContentOrigin origin) {
        
        WebContainer container = WebContainer.getInstance();
        if (container != null) { // Make sure that webcontainer is up.

            /*
             *Ask the web container to route requests for the app client's context-root to 
             *our ad hoc servlet using the appropriate target path for this origin.
             */
            String targetPathString = origin.getTargetPath();
            WebPath targetPath = new WebPath(targetPathString);
            JWSAdHocServletInfo info = new JWSAdHocServletInfo(targetPath.path(), targetPath.contextRoot());

            String virtualContextRoot = origin.getVirtualPath();
            WebPath virtualPath = new WebPath(virtualContextRoot);
            _logger.info("Registering ad hoc servlet: " + virtualPath);
            container.registerAdHocPath(virtualPath.path(),
                                        virtualPath.contextRoot(),
                                        origin.getTopLevelRegistrationName(),
                                        info);
            origin.adhocPathRegistered();
        }
    }
    
    /**
     *Does the work to end Java Web Start support for the specified app client.
     *@param the app client for which Java Web Start is no longer needed
     */
    private void endJWSServices(AppclientContentOrigin origin) {
        
        for (Content c : origin.getContents()) {
            contentMap.remove(c.getContentKey());
        }
        
        WebPath path = new WebPath(origin.getVirtualPath());
        _logger.fine("Unregistering ad hoc servlet: " + path);

        WebContainer container = WebContainer.getInstance();
        if (container != null) {
            container.unregisterAdHocPath(path.path(), path.contextRoot());
        }
    }

    /**
     *Does the work to start Java Web Start services for app clients
     *nested within an application.
     *@param the applicationOrigin for which to begin JWS services
     */
    private void startJWSServices(ApplicationContentOrigin origin) {
        
        WebContainer container = WebContainer.getInstance();
        if (container != null) { // Make sure that webcontainer is up.
            /*
             *Start services for each of the child app clients.
             */
            for (AppclientContentOrigin appclient : origin.getAppclientOrigins()) {
                startJWSServices(appclient); 
            }
            origin.adhocPathRegistered();
        }
    }
    
    /**
     *Does the work to end Java Web Start support for the app clients
     *within the specified parent application content origin.
     *@param the app client for which Java Web Start is no longer needed
     */
    private void endJWSServices(ApplicationContentOrigin origin) {

        for (AppclientContentOrigin appclient : origin.getAppclientOrigins()) {
            endJWSServices(appclient); 
        }
        
        for (Content c : origin.getContents()) {
            contentMap.remove(c.getContentKey());
        }
    }
    
    /**
     *Loads several app server objects for retrieving useful information
     *about the app server instance and applications deployed on it.
     *@throws IllegalStateException reporting any error retrieving the references
     */
    private void findAppServerObjects() throws IllegalStateException {
        /*
         *Build a string listing each failed attempt to locate a useful object.
         */
        StringBuilder failedObjects = new StringBuilder();
        
        Throwable relatedException = null;
        try {
            if ((appServerContext = ApplicationServer.getServerContext()) == null) {
                failedObjects.append(lineSep).append(" ApplicationServer.getServerContext()");
            } else if ((instEnv = appServerContext.getInstanceEnvironment()) == null) {
                failedObjects.append(lineSep).append(" appServerContext.getInstanceEnvironment()");
            } else {
                try {
                    appsManager = new AppsManager(instEnv, false);

                } catch (ConfigException ce) {
                    relatedException = ce;
                    failedObjects.append(lineSep).append(" AppsManager(instEnv)");
                }

                try {
                    appclientModulesManager = new AppclientModulesManager(instEnv, false);
                } catch (ConfigException ce) {
                    relatedException = ce;  // Might override earlier assignment; ok, since it'd be rare and both are config exc.
                    failedObjects.append(lineSep).append(" AppclientModulesManager(instEnv)");
                }
            }
        } finally {
            /*
             *If there were any failures to find the objects, report them.
             */
            if (failedObjects.length() > 0) {
                if (relatedException == null) {
                     relatedException = new IllegalStateException("Null returned");
                }
                throw new IllegalStateException("The following utility objects could not be initialized: " + failedObjects.toString(), relatedException);
            }
        }
    }

    /**
     *Loads the appserver files map.
     *<p>
     *This method loads the app server file entries into the map.  This map is
     *also used to populate part of the JNLP documents that list app
     *server files for Java Web Start.  This method is then the only place that needs to change
     *if the list of app server files needs to change.
     *@exception IOException indicates problems loading a dynamic template
     */
    private void preloadAppserverContent() throws IOException, Exception {
        
        /*
         *Build the collection of signed jar files as one content origin.
         *
         *Note that this may change so that the files listed below become part
         *of the previous group instead with only a small bootstrap jar needing
         *to be signed.
         */
        AppserverContentOrigin signedLibFilesOrigin = new AppserverContentOrigin(
                NamingConventions.APPSERVER_CATEGORY,
                NamingConventions.APPSERVER_LIB_FILES);

        addJWSACCStaticContent(signedLibFilesOrigin);
        
        /*
         *Add the app server lib files entry to the collection of app server entries.
         */
        signedAppserverOrigins.put(NamingConventions.APPSERVER_LIB_FILES, signedLibFilesOrigin);

        AppserverContentOrigin libFilesOrigin = new AppserverContentOrigin(
                NamingConventions.APPSERVER_CATEGORY,
                NamingConventions.APPSERVER_LIB_FILES);

        /*
         *Add a new static content object for each unsigned app server lib file to the
         *app server lib entry.
         */
        addAppserverStaticContent(libRoot, libFilesOrigin, false /* isMain */, 
                "appserv-rt.jar",
                "appserv-cmp.jar",
                "appserv-admin.jar",
                "appserv-deployment-client.jar",
                "javaee.jar",
                "jmac-api.jar",
                "appserv-ext.jar",
                "mail.jar",
                "activation.jar",
                "webservices-rt.jar",
                "webservices-tools.jar",
                "toplink-essentials.jar",
                "dbschema.jar"
                );

        /*
         *Add the app server lib files entry to the collection of app server origins.
         */
        appserverOrigins.put(NamingConventions.APPSERVER_LIB_FILES, libFilesOrigin);

        /*
         *Add the derby jar.
         */
        AppserverContentOrigin derbyLibFilesOrigin = new AppserverContentOrigin(
                NamingConventions.APPSERVER_CATEGORY,
                NamingConventions.APPSERVER_LIB_FILES);
        File derbyLib = new File(derbyRootDir, "lib");
        
    addAppserverStaticContent(derbyLib, derbyLibFilesOrigin, false /* isMain */, "derbyclient.jar");
        appserverOrigins.put(NamingConventions.APPSERVER_DERBY_FILES, derbyLibFilesOrigin);
        
        /*
         *Add static content objects for the mq files needed on the client and add
         *the origin to the appserver origins.
         */
        AppserverContentOrigin mqlibFilesOrigin = new AppserverContentOrigin(
            NamingConventions.APPSERVER_CATEGORY,
            NamingConventions.APPSERVER_LIB_FILES);

        File mqRoot = new File(installRootDir,  "imq");
        File mqlibRoot = new File(mqRoot, "lib");
        
        addAppserverStaticContent(mqlibRoot, mqlibFilesOrigin, false /* isMain */, "fscontext.jar");
        appserverOrigins.put(NamingConventions.APPSERVER_MQLIB_FILES, mqlibFilesOrigin);

        /*
         *Again, for the jms ra jar.
         */
        AppserverContentOrigin jmsraFilesOrigin = new AppserverContentOrigin(
            NamingConventions.APPSERVER_CATEGORY,
            NamingConventions.APPSERVER_LIB_FILES);
        
        File installRoot = new File(libRoot, "install");
        File appsRoot = new File(installRoot, "applications");
        File jmsraRoot = new File(appsRoot, "jmsra");
        
        addAppserverStaticContent(jmsraRoot, jmsraFilesOrigin, false /* isMain */, "imqjmsra.jar");
        
        appserverOrigins.put(NamingConventions.APPSERVER_JMSRALIB_FILES, jmsraFilesOrigin);
        
        /*
         *Add static content for the extension jars.
         */
        AppserverContentOrigin extJarsOrigin = new AppserverContentOrigin(
                NamingConventions.APPSERVER_CATEGORY,
                NamingConventions.APPSERVER_LIB_FILES);
        
        Map<ExtensionFileManager.ExtensionKey,ExtensionFileManager.Extension> extFileInfo = extensionFileManager.getExtensionFileEntries();
        
        for (ExtensionFileManager.Extension e : extFileInfo.values()) {
            File f = e.getFile();
            String path = NamingConventions.extJarFilePath(e.getExtDirectoryNumber(), f);
            String contentKey = extJarsOrigin.getContentKeyPrefix() + path;
            AppserverStaticContent extContent = new AppserverStaticContent(
                extJarsOrigin,
                contentKey,
                path,
                f,
                installRootURI,
                false);
                
            extJarsOrigin.pathToContent.put(extContent.getContentKey(), extContent);
            contentMap.put(extContent.getContentKey(), extContent);
        }
        
        extJarAppserverOrigins.put(NamingConventions.APPSERVER_EXTJAR_FILES, extJarsOrigin);
    }

    private void addJWSACCStaticContent(AppserverContentOrigin origin) throws Exception {

        String signedJarFileName = NamingConventions.SignedJar.signedJarPath(UNSIGNED_JWSACC_JARFILE_NAME);
        String path = "/" + signedJarFileName;
        String contentKey = origin.getContentKeyPrefix() + path;
        File unsignedJar = new File(installRootDir, "lib" + File.separator + UNSIGNED_JWSACC_JARFILE_NAME);
        File signedJar = new File(instEnv.getJavaWebStartPath(), signedJarFileName);
            
        SignedStaticContent content = new SignedStaticContent(
            origin,
            contentKey, 
            path,
            signedJar,
            unsignedJar,
            installRootURI,
            localStrings,
            true /* isMain */
            );
            
        addAppserverStaticContent(origin, content);
    }

    /**
     *Adds static content objects to an origin.
     *
     *@param prefix to use at the beginning of the URL path for these files
     *@param the directory in which the files actually reside
     *@param the appserver origin whose pathToContent map should contain mappings for these paths to the files
     *@param list of file names
     */
    private void addAppserverStaticContent(File dir, AppserverContentOrigin origin, boolean isMain, String... fileName) {
        for (String fn : fileName) {
            String path = "/" + fn;
            String contentKey = origin.getContentKeyPrefix() + path;
            
            AppserverStaticContent content = new AppserverStaticContent(
                origin,
                contentKey, 
                path, 
                new File(dir, fn), 
                installRootURI, 
                isMain);

            /*
             *Add the new content object both to the origin's map and also to 
             *the global content map.
             */
            addAppserverStaticContent(origin, content);
        }
    }

    /**
     *Adds static content objects to an origin.
     *
     *@param prefix to use at the beginning of the URL path for these files
     *@param the directory in which the files actually reside
     *@param the appserver origin whose pathToContent map should contain mappings for these paths to the files
     *@param list of file names
     */
    private void addAppserverStaticContent(AppserverContentOrigin origin, StaticContent content) {
        /*
         *Add the new content object both to the origin's map and also to 
         *the global content map.
         */
        origin.pathToContent.put(content.getContentKey(), content);
        contentMap.put(content.getContentKey(), content);
    }

    /**
     *Creates Properties object containing names and values of placeholders 
     *known when an appclient or application is loaded.  This excludes information
     *available only when a request arrives.  This Properties object is used
     *to substitute the values for the names in the generated documents.
     *<p>
     *All properties are defined in a single Properties object, and that Properties
     *instance is used in processing all dynamic documents before those dynamic 
     *documents are stored in the maps.  At request-time, further substitution
     *occurs with information available only then.
     *@param the origin for which values are to be assigned
     *@param the app client's specified main class name
     *@param the name of a class present in the generated app client jar file
     *@return Properties object containing the token-to-value correspondence
     *@throws IOException for errors getting information 
     */
    protected Properties prepareInitPlaceholders(
            AppclientContentOrigin origin, 
            String mainClassName,
            String location
                ) throws ConfigException, URISyntaxException {
        String appclientRegName = origin.getTopLevelRegistrationName();

        Properties answer = new Properties();

        answer.setProperty("appserver.codebase.path", NamingConventions.appServerCodebasePath());
        
        answer.setProperty("appclient.codebase.path", NamingConventions.appclientCodebasePath(origin));
        answer.setProperty("appclient.context-root", origin.getContextRoot());
        String vendor = origin.getVendor();
        answer.setProperty("appclient.vendor", (vendor == null || vendor.length() == 0) ? localStrings.getString("jws.defaultVendorName") : vendor);
        answer.setProperty("appclient.client.jnlp.filename", NamingConventions.Client.JNLPFilename(appclientRegName));
        answer.setProperty("appclient.client.html.filepath", NamingConventions.Client.HTMLPath(appclientRegName));
        answer.setProperty("appclient.mainext.jnlp.filename", NamingConventions.Main.JNLPExtFilename(appclientRegName));
        
        /*
         *Stop-gap support for specifying image and splash image.
         */
        String imageElements = prepareImageInfo(origin, location);
        answer.setProperty("appclient.information.images", imageElements);
        
//        answer.setProperty("default.wss.client.config.path", NamingConventions.defaultWSSClientConfigPathInJNLP());
//        
//        answer.setProperty("default.sun.acc.xml.url.property.name", com.sun.enterprise.appclient.MainWithModuleSupport.DEFAULT_SUN_ACC_URL_PROPERTY);
//        answer.setProperty("default.wss.client.config.xml.url.property.name", com.sun.enterprise.appclient.MainWithModuleSupport.DEFAULT_WSS_CONFIG_URL_PROPERTY);
//
//        answer.setProperty("default.sun.acc.xml.path", NamingConventions.defaultSunACCPathInJNLP());
//        answer.setProperty("security.config.file.property.name", com.sun.enterprise.appclient.MainWithModuleSupport.SUN_ACC_SECURITY_CONFIG_PROPERTY);

        prepareIIOPProperties(answer);
        
        /*
         *The Java Web Start ACC creates temporary files that are marked for deletion on exit. 
         *For diagnostic purposes, administrators can set a property on the server to
         *ask that these files be retained on the client.  The property value is sent to
         *each client when the user requests the main JNLP document.
         */
        boolean retainTempFiles = Boolean.getBoolean(SERVER_RETAIN_TEMP_FILES_PROPERTYNAME);
        answer.setProperty("appclient.retainTempFiles.propertyName", MainWithModuleSupport.APPCLIENT_RETAIN_TEMP_FILES_PROPERTYNAME);
        answer.setProperty("appclient.retainTempFiles", String.valueOf(retainTempFiles));
        
        /*
         *Set the JNLP information title to the app client module's display name,
         *if one is present.
         */
        String displayName = origin.getDisplayName();
        String jnlpInformationTitle = (displayName != null && displayName.length() > 0) ? displayName : "Application Client " + appclientRegName;
        answer.setProperty("appclient.information.title", jnlpInformationTitle);
        
        /*
         *Set the file part of the homepage URL.
         */
        String jnlpInformationHomepageFilepath = NamingConventions.Main.HTMLPath(appclientRegName);
        answer.setProperty("appclient.information.homepage.filepath", jnlpInformationHomepageFilepath);
        
        /*
         *Set the one-line description the same as the title for now.
         */
        answer.setProperty("appclient.information.description.one-line", jnlpInformationTitle);
        
        /*
         *Set the short description to the description from the descriptor, if any.
         */
        String description = origin.getDescription();
        String jnlpInformationShortDescription = (description != null && description.length() > 0) ? description : jnlpInformationTitle;
        answer.setProperty("appclient.information.description.short", jnlpInformationShortDescription);
        
        /*
         *Set up variables used for populating descriptive information in the
         *JNLP document that lists app server jars.  Although this document is
         *generated for each separate app client that is loaded, it's content
         *is basically the same for all since all app clients rely on the same
         *app server jars.
         */
        prepareAppserverPlaceholders(answer);
        
        /*
         *If a main class is specified in the manifest, use it to specify the
         *main class argument to ACC.  Otherwise, do not emit the mainclass
         *argument into the JNLP document and let the app client container
         *process any user-supplied mainclass or name argument.
         */
        String mainClassArgsValue = "";
        if ((mainClassName != null) && ( ! mainClassName.equals("")) ) {
            mainClassArgsValue = "        <argument>-mainclass</argument>" + lineSep + 
                    "        <argument>" + mainClassName + "</argument>";
        }
        answer.setProperty(
            "appclient.main.class.arguments", mainClassArgsValue);

        /*
         *Set a property that indicates to the app client container that it is
         *running under Java Web Start and what the download host name is.
         */
        answer.setProperty("appclient.isJWS.propertyName", MainWithModuleSupport.APPCLIENT_ISJWS_PROPERTYNAME);
        answer.setProperty("appclient.download.host.propertyName", MainWithModuleSupport.APPCLIENT_DOWNLOAD_HOST_PROPERTYNAME);
        answer.setProperty("appclient.user.code.is.signed.propertyName", AppClientInfo.USER_CODE_IS_SIGNED_PROPERTYNAME);
        return answer;
    }
    
    /**
     *Returns XML to specify the icon image, the splash screen image, neither, or 
     *both, depending on the contents of the <vendor> text in the descriptor.
     *@param origin the AppclientContentOrigin
     *@return XML specifying one or both images; empty if the developer encoded 
     *no image information in the <vendor> element.
     */
    private String prepareImageInfo(AppclientContentOrigin origin, String location) throws ConfigException, URISyntaxException {
        StringBuilder result = new StringBuilder();
        String imageURI = origin.getImageURI();
        if (imageURI.length() > 0) {
            result.append("<icon href=\"" + imageURI + "\"/>");
            addImageContent(origin, location, imageURI);
        }
        String splashImageURI = origin.getSplashImageURI();
        if (splashImageURI.length() > 0) {
            result.append("<icon kind=\"splash\" href=\"" + splashImageURI + "\"/>");
            addImageContent(origin, location, splashImageURI);
        }
        return result.toString();
    }
    
    private void addImageContent(AppclientContentOrigin origin, String location, String imageURI) throws ConfigException, URISyntaxException {
        addStaticContent(origin, ensureLeadingSlash(imageURI), new File(location, imageURI)); 
    }
    
    private String ensureLeadingSlash(String s) {
        if ( ! s.startsWith("/")) {
            return "/" + s;
        } else {
            return s;
        }
    }

    /**
     *Prepares property value assignments for IIOP connectitivy.
     */
    private void prepareIIOPProperties(Properties props) {
        /*
         *Set the default IIOP information for the app client based on this server.  The template
         *uses the host retrieved from the incoming request as the default host value. Note that 
         *because this code and the app client container Main both must use the same property names
         *for this to work, those names are defined as constants in Main and the constants are used
         *both there and here.
         */
        props.setProperty("appclient.iiop.defaultHost.propertyName", MainWithModuleSupport.APPCLIENT_IIOP_DEFAULTHOST_PROPERTYNAME);
        props.setProperty("appclient.iiop.defaultPort.propertyName", MainWithModuleSupport.APPCLIENT_IIOP_DEFAULTPORT_PROPERTYNAME);
        props.setProperty("appclient.iiop.defaultPort", String.valueOf(ORBManager.getORBInitialPort()));
        
        /*
         *If this instance participates in a failover group, prepare the property 
         *that the JWS-aware ACC will recognize.
         */
        String failoverEndpoints = getIIOPEndpoints();
        String failoverEndpointsSetting = "";
        if (failoverEndpoints != null) {
            failoverEndpointsSetting = "<property name=\"" + MainWithModuleSupport.APPCLIENT_IIOP_FAILOVER_ENDPOINTS_PROPERTYNAME + "\" value=\"" + failoverEndpoints + "\"/>";
        }
        props.setProperty("appclient.iiop.failover.endpoints", failoverEndpointsSetting);
    }
    
    /**
     *Returns the IIOP endpoints in the cluster in which the current instance
     *participates.
     *@return String suitable for use in defining the IIOP property for endpoints; null if this instance is not in a cluster
     */
    private String getIIOPEndpoints() {
        String result = com.sun.enterprise.iiop.IIOPEndpointsInfo.getIIOPEndpoints();
        return result;
    }

    /**
     *Adds values for placeholders used in the generated JNLP document that
     *lists app server jars.  These values do not depend on the particular app
     *client but are retrieved from the local strings bundle.
     *@param p the Properties holding placeholders and their values
     */
    private void prepareAppserverPlaceholders(Properties p) {
        p.setProperty(
                "appserver.information.title", 
                localStrings.getString("jws.appserver.information.title"));
        p.setProperty(
                "appserver.information.vendor", 
                localStrings.getString("jws.appserver.information.vendor"));
        p.setProperty(
                "appserver.information.description.one-line", 
                localStrings.getString("jws.appserver.information.description.one-line"));
        p.setProperty(
                "appserver.information.description.short", 
                localStrings.getString("jws.appserver.information.description.short"));
    }
    
    /**
     *Prepares the document entries for an application's nested app clients.
     *@param application the Application descriptor
     *@param moduleDescrs array of ModuleDescriptor for the app's submodules
     *@param earDirectory the directory where the app is deployed
     *@param generatedXMLLocation the directory where the app's generated XML files reside
     */
    private ApplicationContentOrigin prepareApplication(Application application, 
            ModuleDescriptor [] moduleDescrs, String earDirectory, String generatedXMLLocation) 
        throws IOException, URISyntaxException, SAXParseException, ConfigException, Exception {
        
        ApplicationContentOrigin result = new ApplicationContentOrigin(application);
        
        /*
         *Add a static content instance for the single combined client jar file
         *for all app clients in this module.
         */
        SignedStaticContent jarFileContent = addAppclientJarContent(result, generatedXMLLocation);

        String jarHrefs = jarFileContent.asJNLPJarElement();
        
        /**
         *Each module descriptor in the array represents an app client that is eligible
         *for Java Web Start services.  For each, create a nested app client content
         *origin and add it to the parent application content origin.
         */
        for (ModuleDescriptor md : moduleDescrs) {
            /*
             *Get the descriptor for the app client of interest.
             *The prepareAppClient method uses it to extract the main class for use as a command line argument.
             */
            Manifest mf = getManifest(earDirectory, md);
            Attributes mainAttrs = mf.getMainAttributes();
            String mainClassName = mainAttrs.getValue(Attributes.Name.MAIN_CLASS);
            String appclientJarURI = md.getArchiveUri();

            /*
             *Extension libraries are not bundled into the generated jar file.
             *So any relevant extension library jar needs to be added as static
             *content for this appclient origin.  An extension library could
             *be referenced from a jar that appears in the app client's 
             *Class-Path expression.  URIs there are relative to the directory
             *that contains the jar with the Class-Path.  So we need the 
             *directory that contains the developer's app client jar.
             */
            File appclientJar = new File(appclientJarURI);
            File appclientJarDir = appclientJar.getParentFile();
            
            if ( ! appclientJar.isAbsolute()) {
                appclientJar = new File(earDirectory, appclientJarURI);
            }
            
            /*
             *Create and add the nested origin.
             */
            NestedAppclientContentOrigin nestedAppclient = prepareNestedAppclient(
                    result, 
                    md,
                    earDirectory,
                    jarHrefs, 
                    mainClassName, 
                    mainAttrs, 
                    appclientJar.getParent(), 
                    jarFileContent);
            result.addNestedOrigin(nestedAppclient);
        }
        
        return result;
    }
    
    /**
     *Returns the manifest for the nested app client specified by the
     *module descriptor.
     *@param earDirectoryPath the directory into which the ear has been expanded
     *@param md the ModuleDescriptor for the embedded app client of interest
     */
    private Manifest getManifest(String earDirectoryPath, ModuleDescriptor md) {
        /*
         *Use the same logic that the J2EEModuleExploder uses to choose the
         *name for the embedded app client's subdirectory.
         */
        File submoduleDir = new File(DeploymentUtils.getEmbeddedModulePath(earDirectoryPath, md.getArchiveUri()));
        Manifest mf = EJBClassPathUtils.getManifest(submoduleDir.getAbsolutePath());
        return mf;
    }
    
    /**
     *Prepares the content origin for a top-level app client.
     *<p>
     *The object returned describes a single, identifiable origin for content pertinent
     *to a single app client.
     *@param application the Application for the app client
     *@param moduleDescr the ModuleDescriptor describing the app client
     *@param location the directory where the app client resides
     *@param generatedXMLLocation the directory where the app client's generated XML files reside
     *@return the populated content origin object for the app client; null if there is a problem creating it
     *@throws IOException for errors reading the app client's manifest
     *@throws URISyntaxException for errors preparing URIs for static content
     *@throws ConfigException for errors finding the app client's generated JAR directory
     */
    private AppclientContentOrigin prepareTopLevelAppclient(Application application, 
            ModuleDescriptor moduleDescr, String location, String generatedXMLLocation) 
        throws IOException, URISyntaxException, ConfigException, FileNotFoundException, SAXParseException, Exception {
        
        String contextRoot = NamingConventions.TopLevelAppclient.defaultVirtualContextRoot(application);

        AppclientContentOrigin result = new AppclientContentOrigin(application, moduleDescr, contextRoot);
        String regName = result.getTopLevelRegistrationName();
        
        /*
         *Potentially, there could be multiple jar files needed by this app client.  
         *Currently, only the generated app client jar is needed.  Build the
         *string containing <jar href...> elements for insertion into the
         *client JNLP document.
         */
        
        String appclientJarPath = result.getAppclientJarPath();
        
        /*
         *Add a static content instance for the single combined client jar file
         *for all app clients in this module.
         */
        StaticContent jarFileContent = addAppclientJarContent(result, generatedXMLLocation);

        String jarHrefs = jarFileContent.asJNLPJarElement();

        String dirPath = location;
        Manifest mf = EJBClassPathUtils.getManifest(dirPath);
        Attributes mainAttrs = mf.getMainAttributes();
        String mainClassName = mainAttrs.getValue(Attributes.Name.MAIN_CLASS);
        
        /*
         *Perform the rest of the initialization which is shared with nested app client origins.
         */
        prepareAppclient(result, application, moduleDescr, dirPath, jarHrefs, regName, mainClassName, mainAttrs, dirPath, jarFileContent);
        return result;
    }
    
    /**
     *Performs initialization that is common between top-level and nested app clients.
     *@param origin the app client origin object to be further set up
     *@param application the Application for this app client
     *@param moduleDescr the ModuleDescriptor for this app client
     *@param location the directory holding the app client files
     *@param jarHRefs a string representing the module name to be used in constructing virtual file names
     *@param modName the name of the module
     *@param mainClassName the name of the main class for this app client
     *@param mainAttrs the main attributes for the client that may contain extension jar requirements
     *@param dirPathForRelativeClassPathEntries location of the jar file to be used in resolving relative Class-Path manifest entries
     *@param appclientJarContent the content object describing the app client jar to publish
     */
    private void prepareAppclient(
            AppclientContentOrigin origin, 
            Application application, 
            ModuleDescriptor moduleDescr, 
            String location, 
            String jarHRefs, 
            String modName, 
            String mainClassName, 
            Attributes mainAttrs,
            String dirPathForRelativeClassPathEntries,
            StaticContent appclientJarContent) 
                throws FileNotFoundException, IOException, URISyntaxException, ConfigException {

        /*
         *Using information now available, assemble as much of the JNLP and HTML
         *documents as possible and store them with the content origin object
         *for this app client.  As it handles specific incoming
         *requests, the JWS system servlet will do further substitutions.
         */
        Properties tokenValues = prepareInitPlaceholders(
                origin, 
                mainClassName,
                location
                );
        
        /*
         *Prepare the main JNLP document.
         *
         *Build a string containing the <jar> elements for the app server files 
         *that need to be listed in the main JNLP document.  Add the placeholder
         *name and the resulting string to the token values.
         *
         *Get the template, merge with user content (future), and substitute for placeholders.
         *Then add the now partially-substituted main JNLP document template to
         *the content map.
         */
        
        StringBuilder appserverJarElements = buildJarElements(appserverOrigins);
        StringBuilder signedAppserverJarElements = buildJarElements(signedAppserverOrigins);
        
        /*
         *Build hrefs for the extension jars in this app client.  The hrefs will be
         *inserted into the JNLP document for the app client.
         */
        Set<ExtensionFileManager.Extension> earExtJars = findExtensions(mainAttrs, dirPathForRelativeClassPathEntries);
        
        String earJarHrefs = prepareHrefsForFiles(earExtJars);
        
        
        tokenValues.setProperty("appserver.jar.elements", appserverJarElements.toString());
        tokenValues.setProperty("appserver.jar.elements.signed", signedAppserverJarElements.toString());
        tokenValues.put("appclient.jar.elements", jarHRefs);

        /*
         *Prepare the main JNLP document.
         */
        String mainJNLPPath = NamingConventions.Main.JNLPPath(modName);
        addDynamicContent(
                origin,
                mainJNLPPath,
                NamingConventions.APPCLIENT_MAIN_JNLP_TEMPLATE_NAME,
                tokenValues,
                JNLP_MIME_TYPE,
                true /* requiresElevatedPrivs */
                );

        /*
         *Prepare the main extension JNLP document.
         */
        String mainExtJNLPPath = NamingConventions.Main.JNLPExtPath(modName);
        addDynamicContent(
                origin,
                mainExtJNLPPath, 
                NamingConventions.APPCLIENT_MAIN_JNLP_EXT_TEMPLATE_NAME, 
                tokenValues, 
                JNLP_MIME_TYPE);
        
        /*
         *Prepare the main HTML document.
         */
        String mainHTMLPath = NamingConventions.Main.HTMLPath(modName);
        addDynamicContent(
                origin,
                mainHTMLPath, 
                NamingConventions.APPCLIENT_MAIN_HTML_TEMPLATE_NAME, 
                tokenValues, 
                HTML_MIME_TYPE);
        
        /*
         *Prepare the client HTML document.
         */
        String clientHTMLPath = NamingConventions.Client.HTMLPath(modName);
        addDynamicContent(
                origin,
                clientHTMLPath, 
                NamingConventions.APPCLIENT_CLIENT_HTML_TEMPLATE_NAME, 
                tokenValues, 
                HTML_MIME_TYPE);
        
        /*
         *Prepare the client JNLP document.  Get template, merge (future), substitute, add to map.
         *Note that this document's template contains a placeholder for the list of <jar>
         *elements to describe all user-provided jars it needs.  
         */
        String clientJNLPPath = NamingConventions.Client.JNLPPath(modName);
        
        String contentKey = origin.getContentKeyPrefix() + clientJNLPPath;
        String docText = Util.replaceTokens(
                templateCache.getTemplate(NamingConventions.APPCLIENT_CLIENT_JNLP_TEMPLATE_NAME),
                tokenValues);

        DynamicContent clientJNLPContent = new DynamicContent(
                origin,
                contentKey,
                clientJNLPPath,
                docText,
                JNLP_MIME_TYPE,
                true /* requiresElevatedPrivs */
                );
        
        /*
         *The security settings for the app client jar dynamic content is set
         *when the request arrives by checking the current availability of a
         *signed jar at that time. So it is not set here.
         */
        addDynamicContent(origin, clientJNLPContent);
    }

    /**
     *Returns an initialized nested app client content origin.
     *@param parent the parent application content origin
     *@param moduleDescr the module descriptor for the app client
     *@param location the directory containing the deployed parent EAR's files
     *@param jarHRefs a string representing the module name to be used in constructing virtual file names
     *@param mainClassName the name of the main class for this app client
     *@param mainAttrs the main attributes for the client that may contain extension jar requirements
     *@param dirPathForRelativeClassPathEntries location of the jar file to be used in resolving relative Class-Path manifest entries
     *@param appclientJarContent the content object describing the app client jar to publish
     *@return the new NestedAppclientContentOrigin
     */
    private NestedAppclientContentOrigin prepareNestedAppclient(
            ApplicationContentOrigin parent, 
            ModuleDescriptor moduleDescr,
            String location,
            String jarHRefs, 
            String mainClassName, 
            Attributes mainAttrs,
            String dirPathForRelativeClassPathEntries,
            StaticContent appclienJarContent)
                throws FileNotFoundException, IOException, URISyntaxException, ConfigException {
        
        String contextRoot = NamingConventions.NestedAppclient.defaultVirtualContextRoot(parent.getApplication(), moduleDescr);
        
        NestedAppclientContentOrigin result = new NestedAppclientContentOrigin(parent, moduleDescr, contextRoot);

        prepareAppclient(
                result, 
                parent.getApplication(), 
                moduleDescr, 
                location, 
                jarHRefs, 
                result.getName(), 
                mainClassName, 
                mainAttrs, 
                dirPathForRelativeClassPathEntries,
                appclienJarContent
                );
        
        return result;
    }
    
    /**
     *Refreshes the config context in use by the apps manager and the app 
     *client modules manager.  
     *@throws ConfigException in case of error refreshing either context
     */
    private void refreshConfigContextForManagers(ConfigContext configContext) throws ConfigException {
        if (configContext == null) {
            if (! ServerHelper.isDAS(appsManager.getConfigContext(), instEnv.getName())) {
                appsManager.refreshConfigContext();
                appclientModulesManager.refreshConfigContext();
            }
        } else {
            appsManager.refreshConfigContext(configContext);
            appclientModulesManager.refreshConfigContext(configContext);
        }
    }
    
    /**
     * Returns Extension objects for the extension jars cited by the app client.
     * The jars represented by the Extension objects need to be available to app clients in the ear
     * (when dealing with an ear) or to the single stand-alone app client (when
     * dealing with a stand-alone app client).
     * @param attrs the main attributes of the jar of interest
     * @param appDirPath the app's directory to be used in resolving relative Class-Path entries
     * @return Set containing File objects for the required jars
     * @throws IOException for any error while processing the archive
     * @throws ConfigException for any error retrieving information about the application
     */
    private Set<ExtensionFileManager.Extension> findExtensions(Attributes attrs, String appDirPath) throws IOException, ConfigException {
        Set<ExtensionFileManager.Extension> result = null;
        
        File appDir = new File(appDirPath);
        result = extensionFileManager.findExtensionTransitiveClosure(appDir, attrs);
        return result;
    }
    
    private Manifest loadManifestFromFile(String dirPath) throws IOException {
        Manifest result = null;
        InputStream is = null;
        try {
            File manifestFile = new File(dirPath, JarFile.MANIFEST_NAME);
            if (manifestFile.exists() && manifestFile.canRead()) {
                is = new FileInputStream(manifestFile);
                result = new Manifest(is);
            }
            return result;
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }
    
    /**
     * Converts a set of ExtensionFileManager.Extension objects into a String containing JNLP-friendly
     * HREFs for those files.
     * @param extJarInfo List of ExtensionFileManager.Extension objects representing the jars
     * @return String containing an HREF for each jar in the set
     */
    private String prepareHrefsForFiles(Set<ExtensionFileManager.Extension> extJarInfo) {
        ContentOrigin extJarsOrigin = extJarAppserverOrigins.get(NamingConventions.APPSERVER_EXTJAR_FILES);
        StringBuilder result = new StringBuilder();
        for (ExtensionFileManager.Extension e : extJarInfo) {
            String path = NamingConventions.extJarFilePath(e.getExtDirectoryNumber(), e.getFile());
            String contentKey = extJarsOrigin.getContentKeyPrefix() + path;
            StaticContent content = (StaticContent) extJarsOrigin.getContent(contentKey);
            result.append(content.asJNLPJarElement()).append(lineSep);
        }
        return result.toString();
    }

    /**
     *Convenience method to both add content to the origin and also to the 
     *overall content map.
     *@param origin content origin to which to add the content
     *@param path URI path by which the content can be addressed within its origin
     *@param templateName name of the template to use for this dynamic content
     *@param tokenValues placeholder->value Properties object
     *@param mimeType MIME type of this content
     *@param requiresElevatedPrivs indicates if the JNLP should request privs
     *@return the newly-created Content
     *@throws IOException in case of errors retrieving the template
     */
    private DynamicContent addDynamicContent(
            ContentOrigin origin, 
            String path, 
            String templateName, 
            Properties tokenValues, 
            String mimeType,
            boolean requiresElevatedPrivs) throws IOException {
         
        DynamicContent content = origin.addDynamicContent(
                path, 
                templateCache.getTemplate(templateName), 
                tokenValues, 
                mimeType, 
                requiresElevatedPrivs);
        contentMap.put(content.getContentKey(), content);
        return content;
    }
     
    /**
     *Convenience method to both add content to the origin and also to the 
     *overall content map.
     *@param origin content origin to which to add the content
     *@param path URI path by which the content can be addressed within its origin
     *@param templateName name of the template to use for this dynamic content
     *@param tokenValues placeholder->value Properties object
     *@param mimeType MIME type of this content
     *@return the newly-created Content
     *@throws IOException in case of errors retrieving the template
     */
    private DynamicContent addDynamicContent(
            ContentOrigin origin,
            String path, 
            String templateName, 
            Properties tokenValues, 
            String mimeType) throws IOException {
        return addDynamicContent(origin, path, templateName, tokenValues, mimeType, false /* requiredElevatedPrivs */);
    }
    
    private DynamicContent addDynamicContent(
            ContentOrigin origin, 
            DynamicContent content) {
         
        origin.addDynamicContent(content);
        contentMap.put(content.getContentKey(), content);
        return content;
    }

    /**
     *Convenience method to both add content to the origin and also to the 
     *overall content map.
     *@param the content origin to which to add the content
     *@param the path by which the content can be addressed within its origin
     *@param the file where the static content resides
     *@return the newly-created Content
     *@throws URISyntaxException in case of errors deriving a relative URI for the file
     */
     private StaticContent addStaticContent(
             ContentOrigin origin, 
             String path, 
             File file) throws URISyntaxException {
         
         StaticContent content = origin.addStaticContent(path, installRootURI, file);
         contentMap.put(content.getContentKey(), content);
         return content;
     }
     
     /**
      *Convenience method to both add content to the origin and also to the
      *overall content map.
      *@param origin the origin to add the content to
      *@param content the StaticContent instance to add
      *@returns the content added to the origin
      */
     private StaticContent addStaticContent(
             ContentOrigin origin,
             StaticContent content) throws URISyntaxException {
         
         origin.addStaticContent(content);
         contentMap.put(content.getContentKey(), content);
         return content;
     }
     
     /**
      *Adds the special app client static content object to the application 
      *origin and to the global content map.
      *@param origin the ApplicationContentOrigin to add the content to
      *@param generatedXMLLocation directory containing generated files for this app client
      *@return the newly-added static content
      */
     private SignedStaticContent addAppclientJarContent(
             ApplicationContentOrigin origin,
             String generatedXMLLocation) throws FileNotFoundException, URISyntaxException, Exception {

         return addAppclientJarContent(origin, origin.getAppclientJarPath(), generatedXMLLocation);
     }
     
     /**
      *Adds the special app client static content object to the app client
      *origin and to the global content map.
      *@param origin the AppclientContentOrigin to add the content to
      *@param generatedXMLLocation directory containing generated files for this app client
      *@return the newly-added static content
      */
     private SignedStaticContent addAppclientJarContent(
             AppclientContentOrigin origin,
             String generatedXMLLocation) throws FileNotFoundException, URISyntaxException, Exception {
         
         return addAppclientJarContent(origin, origin.getAppclientJarPath(), generatedXMLLocation);
     }
     
     /**
      *Adds the app client content to the specified origin, regardless of which
      *type of user content origin is involved.
      *@param origin the application or app client origin to add the content to
      *@param appclientJarPath path to the generated app client jar
      *@param generatedXMLLocation directory containing the app client's generated files
      *@return the created StaticContent for the app client jar file
      */
     private SignedStaticContent addAppclientJarContent(
             UserContentOrigin origin,
             String appclientJarPath,
             String generatedXMLLocation) throws FileNotFoundException, URISyntaxException, Exception {
         
         File generatedJar = origin.locateGeneratedAppclientJarFile(generatedXMLLocation);
         File signedGeneratedJar = 
                 NamingConventions.SignedJar.signedGeneratedAppclientJarFile(
                        origin,
                        instEnv,
                        generatedJar);
         String contentKey = origin.getContentKeyPrefix() + "/" + signedGeneratedJar.getName();
        
         SignedStaticContent content = new SignedStaticContent(
                 origin,
                 contentKey,
                 appclientJarPath,
                 signedGeneratedJar,
                 generatedJar,
                 installRootURI,
                 localStrings,
                 false /* isMain */);
        
         addAppclientJarContent(origin, content);
         return content;
     }
     
     /**
      *Adds the app client jar content to the origin - as with any content - but 
      *also identifies it as the app client jar.  This information is used
      *during the generation of the main JNLP document and the client JNLP
      *document when deciding if the app client jar is signed or not.
      *@param origin the origin to which to add the app client jar content
      *@param content the app client jar static content object
      *@return the content object itself
      */
     private SignedStaticContent addAppclientJarContent(UserContentOrigin origin, SignedStaticContent content) throws URISyntaxException {
         addStaticContent(origin, content);
//         origin.setPublishedAppclientJarStaticContent(content);
         return content;
         
     }

     /**
     *Returns a StringBuilder containing XML syntax suitable for inclusion in
     *a JNLP document's resources section listing the jar files required
     *by this module.
     *<p>
     *The return type is StringBuilder (rather than String) so the caller
     *can, if needed, add additional text to the result conveniently.
     *
     *@param map containing content origins of a particular category
     *@return StringBuilder containing JAR element text for the required jar files
     */
    private StringBuilder buildJarElements(Map<String,ContentOrigin> origins) {

        StringBuilder appserverJarElements = new StringBuilder();
        /*
         *Go through each of the groups of app server files (aslib, mqlib, etc.)
         */
        for (ContentOrigin origin : origins.values()) {
            /*
             *For this group, go through all the files.
             */
            for (Content c : origin.pathToContent.values()) {
                if (c instanceof StaticContent) {
                    StaticContent sc = (StaticContent) c;
                    appserverJarElements.append(sc.asJNLPJarElement());
                }
            }
        }
        return appserverJarElements;
    }
    
    /**
     *Represents either an application config bean or an app client config bean.
     *<p>
     *This class encapsulates the differences between an application config bean 
     *and an app client config bean, as well as whether the information about
     *the app or app client config comes from the current environment's config context 
     *or from the config context supplied during a deployment event.
     */
    private abstract class EventConfig {

        /**
         *Creates an Applications config bean using the specified context.
         *<p>
         *If the specified context is null then the Applications config bean is
         *created using the current config context from either the appsManager or
         *appClientModulesManager, depending on whether an ApplicationConfig or
         *an AppClientConfig is being instantiated.
         *@param configContext the configuration the Applications bean should use
         *@return the Applications object based on the specified config
         */
        protected Applications getAppsConfig(ConfigContext configContext) throws ConfigException {
            if (configContext == null) {
                /*
                 *The config context from the event is null, which means this is a
                 *server restart.  In that case the config present in the appsManager
                 *or appClientModulesManager is current, so use that for the config.
                 */
                configContext = chooseManager().getConfigContext();
            }
            Applications apps = (Applications) ConfigBeansFactory.getConfigBeanByXPath(configContext, 
                            ServerXPathHelper.XPATH_APPLICATIONS);
            return apps;
        }
     
        /**
         *Returns the location string for the app or app client config.
         *@return the location (directory) for the module
         */
        abstract String getLocation();
        
        /**
         *Returns the location of the generated XML directory for the app or app client.
         *@return the generated XML directory location for the module
         */
        abstract String getGeneratedXMLLocation();
        
        /**
         *Returns a BaseManager, either AppsManager or AppClientModulesManager,
         *depending on which type of config object is being created.
         */
        abstract BaseManager chooseManager();
    }
    
    /**
     *Represents the config for an application.
     */
    private class ApplicationConfig extends EventConfig {
        
        private J2eeApplication appBean;
        
        private ApplicationConfig(ConfigContext configContext, String regName) throws ConfigException {
            appBean = getAppsConfig(configContext).getJ2eeApplicationByName(regName);
        }
        
        String getLocation() {
            return appBean.getLocation();
        }

        String getGeneratedXMLLocation() {
            ApplicationEnvironment env = instEnv.getApplicationEnvironment(appBean.getName());
            return env.getAppGeneratedXMLPath();
        }
        
        BaseManager chooseManager() {
            return appsManager;
        }
    }
    
    /**
     *Represents the config for an app client.
     */
    private class AppClientConfig extends EventConfig {
        
        private AppclientModule appClientBean;
        
        private AppClientConfig(ConfigContext configContext, String regName) throws ConfigException {
            appClientBean = getAppsConfig(configContext).getAppclientModuleByName(regName);
        }
        
        String getLocation() {
            return appClientBean.getLocation();
        }

        String getGeneratedXMLLocation() {
            ModuleEnvironment menv = instEnv.getModuleEnvironment(appClientBean.getName(),
                                                         DeployableObjectType.CAR); 
            return menv.getModuleGeneratedXMLPath();
        }
        
        BaseManager chooseManager() {
            return appclientModulesManager;
        }
    }
}
