Introduction
If you are doing any development using DFC, chances are high that you are going to execute
a DQL statement to interact with the content server, either to query some values or to
alter the docbase in some manner. The statement execution is achieved by invoking
execute()
on an IDfQuery, which always returns an IDfCollection.
What is returned in the IDfCollection is completely dependent upon the DQL statement
which was executed. If you are processing the result of executing “Administration Methods”,
then refer to the Invoking Administration Methods section of the Content Server DQL Reference
for details on what the Administration Method returns. More likely, you will be processing
the results of a standard DQL SELECT statement, in which case the IDfCollection will
contain multiple rows of results, one row for each selection which met the criteria of
the statement. Each row will contain values for the attributes which were specified in
the select statement, and the IDfCollection provides mechanisms for extracting those values.
While you may not care to iterate over the results contained in the IDfCollection,
it is mandatory that you close the IDfCollection when you are done with it
(see Cleaning Up).
We’ll go into several code examples later, but here is the standard paradigm for executing a DQL statement:
- Construct the DQL for the statement you want to execute.
- Construct a DfQuery object and set its DQL accordingly.
- Execute the DfQuery using an IDfSession and get the IDfCollection returned from theexecute()
call.
- Iterate over the results in the IDfCollection, one row at a time, extracting the values you need
from each row. - Close the IDfCollection.
Interfaces
The following are some of the interfaces which you will be working with when you
process an IDfCollection.
IDfValue
IDfValue is a container for an attribute data value, with convenience methods to perform conversions
of values from one type to another. Note that an IDfValue can only represent a single value, not
a set of repeating values. You do not need to use IDfValue objects to process the results in an
IDfCollection, but they are useful for type conversions.
IDfAttr
An IDfAttr encapsulates the name and type information for an attribute, including whether an
attribute is repeating. IDfAttr objects are managed by IDfTypedObjects. The supported types are
published as int constants in this interface and are:
Value Type | Description | Supports Null Value? |
IDfAttr.DM_BOOLEAN | boolean values | No |
IDfAttr.DM_INTEGER | integer values | No |
IDfAttr.DM_DOUBLE | floating-point values | No |
IDfAttr.DM_STRING | String values | Yes |
IDfAttr.DM_ID | IDfId values (Documentum object IDs) | Yes |
IDfAttr.DM_TIME | Time-based values (dates) | Yes |
Like IDfValue, it is not necessary to use IDfAttr objects when
processing the results in an IDfCollection, but they come in handy when you do not know the
types of the attributes selected in a statement.
IDfTypedObject
IDfCollection extends IDfTypedObject, which gives it a rich set of methods
for retrieving attribute values of various types. An IDfTypedObject is essentially a container for
IDfAttr objects (see above). The IDfTypedObject provides methods to locate its IDfAttr objects,
as well as methods to access the values for attributes. You will most likely be using
the getter methods of this interface in order to retrieve the values of attributes you specified
in a select statement. The large number of methods in this interface are a result of
providing setters and getters for six different data types, including accessors for repeating
attributes. However, after you use it a couple of times, it becomes less daunting.
IDfQuery
The IDfQuery interface is used to perform the execution of a DQL statement using an IDfSession.
These are almost always constructed directly as a DfQuery.
IDfCollection
The return value for IDfQuery.execute()
, an IDfCollection allows clients to iterator over the
results returned from the content server when a DQL statement is executed. Clients do not
construct instances of these, since the DFC framework will instantiate and return one every
time IDfQuery.execute()
is called.
Note:
It is imperative that every IDfCollection is closed, even if you do not care about the
results contained in it. So, always get the IDfCollection returned from IDfQuery.execute()
and always close it in a finally section of code which is guaranteed to be run.
See Cleaning Up for more details.
Using IDfCollections
An IDfCollection is a cursor into the results returned by the content server. In order to
advance the cursor and process the next row of results, you use the method next()
. This
method returns true if there is any data left to process, so it is usually invoked in
a while loop, in order to process all the rows of results. Here is a typical processing loop:
// Typical structure of a method which executes some DQL public static void execute(IDfSession session) throws DfException { // We define the IDfCollection outside of the try{} // block so that we can close it in the finally{}. // I usually name IDfCollections "rs", since they // are so similar to JDBC ResultSets (old habits die hard). IDfCollection rs = null; try { // Setup a new dql query IDfQuery query = new DfQuery(); query.setDQL(someDqlStatement); // Execute the query and recover the results rs = query.execute(session, DfQuery.DF_READ_QUERY); // Loop through the results while (rs.next()) { // Value extraction code goes here to get // desired attributes from the current row. } } finally { // Close the IDfCollection. // This is extremely important, and hence is called // inside a finally{} block. if (rs != null) { rs.close(); } } }
Getting the Attribute Values For Current Result Row
Now that the IDfCollection is positioned on a row of result attributes, the only remaining
work to do is get those attribute values out of the collection.
There are basically two ways to access the attribute data contained in the current row of
an IDfCollection, and the mechanism you choose to use will depend on how much information
you know about the attributes returned in the collection.
Accessing Attribute Values Directly
To use this approach, you must know the names and types of the attributes being processed,
as well as whether they are single-valued or repeating. To get a value out, you call the
method specific to the type of the attribute, passing the name of the attribute to the
method.
Note:
The IDfCollection will NOT
perform any type conversions for you, and it will throw a DfException if you try
to get a value as a type which is not the actual value type of the attribute. So,
it is important to call the correct method based on the type of the data.
The following table shows the types and the methods to use in IDfCollection to
retrieve the values:
Value Type | Single Value Accessor | Repeating Value Accessor |
IDfAttr.DM_BOOLEAN | getBoolean() | getRepeatingBoolean() |
IDfAttr.DM_INTEGER | getInt() | getRepeatingInt() |
IDfAttr.DM_DOUBLE | getDouble() | getRepeatingDouble() |
IDfAttr.DM_STRING | getString() | getRepeatingString() |
IDfAttr.DM_ID | getId() | getRepeatingId() |
IDfAttr.DM_TIME | getTime() | getRepeatingTime() |
Here is a simple example which gets the object ID, the name, and the content size
for all the dm_document objects in the docbase. Note that we use different accessor
methods for each attribute, since they are all of different types.
public static void execute(IDfSession session) throws DfException { IDfCollection rs = null; try { // Setup a new dql query IDfQuery query = new DfQuery(); query.setDQL("select r_object_id, object_name, r_content_size from dm_document"); // Execute the query and recover the results rs = query.execute(session, DfQuery.DF_READ_QUERY); // Loop through the results while (rs.next()) { IDfId id = rs.getId("r_object_id"); String name = rs.getString("object_name"); int size = rs.getInt("r_content_size"); } } finally { // Close the IDfCollection. if (rs != null) { rs.close(); } } }
Accessing Attribute Values Generically
This approach can be used to retrieve the attribute values when you do not know the types of the
attributes in the collection. The key is to ask the IDfCollection what IDfAttr objects it contains,
which will provide you with enough information to know which of the getter methods to call to
retrieve the values. The following example shows how you iterate over the IDfAttrs and a little
bit of the code you would have to write to pull the values out of the attributes.
public static void execute(IDfSession session) throws DfException { IDfCollection rs = null; try { // Setup a new dql query IDfQuery query = new DfQuery(); query.setDQL("select r_object_id, object_name, r_content_size from dm_document"); // Execute the query and recover the results rs = query.execute(session, DfQuery.DF_READ_QUERY); // Loop through the results while (rs.next()) { // Recover the IDfAttrs for the row, so we can get their names for (int i=0; i<rs.getAttrCount(); ++i) { IDfAttr attr = rs.getAttr(i); // Extract the IDfValue for the attribute. // This is convenient to get the data out of the collection, // but we still need to know how to interpret the value // (ie., as int, string, id, ...). IDfValue attrValue = rs.getValue(attr.getName()); } } } finally { // Close the IDfCollection. if (rs != null) { rs.close(); } } }
If you find yourself going down this road, then be prepared for a bumpy ride. Notice that
we didn’t really do much with the IDfValue after we got it out. That’s because the example
was not well suited to the generic approach. If you need the generic approach, you are probably
doing something quite complicated, like trying to put an O/R mapping framework on top of
Documentum. If that is the case, you will have to be prepared to deal with the generic
values coming out of the collection. For example, here’s a utility method to recover the
data stored in an IDfValue as an Object, automatically converting the primitive values
to their Object counterparts.
/** * Returns the DFC object that is the value of the given IDfValue object. * The return types correspond to the data type of the IDfValue as follows: * * <ul> * <li><b>DF_BOOLEAN</b> - java.lang.Boolean</li> * <li><b>DF_DOUBLE</b> - java.lang.Double</li> * <li><b>DF_ID</b> - com.documentum.fc.common.IDfId</li> * <li><b>DF_INTEGER</b> - java.lang.Integer</li> * <li><b>DF_STRING</b> - java.lang.String</li> * <li><b>DF_TIME</b> - com.documentum.fc.common.IDfTime</li> * <li><b>DF_UNDEFINED</b> - com.documentum.fc.common.IDfValue</li> * </ul> * * Note that if the type is undefined, the given value is simply returned. * * @param value the IDfValue containing the data to extract * @return the Object value of the IDfValue */ public static Object getDfObjectValue(IDfValue value) { // Sanity check if (value == null) { return null; } // Recover the IDfValue's data type, so the correct // method can be called to extract the value. switch (value.getDataType()) { case IDfValue.DF_BOOLEAN: return value.asBoolean() ? Boolean.TRUE : Boolean.FALSE; case IDfValue.DF_DOUBLE: return new Double(value.asDouble()); case IDfValue.DF_ID: return value.asId(); case IDfValue.DF_INTEGER: return new Integer(value.asInteger()); case IDfValue.DF_STRING: return value.asString(); case IDfValue.DF_TIME: return value.asTime(); case IDfValue.DF_UNDEFINED: return value; default: return value; } }
Repeating Values
If you are processing repeating attributes, then you will want to use the appropriate
accessor method on IDfCollection to get the values. Note that the accessors for
repeating values only return one value at a time, so if you need all the attributes,
you might want to consider writing some utility methods. Here is one that I use
to read boolean values (I have omitted the other typed versions for brevity, but as
you can imagine, they look just like the boolean one).
/** * Get all the boolean values for a repeating attribute in * an IDfTypedObject. If the attribute is not repeating, this will * return an boolean[] of length 1. Else, the array will be sized * according to the length of the repeating attribute. This will * throw a DfException under any of the following conditions: * <ul> * <li>object is null</li> * <li>attr is null</li> * <li>attr does not exist in object</li> * <li>attr exists but is not of type DM_BOOLEAN</li> * </ul> * * @param object the IDfTypedObject; should not be null * @param attr the attribute name; should not be null * * @throws DfException if either of the params are <code>null</code>, * or the attr does not exist, or the attr is not of type * DM_BOOLEAN * @return the boolean[] of (repeating) attribute values in the * object */ public static boolean[] getBooleanValues(IDfTypedObject object, String attr) throws DfException { // Sanity check check(object, attr, IDfAttr.DM_BOOLEAN, "DM_BOOLEAN"); // Extract the values int length = object.getValueCount(attr); boolean[] vals = new boolean[length]; for (int i=0; i<length; ++i) { vals[i] = object.getRepeatingBoolean(attr, i); } return vals; } /** * Check the validity of an IDfTypedObject, an attribute, and the * attribute's type. This will throw a DfException under any of * the following conditions: * <ul> * <li>object is null</li> * <li>attr is null</li> * <li>attr does not exist in object</li> * <li>attr exists but is not of the specified type</li> * </ul> * * @param object the IDfTypedObject; should not be null * @param attr the attribute name; should not be null * @param type the expected IDfAttr type of the attribute * @param typeStr a description of the type in the event that an * an exception is thrown * * @throws DfException if either of the params are <code>null</code>, * or the attr does not exist, or the attr is not of correct type */ private static void check(IDfTypedObject object, String attr, int type, String typeStr) throws DfException { // Sanity check if (object == null) { throw new DfException( DfException.DM_DFC_E_BAD_VALUE, "object must not be null"); } if (attr == null) { throw new DfException( DfException.DM_DFC_E_BAD_VALUE, "attr must not be null"); } // Check for matching type if (object.getAttrDataType(attr) != type) { throw new DfException( DfException.DM_DFC_E_TYPE_MISMATCH, "attr '" + attr + "' is not type " + typeStr); } }
Cleaning Up
It is important to understand that every IDfCollection which is created by calling IDfQuery.execute()
MUST BE CLOSED, regardless of whether you process the results or not. This implies that you must
always store the IDfCollection in a variable so that it can be closed. The preferred way to close the IDfCollection is
to invoke the close()
method from a finally{}
block, which is guaranteed to be called,
even if exceptions are thrown.
Once an IDfCollection has been closed, it cannot be used again. Attempting to invoke any of the attribute
accessor methods on a closed collection will result in a DfException being thrown. You can safely determine
if an IDfCollection has been closed by calling getState()
on the collection and comparing the result
to IDfCollection.DF_CLOSED_STATE
.
Note:
I have seen code which re-uses the same IDfCollection variable in a method in order to execute multiple
DQL statements. In such a case, be sure to close the IDfCollection before using it again. For example,
the following code will leak one IDfCollection every time it is run:
public static void leakyExecute(IDfSession session) throws DfException { IDfCollection rs = null; try { // Setup a new dql query IDfQuery query = new DfQuery(); query.setDQL(someDql); // Execute the query and recover the results rs = query.execute(session, DfQuery.DF_READ_QUERY); // Loop through the results while (rs.next()) { } // Execute a new query and recover the results query.setDQL(someMoreDql); rs = query.execute(session, DfQuery.DF_READ_QUERY); // At this point, we just leaked the original collection, // since we failed to close it before re-assigning it. // Loop through the results while (rs.next()) { } } finally { // Close the IDfCollection. if (rs != null) { rs.close(); } } }
Conclusion
That wraps up our discussion on IDfCollections. As always, good luck in your Documentum
endeavors, and remember to always close those IDfCollections!