1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.custom.validatebeanbehavior;
20
21 import java.lang.annotation.Annotation;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24 import java.text.SimpleDateFormat;
25 import java.util.ArrayList;
26 import java.util.Calendar;
27 import java.util.Date;
28 import java.util.List;
29 import java.util.Set;
30
31 import javax.el.ELContext;
32 import javax.el.ValueExpression;
33 import javax.el.ValueReference;
34 import javax.faces.FacesException;
35 import javax.faces.application.ResourceDependency;
36 import javax.faces.component.UICommand;
37 import javax.faces.component.UIComponent;
38 import javax.faces.component.UIForm;
39 import javax.faces.component.UIInput;
40 import javax.faces.component.UIMessages;
41 import javax.faces.component.UIViewRoot;
42 import javax.faces.component.behavior.ClientBehaviorBase;
43 import javax.faces.component.behavior.ClientBehaviorContext;
44 import javax.faces.context.FacesContext;
45 import javax.faces.convert.Converter;
46 import javax.faces.convert.DateTimeConverter;
47 import javax.servlet.ServletContext;
48 import javax.validation.Validation;
49 import javax.validation.ValidatorFactory;
50 import javax.validation.constraints.Future;
51 import javax.validation.constraints.Max;
52 import javax.validation.constraints.Min;
53 import javax.validation.constraints.NotNull;
54 import javax.validation.metadata.BeanDescriptor;
55 import javax.validation.metadata.ConstraintDescriptor;
56 import javax.validation.metadata.PropertyDescriptor;
57
58 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFClientBehavior;
59
60
61
62
63
64
65
66
67
68 @JSFClientBehavior(
69 name="s:validateBean",
70 id="org.apache.myfaces.custom.ValidateBeanBehavior")
71 @ResourceDependency(name = "validateBeanBehavior.js")
72 public class ValidateBeanBehavior extends ClientBehaviorBase {
73
74
75 @Override
76 public String getScript(final ClientBehaviorContext ctx) {
77 final UIComponent component = ctx.getComponent();
78 if (!(component instanceof UICommand)) {
79 throw new FacesException("Unsupported component: " + component + " Only UICommand components are supported");
80 }
81 final UIViewRoot viewRoot = ctx.getFacesContext().getViewRoot();
82
83 UIForm form = ComponentUtils.findParentForm(component);
84 List<UIInput> inputsInForm = ComponentUtils.findInputsInForm(form);
85 UIMessages messages = ComponentUtils.findMessagesInTree(viewRoot);
86
87 return getSubmitHandler(form, inputsInForm, messages);
88 }
89
90
91
92
93
94
95
96
97
98 private String getSubmitHandler(final UIForm form, final List<UIInput> inputsInForm, final UIMessages messages) {
99 final FacesContext fc = FacesContext.getCurrentInstance();
100
101 final String clientId = form.getClientId(fc);
102 final String messagesId = (messages != null) ? "'" + messages.getClientId(fc) + "'": "null";
103 final List<ValidationPropertyModel> validations = new ArrayList<ValidationPropertyModel>();
104
105 for (final UIInput input : inputsInForm) {
106 final PropertyDescriptor propertyDescriptor = getPropertyDescriptor(fc, input);
107 final Set<ConstraintDescriptor<?>> constraints = propertyDescriptor.getConstraintDescriptors();
108 final ValidationPropertyModel model = createValidationModel(input, propertyDescriptor, constraints);
109 validations.add(model);
110 }
111
112 return writeJavaScript(fc, clientId, messagesId, validations);
113 }
114
115
116
117
118
119
120
121
122
123
124 private String writeJavaScript(final FacesContext fc, final String clientId,
125 final String messagesId, final List<ValidationPropertyModel> validations) {
126 final StringBuilder sb = new StringBuilder();
127 sb.append("return org.jkva.validateBean.validateForm(");
128 sb.append("'").append(clientId).append("', ");
129 sb.append("'").append(messagesId).append("', ");
130 sb.append("[");
131 String sep = "";
132
133 for (final ValidationPropertyModel validationModel : validations) {
134 sb.append(sep);
135 final String validation = writeJavaScriptForField(validationModel, fc);
136 sb.append(validation);
137 sep = ",";
138 }
139
140 sb.append("])");
141 return sb.toString();
142 }
143
144
145
146
147
148
149
150
151 private String writeJavaScriptForField(final ValidationPropertyModel model, final FacesContext fc) {
152 final StringBuilder sb = new StringBuilder();
153 sb.append("{");
154 sb.append("fieldId: '").append(model.getComponent().getClientId(fc)).append("'");
155 if (model.isRequired()) {
156 sb.append(",required: true");
157 }
158 sb.append(",type: '").append(model.getType()).append("'");
159 if (model.getMin() != null) {
160 sb.append(",min: true");
161 sb.append(",minValue: ").append(model.getMin()).append("");
162 }
163 if (model.getMax() != null) {
164 sb.append(",max: true");
165 sb.append(",maxValue: ").append(model.getMax()).append("");
166 }
167 if (model.isFutureDate()) {
168 String nowStr = new SimpleDateFormat(model.getDateFormat()).format(new Date());
169 sb.append(",future: true");
170 sb.append(",nowStr: '").append(nowStr).append("'");
171 sb.append(",dateFormat: '").append(model.getDateFormat()).append("'");
172 }
173 sb.append("}");
174
175 return sb.toString();
176 }
177
178
179
180
181
182
183
184
185
186 private ValidationPropertyModel createValidationModel(final UIComponent component,
187 final PropertyDescriptor propertyDescriptor,
188 final Set<ConstraintDescriptor<?>> constraints) {
189 final ValidationPropertyModel model = new ValidationPropertyModel();
190 model.setComponent(component);
191
192 if (component instanceof UIInput) {
193 model.setRequired(((UIInput) component).isRequired());
194 }
195
196 final Class<?> type = propertyDescriptor.getElementClass();
197 if (type.equals(String.class)) {
198 model.setType("text");
199 } else if (Number.class.isAssignableFrom(type)) {
200 model.setType("numeric");
201 } else if (Date.class.isAssignableFrom(type)
202 || Calendar.class.isAssignableFrom(type)) {
203 model.setType("date");
204 }
205
206 for (final ConstraintDescriptor<?> constraint : constraints) {
207 final Annotation annotation = constraint.getAnnotation();
208 if (annotation instanceof NotNull) {
209 model.setRequired(true);
210 } else if (annotation instanceof Min) {
211 model.setMin(((Min) annotation).value());
212 } else if (annotation instanceof Max) {
213 model.setMax(((Max) annotation).value());
214 } else if (annotation instanceof Future) {
215 model.setFutureDate(true);
216 }
217 }
218
219 final Converter converter = ((UIInput) component).getConverter();
220 if (converter instanceof DateTimeConverter) {
221 model.setDateFormat(((DateTimeConverter) converter).getPattern());
222 }
223
224 return model;
225 }
226
227
228 private PropertyDescriptor getPropertyDescriptor(final FacesContext fc, final UIComponent component) {
229 final ValueReferenceWrapper reference = getValueReference(component, fc);
230
231 if (reference != null) {
232 final Object base = reference.getBase();
233 if (base != null) {
234 final Class<?> valueBaseClass = base.getClass();
235 final String valueProperty = (String) reference.getProperty();
236 if (valueBaseClass != null && valueProperty != null) {
237
238 final ValidatorFactory validatorFactory = createValidatorFactory(fc);
239 final javax.validation.Validator validator = createValidator(validatorFactory);
240 final BeanDescriptor beanDescriptor = validator.getConstraintsForClass(valueBaseClass);
241 if (beanDescriptor.isBeanConstrained()) {
242 return beanDescriptor.getConstraintsForProperty(valueProperty);
243 }
244 }
245 }
246 }
247
248 return null;
249 }
250
251
252 private javax.validation.Validator createValidator(final ValidatorFactory validatorFactory) {
253 return validatorFactory
254 .usingContext()
255 .messageInterpolator(FacesMessageInterpolatorHolder.get(validatorFactory))
256 .getValidator();
257
258 }
259
260
261 private ValueReferenceWrapper getValueReference(final UIComponent component, final FacesContext context) {
262 final ValueExpression valueExpression = component.getValueExpression("value");
263 final ELContext elCtx = context.getELContext();
264 if (ExternalSpecifications.isUnifiedELAvailable()) {
265 final ValueReference valueReference = getUELValueReference(valueExpression, elCtx);
266 if (valueReference == null) {
267 return null;
268 }
269 return new ValueReferenceWrapper(valueReference.getBase(), valueReference.getProperty());
270 } else {
271 return ValueReferenceResolver.resolve(valueExpression, elCtx);
272 }
273 }
274
275
276 private ValueReference getUELValueReference(final ValueExpression valueExpression, final ELContext elCtx) {
277 final String methodName = "getValueReference";
278 final String methodSignature = valueExpression.getClass().getName() +
279 "." + methodName +
280 "(" + ELContext.class + ")";
281 try {
282 final Method method = valueExpression.getClass().getMethod(methodName, ELContext.class);
283 if (!ValueReference.class.equals(method.getReturnType())
284 && !ValueReference.class.isAssignableFrom(method.getReturnType())) {
285 throw new NoSuchMethodException(
286 methodSignature +
287 "doesn't return " + ValueReference.class +
288 ", but " + method.getReturnType());
289 }
290 return (ValueReference) method.invoke(valueExpression, elCtx);
291 } catch (NoSuchMethodException e) {
292 throw new FacesException(
293 "MyFaces indicates Unified EL is available, but method: " +
294 methodSignature +
295 " is not available", e);
296 } catch (InvocationTargetException e) {
297 throw new FacesException("Exception invoking " + methodSignature, e);
298 } catch (IllegalAccessException e) {
299 throw new FacesException("Exception invoking " + methodSignature, e);
300 }
301 }
302
303
304 private synchronized ValidatorFactory createValidatorFactory(final FacesContext context) {
305 final Object ctx = context.getExternalContext().getContext();
306 if (ctx instanceof ServletContext) {
307 final ServletContext servletCtx = (ServletContext) ctx;
308 final Object attr = servletCtx.getAttribute(VALIDATOR_FACTORY_KEY);
309 if (attr != null) {
310 return (ValidatorFactory) attr;
311 } else {
312 if (ExternalSpecifications.isBeanValidationAvailable()) {
313 final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
314 servletCtx.setAttribute(VALIDATOR_FACTORY_KEY, attr);
315 return factory;
316 } else {
317 throw new FacesException(
318 "Bean Validation (API or implementation) is not present, but required for " +
319 this.getClass().getSimpleName());
320 }
321 }
322 } else {
323 throw new FacesException("Only Servlet environments are supported for " +
324 this.getClass().getSimpleName());
325 }
326 }
327
328
329 public static final String VALIDATOR_FACTORY_KEY = "javax.faces.validator.beanValidator.ValidatorFactory";
330 }
331