/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, 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 "Apache" and "Apache Software Foundation" and
 *     "Apache Jetspeed" 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" or
 *    "Apache Jetspeed", 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 (INCLUDING, 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.jetspeed.services.resources;

// Java Core Classes
import java.io.File;
import java.io.IOException;
import java.util.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;

// Turbine stuff.
import org.apache.commons.configuration.Configuration;
import org.apache.turbine.services.resources.ResourceService;
import org.apache.turbine.services.resources.TurbineResourceService;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.InitializationException;
import org.apache.turbine.util.Log;

/**
 * <p>This implementation of the <code>resourcesService</code> relies
 * on an external properties file for storing the configuration keys
 * and values</p>
 *
 * <P>In order to be compatible with legacy applications, this implementation
 * kept a static method for initializing the service, so it's still possible
 * to write the following code:
 * <p><code>
 * TurbineResourceService.setPropertiesName("d:/conf/Turbine.properties");
 * Vector myVar = TurbineResources.getVector("myvar");
 * </code></p>
 *
 * <p>This implementation allows the use of several pre-defined variables within
 *    the configuration file. The variables are identified by the following
 *    sequence of tokens: ${<varname>}. Varname is always folded to lowercase.</p>
 *  <P>The predefined variables are:
 *    <ul>
 *       <li>webapp.dir: base directory for the web application
 *       <li>jvm.dir: JVM startup directory
 *     </ul>
 *  </p>
 *  <p>The init parameters of the servlet are also imported as default variables.
 *     They may override the previously defined default variables
 *  </p>
 *
 * @author <a href="mailto:raphael@apache.org">Raphaël Luta</a>
 * @version $Id: VariableResourcesService.java,v 1.12 2003/03/04 00:05:10 sgala Exp $
 */
public class VariableResourcesService extends TurbineResourceService
{

    public static final String WEBAPP_DIR="webapp.dir";
    public static final String WEB_DIR="web.dir";
    public static final String JVM_DIR="jvm.dir";
    public static final String START_TOKEN="${";
    public static final String END_TOKEN="}";
    
    /** The container for the generic resources. */
    private Hashtable variables = null;

    /** The container for the generic resources. */
    private Hashtable strings = null;
    private Hashtable vectors = null;
    private Hashtable arrays = null;


    /**
     * Late init. Don't return control until early init says we're done.
     */
    public void init( )
    {
        while( !getInit() ) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie ) {
                Log.info("VariableResources service: Waiting for init()..." );
            }
        }
    }


    /**
     * This method is called when the Service is initialized
     *
     * @param config a ServletConfig object
     */
    public synchronized void init(ServletConfig config) throws InitializationException
    {        
        if (getInit()) return;
        String props = config.getInitParameter(TurbineServices.PROPERTIES_PATH_KEY);

        variables = new Hashtable();
        strings = new Hashtable();
        vectors = new Hashtable();
        arrays = new Hashtable();
        initVariables(config);

        super.init(config);
    }

    /**
     * Initializer method that sets up the generic resources.
     *
     * @param confs A Configurations object.
     */
    private void initVariables(ServletConfig config)
    {
        ServletContext ctxt = config.getServletContext();
        
        String path = ctxt.getRealPath("/");
        
        // define web app dir
        if (path != null) {
            variables.put(WEBAPP_DIR, normalizePath(path) );
        }
        
        // FIXME. the following code blocks on Tomcat 
        // when loaded on startup
        /*
        path = ctxt.getContext("/").getRealPath("/");
        if (path != null ) {
            variables.put(WEB_DIR, normalizePath(path) );
        }
        */

        // define JVM app dir
        try {
            path = new File(".").getCanonicalPath();
            if (path != null) {
                variables.put(JVM_DIR, normalizePath(path) );
            }
        } catch (IOException e) {
            //very unlikely that the JVM can't 
            //resolve its path
        }

        // load servlet init parameters as variables, they may override
        // the previously defined variables. All param names are folded
        // to lower case
        
        Enumeration en = config.getInitParameterNames();
        while( en.hasMoreElements() ) {
            String paramName = (String)en.nextElement();
            String paramValue = config.getInitParameter(paramName);
            variables.put(paramName.toLowerCase(),paramValue);
        }
            
    }

    private static String normalizePath(String path) {
        // change all separators to forward
        // slashes
        if (File.separatorChar != '/') {
            path = path.replace(File.separatorChar,'/');
        }
        
        // remove any trailing slash
        if (path.endsWith("/")) {
            path = path.substring(0,path.length()-1);
        }
        
        return path;
    }
    
    protected void setVariables(Hashtable vars)
    {
        synchronized (this)
        {
            this.variables = vars;
            this.strings = new Hashtable();
            this.vectors = new Hashtable();
            this.arrays = new Hashtable();
        }
    }
    
    protected String substituteString( String base ) {
        if (base == null) return null;
        
        int begin = -1;
        int end = -1;
        int prec = 0-END_TOKEN.length();
        String var = null;
        StringBuffer result = new StringBuffer();
        
        // FIXME: we should probably allow the escaping of the start token
        while ( ((begin=base.indexOf(START_TOKEN,prec+END_TOKEN.length()))>-1)
                && ((end=base.indexOf(END_TOKEN,begin))>-1) ) {
            
            result.append(base.substring(prec+END_TOKEN.length(),begin));
            var = base.substring(begin+START_TOKEN.length(),end);
            if (variables.get(var)!=null) {
                result.append(variables.get(var));
            }
            prec=end;
        }
        result.append(base.substring(prec+END_TOKEN.length(),base.length()));
        
        return result.toString();
    }
        
        
    /**
     * The purpose of this method is to get the configuration resource
     * with the given name as a string.
     *
     * @param name The resource name.
     * @return The value of the resource as a string.
     */
    public String getString(String name)
    {
        String std = (String)strings.get(name);
        
        if (std == null) {
            std = substituteString(super.getString(name));
            if (std != null) strings.put(name,std);
        }
        
        return std;
    }

    /**
     * The purpose of this method is to get the configuration resource
     * with the given name as a string, or a default value.
     *
     * @param name The resource name.
     * @param def The default value of the resource.
     * @return The value of the resource as a string.
     */
    public String getString(String name,
                                   String def)
    {
        String std = getString(name);
        
        if (std == null)
            std = substituteString(def);
    
        return std;
    }

    /**
     * The purpose of this method is to get the configuration resource
     * with the given name as a string array.
     *
     * @param name The resource name.
     * @return The value of the resource as a string array.
     */
    public String[] getStringArray(String name)
    {
        String[] std = (String[])arrays.get(name);
        if (std==null) {
            std = super.getStringArray(name);
            if (std != null) {
                for(int i=0;i<std.length;i++) {
                    std[i]=substituteString(std[i]);
                }
                arrays.put(name,std);
            }
        }
        
        return std;
    }

    /**
     * The purpose of this method is to get the configuration resource
     * with the given name as a vector.
     *
     * @param name The resource name.
     * @return The value of the resource as a vector.
     */
    public Vector getVector(String name)
    {
        Vector std = (Vector)vectors.get(name);
        
        if (std==null) {
            std = super.getVector(name);
            if (std != null) {
                Vector newstd = new Vector();
                Enumeration en = std.elements();
                while (en.hasMoreElements()) {
                    newstd.addElement(substituteString((String)en.nextElement()));
                }
                std = newstd;
                vectors.put(name,std);
            }
        }
        
        return std;
    }

    /**
     * The purpose of this method is to get the configuration resource
     * with the given name as a vector, or a default value.
     *
     * @param name The resource name.
     * @param def The default value of the resource.
     * @return The value of the resource as a vector.
     */
    public Vector getVector(String name,
                                   Vector def)
    {
        Vector std = getVector(name); 
        if ( std == null) {
            if (def != null) {
                std = new Vector();
                Enumeration en = def.elements();
                while (en.hasMoreElements()) {
                    std.addElement(substituteString((String)en.nextElement()));
                }
            }
        }

        return std;
    }

    /**
     * The purpose of this method is to extract a subset of configuraton
     * resources sharing a common name prefix. The prefix is stripped
     * from the names of the resulting resources.
     *
     * @param prefix the common name prefix
     * @return A ResourceService providing the subset of configuration.
     */
    public ResourceService getResources(String prefix)
    {
        Configuration config = getConfiguration().subset(prefix);
        
        if (config == null)
        {
            return null;
        }
        
        VariableResourcesService res = new VariableResourcesService();
        try { res.init(config); } catch (Exception e) {}
        res.setVariables(this.variables);
        return (ResourceService)res;
    }


}
