View Javadoc

1   /*
2    * The SmartWeb Framework
3    * Copyright (C) 2004-2006
4    *
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18   *
19   * For further informations on the SmartWeb Framework please visit
20   *
21   *                        http://smartweb.sourceforge.net
22   */
23   
24  package net.smartlab.web;
25  
26  import java.lang.reflect.InvocationTargetException;
27  import java.lang.reflect.Method;
28  import java.util.Arrays;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.Locale;
32  import java.util.Map;
33  
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import org.apache.commons.collections.FastHashMap;
38  import org.apache.struts.Globals;
39  import org.apache.struts.action.ActionForm;
40  import org.apache.struts.action.ActionForward;
41  import org.apache.struts.action.ActionMapping;
42  import org.apache.struts.config.MessageResourcesConfig;
43  import org.apache.struts.config.ModuleConfig;
44  import org.apache.struts.util.MessageResources;
45  
46  /**
47   * This class represent the dynamic version of the basic Action class allowing 
48   * a single class to define multiple actions.
49   * 
50   * Each declared method respecting the <code>PARAMETERS</code> signature and 
51   * returning a <code>String</code> or an <code>ActionMapping</code> will be 
52   * available as an action mapping in Struts. 
53   * 
54   * As an example both the following methods can be used in the struts 
55   * configuration file as valid mappings.
56   * 
57   *  <ul>
58   *    <li>public String myMethod (ActionForm form, HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws Exception</li>
59   *    <li>public ActionMapping myOtherMethod (ActionForm form, HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws Exception</li>
60   *  </ul>
61   * 
62   *  In addition this class supports the same features providen by the 
63   *  <code>org.apache.struts.actions.LookupDispatchAction</code> but to distinguish 
64   *  between direct method naming and reverse lookup naming you have to prefix
65   *  the reverse lookup mapping parameter with <code>@</code> (at) character.
66   * 
67   * @author rlogiacco
68   */
69  public abstract class DynaAction extends Action {
70  
71  	/**
72  	 * Standard dynamic method parameters.
73  	 */
74  	public final static Class[] PARAMETERS = new Class[] {ActionForm.class, HttpServletRequest.class, HttpServletResponse.class, ActionMapping.class};
75  
76  	/**
77  	 * Mapping of exposed methods.
78  	 */
79  	private final Map methods = new FastHashMap();
80  
81  	/**
82  	 * Mapping of locales to resource messages for reverse lookup.
83  	 */
84  	private final Map locales = new HashMap();
85  
86  	/**
87  	 * Default constructor.
88  	 */
89  	public DynaAction() {
90  		Method[] methods = this.getClass().getMethods();
91  		for (int i = 0; i < methods.length; i++) {
92  			if (Arrays.equals(methods[i].getParameterTypes(), PARAMETERS)) {
93  				this.methods.put(methods[i].getName(), methods[i]);
94  			}
95  		}
96  		((FastHashMap)this.methods).setFast(true);
97  	}
98  
99  	/**
100 	 * @see net.smartlab.web.Action#execute(org.apache.struts.action.ActionForm,
101 	 *      javax.servlet.http.HttpServletRequest,
102 	 *      javax.servlet.http.HttpServletResponse, ActionMapping)
103 	 */
104 	protected ActionForward execute(ActionForm form, HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws Exception {
105 		if (logger.isDebugEnabled()) {
106 			logger.debug("execute(" + mapping.getPath() + ") - start");
107 		}
108 		String method = mapping.getParameter();
109 		if (mapping == null) {
110 			throw new ActionException("action.parameter.null");
111 		}
112 		if (method.startsWith("@")) {
113 			// Strip the leading char
114 			method = method.substring(1);
115 			// Dynamically find the method using the request parameter value
116 			method = request.getParameter(method);
117 			if (!methods.containsKey(method)) {
118 				// Try to reverse resolve using the resource bundle
119 				// Based on this request's Locale get the lookupMap
120 				Map lookup = null;
121 				Locale locale = request.getLocale();
122 				boolean exists = true;
123 				synchronized (locales) {
124 					lookup = (Map)this.locales.get(locale);
125 					if (lookup == null) {
126 						exists = false;
127 						lookup = new HashMap();
128 						locales.put(locale, lookup);
129 					}
130 				}
131 				if (!exists) {
132 					synchronized (lookup) {
133 						/*
134 						 * This is the first time this Locale is used so build
135 						 * the reverse lookup Map. Search for message keys in
136 						 * all configured MessageResources for the current
137 						 * module.
138 						 */
139 						ModuleConfig module = (ModuleConfig)request.getAttribute(Globals.MODULE_KEY);
140 						MessageResourcesConfig[] messages = module.findMessageResourcesConfigs();
141 						// Look through all module's MessageResources
142 						for (int i = 0; i < messages.length; i++) {
143 							MessageResources resources = this.getResources(request, messages[i].getKey());
144 							// Look for method name in Messages
145 							Iterator names = this.methods.keySet().iterator();
146 							while (names.hasNext()) {
147 								String name = (String)names.next();
148 								String message = resources.getMessage(locale, name);
149 								if ((message != null) && !lookup.containsKey(message)) {
150 									// Found method name and haven't added to
151 									// Map yet, so add the text
152 									if (name.indexOf('.') > -1) {
153 										// Strip out any dot
154 										name = name.substring(name.lastIndexOf('.') + 1);
155 									}
156 									if (methods.containsKey(name)) {
157 										lookup.put(message, name);
158 									}
159 								}
160 							}
161 						}
162 					}
163 				}
164 				// Find the method
165 				method = (String)lookup.get(method);
166 			}
167 		}
168 		// Execute the specified method
169 		if (logger.isTraceEnabled()) {
170 			logger.trace("   method = " + method);
171 		}
172 		try {
173 			Object forward = ((Method)methods.get(method)).invoke(this, new Object[] {form, request, response, mapping});
174 			if (forward instanceof String) {
175 				return mapping.findForward((String)forward);
176 			} else {
177 				return (ActionForward)forward;
178 			}
179 		} catch (InvocationTargetException ite) {
180 			Throwable cause = ite.getTargetException();
181 			if (cause instanceof Exception) {
182 				throw (Exception)cause;
183 			} else {
184 				logger.debug("execute( " + mapping.getPath() + ") - error", cause);
185 				throw new ActionException("action.error.method", cause);
186 			}
187 		} catch (Exception e) {
188 			logger.debug("execute( " + mapping.getPath() + ") - error", e);
189 			throw new ActionException("action.error.method.unknown", e);
190 		}
191 	}
192 
193 	/**
194 	 * Default method action used to simply forward requests to the resource mapped as <code>success</code>.
195 	 * 
196 	 * @return <code>success</code>
197 	 */
198 	public final String forward(ActionForm form, HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) {
199 		return "success";
200 	}
201 }