/* 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;
}
}