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 java.util.HashMap;
25 import java.util.Map;
26
27 import org.apache.struts2.ServletActionContext;
28 import org.apache.struts2.dispatcher.mapper.ActionMapping;
29
30 import com.opensymphony.xwork2.Action;
31 import com.opensymphony.xwork2.ActionContext;
32 import com.opensymphony.xwork2.ActionInvocation;
33 import com.opensymphony.xwork2.ValidationAware;
34 import com.opensymphony.xwork2.inject.Inject;
35 import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
36 import com.opensymphony.xwork2.util.logging.Logger;
37 import com.opensymphony.xwork2.util.logging.LoggerFactory;
38
39 import static javax.servlet.http.HttpServletResponse.*;
40
41 /***
42 * <!-- START SNIPPET: description -->
43 *
44 * An interceptor that makes sure there are not validation errors before allowing the interceptor chain to continue.
45 * <b>This interceptor does not perform any validation</b>.
46 *
47 * <p>Copied from the {@link com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor}, this interceptor adds support for error handling of Restful
48 * operations. For example, if an validation error is discovered, a map of errors is created and processed to be
49 * returned, using the appropriate content handler for rendering the body.</p>
50 *
51 * <p/>This interceptor does nothing if the name of the method being invoked is specified in the <b>excludeMethods</b>
52 * parameter. <b>excludeMethods</b> accepts a comma-delimited list of method names. For example, requests to
53 * <b>foo!input.action</b> and <b>foo!back.action</b> will be skipped by this interceptor if you set the
54 * <b>excludeMethods</b> parameter to "input, back".
55 *
56 * <b>Note:</b> As this method extends off MethodFilterInterceptor, it is capable of
57 * deciding if it is applicable only to selective methods in the action class. This is done by adding param tags
58 * for the interceptor element, naming either a list of excluded method names and/or a list of included method
59 * names, whereby includeMethods overrides excludedMethods. A single * sign is interpreted as wildcard matching
60 * all methods for both parameters.
61 * See {@link MethodFilterInterceptor} for more info.
62 *
63 * <!-- END SNIPPET: description -->
64 *
65 * <p/> <u>Interceptor parameters:</u>
66 *
67 * <!-- START SNIPPET: parameters -->
68 *
69 * <ul>
70 *
71 * <li>inputResultName - Default to "input". Determine the result name to be returned when
72 * an action / field error is found.</li>
73 *
74 * </ul>
75 *
76 * <!-- END SNIPPET: parameters -->
77 *
78 * <p/> <u>Extending the interceptor:</u>
79 *
80 * <p/>
81 *
82 * <!-- START SNIPPET: extending -->
83 *
84 * There are no known extension points for this interceptor.
85 *
86 * <!-- END SNIPPET: extending -->
87 *
88 * <p/> <u>Example code:</u>
89 *
90 * <pre>
91 * <!-- START SNIPPET: example -->
92 *
93 * <action name="someAction" class="com.examples.SomeAction">
94 * <interceptor-ref name="params"/>
95 * <interceptor-ref name="validation"/>
96 * <interceptor-ref name="workflow"/>
97 * <result name="success">good_result.ftl</result>
98 * </action>
99 *
100 * <-- In this case myMethod as well as mySecondMethod of the action class
101 * will not pass through the workflow process -->
102 * <action name="someAction" class="com.examples.SomeAction">
103 * <interceptor-ref name="params"/>
104 * <interceptor-ref name="validation"/>
105 * <interceptor-ref name="workflow">
106 * <param name="excludeMethods">myMethod,mySecondMethod</param>
107 * </interceptor-ref name="workflow">
108 * <result name="success">good_result.ftl</result>
109 * </action>
110 *
111 * <-- In this case, the result named "error" will be used when
112 * an action / field error is found -->
113 * <-- The Interceptor will only be applied for myWorkflowMethod method of action
114 * classes, since this is the only included method while any others are excluded -->
115 * <action name="someAction" class="com.examples.SomeAction">
116 * <interceptor-ref name="params"/>
117 * <interceptor-ref name="validation"/>
118 * <interceptor-ref name="workflow">
119 * <param name="inputResultName">error</param>
120 * <param name="excludeMethods">*</param>
121 * <param name="includeMethods">myWorkflowMethod</param>
122 * </interceptor-ref>
123 * <result name="success">good_result.ftl</result>
124 * </action>
125 *
126 * <!-- END SNIPPET: example -->
127 * </pre>
128 *
129 * @author Jason Carreira
130 * @author Rainer Hermanns
131 * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
132 * @author Philip Luppens
133 * @author tm_jee
134 */
135 public class RestWorkflowInterceptor extends MethodFilterInterceptor {
136
137 private static final long serialVersionUID = 7563014655616490865L;
138
139 private static final Logger LOG = LoggerFactory.getLogger(RestWorkflowInterceptor.class);
140
141 private String inputResultName = Action.INPUT;
142
143 private ContentTypeHandlerManager manager;
144
145 private String postMethodName = "create";
146 private String editMethodName = "edit";
147 private String newMethodName = "editNew";
148 private String putMethodName = "update";
149
150 @Inject(required=false,value="struts.mapper.postMethodName")
151 public void setPostMethodName(String postMethodName) {
152 this.postMethodName = postMethodName;
153 }
154
155 @Inject(required=false,value="struts.mapper.editMethodName")
156 public void setEditMethodName(String editMethodName) {
157 this.editMethodName = editMethodName;
158 }
159
160 @Inject(required=false,value="struts.mapper.newMethodName")
161 public void setNewMethodName(String newMethodName) {
162 this.newMethodName = newMethodName;
163 }
164
165 @Inject(required=false,value="struts.mapper.putMethodName")
166 public void setPutMethodName(String putMethodName) {
167 this.putMethodName = putMethodName;
168 }
169
170 @Inject
171 public void setContentTypeHandlerManager(ContentTypeHandlerManager mgr) {
172 this.manager = mgr;
173 }
174
175 /***
176 * Set the <code>inputResultName</code> (result name to be returned when
177 * a action / field error is found registered). Default to {@link Action#INPUT}
178 *
179 * @param inputResultName what result name to use when there was validation error(s).
180 */
181 public void setInputResultName(String inputResultName) {
182 this.inputResultName = inputResultName;
183 }
184
185 /***
186 * Intercept {@link ActionInvocation} and processes the errors using the {@link org.apache.struts2.rest.handler.ContentTypeHandler}
187 * appropriate for the request.
188 *
189 * @return String result name
190 */
191 protected String doIntercept(ActionInvocation invocation) throws Exception {
192 Object action = invocation.getAction();
193
194 if (action instanceof ValidationAware) {
195 ValidationAware validationAwareAction = (ValidationAware) action;
196
197 if (validationAwareAction.hasErrors()) {
198 if (LOG.isDebugEnabled()) {
199 LOG.debug("Errors on action "+validationAwareAction+", returning result name 'input'");
200 }
201 ActionMapping mapping = (ActionMapping) ActionContext.getContext().get(ServletActionContext.ACTION_MAPPING);
202 String method = inputResultName;
203 if (postMethodName.equals(mapping.getMethod())) {
204 method = newMethodName;
205 } else if (putMethodName.equals(mapping.getMethod())) {
206 method = editMethodName;
207 }
208
209
210 HttpHeaders info = new DefaultHttpHeaders()
211 .disableCaching()
212 .renderResult(method)
213 .withStatus(SC_BAD_REQUEST);
214
215 Map errors = new HashMap();
216
217 errors.put("actionErrors", validationAwareAction.getActionErrors());
218 errors.put("fieldErrors", validationAwareAction.getFieldErrors());
219 return manager.handleResult(invocation.getProxy().getConfig(), info, errors);
220 }
221 }
222
223 return invocation.invoke();
224 }
225
226 }