/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) @year@ The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Jakarta", "Avalon", "Excalibur" and "Apache Software Foundation"
    must not be used to endorse or promote products derived from this  software
    without  prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/
package org.apache.excalibur.fortress;

import java.util.Iterator;
import java.util.Map;
import org.apache.avalon.excalibur.logger.LoggerManager;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.DefaultContext;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.logger.NullLogger;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.DefaultServiceManager;
import org.apache.commons.collections.StaticBucketMap;
import org.apache.excalibur.event.command.CommandManager;
import org.apache.excalibur.event.command.TPCThreadManager;
import org.apache.excalibur.event.command.ThreadManager;
import org.apache.excalibur.instrument.InstrumentManager;
import org.apache.excalibur.instrument.manager.DefaultInstrumentManager;
import org.apache.excalibur.mpool.DefaultPoolManager;
import org.apache.excalibur.mpool.PoolManager;

/**
 * The Kernel sets up the Fortress containers and core services that are
 * shared between the containers.  It is very easy to set up and use, and
 * its settings are all derived from a configuration file.  Here is a
 * typical usage:
 *
 * <pre>
 *   public class Main
 *   {
 *      public static int main( String[] )
 *      {
 *          Kernel kernel = new Kernel();
 *          kernel.initialize();
 *
 *          // do whatever in the main loop.
 *          MyRootContainer container = (MyRootContainer) kernel.getRootContainer();
 *          container.doStuff();
 *
 *          // When we are done we dispose of everything.
 *          kernel.dispose();
 *      }
 *   }
 * </pre>
 *
 * <p>
 *   The configuration location will be set up in specific locations in a
 *   Context object.
 * </p>
 *
 * @author <a href="bloritsch@apache.org">Berin Loritsch</a>
 * @version CVS $Revision: 1.3 $ $Date: 2002/11/08 02:32:30 $
 */
public final class Kernel implements Disposable, Initializable
{
    // Only used on startup--need to make them unnecessary.
    private Configuration m_kernelConfig;

    private Map m_rootContainers;
    private TPCThreadManager m_threadManager;
    private CommandManager m_commandManager;
    private PoolManager m_poolManager;
    private LoggerManager m_loggerManager;
    private InstrumentManager m_instrumentManager;
    private Context m_rootContext;
    private DefaultServiceManager m_serviceManager;

    /**
     * Initialize the kernel with the default location for the kernel.xconf
     * file.  The default location and name for the "kernel.xconf" file is
     * <code>"../conf/kernel.xconf"</code>.  This constructor also suppresses
     * any and all debug information that may occur before the
     * {@link LoggerManager} is set up.
     */
    public Kernel()
        throws Exception
    {
        this( "../conf/kernel.xconf" );
    }

    /**
     * Initialize the kernel with the supplied location of the kernel.xconf
     * file.  If the supplied location is a JAR file, then the Kernel will
     * look inside the JAR with the following path:
     * <code>"META-INF/fortress/kernel.xconf"</code>.  This constructor also
     * suppresses any and all debug information that may occur before the
     * {@link LoggerManager} is set up.
     */
    public Kernel( String configURI )
        throws Exception
    {
        this( configURI, new NullLogger() );
    }

    /**
     * Initialize the kernel with the supplied location of the kernel.xconf
     * file.  If the supplied location is a JAR file, then the Kernel will
     * look inside the JAR with the following path:
     * <code>"META-INF/fortress/kernel.xconf"</code>.  This constructor also
     * allows you to pass in a Logger object so that you can see what is
     * happening while the Kernel is going through its initialization stages.
     */
    public Kernel( String configURI, Logger logger )
        throws Exception
    {
        if( null == logger )
            throw new IllegalArgumentException( "You must pass in a logger for " +
                                                getClass().getName() + "'s constructor" );
        if( null == configURI )
            throw new IllegalArgumentException( "You must pass in a configuration URI for " +
                                                getClass().getName() + "'s constructor" );
        m_rootContainers = new StaticBucketMap( 17 );
        m_rootContext = new DefaultContext();
        m_kernelConfig = loadKernelConfig( logger, configURI );
        m_loggerManager = initializeLoggerManager( logger );
        m_serviceManager = new DefaultServiceManager();
    }

