Validation 如何验证primefaces树<;p:tree>;

Validation 如何验证primefaces树<;p:tree>;,validation,jsf-2,primefaces,treeview,Validation,Jsf 2,Primefaces,Treeview,primefaces似乎不是EditableValueHolder,即使它提供了使树可选择的功能。在我看来,这似乎就是EditableValueHolder的定义,因为它既保存值(选定的节点列表),又可编辑(您可以更改选择)。在使树可选择的过程中,它基本上将其转换为selectOneXxx/selectManyXxx。这就是我使用这个小部件的方式。但是,我不是EditableValueHolder,无法直接将验证器附加到它。我可以使用actionListener将验证添加到表单提交操作中,但随后

primefaces
似乎不是EditableValueHolder,即使它提供了使树可选择的功能。在我看来,这似乎就是
EditableValueHolder
的定义,因为它既保存值(选定的节点列表),又可编辑(您可以更改选择)。在使树可选择的过程中,它基本上将其转换为selectOneXxx/selectManyXxx。这就是我使用这个小部件的方式。但是,我不是EditableValueHolder,无法直接将验证器附加到它。我可以使用
actionListener
将验证添加到表单提交操作中,但随后它就脱离了相应的生命周期阶段,在
UITree
组件中检查属性(如验证失败的i18n消息)要困难得多。以前有人处理过这个问题吗?你是做什么的

------编辑----

我在primefaces bug跟踪器中发现了一个问题,似乎与此相关:

和论坛帖子:

------编辑----

这就是我想出的解决办法。有些jQuery非常复杂,因为它使用服务器端el生成客户端javascript。但在大多数情况下,它是有效的。只需找出空数组跳过验证的原因。。。但这是另一个故事

<h:panelGroup id="pnpCois" styleClass="pnp-input-group pnp-cois">
  <h:outputLabel for="inputCois"
    value="#{i18n['communities-of-interest']}" />
  <p:tree id="inputCois"
    value="#{subscriptions.selected.coiTreeRootNode}" var="node"
    selectionMode="checkbox"
    selection="#{subscriptions.selected.selectedCoiNodes}">
    <p:ajax event="select" process="@this :#{component.clientId}_validator" update="@this"
      onstart="$('##{component.clientId}_validator'.replace(':','\\:')).val($('##{component.clientId}_selection'.replace(':','\\:')).val());" />
    <p:ajax event="unselect" process="@this :#{component.clientId}_validator" update="@this"
      onstart="$('##{component.clientId}_validator'.replace(':','\\:')).val($('##{component.clientId}_selection'.replace(':','\\:')).val());" />
    <p:treeNode>
      <h:outputText value="#{node}" />
    </p:treeNode>
  </p:tree>
  <h:inputHidden id="inputCois_validator">
    <f:converter converterId="asias.stringCsvToArray" /> 
    <f:validator validatorId="asias.atLeastOneSelected" />
    <f:attribute name="atLeastOneSelectedMessage"
      value="#{i18n['at-least-one-coi-must-be-selected']}" />
  </h:inputHidden>
</h:panelGroup>

------编辑----


