Mindoo Blog - Cutting edge technologies - About Java, Lotus Notes and iPhone

  • XPages series #4: Backing Bean Management with XPages

    Karsten Lehmann  16 July 2009 09:58:16
    In this part of the XPages series about an alternative application architecture, I'm going to talk about Backing Bean Management.

    At the end of this blog entry, you should get the trick how the XPages user interface can be connected to backend Java Beans. I will follow up with another article that contains code snippets you might need after you start working with this technology in a real-life project.

    And I promise to show an example at the very end of this series, to not leave you alone, totally confused with Java, Beans, Bindings, Expression Language and other things. :-)

    Warning! This article is looooong ;-).

    Separation of UI and application logic
    A Backing Bean is a Java Bean that is associated with UI components used in a page. The purpose of Backing Beans is to separate the definition of UI components from objects that perform application-specific processing and hold data. That way your XPage will contain more or less just the UI, while the application logic is stored in the bean.

    To continue with our Movie Actor database, we start with a pretty simple Java class that is a container for the data of an actor:

    package com.acme.actors.model;

    public class Actor {
            private String m_id;
            private String m_firstName;
            private String m_lastName;
            private String m_comment;
           
            public Actor() {}
           
            public Actor(String id, String firstName, String lastName, String comment) {
                    m_id=id;
                    m_firstName=firstName;
                    m_lastName=lastName;
                    m_comment=comment;
            }

            public String getId() {
                    return m_id;
            }
           
            public void setId(String newId) {
                    m_id=newId;
            }
           
            public String getFirstname() {
                    return m_firstName;
            }
           
            public void setFirstname(String newFirstName) {
                    m_firstName=newFirstName;
            }
           
            public String getLastname() {
                    return m_lastName;
            }
           
            public void setLastname(String newLastname) {
                    m_lastName=newLastname;
            }
           
            public String getComment() {
                    return m_comment;
            }
           
            public void setComment(String newComment) {
                    m_comment=newComment;
            }
    }


    Well, pretty self-explanatory. The class will be is used to store the firstname, lastname, a comment and a unique identifier of an actor. Notice, that the class is completely independent from Lotus Notes. The identifier field might be later on used for a Notes document's universal id (in case we load Notes data), but it could just as well be something completely different like a primary key of table rows in a SQL database.

    Let's take a look at our first Backing Bean. It will be used to provide the backend logic of our actor list XPage:

    package com.acme.actors.controller;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.UUID;
    import com.acme.actors.model.Actor;

    public class ActorList {
            private List<Actor> m_actors;

            public ActorList() {}

            private void init() {
                    if (m_actors==null) {
                            //list is empty and needs to be filled
                            m_actors=new ArrayList<Actor>();

                            m_actors.add(new Actor(createNewId(), "Sean", "Connery", "James Bond"));
                            m_actors.add(new Actor(createNewId(), "Daniel", "Craig", "James Bond"));
                            m_actors.add(new Actor(createNewId(), "Catherine", "Zeta-Jones", "Ocean's Twelve"));
                            m_actors.add(new Actor(createNewId(), "Tobey", "Maguire", "Spiderman"));
                            m_actors.add(new Actor(createNewId(), "Kirsten", "Dunst", "Spiderman"));
                            m_actors.add(new Actor(createNewId(), "John", "Travolta", "Pulp Fiction"));
                            m_actors.add(new Actor(createNewId(), "Bruce", "Willis", "Die Hard"));
                            m_actors.add(new Actor(createNewId(), "Christopher", "Lee", "Dracula"));
                            m_actors.add(new Actor(createNewId(), "Patrick", "Stewart", "Star Trek"));
                            m_actors.add(new Actor(createNewId(), "Charlton", "Heston", "Planet of the Apes"));
                    }
            }

            private String createNewId() {
                    //create a new unique identifier for a list entry
                    return UUID.randomUUID().toString();
            }

            public List<Actor> getActors() {
                    //init() initializes the actor list the first time it is called
                    init();

                    return m_actors;
            }

            public void setActors(List<Actor> newActors) {
                    m_actors=newActors;
            }
    }


    Our class does not contain any real business logic at the moment. It contains a list of ten actors that are written into the internal variable "m_actors", which is returned by the method "getActors()".
    We will use this method in our XPage, to feed the rows of a table.

    Bean declaration in faces-config.xml
    Next we need to tell the JavaServer Faces runtime about our new bean.
    To do this, modify the faces-config.xml file and add the following <managed-bean> section:

    <?xml version="1.0" encoding="UTF-8"?>
    <faces-config>
      <managed-bean>
        <managed-bean-name>ActorList</managed-bean-name>
        <managed-bean-class>com.acme.actors.controller.ActorList</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
      </managed-bean>
    </faces-config>


    With this information, the JSF runtime is able to resolve an expression language construct like "#{ActorList}" to find our bean. It knows that the bean has the classname "com.acme.actors.controller.ActorList" and can create a new instance of it.
    We also tell the runtime something about the bean's life span, called "scope", in this line:

    <managed-bean-scope>session</managed-bean-scope>

    The scope "application" means that there will be only one instance of the bean in the whole application (=one bean for all users, useful for translation dictionaries). With"session", one bean is created for the whole session of a single user, e.g. to hold personal profile information. With the scope"request" you can create a bean that is only valid for a single HTTP request (to pass data from one page to the other) and the scope "none"  lets the JSF runtime dispose the bean after every HTTP request. In that case, it is stored nowhere automatically.

    And now, the end is near
    To bring all the pieces together, we now create our ActorList XPage and insert a data table control with three columns:

    Image:XPages series #4: Backing Bean Management with XPages

    For the table's binding, we click on "Advanced", select "Expression Language (EL)" from the combo box and enter the following text: ActorList.actors.
    This produces the string "#{ActorList.actors}" in the source code of the XPage. When the JavaServer Faces runtime resolves this expression to retrieve the row data for the table, it fetches/creates our ActorList Java Bean from the sessionScope and searches for a method "getActors" in it. This is our method that returns the List of actors.

    The table will now display each List entry in its own table row. By entering the string "currentActor" in the Collection name field (see #2 in the screenshot above), the row data (the current Actor objects) can be accessed in the row controls (in my case three computed fields for the firstname, lastname and comment):

    Image:XPages series #4: Backing Bean Management with XPages

    Here is the full source code of the ActorList XPage:

    <?xml version="1.0" encoding="UTF-8"?>
    <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
            <xp:dataTable rows="5" id="actorTable" var="currentActor"
                    style="width:50%" value="#{ActorList.actors}">
                    <xp:this.facets>
                            <xp:pager layout="Previous Group Next" xp:key="header" id="pager1">
                            </xp:pager>
                            <xp:pager layout="Previous Group Next" xp:key="footer" id="pager2">
                            </xp:pager>
                    </xp:this.facets>
                    <xp:column id="firstnameColumn">
                            <xp:text escape="true" id="firstnameField" value="#{currentActor.firstname}">
                            </xp:text>
                    </xp:column>
                    <xp:column id="lastnameColumn">
                            <xp:text escape="true" id="lastnameField" value="#{currentActor.lastname}">
                            </xp:text>
                    </xp:column>
                    <xp:column id="commentColumn">
                            <xp:text escape="true" id="commentField" value="#{currentActor.comment}">
                            </xp:text>
                    </xp:column>
            </xp:dataTable>
    </xp:view>


    And finally:
    This is how our example looks like. It's a table with our in-memory Actor Java objects:

    Image:XPages series #4: Backing Bean Management with XPages

    Instead of a data table, you could of course also use a repeat control to have full control over the generated code. To make the table cells editable, just use an edit box instead of the computed fields. The XPages/JavaServer Faces runtime will automatically inform you about the new values by calling the set-methods of the Actor objects.

    Hint #1
    The method "getActors()" is called on every page refresh and also when the user switches from page 1 of the table to page 2. So you should make sure that you do some caching in your bean and avoid CPU intensive data lookups on every call.

    Hint #2:
    Please note that we return a Java List (java.util.List) implementation in our method "getActors()".
    Nobody said that all the data needs to be in-memory when the data table component calls "getActors()". When you develop your own List implementation, you have full control, which data really has to be loaded from your (external) data store. The table/pager only needs the currently visible rows of the active page.

    Hint #3
    You should avoid to return a Java Collection (java.util.Collection) instead of the Java List in the "getActors()" method.
    That does works, but the pager around the data table needs to know how many elements there are, to calculate the amount of pages for the navigation. Since a Java Collection does not return the total number of entries, the pager calls Collection.toArray(), which grabs the whole data list as an array into memory. That might kill the server for large amounts of data.