1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts2.oval.interceptor;
22
23 import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
24 import com.opensymphony.xwork2.interceptor.PrefixMethodInvocationUtil;
25 import com.opensymphony.xwork2.ActionInvocation;
26 import com.opensymphony.xwork2.ActionProxy;
27 import com.opensymphony.xwork2.ModelDriven;
28 import com.opensymphony.xwork2.Validateable;
29 import com.opensymphony.xwork2.inject.Inject;
30 import com.opensymphony.xwork2.validator.ValidatorContext;
31 import com.opensymphony.xwork2.validator.DelegatingValidatorContext;
32 import com.opensymphony.xwork2.util.logging.Logger;
33 import com.opensymphony.xwork2.util.logging.LoggerFactory;
34 import com.opensymphony.xwork2.util.ValueStack;
35 import net.sf.oval.Validator;
36 import net.sf.oval.ConstraintViolation;
37 import net.sf.oval.configuration.Configurer;
38 import net.sf.oval.context.FieldContext;
39 import net.sf.oval.context.OValContext;
40 import net.sf.oval.context.MethodReturnValueContext;
41
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.List;
46 import java.lang.reflect.Method;
47 import java.lang.reflect.Field;
48
49 import org.apache.struts2.oval.annotation.Profiles;
50 import org.apache.commons.lang.xwork.StringUtils;
51
52
53
54
55 public class OValValidationInterceptor extends MethodFilterInterceptor {
56 private static final Logger LOG = LoggerFactory.getLogger(OValValidationInterceptor.class);
57
58 protected final static String VALIDATE_PREFIX = "validate";
59 protected final static String ALT_VALIDATE_PREFIX = "validateDo";
60
61 protected boolean alwaysInvokeValidate = true;
62 protected boolean programmatic = true;
63 protected OValValidationManager validationManager;
64 private boolean validateJPAAnnotations;
65
66 @Inject
67 public void setValidationManager(OValValidationManager validationManager) {
68 this.validationManager = validationManager;
69 }
70
71 /***
72 * Enable OVal support fopr JPA
73 */
74 public void setValidateJPAAnnotations(boolean validateJPAAnnotations) {
75 this.validateJPAAnnotations = validateJPAAnnotations;
76 }
77
78 /***
79 * Determines if {@link com.opensymphony.xwork2.Validateable}'s <code>validate()</code> should be called,
80 * as well as methods whose name that start with "validate". Defaults to "true".
81 *
82 * @param programmatic <tt>true</tt> then <code>validate()</code> is invoked.
83 */
84 public void setProgrammatic(boolean programmatic) {
85 this.programmatic = programmatic;
86 }
87
88 /***
89 * Determines if {@link com.opensymphony.xwork2.Validateable}'s <code>validate()</code> should always
90 * be invoked. Default to "true".
91 *
92 * @param alwaysInvokeValidate <tt>true</tt> then <code>validate()</code> is always invoked.
93 */
94 public void setAlwaysInvokeValidate(String alwaysInvokeValidate) {
95 this.alwaysInvokeValidate = Boolean.parseBoolean(alwaysInvokeValidate);
96 }
97
98 protected String doIntercept(ActionInvocation invocation) throws Exception {
99 Object action = invocation.getAction();
100 ActionProxy proxy = invocation.getProxy();
101 ValueStack valueStack = invocation.getStack();
102 String methodName = proxy.getMethod();
103 String context = proxy.getConfig().getName();
104
105 if (LOG.isDebugEnabled()) {
106 LOG.debug("Validating [#0/#1] with method [#2]", invocation.getProxy().getNamespace(), invocation.getProxy().getActionName(), methodName);
107 }
108
109
110 performOValValidation(action, valueStack, methodName, context);
111
112
113 performProgrammaticValidation(invocation, action);
114
115 return invocation.invoke();
116 }
117
118 private void performProgrammaticValidation(ActionInvocation invocation, Object action) throws Exception {
119 if (action instanceof Validateable && programmatic) {
120
121 Exception exception = null;
122
123 Validateable validateable = (Validateable) action;
124 if (LOG.isDebugEnabled()) {
125 LOG.debug("Invoking validate() on action [#0]", validateable.toString());
126 }
127
128 try {
129 PrefixMethodInvocationUtil.invokePrefixMethod(
130 invocation,
131 new String[]{VALIDATE_PREFIX, ALT_VALIDATE_PREFIX});
132 } catch (Exception e) {
133
134
135 LOG.warn("An exception occured while executing the prefix method", e);
136 exception = e;
137 }
138
139 if (alwaysInvokeValidate) {
140 validateable.validate();
141 }
142
143 if (exception != null) {
144
145 throw exception;
146 }
147 }
148 }
149
150 protected void performOValValidation(Object action, ValueStack valueStack, String methodName, String context) throws NoSuchMethodException {
151 Class clazz = action.getClass();
152
153 List<Configurer> configurers = validationManager.getConfigurers(clazz, context, validateJPAAnnotations);
154
155 Validator validator = configurers.isEmpty() ? new Validator() : new Validator(configurers);
156
157 Method method = clazz.getMethod(methodName, new Class[0]);
158 if (method != null) {
159 Profiles profiles = method.getAnnotation(Profiles.class);
160 if (profiles != null) {
161 String[] profileNames = profiles.value();
162 if (profileNames != null && profileNames.length > 0) {
163 validator.disableAllProfiles();
164 if (LOG.isDebugEnabled())
165 LOG.debug("Enabling profiles [#0]", StringUtils.join(profileNames, ","));
166 for (String profileName : profileNames)
167 validator.enableProfile(profileName);
168 }
169 }
170 }
171
172
173 List<ConstraintViolation> violations = validator.validate(action);
174 addValidationErrors(violations.toArray(new ConstraintViolation[0]), action, valueStack, null);
175 }
176
177 private void addValidationErrors(ConstraintViolation[] violations, Object action, ValueStack valueStack, String parentFieldname) {
178 if (violations != null) {
179 ValidatorContext validatorContext = new DelegatingValidatorContext(action);
180 for (ConstraintViolation violation : violations) {
181
182 String key = violation.getMessage();
183
184
185 valueStack.push(violation.getContext());
186 String message = key;
187 try {
188 message = validatorContext.getText(key);
189 } finally {
190 valueStack.pop();
191 }
192
193 if (isActionError(violation)) {
194 LOG.debug("Adding action error '#0'", message);
195 validatorContext.addActionError(message);
196 } else {
197 ValidationError validationError = buildValidationError(violation, message);
198
199
200 String fieldName = validationError.getFieldName();
201 if (parentFieldname != null) {
202 fieldName = parentFieldname + "." + fieldName;
203 }
204
205 LOG.debug("Adding field error [#0] with message '#1'", fieldName, validationError.getMessage());
206 validatorContext.addFieldError(fieldName, validationError.getMessage());
207
208
209 if ((action instanceof ModelDriven) && "model".equals(fieldName)) {
210 fieldName = null;
211 }
212
213
214 addValidationErrors(violation.getCauses(), action, valueStack, fieldName);
215 }
216 }
217 }
218 }
219
220
221
222
223 /***
224 * Get field name and message, used to add the validation error to fieldErrors
225 */
226 protected ValidationError buildValidationError(ConstraintViolation violation, String message) {
227 OValContext context = violation.getContext();
228 if (context instanceof FieldContext) {
229 Field field = ((FieldContext) context).getField();
230 String className = field.getDeclaringClass().getName();
231
232
233 String finalMessage = StringUtils.removeStart(message, className + ".");
234
235 return new ValidationError(field.getName(), finalMessage);
236 } else if (context instanceof MethodReturnValueContext) {
237 Method method = ((MethodReturnValueContext) context).getMethod();
238 String className = method.getDeclaringClass().getName();
239 String methodName = method.getName();
240
241
242 String finalMessage = StringUtils.removeStart(message, className + ".");
243
244 String fieldName = null;
245 if (methodName.startsWith("get")) {
246 fieldName = StringUtils.uncapitalize(StringUtils.removeStart(methodName, "get"));
247 } else if (methodName.startsWith("is")) {
248 fieldName = StringUtils.uncapitalize(StringUtils.removeStart(methodName, "is"));
249 }
250
251
252 if (fieldName != null)
253 finalMessage = finalMessage.replaceAll(methodName + "//(.*?//)", fieldName);
254
255 return new ValidationError(StringUtils.defaultString(fieldName, methodName), finalMessage);
256 }
257
258 return new ValidationError(violation.getCheckName(), message);
259 }
260
261 /***
262 * Decide if a violation should be added to the fieldErrors or actionErrors
263 */
264 protected boolean isActionError(ConstraintViolation violation) {
265 return false;
266 }
267
268 class ValidationError {
269 private String fieldName;
270 private String message;
271
272 ValidationError(String fieldName, String message) {
273 this.fieldName = fieldName;
274 this.message = message;
275 }
276
277 public String getFieldName() {
278 return fieldName;
279 }
280
281 public String getMessage() {
282 return message;
283 }
284 }
285 }