/*
* Copyright 2005-2006 the original author or authors.
*
* 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 org.strecks.web.struts;
import org.apache.struts.action.RequestProcessor;
/**
* Implements extension of <code>RequestProcessor</code> to allow for Strecks specific functionality - namely,
* handling <code>ValidBindingForm</code>, and <code>StatefulAction</code>
* @author Phil Zoio
* @deprecated
*/
public class StatefulActionRequestProcessor extends RequestProcessor
{
/*private static Log log = LogFactory.getLog(StatefulActionRequestProcessor.class);
private Map<Class, Map<String, InjectionWrapper>> injectionMap = new HashMap<Class, Map<String, InjectionWrapper>>();
private Map<Class, ValidationInfo> validatorMap = new HashMap<Class, ValidationInfo>();
private Map<Class, BindConvertInfo> bindHandlerMap = new HashMap<Class, BindConvertInfo>();
/**
* Delegates to superclass to create instance of <code>ActionForm</code>. If this instance is not of type
* <code>ValidBindingForm</code>, then proceeds as normal. <br>
* <br>
* If the form is a <code>ValidBindingForm</code> instance, then the form is inspected for validation and binding
* annotations. The validation and binding metadata is then used to build the <code>Validator</code> and
* <code>BindHandler</code> instances and associated contextual information necessary for performing
* annotation-based binding and validation
@Override
protected ActionForm processActionForm(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping)
{
ActionForm form = super.processActionForm(request, response, mapping);
if (form instanceof ValidBindingForm)
{
Class formClass = form.getClass();
BindConvertInfo bindMap = null;
synchronized (bindHandlerMap)
{
bindMap = bindHandlerMap.get(formClass);
if (bindMap == null)
{
BindAnnotationReader bindablesReader = new BindAnnotationReader();
bindMap = bindablesReader.readBindables(form);
bindHandlerMap.put(formClass, bindMap);
}
}
ValidationInfo validMap = null;
synchronized (validatorMap)
{
// possibly the java.util.concurrent library
validMap = validatorMap.get(formClass);
if (validMap == null)
{
ValidationAnnotationReader reader = new ValidationAnnotationReader();
validMap = reader.readValidationHandlers(form, bindMap);
validatorMap.put(formClass, validMap);
}
}
ValidBindingForm validatorForm = (ValidBindingForm) form;
validatorForm.setValidationInfo(validMap);
validatorForm.setBindConvertInfo(bindMap);
boolean isPosted = "POST".equalsIgnoreCase(request.getMethod());
validatorForm.setPosted(isPosted);
return validatorForm;
}
return form;
}
/**
* Overrides default error storing mechanism so that errors stored under the key <code>Globals.ERROR_KEY</code>
* are added to the session automatically. This allows the "Redirect After Post" pattern to be supported for forms
* failing validation in addition to successful form submissions. Similarly, any errors previously in session are
* added to request scope but removed from the session.
@Override
protected boolean processValidate(HttpServletRequest request, HttpServletResponse response, ActionForm form,
ActionMapping mapping) throws IOException, ServletException
{
if (mapping.getValidate())
{
// clear out errors from any previous validation from session
request.getSession().setAttribute(Globals.ERROR_KEY, null);
}
boolean validate = super.processValidate(request, response, form, mapping);
if (validate == false)
{
// if validation failed, then add the errors to the session
Object errors = request.getAttribute(Globals.ERROR_KEY);
request.getSession().setAttribute(Globals.ERROR_KEY, errors);
}
return validate;
}
/**
* Works in a similar way to the superclass, using the superclass (<code>RequestProcessor</code> to create
* <code>Action</code> subclass instances. Adds additional logic to check if the returned action is a
* <code>StatefulAction</code> implementation. If so, the <code>InjectionAnnotationReader</code> is used to load
* all the <code>InjectionHandler</code>s and associated contextual information so for subsequent injection of
* action class dependencies. In addition, the prototype instance of <code>StatefulAction</code> is cloned, so
* that it can store request specific state in a threadsafe manner. Note that the <code>InjectionHandler</code>s
* themselves do not store any thread-specific state
@Override
protected Action processActionCreate(HttpServletRequest request, HttpServletResponse response,
ActionMapping actionMapping) throws IOException
{
String type = actionMapping.getType();
Action action = null;
synchronized (actions)
{
action = (Action) actions.get(type);
if (action == null)
{
action = super.processActionCreate(request, response, actionMapping);
// don't need to synchronize on inputMap - same scope as actions
if (action instanceof StatefulAction)
{
// now hold input parameter list to be extracted
InjectionAnnotationReader injectionAnnotationReader = new InjectionAnnotationReader();
injectionAnnotationReader.readAnnotations(action.getClass());
Map<String, InjectionWrapper> inputInfos = injectionAnnotationReader.getInjectionMap();
injectionMap.put(action.getClass(), inputInfos);
}
}
}
// clone to make this thread safe. When the request is finished it
// should be garbage collected, because
// there will be no other references to this action
if (action instanceof StatefulAction)
{
action = ((StatefulAction) action).clone();
}
return action;
}
/**
* Extensively re-implements <code>RequestProcessor.processActionPerform()</code> in the following ways:
* <ul>
* <li>adds <code>beforePeform()</code> and <code>afterPerform()</code> callbacks</li>
* <li>checks to see if action class implements <code>StatefulAction</code>. If so, then
* <ul>
* <li>consumes any redirect parameters (by removing them from session scope and adding them to the request scope</li>
* <li>sets the <code>cancelled</code> property of <code>StatefulAction</code></li>
* <li>calls <code>StatefulAction.setEnvironment()</code>, so that this does not have to passed into
* <code>execute()</code></li>
* <li>calls <code>doInjection()</code> to inject any dependencies into the <code>StatefulAction</code>
* implementation</li>
* <li>calls <code>StatefulAction.preBind()</code>, so that any pre-binding initialization can occur (e.g.
* reference data lookup, etc)</li>
* <li>performs any inward binding of data, if necessary</li>
* <li>calls <code>StatefulAction.execute()</code>, so that any pre-binding initialization can occur (e.g.
* reference data lookup, etc)</li>
* <li>performs any outward binding of domain model data to forms, if necessary</li>
* ation can occur (e.g. reference data lookup, etc)</li>
* <li>If the form has been cancelled, then the last four steps (from <code>preBind()</code> onwards, are
* omitted. Instead, <code>statefulAction.cancel()</code> is called</li>
* <li>If any exception is thrown, either by <code>cancel()</code>, <code>preBind()</code> or any of the
* methods following it, <code>statefulAction.handleRuntimeException(e)</code> is called, giving the action class
* an opportunity to handle the exception</li>
* <li>the <code>StatefulAction</code> instance is then added to the request scope under the key
* <code>InfrastructureKeys.ACTION_CLASS</code> </li>
* </ul>
* </li>
* <li>if the request is using the GET method, the URL is added to the "history list", allowing for implementing
* back operations and reverting control to previous screens</li>
* <li>if the <code>ActionForward</code> is a <code>PageClassForward</code> instance, the
* <code>PageClass</code> instance is attached to request scope using the key
* <code>InfrastructureKeys.PAGE_CLASS</code>. If it is a <code>RedirectForward</code>, this object is added
* to request scope using the key <code>InfrastructureKeys.REDIRECT</code>, and the redirect parameters are added
* to session scope using the key <code>InfrastructureKeys.REDIRECT_PARAMETERS</code></li>
* <li>if the request is using the GET method, the URL is added to the "history list", allowing for implementing
* back operations and reverting control to previous screens</li>
*
* </ul>
*
*
@Override
protected ActionForward processActionPerform(HttpServletRequest request, HttpServletResponse response,
Action action, ActionForm form, ActionMapping mapping) throws IOException, ServletException
{
log.info("Starting process action perform " + request.getRequestURI());
log.info("Using " + action.getClass().getName());
ActionForward actionForward;
try
{
actionForward = beforePerform(action, mapping, request, response);
if (actionForward == null)
{
if (action instanceof StatefulAction)
{
// consume any redirect parameters
Map redirectParams = (Map) request.getSession()
.getAttribute(InfrastructureKeys.REDIRECT_PARAMETERS);
if (redirectParams != null)
{
request.getSession().removeAttribute(InfrastructureKeys.REDIRECT_PARAMETERS);
request.setAttribute(InfrastructureKeys.REDIRECT_PARAMETERS, redirectParams);
}
StatefulAction statefulAction = (StatefulAction) action;
boolean cancelled = false;
if (request.getAttribute(Globals.CANCEL_KEY) != null)
{
cancelled = true;
}
// set up request environment for stateful action
statefulAction.setEnvironment(mapping, form, request, response);
ActionContext context = new ActionContextImpl(request, response, getServletContext(), form, mapping);
// inject action inputs
doInjection(statefulAction, context);
try
{
if (cancelled)
actionForward = statefulAction.cancel();
else
{
// notify action that binding is about to occur.
// prior to domain object bound from form
statefulAction.preBind();
// do incoming bindings
bindInwards(form, request);
actionForward = statefulAction.execute();
// do outgoing bindings
bindOutwards(form, request);
}
}
catch (RuntimeException e)
{
actionForward = statefulAction.handleRuntimeException(e);
}
request.setAttribute(InfrastructureKeys.ACTION_BEAN, statefulAction);
}
else
{
// execute regular Struts actions
actionForward = action.execute(mapping, form, request, response);
}
if (request.getMethod().equalsIgnoreCase("GET"))
{
HttpSession session = request.getSession();
@SuppressWarnings("unchecked")
List<String> goodURLs = (List<String>) session.getAttribute(InfrastructureKeys.GOOD_URL_HISTORY);
if (goodURLs == null)
{
goodURLs = new ArrayList<String>();
}
goodURLs.add(buildURL(request));
session.setAttribute(InfrastructureKeys.GOOD_URL_HISTORY, goodURLs);
}
}
}
catch (Exception e)
{
log.error(e);
request.setAttribute(InfrastructureKeys.APPLICATION_EXCEPTION, e);
actionForward = super.processException(request, response, e, form, mapping);
}
if (actionForward instanceof PageForward)
{
// save the instance of the PageClass
PageForward pageClassForward = (PageForward) actionForward;
Page pageClass = pageClassForward.getPage();
pageClass.setHttpServletResponse(response);
request.setAttribute(InfrastructureKeys.PAGE_BEAN, pageClass);
}
else
{
if (actionForward instanceof RedirectForward)
{
RedirectForward r = (RedirectForward) actionForward;
request.setAttribute(InfrastructureKeys.REDIRECT, actionForward);
request.getSession().setAttribute(InfrastructureKeys.REDIRECT_PARAMETERS, r.getSessionParameters());
}
}
log.info("Ended action perform of " + request.getRequestURI() + StringUtils.LINE_SEPARATOR);
return actionForward;
}
/**
* Binds outwards only for new forms. Once form is populated then no additional outward data binding should be
* necessary
protected void bindOutwards(ActionForm form, HttpServletRequest request)
{
if (form instanceof ValidBindingForm)
{
BindingForm v = (BindingForm) form;
if (v.getBindOutwards())
{
v.bindOutwards();
}
}
}
/**
* Bind inwards for posts which are not cancelled
protected void bindInwards(ActionForm form, HttpServletRequest request)
{
if (form instanceof ValidBindingForm)
{
ValidBindingForm v = (ValidBindingForm) form;
// if method is post and cancel key is not set then bind inwards
if (v.isPosted() && request.getAttribute(Globals.CANCEL_KEY) == null)
{
v.bindInwards();
}
}
}
/**
* Run check prior to invocation of access. Main purpose: user logon check. Only return ActionForward if state is
* incorrect (eg logging on needs to occur)
protected ActionForward beforePerform(Action action, ActionMapping mapping, HttpServletRequest request,
HttpServletResponse response)
{
return null;
}
/**
* Do any post action finalization. Guarranteed to run because this is in finally block
protected void afterPerform(Action action, ActionMapping mapping, HttpServletRequest request,
HttpServletResponse response)
{
}
private void doInjection(StatefulAction action, ActionContext context)
{
Map<String, InjectionWrapper> inputHandlerMap = injectionMap.get(action.getClass());
if (inputHandlerMap != null)
{
Set<String> keySet = inputHandlerMap.keySet();
for (String propertyName : keySet)
{
InjectionWrapper wrapper = inputHandlerMap.get(propertyName);
wrapper.inject(action, context);
}
}
}
private String buildURL(HttpServletRequest request)
{
String servletPath = request.getServletPath();
String queryString = request.getQueryString();
if (queryString != null)
{
servletPath += "?" + queryString;
}
return servletPath;
}*/
}
|