View Javadoc

1   /*
2    * $Id: DefaultActionMapper.java 449368 2006-09-24 06:50:57Z mrdon $
3    *
4    * Copyright 2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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   *  &lt;!-- START SNIPPET: method-example --&gt;
83   *  &lt;a:form action=&quot;baz&quot;&gt;
84   *      &lt;a:textfield label=&quot;Enter your name&quot; name=&quot;person.name&quot;/&gt;
85   *      &lt;a:submit value=&quot;Create person&quot;/&gt;
86   *      &lt;a:submit name=&quot;method:anotherMethod&quot; value=&quot;Cancel&quot;/&gt;
87   *  &lt;/a:form&gt;
88   *  &lt;!-- END SNIPPET: method-example --&gt;
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  *  &lt;!-- START SNIPPET: action-example --&gt;
104  *  &lt;a:form action=&quot;baz&quot;&gt;
105  *      &lt;a:textfield label=&quot;Enter your name&quot; name=&quot;person.name&quot;/&gt;
106  *      &lt;a:submit value=&quot;Create person&quot;/&gt;
107  *      &lt;a:submit name=&quot;action:anotherAction&quot; value=&quot;Cancel&quot;/&gt;
108  *  &lt;/a:form&gt;
109  *  &lt;!-- END SNIPPET: action-example --&gt;
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  *  &lt;!-- START SNIPPET: redirect-example --&gt;
125  *  &lt;a:form action=&quot;baz&quot;&gt;
126  *      &lt;a:textfield label=&quot;Enter your name&quot; name=&quot;person.name&quot;/&gt;
127  *      &lt;a:submit value=&quot;Create person&quot;/&gt;
128  *      &lt;a:submit name=&quot;redirect:www.google.com&quot; value=&quot;Cancel&quot;/&gt;
129  *  &lt;/a:form&gt;
130  *  &lt;!-- END SNIPPET: redirect-example --&gt;
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  *  &lt;!-- START SNIPPET: redirect-action-example --&gt;
147  *  &lt;a:form action=&quot;baz&quot;&gt;
148  *      &lt;a:textfield label=&quot;Enter your name&quot; name=&quot;person.name&quot;/&gt;
149  *      &lt;a:submit value=&quot;Create person&quot;/&gt;
150  *      &lt;a:submit name=&quot;redirect-action:dashboard&quot; value=&quot;Cancel&quot;/&gt;
151  *  &lt;/a:form&gt;
152  *  &lt;!-- END SNIPPET: redirect-action-example --&gt;
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      * (non-Javadoc)
225      * 
226      * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest)
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             // handle "name!method" convention.
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         // handle special parameter prefixes.
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             // ww-1046, assume it is the root namespace, it will fallback to
301             // default
302             // namespace anyway if not found in root namespace.
303             namespace = "/";
304             name = uri.substring(lastSlash + 1);
305         } else {
306             String prefix = uri.substring(0, lastSlash);
307             namespace = "";
308             // Find the longest matching namespace, defaulting to the default
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         // handle http dispatcher includes.
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      * (non-Javadoc)
400      * 
401      * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getUriFromActionMapping(org.apache.struts2.dispatcher.mapper.ActionMapping)
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 }