/* Copyright 2005-2006 Tim Fennell * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sourceforge.stripes.tag; import net.sourceforge.stripes.action.ActionBean; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.config.Configuration; import net.sourceforge.stripes.controller.ActionResolver; import net.sourceforge.stripes.controller.DispatcherHelper; import net.sourceforge.stripes.controller.ExecutionContext; import net.sourceforge.stripes.controller.Interceptor; import net.sourceforge.stripes.controller.LifecycleStage; import net.sourceforge.stripes.controller.StripesFilter; import net.sourceforge.stripes.controller.DispatcherServlet; import net.sourceforge.stripes.exception.StripesJspException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.JspException; /** * <p>This tag supports the use of Stripes ActionBean classes as view helpers. * It allows for the use of actions as the controller and then their reuse * on the page, creating it if it does not exist. A typical usage pattern would * be for a page that contains two types of information, the interaction with each being * handled by separate ActionBean implementation. Some page events route to the first * action and others to the second, but the page still requires data from both in * order to render. This tag would define both ActionBeans in the page scope, creating * the one that wasn't executing the event.</p> * * <p>This class will bind parameters to a created ActionBean just as the execution of * an event on an ActionBean would. It does not rebind values to ActionBeans that * were previously created for execution of the action. Validation is not done * during this binding, except the type conversion required for binding, and no * validation errors are produced.</p> * * <p>The binding of the ActionBean to the page scope happens whether the ActionBean * is created or not, making for a consistent variable to always use when referencing * the ActionBean.</p> * * @author Greg Hinkle, Tim Fennell */ public class UseActionBeanTag extends StripesTagSupport { /** The UrlBinding of the ActionBean to create */ private String binding; /** The event, if any, to execute when creating */ private String event; /** A page scope variable to which to bind the ActionBean */ private String var; /** Indicates that validation should be executed. */ private boolean validate = false; /** Indicates whether the event should be executed even if the bean was already present. */ private boolean alwaysExecuteEvent = false; /** Indicates whether the resolution should be executed - false by default. */ private boolean executeResolution = false; /** * The main work method of the tag. Looks up the action bean, instantiates it, * runs binding and then runs either the named event or the default. * * @return SKIP_BODY in all cases. * @throws JspException if the ActionBean could not be instantiate and executed */ @Override public int doStartTag() throws JspException { // Check to see if the action bean already exists ActionBean actionBean = (ActionBean) getPageContext().findAttribute(binding); boolean beanNotPresent = actionBean == null; try { final Configuration config = StripesFilter.getConfiguration(); final ActionResolver resolver = StripesFilter.getConfiguration().getActionResolver(); final HttpServletRequest request = (HttpServletRequest) getPageContext().getRequest(); final HttpServletResponse response = (HttpServletResponse) getPageContext().getResponse(); Resolution resolution = null; ExecutionContext ctx = new ExecutionContext(); // Lookup the ActionBean if we don't already have it if (beanNotPresent) { ActionBeanContext tempContext = config.getActionBeanContextFactory().getContextInstance(request, response); tempContext.setServletContext(getPageContext().getServletContext()); ctx.setLifecycleStage(LifecycleStage.ActionBeanResolution); ctx.setActionBeanContext(tempContext); // Run action bean resolution ctx.setInterceptors(config.getInterceptors(LifecycleStage.ActionBeanResolution)); resolution = ctx.wrap( new Interceptor() { public Resolution intercept(ExecutionContext ec) throws Exception { ActionBean bean = resolver.getActionBean(ec.getActionBeanContext(), binding); ec.setActionBean(bean); return null; } }); } else { ctx.setActionBean(actionBean); ctx.setActionBeanContext(actionBean.getContext()); } // Then, if and only if an event was specified, run handler resolution if (resolution == null && event != null && (beanNotPresent || this.alwaysExecuteEvent)) { ctx.setLifecycleStage(LifecycleStage.HandlerResolution); ctx.setInterceptors(config.getInterceptors(LifecycleStage.HandlerResolution)); resolution = ctx.wrap( new Interceptor() { public Resolution intercept(ExecutionContext ec) throws Exception { ec.setHandler(resolver.getHandler(ec.getActionBean().getClass(), event)); ec.getActionBeanContext().setEventName(event); return null; } }); } // Make the PageContext available during the validation stage so that we // can execute EL based expression validation try { DispatcherHelper.setPageContext(getPageContext()); // Bind applicable request parameters to the ActionBean if (resolution == null && (beanNotPresent || this.validate == true)) { resolution = DispatcherHelper.doBindingAndValidation(ctx, this.validate); } // Run custom validations if we're validating if (resolution == null && this.validate == true) { String temp = config.getBootstrapPropertyResolver().getProperty( DispatcherServlet.RUN_CUSTOM_VALIDATION_WHEN_ERRORS); boolean validateWhenErrors = temp != null && Boolean.valueOf(temp); resolution = DispatcherHelper.doCustomValidation(ctx, validateWhenErrors); } } finally { DispatcherHelper.setPageContext(null); } // Fill in any validation errors if they exist if (resolution == null && this.validate == true) { resolution = DispatcherHelper.handleValidationErrors(ctx); } // And (again) if an event was supplied, then run the handler if (resolution == null && event != null && (beanNotPresent || this.alwaysExecuteEvent)) { resolution = DispatcherHelper.invokeEventHandler(ctx); } DispatcherHelper.fillInValidationErrors(ctx); // just in case! if (resolution != null && this.executeResolution) { DispatcherHelper.executeResolution(ctx, resolution); } // If a name was specified, bind the ActionBean into page context if (getVar() != null) { pageContext.setAttribute(getVar(), ctx.getActionBean()); } return SKIP_BODY; } catch(Exception e) { throw new StripesJspException("Unabled to prepare ActionBean for JSP Usage",e); } } /** * Does nothing. * @return EVAL_PAGE in all cases. */ @Override public int doEndTag() { return EVAL_PAGE; } /** * Sets the binding attribute by figuring out what ActionBean class is identified * and then in turn finding out the appropriate URL for the ActionBean. * * @param beanclass the FQN of an ActionBean class, or a Class object for one. */ public void setBeanclass(Object beanclass) throws StripesJspException { String url = getActionBeanUrl(beanclass); if (url == null) { throw new StripesJspException("The 'beanclass' attribute provided could not be " + "used to identify a valid and configured ActionBean. The value supplied was: " + beanclass); } else { this.binding = url; } } /** Get the UrlBinding of the requested ActionBean */ public String getBinding() { return binding; } /** Set the UrlBinding of the requested ActionBean */ public void setBinding(String binding) { this.binding = binding; } /** The event name, if any to execute. */ public String getEvent() { return event; } /** The event name, if any to execute. */ public void setEvent(String event) { this.event = event; } /** Gets the name of the page scope variable to which the ActionBean will be bound. */ public String getVar() { return var; } /** Sets the name of the page scope variable to which the ActionBean will be bound. */ public void setVar(String var) { this.var = var; } /** Alias for getVar() so that the JSTL and jsp:useBean style are allowed. */ public String getId() { return getVar(); } /** Alias for setVar() so that the JSTL and jsp:useBean style are allowed. */ public void setId(String id) { setVar(id); } public boolean isValidate() { return validate; } public void setValidate(boolean validate) { this.validate = validate; } public boolean isAlwaysExecuteEvent() { return alwaysExecuteEvent; } public void setAlwaysExecuteEvent(boolean alwaysExecuteEvent) { this.alwaysExecuteEvent = alwaysExecuteEvent; } public boolean isExecuteResolution() { return executeResolution; } public void setExecuteResolution(boolean executeResolution) { this.executeResolution = executeResolution; } }