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

  • XPages series #11: Log data changes using beans and the DataObject interface

    Karsten Lehmann  18 March 2011 10:47:26
    As promised last week, this blog article demonstrates how managed beans can be used to create and edit Notes documents, transparently log data changes and even support alternative storage systems. This is my third and final sample from the Lotusphere 2011 session BP212.

    If you followed this blog series from the beginning and take a look at the slides for the session BP212, you already know most of the technical details this sample is about:
    • declare managed beans
    • bind XPages UI fields with bean properties
    • use managed properties to declaratively configure managed beans

    So there is not much new content, but it's a nice sample that shows how all the parts fit together in an application.


    The download for this article contains a simple Notes database with four XPages:

    Start.xsp
    This XPage is the starting point when you open the database. It contains three buttons to create new company documents, view a list of existing documents and a button to view a change log that tracks data changes in the database:

    Image:XPages series #11: Log data changes using beans and the DataObject interface


    Company.xsp
    The Company XPage let's you create and edit company documents with fields for the company name and its address:

    Image:XPages series #11: Log data changes using beans and the DataObject interface


    Here is the source code for the XPage. The important parts are marked in red:

    <?xml version="1.0" encoding="UTF-8"?>
    <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
            <xp:span style="font-size:18pt;text-decoration:underline">Company information</xp:span>
            <xp:br style="font-size:18pt"></xp:br>
            <xp:br></xp:br>
            <xp:span style="font-size:18pt">Company name:</xp:span>
            <xp:br></xp:br>
            <xp:inputText id="inputText1" style="width:295.0px;font-size:18pt"
                    value="#{data.companyname}"></xp:inputText>
            <xp:br></xp:br>
            <xp:br></xp:br>
            <xp:span style="font-size:18pt">Address 1</xp:span>
            <xp:br style="font-size:18pt"></xp:br>
            <xp:inputText id="inputText2" style="width:295.0px;font-size:18pt"
                    value="#{data.address1}"></xp:inputText>
            <xp:br></xp:br>
            <xp:br></xp:br>
            <xp:span style="font-size:18pt">Address 2
    </xp:span>
            <xp:br></xp:br>
            <xp:inputText id="inputText3" style="width:295.0px;font-size:18pt"
                    value="#{data.address2}"></xp:inputText>
            <xp:br></xp:br>
            <xp:br></xp:br>
            <xp:span style="font-size:18pt">
                    City
            </xp:span>
            <xp:br style="font-size:18pt"></xp:br>
            <xp:inputText id="inputText4" style="width:295.0px;font-size:18pt"
                    value="#{data.city}"></xp:inputText>
            <xp:br></xp:br>
            <xp:br></xp:br>
            <xp:button value="Save" id="saveButton" style="font-size:18pt">
                    <xp:eventHandler event="onclick" submit="true"
                            refreshMode="complete">
                            <xp:this.action><![CDATA[#{javascript:actions["save"].execute();
    context.redirectToPage("CompanyList");}]]></xp:this.action>
                    </xp:eventHandler>
            </xp:button>
            <xp:button value="Cancel" id="cancelButton" style="font-size:18pt">
                    <xp:eventHandler event="onclick" submit="true"
                            refreshMode="complete">
                            <xp:this.action><![CDATA[#{javascript:context.redirectToPage("Start");}]]></xp:this.action>
                    </xp:eventHandler>
            </xp:button>
    </xp:view>


    We do not directly bind UI fields to document items (there is no document datasource declaration), but use a managed bean called "data" instead, hence the EL strings like #{data.companyname}.

    Actually, the bean implementation does not handle the load/store operation itself, it just redirects the calls to another class that handles them. This redirection is used to be able to change the storage system later on, e.g. from NSF to SQL, without modifying anything in the XPages UI.

    All we need to do is change a single managed property in the faces-config.xml file, which can be found in the WebContent/WEB-INF folder in the Java perspective of DDE:

    <?xml version="1.0" encoding="UTF-8"?>
    <faces-config>
            <managed-bean>
                    <managed-bean-name>data</managed-bean-name>
                    <managed-bean-class>com.ls11.uibackend.PageDataBean
                    </managed-bean-class>
                    <managed-bean-scope>request</managed-bean-scope>
                   
                    <managed-property>
                            <property-name>dataProviderClass</property-name>
                            <!-- Dummy implementation to simulate loaded data -->
                            <!--
                            <value>com.ls11.uibackend.dummy.DummyPageDataProvider</value>
                            -->

                            <!-- Implementation that loads/stores data in a Notes database -->
                            <!--
                            <value>com.ls11.uibackend.nsf.NSFPageDataProvider</value>
                             -->

                             
                            <value>com.ls11.uibackend.dummy.DummyPageDataProvider</value>
                    </managed-property>
            </managed-bean>
            <managed-bean>
                    <managed-bean-name>actions>
                    <managed-bean-class>com.ls11.uibackend.PageActionsBean
                    </managed-bean-class>
                    <managed-bean-scope>request>
            </managed-bean>
      <!--AUTOGEN-START-BUILDER: Automatically generated by IBM Lotus Domino Designer. Do not modify.-->
      <!--AUTOGEN-END-BUILDER: End of automatically generated section-->
    </faces-config>


    As you can see, the database contains two implementations to load/store data:
    The NSFPageDataProvider  reads the documentId query string parameter and uses the corresponding Notes document to get the data. DummyPageDataProvider is just a dummy implementation that creates placeholder text, which might be useful for the UI designer to already test the UI even though the database developer is still working on the NSF/SQL storage code.

    Image:XPages series #11: Log data changes using beans and the DataObject interface
    Company XPage powered by the DummyPageDataProvider



    CompanyList.xsp
    The CompanyList XPage contains a view control and displays the company documents in the database:

    Image:XPages series #11: Log data changes using beans and the DataObject interface


    ChangeLog.xsp
    The main benefit of using managed beans for the UI bindings instead of directly binding fields to document datasource properties is that you can track, log, transform and prevent data changes in your code. During the page request, JSF calls the method setValue in our Java class with the item names and the new item values.

    We can then compare the old and new values and create a log record if the value has been changed:

            public void setValue(Object id, Object newValue) {
                    Object oldValue=getValue(id);
                   
                    boolean changed=(newValue==null & oldValue!=null) || (newValue!=null && !newValue.equals(oldValue));
                    if (changed) {
                            m_changedValues.put(id.toString(), newValue==null ? null : newValue.toString());
                            logChangedData(id.toString(), oldValue, newValue);
                    }
            }

            /**
             * Create a new change log entry for the modified field
             *
             * @param id field name
             * @param oldValue old value
             * @param newValue new value
             */

            private void logChangedData(String id, Object oldValue, Object newValue) {
                    Session session=NotesContext.getCurrent().getCurrentSession();

                    try {
                            DataChangeLogEntry newEntry=new DataChangeLogEntry(m_documentId==null || "".equals(m_documentId) ? "-New-" : m_documentId, session.getUserName(), new Date(), id, oldValue==null ? null : oldValue.toString(), newValue==null ? null : newValue.toString());
                            m_pendingLogEntries.add(newEntry);
                    } catch (NotesException e) {
                            throw new FacesException(e);
                    }
            }


    When the Company XPage is saved, all pending log records are written to a permanent log list which is displayed in a data table on the ChangeLog XPage:

    Image:XPages series #11: Log data changes using beans and the DataObject interface


    A word about com.ibm.xsp.model.DataObject
    If you take a deeper look at the code in the sample database, you might notice that the data bean implements the interface com.ibm.xsp.model.DataObject.
    This interface is part of the XPages runtime (not part of the JSF standard!) and simplifies both writing beans and the EL syntax to access bean properties.

    You will also find this interface in the session slides for BP212. Here is an excerpt from the slides:

           package com.acme.demo.persondata;

            import com.ibm.xsp.model.DataObject;

            public class PersonData implements DataObject {
                    public Class getType(Object id) {
                            // Return the type of class that id resolves to.
                            // Complex case could return Employee or Customer

                            return Person.class;
                    }

                    public Object getValue(Object id) {
                            // Retrieve a record from some store, based on id
                            return null;
                    }

                    public boolean isReadOnly(Object id) {
                            // You are free to implement your own, or rely on your underlying data store
                            return false;
                    }

                    public void setValue(Object id, Object value) {
                            // Store value in your data store using id
                    }
            }


    Implementing the DataObject interface makes JSF call the setValue/getValue methods in your bean for any string behind the bean name in the EL string: #{data.companyname], #{data.address1}, #{data.address2}, #{data.whatever}.

    You don't have to write getter and setter methods for every property!

    Please note that this could already be done without the DataObject interface by implementing the java.util.Map interface (JSF then calls the method Map.get(Object) instead of DataObject.getValue(Object)), but this XPages runtime class makes the implementation much easier and cleaner.

    That's it for today! Here is the download archive with the sample database:
    ls11_uibackendsep.zip


    Comments

    1John Mackey  18.03.2011 14:26:41  XPages series #11: Log data changes using beans and the DataObject interface

    Karsten, as always I enjoy your articles. Thanks for the series....good stuff!

    -John

    2Michael Sobczak  02.04.2011 20:02:09  A few questions

    Will this technique work with 8.5.1?

    Also, will this technique work with databases based on templates?

    3Karsten Lehmann  03.04.2011 01:07:16  XPages series #11: Log data changes using beans and the DataObject interface

    I haven't tested it but it should work in 8.5.1. I have worked on a few development projects for 8.5.1 so far and haven't had any issues with managed beans.

    4Keith Strickland  04.04.2011 00:31:43  XPages series #11: Log data changes using beans and the DataObject interface

    Karsten, great series. I've been playing with your sample database today and the technique you use there is brilliant, one bean to rule them all!

    5Dharmeh Bhakta  15.04.2011 22:57:21  XPages series #11: Log data changes using beans and the DataObject interface

    I'm trying this with 8.5.2 and getting java.io.NotSerializableException: com.ls11.uibackend.nsf.NSFPageDataProvider exception. And then I looked at the public java doc and I can't seem to find com.ibm.xsp.model.DataObject class..

    am i missing something here?

    6Jeff Byrd  02.05.2011 15:57:15  XPages series #11: Log data changes using beans and the DataObject interface

    @5 I'm getting the same error.

    7Karsten Lehmann  02.05.2011 16:28:45  XPages series #11: Log data changes using beans and the DataObject interface

    Ok, guys, looks like I only tested it locally in the Notes client.

    I have modified the code and re-uploaded the file. Now it should work on the web (Domino 8.5.2.) and locally.

    8Köll  16.05.2011 17:13:32  XPages series #11: Log data changes using beans and the DataObject interface

    Greetings Karsten!

    Wonderful post, it helps me futher understand the marriage of Xpages and JSF. You also made it very easy implement sample code into other Apps...

    I am having a slight issues, that seems to be coming back even after I think I'd fixed it.

    (1) First I had to build a form (Company), the project did not have one and would not let me go further without one.

    (2) After adding the form, the App ran okay, added data, edited, and looked up the log page...

    However, after rebooting machine, the App longer works, threw the following errors

    Unexpected runtime error

    The runtime has encountered an unexpected error.

    Error source

    Page Name:/Company.xsp

    Control Id: saveButton

    Property: onclick

    Exception

    Error while executing JavaScript action expression

    Script interpreter error, line=1, col=17: Error calling method 'execute()' on java class 'com.osc.its.kcherizard.dashboards.Actions.SaveDataAction'

    Could not store data to Notes document

    JavaScript code

    1: actions["save"].execute();

    2: context.redirectToPage("CompanyList");

    if I start over, build the form again, clear my cache and reboot, same deal.

    What are your thoughts? Thanks again for psoting this, truly helpful.

    In a bit!

    9Köll  18.05.2011 17:34:13  XPages series #11: Log data changes using beans (ACL)

    Figured it out Karsten:

    I did build everything from scratch after trying your option

    In doing do, I first forgot to modify my ACL... If the ACL does not have the specific access to Create Docs and so on, the project willnot run.

    Again, thanks for posting this, please disregard previous post.

    Have a great week!

    10Karsten Lehmann  18.05.2011 17:37:11  XPages series #11: Log data changes using beans and the DataObject interface

    Hi!

    I'm glad that you could solve the issue. I'm quite busy these days and had not found the time yet to take a look at it, but it was definitely on my to do list.

    Best regards,

    Karsten

    11Köll  19.05.2011 05:04:56  XPages series #11: Log data changes using beans and the DataObject interface

    I actually did sense you were busy, always hard at work it seems. You're a credit to your community...

    I got pretty lucky with log errors I was also getting on the Server. Went head to head with it and got it going. I appreciate your reply Karsten, keep up the good work.

    Regards,

    Köll

    12Petri Niemi  07.09.2011 12:17:08  XPages series #11: Log data changes using beans and the DataObject interface

    Hi,

    Sweet design pattern. I think that I will use something like this in the future. Maybe I'll write few beans myself and use this kind of approach in simple xPages.

    Thanks for sharing!

    Br,

    PT