This can be done. In the following code, take particular note of the postValidate
event in the composite component and the postValidate
method in the backing component. Notice how it resolves the MethodExpression
attribute and invokes the passed-in method.
Here's the composite component:
<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>
The backing component:
@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";
}
The LoginFormValue
class for completeness:
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;
}
The page that uses the login form:
<!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>
And finally, the page's backing 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;
}
postValidate
handler method. So are you saying the problem isn't the hard-coded IDs, but which class they're referenced in? – Sheave