Sometimes you want the entire set of an object from the database, but in most cases, you want a specific collection. Earlier in the section on finder methods in the BusinessObjectFactoryinterface, a mechanism was discussed in the reference architecture through which a named collection was defined in the business object metadata. These collections were tied to a defined SQL or OQL WHERE clause. This same mechanism can be used to define the collections for the ObjectListutility. As an example, assume that you wanted to retrieve active accounts for a customer. You could define another Accountcollection for this as follows:
<Collection name="activeByCustomer"
query="where customer_id = ? and last_modified_date > ?" />
ObjectListis a JDBC wrapper utility, so it needs the database column and table names specified in the business object metadata in order to generate the correct SQL. This was otherwise not required in the reference architecture metadata for Entity Beans or other persistence framework options. Also note that the query is defined using regular SQL and Java’s PreparedStatementformat of question marks as placeholders for run-time bindings.
N OT E
B E ST P R AC T I C E
Figure 4.3 ObjectList UML Model.
EntityObjectList
getAsBusinessObject()
CastorObjectList
getAsBusinessObject() ObjectList
getValueObjects() size()
hasNext() next()
objects:Collection objectName:String counter:int
TheObjectListutility can be constructed for a given object type. You can then use it to retrieve various defined collections of the corresponding value objects. It can hold a retrieved list until a new collection is retrieved. You can provide an interface similar to Iteratorfor navigating the list. You also want to have a method that can be used to get the current object as a business object; however, this will be imple-mented differently based on your particular business object implementation model.
Thus, subclasses of ObjectListcan be created, such as EntityObjectList, that add a getAsBusinessObjectmethod. The overall object design for this utility is shown in Figure 4.3.
The code forObjectListis shown next. ThegetValueObjectsmethod is used to retrieve the collection. It returns the collection but also stores it to be potentially iter-ated using thehasNextandnextmethods. AgetSelectAllSQLmethod is used on the business object metadata class that returns the ‘selectfield1, field2,...
fromtableName’ for the object. The complete SQL string for the query is formed by appending the WHERE clause for the collection to this string.
public class ObjectList {
// The collection of objects
protected ArrayList collection = null;
// The business object metadata
protected BusinessObjectMetadata bom = null;
// The name of the object in the list protected String objectName = null;
// An index of the current object // being iterated
protected int counter = 0;
152 J2EE Best Practices: Java Design Patterns, Automation, and Performance
// The size of the list
protected int collectionSize = 0;
/*
* Constructor for a given object
*/
public ObjectList(String objectName) { this.objectName = objectName;
try { bom =
MetadataManager.getBusinessObject(objectName);
} catch (Exception ex) { ex.printStackTrace();
} } /*
* The default constructor should not be used.
*/
private ObjectList() { }
/*
* Get the entire list of objects for this type.
*/
public Collection getValueObjects() throws BlfException { return getValueObjects(null, null);
} /*
* Get a named collection of value objects.
*/
public Collection getValueObjects(String queryId, ArrayList args) throws BlfException {
try {
JDBCUtility dbutil = new JDBCUtility();
if (queryId == null) {
// If no query defined, get the entire list collection = dbutil.getValueObjects(objectName,
bom.getSelectAllSQL());
collectionSize = collection.size();
} else {
// Build the SQL string
// core select + defined query (where clause) StringBuffer buffer =
new StringBuffer(bom.getSelectAllSQL());
buffer.append(' ');
buffer.append(bom.getQuery(queryId));
String sql = buffer.toString();
collection = dbutil.getValueObjects(objectName, sql , args);
collectionSize = collection.size();
}
} catch (Exception e) {
throw new BlfException(e.getMessage());
}
return collection;
} /*
* Returns an indicator of whether there
* is another object in the list
*/
public boolean hasNext() {
if (counter > (collectionSize - 1)) { return false;
}
return true;
} /*
* Return the next object in the list.
*/
public ValueObject next() {
return (ValueObject) collection.get(counter++);
} /*
* Return the size of the list.
*/
public int size() { return collectionSize;
} /*
* Accessor for business object metadata
*/
protected BusinessObjectMetadata getMetadata() { return bom;
} }
TheEntityObjectListsubclass then just adds the getAsBusinessObject method. The code for this class is as follows:
public class EntityObjectList extends ObjectList { public EntityObjectList(String objectName) {
super(objectName);
}
154 J2EE Best Practices: Java Design Patterns, Automation, and Performance
public Object getAsBusinessObject() throws BlfException { if (counter == 0) {
throw new BlfException("You need to invoke next()"
+ "before you can invoke getAsBusinessObject().");
}
ValueObject valueObj = (ValueObject) collection.get(counter - 1);
String keyProperty =
getMetadata().getKeyField().getName();
Object obj = EJBFactoryImpl.findByPrimaryKey(
valueObj.getObjectName(),
valueObj.getProperty(keyProperty));
return obj;
} }
The primary code used from JDBCUtilityfollows:
public ArrayList getValueObjects(String object, String sql) throws BlfException {
return getObjects(object,sql,emptyArgs,true);
}
public ArrayList getValueObjects(String object, String sql, ArrayList args) throws BlfException {
return getObjects(object,sql,args,true);
}
public ArrayList getObjects(String object, String sql, ArrayList args,
boolean createValueObjects) throws ValidationException {
ArrayList results = new ArrayList();
try {
// Determine the collection class name.
BusinessObjectMetadata bom =
MetadataManager.getBusinessObject(object);
HashMap attributeMetadata = bom.getPropertyMap();
String objectClassName = null;
if (createValueObjects) {
objectClassName = bom.getValueObjClass();
} else {
objectClassName = bom.getBusObjClass();
}
// Invoke a generic method that executes a // prepared statement with arguments.
rs = executePreparedStatementQuery(sql, args);
while (rs.next()) {
// Use the common interface for the two:
// value objects and business objects.
ValueObject valueObject = (ValueObject)
(Class.forName(objectClassName)).newInstance();
Iterator propertyIterator =
attributeMetadata.values().iterator();
while (propertyIterator.hasNext()) { PropertyMetadata prop =
(PropertyMetadata)propertyIterator.next();
String columnName = prop.getDBName();
// Not all properties are stored // in the database.
if (columnName != null) {
// Call a generic method that uses // the common value object interface to // set the property value.
setField(rs, prop.getName(), prop.getDBName(), prop.getType(), valueObject);
} }
results.add(valueObject);
}
} catch (Exception e) {
throw new BlfException(e.getMessage();
}
close();
return results;
}
Using ObjectList
There will likely be cases in which you simply want to retrieve a list of objects for dis-play to the user. You can use ObjectListfor this purpose, but you can also use it if you potentially need to update some of the objects and don’t want to incur the over-head of using a collection of Entity Beans. As an example, assume you are using Entity Beans to implement the business objects and you want to assess a fee to customers if the total of all their account balances is less than $1,000. This is a contrived example, but it could be anything that requires some business logic that you might not do di-rectly in a SQL query. Thus, you can use ObjectListto run the previous search ex-ample of active accounts by customer. You then want to iterate through the collection, calculate the total balance of all the accounts, and if it is less than $1,000, assess a fee to one of the accounts. To do this, the getValueObjectsmethod is invoked to run the query and create the list of value objects. You can then iterate through the collection and, after calculating the total, use the getAsBusinessObjectmethod to obtain a
156 J2EE Best Practices: Java Design Patterns, Automation, and Performance
handle to that particular Entity Bean if you want to perform an update. The code to do this is as follows:
EntityObjectList eol = new EntityObjectList("Account");
ArrayList args = new ArrayList(2);
args.add(customerId);
args.add(cutoffDate);
eol.getValueObjects("activeByCustomer",args);
BigDecimal total = new BigDecimal(0);
while (eol.hasNext()) {
ValueObject valueObj = eol.next();
total = total.add(
valueObj.getDecimalProperty("currentBalance"));
}
if (total.compareTo(new BigDecimal(1000)) < 0) { AccountLocal account = (AccountLocal)
eol.getAsBusinessObject();
account.withdraw(new BigDecimal(10), "bank fee");
}