/*
 ************************************************************************
 *
 * Copyright (c) 2003-2004, C&C Research Laboratories, NEC Europe Ltd.
 *
 * Copyright in this software belongs to C&C Research Laboratories,
 * Rathausallee 10, 53757 Sankt Augustin, Germany.
 *
 * This software may not be used, sold, licensed, transferred, copied
 * or reproduced in whole or in part in any manner or form or in or
 * on any media by any person other than in accordance with the terms
 * of the Licence Agreement supplied with the software, or otherwise
 * without the prior written consent of the copyright owners.
 *
 * This software is distributed WITHOUT ANY WARRANTY, without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE, except where stated in the Licence Agreement supplied with
 * the software.
 *
 *  Created By :           G.A. Kohring
 *  Created for Project :  GEMSS (IST-2001-37153)
 *
 ************************************************************************
 */

package eu.gemss;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Logger;
import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;


import eu.gemss.components.ComponentManager;
import eu.gemss.components.providers.Provider;
import eu.gemss.sandbox.Sandbox;


/**
 * This class provides the main entry point into the GEMSS environment. 
 * It contains a single instance of a <code>ComponentManager</code>
 * whose main function is to locate and organize the component architecture.
 * <p>
 */
final public class GEMSS {

    // The single ComponentManager instance.
    private static ComponentManager instance = null;

    // The ComponentManager provider
    private static String providerName =
                    "de.nece.ccrle.organizer.OrganizerFactory";

    // The ComponentManager component
    private static final String MANAGER_COMPONENT =
             "eu.gemss.components.ComponentManager";

    // The sandbox
    private static Sandbox sandbox = null;

    // The logger
    private static Logger logger = Logger.getLogger( GEMSS.class.getName() );


    // Make sure nobody tries to instantiate this class.
    private GEMSS(){
    }

    /**
     * Initializes the GEMSS sandbox. This method should be called the
     * first time a new instence of GEMSS is created.
     */
    private static void initSandbox(){

        // Create the sandbox
        try{
            AccessController.doPrivileged(
                new PrivilegedExceptionAction() {
                    public Object run() throws IOException,
                         GeneralSecurityException, ClassNotFoundException,
                         InstantiationException, IllegalAccessException,
                         MalformedURLException {

                        String gemssHome =  System.getProperty( "gemss.home" );
                        
                        String caCertsDir =  System.getProperty( 
                                                    "gemss.cert.ca" );
                        if ( caCertsDir == null ){
                            caCertsDir =  gemssHome + File.separator +
                              "security" + File.separator + "cacerts";
                        }
                        String crlDir =  System.getProperty( 
                                                    "gemss.cert.crl" );
                        if ( crlDir == null ){
                            crlDir =  gemssHome + File.separator +
                                    "security" + File.separator + "crls";
                        }

                        File sbJar = new File( gemssHome +
                                       "/security/sandbox/sandbox.jar" );
                        File sbSP = new File( gemssHome +
                               "/security/sandbox/security-provider.jar" );

                        URLClassLoader sbcl = new URLClassLoader(
                            new URL[]{ sbJar.toURL(), sbSP.toURL() },
                            this.getClass().getClassLoader() );

                        String sbClassName = 
                            System.getProperty( "gemss.sandbox" );

                        if ( sbClassName == null ){
                            sbClassName = "de.nece.ccrle.sandbox.NECsandbox";
                        }

                        Class sbClass = sbcl.loadClass( sbClassName );

                        Sandbox sandbox = (Sandbox) sbClass.newInstance();

                        sandbox.init( caCertsDir, crlDir );

                        return null;
                    }
                }
            );
        } catch ( PrivilegedActionException pae ){

            if ( pae.getException() instanceof ClassNotFoundException ){
                logger.severe( "Class not found: " + 
                                        pae.getException().getMessage() );
            } else if ( pae.getException() instanceof InstantiationException ){
                logger.severe( "Could not instantiate: " + 
                                        pae.getException().getMessage() );
            } else if ( pae.getException() instanceof IllegalAccessException ){
                logger.severe( "Access not allowed: " + 
                                        pae.getException().getMessage() );
            }

            logger.warning( "The GEMSS sandbox has not been installed. This may result in your system's security being comprimised. Please refer to your service provider for assistance with the security settings in this environment." );
        } 

    }

    /**
     * Retrieves an instance of the <code>ComponentManager</code> which 
     * initializes and organizes the component architecture.
     * <p>
     * The first time this method is called a new component manager is
     * created, then the <code>ComponentManager</code>'s
     * <code>startDaemons()</code> method is called. Subsequent calls return
     * the same instance.
     *
     * @return an object implementing the <code>ComponentManager</code> 
     *      interface or <code>null</code> if an error occurs during loading,
     *      initialization, or instantiation.
     * @see eu.gemss.components.ComponentManager
     */
    public static synchronized ComponentManager getInstance() {

        if ( instance == null ) {
            initSandbox();
            try {
                instance = createComponentManager();
            } catch ( SecurityException se ){
                throw new SecurityException( se.getMessage() );
            } catch ( IOException ioe ){
                throw new RuntimeException( ioe.getMessage() );
            }
            instance.startDaemons();
        }
        return instance;
    }


