View Javadoc

1   /*
2    * $Id: RestWorkflowInterceptor.java 676195 2008-07-12 15:55:58Z mrdon $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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   * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
92   *     &lt;interceptor-ref name="params"/&gt;
93   *     &lt;interceptor-ref name="validation"/&gt;
94   *     &lt;interceptor-ref name="workflow"/&gt;
95   *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
96   * &lt;/action&gt;
97   * 
98   * &lt;-- In this case myMethod as well as mySecondMethod of the action class
99   *        will not pass through the workflow process --&gt;
100  * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
101  *     &lt;interceptor-ref name="params"/&gt;
102  *     &lt;interceptor-ref name="validation"/&gt;
103  *     &lt;interceptor-ref name="workflow"&gt;
104  *         &lt;param name="excludeMethods"&gt;myMethod,mySecondMethod&lt;/param&gt;
105  *     &lt;/interceptor-ref name="workflow"&gt;
106  *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
107  * &lt;/action&gt;
108  *
109  * &lt;-- In this case, the result named "error" will be used when
110  *        an action / field error is found --&gt;
111  * &lt;-- 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 --&gt;
113  * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
114  *     &lt;interceptor-ref name="params"/&gt;
115  *     &lt;interceptor-ref name="validation"/&gt;
116  *     &lt;interceptor-ref name="workflow"&gt;
117  *        &lt;param name="inputResultName"&gt;error&lt;/param&gt;
118 *         &lt;param name="excludeMethods"&gt;*&lt;/param&gt;
119 *         &lt;param name="includeMethods"&gt;myWorkflowMethod&lt;/param&gt;
120  *     &lt;/interceptor-ref&gt;
121  *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
122  * &lt;/action&gt;
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 }