TBOs – Part 3

In the last post I showed you a simple skeleton for implementing a TBO.  In this post I will flesh-out the doSave() method with some simple logic to do metadata inheritance.  For some reason this has been a very popular topic lately in the discussion forums.  I hope when we are done here you will see this is pretty easy to implement.

The scenario for this TBO is that a case is implemented as a case_folder object (a subtype of dm_folder).  case_folder has a custom attribute named case_number.  Each case_document (subtype of dm_document) imported or created in a case_folder must inherit the case_number from the case_folder.  So, whenever the case_document is saved, the TBO will get the case_number from the containing case_folder and apply it to the case_document.

 * Case Document TBO
 * (C) 2010 M. Scott Roth / dm_misc

public void doSave(boolean saveLock, String versionLabel, Object[] extendedArgs) throws DfException {
    DfLogger.info(this, "ENTER: doSave method for obj id {0}, obj name {1}",
                      new String [] {this.getObjectId().toString(),
                      this.getObjectName() },

    // #1 get the containing case folder
    IDfFolder caseFolder = findCaseFolder(this);

    // #2 inherit the case number from the case folder
    if (caseFolder != null) {
         this.setString("case_number", caseFolder.getString("case_number"));
         DfLogger.info(this, "doSave: inherited case number: {0}",
                           new String [] {this.getString("case_number") },
    } else {
         DfLogger.error(this,"doSave: Could not find containing case folder for {0}",
                            new String [] {this.getObjectName() },

         // #3 throw exception back to client
         throw new DfException ("This case document is not saved in a case folder.");

    // #4 call super to do actual save
    super.doSave(saveLock, versionLabel, extendedArgs);

    DfLogger.info(this, "EXIT: doSave method for obj id {0}, obj name {1}",
                      new String [] {this.getObjectId().toString(),
                      this.getObjectName() },

 private IDfFolder findCaseFolder(IDfSysObject sObj) throws DfException {
        IDfFolder folder = null;

        // #5 loop through folder path
        int cnt = sObj.getValueCount("i_ancestor_id");

        for (int i=0; i<cnt; i++) {
            IDfFolder fld = (IDfFolder) sObj.getSession().getObject(sObj.getId("i_ancestor_id"));

            if (fld.getTypeName().equalsIgnoreCase("case_folder")) {

                DfLogger.debug(sObj, "findCaseFolder: {0} is case_folder",
                                     new String [] {fld.getFolderPath(0)},

                // # 6 found case folder
                folder = fld;

            } else {
                DfLogger.debug(sObj, "findCaseFolder: {0} is NOT a case_folder; looping",
                                     new String [] {fld.getObjectName()},
        return folder;

Code Notes:

  1. Find this document’s containing folder (or parent). This is accomplished using the findParentFolder() method described below.
  2. If the parent of this document is a case_folder, findParentFolder() will return it as an IDfFolder, else it will return null.  If a valid folder is returned, simply set this document’s case_number attribute equal to that of the parent folder.
  3. If a parent case_folder can’t be found, throw an exception.  Throwing an exception in the TBO will bubble it all the way up to the UI (Webtop) and abort the save.  You could take a less dramatic action here instead if you don’t want exceptions popping up in Webtop.
  4. If all is well with the case_number and the code reaches here, call the super.doSave() to actually save the document and metadata to the repository.
  5. This is the findCaseFolder() method.  Loop through all the folders in the i_ancestor_id attribute and test if each one is a case_folder.
  6. If a case_folder is found, return it and break the loop.

A snippet from the log4j file shows that the TBO is working.

17:27:35,355  INFO [http-8080-1] com.dm_misc.tbo.CaseDocumentTBO - ENTER: doSave method for obj id 090000018000ad67, obj name case doc 2
17:27:35,355 DEBUG [http-8080-1] com.dm_misc.tbo.CaseDocumentTBO - findCaseFolder: Background is NOT a case_folder; looping
17:27:35,355 DEBUG [http-8080-1] com.dm_misc.tbo.CaseDocumentTBO - findCaseFolder: /Case Files/Cases 2010/Case 123 is case_folder
17:27:35,355  INFO [http-8080-1] com.dm_misc.tbo.CaseDocumentTBO - doSave: inherited case number: 123
17:27:35,402  INFO [http-8080-1] com.dm_misc.tbo.CaseDocumentTBO - EXIT: doSave method for obj id 090000018000ad67, obj name case doc 2

This idea of metadata inheritance is pretty simple and can be extended to any number of attributes or folder ancestry.  It could even be extended to work with virtual documents.  In the next post I will show you how to override the onDelete() method to log each deletion and prevent accidental deletes (another popular request).

Note:  all of the code discussed in this series can be downloaded here.


About Scott
I have been implementing Documentum solutions since 1997. In 2005, I published a book about developing Documentum solutions for the Documentum Desktop Client (ISBN 0595339689). In 2010, I began this blog as a record of interesting and (hopefully) helpful bits of information related to Documentum, and as a creative outlet.

6 Responses to TBOs – Part 3

  1. John W says:

    I have been intending to write you something about unit testing TBOs, but this is a GREAT post for me to respond.

    To unit test this, that “findCaseFolder()” requires data that is only available if this is running against an active object in Documentum. While you could test an object in the system and write a unit test to call save() that could invoke other logic written into a doSave() extension. To properly unit test, we would want to invoke findCaseFolder() directly.

    Therefore, change that method to accept an IDfSysObject or IDfId. You can then use that as either the IDfSysObject or IDfId of the current object (so pass in ‘this’ or ‘this.getId()’) or of the folder.

    With that change, you can then write a unit test that will test findCaseFolder() with a test object of your choosing.

    Now… which object should it test? A practice I adopted was to setup a configuration file for my JUnit tests. When I put that in an SCS (like Subversion) I would put it in as an examples file. For example, CaseFolder.EXAMPLES.properties. Each developer would then make a non-EXAMPLES file (e.g., CaseFolder.properties) based on the example file and tune it for their local testing. That way we could all test on different objects without constantly updating the prop configuration in SVN. Additionally, if new data was added to the examples file, that shows up with a sync.

    Finally, with JUnit4, you can use the @BeforeClass annotation to load the properties file for all test cases in the file to read. That way you read in the necessary information to retrieve the object you want to test and can pass it into findCaseFolder() as whichever type you choose.

    I hope that helps. Let me know if I can clarify any of that.


    • scott says:

      John, thanks for the comment. This is excellent advice for using JUnit with TBOs! I will use your advice in future posts and revise this one to follow your comment also. If I get good at it, maybe I’ll post some JUnit tests at the end of this series. Thanks again!



  2. Pingback: TBOs – Part 2 « dm_misc: Miscellaneous Documentum Tidbits and Information

  3. Rob says:

    Hi Scott,

    Thanks for the code. I don’t see where the code is actually saving the metadata to the custom document type called “case document.”




  4. Pingback: D2 Overview – Part III « dm_misc: Miscellaneous Documentum Tidbits and Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: