/*
 * 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.struts;


import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletContext;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.util.MessageResources;
import org.apache.velocity.tools.view.context.ViewContext;
import org.apache.velocity.tools.view.tools.ContextTool;


/**
 * Struts integrated Context tool for easy access to message resources
 * and errors
 *
 * I realize this duplicates a lot of the functionality present in
 * StrutsHtmlTool, but as a user coming from a Turbine/Velocity background
 * rather than a Struts background, i feel this is a cleaner and 
 * easier tool for Velocity/Struts integration.  I can imagine that 
 * long-time Struts users may feel more comfortable using StrutsHtmlTool
 * as it somewhat mimics the struts taglibs, but since i am morally opposed
 * to JSP, i think it is unwise and inappropriate to make context tools 
 * which simply mimic some taglib behavior.  No offense intended to their
 * authors; i just think this way is better practice.
 * 
 *
 * @author <a href="mailto:nathan@esha.com">Nathan Bubna</a>
 * @version $Id: MessageTool.java,v 1.1 2002/02/07 22:36:59 nathan Exp $
 */

public class MessageTool implements ContextTool
{


    private static final String REQUEST = "request";

    private HttpServletRequest request;
    private HttpSession session;
    private ServletContext application;
    private MessageResources resources;
    private Locale locale;


    /**
     * Constructor to obtain a factory. Use
     * method {@link #init(ViewContext context)}
     * to obtain instances of the tool.
     */
    public MessageTool() {}


    /**
     * For internal use.
     *
     * Use method {@link #init(ViewContext context)}
     * to obtain instances of the tool.
     */
    private MessageTool(ViewContext context)
    {
        request = context.getRequest();
        session = request.getSession(false);
        application = context.getServletContext();
        if (application != null)
        {
            resources = (MessageResources)application.getAttribute(Action.MESSAGES_KEY);
        }
        if (resources == null)
        {
            //FIXME: i need some real logging!
            System.out.println("[MessageTool] couldn't find resources!");
        }
    }


    /************************* ContextTool interface *************************/

    public Object init(ViewContext context)
    {
        return new MessageTool(context);
    }


    public void destroy(Object obj) 
    {
        MessageTool tool = (MessageTool)obj;
        tool.request = null;
        tool.session = null;
        tool.application = null;
        tool.resources = null;
        tool.locale = null;
    }


    /**************************** MessageTool methods ****************************/

    /**
     * @return the current locale as found in the request or session
     */
    public Locale getLocale()
    {
        if (locale == null)
        {
            if (session == null) 
            {
                locale = request.getLocale();
            }
            else 
            {
                locale = (Locale)session.getAttribute(Action.LOCALE_KEY);
            }
        }
        return locale;
    }


    /**
     * @return message matching this key or <code>null</code>
     *         if none can be found
     * 
     * @use $msg.get('foo') or $msg.foo
     */
    public String get(String key)
    {
        if (resources == null)
        {
            return null;
        }
        return resources.getMessage(getLocale(), key);
    }


    /**
     * @return message matching this key based on the given
     *         params or <code>null</code> if none can be found
     */
    public String get(String key, Object args[])
    {
        if (args == null)
        {
            return get(key);
        }
        if (resources == null)
        {
            return null;
        }
        return resources.getMessage(getLocale(), key, args);
    }


    /**
     * @return true if a message matching this key is found in the resources
     */
    public boolean exists(String key)
    {
        if (resources == null)
        {
            return false;
        }
        return resources.isPresent(getLocale(), key);
    }


    /**
     * FIXME? does this really belong here?
     * Ok, this doesn't quite seem appropriate since the form doesn't
     * contain messages so to speak, but since this is typically needed
     * when errors are present, i suppose this is as good a place as 
     * any for now.  it may be that error and form access should be split
     * into a separate tool at some point.
     *
     * @return the ActionForm for this request, if it exists.
     */
    public ActionForm getActionForm() 
    {
        ActionMapping mapping = (ActionMapping)request.getAttribute(Action.MAPPING_KEY);
        if (mapping != null) 
        {
            if (REQUEST.equals(mapping.getScope()))
            {
                return (ActionForm)request.getAttribute(mapping.getAttribute());
            }
            if (session != null)
            {
                return (ActionForm)session.getAttribute(mapping.getAttribute());
            }
        }
        return null;
    }


    /**
     * @return <code>true</code if there are ActionErrors for this request
     *         otherwise return <code>false</code>
     */
    public boolean hasErrors()
    {
        ActionErrors errors = (ActionErrors)request.getAttribute(Action.ERROR_KEY);
        return (errors != null && !errors.empty());
    }


    /**
     * @return a map of all the struts errors
     *
     * the errors should be easily accessible in a Velocity template
     * e.g. 
     * <code> $!msg.errors.fooError </code>
     *
     * where the resources contain error messages like
     *
     * error.fooError=Foo is not ok!
     *
     */
    public ErrorMap getErrors()
    {
        return getErrors(null);
    }


    /**
     * @return a map of all the errors of a given property
     *
     * the errors should be easily accessible in a Velocity template
     * e.g. 
     * <code>
     * #set( $fooerrors = $msg.getErrors('foo') )
     * #set( $errorCount = $fooerrors.size() )
     * $!fooerrors.oops
     * $!fooerrors.get('oh.crap')
     * </code>
     *
     * where the resources contain error messages like
     *
     * error.oops=Oops!
     * error.oh.crap=Oh crap!! That sucks.
     *
     */
    public ErrorMap getErrors(String property)
    {
        ActionErrors errors = (ActionErrors)request.getAttribute(Action.ERROR_KEY);

        if (errors == null || errors.empty())
        {
            return null;
        }

        ErrorMap map = new ErrorMap(errors.size());

        Iterator reports;
        if (property == null)
        {
            reports = errors.get();
        }
        else
        {
            reports = errors.get(property);
        }

        while (reports.hasNext())
        {
            ActionError report = (ActionError)reports.next();
            String message = get(report.getKey(), report.getValues());
            if (message != null)
            {
                map.add(report.getKey(), message);
            }
            else
            {
                //FIXME: i need to get some real logging!!
                System.out.println("[MessageTool] No error message found for "+report.getKey());
            }
        }
        return map;
    }


    /**
     * Ok, i'll admit this is something of a hack, but since struts
     * was designed with JSP and taglibs in mind, something must be
     * done to make things work more nicely with Velocity.  it's either
     * this, or mess directly with the ActionErrors and ActionError classes
     * in templates.
     *
     * inner util class to make ActionErrors more Velocity-friendly
     */
    public class ErrorMap extends HashMap 
    {

        //this should correspond to the prefix for
        //error message keys in the message resources
        private static final String PREFIX = "error.";

        public ErrorMap(int capacity)
        {
            super(capacity);
        }

        /**
         * for adding messages to the map...
         */
        public void add(String key, String value)
        {
            super.put(key, value);
        }

        /**
         * Override to force use of add(String, String)
         * Not that i can imagine anyone would have cause to
         * use this, but just in case...
         */
        public Object put(Object key, Object value)
        {
            return null;
        }

        /**
         * Override to check for both key & PREFIX+key
         */
        public Object get(Object key)
        {
            String out = (String)super.get(key);
            if (out == null)
            {
                out = (String)super.get(PREFIX+key);
            }
            return out;
        }

    }


}