MYFACES-4550: throw exception if f:validateWholeBean is misplaced
use findClosest utility method to find closest form
diff --git a/impl/src/main/java/org/apache/myfaces/component/validate/ValidateWholeBeanComponent.java b/impl/src/main/java/org/apache/myfaces/component/validate/ValidateWholeBeanComponent.java
index a5c4733..a69127e 100644
--- a/impl/src/main/java/org/apache/myfaces/component/validate/ValidateWholeBeanComponent.java
+++ b/impl/src/main/java/org/apache/myfaces/component/validate/ValidateWholeBeanComponent.java
@@ -18,13 +18,22 @@
*/
package org.apache.myfaces.component.validate;
+import jakarta.faces.application.ProjectStage;
+import jakarta.faces.component.EditableValueHolder;
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIForm;
import jakarta.faces.component.UIInput;
import jakarta.faces.context.FacesContext;
import jakarta.faces.convert.Converter;
import jakarta.faces.validator.BeanValidator;
import jakarta.faces.validator.Validator;
+
+import java.io.IOException;
+import java.util.List;
+
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
+import org.apache.myfaces.core.api.shared.ComponentUtils;
import org.apache.myfaces.util.WebConfigParamUtils;
/**
@@ -53,6 +62,72 @@ public void addValidator(Validator validator)
// No-op. It does not make sense to allow additional validators to be installed.
}
+
+ @Override
+ public void encodeBegin(FacesContext context) throws IOException
+ {
+ // https://github.com/jakartaee/faces/issues/1780
+ if (context.isProjectStage(ProjectStage.Development))
+ {
+ // find closest form
+ UIForm closestForm = ComponentUtils.findClosest(UIForm.class, this);
+
+ if (closestForm == null)
+ {
+ // Throw an exception just as Mojarra
+ throw new IllegalStateException("f:validateWholeBean must be placed within a form");
+ }
+
+ validateTagPlacement(closestForm, this.getClientId(context));
+ }
+ }
+
+ /*
+ * As required by https://github.com/jakartaee/faces/issues/1
+ * Also ensures all inputs are available for f:wholeBeanValidate processing
+ * (otherwise they'd be empty during the validation)
+ * Inspired by Mojarra's UIValidateWholeBean#misplacedComponentCheck
+ * 1) Get all children of the form component
+ * 2) Loop in reverse thorough each child in the form
+ * 3) If we find an editable component (EditableValueHolder)
+ * and it's group validator matches f:wholeBeanValidate (this part is unique to
+ * myfaces) then throw an exception.
+ * 4) If we find the f:wholeBeanValidate's client id before any
+ * EditableValueHolder tags, return.
+ */
+ public void validateTagPlacement(UIComponent component, String clientId) throws IllegalStateException
+ {
+ List<UIComponent> children = component.getChildren();
+
+ for (int i = children.size() -1; i >=0; i--)
+ {
+ UIComponent c = children.get(i);
+ if (c instanceof EditableValueHolder && !(c instanceof ValidateWholeBeanComponent))
+ {
+ Validator[] validators = ((EditableValueHolder) c).getValidators();
+ for (Validator v : validators)
+ {
+ if (v instanceof BeanValidator
+ && ((BeanValidator) v).getValidationGroups().equals(this.getValidationGroups()))
+ {
+ throw new IllegalStateException("f:validateWholeBean must be placed after all validated inputs");
+ }
+ }
+ }
+ else
+ {
+ if (c.getClientId().equals(clientId))
+ {
+ return; // found f:validateWholeBean before any inputs
+ }
+ else
+ {
+ validateTagPlacement(c, clientId);
+ }
+ }
+ }
+ }
+
@Override
public void validate(FacesContext context)
{