首页 文章

通过postValidate进行JSF交叉字段验证,而无需在支持bean中按名称查找组件

提问于
浏览
2

我正在构建一个登录表单复合组件 . 使用它的页面将传递一个将验证用户名和密码的事件处理程序 . 通常(不使用复合组件)当我们通过 postValidate 执行交叉字段验证时,事件处理程序必须按名称查找字段的组件 . 验证器最好不要这样做,因为这些是应该抽象的组件的内部细节 .

知道如何在不了解复合组件的内部细节的情况下,在 postValidate 处理程序中获取用户名和密码字段的转换值吗?

Update: 这一点不是为了避免按名称查找组件,而是为了能够跨域验证复合组件's fields in a way that doesn' t要求使用页面和/或bean来了解组件的内部细节 .

3 回答

  • 3

    这可以做到 . 在以下代码中,请特别注意复合组件中的 postValidate 事件和后备组件中的 postValidate 方法 . 请注意它如何解析 MethodExpression 属性并调用传入方法 .

    这是复合组件:

    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:cc="http://java.sun.com/jsf/composite"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:p="http://primefaces.org/ui">
    
        <!-- Login form. -->
        <cc:interface componentType="com.example.LoginForm">
            <cc:attribute name="emailAddress" type="java.lang.String" required="true"/>
            <cc:attribute name="rememberMe" type="java.lang.Boolean" required="true"/>
            <cc:attribute name="checkCredentials"
                          method-signature="void checkCredentials(java.lang.String,java.lang.String,java.lang.String)"
                          shortDescription="Parameters are clientId, username and password. If credentials are invalid, attach a FacesMessage to the component specified by clientId."
                          required="true"/>
            <cc:attribute name="actionListener" method-signature="void actionListener()" required="true"/>
            <cc:attribute name="registerOutcome" type="java.lang.String" required="true"/>
            <cc:attribute name="recoverPasswordOutcome" type="java.lang.String" required="true"/>
            <cc:attribute name="headerTitle" type="java.lang.String" default="Sign In"/>
            <cc:attribute name="emailAddressLabel" type="java.lang.String" default="Email address:"/>
            <cc:attribute name="passwordLabel" type="java.lang.String" default="Password:"/>
            <cc:attribute name="rememberMeLabel" type="java.lang.String" default="Stay signed in on this machine"/>
            <cc:attribute name="loginLabel" type="java.lang.String" default="Sign In"/>
            <cc:attribute name="recoverPasswordLabel" type="java.lang.String" default="Forgot password?"/>
            <cc:attribute name="emailAddressRequiredMessage" type="java.lang.String" default="Email address required"/>
            <cc:attribute name="passwordRequiredMessage" type="java.lang.String" default="Password required"/>
            <cc:attribute name="registerLabel" type="java.lang.String" default="Register"/>
        </cc:interface>
    
        <cc:implementation>
            <h:outputStylesheet library="components/example/login-form" name="style.css"/>
    
            <div id="#{cc.clientId}">
                <h:form id="form">
    
                    <f:event type="postValidate" listener="#{cc.postValidate}"/>
    
                    <div style="margin-top:10px;">
                        <p:panel header="#{cc.attrs.headerTitle}" styleClass="loginPanel">
                            <div class="login-form_errorContainer">
                                <p:messages rendered="#{facesContext.maximumSeverity.ordinal ge 2}"/>
                            </div>
                            <h:panelGrid columns="3">
                                <h:outputText styleClass="login-form_label" value="#{cc.attrs.emailAddressLabel}"/>
                                <h:panelGroup styleClass="login-form_cell">
                                    <h:inputText id="emailAddress"
                                                 value="#{cc.attrs.emailAddress}"
                                                 required="true"
                                                 requiredMessage="#{cc.attrs.emailAddressRequiredMessage}"
                                                 styleClass="login-form_field"
                                                 immediate="true"/>
                                </h:panelGroup>
                                <h:panelGroup/>
    
                                <h:outputText styleClass="login-form_label" value="#{cc.attrs.passwordLabel}"/>
                                <h:panelGroup styleClass="login-form_cell">
                                    <h:inputSecret id="password"
                                                   value="#{cc.attrs.password}"
                                                   required="true"
                                                   requiredMessage="#{cc.attrs.passwordRequiredMessage}"
                                                   styleClass="login-form_field"
                                                   immediate="true"/>
                                </h:panelGroup>
                                <h:link styleClass="login-form_link" value="#{cc.attrs.recoverPasswordLabel}" outcome="#{cc.attrs.recoverPasswordOutcome}"/>
    
                                <h:panelGroup/>
                                <p:selectBooleanCheckbox value="#{cc.attrs.rememberMe}" itemLabel="#{cc.attrs.rememberMeLabel}" immediate="true"/>
                                <h:panelGroup/>
    
                                <h:panelGroup/>
                                <h:panelGroup>
                                    <p:commandButton id="submitForm" value="#{cc.attrs.loginLabel}" actionListener="#{cc.attrs.actionListener}" update="form"/>
                                    <span class="login-form_or">or</span>
                                    <h:link styleClass="login-form_link" value="#{cc.attrs.registerLabel}" outcome="#{cc.attrs.registerOutcome}"/>
                                </h:panelGroup>
                                <h:panelGroup/>
                            </h:panelGrid>
                        </p:panel>
                    </div>
                </h:form>
            </div>
        </cc:implementation>
    </html>
    

    支持组件:

    @FacesComponent("com.example.LoginForm")
    public class LoginFormComponent extends UIInput implements NamingContainer
    {
        @Override
        protected Object getConvertedValue(FacesContext context, Object newSubmittedValue) throws ConverterException
        {
            UIInput emailAddressComponent = (UIInput) findComponent(EMAIL_ADDRESS_ID);
            UIInput passwordComponent = (UIInput) findComponent(PASSWORD_ID);
            String emailAddress = (String) emailAddressComponent.getValue();
            String password = (String) passwordComponent.getValue();
            return new LoginFormValue(emailAddress, password);
        }
    
        public void postValidate(ComponentSystemEvent e) {
            FacesContext ctx = getFacesContext();
    
            // Don't validate credentials if the username and/or password fields are invalid.
            if (!ctx.getMessageList(EMAIL_ADDRESS_ID).isEmpty() || !ctx.getMessageList(PASSWORD_ID).isEmpty())
            {
                return;
            }
    
            LoginFormValue value = (LoginFormValue) getConvertedValue(null, null);
            MethodExpression checkCredentials = (MethodExpression) getAttributes().get(CHECK_CREDENTIALS_ATTRIBUTE_NAME);
            checkCredentials.invoke(ctx.getELContext(), new Object[]{getClientId(), value.getEmailAddress(), value.getPassword()});
        }
    
        @Override
        public String getFamily()
        {
            return "javax.faces.NamingContainer";
        }
    
        public static final String CHECK_CREDENTIALS_ATTRIBUTE_NAME = "checkCredentials";
        public static final String EMAIL_ADDRESS_ID = "form:emailAddress";
        public static final String PASSWORD_ID = "form:password";
    }
    

    完整性的 LoginFormValue 类:

    public class LoginFormValue
    {
        public LoginFormValue(String emailAddress, String password)
        {
            this.emailAddress = emailAddress;
            this.password = password;
        }
    
        public String getEmailAddress()
        {
            return emailAddress;
        }
    
        public String getPassword()
        {
            return password;
        }
    
        private String emailAddress;
        private String password;
    }
    

    使用登录表单的页面:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:p="http://primefaces.org/ui"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:ex="http://java.sun.com/jsf/composite/components/example">
        <h:head>
            <title></title>
        </h:head>
        <h:body>
            <ui:composition template="/WEB-INF/templates/myLayout.xhtml">
                <ui:define name="windowTitle">Sign In</ui:define>
                <ui:define name="body">
    
                    <ex:login-form emailAddress="#{loginBean.emailAddress}"
                                   rememberMe="#{loginBean.rememberMe}"
                                   checkCredentials="#{loginBean.checkCredentials}"
                                   actionListener="#{loginBean.submit()}"
                                   recoverPasswordOutcome="recover-password"
                                   registerOutcome="signup"/>
    
                </ui:define>
            </ui:composition>
        </h:body>
    </html>
    

    最后,页面的支持bean:

    @Named
    @RequestScoped
    public class LoginBean implements Serializable
    {
        public String getEmailAddress()
        {
            return emailAddress;
        }
    
        public void setEmailAddress(String emailAddress)
        {
            this.emailAddress = emailAddress;
        }
    
        public boolean isRememberMe()
        {
            return rememberMe;
        }
    
        public void setRememberMe(boolean rememberMe)
        {
            this.rememberMe = rememberMe;
        }
    
        /** Action listener for login-form. Called after validation passes. */
        public void submit()
        {
            User user = userDao.findByEmailAddress(emailAddress);
            userRequestBean.login(user.getUserId());
    
            // Remember me
            if (!rememberMe)
            {
                return;
            }
    
            // Handle rememberMe here (create a cookie, etc.)
        }
    
        /** Called by the backing component's postValidate event handler */
        public void checkCredentials(String clientId, String emailAddress, String password)
        {
            if (!securityEjb.checkCredentials(emailAddress, password))
            {
                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Incorrect email address/password", null);
                FacesContext ctx = FacesContext.getCurrentInstance();
                ctx.addMessage(clientId, message);
                ctx.renderResponse();
            }
        }
    
        private String emailAddress = "";
    
        private boolean rememberMe = true;
    
        @Inject
        private UserRequestBean userRequestBean;
    
        @EJB
        private SecurityEjb securityEjb;
    
        @EJB
        private UserDao userDao;
    
        @EJB
        private LoginCookieDao loginCookieDao;
    }
    
  • 0

    f:event postValidate 方法不会给你留下很多选择 .

    我更喜欢的选项是对表单的最后一个组件进行验证,然后使用binding和f:attribute传入其他组件 .

    例如

    <h:inputText id="field1" binding="#{field1}" ... />
    <h:inputText id="field2" validator="#{...}">
      <f:attribute name="field1" value="#{field1}"/>
    </h:inputText>
    

    然后在验证器中,您可以从UIInput中获取其他组件:

    UIComponent field1 = field2.getAttributes().get("field1")
    
  • 0

    我使用JsfWarn来解决类似的问题,我认为它以更清洁的方式解决了你的问题 .

    与JSF验证器不同,在模型更新后,在调用应用程序之后,在渲染响应之前执行WarningValidators,因此您只需访问应用程序以获得验证结果 .

    @Named
    public class BarWarningValidator implements WarningValidator{
    
        @Inject
        private MyValidationBean validationBean;
    
        @Override
        public void process(FacesContext context, UIInput component, ValidationResult validationResult) {
            if(!validationBean.isBarValid()) {
                validationResult.setFacesMessage(new FacesMessage(FacesMessage.SEVERITY_WARN, "FooBar", "This is a warning."));
            }
        }
    }
    

    并将验证器添加到目标字段:

    <h:outputLabel for="bar" value="Default warning:" />
    <h:inputText id="bar">
        <jw:warning validator="#{barWarningValidator}" />
        <f:ajax event="change" render="..." />
    </h:inputText>
    <h:message for="bar" />
    

相关问题