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)
     {