/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 */
package org.apache.avalon.phoenix.components.embeddor;

import java.io.File;
import java.util.Date;
import java.util.Observable;
import java.util.Observer;
import org.apache.avalon.excalibur.i18n.ResourceManager;
import org.apache.avalon.excalibur.i18n.Resources;
import org.apache.avalon.excalibur.io.ExtensionFileFilter;
import org.apache.avalon.framework.CascadingException;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.DefaultServiceManager;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.phoenix.Constants;
import org.apache.avalon.phoenix.interfaces.Deployer;
import org.apache.avalon.phoenix.interfaces.Embeddor;
import org.apache.avalon.phoenix.interfaces.EmbeddorMBean;
import org.apache.avalon.phoenix.interfaces.Kernel;

/**
 * This is the object that is interacted with to create, manage and
 * dispose of the kernel and related resources.
 *
 * @author <a href="mail@leosimons.com">Leo Simons</a>
 * @author <a href="peter@apache.org">Peter Donald</a>
 * @author <a href="bauer@denic.de">Joerg Bauer</a>
 */
public class DefaultEmbeddor extends AbstractEmbeddor
                             implements EmbeddorMBean
{
    private static final String DEFAULT_APPS_PATH = "/apps";
    private EmbeddorObservable m_observable = new EmbeddorObservable();
    private String m_phoenixHome;
    /**
     * The default directory in which applications are deployed from.
     */
    private String m_appDir;
    
    /**
     * If true, flag indicates that the Embeddor should continue running
     * even when there are no applications in kernel. Otherwise the
     * Embeddor will shutdown when it detects there is no longer any
     * applications running.
     */
    private boolean m_persistent;
  
    
    protected Resources getResources()
    {
      return  ResourceManager.getPackageResources( DefaultEmbeddor.class ); 
    }
    /**
     * Pass the Context to the embeddor.
     * It is expected that the following will be entrys in context;
     * <ul>
     *   <li><b>common.classloader</b>: ClassLoader shared betweeen
     *      container and applications</li>
     *   <li><b>container.classloader</b>: ClassLoader used to load
     *      container</li>
     * </ul>
     *
     * @param context
     * @throws ContextException
     */
    public void contextualize( final Context context )
        throws ContextException
    {
        super.contextualize(context);
        try
        {
            final Observer observer = (Observer)context.get( Observer.class.getName() );
            m_observable.addObserver( observer );
        }
        catch( final ContextException ce )
        {
            final String message = REZ.getString( "embeddor.notice.no-restart" );
            getLogger().warn( message );
        }
    }
    
    /**
     * Set parameters for this component.
     * This must be called after contextualize() and before initialize()
     *
     * Make sure to provide all the neccessary information through
     * these parameters. All information it needs consists of strings.
     * There are two types of strings included in parameters. The first
     * type include parameters used to setup proeprties of the embeddor.
     * The second type include the implementation names of the components
     * that the Embeddor manages. For instance if you want to replace the
     * <code>ConfigurationRepository</code> with your own repository you
     * would pass in a parameter such as;</p>
     * <p>org.apache.avalon.phoenix.interfaces.ConfigurationRepository =
     * com.biz.MyCustomConfigurationRepository</p>
     *
     * <p>Of the other type of parameters, the following are supported by
     * the DefaultEmbeddor implementation of Embeddor. Note that some of
     * the embedded components may support other parameters.</p>
     * <ul>
     * <li><b>phoenix.home</b>, the home directory of phoenix. Defaults
     * to "..".</li>
     * <li><b>log-destination</b>, the file to save log
     * messages in. If omitted, ${phoenix.home}/logs/phoenix.log is used.</li>
     * <li><b>log-priority</b>, the priority at which log messages are filteres.
     * If omitted, then INFO will be default level used.</li>
     * <li><b>applications-directory</b>, the directory in which
     * the defaul applications to be loaded by the kernel are stored
     * (in .sar format). Defaults to ${phoenix.home}/apps</li>
     * </ul>
     *
     * @param parameters the Parameters for embeddor
     * @throws ParameterException if an error occurs
     */    
    public synchronized void parameterize( final Parameters parameters )
        throws ParameterException
    {
        super.parameterize(parameters);
        m_phoenixHome = getParameters().getParameter( "phoenix.home", ".." );
        m_persistent = getParameters().getParameterAsBoolean( "persistent", false );
        m_appDir = getParameters().getParameter( "phoenix.apps.dir",
                                              m_phoenixHome + DEFAULT_APPS_PATH );
    }
    
    protected void defaultDeployment() throws Exception
    {
      deployDefaultApplications();
    }
    
    protected void deployDefaultApplications()  throws Exception
    {
        //Name of optional application specified on CLI
        final String application =
            getParameters().getParameter( "application-location", null );
        if( null != application )
        {
            final File file = new File( application );
            deployFile( file );
        }
        if( null != m_appDir )
        {
            final File directory = new File( m_appDir );
            final ExtensionFileFilter filter = new ExtensionFileFilter( ".sar" );
            final File[] files = directory.listFiles( filter );
            if( null != files )
            {
                deployFiles( files );
            }
        }
    }

    private void deployFiles( final File[] files )   throws Exception
    {
        for( int i = 0; i < files.length; i++ )
        {
            deployFile( files[ i ] );
        }
    }

    private void deployFile( final File file )  throws Exception
    {
        final String filename = file.getName();
        int index = filename.lastIndexOf( '.' );
        if( -1 == index )
        {
            index = filename.length();
        }
        final String name = filename.substring( 0, index );
        final File canonicalFile = file.getCanonicalFile();
        deployFile( name, canonicalFile );
    }

    protected final synchronized void deployFile( final String name, final File file )
        throws Exception
    {
        final Deployer deployer = (Deployer)getEmbeddorComponent( Deployer.ROLE );
        deployer.deploy( name, file.toURL() );
    }
    
    protected  void mainLoop()
    {

        //  If the kernel is empty at this point, it is because the server was
        //  started without supplying any applications, display a message to
        //  give the user a clue as to why the server is shutting down
        //  immediately.
        if( emptyKernel() )
        {
            final String message = REZ.getString( "embeddor.error.start.no-apps" );
            getLogger().fatalError( message );
        }
        else
        {
            // loop until <code>Shutdown</code> is created.
            while( true )
            {
                // wait() for shutdown() to take action...
                if( wasShutdown() || ( emptyKernel() && !m_persistent ) )
                {
                    // The server will shut itself down when all applications are disposed.
                    if( emptyKernel() )
                    {
                        final String message =
                            REZ.getString( "embeddor.shutdown.all-apps-disposed" );
                        getLogger().info( message );
                    }
                    break;
                }
                gotoSleep();
            }
        }      
    }
    
    private boolean emptyKernel()
    {
        final Kernel kernel = getKernel();
        if( null != kernel )
        {
            final String[] names = kernel.getApplicationNames();
            return ( 0 == names.length );
        }
        else
        {
            //Consider the kernel empty
            //if it has been shutdown
            return true;
        }
    }

    private void gotoSleep()
    {
        try
        {
            synchronized( this )
            {
                wait( 1000 );
            }
        }
        catch( final InterruptedException e )
        {
        }
    }
     
    
    /**
     * Ask the embeddor to restart itself if this operation is supported.
     *
     * @throws UnsupportedOperationException if restart not supported
     */
    public void restart()
        throws UnsupportedOperationException
    {
        try
        {
            m_observable.change();
            m_observable.notifyObservers( "restart" );
        }
        catch( final Exception e )
        {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * Get name by which the server is know.
     * Usually this defaults to "Phoenix" but the admin
     * may assign another name. This is useful when you
     * are managing a cluster of Phoenix servers.
     *
     * @return the name of server
     */
    public String getName()
    {
        return Constants.SOFTWARE;
    }

    /**
     * Get location of Phoenix installation
     *
     * @return the home directory of phoenix
     */
    public String getHomeDirectory()
    {
        return m_phoenixHome;
    }

    /**
     * Get the date at which this server started.
     *
     * @return the date at which this server started
     */
    public Date getStartTime()
    {
        return new Date( getStartupTime() );
    }

    /**
     * Retrieve the number of millisecond
     * the server has been up.
     *
     * @return the the number of millisecond the server has been up
     */
    public long getUpTimeInMillis()
    {
        return System.currentTimeMillis() - getStartupTime();
    }

    /**
     * Retrieve a string identifying version of server.
     * Usually looks like "v4.0.1a".
     *
     * @return version string of server.
     */
    public String getVersion()
    {
        return Constants.VERSION;
    }

    /**
     * Get a string defining the build.
     * Possibly the date on which it was built, where it was built,
     * with what features it was built and so forth.
     *
     * @return the string describing build
     */
    public String getBuild()
    {
        return "(" + Constants.DATE + ")";
    }
    
    protected Parameters createChildParameters()
    {
        final Parameters parameters = new Parameters();
        parameters.merge( getParameters() );
        parameters.setParameter( "phoenix.apps.dir", m_appDir );
        return parameters;
    }
     /**
     * Allow subclasses to get access to kernel.
     *
     * @return the Kernel
     */
    protected final Kernel getKernel()
    {
        return (Kernel)getEmbeddorComponent( Kernel.ROLE );
    }
}
class EmbeddorObservable  extends Observable
{
    public void change()
    {
        super.setChanged();
    }
}

