/*
 * Title:        Factory
 * Description:
 *
 * This software is published under the terms of the OpenSymphony Software
 * License version 1.1, of which a copy has been included with this
 * distribution in the LICENSE.txt file.
 */

package com.opensymphony.module.sitemesh;

import com.opensymphony.module.sitemesh.factory.FactoryException;
import com.opensymphony.module.sitemesh.util.ClassLoaderUtil;
import com.opensymphony.module.sitemesh.util.Container;

import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Factory responsible for creating appropriate instances of implementations.
 * This is specific to a web context and is obtained through {@link #getInstance(com.opensymphony.module.sitemesh.Config)}.
 *
 * <p>The actual Factory method used is determined by the enviroment entry <code>sitemesh.factory</code>.
 * If this doesn't exist, it defaults to {@link com.opensymphony.module.sitemesh.factory.DefaultFactory} .</p>
 *
 * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
 * @version $Revision: 1.9 $
 */
public abstract class Factory implements PageParserSelector {
    /** Web context lookup key */
    private static final String SITEMESH_FACTORY = "sitemesh.factory";

    /**
     * Entry-point for obtaining singleton instance of Factory. The default factory class
     * that will be instantiated can be overridden with the environment
     * entry <code>sitemesh.factory</code>.
     */
    public static Factory getInstance(Config config) {
        Factory instance = (Factory)config.getServletContext().getAttribute(SITEMESH_FACTORY);
        if (instance == null) {
            String factoryClass = getEnvEntry("sitemesh.factory", "com.opensymphony.module.sitemesh.factory.DefaultFactory");
            try {
                Class cls = ClassLoaderUtil.loadClass(factoryClass, config.getClass());
                Constructor con = cls.getConstructor(new Class[] { Config.class });
                instance = (Factory)con.newInstance(new Config[] { config });
                config.getServletContext().setAttribute(SITEMESH_FACTORY, instance);
            } catch (InvocationTargetException e) {
                throw new FactoryException("Cannot construct Factory : " + factoryClass, e.getTargetException());
        
            } catch (Exception e) {
                throw new FactoryException("Cannot construct Factory : " + factoryClass, e);
            }
        }
        instance.refresh();
        return instance;
    }

    public abstract void refresh();

    /** Return instance of DecoratorMapper. */
    public abstract DecoratorMapper getDecoratorMapper();

    /**
     * Create a PageParser suitable for the given content-type.
     *
     * <p>For example, if the supplied parameter is <code>text/html</code>
     * a parser shall be returned that can parse HTML accordingly.</p> Never returns null.
     *
     * @param contentType The MIME content-type of the data to be parsed
     * @return Appropriate <code>PageParser</code> for reading data
     *
     */
    public abstract PageParser getPageParser(String contentType);

    /** Determine whether a Page of given content-type should be parsed or not. */
    public abstract boolean shouldParsePage(String contentType);

    /**
     * Determine whether the given path should be excluded from decoration or not.
     */
    public abstract boolean isPathExcluded(String path);

    /** Find String environment entry, or return default if not found. */
    private static String getEnvEntry(String envEntry, String defaultValue) {
        String result = null;
        try {
            if (Container.get() != Container.JRUN) {
                // TODO: JRun really isn't happy with this
                InitialContext ctx = new InitialContext();
                Object o = ctx.lookup("java:comp/env/" + envEntry);
                ctx.close();
                result = (String)PortableRemoteObject.narrow(o, String.class); // rmi-iiop friendly.
            }
        }
        catch (Exception e) { } // failed - don't moan, just return default.
        catch (NoClassDefFoundError e) { } // to deal with restricted class loaders (i.e. on AppEngine).
        return result == null || result.trim().length() == 0 ? defaultValue : result;
    }
}