Search This Blog

Loading...

Wednesday, September 7, 2011

JSF composite component binding to custom UIComponent

In last post about  JSF form authentication on servlet 3 container I showed an example of how to make a simple user authentication on servlet 3 conainer using JSF.

This example creates jsf composite component that handles login.
When composite component is included in the page, actually whole subtree of components is included with parent top level component that is actual representation of composite component.  This is just a simple showcase, but when developing more complicated composite components, sometimes it is handy to implement behavior using java code instead of only using xhtml markup. So, this example will create some sort of backing class for the composite component.

First, here is an example of using page that uses login component:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
 <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:g="http://java.sun.com/jsf/composite/components">  
   
 <h:body>  
      <p>Login to access secure pages:</p>  
      <h:form id="loginComponent">  
           <g:login login="#{authBackingBean.login}"   
                success="/admins/admins?faces-redirect=true"/>  
      </h:form>  
 </h:body>  
 </html>

And the actual login is performed by #{authBackingBean.login} method that is called by our composite component when user clicks login button. Only requirement for login method is that it must return boolean value and accept two String parameters (that will be username and password). Here is an example of backing bean:

@ManagedBean  
 @RequestScoped  
 public class AuthBackingBean {  
        
      private static Logger log = Logger.getLogger(AuthBackingBean.class.getName());  
             
      public boolean login(String username, String password) {  
           FacesContext context = FacesContext.getCurrentInstance();  
           HttpServletRequest request =   
                (HttpServletRequest)context.getExternalContext().getRequest();  
             
           try {  
                request.login(username, password);  
           } catch (ServletException e) {  
                context.addMessage(null,   
                          new FacesMessage(  
                                    FacesMessage.SEVERITY_WARN, "Login failed!", null));  
                return false;  
           }  
   
           Principal principal = request.getUserPrincipal();  
           log.info("Authenticated user: " + principal.getName());  
           return true;       
      }  
 } 


Composite component is then responsible for calling login method with parameters that are entered by user and submited as username and password.

Below is an example of composite login component. It consists of login.xhtml  facelet file and LoginComponent class that extends UINamingContainer component.

login.xhtml markup file should be placed in resource library folder. Resource library is folder named resources in the top-level web application root and is accessible to the classpath.
In my example, I have resources/components folder that contains login.xhtml facelet file.


The name of the resource library is what comes after http://java.sun.com/jsf/composite and is actually the name of subdirectory that resides in resources folder. In code snipet above, i have http://java.sun.com/jsf/composite/components namespace since my component is in components subdirectory.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
 <html xmlns="http://www.w3.org/1999/xhtml"  
   xmlns:h="http://java.sun.com/jsf/html"  
   xmlns:f="http://java.sun.com/jsf/core"   
   xmlns:composite="http://java.sun.com/jsf/composite">  
   
  <composite:interface componentType="loginComponent">  
       <composite:attribute name="login" required="true"   
                 method-signature="boolean f(java.lang.String, java.lang.String)"/>  
       <composite:attribute name="success" required="true"/>  
       <composite:attribute name="failure" required="false"/>  
  </composite:interface>  
   
       
   <composite:implementation>  
           <h:panelGrid columns="2">  
                <h:outputLabel for="#{cc.clientId}:username" value="Username:" />  
                <h:inputText id="username" binding="#{cc.username}" />  
                                 
                <h:outputLabel for="#{cc.clientId}:password" value="Password:" />  
                <h:inputSecret id="password" binding="#{cc.password}" />  
                                 
                <h:commandButton id="loginButton" value="Login"   
                     action="#{cc.action}" actionListener="#{cc.actionListener}" />  
           </h:panelGrid>  
   </composite:implementation>  
 </html>

I will not go into detail about interface and implementation sections, there is plenty of articles about composite components. 
Here is important to note that componentType on interface tag is interpreted as component type of a component already registered with JSF  and when JSF runtime encounters a composite component tag of that type it will create instance of that component that will serve as the composite component root (top level component).

