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 int lastSlashPos = fullName.lastIndexOf('/');
187 String id = null;
188 if (lastSlashPos > -1) {
189
190
191 int prevSlashPos = fullName.lastIndexOf('/', lastSlashPos - 1);
192 if (prevSlashPos > -1) {
193 mapping.setMethod(fullName.substring(lastSlashPos+1));
194 fullName = fullName.substring(0, lastSlashPos);
195 lastSlashPos = prevSlashPos;
196 }
197 id = fullName.substring(lastSlashPos+1);
198 }
199
200
201
202
203 if (mapping.getMethod() == null) {
204
205
206 if (lastSlashPos == -1 || lastSlashPos == fullName.length() -1) {
207
208
209 if (isGet(request)) {
210 mapping.setMethod(indexMethodName);
211
212
213 } else if (isPost(request)) {
214 mapping.setMethod(postMethodName);
215 }
216
217
218 } else if (id != null) {
219
220
221 if (isGet(request) && id.endsWith(";edit")) {
222 id = id.substring(0, id.length() - ";edit".length());
223 mapping.setMethod(editMethodName);
224
225
226 } else if (isGet(request) && "new".equals(id)) {
227 mapping.setMethod(newMethodName);
228
229
230 } else if (isDelete(request)) {
231 mapping.setMethod(deleteMethodName);
232
233
234 } else if (isGet(request)) {
235 mapping.setMethod(getMethodName);
236
237
238 } else if (isPut(request)) {
239 mapping.setMethod(putMethodName);
240 }
241 }
242 }
243
244
245 if (id != null) {
246 if (!"new".equals(id)) {
247 if (mapping.getParams() == null) {
248 mapping.setParams(new HashMap());
249 }
250 mapping.getParams().put(idParameterName, new String[]{id});
251 }
252 fullName = fullName.substring(0, lastSlashPos);
253 }
254
255 mapping.setName(fullName);
256 }
257
258 return mapping;
259 }
260
261 /***
262 * Parses the name and namespace from the uri. Uses the configured package
263 * namespaces to determine the name and id parameter, to be parsed later.
264 *
265 * @param uri
266 * The uri
267 * @param mapping
268 * The action mapping to populate
269 */
270 protected void parseNameAndNamespace(String uri, ActionMapping mapping,
271 ConfigurationManager configManager) {
272 String namespace, name;
273 int lastSlash = uri.lastIndexOf("/");
274 if (lastSlash == -1) {
275 namespace = "";
276 name = uri;
277 } else if (lastSlash == 0) {
278
279
280
281 namespace = "/";
282 name = uri.substring(lastSlash + 1);
283 } else {
284
285 Configuration config = configManager.getConfiguration();
286 String prefix = uri.substring(0, lastSlash);
287 namespace = "";
288
289 for (Iterator i = config.getPackageConfigs().values().iterator(); i
290 .hasNext();) {
291 String ns = ((PackageConfig) i.next()).getNamespace();
292 if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
293 if (ns.length() > namespace.length()) {
294 namespace = ns;
295 }
296 }
297 }
298
299 name = uri.substring(namespace.length() + 1);
300 }
301
302 mapping.setNamespace(namespace);
303 mapping.setName(name);
304 }
305
306 protected boolean isGet(HttpServletRequest request) {
307 return "get".equalsIgnoreCase(request.getMethod());
308 }
309
310 protected boolean isPost(HttpServletRequest request) {
311 return "post".equalsIgnoreCase(request.getMethod());
312 }
313
314 protected boolean isPut(HttpServletRequest request) {
315 if ("put".equalsIgnoreCase(request.getMethod())) {
316 return true;
317 } else {
318 return isPost(request) && "put".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM));
319 }
320 }
321
322 protected boolean isDelete(HttpServletRequest request) {
323 if ("delete".equalsIgnoreCase(request.getMethod())) {
324 return true;
325 } else {
326 return "delete".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM));
327 }
328 }
329
330 }