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