1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts2.dispatcher.mapper;
19
20 import java.util.Arrays;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24
25 import javax.servlet.http.HttpServletRequest;
26
27 import org.apache.struts2.RequestUtils;
28 import org.apache.struts2.StrutsConstants;
29 import org.apache.struts2.config.Settings;
30 import org.apache.struts2.dispatcher.ServletRedirectResult;
31 import org.apache.struts2.util.PrefixTrie;
32
33 import com.opensymphony.xwork2.config.Configuration;
34 import com.opensymphony.xwork2.config.ConfigurationManager;
35 import com.opensymphony.xwork2.config.entities.PackageConfig;
36
37 /***
38 * <!-- START SNIPPET: javadoc -->
39 *
40 * Default action mapper implementation, using the standard *.[ext] (where ext
41 * usually "action") pattern. The extension is looked up from the Struts
42 * configuration key <b>struts.action.exection</b>.
43 *
44 * <p/> To help with dealing with buttons and other related requirements, this
45 * mapper (and other {@link ActionMapper}s, we hope) has the ability to name a
46 * button with some predefined prefix and have that button name alter the
47 * execution behaviour. The four prefixes are:
48 *
49 * <ul>
50 *
51 * <li>Method prefix - <i>method:default</i></li>
52 *
53 * <li>Action prefix - <i>action:dashboard</i></li>
54 *
55 * <li>Redirect prefix - <i>redirect:cancel.jsp</i></li>
56 *
57 * <li>Redirect-action prefix - <i>redirect-action:cancel</i></li>
58 *
59 * </ul>
60 *
61 * <p/> In addition to these four prefixes, this mapper also understands the
62 * action naming pattern of <i>foo!bar</i> in either the extension form (eg:
63 * foo!bar.action) or in the prefix form (eg: action:foo!bar). This syntax tells
64 * this mapper to map to the action named <i>foo</i> and the method <i>bar</i>.
65 *
66 * <!-- END SNIPPET: javadoc -->
67 *
68 * <p/> <b>Method Prefix</b> <p/>
69 *
70 * <!-- START SNIPPET: method -->
71 *
72 * With method-prefix, instead of calling baz action's execute() method (by
73 * default if it isn't overriden in struts.xml to be something else), the baz
74 * action's anotherMethod() will be called. A very elegant way determine which
75 * button is clicked. Alternatively, one would have submit button set a
76 * particular value on the action when clicked, and the execute() method decides
77 * on what to do with the setted value depending on which button is clicked.
78 *
79 * <!-- END SNIPPET: method -->
80 *
81 * <pre>
82 * <!-- START SNIPPET: method-example -->
83 * <a:form action="baz">
84 * <a:textfield label="Enter your name" name="person.name"/>
85 * <a:submit value="Create person"/>
86 * <a:submit name="method:anotherMethod" value="Cancel"/>
87 * </a:form>
88 * <!-- END SNIPPET: method-example -->
89 * </pre>
90 *
91 * <p/> <b>Action prefix</b> <p/>
92 *
93 * <!-- START SNIPPET: action -->
94 *
95 * With action-prefix, instead of executing baz action's execute() method (by
96 * default if it isn't overriden in struts.xml to be something else), the
97 * anotherAction action's execute() method (assuming again if it isn't overriden
98 * with something else in struts.xml) will be executed.
99 *
100 * <!-- END SNIPPET: action -->
101 *
102 * <pre>
103 * <!-- START SNIPPET: action-example -->
104 * <a:form action="baz">
105 * <a:textfield label="Enter your name" name="person.name"/>
106 * <a:submit value="Create person"/>
107 * <a:submit name="action:anotherAction" value="Cancel"/>
108 * </a:form>
109 * <!-- END SNIPPET: action-example -->
110 * </pre>
111 *
112 * <p/> <b>Redirect prefix</b> <p/>
113 *
114 * <!-- START SNIPPET: redirect -->
115 *
116 * With redirect-prefix, instead of executing baz action's execute() method (by
117 * default it isn't overriden in struts.xml to be something else), it will get
118 * redirected to, in this case to www.google.com. Internally it uses
119 * ServletRedirectResult to do the task.
120 *
121 * <!-- END SNIPPET: redirect -->
122 *
123 * <pre>
124 * <!-- START SNIPPET: redirect-example -->
125 * <a:form action="baz">
126 * <a:textfield label="Enter your name" name="person.name"/>
127 * <a:submit value="Create person"/>
128 * <a:submit name="redirect:www.google.com" value="Cancel"/>
129 * </a:form>
130 * <!-- END SNIPPET: redirect-example -->
131 * </pre>
132 *
133 * <p/> <b>Redirect-action prefix</b> <p/>
134 *
135 * <!-- START SNIPPET: redirect-action -->
136 *
137 * With redirect-action-prefix, instead of executing baz action's execute()
138 * method (by default it isn't overriden in struts.xml to be something else), it
139 * will get redirected to, in this case 'dashboard.action'. Internally it uses
140 * ServletRedirectResult to do the task and read off the extension from the
141 * struts.properties.
142 *
143 * <!-- END SNIPPET: redirect-action -->
144 *
145 * <pre>
146 * <!-- START SNIPPET: redirect-action-example -->
147 * <a:form action="baz">
148 * <a:textfield label="Enter your name" name="person.name"/>
149 * <a:submit value="Create person"/>
150 * <a:submit name="redirect-action:dashboard" value="Cancel"/>
151 * </a:form>
152 * <!-- END SNIPPET: redirect-action-example -->
153 * </pre>
154 *
155 */
156 public class DefaultActionMapper implements ActionMapper {
157
158 static final String METHOD_PREFIX = "method:";
159
160 static final String ACTION_PREFIX = "action:";
161
162 static final String REDIRECT_PREFIX = "redirect:";
163
164 static final String REDIRECT_ACTION_PREFIX = "redirect-action:";
165
166 private static boolean allowDynamicMethodCalls = "true".equals(Settings
167 .get(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION));
168
169 private PrefixTrie prefixTrie = null;
170
171 public DefaultActionMapper() {
172 prefixTrie = new PrefixTrie() {
173 {
174 put(METHOD_PREFIX, new ParameterAction() {
175 public void execute(String key, ActionMapping mapping) {
176 mapping
177 .setMethod(key
178 .substring(METHOD_PREFIX.length()));
179 }
180 });
181
182 put(ACTION_PREFIX, new ParameterAction() {
183 public void execute(String key, ActionMapping mapping) {
184 String name = key.substring(ACTION_PREFIX.length());
185 if (allowDynamicMethodCalls) {
186 int bang = name.indexOf('!');
187 if (bang != -1) {
188 String method = name.substring(bang + 1);
189 mapping.setMethod(method);
190 name = name.substring(0, bang);
191 }
192 }
193 mapping.setName(name);
194 }
195 });
196
197 put(REDIRECT_PREFIX, new ParameterAction() {
198 public void execute(String key, ActionMapping mapping) {
199 ServletRedirectResult redirect = new ServletRedirectResult();
200 redirect.setLocation(key.substring(REDIRECT_PREFIX
201 .length()));
202 mapping.setResult(redirect);
203 }
204 });
205
206 put(REDIRECT_ACTION_PREFIX, new ParameterAction() {
207 public void execute(String key, ActionMapping mapping) {
208 String location = key.substring(REDIRECT_ACTION_PREFIX
209 .length());
210 ServletRedirectResult redirect = new ServletRedirectResult();
211 String extension = getDefaultExtension();
212 if (extension != null) {
213 location += "." + extension;
214 }
215 redirect.setLocation(location);
216 mapping.setResult(redirect);
217 }
218 });
219 }
220 };
221 }
222
223
224
225
226
227
228 public ActionMapping getMapping(HttpServletRequest request,
229 ConfigurationManager configManager) {
230 ActionMapping mapping = new ActionMapping();
231 String uri = getUri(request);
232
233 uri = dropExtension(uri);
234 if (uri == null) {
235 return null;
236 }
237
238 parseNameAndNamespace(uri, mapping, configManager.getConfiguration());
239
240 handleSpecialParameters(request, mapping);
241
242 if (mapping.getName() == null) {
243 return null;
244 }
245
246 if (allowDynamicMethodCalls) {
247
248 String name = mapping.getName();
249 int exclamation = name.lastIndexOf("!");
250 if (exclamation != -1) {
251 mapping.setName(name.substring(0, exclamation));
252 mapping.setMethod(name.substring(exclamation + 1));
253 }
254 }
255
256 return mapping;
257 }
258
259 /***
260 * Special parameters, as described in the class-level comment, are searched
261 * for and handled.
262 *
263 * @param request
264 * The request
265 * @param mapping
266 * The action mapping
267 */
268 public void handleSpecialParameters(HttpServletRequest request,
269 ActionMapping mapping) {
270
271 Map parameterMap = request.getParameterMap();
272 for (Iterator iterator = parameterMap.keySet().iterator(); iterator
273 .hasNext();) {
274 String key = (String) iterator.next();
275 ParameterAction parameterAction = (ParameterAction) prefixTrie
276 .get(key);
277 if (parameterAction != null) {
278 parameterAction.execute(key, mapping);
279 break;
280 }
281 }
282 }
283
284 /***
285 * Parses the name and namespace from the uri
286 *
287 * @param uri
288 * The uri
289 * @param mapping
290 * The action mapping to populate
291 */
292 void parseNameAndNamespace(String uri, ActionMapping mapping,
293 Configuration config) {
294 String namespace, name;
295 int lastSlash = uri.lastIndexOf("/");
296 if (lastSlash == -1) {
297 namespace = "";
298 name = uri;
299 } else if (lastSlash == 0) {
300
301
302
303 namespace = "/";
304 name = uri.substring(lastSlash + 1);
305 } else {
306 String prefix = uri.substring(0, lastSlash);
307 namespace = "";
308
309 for (Iterator i = config.getPackageConfigs().values().iterator(); i
310 .hasNext();) {
311 String ns = ((PackageConfig) i.next()).getNamespace();
312 if (ns != null && prefix.startsWith(ns)) {
313 if (ns.length() > namespace.length()) {
314 namespace = ns;
315 }
316 }
317 }
318
319 name = uri.substring(namespace.length() + 1);
320 }
321 mapping.setNamespace(namespace);
322 mapping.setName(name);
323 }
324
325 /***
326 * Drops the extension from the action name
327 *
328 * @param name
329 * The action name
330 * @return The action name without its extension
331 */
332 String dropExtension(String name) {
333 List extensions = getExtensions();
334 if (extensions == null) {
335 return name;
336 }
337 Iterator it = extensions.iterator();
338 while (it.hasNext()) {
339 String extension = "." + (String) it.next();
340 if (name.endsWith(extension)) {
341 name = name.substring(0, name.length() - extension.length());
342 return name;
343 }
344 }
345 return null;
346 }
347
348 /***
349 * Returns null if no extension is specified.
350 */
351 static String getDefaultExtension() {
352 List extensions = getExtensions();
353 if (extensions == null) {
354 return null;
355 } else {
356 return (String) extensions.get(0);
357 }
358 }
359
360 /***
361 * Returns null if no extension is specified.
362 */
363 static List getExtensions() {
364 String extensions = (String) org.apache.struts2.config.Settings
365 .get(StrutsConstants.STRUTS_ACTION_EXTENSION);
366
367 if ("".equals(extensions)) {
368 return null;
369 } else {
370 return Arrays.asList(extensions.split(","));
371 }
372 }
373
374 /***
375 * Gets the uri from the request
376 *
377 * @param request
378 * The request
379 * @return The uri
380 */
381 String getUri(HttpServletRequest request) {
382
383 String uri = (String) request
384 .getAttribute("javax.servlet.include.servlet_path");
385 if (uri != null) {
386 return uri;
387 }
388
389 uri = RequestUtils.getServletPath(request);
390 if (uri != null && !"".equals(uri)) {
391 return uri;
392 }
393
394 uri = request.getRequestURI();
395 return uri.substring(request.getContextPath().length());
396 }
397
398
399
400
401
402
403 public String getUriFromActionMapping(ActionMapping mapping) {
404 StringBuffer uri = new StringBuffer();
405
406 uri.append(mapping.getNamespace());
407 if (!"/".equals(mapping.getNamespace())) {
408 uri.append("/");
409 }
410 String name = mapping.getName();
411 String params = "";
412 if (name.indexOf('?') != -1) {
413 params = name.substring(name.indexOf('?'));
414 name = name.substring(0, name.indexOf('?'));
415 }
416 uri.append(name);
417
418 if (null != mapping.getMethod() && !"".equals(mapping.getMethod())) {
419 uri.append("!").append(mapping.getMethod());
420 }
421
422 String extension = getDefaultExtension();
423 if (extension != null) {
424 if (uri.indexOf('.' + extension) == -1) {
425 uri.append(".").append(extension);
426 if (params.length() > 0) {
427 uri.append(params);
428 }
429 }
430 }
431
432 return uri.toString();
433 }
434
435 /***
436 * Defines a parameter action prefix
437 */
438 interface ParameterAction {
439 void execute(String key, ActionMapping mapping);
440 }
441 }