    public void initialize()
        throws Exception
    {
        Configuration[] containers = m_kernelConfig.getChildren( "container" );
        if( containers.length == 0 )
            throw new ConfigurationException( "There must be at least one container!" );

        m_serviceManager.put( LoggerManager.class.getName(), m_loggerManager );

        // Set up the ThreadManager
        Configuration threadManagerConfig = m_kernelConfig.getChild( "thread-manager" );
        m_threadManager = new TPCThreadManager();
        initializeService( m_threadManager, "thread-manager", threadManagerConfig );
        m_serviceManager.put( ThreadManager.class.getName(), m_threadManager );

        // Set up the CommandManager and PoolManager
        m_commandManager = new CommandManager();
        m_threadManager.register( m_commandManager );
        m_poolManager = new DefaultPoolManager( m_commandManager.getCommandSink() );
        m_serviceManager.put( CommandManager.class.getName(), m_commandManager );
        m_serviceManager.put( PoolManager.class.getName(), m_poolManager );

        m_instrumentManager = new DefaultInstrumentManager();
        initializeService( m_instrumentManager, "instrument-manager", m_kernelConfig );
        m_serviceManager.put( InstrumentManager.class.getName(), m_instrumentManager );
        m_serviceManager.makeReadOnly();

        for( int i = 0; i < containers.length; i++ )
        {
            Class klass = getClass().getClassLoader().loadClass( containers[ i ].getAttribute( "class" ) );
            Object container = klass.newInstance();
            initializeService( container, containers[ i ].getAttribute( "name" ), containers[ i ] );
        }
    }

    public void dispose()
    {
        disposeService( m_instrumentManager );

        Iterator it = m_rootContainers.keySet().iterator();
        while( it.hasNext() )
        {
            disposeService( it.next() );
            it.remove();
        }

        disposeService( m_poolManager );
        disposeService( m_commandManager );
        m_threadManager.deregisterAll();
        disposeService( m_threadManager );
    }

    /**
     * Get the default root container.  This is the most common case.
     *
     * @return the default container
     */
    public Object getRootContainer()
    {
        return getRootContainer( "default" );
    }

    /**
     * Get a named root container.  This only has any real effect if there are
     * more than one root container.
     *
     * @param name  The name of the root container
     * @return the container we want--or <code>null</code> if there is no matching container
     */
    public Object getRootContainer( String name )
    {
        return m_rootContainers.get( name );
    }

    private Configuration loadKernelConfig( final Logger initLogger, final String configURI )
        throws ConfigurationException
    {
        Configuration config = null;
        String uri = configURI;

        if( configURI.endsWith( ".jar" ) )
        {
            initLogger.debug( "The configuration URI is a jar file, looking inside..." );
            uri = "jar:" + uri + "!/META-INF/fortress/kernel.xconf";
        }

        config = loadConfig( initLogger, uri );

        return config;
    }

    private Configuration loadConfig( Logger initLogger, String configURI )
        throws ConfigurationException
    {
        Configuration config = null;

        if( initLogger.isDebugEnabled() )
            initLogger.debug( "Looking for the config here: " + configURI );

        try
        {
            DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
            config = builder.build( configURI );
        }
        catch( ConfigurationException ce )
        {
            throw ce;
        }
        catch( Exception e )
        {
            throw new ConfigurationException( "Could not load configuration", e );
        }

        if( initLogger.isDebugEnabled() )
            initLogger.debug( "Found and loaded configuration successfully" );

        return config;
    }

    private Configuration getLoggerManagerConfig( Logger initLogger )
        throws ConfigurationException
    {
        Configuration config = m_kernelConfig.getChild( "logger-manager" );
        String loggerConfigURI = config.getAttribute( "config-uri", "" );

        if( loggerConfigURI.equals( "" ) )
        {
            initLogger.debug( "The logger configuration was completely local" );
        }
        else
        {
            initLogger.debug( "The logger configuration is external to the kernel config" );
            config = loadConfig( initLogger, loggerConfigURI );
        }

        return config;
    }

    private LoggerManager initializeLoggerManager( Logger logger )
        throws Exception
    {
        Configuration config = getLoggerManagerConfig( logger );
        LoggerManager logManager = (LoggerManager)getClass().getClassLoader()
            .loadClass( config.getAttribute( "class" ) ).newInstance();
        initializeService( logManager, logger, config );
        return logManager;
    }

    /**
     * Start up a service with all the necessary pieces.
     *
     * @param service  The service to start up
     * @param config   The configuration for the service
     *
     * @throws Exception  when someting goes wrong during startup.
     */
    private void initializeService( Object service, String name, Configuration config )
        throws Exception
    {
        initializeService( service, m_loggerManager.getLoggerForCategory( name ), config );
    }

    /**
     * Start up a service with all the necessary pieces.
     *
     * @param service  The service to start up
     * @param logger   The intended Logger
     * @param config   The configuration for the service
     *
     * @throws Exception  when someting goes wrong during startup.
     */
    private void initializeService( Object service, Logger logger, Configuration config )
        throws Exception
    {
        ContainerUtil.enableLogging( service, logger );
        ContainerUtil.contextualize( service, m_rootContext );
        ContainerUtil.service( service, m_serviceManager );
        ContainerUtil.configure( service, config );
        ContainerUtil.parameterize( service, Parameters.fromConfiguration( config ) );
        ContainerUtil.initialize( service );
    }

    /**
     * Close down the service.
     *
     * @param service  The service to be disposed of.
     */
    private void disposeService( Object service )
    {
        try
        {
            ContainerUtil.stop( service );
        }
        catch( Exception e )
        {
            m_loggerManager.getDefaultLogger().error( "Can't stop service, will attempt to discontinue anyway", e );
        }

        ContainerUtil.dispose( service );
    }
}

