1 /*
2 * Copyright (c) 2002-2007 by OpenSymphony
3 * All rights reserved.
4 */
5 package com.opensymphony.xwork2.validator;
6
7 import com.opensymphony.xwork2.ActionInvocation;
8 import com.opensymphony.xwork2.ActionProxy;
9 import com.opensymphony.xwork2.Validateable;
10 import com.opensymphony.xwork2.inject.Inject;
11 import com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor;
12 import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
13 import com.opensymphony.xwork2.interceptor.PrefixMethodInvocationUtil;
14 import com.opensymphony.xwork2.util.logging.Logger;
15 import com.opensymphony.xwork2.util.logging.LoggerFactory;
16
17 /**
18 * <!-- START SNIPPET: description -->
19 *
20 * This interceptor runs the action through the standard validation framework, which in turn checks the action against
21 * any validation rules (found in files such as <i>ActionClass-validation.xml</i>) and adds field-level and action-level
22 * error messages (provided that the action implements {@link com.opensymphony.xwork2.ValidationAware}). This interceptor
23 * is often one of the last (or second to last) interceptors applied in a stack, as it assumes that all values have
24 * already been set on the action.
25 *
26 * <p/>This interceptor does nothing if the name of the method being invoked is specified in the <b>excludeMethods</b>
27 * parameter. <b>excludeMethods</b> accepts a comma-delimited list of method names. For example, requests to
28 * <b>foo!input.action</b> and <b>foo!back.action</b> will be skipped by this interceptor if you set the
29 * <b>excludeMethods</b> parameter to "input, back".
30 *
31 * </ol>
32 *
33 * <p/> The workflow of the action request does not change due to this interceptor. Rather,
34 * this interceptor is often used in conjuction with the <b>workflow</b> interceptor.
35 *
36 * <p/>
37 *
38 * <b>NOTE:</b> As this method extends off MethodFilterInterceptor, it is capable of
39 * deciding if it is applicable only to selective methods in the action class. See
40 * <code>MethodFilterInterceptor</code> for more info.
41 *
42 * <!-- END SNIPPET: description -->
43 *
44 * <p/> <u>Interceptor parameters:</u>
45 *
46 * <!-- START SNIPPET: parameters -->
47 *
48 * <ul>
49 *
50 * <li>alwaysInvokeValidate - Defaults to true. If true validate() method will always
51 * be invoked, otherwise it will not.</li>
52 *
53 * <li>programmatic - Defaults to true. If true and the action is Validateable call validate(),
54 * and any method that starts with "validate".
55 * </li>
56 *
57 * <li>declarative - Defaults to true. Perform validation based on xml or annotations.</li>
58 *
59 * </ul>
60 *
61 * <!-- END SNIPPET: parameters -->
62 *
63 * <p/> <u>Extending the interceptor:</u>
64 *
65 * <p/>
66 *
67 * <!-- START SNIPPET: extending -->
68 *
69 * There are no known extension points for this interceptor.
70 *
71 * <!-- END SNIPPET: extending -->
72 *
73 * <p/> <u>Example code:</u>
74 *
75 * <pre>
76 * <!-- START SNIPPET: example -->
77 *
78 * <action name="someAction" class="com.examples.SomeAction">
79 * <interceptor-ref name="params"/>
80 * <interceptor-ref name="validation"/>
81 * <interceptor-ref name="workflow"/>
82 * <result name="success">good_result.ftl</result>
83 * </action>
84 *
85 * <-- in the following case myMethod of the action class will not
86 * get validated -->
87 * <action name="someAction" class="com.examples.SomeAction">
88 * <interceptor-ref name="params"/>
89 * <interceptor-ref name="validation">
90 * <param name="excludeMethods">myMethod</param>
91 * </interceptor-ref>
92 * <interceptor-ref name="workflow"/>
93 * <result name="success">good_result.ftl</result>
94 * </action>
95 *
96 * <-- in the following case only annotated methods of the action class will
97 * be validated -->
98 * <action name="someAction" class="com.examples.SomeAction">
99 * <interceptor-ref name="params"/>
100 * <interceptor-ref name="validation">
101 * <param name="validateAnnotatedMethodOnly">true</param>
102 * </interceptor-ref>
103 * <interceptor-ref name="workflow"/>
104 * <result name="success">good_result.ftl</result>
105 * </action>
106 *
107 *
108 * <!-- END SNIPPET: example -->
109 * </pre>
110 *
111 * @author Jason Carreira
112 * @author Rainer Hermanns
113 * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
114 * @see ActionValidatorManager
115 * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor
116 */
117 public class ValidationInterceptor extends MethodFilterInterceptor {
118
119 private boolean validateAnnotatedMethodOnly;
120
121 private ActionValidatorManager actionValidatorManager;
122
123 private static final Logger LOG = LoggerFactory.getLogger(ValidationInterceptor.class);
124
125 private final static String VALIDATE_PREFIX = "validate";
126 private final static String ALT_VALIDATE_PREFIX = "validateDo";
127
128 private boolean alwaysInvokeValidate = true;
129 private boolean programmatic = true;
130 private boolean declarative = true;
131
132 @Inject
133 public void setActionValidatorManager(ActionValidatorManager mgr) {
134 this.actionValidatorManager = mgr;
135 }
136
137 /**
138 * Determines if {@link Validateable}'s <code>validate()</code> should be called,
139 * as well as methods whose name that start with "validate". Defaults to "true".
140 *
141 * @param programmatic <tt>true</tt> then <code>validate()</code> is invoked.
142 */
143 public void setProgrammatic(boolean programmatic) {
144 this.programmatic = programmatic;
145 }
146
147 /**
148 * Determines if validation based on annotations or xml should be performed. Defaults
149 * to "true".
150 *
151 * @param declarative <tt>true</tt> then perform validation based on annotations or xml.
152 */
153 public void setDeclarative(boolean declarative) {
154 this.declarative = declarative;
155 }
156
157 /**
158 * Determines if {@link Validateable}'s <code>validate()</code> should always
159 * be invoked. Default to "true".
160 *
161 * @param alwaysInvokeValidate <tt>true</tt> then <code>validate()</code> is always invoked.
162 */
163 public void setAlwaysInvokeValidate(String alwaysInvokeValidate) {
164 this.alwaysInvokeValidate = Boolean.parseBoolean(alwaysInvokeValidate);
165 }
166
167 /**
168 * Gets if <code>validate()</code> should always be called or only per annotated method.
169 *
170 * @return <tt>true</tt> to only validate per annotated method, otherwise <tt>false</tt> to always validate.
171 */
172 public boolean isValidateAnnotatedMethodOnly() {
173 return validateAnnotatedMethodOnly;
174 }
175
176 /**
177 * Determine if <code>validate()</code> should always be called or only per annotated method.
178 * Default to <tt>false</tt>.
179 *
180 * @param validateAnnotatedMethodOnly <tt>true</tt> to only validate per annotated method, otherwise <tt>false</tt> to always validate.
181 */
182 public void setValidateAnnotatedMethodOnly(boolean validateAnnotatedMethodOnly) {
183 this.validateAnnotatedMethodOnly = validateAnnotatedMethodOnly;
184 }
185
186 /**
187 * Gets the current action and its context and delegates to {@link ActionValidatorManager} proper validate method.
188 *
189 * @param invocation the execution state of the Action.
190 * @throws Exception if an error occurs validating the action.
191 */
192 protected void doBeforeInvocation(ActionInvocation invocation) throws Exception {
193 Object action = invocation.getAction();
194 ActionProxy proxy = invocation.getProxy();
195
196 //the action name has to be from the url, otherwise validators that use aliases, like
197 //MyActio-someaction-validator.xml will not be found, see WW-3194
198 String context = proxy.getActionName();
199 String method = proxy.getMethod();
200
201 if (log.isDebugEnabled()) {
202 log.debug("Validating "
203 + invocation.getProxy().getNamespace() + "/" + invocation.getProxy().getActionName() + " with method "+ method +".");
204 }
205
206
207 if (declarative) {
208 if (validateAnnotatedMethodOnly) {
209 actionValidatorManager.validate(action, context, method);
210 } else {
211 actionValidatorManager.validate(action, context);
212 }
213 }
214
215 if (action instanceof Validateable && programmatic) {
216 // keep exception that might occured in validateXXX or validateDoXXX
217 Exception exception = null;
218
219 Validateable validateable = (Validateable) action;
220 if (LOG.isDebugEnabled()) {
221 LOG.debug("Invoking validate() on action "+validateable);
222 }
223
224 try {
225 PrefixMethodInvocationUtil.invokePrefixMethod(
226 invocation,
227 new String[] { VALIDATE_PREFIX, ALT_VALIDATE_PREFIX });
228 }
229 catch(Exception e) {
230 // If any exception occurred while doing reflection, we want
231 // validate() to be executed
232 LOG.warn("an exception occured while executing the prefix method", e);
233 exception = e;
234 }
235
236
237 if (alwaysInvokeValidate) {
238 validateable.validate();
239 }
240
241 if (exception != null) {
242 // rethrow if something is wrong while doing validateXXX / validateDoXXX
243 throw exception;
244 }
245 }
246 }
247
248 @Override
249 protected String doIntercept(ActionInvocation invocation) throws Exception {
250 doBeforeInvocation(invocation);
251
252 return invocation.invoke();
253 }
254
255 }