[ http://mc4j.org/jira/browse/STS-268?page=comments#action_10461 ] Kai Grabfelder commented on STS-268: ------------------------------------
"Cleaner url" handling is a point that I would like to see in stripes very much, too. Stripes looks really promsing - for URL routing I would like to recommand a system similar to my favourite php webframework, symfony. The symfony documentation describes on http://www.symfony-project.com/book/trunk/routing their routing setup. In symfony you match urls to certain "actions" (just method calls in certain classes). The goodie is that you can define an url-routing like this: article_by_title: url: /articles/show/:articleName param: { module: article, action: showName} let me excplain this in short: - article_by_title is just the name of the routing mapping (which coresponds to the fully qualified class name of the action bean in stripes) - url: the url that should be mapped to this action :articleName is a placeholder - param: specifies which method in which class file to call Now the cool thing is that during run time the value of the :articleName placeholder can be accessed throuth a simple httpRequest.getRequestParameter("articleName") inside the action that is called Now it would be cool if this could be transfered to stripes with an annotation like the following @UrlBinding("/articles/show/:articleName") and the value of articleName beeing available in the httpRequest or automatically bind to a property name articleName in the actionBean. I'm not sure what the proposed CPartyActionBeanFilter does (I had no chance to look at it yet) but something like that I've wrote above would be a real benefit for the usefulness and "easy to configure'ness" of stripes... cheers Kai > 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 ------------------------------------------------------------------------- Take Surveys. Earn Cash. Influence the Future of IT Join SourceForge.net's Techsay panel and you'll get the chance to share your opinions on IT & business topics through brief surveys -- and earn cash http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV _______________________________________________ Stripes-development mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/stripes-development
