2

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

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

更新: 这样做的重点不是完全避免按名称查找组件,而是能够以不需要使用页面和/或 bean 知道内部的方式跨字段验证复合组件的字段组件的详细信息。

4

3 回答 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;
}
于 2012-06-10T05:36:04.847 回答
0

这种f:event postValidate方法不会给您留下很多选择。

我更喜欢的选项是对表单的最后一个组件进行验证,然后使用绑定和 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")
于 2012-06-10T01:30:30.130 回答
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" />
于 2015-07-06T21:36:44.933 回答