/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002 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.servlet;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;

import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.view.servlet.VelocityViewServlet;


/**
 * Extension of the VelocityViewServlet to handle layout rendering
 * and a custom error screen.
 *
 * @author <a href="mailto:nathan@esha.com">Nathan Bubna</a>
 * @version $Id: ToolInfo.java,v 1.1 2002/05/10 05:42:18 sidler Exp $
 */

public class VelocityLayoutServlet extends VelocityViewServlet {


    //TODO: move these constants to a properties file?

    // template key for the screen content
    public static String KEY_SCREEN_CONTENT = "screen_content";

    // template key for setting the layout in a template
    public static String KEY_LAYOUT = "layout";

    // template keys for error data
    public static String KEY_ERROR_CAUSE = "cause";
    public static String KEY_ERROR_STACKTRACE = "stack_trace";
    public static String KEY_ERROR_INVOCATION_EXCEPTION = "invocation_exception";

    // default template names
    public static String DEFAULT_ERROR_SCREEN = "Error.vm";
    public static String DEFAULT_LAYOUT = "DefaultLayout.vm";


    /**
     * Override to check the request for an alternate layout
     *
     * @param request client request
     * @param response client response
     * @return the Context to fill
     */
    protected Context createContext(HttpServletRequest request,
                                    HttpServletResponse response) {

        Context ctx = super.createContext(request, response);

        // check if an alternate layout has been specified 
        // by way of the request parameters
        String layout = request.getParameter(KEY_LAYOUT);
        if (layout != null) {
            // let the template know what its new layout is
            ctx.put(KEY_LAYOUT, layout);
        }
        return ctx;
    }


    /**
     * Overrides VelocityServlet.mergeTemplate to do a two-pass 
     * render for handling layouts
     */
    protected void mergeTemplate(Template template, 
                                 Context context, 
                                 HttpServletResponse response)
        throws ResourceNotFoundException, ParseErrorException, 
               MethodInvocationException, IOException,
               UnsupportedEncodingException, Exception {


        //
        // this section is based on Tim Colson's "two pass render"
        //
        // Render the screen content
        StringWriter sw = new StringWriter();
        template.merge(context, sw);
        // Add the resulting content to the context
        context.put(KEY_SCREEN_CONTENT, sw.toString());

        // Check for an alternate layout
        //
        // we check after merging the screen template so the screen 
        // can overrule any layout set in the request parameters
        // by doing #set( $layout = "MyLayout.vm" )
        String layout = (String)context.get(KEY_LAYOUT);
        if (layout == null) {
            // no alternate, use default
            layout = DEFAULT_LAYOUT;
        }

        try {
            //load the layout template
            template = getTemplate(layout);
        } catch (Exception e) {
            getServletContext().log("Can't get layout \"" + layout + "\": " + e);

            // if it was an alternate layout we couldn't get...
            if (layout != DEFAULT_LAYOUT) {
                // try to get the default layout
                // if this also fails, let the exception go
                template = getTemplate(DEFAULT_LAYOUT);
            }
        }

        // Render the layout template into the response
        super.mergeTemplate(template, context, response);
    }


    /**
     * Override to display custom error template
     */
    protected void error(HttpServletRequest request, 
                         HttpServletResponse response, 
                         Exception e)
        throws ServletException, IOException {

        try {
            // get a velocity context
            Context ctx = createContext(request, response);

            Throwable cause = e;

            // if it's an MIE, i want the real cause and stack trace!
            if (cause instanceof MethodInvocationException) {
                // put the invocation exception in the context
                ctx.put(KEY_ERROR_INVOCATION_EXCEPTION, e);
                // get the real cause
                cause = ((MethodInvocationException)e).getWrappedThrowable();
            }

            // add the cause to the context
            ctx.put(KEY_ERROR_CAUSE, cause);
                
            // grab the cause's stack trace and put it in the context
            StringWriter sw = new StringWriter();
            cause.printStackTrace(new java.io.PrintWriter(sw));
            ctx.put(KEY_ERROR_STACKTRACE, sw.toString());

            // retrieve and render the error template
            Template errorTemplate = getTemplate(DEFAULT_ERROR_SCREEN);
            mergeTemplate(errorTemplate, ctx, response);

            // ok, i know this is just a dummy method right now,
            // but i'm liable to forget about this if that ever changes
            requestCleanup(request, response, ctx);

        } catch (Exception e2) {
            // d'oh! punt this to a higher authority
            super.error(request, response, e);
        }
    }


}