Interface section declares method attribute login that has two String parameters and  will be called when user clicks login button and will perform actual login process. Login method is called by backing UIComponent class for showcase purpose to show how method expression can be called manually from java code .  If there were no custom backing UIComponent attached to this composite component, than method expresion could be attached directly to action attribute of login button.
In case of successful login, success attribute is used as outcome where user will be redirected.

Interesting thing is also #{cc.action} and #{cc.actionListener} action methods that point directly to methods on actual UIComponent that is top-level component. JSF assumes that top-level component for composite component is javax.faces.component.NamingContainer component.

The easiest way is to extend javax.faces.component.UINamingContainer. Here is an example of backing component for composite component:

@FacesComponent(value="loginComponent")  
 public class LoginComponent extends UINamingContainer {  
        
      private UIInput username;  
      private UIInput password;  
        
      private String action;  
        
      public LoginComponent() {  
           super();  
      }  
        
      public void actionListener(ActionEvent ae) {  
             
           FacesContext context = FacesContext.getCurrentInstance();  
           ELContext elContext = context.getELContext();  
   
           Object params[] = new Object[2];  
           params[0] = username.getValue();  
           params[1] = password.getValue();  
             
           MethodExpression me = (MethodExpression)this.getAttributes().get("login");  
           boolean loginSuccess = (Boolean)me.invoke(elContext, params);  
             
           if(loginSuccess) {  
                action = (String)getAttributes().get("success");  
           } else {  
                action = (String)getAttributes().get("failure");  
           }  
      }  
        
      public String action() {  
           return action;  
      }  
        
      public UIInput getUsername() {  
           return username;  
      }  
   
      public void setUsername(UIInput username) {  
           this.username = username;  
      }  
   
      public UIInput getPassword() {  
           return password;  
      }  
   
      public void setPassword(UIInput password) {  
           this.password = password;  
      }            
 }  

@FacesComponent annotation registers this UIComponent with JSF runtime. Notice that annotation value loginComponent matches the value of componentType in login.xhtml facelet file. When user clicks login button, ActionListener method is called. Login method expression is obtained from attributes map and submited username and password values are obtained from binded UIInput components. This values are then used as parameters to invoke login method.
Depending on method invocation result, action  variable is then set with success or failure attribute value and is used as outcome for login button so that user is redirected to appropriate view.

6 comments:

  1. Hi,

    We are following your example, however we are not quite sure where you get the 'cc' part from and what that points to eg.binding="#{cc.username}"

    Any help would be appreciated!

    cheers

    ReplyDelete
    Replies
    1. Hi,

      'cc' is a reserved word in JSF for composite component and in my case that is LoginComponent. So, cc.username binds to username UIInput variable in LoginComponent class.

      Delete
  2. Hi, Gordan,

    Thanks for Your work! It's helpful.
    Instead of 'cc' :)
    At JBoss 7.1.0.Final I've got an exception:
    javax.el.PropertyNotFoundException: /resources/components/login.xhtml @18,57 binding="#{cc.username}": Target Unreachable, identifier 'cc' resolved to null

    And I'm absolutely sure that my backing Auth bean exists and LoginComponent is exists in classpath, because I inserted a component at my existing working login page.

    What can I do to avoid an exception?

    Cheers!

    Alexander.

    ReplyDelete
    Replies
    1. Did you solve this? I've the same problem.
      Using Tomcat 7.0.26 + PrimeFaces 3.3.1

      Delete
    2. I had the same problem but i was making a mistake, because i was overriding saveState but without saving and restoring super.saveState so all the attributes was lost !

      Delete
  3. Hi,

    I tried the example in both JSF 2.0/Weblogic and Mojarra JSF 2.1.11/Tomcat. But both cc.clientId and cc:password cannot be evaluated. Did you encounter this problem before? Have your code been tested in the environments listed above yet?

    Thanks.

    ReplyDelete