Interceptor Pattern for TableModel From ADF Table Binding
Written by Don
Young , InPowerSoft, January 2006
Introduction
Using a Swing table to display database data is probably the most natural
and prolific presentation in a Swing/ADF based application. Fortunately,
ADF Swing has very strong support that allows a user to create a table binding
that binds a view object to a JTable. The table binding acts as a controller
and takes care of many mundane manipulations of data via the JTable components.
For example, UPDATE, DELETE, INSERT and SELECT of data can be performed
via the JTable and other UI components such as the tool bar to manipulate
a ADF view object's data.These operations if to be wired from first principle
without the aid of ADF Swing and ADF will be nontrivial and error prone.
ADF Swings code produced a TableModel object to drive the JTable as a result
of creating a table binding. However, an application sometimes has needs
to intercept what goes into and out of the ADF layer before they interface
with a JTable component. For example, if we prefer a different mechanism
than what's in ADF to detect dirty cell status within a JTable in comparison
to the underlying ADF view object data so that the owning transaction
can be marked dirty. We will use the interceptor pattern by introducing
an object between ADF's TableModel object and JTable objects. There are
many other applications for this Table Model interceptor including converting
native oracle.jbo.domain.* data types to your own application Java types.
The JUTableBinding - a Deeper Look
In JUTableBinding class, you will find a function:
public
static
TableModel createAttributeListBinding(JUPanelBinding formBinding, JTable
control, String voInstanceName, String voIterName, /*temporarily
taking nulls for this*/
String voIterBindingName, String[] attrNames);
If you use ADF API to create a table binding, you may call this function
directly, otherwise if you use UI editor in JDeveloper, this function
may be called on your behalf. This function will create a table binding
based on the table attributes you passed in that map to the attributes
of a view object you want to display in a JTable (see ADF doc for details).
The return from this call is a TableModel object that manufactured by
ADF.
A Swing TableModel is an interface that is rather simple. Of all the
functions on this interface, two deserve more explanation than others.
public
Object getValueAt(int
row, int
column) ;
Whenever JTable needs to paint its content, this function will be called.
The ADF TableModel object will get the data from a view object and its
cache which in turn get the data from the corresponding entity object
and its cache, tracing all the way to the database. In the reverse order,
public
void
setValueAt(Object newValue, int
row, int
column) ;
does the opposite of the getValueAt. Notice that the view object will
deliver or accept what it expects of a particular java types that maps
to ADF domain types. For example, ADF uses oracle.jbo.domain.Number to
represent any numeric quantity and that is very different from the traditional
usage of Long,Integer,Double,Short,Float and BigDecimal data types. ADF
also uses oracle.jbo.domain.Date to represent date time information different
from java.util.Date. These underlying oracle domain data types are not
type compatible from the traditional java types in terms of derivation
hierarchy.
Implementing the TableModel
In order to create an interceptor that is compatible with the TableModel,
we have to create an implementation of TableModel and catch the instance
of TableModel returned by ADF. Notice that every TableModel call has to
be delegated to the ADF's TableModel instance we obtained earlier. You
now have every opportunity to do processing as deem fit for every call
that you intercept. In the example below, we intercept the setValueAt
function to detect dirty cell which is slight different than what is in
ADF's TableModel implementation (step into the source code for verification).
public class IpsTableModel implements TableModel
{
protected TableModel juTableBindingTableModel;
public IpsTableModel(TableModel juTableBindingTableModel)
{
this.juTableBindingTableModel = juTableBindingTableModel;
}
public int getRowCount()
{
return juTableBindingTableModel.getRowCount();
}
public int getColumnCount()
{
return juTableBindingTableModel.getColumnCount();
}
public String getColumnName(int column)
{
return juTableBindingTableModel.getColumnName(column);
}
public Class getColumnClass(int column)
{
return juTableBindingTableModel.getColumnClass(column);
}
public boolean isCellEditable(int row, int column)
{
return juTableBindingTableModel.isCellEditable(row, column);
}
public Object getValueAt(int row, int column)
{
return juTableBindingTableModel.getValueAt(row, column);
}
public void setValueAt(Object newValue, int row, int column)
{
Object oldValue = getValueAt(row, column);
boolean save = false;
if(oldValue != null && !oldValue.equals(newValue))
save = true;
else if(newValue != null && !newValue.equals(oldValue))
save = true;
if(save)
juTableBindingTableModel.setValueAt(newValue, row, column);
}
public void addTableModelListener(TableModelListener l)
{
juTableBindingTableModel.addTableModelListener(l);
}
public void removeTableModelListener(TableModelListener l)
{
juTableBindingTableModel.removeTableModelListener(l);
}
}
Intercepting Values
If you choose to convert ADF data type to your Java data type,
you can do it within the setValueAt and getValueAt functions. For example,
we like to convert oracle.jbo.domain.Date to java.util.Date, we can use
the following code:
public Object getValueAt(int row, int column)
{
Object valueAt = juTableBindingTableModel.getValueAt(row, column);
if(valueAt instanceof oracle.jbo.domain.Date)
{
oracle.jbo.domain.Date date = ((oracle.jbo.domain.Date) valueAt);
return Converter.oracleDateToJavaDate(date);
}
return juTableBindingTableModel.getValueAt(row, column);
}
public void setValueAt(Object newValue, int row, int column)
{
if(newValue instanceof java.util.Date)
newValue = Converter.javaDateToOracleDate((Date) newValue);
Object oldValue = getValueAt(row, column);
boolean save = false;
if(oldValue != null && !oldValue.equals(newValue))
save = true;
else if(newValue != null && !newValue.equals(oldValue))
save = true;
if(save)
juTableBindingTableModel.setValueAt(newValue, row, column);
}
Here we assume to have a utility class Converter that knows how to convert
from a java.util.Date to oracle.jbo.domain.Date and vice versa. These
type conversion may be necessary if you buy a date picker component that
would not understand the oracle data types and only work with traditional
java types. All editor components in our InPowerForms - a framework that extends Oracle's ADF Swing, however, works with
native ADF java data types and will require no conversion in the process.
Needless to say, if you do your type conversion, you will also need to
intercept public
Class getColumnClass(int
column); such that it returns the right type to the JTable. Do not intercept
public
int
getRowCount() and
public
int
getColumnCount() or you would confuse JTable and make it inconsistent
with the ADF view object. ADF Swing does some smart on-demand scrolling
of the table if it has a large number of rows.
Unless you want to use a different resource bundle than ADF's for internationalization,
do not change
public
String getColumnName(int
column) as well.
Instantiate the TableModel
Last, we will create a static function to instantiate a TableModel. During
the instantiation process, we will insert our own version of the table
model and capture the original version from ADF for delegation purpose.
public static TableModel myCreateAttributeListBinding(final JUPanelBinding formBinding,
JTable control, String voInstanceName,
String voIterName, String voIterBindingName,
String[] attrNames)
{
TableModel innerModel = JUTableBinding.createAttributeListBinding(formBinding, control,
voInstanceName, voIterName, voIterBindingName, attrNames);
return new IpsTableModel(innerModel,null);
}
|