/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 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 acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Velocity", 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 names without prior written
 *    permission of the Apache Group.
 *
 * 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.velocity.tools.view.tools;


import java.io.InputStream;
import java.util.List;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import javax.servlet.http.HttpSession;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.apache.velocity.tools.view.context.ToolboxContext;
import org.apache.velocity.tools.view.context.ViewContext;


/**
 * Loads tools from toolbox.xml config file.
 *
 * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>?or should Gabe be credited?
 * @author <a href="mailto:nathan@esha.com">Nathan Bubna</a>
 *
 * @version $Id: ToolboxManager.java,v 1.1 2002/01/03 20:21:55 geirm Exp $
 * 
 */
public class ToolboxManager
{

    public static final String TOOL_KEY = "key";
    public static final String TOOL_SCOPE = "scope";
    public static final String TOOL_CLASS = "class";
    public static final String TOOL_INSTANCE = "instance";

    public static final String REQUEST_SCOPE = "request";
    public static final String SESSION_SCOPE = "session";
    public static final String GLOBAL_SCOPE  = "global";

    public static final String SESSION_TOOLS_KEY = "sessionTools";

    private SAXReader saxReader;
    private Map globalTools;
    private ArrayList sessionTools;
    private ArrayList requestTools;


    /**
     * Default constructor
     */
    public ToolboxManager()
    {
        saxReader = new SAXReader();
        globalTools = new HashMap();
        sessionTools = new ArrayList();
        requestTools = new ArrayList();
    }


    //FIXME:  log to file, not standard out
    private void log(String s) {
        System.out.println("ToolboxManager - "+s);
    }


    /**
     * Reads an XML document from an {@link InputStream}
     * using <a href="http://dom4j.org">dom4j</a> and
     * sets up the toolbox for the servlet from that.
     * 
     * Assumes toolbox.xml has a format like
     *
     *  <toolbox>
     *    <tool>
     *      <key>foo</key>
     *      <scope>request</scope>
     *      <class>com.mycompany.tools.Foo</class>
     *    </tool>
     *    <tool>
     *      <key>bar</key>
     *      <class>org.yourorganization.tools.Bar</class>
     *    </tool>
     *  </toolbox>
     *
     * @param input the InputStream to read from
     */
    public void load(InputStream input) throws Exception
    {
        log("Loading the toolbox...");
        Document document = saxReader.read(input);
        List tools = document.selectNodes("//toolbox/*");

        Iterator i = tools.iterator();
        while(i.hasNext())
        {
            Element e = (Element)i.next();
            String name = e.getName();
            log("Loading " + name);

            //read tool's key
            Node n = e.selectSingleNode(TOOL_KEY);
            String key = n.getText();
            log("Context key: " + key);

            //try to read the tool's scope
            n = e.selectSingleNode(TOOL_SCOPE);
            String scope;
            if (n != null)
            {
                scope = n.getText();
            }
            else
            {
                //default scope is global
                scope = GLOBAL_SCOPE;
            }
            log("Scope: " + scope);

            //read the tool's class
            n = e.selectSingleNode(TOOL_CLASS);
            String classname = n.getText();
            log("Class: " + classname);
            
            //create an instance of the tool
            Object obj;
            try
            {
                obj = Class.forName(classname).newInstance();
            }
            catch(NoClassDefFoundError ncdfe)
            {
               log("Error creating classname: " + ncdfe);
               //don't bother trying to store the tool
               continue;
            }
            
            //store the instance in the appropriate place and
            //enforce implementation of ContextTool for non-global tools
            try
            {
                if (scope.equalsIgnoreCase(REQUEST_SCOPE))
                {
                    requestTools.add(new ToolInfo(key, (ContextTool)obj));
                }
                else if (scope.equalsIgnoreCase(GLOBAL_SCOPE))
                {
                    globalTools.put(key, obj);
                }
                else if (scope.equalsIgnoreCase(SESSION_SCOPE))
                {
                    sessionTools.add(new ToolInfo(key, (ContextTool)obj));
                }
                else
                {
                    log("Unknown scope: \""+scope+"\". Setting tool scope to global.");
                    globalTools.put(key, obj);
                }
            }
            catch (ClassCastException cce)
            {
                log("Request and session scope tools must implement "+ContextTool.class.getName()+".  Setting tool scope to global.");
                globalTools.put(key, obj);
            }
        }

        log("Done loading the toolbox.");
    }


    /**
     * Creates a {@link ToolboxContext} from the tools loaded
     * in this manager.  It uses the given {@link ViewContext}
     * to create instances of session and request tools.  Request
     * tools are created on every call to this method. Session
     * tools are created once per session.  Global tool instances
     * are re-used for the entire runtime.
     *
     * @param ctx the current view context
     * @return the created ToolboxContext
     */
    public ToolboxContext getToolboxContext(ViewContext ctx)
    {
        //create the toolbox map with the global tools
        Map toolbox = new HashMap(globalTools);
        
        if (!sessionTools.isEmpty())
        {
            HttpSession session = ctx.getRequest().getSession();

            //get the initialized session tools
            Map stmap = (Map)session.getAttribute(SESSION_TOOLS_KEY);

            //if session tools aren't initialized, 
            //do so and store them in the session
            if (stmap == null)
            {
                stmap = new HashMap(sessionTools.size());
                Iterator i = sessionTools.iterator();
                while(i.hasNext())
                {
                    ToolInfo info = (ToolInfo)i.next();
                    stmap.put(info.getKey(), info.getInstance(ctx));
                }
                session.setAttribute(SESSION_TOOLS_KEY, stmap);
            }

            //add the initialized session tools to the toolbox
            toolbox.putAll(stmap);
        }
        
        //add and initialize request tools
        Iterator i = requestTools.iterator();
        while(i.hasNext())
        {
            ToolInfo info = (ToolInfo)i.next();
            toolbox.put(info.getKey(), info.getInstance(ctx));
        }
        
        return new ToolboxContext(toolbox);
    }


    /**
     * This class holds a ContextTool's key and
     * original instance.
     */
    protected final class ToolInfo
    {
        private String key;
        private ContextTool tool;
        
        ToolInfo(String key, ContextTool tool)
        {
            this.key = key;
            this.tool = tool;
        }
        
        String getKey()
        {
            return key;
        }
        
        Object getInstance(ViewContext ctx)
        {
            return tool.getInstance(ctx);
        }
        
    }


}
