1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts2.rest;
23
24 import com.opensymphony.xwork2.config.Configuration;
25 import com.opensymphony.xwork2.config.ConfigurationManager;
26 import com.opensymphony.xwork2.config.entities.PackageConfig;
27 import com.opensymphony.xwork2.inject.Inject;
28 import com.opensymphony.xwork2.util.logging.Logger;
29 import com.opensymphony.xwork2.util.logging.LoggerFactory;
30 import org.apache.struts2.StrutsConstants;
31 import org.apache.struts2.dispatcher.mapper.ActionMapping;
32 import org.apache.struts2.dispatcher.mapper.DefaultActionMapper;
33
34 import javax.servlet.http.HttpServletRequest;
35 import java.util.HashMap;
36 import java.util.Iterator;
37
38 /***
39 * <!-- START SNIPPET: description -->
40 *
41 * This Restful action mapper enforces Ruby-On-Rails Rest-style mappings. If the method
42 * is not specified (via '!' or 'method:' prefix), the method is "guessed" at using
43 * ReST-style conventions that examine the URL and the HTTP method. Special care has
44 * been given to ensure this mapper works correctly with the codebehind plugin so that
45 * XML configuration is unnecessary.
46 *
47 * <p>
48 * This mapper supports the following parameters:
49 * </p>
50 * <ul>
51 * <li><code>struts.mapper.idParameterName</code> - If set, this value will be the name
52 * of the parameter under which the id is stored. The id will then be removed
53 * from the action name. Whether or not the method is specified, the mapper will
54 * try to truncate the identifier from the url and store it as a parameter.
55 * </li>
56 * <li><code>struts.mapper.indexMethodName</code> - The method name to call for a GET
57 * request with no id parameter. Defaults to 'index'.
58 * </li>
59 * <li><code>struts.mapper.getMethodName</code> - The method name to call for a GET
60 * request with an id parameter. Defaults to 'show'.
61 * </li>
62 * <li><code>struts.mapper.postMethodName</code> - The method name to call for a POST
63 * request with no id parameter. Defaults to 'create'.
64 * </li>
65 * <li><code>struts.mapper.putMethodName</code> - The method name to call for a PUT
66 * request with an id parameter. Defaults to 'update'.
67 * </li>
68 * <li><code>struts.mapper.deleteMethodName</code> - The method name to call for a DELETE
69 * request with an id parameter. Defaults to 'destroy'.
70 * </li>
71 * <li><code>struts.mapper.editMethodName</code> - The method name to call for a GET
72 * request with an id parameter and the 'edit' view specified. Defaults to 'edit'.
73 * </li>
74 * <li><code>struts.mapper.newMethodName</code> - The method name to call for a GET
75 * request with no id parameter and the 'new' view specified. Defaults to 'editNew'.
76 * </li>
77 * </ul>
78 * <p>
79 * The following URL's will invoke its methods:
80 * </p>
81 * <ul>
82 * <li><code>GET: /movies => method="index"</code></li>
83 * <li><code>GET: /movies/Thrillers => method="show", id="Thrillers"</code></li>
84 * <li><code>GET: /movies/Thrillers;edit => method="edit", id="Thrillers"</code></li>
85 * <li><code>GET: /movies/Thrillers/edit => method="edit", id="Thrillers"</code></li>
86 * <li><code>GET: /movies/new => method="editNew"</code></li>
87 * <li><code>POST: /movies => method="create"</code></li>
88 * <li><code>PUT: /movies/Thrillers => method="update", id="Thrillers"</code></li>
89 * <li><code>DELETE: /movies/Thrillers => method="destroy", id="Thrillers"</code></li>
90 * </ul>
91 * <p>
92 * To simulate the HTTP methods PUT and DELETE, since they aren't supported by HTML,
93 * the HTTP parameter "_method" will be used.
94 * </p>
95 * <!-- END SNIPPET: description -->
96 */
97 public class RestActionMapper extends DefaultActionMapper {
98
99 protected static final Logger LOG = LoggerFactory.getLogger(RestActionMapper.class);
100 public static final String HTTP_METHOD_PARAM = "_method";
101 private String idParameterName = "id";
102 private String indexMethodName = "index";
103 private String getMethodName = "show";
104 private String postMethodName = "create";
105 private String editMethodName = "edit";
106 private String newMethodName = "editNew";
107 private String deleteMethodName = "destroy";
108 private String putMethodName = "update";
109
110 public RestActionMapper() {
111 }
112
113 public String getIdParameterName() {
114 return idParameterName;
115 }
116
117 @Inject(required=false,value=StrutsConstants.STRUTS_ID_PARAMETER_NAME)
118 public void setIdParameterName(String idParameterName) {
119 this.idParameterName = idParameterName;
120 }
121
122 @Inject(required=false,value="struts.mapper.indexMethodName")
123 public void setIndexMethodName(String indexMethodName) {
124 this.indexMethodName = indexMethodName;
125 }
126
127 @Inject(required=false,value="struts.mapper.getMethodName")
128 public void setGetMethodName(String getMethodName) {
129 this.getMethodName = getMethodName;
130 }
131
132 @Inject(required=false,value="struts.mapper.postMethodName")
133 public void setPostMethodName(String postMethodName) {
134 this.postMethodName = postMethodName;
135 }
136
137 @Inject(required=false,value="struts.mapper.editMethodName")
138 public void setEditMethodName(String editMethodName) {
139 this.editMethodName = editMethodName;
140 }
141
142 @Inject(required=false,value="struts.mapper.newMethodName")
143 public void setNewMethodName(String newMethodName) {
144 this.newMethodName = newMethodName;
145 }
146
147 @Inject(required=false,value="struts.mapper.deleteMethodName")
148 public void setDeleteMethodName(String deleteMethodName) {
149 this.deleteMethodName = deleteMethodName;
150 }
151
152 @Inject(required=false,value="struts.mapper.putMethodName")
153 public void setPutMethodName(String putMethodName) {
154 this.putMethodName = putMethodName;
155 }
156
157 public ActionMapping getMapping(HttpServletRequest request,
158 ConfigurationManager configManager) {
159 ActionMapping mapping = new ActionMapping();
160 String uri = getUri(request);
161
162 uri = dropExtension(uri, mapping);
163 if (uri == null) {
164 return null;
165 }
166
167 parseNameAndNamespace(uri, mapping, configManager);
168
169 handleSpecialParameters(request, mapping);
170
171 if (mapping.getName() == null) {
172 return null;
173 }
174
175
176 String name = mapping.getName();
177 int exclamation = name.lastIndexOf("!");
178 if (exclamation != -1) {
179 mapping.setName(name.substring(0, exclamation));
180 mapping.setMethod(name.substring(exclamation + 1));
181 }
182
183 String fullName = mapping.getName();
184
185 if (fullName != null && fullName.length() > 0) {
186
187
188 int scPos = fullName.indexOf(';');
189 if (scPos > -1 && !"edit".equals(fullName.substring(scPos+1))) {
190 fullName = fullName.substring(0, scPos);
191 }
192
193 int lastSlashPos = fullName.lastIndexOf('/');
194 String id = null;
195 if (lastSlashPos > -1) {
196
197
198 int prevSlashPos = fullName.lastIndexOf('/', lastSlashPos - 1);
199 if (prevSlashPos > -1) {
200 mapping.setMethod(fullName.substring(lastSlashPos+1));
201 fullName = fullName.substring(0, lastSlashPos);
202 lastSlashPos = prevSlashPos;
203 }
204 id = fullName.substring(lastSlashPos+1);
205 }
206
207
208
209
210 if (mapping.getMethod() == null) {
211
212
213 if (lastSlashPos == -1 || lastSlashPos == fullName.length() -1) {
214
215
216 if (isGet(request)) {
217 mapping.setMethod(indexMethodName);
218
219
220 } else if (isPost(request)) {
221 mapping.setMethod(postMethodName);
222 }
223
224
225 } else if (id != null) {
226
227
228 if (isGet(request) && id.endsWith(";edit")) {
229 id = id.substring(0, id.length() - ";edit".length());
230 mapping.setMethod(editMethodName);
231
232
233 } else if (isGet(request) && "new".equals(id)) {
234 mapping.setMethod(newMethodName);
235
236
237 } else if (isDelete(request)) {
238 mapping.setMethod(deleteMethodName);
239
240
241 } else if (isGet(request)) {
242 mapping.setMethod(getMethodName);
243
244
245 } else if (isPut(request)) {
246 mapping.setMethod(putMethodName);
247 }
248 }
249 }
250
251
252 if (id != null) {
253 if (!"new".equals(id)) {
254 if (mapping.getParams() == null) {
255 mapping.setParams(new HashMap());
256 }
257 mapping.getParams().put(idParameterName, new String[]{id});
258 }
259 fullName = fullName.substring(0, lastSlashPos);
260 }
261
262 mapping.setName(fullName);
263 }
264
265 return mapping;
266 }
267
268 /***
269 * Parses the name and namespace from the uri. Uses the configured package
270 * namespaces to determine the name and id parameter, to be parsed later.
271 *
272 * @param uri
273 * The uri
274 * @param mapping
275 * The action mapping to populate
276 */
277 protected void parseNameAndNamespace(String uri, ActionMapping mapping,
278 ConfigurationManager configManager) {
279 String namespace, name;
280 int lastSlash = uri.lastIndexOf("/");
281 if (lastSlash == -1) {
282 namespace = "";
283 name = uri;
284 } else if (lastSlash == 0) {
285
286
287
288 namespace = "/";
289 name = uri.substring(lastSlash + 1);
290 } else {
291
292 Configuration config = configManager.getConfiguration();
293 String prefix = uri.substring(0, lastSlash);
294 namespace = "";
295
296 for (Iterator i = config.getPackageConfigs().values().iterator(); i
297 .hasNext();) {
298 String ns = ((PackageConfig) i.next()).getNamespace();
299 if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
300 if (ns.length() > namespace.length()) {
301 namespace = ns;
302 }
303 }
304 }
305
306 name = uri.substring(namespace.length() + 1);
307 }
308
309 mapping.setNamespace(namespace);
310 mapping.setName(name);
311 }
312
313 protected boolean isGet(HttpServletRequest request) {
314 return "get".equalsIgnoreCase(request.getMethod());
315 }
316
317 protected boolean isPost(HttpServletRequest request) {
318 return "post".equalsIgnoreCase(request.getMethod());
319 }
320
321 protected boolean isPut(HttpServletRequest request) {
322 if ("put".equalsIgnoreCase(request.getMethod())) {
323 return true;
324 } else {
325 return isPost(request) && "put".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM));
326 }
327 }
328
329 protected boolean isDelete(HttpServletRequest request) {
330 if ("delete".equalsIgnoreCase(request.getMethod())) {
331 return true;
332 } else {
333 return "delete".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM));
334 }
335 }
336
337 }