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:
Company.xsp
The Company XPage let's you create and edit company documents with fields for the company name and its address:
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. Company XPage powered by the DummyPageDataProvider
CompanyList.xsp
The CompanyList XPage contains a view control and displays the company documents in the database:
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:
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 [24]