Print Header

Related Topics

Related Case Studies

Contact Blue Fish

Blue Fish Development Group
701 Brazos St. #700
Austin, TX 78701
(512) 469-9300

Using DFC Collections

January 1, 2005 - Article by Bump Verde

Learn how to use the IDfCollection interface to process the results of DQL statement executions.

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 the execute() 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!

1 Votes | Average: 5 out of 51 Votes | Average: 5 out of 51 Votes | Average: 5 out of 51 Votes | Average: 5 out of 51 Votes | Average: 5 out of 5 (1 votes, average: 5 out of 5)
Loading ... Loading ...

Comment on this article:

You must be logged in to post a comment.

Notification

Subscribe to our newsletter to be notified when new articles are posted. You can unsubscribe at any time.