001: /*
002: * Copyright 2005-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005: * in compliance with the License. You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software distributed under the License
010: * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011: * or implied. See the License for the specific language governing permissions and limitations under
012: * the License.
013: */
014:
015: package org.strecks.web.struts;
016:
017: import org.apache.struts.action.RequestProcessor;
018:
019: /**
020: * Implements extension of <code>RequestProcessor</code> to allow for Strecks specific functionality - namely,
021: * handling <code>ValidBindingForm</code>, and <code>StatefulAction</code>
022: * @author Phil Zoio
023: * @deprecated
024: */
025: public class StatefulActionRequestProcessor extends RequestProcessor {
026:
027: /*private static Log log = LogFactory.getLog(StatefulActionRequestProcessor.class);
028:
029: private Map<Class, Map<String, InjectionWrapper>> injectionMap = new HashMap<Class, Map<String, InjectionWrapper>>();
030:
031: private Map<Class, ValidationInfo> validatorMap = new HashMap<Class, ValidationInfo>();
032:
033: private Map<Class, BindConvertInfo> bindHandlerMap = new HashMap<Class, BindConvertInfo>();
034:
035: /**
036: * Delegates to superclass to create instance of <code>ActionForm</code>. If this instance is not of type
037: * <code>ValidBindingForm</code>, then proceeds as normal. <br>
038: * <br>
039: * If the form is a <code>ValidBindingForm</code> instance, then the form is inspected for validation and binding
040: * annotations. The validation and binding metadata is then used to build the <code>Validator</code> and
041: * <code>BindHandler</code> instances and associated contextual information necessary for performing
042: * annotation-based binding and validation
043:
044: @Override
045: protected ActionForm processActionForm(HttpServletRequest request, HttpServletResponse response,
046: ActionMapping mapping)
047: {
048:
049: ActionForm form = super.processActionForm(request, response, mapping);
050: if (form instanceof ValidBindingForm)
051: {
052:
053: Class formClass = form.getClass();
054:
055: BindConvertInfo bindMap = null;
056: synchronized (bindHandlerMap)
057: {
058: bindMap = bindHandlerMap.get(formClass);
059: if (bindMap == null)
060: {
061: BindAnnotationReader bindablesReader = new BindAnnotationReader();
062: bindMap = bindablesReader.readBindables(form);
063: bindHandlerMap.put(formClass, bindMap);
064: }
065: }
066:
067: ValidationInfo validMap = null;
068:
069: synchronized (validatorMap)
070: {
071: // possibly the java.util.concurrent library
072: validMap = validatorMap.get(formClass);
073: if (validMap == null)
074: {
075: ValidationAnnotationReader reader = new ValidationAnnotationReader();
076: validMap = reader.readValidationHandlers(form, bindMap);
077: validatorMap.put(formClass, validMap);
078: }
079: }
080:
081: ValidBindingForm validatorForm = (ValidBindingForm) form;
082: validatorForm.setValidationInfo(validMap);
083: validatorForm.setBindConvertInfo(bindMap);
084:
085: boolean isPosted = "POST".equalsIgnoreCase(request.getMethod());
086: validatorForm.setPosted(isPosted);
087:
088: return validatorForm;
089: }
090: return form;
091:
092: }
093:
094: /**
095: * Overrides default error storing mechanism so that errors stored under the key <code>Globals.ERROR_KEY</code>
096: * are added to the session automatically. This allows the "Redirect After Post" pattern to be supported for forms
097: * failing validation in addition to successful form submissions. Similarly, any errors previously in session are
098: * added to request scope but removed from the session.
099:
100: @Override
101: protected boolean processValidate(HttpServletRequest request, HttpServletResponse response, ActionForm form,
102: ActionMapping mapping) throws IOException, ServletException
103: {
104:
105: if (mapping.getValidate())
106: {
107: // clear out errors from any previous validation from session
108: request.getSession().setAttribute(Globals.ERROR_KEY, null);
109: }
110:
111: boolean validate = super.processValidate(request, response, form, mapping);
112:
113: if (validate == false)
114: {
115: // if validation failed, then add the errors to the session
116: Object errors = request.getAttribute(Globals.ERROR_KEY);
117: request.getSession().setAttribute(Globals.ERROR_KEY, errors);
118: }
119: return validate;
120: }
121:
122: /**
123: * Works in a similar way to the superclass, using the superclass (<code>RequestProcessor</code> to create
124: * <code>Action</code> subclass instances. Adds additional logic to check if the returned action is a
125: * <code>StatefulAction</code> implementation. If so, the <code>InjectionAnnotationReader</code> is used to load
126: * all the <code>InjectionHandler</code>s and associated contextual information so for subsequent injection of
127: * action class dependencies. In addition, the prototype instance of <code>StatefulAction</code> is cloned, so
128: * that it can store request specific state in a threadsafe manner. Note that the <code>InjectionHandler</code>s
129: * themselves do not store any thread-specific state
130:
131: @Override
132: protected Action processActionCreate(HttpServletRequest request, HttpServletResponse response,
133: ActionMapping actionMapping) throws IOException
134: {
135:
136: String type = actionMapping.getType();
137: Action action = null;
138:
139: synchronized (actions)
140: {
141: action = (Action) actions.get(type);
142: if (action == null)
143: {
144: action = super.processActionCreate(request, response, actionMapping);
145:
146: // don't need to synchronize on inputMap - same scope as actions
147: if (action instanceof StatefulAction)
148: {
149: // now hold input parameter list to be extracted
150: InjectionAnnotationReader injectionAnnotationReader = new InjectionAnnotationReader();
151: injectionAnnotationReader.readAnnotations(action.getClass());
152:
153: Map<String, InjectionWrapper> inputInfos = injectionAnnotationReader.getInjectionMap();
154:
155: injectionMap.put(action.getClass(), inputInfos);
156: }
157: }
158: }
159:
160: // clone to make this thread safe. When the request is finished it
161: // should be garbage collected, because
162: // there will be no other references to this action
163: if (action instanceof StatefulAction)
164: {
165: action = ((StatefulAction) action).clone();
166: }
167: return action;
168: }
169:
170: /**
171: * Extensively re-implements <code>RequestProcessor.processActionPerform()</code> in the following ways:
172: * <ul>
173: * <li>adds <code>beforePeform()</code> and <code>afterPerform()</code> callbacks</li>
174: * <li>checks to see if action class implements <code>StatefulAction</code>. If so, then
175: * <ul>
176: * <li>consumes any redirect parameters (by removing them from session scope and adding them to the request scope</li>
177: * <li>sets the <code>cancelled</code> property of <code>StatefulAction</code></li>
178: * <li>calls <code>StatefulAction.setEnvironment()</code>, so that this does not have to passed into
179: * <code>execute()</code></li>
180: * <li>calls <code>doInjection()</code> to inject any dependencies into the <code>StatefulAction</code>
181: * implementation</li>
182: * <li>calls <code>StatefulAction.preBind()</code>, so that any pre-binding initialization can occur (e.g.
183: * reference data lookup, etc)</li>
184: * <li>performs any inward binding of data, if necessary</li>
185: * <li>calls <code>StatefulAction.execute()</code>, so that any pre-binding initialization can occur (e.g.
186: * reference data lookup, etc)</li>
187: * <li>performs any outward binding of domain model data to forms, if necessary</li>
188: * ation can occur (e.g. reference data lookup, etc)</li>
189: * <li>If the form has been cancelled, then the last four steps (from <code>preBind()</code> onwards, are
190: * omitted. Instead, <code>statefulAction.cancel()</code> is called</li>
191: * <li>If any exception is thrown, either by <code>cancel()</code>, <code>preBind()</code> or any of the
192: * methods following it, <code>statefulAction.handleRuntimeException(e)</code> is called, giving the action class
193: * an opportunity to handle the exception</li>
194: * <li>the <code>StatefulAction</code> instance is then added to the request scope under the key
195: * <code>InfrastructureKeys.ACTION_CLASS</code> </li>
196: * </ul>
197: * </li>
198: * <li>if the request is using the GET method, the URL is added to the "history list", allowing for implementing
199: * back operations and reverting control to previous screens</li>
200: * <li>if the <code>ActionForward</code> is a <code>PageClassForward</code> instance, the
201: * <code>PageClass</code> instance is attached to request scope using the key
202: * <code>InfrastructureKeys.PAGE_CLASS</code>. If it is a <code>RedirectForward</code>, this object is added
203: * to request scope using the key <code>InfrastructureKeys.REDIRECT</code>, and the redirect parameters are added
204: * to session scope using the key <code>InfrastructureKeys.REDIRECT_PARAMETERS</code></li>
205: * <li>if the request is using the GET method, the URL is added to the "history list", allowing for implementing
206: * back operations and reverting control to previous screens</li>
207: *
208: * </ul>
209: *
210: *
211:
212: @Override
213: protected ActionForward processActionPerform(HttpServletRequest request, HttpServletResponse response,
214: Action action, ActionForm form, ActionMapping mapping) throws IOException, ServletException
215: {
216:
217: log.info("Starting process action perform " + request.getRequestURI());
218: log.info("Using " + action.getClass().getName());
219: ActionForward actionForward;
220:
221: try
222: {
223: actionForward = beforePerform(action, mapping, request, response);
224: if (actionForward == null)
225: {
226:
227: if (action instanceof StatefulAction)
228: {
229:
230: // consume any redirect parameters
231: Map redirectParams = (Map) request.getSession()
232: .getAttribute(InfrastructureKeys.REDIRECT_PARAMETERS);
233: if (redirectParams != null)
234: {
235: request.getSession().removeAttribute(InfrastructureKeys.REDIRECT_PARAMETERS);
236: request.setAttribute(InfrastructureKeys.REDIRECT_PARAMETERS, redirectParams);
237: }
238:
239: StatefulAction statefulAction = (StatefulAction) action;
240: boolean cancelled = false;
241: if (request.getAttribute(Globals.CANCEL_KEY) != null)
242: {
243: cancelled = true;
244: }
245:
246: // set up request environment for stateful action
247: statefulAction.setEnvironment(mapping, form, request, response);
248:
249: ActionContext context = new ActionContextImpl(request, response, getServletContext(), form, mapping);
250:
251: // inject action inputs
252: doInjection(statefulAction, context);
253:
254: try
255: {
256: if (cancelled)
257: actionForward = statefulAction.cancel();
258:
259: else
260: {
261:
262: // notify action that binding is about to occur.
263: // prior to domain object bound from form
264: statefulAction.preBind();
265:
266: // do incoming bindings
267: bindInwards(form, request);
268:
269: actionForward = statefulAction.execute();
270:
271: // do outgoing bindings
272: bindOutwards(form, request);
273:
274: }
275:
276: }
277: catch (RuntimeException e)
278: {
279: actionForward = statefulAction.handleRuntimeException(e);
280: }
281:
282: request.setAttribute(InfrastructureKeys.ACTION_BEAN, statefulAction);
283:
284: }
285: else
286: {
287: // execute regular Struts actions
288: actionForward = action.execute(mapping, form, request, response);
289: }
290:
291: if (request.getMethod().equalsIgnoreCase("GET"))
292: {
293: HttpSession session = request.getSession();
294:
295: @SuppressWarnings("unchecked")
296: List<String> goodURLs = (List<String>) session.getAttribute(InfrastructureKeys.GOOD_URL_HISTORY);
297:
298: if (goodURLs == null)
299: {
300: goodURLs = new ArrayList<String>();
301: }
302:
303: goodURLs.add(buildURL(request));
304: session.setAttribute(InfrastructureKeys.GOOD_URL_HISTORY, goodURLs);
305: }
306:
307: }
308: }
309: catch (Exception e)
310: {
311: log.error(e);
312: request.setAttribute(InfrastructureKeys.APPLICATION_EXCEPTION, e);
313: actionForward = super.processException(request, response, e, form, mapping);
314: }
315:
316: if (actionForward instanceof PageForward)
317: {
318: // save the instance of the PageClass
319: PageForward pageClassForward = (PageForward) actionForward;
320: Page pageClass = pageClassForward.getPage();
321: pageClass.setHttpServletResponse(response);
322: request.setAttribute(InfrastructureKeys.PAGE_BEAN, pageClass);
323: }
324: else
325: {
326: if (actionForward instanceof RedirectForward)
327: {
328: RedirectForward r = (RedirectForward) actionForward;
329: request.setAttribute(InfrastructureKeys.REDIRECT, actionForward);
330: request.getSession().setAttribute(InfrastructureKeys.REDIRECT_PARAMETERS, r.getSessionParameters());
331: }
332: }
333:
334: log.info("Ended action perform of " + request.getRequestURI() + StringUtils.LINE_SEPARATOR);
335: return actionForward;
336: }
337:
338: /**
339: * Binds outwards only for new forms. Once form is populated then no additional outward data binding should be
340: * necessary
341:
342: protected void bindOutwards(ActionForm form, HttpServletRequest request)
343: {
344: if (form instanceof ValidBindingForm)
345: {
346: BindingForm v = (BindingForm) form;
347: if (v.getBindOutwards())
348: {
349: v.bindOutwards();
350: }
351: }
352: }
353:
354: /**
355: * Bind inwards for posts which are not cancelled
356:
357: protected void bindInwards(ActionForm form, HttpServletRequest request)
358: {
359: if (form instanceof ValidBindingForm)
360: {
361: ValidBindingForm v = (ValidBindingForm) form;
362: // if method is post and cancel key is not set then bind inwards
363: if (v.isPosted() && request.getAttribute(Globals.CANCEL_KEY) == null)
364: {
365: v.bindInwards();
366: }
367: }
368: }
369:
370: /**
371: * Run check prior to invocation of access. Main purpose: user logon check. Only return ActionForward if state is
372: * incorrect (eg logging on needs to occur)
373:
374: protected ActionForward beforePerform(Action action, ActionMapping mapping, HttpServletRequest request,
375: HttpServletResponse response)
376: {
377: return null;
378: }
379:
380: /**
381: * Do any post action finalization. Guarranteed to run because this is in finally block
382:
383: protected void afterPerform(Action action, ActionMapping mapping, HttpServletRequest request,
384: HttpServletResponse response)
385: {
386: }
387:
388: private void doInjection(StatefulAction action, ActionContext context)
389: {
390: Map<String, InjectionWrapper> inputHandlerMap = injectionMap.get(action.getClass());
391: if (inputHandlerMap != null)
392: {
393: Set<String> keySet = inputHandlerMap.keySet();
394: for (String propertyName : keySet)
395: {
396: InjectionWrapper wrapper = inputHandlerMap.get(propertyName);
397: wrapper.inject(action, context);
398: }
399: }
400: }
401:
402: private String buildURL(HttpServletRequest request)
403: {
404: String servletPath = request.getServletPath();
405:
406: String queryString = request.getQueryString();
407: if (queryString != null)
408: {
409: servletPath += "?" + queryString;
410: }
411: return servletPath;
412: }*/
413: }
|