001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: /* Generated by Together */
051:
052: package org.jaffa.presentation.portlet;
053:
054: import org.apache.struts.action.Action;
055: import org.apache.struts.action.ActionForward;
056: import org.apache.struts.action.ActionMapping;
057: import org.apache.struts.action.ActionForm;
058: import org.apache.struts.Globals;
059: import javax.servlet.http.HttpServletRequest;
060: import javax.servlet.http.HttpServletResponse;
061: import java.io.IOException;
062: import java.io.UnsupportedEncodingException;
063: import javax.servlet.ServletException;
064: import org.jaffa.presentation.portlet.FormBase;
065: import org.jaffa.util.EventHandler;
066: import org.apache.log4j.Logger;
067: import java.lang.reflect.Method;
068: import org.jaffa.presentation.portlet.session.UserSession;
069: import org.jaffa.presentation.portlet.widgets.taglib.FormTag;
070: import org.jaffa.presentation.portlet.component.Component;
071: import javax.servlet.http.HttpSession;
072: import org.jaffa.datatypes.Parser;
073: import org.jaffa.config.Config;
074: import java.lang.reflect.Field;
075: import java.net.URLDecoder;
076: import java.util.ArrayList;
077: import java.util.List;
078: import java.util.StringTokenizer;
079: import org.jaffa.presentation.portlet.FormKeyChangeEvent;
080: import org.jaffa.presentation.portlet.FormKeyChangeListener;
081: import org.jaffa.exceptions.ApplicationExceptions;
082: import org.jaffa.exceptions.TokenMismatchException;
083:
084: /** This is the base class for all 'Action' classes
085: */
086: public class ActionBase extends Action {
087:
088: private static Logger log = Logger.getLogger(ActionBase.class);
089: private static final String NAME = "NAME";
090:
091: /** The ActionMapping used to select this instance
092: */
093: protected ActionMapping mapping = null;
094: /** The optional ActionForm bean for this request (if any)
095: */
096: protected ActionForm form = null;
097: /** The HTTP request we are processing
098: */
099: protected HttpServletRequest request = null;
100: /** The HTTP response we are creating
101: */
102: protected HttpServletResponse response = null;
103: /** The optional component for this request (if any)
104: */
105: protected Component component = null;
106:
107: /**
108: * Check the eventId & invoke the suitable method which will return the FormKey.
109: * It will invoke the defaultAction() method in case the eventId is null.
110: * This will throw the EventHandlerMissingRuntimeException in case no method is found.
111: * @param mapping The ActionMapping used to select this instance
112: * @param form The optional ActionForm bean for this request (if any)
113: * @param request The HTTP request we are processing
114: * @param response The HTTP response we are creating
115: * @throws Exception if the application business logic throws an exception
116: * @return An ActionForward instance describing where and how control should be forwarded, or null if the response has already been completed
117: */
118: public ActionForward execute(ActionMapping mapping,
119: ActionForm form, HttpServletRequest request,
120: HttpServletResponse response) throws Exception {
121: // NOTE: Struts has implemented a singleton pattern for each Action class.
122: // But we need to set instance variables on the Action class.
123: // To get around Struts, we'll create a new instance of the Action class and invoke methods on that instance.
124: ActionBase actionInstance = (ActionBase) this .getClass()
125: .newInstance();
126:
127: // ******************* VERY IMPORTANT NOTE ***************************
128: // Invoke methods on 'actionInstance', rather than 'this'
129: // ******************* VERY IMPORTANT NOTE ***************************
130:
131: actionInstance.mapping = mapping;
132: actionInstance.form = form;
133: actionInstance.request = request;
134: actionInstance.response = response;
135:
136: FormKey fk = null;
137: String eventId = null;
138: FormBase formBase = null;
139:
140: // Get the eventId
141: eventId = request.getParameter(FormTag.PARAMETER_EVENT_ID);
142:
143: // set the component
144: if (form != null) {
145: formBase = (FormBase) form;
146: actionInstance.component = formBase.getComponent();
147: }
148:
149: // invoke the defaultAction or the eventHandler
150: if (eventId == null || eventId.length() == 0)
151: fk = actionInstance.defaultAction();
152: else
153: fk = actionInstance.executeEvent(eventId);
154:
155: if (fk != null) {
156: if (log.isDebugEnabled())
157: log.debug("Received the Form Key: " + fk);
158:
159: // Save the transaction token on the session and the component represented by the FormKey
160: actionInstance.saveToken(fk);
161:
162: // Get the ContainerFormKey, if the component has one defined.
163: fk = actionInstance.processContainerFormKey(fk);
164:
165: // Put this FormKey on the RequestStream
166: request.setAttribute(FormKey.class.getName(), fk);
167:
168: // Add this FormKey to the historyNavList
169: try {
170: HistoryNav.addFormKeyToHistoryNav(request, fk);
171: } catch (Exception e) {
172: if (log.isInfoEnabled())
173: log
174: .info(
175: "Exception thrown while trying to add the FormKey to the historyNavList",
176: e);
177: }
178:
179: // forward to the Form
180: ActionForward forward = mapping.findForward(actionInstance
181: .parseFormName(fk.getFormName()));
182: if (log.isDebugEnabled())
183: log.debug("Forward object is " + forward);
184: return (forward);
185: } else {
186: // kill the userSession & return to the finalUrl
187: return actionInstance.handleNullFormKey();
188: }
189: }
190:
191: /**
192: * The default action method which should be overriden by the extending class.
193: * This method is invoked by the perform() method, if no eventId is passed.
194: * If the extending class doesn't provide an implementation of this method, a EventHandlerMissingRuntimeException will be thrown.
195: * If all the 'submit' actions send an eventId, then there is no need to provide any implementation for this method.
196: * @throws Exception if the application business logic throws an exception
197: * @return A FormKey instance which describes the current Component & Form
198: */
199: protected FormKey defaultAction() throws Exception {
200: String str = "No default action defined for the Action class "
201: + this .getClass().getName();
202: log.error(str);
203: throw new EventHandlerMissingRuntimeException(str);
204: }
205:
206: /** Save a new transaction token in the component.
207: * @param fk The FormKey from which the component will be determined.
208: */
209: protected void saveToken(FormKey fk) {
210: synchronized (request.getSession()) {
211: // Save a token on the session using the struts method
212: super .saveToken(request);
213:
214: // Save the token on the component represented by the input formkey
215: if (fk != null && fk.getComponentId() != null) {
216: Component comp = UserSession.getUserSession(request)
217: .getComponent(fk.getComponentId());
218: if (comp != null)
219: comp
220: .setToken((String) request
221: .getSession()
222: .getAttribute(
223: Globals.TRANSACTION_TOKEN_KEY));
224: }
225: }
226: }
227:
228: /** Return true if there is a transaction token stored in the component, and the value submitted as a request parameter with this action matches it. Else it returns false.
229: * @param request The servlet request we are processing
230: * @return true if there is a transaction token stored in the component, and the value submitted as a request parameter with this action matches it. Else it returns false.
231: */
232: protected boolean isTokenValid(HttpServletRequest request) {
233: return isTokenValid(request, false);
234: }
235:
236: /** Return true if there is a transaction token stored in the component, and the value submitted as a request parameter with this action matches it. Else it returns false.
237: * @param request The servlet request we are processing
238: * @param reset Should we reset the token after checking it?
239: * @return true if there is a transaction token stored in the component, and the value submitted as a request parameter with this action matches it. Else it returns false.
240: */
241: protected boolean isTokenValid(HttpServletRequest request,
242: boolean reset) {
243: synchronized (request.getSession()) {
244: if (component != null)
245: request.getSession().setAttribute(
246: Globals.TRANSACTION_TOKEN_KEY,
247: component.getToken());
248: boolean valid = super .isTokenValid(request, reset);
249: if (valid && reset)
250: resetToken(request);
251: return valid;
252: }
253: }
254:
255: /** Reset the saved transaction token in the component. This
256: * indicates that transactional token checking will not be needed
257: * on the next request that is submitted.
258: *
259: * @param request The servlet request we are processing
260: */
261: protected void resetToken(HttpServletRequest request) {
262: super .resetToken(request);
263: if (component != null)
264: component.setToken(null);
265: }
266:
267: /** This merely invokes the isTokenValid() method and throws the ApplicationExceptions if the validation fails.
268: * @param request The servlet request we are processing
269: * @throws ApplicationExceptions if the token on the component does not match the token submitted as the request paramter.
270: */
271: protected void performTokenValidation(HttpServletRequest request)
272: throws ApplicationExceptions {
273: if (!isTokenValid(request)) {
274: String str = "The token in the component does not match the token submitted as the request paramter.";
275: if (log.isDebugEnabled())
276: log.debug(str);
277: ApplicationExceptions appExps = new ApplicationExceptions();
278: appExps.add(new TokenMismatchException());
279: throw appExps;
280: }
281: }
282:
283: /** Determines the currentFormKey, if possible, utilising the NAME field (if defined) and the componentId of the formBase.
284: * This event is invoked when a user changes the settings of the UserGrid in a screen.
285: * @return The FormKey for the Results screen.
286: */
287: public FormKey do_refresh() {
288: if (log.isDebugEnabled())
289: log
290: .debug("Returning the CurrentFormKey for the refresh event");
291: return determineCurrentFormKey((FormBase) form);
292: }
293:
294: /** This is the handler for the event generated in the historyNav for rendering a previous screen.
295: * @param index The index for the screen to be rendered. The most ancient screen(usually the Home) will be indexed '0'
296: * @return The FormKey for a previous screen.
297: */
298: public FormKey do_HistoryNav_Clicked(String index) {
299: FormKey fk = null;
300: try {
301: int i = Integer.parseInt(index);
302: List historyNavList = HistoryNav.obtainHistoryNav(request);
303: if (historyNavList != null && historyNavList.size() > i)
304: fk = (FormKey) historyNavList.get(i);
305: } catch (Exception e) {
306: if (log.isInfoEnabled())
307: log
308: .info(
309: "Exception thrown while trying to obtain the FormKey from the historyNavList",
310: e);
311: }
312:
313: if (fk == null) {
314: if (log.isDebugEnabled())
315: log
316: .debug("Unable to obtain the FormKey from the historyNavList. Will invoke the 'refresh' event to re-render the current screen");
317: fk = do_refresh();
318: }
319:
320: return fk;
321: }
322:
323: /** This method will introspect this class for a suitable handler for the input eventId and invoke it.
324: * @param eventId The event.
325: * @throws Exception If any error occurs in introspection or invocation of the event-handler.
326: * @return The output from the input handler.
327: */
328: protected FormKey executeEvent(String eventId) throws Exception {
329: Method m = null;
330: Object[] argumentValues = null;
331:
332: // Get all the possible methods for the eventId & invoke the 1st method which is available
333: if (log.isDebugEnabled())
334: log.debug("Evaluating the methods for handling the event "
335: + eventId);
336:
337: EventHandler.Method[] methods = EventHandler
338: .getEventMethods(eventId);
339: if (methods != null) {
340: Class actionClass = this .getClass();
341: for (int i = 0; i < methods.length; i++) {
342: EventHandler.Method method = methods[i];
343: if (log.isDebugEnabled())
344: log.debug("Introspecting " + actionClass.getName()
345: + " for the method " + method);
346:
347: try {
348: m = actionClass.getMethod(method.getName(), method
349: .getArgumentTypes());
350: } catch (Exception e) {
351: if (log.isDebugEnabled())
352: log.debug("Method not found - " + method);
353:
354: // Not found.. so continue
355: m = null;
356: continue;
357: }
358:
359: if (log.isDebugEnabled())
360: log.debug("Method found - " + method);
361:
362: // make sure the method returns a FormKey
363: if (FormKey.class.isAssignableFrom(m.getReturnType())) {
364: argumentValues = method.getArgumentValues();
365: break;
366: } else {
367: m = null;
368: if (log.isDebugEnabled())
369: log
370: .debug("'FormKey' is not returned by the method "
371: + method);
372: continue;
373: }
374: }
375: }
376:
377: if (m == null) {
378: String str = "No method found for handling the event "
379: + eventId;
380: log.error(str);
381: throw new EventHandlerMissingRuntimeException(str);
382: }
383:
384: // Execute the method
385: return (FormKey) m.invoke(this , argumentValues);
386: }
387:
388: /** This is invoked whenever a null FormKey is returned by an event handler.
389: * This will kill the UserSession, if the config property 'PROP_SECURITY_PORTLET_INVALIDATE_SESSION_BEFORE_REDIRECTING_TO_FINAL_URL' is set to true.
390: * It will then redirect to the root of this webapp '/'.
391: * @throws Exception if any error occurs.
392: * @return The ActionForward representing the redirection to the root of this webapp '/'.
393: */
394: protected ActionForward handleNullFormKey() throws Exception {
395: String invalidate = (String) Config
396: .getProperty(
397: Config.PROP_SECURITY_PORTLET_INVALIDATE_SESSION_BEFORE_REDIRECTING_TO_FINAL_URL,
398: "true");
399: if (Parser.parseBoolean(invalidate).booleanValue()) {
400: if (log.isDebugEnabled())
401: log
402: .debug("Received Null FormKey. Killing the UserSession and Invalidating the HttpSession");
403: if (UserSession.isUserSession(request))
404: UserSession.getUserSession(request).kill();
405:
406: // Added by paule, to invalidate the authenticated session.
407: HttpSession s = request.getSession(false);
408: if (s != null)
409: s.invalidate();
410: } else {
411: if (log.isDebugEnabled())
412: log
413: .debug("Received Null FormKey. However the Session will not be invalidated.");
414: }
415:
416: if (log.isDebugEnabled())
417: log.debug("Redirecting to the Url '/'");
418: return new ActionForward("/", true);
419: }
420:
421: /** Determines the currentFormKey, if possible, utilising the NAME field (if defined) and the componentId of the formBase.
422: * @param formBase The input FormBean from which the current FormKey is to be determined.
423: * @return The current FormKey.
424: */
425: protected FormKey determineCurrentFormKey(FormBase formBase) {
426: FormKey fk = null;
427: Class clazz = formBase.getClass();
428: try {
429: Field nameField = clazz.getField(NAME);
430: if (nameField != null) {
431: Object name = nameField.get(formBase);
432: if (name != null && name instanceof String
433: && formBase.getComponent() != null) {
434: fk = new FormKey((String) name, formBase
435: .getComponent().getComponentId());
436: }
437: }
438: } catch (Exception e) {
439: // do nothing
440: if (log.isInfoEnabled())
441: log.info(
442: "Exception thrown while trying to determine the NAME field of the Form "
443: + formBase.getClass().getName(), e);
444: }
445: return fk;
446: }
447:
448: /** This will parse the input FormName for parameters and set them as attributes on the request stream.
449: * It will then return the FormName minus the parameters.
450: * @param formName The input FormName.
451: * @throws UnsupportedEncodingException This will be thrown if the JVM doesn't support the 'UTF-8' encoding. Highly unlikely that this will be thrown.
452: * @return The FormName minus the parameters, if any.
453: */
454: protected String parseFormName(String formName)
455: throws UnsupportedEncodingException {
456: formName = URLDecoder.decode(formName, "UTF-8");
457: int i = formName.indexOf('?');
458: if (i > -1) {
459: if (formName.length() > (i + 1)) {
460: String parameterString = formName.substring(i + 1);
461: StringTokenizer tokenizer = new StringTokenizer(
462: parameterString, "=&");
463: String attributeName = null;
464: while (tokenizer.hasMoreTokens()) {
465: String token = tokenizer.nextToken();
466: if (attributeName == null) {
467: attributeName = token;
468: } else {
469: request.setAttribute(attributeName, token);
470: attributeName = null;
471: }
472: }
473: }
474: formName = formName.substring(0, i);
475: }
476: return formName;
477: }
478:
479: /** Returns the ContainerFormKey, if the component specified by the input FormKey has one defined.
480: * For such a component a FormKeyChangeEvent will be sent to the registered FormKeyChangeListeners.
481: * @param fk The input FormKey.
482: * @return The ContainerFormKey, if exists, or just the input FormKey.
483: */
484: protected FormKey processContainerFormKey(FormKey fk) {
485: // The following should probably be invoked recursively. Will do so if the need arises !!
486: UserSession us = UserSession.getUserSession(request);
487: Component component = us.getComponent(fk.getComponentId());
488: if (component != null
489: && component.getContainerFormKey() != null) {
490: FormKeyChangeListener[] listeners = component
491: .getFormKeyChangeListeners();
492: if (listeners != null && listeners.length > 0) {
493: FormKeyChangeEvent e = new FormKeyChangeEvent(this , fk);
494: for (int i = 0; i < listeners.length; i++)
495: listeners[i].formKeyChanged(e);
496: }
497: fk = component.getContainerFormKey();
498: if (log.isDebugEnabled())
499: log
500: .debug("The component in the received form key has a ContainerFormKey. Fired the FormKeyChangeListeners on the component and using the ContainerFormKey "
501: + fk);
502: }
503: return fk;
504: }
505:
506: }
|