View Javadoc

1   /*
2    * $Id: RestWorkflowInterceptor.java 651946 2008-04-27 13:41:38Z apetrelli $
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 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   * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
94   *     &lt;interceptor-ref name="params"/&gt;
95   *     &lt;interceptor-ref name="validation"/&gt;
96   *     &lt;interceptor-ref name="workflow"/&gt;
97   *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
98   * &lt;/action&gt;
99   * 
100  * &lt;-- In this case myMethod as well as mySecondMethod of the action class
101  *        will not pass through the workflow process --&gt;
102  * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
103  *     &lt;interceptor-ref name="params"/&gt;
104  *     &lt;interceptor-ref name="validation"/&gt;
105  *     &lt;interceptor-ref name="workflow"&gt;
106  *         &lt;param name="excludeMethods"&gt;myMethod,mySecondMethod&lt;/param&gt;
107  *     &lt;/interceptor-ref name="workflow"&gt;
108  *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
109  * &lt;/action&gt;
110  *
111  * &lt;-- In this case, the result named "error" will be used when
112  *        an action / field error is found --&gt;
113  * &lt;-- 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 --&gt;
115  * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
116  *     &lt;interceptor-ref name="params"/&gt;
117  *     &lt;interceptor-ref name="validation"/&gt;
118  *     &lt;interceptor-ref name="workflow"&gt;
119  *        &lt;param name="inputResultName"&gt;error&lt;/param&gt;
120 *         &lt;param name="excludeMethods"&gt;*&lt;/param&gt;
121 *         &lt;param name="includeMethods"&gt;myWorkflowMethod&lt;/param&gt;
122  *     &lt;/interceptor-ref&gt;
123  *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
124  * &lt;/action&gt;
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 }