在与BalusC讨论了一些建议之后,我想我将放弃
,并找到另一种方法…:(

您可以使用一个必需的隐藏输入字段来欺骗它,该字段的值在节点单击时被更改。您可以使用
小部件变量的
selections
属性以数组的形式获取可用的选择

例如


...

'ok'
值完全是任意的。关键是隐藏字段已填充,因此不会触发
必需的验证程序。

请相信我,这是一个很长的答案

由于primefaces树不是一个
EditableValueHolder
,因此在JSF生命周期的标准流程验证阶段无法对其进行验证(如果没有一些重大的黑客攻击)。并且尽我所能,我无法修补primefaces树代码,使其成为
EditableValueHolder
(树不会根据选定的值进行渲染,而是根据支持树的节点的状态进行渲染)。考虑到这些约束,唯一的解决方案是创建我自己的树组件(我没有时间)、使用其他组件(树最适合)或在调用应用程序阶段进行验证

我选择了第三种解决方案,并在这样做的过程中,尝试使其尽可能类似于常规验证。主要思想是,使用一个首先被触发的
actionListener
(在任何其他
actionListener
s或主操作之前)(保存表单)处理验证。如果验证失败,我将在自定义属性中向组件添加有关失败的信息,调用
facesContext.validationFailed()
因此,我可以跳过该操作,然后添加一个
preRenderView
系统事件侦听器,以便在渲染响应阶段之前根据组件的验证状态修改组件。这样做的方式允许您仍然使用自定义组件而不是
以相同的方式指定验证。下面是e代码:

web.xml:

...
  <context-param>
    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
    <param-value>/WEB-INF/somenamespace.taglib.xml</param-value>
  </context-param>
...
。。。
javax.faces.FACELETS\u库
/WEB-INF/somenamespace.taglib.xml
...
somenamespace.taglib.xml:

<facelet-taglib version="2.0" xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd">

  <namespace>http://ns.my.com/ui/extensions</namespace>

  <tag>
    <description><![CDATA[
            Add an actionListener validator to a component
        ]]></description>
    <tag-name>actionListenerValidator</tag-name>
    <handler-class>com.my.ns.actionlistenervalidator.ActionListenerValidatorHandler</handler-class>
    <attribute>
      <description><![CDATA[
                The validatorId.
            ]]></description>
      <name>validatorId</name>
      <type>java.lang.String</type>
    </attribute>
    <attribute>
      <description><![CDATA[
                A ValueExpression that evaluates to an instance of Validator.
            ]]></description>
      <name>binding</name>
      <type>javax.el.ValueExpression</type>
    </attribute>
    <attribute>
      <description><![CDATA[
                The styleClass added to the end of the component style class when a validation error occurs
            ]]></description>
      <name>errorStyleClass</name>
      <type>java.lang.String</type>
    </attribute>
  </tag>
</facelet-taglib>

http://ns.my.com/ui/extensions
神经标定器
com.my.ns.actionListenrvalidator.actionListenrvalidatorHandler
验证器ID
java.lang.String
结合
javax.el.ValueExpression
errorStyleClass
java.lang.String
ActionListenerHandler.java:

package com.my.ns.actionlistenervalidator;


import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import javax.faces.component.UIComponent;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagAttributes;
import javax.faces.view.facelets.TagConfig;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import com.sun.faces.facelets.tag.TagHandlerImpl;


public class ActionListenerValidatorHandler extends TagHandlerImpl {
    private static Logger logger = LoggerFactory.getLogger( ActionListenerValidatorHandler.class );

    public static enum AttributeKeys {
        errorStyleClass("hack.jsf.actionlistenervalidator.errorStyleClass"),
        messages("hack.jsf.actionlistenervalidator.messages"),
        valid("hack.jsf.actionlistenervalidator.valid"),
        validators("hack.jsf.actionlistenervalidator.validators");

        private String key;

        private AttributeKeys( String key ) {
            this.key = key;
        }

        public String getKey() {
            return key;
        }
    }

    public ActionListenerValidatorHandler( TagConfig config ) {
        super( config );
    }

    @Override
    public void apply( FaceletContext ctx, UIComponent parent ) throws IOException {
        ActionListenerValidatorWrapper validator = new ActionListenerValidatorWrapper( ctx.getFacesContext(),
                tagAttributesToMap( ctx, this.tag.getAttributes() ) );

        logger.trace( "adding actionListener validator {} to {}", validator, parent );

        @SuppressWarnings( "unchecked" )
        List<ActionListenerValidatorWrapper> validators = (List<ActionListenerValidatorWrapper>) parent.getAttributes().get( AttributeKeys.validators.getKey() );
        if ( validators == null ) {
            validators = new ArrayList<ActionListenerValidatorWrapper>();
            parent.getAttributes().put( AttributeKeys.validators.getKey(), validators );
        }
        validators.add( validator );
    }

    private Map<String, Object> tagAttributesToMap( FaceletContext ctx, TagAttributes tagAttributes ) {
        Map<String, Object> map = new HashMap<String, Object>();
        for ( TagAttribute attribute : tagAttributes.getAll() ) {
            map.put( attribute.getLocalName(), attribute.getValue( ctx ) );
        }
        return map;
    }
}
package com.my.ns.actionListenrvalidator;
导入java.io.IOException;
导入java.util.ArrayList;
导入java.util.HashMap;
导入java.util.List;
导入java.util.Map;
导入javax.faces.component.UIComponent;
导入javax.faces.view.facelets.FaceletContext;
导入javax.faces.view.facelets.TagAttribute;
导入javax.faces.view.facelets.TagAttributes;
导入javax.faces.view.facelets.TagConfig;
导入org.slf4j.Logger;
导入org.slf4j.LoggerFactory;
导入com.sun.faces.facelets.tag.TagHandlerImpl;
公共类ActionListenerValidatorHandler扩展了TagHandlerImpl{
私有静态记录器Logger=LoggerFactory.getLogger(actionListenrvalidatorHandler.class);
公共静态枚举属性{
errorStyleClass(“hack.jsf.actionlistenervalidator.errorStyleClass”),
消息(“hack.jsf.actionlistenervalidator.messages”),
valid(“hack.jsf.actionlistenervalidator.valid”),
验证器(“hack.jsf.actionListenrvalidator.validators”);
私钥;
私有属性键(字符串键){
this.key=key;
}
公共字符串getKey(){
返回键;
}
}
公共操作ListenrvalidatorHandler(TagConfig配置){
超级(配置);
}
@凌驾
public void apply(FaceletContext ctx,UIComponent父级)引发IOException{
ActionListenrvalidatorWrapper validator=新的ActionListenrvalidatorWrapper(ctx.getFacesContext(),
tagAttributesToMap(ctx,this.tag.getAttributes());
trace(“将actionListener验证程序{}添加到{}”,验证程序,父级);
@抑制警告(“未选中”)
List validators=(List)parent.getAttributes().get(AttributeKeys.validators.getKey());
if(验证器==null){
验证器=新的
package com.my.ns.actionlistenervalidator;


import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import javax.faces.component.UIComponent;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagAttributes;
import javax.faces.view.facelets.TagConfig;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import com.sun.faces.facelets.tag.TagHandlerImpl;


public class ActionListenerValidatorHandler extends TagHandlerImpl {
    private static Logger logger = LoggerFactory.getLogger( ActionListenerValidatorHandler.class );

    public static enum AttributeKeys {
        errorStyleClass("hack.jsf.actionlistenervalidator.errorStyleClass"),
        messages("hack.jsf.actionlistenervalidator.messages"),
        valid("hack.jsf.actionlistenervalidator.valid"),
        validators("hack.jsf.actionlistenervalidator.validators");

        private String key;

        private AttributeKeys( String key ) {
            this.key = key;
        }

        public String getKey() {
            return key;
        }
    }

    public ActionListenerValidatorHandler( TagConfig config ) {
        super( config );
    }

    @Override
    public void apply( FaceletContext ctx, UIComponent parent ) throws IOException {
        ActionListenerValidatorWrapper validator = new ActionListenerValidatorWrapper( ctx.getFacesContext(),
                tagAttributesToMap( ctx, this.tag.getAttributes() ) );

        logger.trace( "adding actionListener validator {} to {}", validator, parent );

        @SuppressWarnings( "unchecked" )
        List<ActionListenerValidatorWrapper> validators = (List<ActionListenerValidatorWrapper>) parent.getAttributes().get( AttributeKeys.validators.getKey() );
        if ( validators == null ) {
            validators = new ArrayList<ActionListenerValidatorWrapper>();
            parent.getAttributes().put( AttributeKeys.validators.getKey(), validators );
        }
        validators.add( validator );
    }

    private Map<String, Object> tagAttributesToMap( FaceletContext ctx, TagAttributes tagAttributes ) {
        Map<String, Object> map = new HashMap<String, Object>();
        for ( TagAttribute attribute : tagAttributes.getAll() ) {
            map.put( attribute.getLocalName(), attribute.getValue( ctx ) );
        }
        return map;
    }
}
package com.my.ns.actionlistenervalidator;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;


import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import javax.faces.view.facelets.FaceletException;


import com.sun.faces.el.ELUtils;


public class ActionListenerValidatorWrapper {
    private Validator validator;
    private String errorStyleClass;

    public ActionListenerValidatorWrapper( FacesContext context, Map<String, Object> attributes ) {
        String binding = (String) attributes.get( "binding" );
        String validatorId = (String) attributes.get( "validatorId" );
        if ( binding != null ) {
            ExpressionFactory factory = context.getApplication().getExpressionFactory();
            ELContext elContext = context.getELContext();
            ValueExpression valueExpression = factory.createValueExpression(
                    elContext, binding, String.class );
            this.validator = (Validator) ELUtils.evaluateValueExpression( valueExpression, context.getELContext() );
        }
        else if ( validatorId != null ) {
            this.validator = context.getApplication().createValidator( validatorId );
            this.errorStyleClass = (String) attributes.get( "errorStyleClass" );

            // inject all attributes
            for ( Method method : validator.getClass().getMethods() ) {
                String methodName = method.getName();
                Class<?>[] types = method.getParameterTypes();
                if ( methodName.startsWith( "set" ) && types.length == 1 ) {
                    String property = Character.toLowerCase( methodName.charAt( 3 ) ) + methodName.substring( 4 );
                    if ( attributes.containsKey( property ) ) {
                        // convert value type
                        Object value = null;
                        if ( types[0] == Integer.TYPE ) {
                            value = intValue( context, attributes.get( property ) );
                        }
                        else {
                            value = attributes.get( property );
                        }

                        // call setter
                        try {
                            method.invoke( validator, value );
                        }
                        catch ( IllegalArgumentException e ) {
                            throw new FaceletException( e );
                        }
                        catch ( IllegalAccessException e ) {
                            throw new FaceletException( e );
                        }
                        catch ( InvocationTargetException e ) {
                            throw new FaceletException( e );
                        }
                    }
                }
            }
        }
        else {
            throw new FaceletException( "ActionListenerValidator requires either validatorId or binding" );
        }
    }

    @Override
    public boolean equals( Object otherObj ) {
        if ( !(otherObj instanceof ActionListenerValidatorWrapper) ) {
            return false;
        }
        ActionListenerValidatorWrapper other = (ActionListenerValidatorWrapper) otherObj;
        return (this.getValidator().equals( other.getValidator() ))
                && (this.getErrorStyleClass().equals( other.getErrorStyleClass() ));

    }

    public String getErrorStyleClass() {
        return errorStyleClass;
    }

    public Validator getValidator() {
        return validator;
    }

    @Override
    public int hashCode() {
        int hashCode = (getValidator().hashCode()
                + getErrorStyleClass().hashCode());
        return (hashCode);
    }

    private Integer intValue( FacesContext context, Object value ) {
        ExpressionFactory factory = context.getApplication().getExpressionFactory();
        ELContext elContext = context.getELContext();
        ValueExpression valueExpression = factory.createValueExpression(
                elContext, value.toString(), String.class );
        if ( !valueExpression.isLiteralText() ) {
            return ((Number) ELUtils.evaluateValueExpression( valueExpression, elContext )).intValue();
        }
        else {
            return Integer.valueOf( valueExpression.getExpressionString() );
        }
    }

    @Override
    public String toString() {
        return validator.getClass().getName();
    }

    public void validate( FacesContext context, UIComponent component, Object value ) throws ValidatorException {
        validator.validate( context, component, value );
    }
}
package com.my.ns.actionlistenervalidator;


import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;


import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.validator.ValidatorException;


import com.my.ns.controller.MyBean;
import com.my.ns.actionlistenervalidator.ActionListenerValidatorHandler.AttributeKeys;
import org.primefaces.component.tree.Tree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@ManagedBean
@ViewScoped
public class ActionListenerValidatorManager implements Serializable {
    private static final long serialVersionUID = -696487579396819893L;
    private static Logger logger = LoggerFactory.getLogger( ActionListenerValidatorManager.class );

    @ManagedProperty( "#{myBean}" )
    private MyBean myBean;

    private void addValidationToComponent( Map<String, Object> attributes, Collection<FacesMessage> facesMessages, Set<String> errorStyleClasses ) {
        attributes.put( AttributeKeys.valid.getKey(), false );
        attributes.put( AttributeKeys.messages.getKey(), facesMessages );

        StringBuilder builder = new StringBuilder();
        if ( errorStyleClasses != null ) {
            for ( String styleClass : errorStyleClasses ) {
                builder.append( styleClass );
            }
            attributes.put( AttributeKeys.errorStyleClass.getKey(), builder.toString() );
        }
    }

    public void applyValidationStateToComponentTreeBecausePrimefacesDidntMakeTreeAnEditableValueHolder( ComponentSystemEvent event ) {
        applyValidationStateToComponentTree( FacesContext.getCurrentInstance() );
    }

    private void applyValidationStateToComponentTree( FacesContext context ) {
        UIViewRoot viewRoot = context.getViewRoot();
        logger.trace( "pre render view for {}", viewRoot );

        viewRoot.visitTree( VisitContext.createVisitContext( context ),
                new VisitCallback() {
                    @Override
                    public VisitResult visit( VisitContext context, UIComponent component ) {
                        Map<String, Object> attributes = component.getAttributes();
                        if ( attributes.containsKey( AttributeKeys.valid.getKey() ) &&
                                !((Boolean) attributes.get( AttributeKeys.valid.getKey() )) ) {
                            // validation state
                            if ( component instanceof EditableValueHolder ) {
                                ((EditableValueHolder) component).setValid( false );
                            }

                            // validation messages
                            FacesContext facesContext = context.getFacesContext();
                            @SuppressWarnings( "unchecked" )
                            List<FacesMessage> messages = (List<FacesMessage>) attributes.get( AttributeKeys.messages.getKey() );
                            if ( messages != null ) {
                                for ( FacesMessage message : messages ) {
                                    facesContext.addMessage( component.getClientId(), message );
                                }
                            }

                            // style class
                            String errorStyleClass = (String) attributes.get( AttributeKeys.errorStyleClass.getKey() );
                            if ( errorStyleClass != null ) {
                                String styleClass = (String) attributes.get( "styleClass" );
                                styleClass = styleClass == null ? errorStyleClass : styleClass + " " + errorStyleClass;
                                attributes.put( "styleClass", styleClass );
                            }
                        }
                        return VisitResult.ACCEPT;
                    }
                } );
    }

    private void clearValidationFromTree( FacesContext context, UIComponent component ) {
        component.visitTree( VisitContext.createVisitContext( context ),
                new VisitCallback() {
                    @Override
                    public VisitResult visit( VisitContext context, UIComponent target ) {
                        clearValidationFromComponent( target.getAttributes() );
                        return VisitResult.ACCEPT;
                    }
                } );
    }

    private void clearValidationFromComponent( Map<String, Object> attributes ) {
        if ( attributes.containsKey( AttributeKeys.validators.getKey() ) ) {
            String errorStyleClass = (String) attributes.get( AttributeKeys.errorStyleClass.getKey() );
            if ( errorStyleClass != null ) {
                String styleClass = (String) attributes.get( "styleClass" );
                styleClass = styleClass.replace( errorStyleClass, "" );
                attributes.put( "styleClass", styleClass );
            }

            attributes.remove( AttributeKeys.valid.getKey() );
            attributes.remove( AttributeKeys.messages.getKey() );
            attributes.remove( AttributeKeys.errorStyleClass.getKey() );
        }
    }

    private Object getValue( FacesContext facesContext, UIComponent component ) {
        Object value = null;
        if ( component instanceof EditableValueHolder ) {
            value = ((EditableValueHolder) component).getValue();
        }
        else if ( component instanceof Tree ) {
            value = myBean.getSelectedIds();
        }

        return value;
    }

    public void setMyBean( MyBean myBean ) {
        this.myBean = myBean;
    }

    private void validate( FacesContext context ) {
        logger.trace( "entering validation" );
        final List<String> validationFailed = new ArrayList<String>();

        UIViewRoot viewRoot = context.getViewRoot();
        viewRoot.visitTree( VisitContext.createVisitContext( context ),
                new VisitCallback() {
                    @Override
                    public VisitResult visit( VisitContext context, UIComponent component ) {
                        if ( !component.isRendered() ) {
                            // remove all validation from subtree as validation
                            // is not performed unless the component is
                            // rendered.
                            clearValidationFromTree( context.getFacesContext(), component );
                            return VisitResult.REJECT;
                        }

                        Map<String, Object> attributes = component.getAttributes();
                        if ( attributes.containsKey( AttributeKeys.validators.getKey() ) ) {
                            Object value = getValue( context.getFacesContext(), component );

                            boolean valid = true;
                            Collection<FacesMessage> facesMessages = null;
                            Set<String> errorStyleClasses = null;

                            @SuppressWarnings( "unchecked" )
                            List<ActionListenerValidatorWrapper> validators =
                                    (List<ActionListenerValidatorWrapper>) attributes.get( AttributeKeys.validators.getKey() );
                            for ( ActionListenerValidatorWrapper validator : validators ) {
                                try {
                                    validator.validate( context.getFacesContext(), component, value );
                                }
                                catch ( ValidatorException validatorException ) {
                                    valid = false;
                                    Collection<FacesMessage> innerMessages = validatorException.getFacesMessages();
                                    if ( innerMessages == null ) {
                                        FacesMessage innerMessage = validatorException.getFacesMessage();
                                        if ( innerMessage != null ) {
                                            innerMessages = Arrays.asList( new FacesMessage[] { innerMessage } );
                                        }
                                    }

                                    if ( facesMessages == null ) {
                                        facesMessages = new ArrayList<FacesMessage>();
                                    }

                                    facesMessages.addAll( innerMessages );

                                    String errorStyleClass = validator.getErrorStyleClass();
                                    if ( errorStyleClass != null ) {
                                        if ( errorStyleClasses == null ) {
                                            errorStyleClasses = new TreeSet<String>();
                                        }
                                        errorStyleClasses.add( errorStyleClass );
                                    }
                                }

                            }

                            if ( valid ) {
                                // remove previous validation state
                                clearValidationFromComponent( attributes );
                            }
                            else {
                                // add validation state
                                addValidationToComponent( attributes, facesMessages, errorStyleClasses );
                                validationFailed.add( "Yes, it did, but cant update final boolean so we use a list" );
                            }
                        }

                        return VisitResult.ACCEPT;
                    }
                } );

        if ( validationFailed.size() > 0 ) {
            context.validationFailed();
        }
    }

    public void validateThisFormBecausePrimefacesDidntMakeTreeAnEditableValueHolder( ActionEvent event ) {
        validate( FacesContext.getCurrentInstance() );
    }
}
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"
  xmlns:fn="http://java.sun.com/jsp/jstl/functions"
  xmlns:myns="http://ns.my.com/ui/extensions">
<h:head />
<h:body>
  <f:event type="preRenderView"
    listener="#{actionListenerValidatorManager.applyValidationStateToComponentTreeBecausePrimefacesDidntMakeTreeAnEditableValueHolder}" />

...

            <h:panelGroup id="treeGroup">
              <h:outputLabel for="treeInput"
                value="#{i18n['my-tree']}" />
              <p:tree id="treeInput"
                value="#{myBean.treeRootNode}" var="node"
                selectionMode="checkbox"
                selection="#{myBean.selectedNodes}">
                <pe:actionListenerValidator
                  validatorId="javax.faces.Required"
                  errorStyleClass="ui-state-error" />
                <p:treeNode>
                  <h:outputText value="#{node}" />
                </p:treeNode>
              </p:tree>
            </h:panelGroup>

...

</h:body>
</html>