    /**
    * Sets the name of the provider responsible for supplying the 
    * <code>ComponentManager</code> instance.
    * <p>
    * NOTE: This method must be called <strong>before</strong> the first call
    * to {@link #getInstance()}, it has no effect afterwards.
    *
    * @param provider the fully qualified class name of the provider.
    */
    public static void setProvider( String provider ){
        if ( instance == null ) {
            providerName = provider;
        }
    }


    /**
    * Retrieves the name of the provider responsible for supplying the 
    * <code>ComponentManager</code> instance.
    *
    * @return a <code>String</code> containing  the provider's name.
    */
    public static String  getProvider(){
        return providerName;
    }


    // The ComponentManager object and its provider must be located in the
    // application class path.
    private static ComponentManager createComponentManager( )
        throws IOException {


        // Search the application class path for any ComponentManager Provider.
        // Extra priveleges are needed for search to cover the entire classpath.

        Set providers = null;
        try{
            providers = (Set) AccessController.doPrivileged(
                new PrivilegedAction() {
                    public Object run() {
                        ClassLoader myCL = GEMSS.class.getClassLoader(); 
                        Set myProviders = findProviders( MANAGER_COMPONENT,
                                                                myCL );
                        if ( myProviders.isEmpty() ){
                            myCL =
                              Thread.currentThread().getContextClassLoader();
                            myProviders = 
                                findProviders( MANAGER_COMPONENT, myCL );
                        }
                        return myProviders;
                    }
                }
            );

        } catch ( SecurityException se ){
            throw new SecurityException( se.getMessage() );
        }

        if ( providers.isEmpty() ){
            throw new IOException( 
            "No ComponentManagerProvider found in application classpath" );
        }

        // Create and initialize a component manager .

        Provider aProvider = null;

        Iterator iop = providers.iterator();
        while ( iop.hasNext() ){
            aProvider = (Provider) iop.next();
            if ( providerName.equals( aProvider.getProviderName() ) ){
                break;
            }
        }

        // Set the provider name to the name of the provider we are actually
        // using.

        String myProvider = aProvider.getProviderName();

        if ( !myProvider.equals( providerName ) ){
            providerName = myProvider;
        }


        return (ComponentManager) aProvider.createComponent();
    }


    // Finds the specified service providers using the specified class loader.
    private static Set findProviders( String serviceName, ClassLoader loader){

        Set spis = new HashSet();

        if ( serviceName == null ) return spis;

        if ( loader == null ) loader = 
                        Thread.currentThread().getContextClassLoader();


        String resourceName = "META-INF/services/" + serviceName;

        try{
            // Locate service providers
            Enumeration enumURLs = loader.getResources( resourceName );
            while( enumURLs.hasMoreElements() ) {
                URL services = (URL) enumURLs.nextElement();
                BufferedReader in = new BufferedReader( 
                            new InputStreamReader( services.openStream() ));

                String inputLine;

                try{
                    while ( (inputLine = in.readLine() ) != null){

                        String spiClassName;
                        String nonComment;

                    // Parse the service provider files

                        int commentCharIndex =  inputLine.indexOf( '#' );
                        if ( commentCharIndex == 0 ){
                            continue;
                        } else if ( commentCharIndex != -1 ) {
                            nonComment = inputLine.substring( 0, 
                                                    commentCharIndex );
                            spiClassName = nonComment.trim();
                        } else{
                            spiClassName = inputLine.trim();
                        }

                    // Get the provider class and instantiate it

                        if ( !spiClassName.equals( "" ) ){
                            Object spi = null;
                            try {
                                Class spiClassDef = 
                                            Class.forName( spiClassName, true,
                                                           loader );
                                spi = spiClassDef.newInstance();
                            } catch ( InstantiationException ie ) {
                                logger.warning( ie.getMessage() );
                                continue;
                            } catch ( IllegalAccessException iae ) {
                                logger.warning( iae.getMessage() );
                                continue;
                            } catch ( ClassNotFoundException cnfe ) {
                                logger.warning( cnfe.getMessage() );
                                continue;
                            }

                            if ( spi != null ){
                                spis.add( spi );
                            }
                        }
                    }
                } catch ( IOException ioe ) {
                    logger.warning( ioe.getMessage() );
                    continue;
                }

                in.close();
            }
        } catch ( IOException ioe ) {
            logger.warning( ioe.getMessage() );
            return spis;
        }

        return spis;
    }

}
