Cleaner URLs
------------

                 Key: STS-268
                 URL: http://mc4j.org/jira/browse/STS-268
             Project: Stripes
          Issue Type: New Feature
          Components: ActionBean Dispatching
    Affects Versions: Release 1.4
            Reporter: Patrick Lightbody
         Assigned To: Tim Fennell


To implement "cleaner URLs", I had to set up both a filter and a servlet in 
web.xml. I also had to create a custom ActionNameResolver.

web.xml:
---------------
    <filter>
        <filter-name>StripesActionFilter</filter-name>
        
<filter-class>com.cparty.stripes.util.CPartyActionBeanFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>StripesActionFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

    <servlet>
        <servlet-name>ServletHelper</servlet-name>
        
<servlet-class>com.cparty.stripes.util.CPartyServletHelper</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
---------------

Servlet:
---------------
package com.cparty.stripes.util;

import javax.servlet.http.HttpServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

public class CPartyServletHelper extends HttpServlet {
    public static CPartyServletHelper servlet;

    public void init(ServletConfig servletConfig) throws ServletException {
        super.init(servletConfig);
        CPartyServletHelper.servlet = this;
    }
}
---------------

Filter:
---------------
package com.cparty.stripes.util;

import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.controller.*;
import net.sourceforge.stripes.exception.StripesServletException;
import net.sourceforge.stripes.validation.BooleanTypeConverter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspFactory;
import javax.servlet.jsp.PageContext;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Stack;

public class CPartyActionBeanFilter implements Filter {
    public static final String RUN_CUSTOM_VALIDATION_WHEN_ERRORS =
            "Validation.InvokeValidateWhenErrorsExist";

    private Boolean alwaysInvokeValidate;
    private FilterConfig filterConfig;


    public void init(final FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse 
servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // It sucks that we have to do this here (in the request cycle), but 
there doesn't
        // seem to be a good way to get at the Configuration from the Filter in 
init()
        doOneTimeConfiguration();

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        if ("/".equals(request.getServletPath())) {
            filterChain.doFilter(request, response);
            return;
        }

        Configuration config = StripesFilter.getConfiguration();
        ActionBeanContext context = 
config.getActionBeanContextFactory().getContextInstance(request, response);
        try {
            // we look up the bean name to see if we should pass the request 
through or not
            
StripesFilter.getConfiguration().getActionResolver().getActionBean(context);
        } catch (StripesServletException e) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        ///////////////////////////////////////////////////////////////////////
        // Here beings the reall processing of the request!
        ///////////////////////////////////////////////////////////////////////
        PageContext pageContext = null;

        try {
            context.setServletContext(filterConfig.getServletContext());

            // Then setup the ExecutionContext that we'll use to process this 
request
            final ExecutionContext ctx = new ExecutionContext();
            
ctx.setInterceptors(config.getInterceptors(LifecycleStage.ActionBeanResolution));
            ctx.setLifecycleStage(LifecycleStage.ActionBeanResolution);
            ctx.setActionBeanContext(context);

            // It's unclear whether this usage of the JspFactory will work in 
all containers. It looks
            // like it should, but still, we should be careful not to screw up 
regular request
            // processing if it should fail. Why do we do this?  So we can have 
a container-agnostic
            // way of getting an ExpressionEvaluator to do expression based 
validation
            try {
                ActionBeanContext abc = ctx.getActionBeanContext();
                pageContext = 
JspFactory.getDefaultFactory().getPageContext(CPartyServletHelper.servlet, // 
the servlet inst
                        abc.getRequest(), // req
                        abc.getResponse(), // res
                        null,   // error page url
                        true,   // need session
                        abc.getResponse().getBufferSize(),
                        true); // autoflush
                DispatcherHelper.setPageContext(pageContext);
            }
            catch (Exception e) {
                // Don't even log this, this failure gets reported if action 
beans actually
                // try and make use of expression validation, otherwise this is 
just noise
            }

            // Resolve the ActionBean, and if an interceptor returns a 
resolution, bail now
            Resolution resolution = resolveActionBean(ctx);
            saveActionBean(request);

            if (resolution == null) {
                resolution = resolveHandler(ctx);

                if (resolution == null) {
                    // Then run binding and validation
                    resolution = doBindingAndValidation(ctx);

                    if (resolution == null) {
                        // Then continue on to custom validation
                        resolution = doCustomValidation(ctx);

                        if (resolution == null) {
                            // And then validation error handling
                            resolution = handleValidationErrors(ctx);

                            if (resolution == null) {
                                // And finally(ish) invoking of the event 
handler
                                resolution = invokeEventHandler(ctx);

                                // If the event produced errors, fill them in
                                DispatcherHelper.fillInValidationErrors(ctx);
                            }
                        }
                    }
                }
            }

            // Whatever stage it came from, execute the resolution
            if (resolution != null) {
                executeResolution(ctx, resolution);
            }
        }
        catch (ServletException se) {
            throw se;
        }
        catch (RuntimeException re) {
            throw re;
        }
        catch (InvocationTargetException ite) {
            if (ite.getTargetException() instanceof ServletException) {
                throw (ServletException) ite.getTargetException();
            } else if (ite.getTargetException() instanceof RuntimeException) {
                throw (RuntimeException) ite.getTargetException();
            } else {
                throw new StripesServletException
                        ("ActionBean execution threw an exception.", 
ite.getTargetException());
            }
        }
        catch (Exception e) {
            throw new StripesServletException("Exception encountered processing 
request.", e);
        }
        finally {
            // Make sure to release the page context
            if (pageContext != null) {
                JspFactory.getDefaultFactory().releasePageContext(pageContext);
                DispatcherHelper.setPageContext(null);
            }
            restoreActionBean(request);
        }
    }

    public void destroy() {
    }

    /**
     * Responsible for resolving the ActionBean for the current request. 
Delegates to
     * [EMAIL PROTECTED] DispatcherHelper#resolveActionBean(ExecutionContext)}.
     */
    protected Resolution resolveActionBean(ExecutionContext ctx) throws 
Exception {
        return DispatcherHelper.resolveActionBean(ctx);
    }

    /**
     * Responsible for resolving the event handler method for the current 
request. Delegates to
     * [EMAIL PROTECTED] DispatcherHelper#resolveHandler(ExecutionContext)}.
     */
    protected Resolution resolveHandler(ExecutionContext ctx) throws Exception {
        return DispatcherHelper.resolveHandler(ctx);
    }

    /**
     * Responsible for executing binding and validation for the current 
request. Delegates to
     * [EMAIL PROTECTED] 
DispatcherHelper#doBindingAndValidation(ExecutionContext, boolean)}.
     */
    protected Resolution doBindingAndValidation(ExecutionContext ctx) throws 
Exception {
        return DispatcherHelper.doBindingAndValidation(ctx, true);
    }

    /**
     * Responsible for executing custom validation methods for the current 
request. Delegates to
     * [EMAIL PROTECTED] DispatcherHelper#doCustomValidation(ExecutionContext, 
boolean)}.
     */
    protected Resolution doCustomValidation(ExecutionContext ctx) throws 
Exception {
        return DispatcherHelper.doCustomValidation(ctx, alwaysInvokeValidate);
    }

    /**
     * Responsible for handling any validation errors that arise during 
validation. Delegates to
     * [EMAIL PROTECTED] 
DispatcherHelper#handleValidationErrors(ExecutionContext)}.
     */
    protected Resolution handleValidationErrors(ExecutionContext ctx) throws 
Exception {
        return DispatcherHelper.handleValidationErrors(ctx);
    }

    /**
     * Responsible for invoking the event handler if no validation errors 
occur. Delegates to
     * [EMAIL PROTECTED] DispatcherHelper#invokeEventHandler(ExecutionContext)}.
     */
    protected Resolution invokeEventHandler(ExecutionContext ctx) throws 
Exception {
        return DispatcherHelper.invokeEventHandler(ctx);
    }

    /**
     * Responsible for executing the Resolution for the current request. 
Delegates to
     * [EMAIL PROTECTED] DispatcherHelper#executeResolution(ExecutionContext, 
Resolution)}.
     */
    protected void executeResolution(ExecutionContext ctx, Resolution 
resolution) throws Exception {
        DispatcherHelper.executeResolution(ctx, resolution);
    }

    /**
     * Performs a simple piece of one time configuration that requires access 
to the
     * Configuration object delivered through the Stripes Filter.
     */
    private void doOneTimeConfiguration() {
        if (alwaysInvokeValidate == null) {
            // Check to see if, in this application, validate() methods should 
always be run
            // even when validation errors already exist
            String callValidateWhenErrorsExist = 
StripesFilter.getConfiguration()
                
.getBootstrapPropertyResolver().getProperty(RUN_CUSTOM_VALIDATION_WHEN_ERRORS);

            if (callValidateWhenErrorsExist != null) {
                BooleanTypeConverter c = new BooleanTypeConverter();
                this.alwaysInvokeValidate = 
c.convert(callValidateWhenErrorsExist, Boolean.class, null);
            }
            else {
                this.alwaysInvokeValidate = false; // Default behaviour
            }
        }
    }

    /**
     * Fetches, and lazily creates if required, a Stack in the request to store 
ActionBeans
     * should the current request involve forwards or includes to other 
ActionBeans.
     *
     * @param request the current HttpServletRequest
     * @return the Stack if present, or if creation is requested
     */
    protected Stack getActionBeanStack(HttpServletRequest request, boolean 
create) {
        Stack stack = (Stack) 
request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN_STACK);
        if (stack == null && create) {
            stack = new Stack();
            request.setAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN_STACK, 
stack);
        }

