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