/
ValidateBean.java
624 lines (536 loc) · 24.4 KB
/
ValidateBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
/*
* Copyright 2017 OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.taghandler;
import static java.util.logging.Level.SEVERE;
import static javax.faces.component.visit.VisitHint.SKIP_UNRENDERED;
import static javax.faces.event.PhaseId.PROCESS_VALIDATIONS;
import static javax.faces.event.PhaseId.RESTORE_VIEW;
import static javax.faces.event.PhaseId.UPDATE_MODEL_VALUES;
import static javax.faces.view.facelets.ComponentHandler.isNew;
import static org.omnifaces.el.ExpressionInspector.getValueReference;
import static org.omnifaces.util.Components.forEachComponent;
import static org.omnifaces.util.Components.getClosestParent;
import static org.omnifaces.util.Components.getCurrentForm;
import static org.omnifaces.util.Components.getLabel;
import static org.omnifaces.util.Components.hasInvokedSubmit;
import static org.omnifaces.util.Events.subscribeToRequestAfterPhase;
import static org.omnifaces.util.Events.subscribeToRequestBeforePhase;
import static org.omnifaces.util.Events.subscribeToViewEvent;
import static org.omnifaces.util.Facelets.getBoolean;
import static org.omnifaces.util.Facelets.getString;
import static org.omnifaces.util.Facelets.getValueExpression;
import static org.omnifaces.util.Faces.getELContext;
import static org.omnifaces.util.Faces.renderResponse;
import static org.omnifaces.util.Faces.validationFailed;
import static org.omnifaces.util.FacesLocal.evaluateExpressionGet;
import static org.omnifaces.util.Messages.addError;
import static org.omnifaces.util.Messages.addGlobalError;
import static org.omnifaces.util.Platform.getBeanValidator;
import static org.omnifaces.util.Reflection.instance;
import static org.omnifaces.util.Reflection.setProperties;
import static org.omnifaces.util.Reflection.toClass;
import static org.omnifaces.util.Utils.coalesce;
import static org.omnifaces.util.Utils.csvToList;
import static org.omnifaces.util.Utils.isEmpty;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.el.ValueExpression;
import javax.el.ValueReference;
import javax.faces.FacesException;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.PostValidateEvent;
import javax.faces.event.PreValidateEvent;
import javax.faces.event.SystemEventListener;
import javax.faces.validator.Validator;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagConfig;
import javax.faces.view.facelets.TagHandler;
import javax.validation.ConstraintViolation;
import org.omnifaces.eventlistener.BeanValidationEventListener;
import org.omnifaces.util.Callback;
import org.omnifaces.util.copier.CloneCopier;
import org.omnifaces.util.copier.Copier;
import org.omnifaces.util.copier.CopyCtorCopier;
import org.omnifaces.util.copier.MultiStrategyCopier;
import org.omnifaces.util.copier.NewInstanceCopier;
import org.omnifaces.util.copier.SerializationCopier;
/**
* <p>
* The <code><o:validateBean></code> allows the developer to control bean validation on a per-{@link UICommand}
* or {@link UIInput} component basis, as well as validating a given bean at the class level.
*
* <p>
* The standard <code><f:validateBean></code> only allows validation control on a per-form
* or a per-request basis (by using multiple tags and conditional EL expressions in its attributes) which may end up in
* boilerplate code.
*
* <p>
* The standard <code><f:validateBean></code> also, despite its name, does not actually have any facilities to
* validate a bean at all.
*
* <h3>Usage</h3>
* <p>
* Some examples
*
* <p>
* <b>Control bean validation per component</b>
* <pre>
* <h:commandButton value="submit" action="#{bean.submit}">
* <o:validateBean validationGroups="javax.validation.groups.Default,com.example.MyGroup" />
* </h:commandButton>
* </pre>
* <pre>
* <h:selectOneMenu value="#{bean.selectedItem}">
* <f:selectItems value="#{bean.availableItems}" />
* <o:validateBean disabled="true" />
* <f:ajax execute="@form" listener="#{bean.itemChanged}" render="@form" />
* </h:selectOneMenu>
* </pre>
*
* <p>
* <b>Validate a bean at the class level</b>
* <pre>
* <h:inputText value="#{bean.product.item}" />
* <h:inputText value="#{bean.product.order}" />
*
* <o:validateBean value="#{bean.product}" validationGroups="com.example.MyGroup" />
* </pre>
*
* <h3>Class level validation details</h3>
* <p>
* In order to validate a bean at the class level, all values from input components should first be actually set on that bean
* and only thereafter should the bean be validated. This however does not play well with the JSF approach where a model
* is only updated when validation passes. But for class level validation we seemingly can not validate until the model
* is updated. To break this tie, a <em>copy</em> of the model bean is made first, and then values are stored in this copy
* and validated there. If validation passes, the original bean is updated.
*
* <p>
* A bean is copied using the following strategies (in the order indicated):
* <ol>
* <li> <b>Cloning</b> - Bean must implement the {@link Cloneable} interface and support cloning according to the rules of that interface. See {@link CloneCopier}
* <li> <b>Serialization</b> - Bean must implement the {@link Serializable} interface and support serialization according to the rules of that interface. See {@link SerializationCopier}
* <li> <b>Copy constructor</b> - Bean must have an additional constructor (next to the default constructor) taking a single argument of its own
* type that initializes itself with the values of that passed in type. See {@link CopyCtorCopier}
* <li> <b>New instance</b> - Bean should have a public no arguments (default) constructor. Every official JavaBean satisfies this requirement. Note
* that in this case no copy is made of the original bean, but just a new instance is created. See {@link NewInstanceCopier}
* </ol>
*
* <p>
* If the above order is not ideal, or if an custom copy strategy is needed (e.g. when it's only needed to copy a few fields for the validation)
* a strategy can be supplied explicitly via the <code>copier</code> attribute. The value of this attribute can be any of the build-in copier implementations
* given above, or can be a custom implementation of the {@link Copier} interface.
* <p>
* If the copying strategy is not possible due to technical limitations, then you could set <code>method</code>
* attribute to <code>"validateActual"</code>.
* <pre>
* <o:validateBean value="#{bean.product}" validationGroups="com.example.MyGroup" method="validateActual" />
* </pre>
* <p>
* This will update the model values and run the validation after update model values phase instead of the validations
* phase. The disadvantage is that the invalid values remain in the model and that the action method is anyway invoked.
* You would need an additional check for {@link FacesContext#isValidationFailed()} in the action method to see if it
* has failed or not.
*
* <h3>Faces messages</h3>
* <p>
* By default, the faces message is added with client ID of the parent {@link UIForm}.
* <pre>
* <h:form id="formId">
* ...
* <h:message for="formId" />
* <o:validateBean ... />
* </h:form>
* </pre>
* <p>
* The faces message can also be shown for all invalidated components using <code>showMessageFor="@all"</code>.
* <pre>
* <h:form>
* <h:inputText id="foo" />
* <h:message for="foo" />
* <h:inputText id="bar" />
* <h:message for="bar" />
* ...
* <o:validateBean ... showMessageFor="@all" />
* </h:form>
* </pre>
* <p>
* The faces message can also be shown as global message using <code>showMessageFor="@global"</code>.
* <pre>
* <h:form>
* ...
* <o:validateBean ... showMessageFor="@global" />
* </h:form>
* <h:messages globalOnly="true" />
* </pre>
* <p>
* The faces message can also be shown for specific components referenced by a space separated collection of their
* client IDs in <code>showMessageFor</code> attribute.
* <pre>
* <h:form>
* <h:inputText id="foo" />
* <h:message for="foo" />
* <h:inputText id="bar" />
* <h:message for="bar" />
* ...
* <o:validateBean ... showMessageFor="foo bar" />
* </h:form>
* </pre>
* <p>
* The faces message can also be shown for components which match {@link ConstraintViolation#getPropertyPath() Property
* Path of the ConstraintViolation} using <code>showMessageFor="@violating"</code>, and when no matching component can
* be found, the message will fallback to being added with client ID of the parent {@link UIForm}.
* <pre>
* <h:form id="formId">
* ...
* <!-- Unmatched messages shown here: -->
* <h:message for="formId" />
* ...
* <h:inputText id="foo" value="#{bean.product.item}" />
*
* <!-- Messages where ConstraintViolation PropertyPath is "item" are shown here: -->
* <h:message for="foo" />
* ...
* <o:validateBean ... value="#{bean.product}" showMessageFor="@violating" />
* </h:form>
* </pre>
* <p>
* The <code>showMessageFor</code> attribute is new since OmniFaces 2.6 and it defaults to <code>@form</code>. The
* <code>showMessageFor</code> attribute does by design not have any effect when <code>validateMethod="actual"</code>
* is used.
*
* @author Bauke Scholtz
* @author Arjan Tijms
* @see BeanValidationEventListener
*/
public class ValidateBean extends TagHandler {
// Constants ------------------------------------------------------------------------------------------------------
private static final Logger logger = Logger.getLogger(ValidateBean.class.getName());
private static final String DEFAULT_SHOWMESSAGEFOR = "@form";
private static final String VALUE_ATTRIBUTE = "value";
private static final String ERROR_MISSING_FORM =
"o:validateBean must be nested in an UIForm.";
private static final String ERROR_INVALID_PARENT =
"o:validateBean parent must be an instance of UIInput or UICommand.";
// Enums ----------------------------------------------------------------------------------------------------------
private enum ValidateMethod {
validateCopy, validateActual;
public static ValidateMethod of(String name) {
if (isEmpty(name)) {
return validateCopy;
}
return valueOf(name);
}
}
// Variables ------------------------------------------------------------------------------------------------------
private ValueExpression value;
private boolean disabled;
private ValidateMethod method;
private String groups;
private String copier;
private String showMessageFor;
// Constructors ---------------------------------------------------------------------------------------------------
/**
* The tag constructor.
* @param config The tag config.
*/
public ValidateBean(TagConfig config) {
super(config);
}
// Actions --------------------------------------------------------------------------------------------------------
/**
* If the parent component has the <code>value</code> attribute or is an instance of {@link UICommand} or
* {@link UIInput} and is new and we're in the restore view phase of a postback, then delegate to
* {@link #processValidateBean(FacesContext, UIComponent)}.
* @throws IllegalArgumentException When the <code>value</code> attribute is absent and the parent component is not
* an instance of {@link UICommand} or {@link UIInput}.
*/
@Override
public void apply(FaceletContext context, final UIComponent parent) throws IOException {
if (getAttribute(VALUE_ATTRIBUTE) == null && (!(parent instanceof UICommand || parent instanceof UIInput))) {
throw new IllegalArgumentException(ERROR_INVALID_PARENT);
}
final FacesContext facesContext = context.getFacesContext();
if (!(isNew(parent) && facesContext.isPostback() && facesContext.getCurrentPhaseId() == RESTORE_VIEW)) {
return;
}
value = getValueExpression(context, getAttribute(VALUE_ATTRIBUTE), Object.class);
disabled = getBoolean(context, getAttribute("disabled"));
method = ValidateMethod.of(getString(context, getAttribute("method")));
groups = getString(context, getAttribute("validationGroups"));
copier = getString(context, getAttribute("copier"));
showMessageFor = coalesce(getString(context, getAttribute("showMessageFor")), DEFAULT_SHOWMESSAGEFOR);
// We can't use getCurrentForm() or hasInvokedSubmit() before the component is added to view, because the client ID isn't available.
// Hence, we subscribe this check to after phase of restore view.
subscribeToRequestAfterPhase(RESTORE_VIEW, new Callback.Void() { @Override public void invoke() {
processValidateBean(facesContext, parent);
}});
}
/**
* Check if the given component has participated in submitting the current form or action and if so, then perform
* the bean validation depending on the attributes set.
* @param context The involved faces context.
* @param component The involved component.
* @throws IllegalStateException When the parent form is missing.
*/
protected void processValidateBean(FacesContext context, UIComponent component) {
UIForm form = (component instanceof UIForm) ? ((UIForm) component) : getClosestParent(component, UIForm.class);
if (form == null) {
throw new IllegalStateException(ERROR_MISSING_FORM);
}
if (!form.equals(getCurrentForm()) || (!(component instanceof UIForm) && !hasInvokedSubmit(component))) {
return;
}
Object bean = null;
if (value != null) {
final Object[] found = new Object[1];
forEachComponent(context).fromRoot(form).invoke(new Callback.WithArgument<UIComponent>() { @Override public void invoke(UIComponent target) {
found[0] = value.getValue(getELContext());
}});
bean = found[0];
}
if (bean == null) {
validateForm();
return;
}
if (!disabled) {
if (method == ValidateMethod.validateActual) {
validateActualBean(form, bean);
}
else {
validateCopiedBean(form, bean);
}
}
}
/**
* After update model values phase, validate actual bean. But don't proceed to render response on fail.
*/
private void validateActualBean(final UIForm form, final Object bean) {
ValidateBeanCallback validateActualBean = new ValidateBeanCallback() { @Override public void run() {
FacesContext context = FacesContext.getCurrentInstance();
validate(context, form, bean, bean, new HashSet<String>(0), false);
}};
subscribeToRequestAfterPhase(UPDATE_MODEL_VALUES, validateActualBean);
}
/**
* Before validations phase of current request, collect all bean properties.
*
* After validations phase of current request, create a copy of the bean, set all collected properties there,
* then validate copied bean and proceed to render response on fail.
*/
private void validateCopiedBean(final UIForm form, final Object bean) {
final Set<String> clientIds = new HashSet<>();
final Map<String, Object> properties = new HashMap<>();
ValidateBeanCallback collectBeanProperties = new ValidateBeanCallback() { @Override public void run() {
FacesContext context = FacesContext.getCurrentInstance();
forEachInputWithMatchingBase(context, form, bean, new Callback.WithArgument<UIInput>() { @Override public void invoke(UIInput input) {
addCollectingValidator(input, clientIds, properties);
}});
}};
ValidateBeanCallback checkConstraints = new ValidateBeanCallback() { @Override public void run() {
FacesContext context = FacesContext.getCurrentInstance();
forEachInputWithMatchingBase(context, form, bean, new Callback.WithArgument<UIInput>() { @Override public void invoke(UIInput input) {
removeCollectingValidator(input);
}});
Object copiedBean = getCopier(context, copier).copy(bean);
setProperties(copiedBean, properties);
validate(context, form, bean, copiedBean, clientIds, true);
}};
subscribeToRequestBeforePhase(PROCESS_VALIDATIONS, collectBeanProperties);
subscribeToRequestAfterPhase(PROCESS_VALIDATIONS, checkConstraints);
}
/**
* Before validations phase of current request, subscribe the {@link BeanValidationEventListener} to validate the form based on groups.
*/
private void validateForm() {
ValidateBeanCallback validateForm = new ValidateBeanCallback() { @Override public void run() {
SystemEventListener listener = new BeanValidationEventListener(groups, disabled);
subscribeToViewEvent(PreValidateEvent.class, listener);
subscribeToViewEvent(PostValidateEvent.class, listener);
}};
subscribeToRequestBeforePhase(PROCESS_VALIDATIONS, validateForm);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void validate(FacesContext context, UIForm form, Object actualBean, Object validableBean, Set<String> clientIds, boolean renderResponseOnFail) {
List<Class> groupClasses = new ArrayList<>();
for (String group : csvToList(groups)) {
groupClasses.add(toClass(group));
}
Set violationsRaw = getBeanValidator().validate(validableBean, groupClasses.toArray(new Class[groupClasses.size()]));
Set<ConstraintViolation<?>> violations = violationsRaw;
if (!violations.isEmpty()) {
context.validationFailed();
String showMessagesFor = showMessageFor;
if ("@violating".equals(showMessageFor)) {
violations = invalidateInputsByPropertyPathAndShowMessages(context, form, actualBean, violations, clientIds);
showMessagesFor = DEFAULT_SHOWMESSAGEFOR;
}
if (!violations.isEmpty()) {
String labels = invalidateInputsByClientIdsAndCollectLabels(context, form, clientIds);
showMessages(context, form, violations, clientIds, labels, showMessagesFor);
}
if (renderResponseOnFail) {
context.renderResponse();
}
}
}
// Helpers --------------------------------------------------------------------------------------------------------
private static void forEachInputWithMatchingBase(final FacesContext context, UIComponent form, final Object base, final String property, final Callback.WithArgument<UIInput> callback) {
forEachComponent(context)
.fromRoot(form)
.ofTypes(UIInput.class)
.withHints(SKIP_UNRENDERED/*, SKIP_ITERATION*/) // SKIP_ITERATION fails in Apache EL (Tomcat 8.0.32 tested) but works in Oracle EL.
.invoke(new Callback.WithArgument<UIInput>() { @Override public void invoke(UIInput input) {
ValueExpression valueExpression = input.getValueExpression(VALUE_ATTRIBUTE);
if (valueExpression != null) {
ValueReference valueReference = getValueReference(context.getELContext(), valueExpression);
if (valueReference.getBase().equals(base) && (property == null || property.equals(valueReference.getProperty()))) {
callback.invoke(input);
}
}
}});
}
private static void forEachInputWithMatchingBase(final FacesContext context, UIComponent form, final Object base, final Callback.WithArgument<UIInput> callback) {
forEachInputWithMatchingBase(context, form, base, null, callback);
}
private static void addCollectingValidator(UIInput input, Set<String> clientIds, Map<String, Object> properties) {
input.addValidator(new CollectingValidator(clientIds, properties));
}
private static void removeCollectingValidator(UIInput input) {
Validator collectingValidator = null;
for (Validator validator : input.getValidators()) {
if (validator instanceof CollectingValidator) {
collectingValidator = validator;
break;
}
}
if (collectingValidator != null) {
input.removeValidator(collectingValidator);
}
}
private static Copier getCopier(FacesContext context, String copierName) {
Copier copier = null;
if (!isEmpty(copierName)) {
Object expressionResult = evaluateExpressionGet(context, copierName);
if (expressionResult instanceof Copier) {
copier = (Copier) expressionResult;
}
else if (expressionResult instanceof String) {
copier = instance((String) expressionResult);
}
}
if (copier == null) {
copier = new MultiStrategyCopier();
}
return copier;
}
private static Set<ConstraintViolation<?>> invalidateInputsByPropertyPathAndShowMessages(final FacesContext context, UIForm form, Object bean, Set<ConstraintViolation<?>> violations, final Set<String> clientIds) {
final Set<ConstraintViolation<?>> remainingViolations = new LinkedHashSet<>(violations);
for (final ConstraintViolation<?> violation : violations) {
forEachInputWithMatchingBase(context, form, bean, violation.getPropertyPath().toString(), new Callback.WithArgument<UIInput>() { @Override public void invoke(UIInput input) {
input.setValid(false);
String clientId = input.getClientId(context);
addError(clientId, violation.getMessage(), getLabel(input));
clientIds.remove(clientId);
remainingViolations.remove(violation);
}});
}
return remainingViolations;
}
private static String invalidateInputsByClientIdsAndCollectLabels(final FacesContext context, UIForm form, Set<String> clientIds) {
final StringBuilder labels = new StringBuilder();
if (!clientIds.isEmpty()) {
forEachComponent(context).fromRoot(form).havingIds(clientIds).invoke(new Callback.WithArgument<UIInput>() { @Override public void invoke(UIInput input) {
input.setValid(false);
if (labels.length() > 0) {
labels.append(", ");
}
labels.append(getLabel(input));
}});
}
return labels.toString();
}
private static void showMessages(FacesContext context, UIForm form, Set<ConstraintViolation<?>> violations, Set<String> clientIds, String labels, String showMessagesFor) {
if ("@form".equals(showMessagesFor)) {
String formId = form.getClientId(context);
addErrors(formId, violations, labels);
}
else if ("@all".equals(showMessagesFor)) {
for (String clientId : clientIds) {
addErrors(clientId, violations, labels);
}
}
else if ("@global".equals(showMessagesFor)) {
for (ConstraintViolation<?> violation : violations) {
addGlobalError(violation.getMessage(), labels);
}
}
else {
for (String clientId : showMessagesFor.split("\\s+")) {
addErrors(clientId, violations, labels);
}
}
}
private static void addErrors(String clientId, Set<ConstraintViolation<?>> violations, String labels) {
for (ConstraintViolation<?> violation : violations) {
addError(clientId, violation.getMessage(), labels);
}
}
// Nested classes -------------------------------------------------------------------------------------------------
public static final class CollectingValidator implements Validator {
private final Set<String> clientIds;
private final Map<String, Object> properties;
public CollectingValidator(Set<String> clientIds, Map<String, Object> properties) {
this.clientIds = clientIds;
this.properties = properties;
}
@Override
public void validate(FacesContext context, UIComponent component, Object value) {
ValueExpression valueExpression = component.getValueExpression(VALUE_ATTRIBUTE);
if (valueExpression != null) {
ValueReference valueReference = getValueReference(context.getELContext(), valueExpression);
clientIds.add(component.getClientId(context));
properties.put(valueReference.getProperty().toString(), value);
}
}
}
// Callbacks ------------------------------------------------------------------------------------------------------
private abstract static class ValidateBeanCallback implements Callback.Void {
@Override
public void invoke() {
try {
run();
}
catch (Exception e) {
// Explicitly log since exceptions in PhaseListeners will be largely swallowed and ignored by JSF runtime.
logger.log(SEVERE, "Exception occured while doing validation.", e);
// Set validation failed and proceed to render response.
validationFailed();
renderResponse();
throw new FacesException(e); // Rethrow, but JSF runtime will do little with it.
}
}
public abstract void run();
}
}