        return stack;
    }

    /**
     * Saves the current value of the 'actionBean' attribute in the request so 
that it
     * can be restored at a later date by calling [EMAIL PROTECTED] 
#restoreActionBean(HttpServletRequest)}.
     * If no ActionBean is currently stored in the request, nothing is changed.
     *
     * @param request the current HttpServletRequest
     */
    protected void saveActionBean(HttpServletRequest request) {
        if (request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN) != 
null) {
            Stack stack = getActionBeanStack(request, true);
            
stack.push(request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN));
        }
    }

    /**
     * Restores the previous value of the 'actionBean' attribute in the 
request. If no
     * ActionBeans have been saved using [EMAIL PROTECTED] 
#saveActionBean(HttpServletRequest)} then this
     * method has no effect.
     *
     * @param request the current HttpServletRequest
     */
    protected void restoreActionBean(HttpServletRequest request) {
        Stack stack = getActionBeanStack(request, false);
        if (stack != null && !stack.empty()) {
            request.setAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN, 
stack.pop());
        }
    }
}
---------------

ActionNameResolver:
---------------
package com.cparty.stripes.util;

import net.sourceforge.stripes.controller.NameBasedActionResolver;

/**
 * We prefer "home" rather than "Home.action", which this class takes care of.
 */
public class CPartyNameBasedActionResolver extends NameBasedActionResolver {
    protected String getUrlBinding(String string) {
        String original = super.getUrlBinding(string);

        int end = original.indexOf(".action");
        String temp = original.substring(0, end);
        int begin = temp.lastIndexOf("/");
        String name = original.substring(begin + 1, end);
        name = name.substring(0, 1).toLowerCase() + name.substring(1);

        return original.substring(0, begin + 1).toLowerCase() + name ;
    }
}
---------------

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: 
http://mc4j.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        

-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to