Writing great code with the IBM FileNet P8 APIs, Part 1: Hello, Document!

Getting started with your first FileNet P8 program

This article gets you started with developing a simple application, HelloDocument, with the IBM® FileNet® P8 Content API. Through a sequence of simple operations, learn to use coding patterns to perform a wide variety of your own operations. The P8 APIs are extensive, and it can be a little tricky for first-timers to know how to get started. This article gives you that start: an orientation and launchpad from which you can easily build your own applications. Even if you are an old hand at P8 development, you will certainly find useful information in this article and subsequent articles in the series. Future articles in this series go into more depth on specific topics in both the process and content APIs.

Share:

Bill Carpenter (WJCarpenter@us.ibm.com), ECM Software Architect, IBM

Bill CarpenterBill Carpenter is an ECM Architect with IBM in the Seattle, Washington area. Bill has experience in the Enterprise Content Management business since 1998 as a developer, development manager, and architect. He is co-author of the books IBM FileNet Content Manager Implementation Best Practices and Recommendations and Developing Applications with IBM FileNet P8 APIs. He has previous experience in building large software systems at Fortune 50 companies and has also served as the CTO of an Internet startup. He has been a frequent mailing list and patch contributor to several open source projects. Bill holds degrees in Mathematics and Computer Science from Rensselaer Polytechnic Institute in Troy, New York.


developerWorks Contributing author
        level

16 October 2008

Also available in Russian Vietnamese

Say Hello to HelloDocument

IBM DB2 e-kit for Database Professionals

Learn how easy it is to get trained and certified for DB2 for Linux, UNIX, and Windows with the IBM DB2 e-kit for Database Professionals. Register now, and expand your skills portfolio, or extend your DBMS vendor support to include DB2.

This article gives you an overview of a complete, self-contained IBM FileNet P8 application. P8 is an IBM platform for Enterprise Content Management (ECM). Although most real-world P8 programs are part of a larger framework like J2EE or .Net, your likely starting point as a developer is with a standalone program. By using a standalone program, you can concentrate on the P8 details without having to focus on the complexities of the larger framework.

The example HelloDocument application performs a number of tasks, some of which you might not want to know about; however, you may want to do some of the individual things in the application. Beyond that, you can use the techniques illustrated in this code and extend them to do things that more closely fit your use cases. Perhaps just as importantly, once you have HelloDocument running, you can be confident that your environment is properly configured. This allows you to then concentrate on your own application details without having lingering doubts about your environment.

To understand this article, you should have a reasonable understanding of Java™ and be able to follow along with descriptions of Java source code. This article does not describe every line of HelloDocument.java but, instead, hits the highlights that illustrate CE API points. The code is heavily commented, and if you are adventurous, you could skip this article and go right to the source code itself (see Downloads).

HelloDocument is structured as a single Java source file of 400-500 lines. Even allowing for the liberal number of comments in the source code, that is still a lot of lines just to say hello. There are some things in HelloDocument.java that you would be likely to move into separate classes or source files. They are presented all in a single place so that you can be sure you have everything. For example, the complete Java Authentication and Authorization Service (JAAS) login sequence, including an inner class that handles the callbacks, is included in the HelloDocument.java source file. That would certainly be an unusual choice for a real application. The HelloDocument class implements the PrivilegedAction interface and encapsulates most of its business logic inside a run method just to easily illustrate the explicit JAAS login model. The business logic for HelloDocument turns out to be no more than 50-100 lines.

This article was written when the current release in the field was P8 4.0.1, and P8 4.5.0 was winding down its development cycle. The code runs in either release. (This article and others in the series do not spend much time on APIs from P8 3.x. In some cases, those APIs are radically different.) Because the API classes and methods used are core to the API, they are very stable. It is quite likely that HelloDocument will run with no modifications for several P8 release cycles to come. HelloDocument is written in Java. Because the P8 Content Java API differs from the P8 Content .Net API mainly in naming conventions and other secondary considerations, it would be a straightforward transliteration job to rewrite HelloDocument in a .Net language like C#. (An exception to the similarity of the two APIs is in the area of authentication. Although this article discusses Java authentication for HelloDocument, a general discussion of authentication is outside the scope of this article.)

So, what does HelloDocument actually do? It creates a document, uploads content for it from a file, checks it in, and files it in a folder. It then reads it back and compares the downloaded content to the original file content. Optionally, you can configure the application to skip part of the file during the download and comparison. Although the creation and filing of documents is common to many use cases, the comparison steps are not. In fact, you should not ever have to verify the content that you have uploaded. These extra pieces during download are meant only to illustrate some API coding aspects. HelloDocument is designed so that you do not have to do any particular set-up in the CE before you run it (other than making sure you have access rights to create new folders and documents). Once you have it working in your environment, you can run it repeatedly without getting errors.


Configuration

Embedded configuration

HelloDocument is driven by several items of configuration. Some of these, such as the Content Engine (CE) connection Uniform Resource Identifier (URI), are common to most P8 applications. Others are specific to the HelloDocument sample. In a real application, you would almost certainly not hard-code these configuration items. You would use command line arguments, a configuration file, or some other mechanism to decouple configuration from the application code itself. For convenience, HelloDocument defines all those configuration items as constants in a static inner class named ConfigInfo, shown in abbreviated form in Listing 1. Where you see references to ConfigInfo.SOME_VALUE elsewhere in the code, they are referring to these constants.

Listing 1. Static inner class ConfigInfo
private static final class ConfigInfo
{
    // . . .

    /**
     * This URI tells us how to find the CE and what protocol to use.
     */
    static String CE_URI = "http://myCEserver:7001/wsi/FNCEWS40DIME";
    /**
     * This ObjectStore must already exist.
     */
    static String OBJECT_STORE_NAME = "MyObjectStore";

    // . . .
}

Each item in ConfigInfo has comments explaining how it is used. You should look at each item and set it to a value appropriate for your environment.

ConfigInfo

Other configuration items

Because this is an article about writing code, it does not go into much detail about how to set your Java classpath and other external configuration items. The P8 platform documentation describes how to set up a thick client environment. It is different for each brand of J2EE application server. That same part of the documentation also describes how to configure your JAAS settings. The examples and instructions in the rest of this article rely on those things being configured properly.


Get or fetch?

A Java object in the API is not literally the same thing as an object in a CE repository. It represents only a reference to that CE object through which you can examine property values, navigate by following object-valued properties (OVPs), and make various kinds of updates. The actual match-up between the API object and the CE object happens when the API makes a round-trip to the server. The number of round-trips to the server is quite often the dominant factor in performance for an application, so the API gives very fine-grained control over when round-trips actually happen and what data goes on the wire for each round-trip. HelloDocument is fairly careful about minimizing round-trips, but it is not particularly careful about minimizing the amount of data involved in the requests or responses. You can tune those payload sizes via property filters and other mechanisms. These property filters are discussed in greater detail in a future article in this series. For now, all you really need to know is that the API is designed to work without surprises if you do not use property filters. Once you get a basic understanding of the API, you will definitely want to use property filters because they can dramatically improve performance, both by reducing payload size and by combining some round-trips.

When you are instantiating a Java object, the API has a useful convention. The names of factory methods (and a few other types of methods) use the prefix get to indicate a local operation and the prefix fetch to indicate that a round-trip will be made to the CE server. (There is a third verb as well: create is used when your intent is to not only instantiate a Java object, but also a new CE object in a repository or elsewhere.) In API jargon, instantiating a Java object without a round-trip to the server is called fetchless instantiation. So, for example, conn = Factory.Connection.getConnection(ConfigInfo.CE_URI) is local. At many places in the HelloDocument source code, a comment of no R/T is used to emphasize fetchless instantiation or another operation that someone might naively think requires a round-trip. Notice that get methods are used wherever possible.

A curious thing can happen when you use fetchless instantiation. The API believes that the CE objects you reference actually exist. If you feel like it, you can tell the API a complete lie, although that is usually not very productive. The reckoning comes when a round-trip happens involving that object. At that point, the CE server methodically reconciles your object references with actual CE objects. Naturally, the creation of a CE object is a special case handled in the way you would expect. There are some other occasions (beyond the scope of this article) where it can be useful to reference CE objects that do not yet exist; they merely have to exist by the time the CE hears about them.

The bottom line is that you get a performance improvement by using fetchless instantiation, but the corresponding cost is that your application's error handling may have to deal with missing objects at a later stage. In practice, that error handling cost is not too severe.


Get to the ObjectStore

The HelloDocument.run method is where the main top-down business logic takes place. The beginning of that method, along with the instance variable for a Connection, illustrates a very common coding pattern (see Listing 2). Without a doubt, most content applications deal only with objects inside CE repositories, and most of those deal only with a single repository.

Listing 2. HelloDocument.run and friend
/**
 * All interaction with the server will make use of this Connection object.
 * Connections are actually stateless, so you don't have to worry about 
 * holding open some CE resource.
 *
 * no R/T
 */
private Connection conn = Factory.Connection.getConnection(ConfigInfo.CE_URI);
// ...
/**
 * This method contains the actual business logic.  Authentication has
 * already happened by the time we get here.
 */
public Object run()
{
    // Standard Connection -> Domain -> ObjectStore
    //no R/T
    Domain dom = Factory.Domain.getInstance(conn, null);
    //no R/T
    ObjectStore os = Factory.ObjectStore.getInstance(dom, 
                                           ConfigInfo.OBJECT_STORE_NAME);

    String containmentName = createAndFileDocument(dom, os);

    File f = new File(ConfigInfo.LOCAL_FILE_NAME);
    long fileSize = f.length();
    System.out.println("Local content size is " + fileSize + " for file " 
                                                    + ConfigInfo.LOCAL_FILE_NAME);
    long skipPoint = 0L;
    if (ConfigInfo.USE_SKIP)
    {
        long midPoint = fileSize / 2;
        // pick a random point in the second half of the content
        skipPoint =  midPoint + (long)Math.floor((Math.random() * midPoint));
    }
    System.out.println("Will skip to " + skipPoint + " of " + fileSize);

    readAndCompareContent(os, containmentName, skipPoint);
    return null;
}

The coding pattern for getting to an ObjectStore is as follows:

ObjectStore

  1. Get a Connection object.
  2. Get a Domain object.
  3. Get an ObjectStore object.

A Connection is a fairly lightweight class. It tells the API how to connect to the CE server. Because interactions between the API and the CE server are stateless from the point of view of the CE server, the Connection object is not holding open any expensive server-side resources. The main item of information held in the Connection is the URI to be used. The API deduces both the method for connecting and the location of the CE from the URI. You should notice, in particular, that the Connection object does not hold user information. It is possible to set additional configuration parameters for a Connection, but that mechanism is not described in this article.

A Domain is an object holding P8 resources at or above the ObjectStore level. To instantiate a Java Domain object, you need a Connection and a domain name. Today, a P8 installation has only a single domain, so the API allows you to pass a null for the domain name into the factory method. The name of the domain is established at P8 installation time, and if you are ever curious about it, you can look at the Domain's Name property value.

An ObjectStore object represents a CE repository. Notice that the factory method for creating an ObjectStore object does not take a Connection object. Instead, it takes a Domain object. (It is proper to say that an ObjectStore is scoped to a Domain, but this article does not say much more about scopes.) Internally, the API uses the same Connection object for the Domain and objects instantiated from it. Later, when the ObjectStore is itself used to instantiate objects, the Connection is automatically passed along to them inside the API.

This section of code is also a good place to notice the use of factory methods in general. The CE API has a large number of factory classes: roughly one per CE class. These are organized for convenience into nested classes inside the com.filenet.api.core.Factory class. To find the factory class for any particular CE class, look inside Factory for an inner class named Document or Folder or whatever CE class you are looking for. Within that inner class, you will find only a handful of methods appropriate to instantiating Java objects for that CE class. It is easy to figure out what the parameters are for these factory methods. The factory methods are type-safe in the sense that they return specific types. The Factory.Folder methods, for example, each return an object of type Folder. The idea behind the factory methods and type-safety in general is to reduce the chances for a programming error. Because you do not have to use casts, it is much more likely that the compiler will catch any problems. That is preferable to runtime type-checking.

There is another, smaller family of methods (not used much in HelloDocument) that are not type-safe. In API jargon, these are commodity methods. An example is ObjectStore.getObject, which can return objects of virtually any independent CE class. The intent of the commodity methods is for use in certain application coding patterns that deal with a wide variety of types in a generic fashion. There is usually not much point in using the commodity methods when you are dealing with specific types known ahead of time.


Create a new Document

The first set of lines inside method HelloDocument.createAndFileDocument lays the groundwork for the creation of a new document with content (see Listing 3). At the end of those lines of code, the Java Document instance is prepared with most of the actions you want to take, but the CE Document instance has not yet been created (because you have not yet made the required round-trip to the server).

Listing 3. Inside HelloDocument.createAndFileDocument, Part 1
//no R/T
ContentTransfer ct = Factory.ContentTransfer.createInstance();
ct.setCaptureSource(fis);
// optional
ct.set_RetrievalName(ConfigInfo.LOCAL_FILE_NAME);
// optional
ct.set_ContentType("application/octet-stream");

ContentElementList cel = Factory.ContentElement.createList();
cel.add(ct);

//no R/T
Document doc = Factory.Document.createInstance(os, null);
//not required
doc.getProperties().putValue("DocumentTitle", ConfigInfo.DOCUMENT_TITLE);
doc.set_ContentElements(cel);
//no R/T
doc.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, CheckinType.MAJOR_VERSION);

The sequence of operations in Listing 3 can seem a little confusing until you get to know the CE object model, especially the relationship between a Document and its content. A single piece of content (for example, a spreadsheet or a text document) resides in an object called a content element. More specifically, because the content is stored in the repository with the Document, you use a content element subclass called ContentTransfer. (The other subclass, ContentReference, is used when the actual bits are stored somewhere else.) A Document can have any number of content elements, and, because they are dependent objects in the jargon of the CE (they cannot exist on their own; they need a containing Document), the Document property ContentElements is of type ContentElementList.

Now, hopefully, things are a little clearer. You use factory methods to create a ContentTransfer object, a ContentElementList object, and a Document object. You add the ContentTransfer object to the ContentElementList object, and then you set the Document's ContentElements property value from the ContentElementList. There is some flexibility in the order of these things, so you can rearrange them in your own application if it makes more sense to you.

This code is a good illustration of the use of type-safe property accessor methods. For example, when setting the retrieval name for the content element (an optional property that serves as a file name hint for applications later downloading the content), you call ct.set_RetrievalName(ConfigInfo.LOCAL_FILE_NAME). That method only accepts a string argument, so it would be a compile-time error if you tried to use an integer value. The corresponding getter method, ContentTransfer.get_RetrievalName, is also type-safe and returns a string value. The Java API naming convention of set_ and get_ (with an underscore) acts as a signal to let you know you are dealing with a property in the CE repository sense and not merely a typical Java object field. Every system-defined property on every CE class has type-safe accessors. For properties that are inherently read-only, there is no type-safe setter method.

The setting of the DocumentTitle property looks quite a bit different from the other setter method calls: doc.getProperties().putValue("DocumentTitle", ConfigInfo.DOCUMENT_TITLE). What is going on there? Many people do not realize that DocumentTitle is not a system-defined property (from the CE server point of view, where a system-defined property is one for which the CE server creates a value or that influences CE server behavior or both). It is instead defined via an AddOn at ObjectStore creation time, and you rarely see an ObjectStore without a DocumentTitle property. However, because it is not a system-defined property, there is no type-safe accessor method for it in the APIs. Instead, you have to set its value via a commodity method.

Method Document.getProperties returns a Properties object representing the locally-cached property values for that Document, and the Properties class contains commodity methods for getting and setting property values. In any real application, you would certainly deal with custom properties, so you should make sure you understand the model for commodity property value access.

As mentioned earlier, even after the checkin method call, the Document still is not created in the CE repository. Instead, it is said to have one or more pending actions. A pending action is an internal tagging of an object with an operation that needs to be performed on the server. (There are facilities in the API for examining pending actions, but you will probably never need to do that.) When you called the factory method to create the Java Document object, it automatically tagged it with the Create pending action. When you called the checkin method, it added the Checkin pending action. It sometimes surprises people to find out that for all of the classes in the entire API, there are only eighteen types of pending action. The number is so low because many operations in the CE actually consist of normal CRUD (create, retrieve, update, delete) operations on objects and properties. Updating a property value, regardless of the property or class involved, is always done via an Update pending action or as an auxiliary to some other pending action type. You could think of pending actions as instructions to the CE server; they tell the CE server what to do with an object when it arrives on the server side.

Now look a little more closely at the checkin method call: doc.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, CheckinType.MAJOR_VERSION). It takes two arguments that are defined in separate constant classes. These constant classes for parameter values are coded with a type-safe enum pattern. Because the API was originally written for and can still operate in a Java 1.4 environment, Java language enums are not available. The type-safe enums provide their type-safety by forcing you to use constants from an appropriate list of choices. For example, the Java compiler does not allow you to accidentally reverse the order of the two arguments to the checkin method. It would result in a compile time error instead of a runtime error. In the absence of this type safety, you might get incorrect behavior with no indication of an error.


File the Document

The article returns to the instantiation of the Folder in a moment, but for now let us look at the filing of the Document into that Folder. There are methods in the API called Folder.file and Folder.unfile. Many people find it surprising that these methods are actually helper methods and not fundamental to the API. Certainly, the notion of filing in folders is a basic CE feature, but the act of filing is really one of creating a relationship object to link the Folder and the containee together. For whatever reason, this idea gives a lot of beginners a reason to pause, so Listing 4 shows how to file the Document without calling the Folder.file method.

Listing 4. Inside HelloDocument.createAndFileDocument, Part 2
Folder folder = instantiateFolder(os);
//no R/T
DynamicReferentialContainmentRelationship rcr = 
    Factory.DynamicReferentialContainmentRelationship.createInstance(os, null, 
                       AutoUniqueName.AUTO_UNIQUE, 
                       DefineSecurityParentage.DO_NOT_DEFINE_SECURITY_PARENTAGE);
rcr.set_Tail(folder);
rcr.set_Head(doc);
rcr.set_ContainmentName(ConfigInfo.CONTAINMENT_NAME);

The class DynamicReferentialContainmentRelationship (DRCR) has a long name, but creating one is easy. It links a folder to a contained document. If you think of a graphic of that relationship as an arrow pointing from the Folder to the Document, you can see why the relationship's OVP linking to the Folder is called Tail and the relationship's OVP linking to the Document is called Head.

The DRCR is dynamic because its Head property is automatically updated as the target Document is versioned. To instantiate the DRCR, call the applicable factory method, which creates a Java instance with a Create pending action, and then set a few system properties. In this case, use AutoUniqueName.AUTO_UNIQUE to instruct the CE server to modify the containment name in the case of a collision (containment names must be unique within a particular Folder). The alternative would be for the operation to fail with an exception.

DRCR is subclassable, so you might someday find it useful to create a subclass and add some metadata properties of your own. Remember that the DRCR is not yet created in the CE repository (nor, indeed, is the target Document).


Save to the repository

Now, finally, it is time to actually write things to the CE repository. It would be a simple matter to call the save methods on the Document and the DRCR, but in keeping with the theme of minimizing round-trips, save them both via an UpdatingBatch (see Listing 5).

Listing 5. Inside HelloDocument.createAndFileDocument, Part 3
UpdatingBatch ub = UpdatingBatch.createUpdatingBatchInstance(dom, 
                                                           RefreshMode.REFRESH);
ub.add(doc, null);
ub.add(rcr, null);
System.out.println("Doing updates via UpdatingBatch");
ub.updateBatch();

Using an UpdatingBatch is conceptually straightforward if you think of it as a bucket for carrying things to the CE server. You create the UpdatingBatch instance, add objects to it, and then call the updateBatch method to send everything to the server. Of course, it only makes sense to add objects that have changes to be saved to the CE repository. Besides minimizing round-trips, an UpdatingBatch has another important characteristic. Everything in an UpdatingBatch happens as an atomic transaction on the server. Either it all succeeds or it all fails. That does not matter for HelloDocument, but it is not unusual to find real use cases that need that transactional behavior. UpdatingBatch is a good way to get it at a reasonable performance cost.

When we created the UpdatingBatch, we supplied an argument to tell the API to return refreshed object instances from the round-trip to the server. Quite often, you will not need to refresh the objects (or you will use property filters to limit the size of the refresh), but in this case we wanted to use the containment name to illustrate something else in HelloDocument. Since we instructed the server to automatically pick a unique containment name, it might be different from the one we used with the DRCR. The refreshed property value tells us what the CE server actually used.


Instantiate the Folder

We skipped over the instantiation of the Folder object, so let us return to that now. Listing 6 shows the HelloDocument.instatiateFolder.

Listing 6. HelloDocument.instantiateFolder
private Folder instantiateFolder(ObjectStore os)
{
    Folder folder = null;
    try
    {
        //no R/T
        folder = Factory.Folder.createInstance(os, null);
        //no R/T
        Folder rootFolder = Factory.Folder.getInstance(os, null, "/");
        folder.set_Parent(rootFolder);
        folder.set_FolderName(ConfigInfo.FOLDER_NAME);
        //R/T
        folder.save(RefreshMode.NO_REFRESH);
    }
    catch (EngineRuntimeException ere)
    {
        // Create failed.  See if it's because the folder exists.
        ExceptionCode code = ere.getExceptionCode();
        if (code != ExceptionCode.E_NOT_UNIQUE)
        {
            throw ere;
        }
        System.out.println("Folder already exists: /" + ConfigInfo.FOLDER_NAME);
        //no R/T
        folder = Factory.Folder.getInstance(os, null, "/" + ConfigInfo.FOLDER_NAME);
    }
    return folder;
}

The implementation of the method instantiateFolder is a little bit artificial because we wanted to make it painless to run HelloDocument multiple times and without any prior set-up. The choice we made was to try to create the Folder and fall back to a fetchless instantiation if that fails. You probably would not do that in a real application because you would be doing it the inefficient way almost all of the time. As an alternative, you could do a real fetch of the Folder and fall back to creating it if it did not exist. That still costs a round-trip to check on the existence of the Folder (which your application probably knows exists most of the time). So, probably the best solution, in terms of performance, is to fetchlessly instantiate the Folder on the assumption that it exists in the repository. That does mean that you would need to adjust your error handling in later code to account for the possibility that it actually does not exist. (We did not want to clutter this example with that complication in the error handling code.)


Read the content

This article does not spend much time describing the reading of the content from the repository since most of the logic is just vanilla Java stream handling. Instead, we'll just highlight a few points starting with Listing 7.

Listing 7. Inside HelloDocument.readAndCompareContent
String fullPath = "/" + ConfigInfo.FOLDER_NAME + "/" + containmentName;
System.out.println("Document: " + fullPath);
//no R/T
Document doc = Factory.Document.getInstance(os, null, fullPath);
//R/T
doc.refresh(new String[] {PropertyNames.CONTENT_ELEMENTS});
InputStream str = doc.accessContentStream(0);

Just to show that it can be done, a full path is constructed to the Document from configuration values and the actual containment name returned from the CE server. You can just as easily get the ID value for the created Document from the refresh and use that ID to instantiate the Document. (It is slightly more efficient to use the ID than it is to use the path since the CE server has to convert the path to an ID anyhow.)

The Document is fetchlessly instantiated via a factory method and then a refresh method is immediately called to obtain the value of the ContentElements property. In this instance, it is done mainly to show the refresh call. Instantiating the Document via a factory fetchInstance creates the same performance efficiency, especially if an appropriate property filter were used to limit the data fetched. The Document.accessContentStream method is a convenience method for calling accessContentStream on the applicable ContentTransfer dependent object.


Authentication

This section returns to an earlier part of the HelloDocument that was glossed over. Although this article does not go into great detail about authentication, it does describe what the authentication code in HelloDocument is doing.

In the CE model, authentication is completely delegated to JAAS. That means that you never really log on to the CE API or the CE. Instead, the CE API expects you to have performed a JAAS login sequence before you make any CE calls. An advantage of this approach is that the CE API does not need to have coded into it the various methods you might use to authenticate. With the pluggable architecture of JAAS, you can use traditional userid/password schemes, fingerprint readers, handheld authenticator gadgets, and many other techniques. No matter how you authenticate, your CE API application code remains the same.

Authentication with CE API helper methods

Look at Listing 8 and notice the top-level authentication-related code from HelloDocument.main. The article returns to the topic of the loginAndRun method, but for now take a look at the else clause.

The CE API UserContext class has a few convenience methods related to authentication. These are provided strictly to make it easy to do authentication in a legacy system that is limited to a traditional userid/password scheme. Use the UserContext.createSubject method to create an instance of a JAAS Subject. The UserContext object also can maintain a stack of JAAS Subjects, where the topmost Subject is actually used for CE round-trips. The UserContext.pushSubject and popSubject methods implement a standard stack paradigm. The stack itself is stored in thread-local storage, which means it is associated with a particular thread of execution. Notice that the popSubject call is inside a finally block to make sure it is called no matter what else happens. That is very important. Although it does not matter much for the standalone HelloDocument application, it does matter in J2EE applications where thread pools are typically used to service requests. You never know where you might end up reusing code, so you should be in the habit of coding it the correct way. You want to clean up the authentication context for a thread after you are done making CE calls.

Listing 8. HelloDocument authentication methods
public static void main(String[] args) throws LoginException 
{
    System.out.println("CE is at " + ConfigInfo.CE_URI);
    System.out.println("ObjectStore is " + ConfigInfo.OBJECT_STORE_NAME);
    HelloDocument fd = new HelloDocument();
    if (ConfigInfo.USE_EXPLICIT_JAAS_LOGIN)
    {
        loginAndRun(fd, ConfigInfo.USERID, ConfigInfo.PASSWORD);
    }
    else
    {
        // This is the standard Subject push/pop model for the helper methods.
        Subject subject = UserContext.createSubject(fd.conn, ConfigInfo.USERID, 
                                  ConfigInfo.PASSWORD, ConfigInfo.JAAS_STANZA_NAME);
        UserContext.get().pushSubject(subject);
        try
        {
            fd.run();
        }
        finally
        {
            UserContext.get().popSubject();
        }
    }
}

Standard JAAS authentication

What happens if you do not use UserContext.pushSubject to activate a Subject for use in CE round-trips? In that case, the CE API internals look for an ambient JAAS Subject. The term ambient here means a Subject that has been associated with the thread by standard JAAS mechanisms. For example, in a Web application you might do a login to the J2EE web container. In HelloDocument, the login is done explicitly as part of the loginAndRun method.

Listing 9. loginAndRun
private static final void loginAndRun(HelloDocument fd, String userid, 
                                              String password) throws LoginException
{
    LoginContext lc = new LoginContext(ConfigInfo.JAAS_STANZA_NAME, 
                                new HelloDocument.CallbackHandler(userid, password));
    lc.login();
    Subject subject = lc.getSubject();
    Subject.doAs(subject, fd);
}

As you can see in Listing 9, the loginAndRun method is pretty simple. It implements a standard JAAS login sequence, though this listing does not show the implementation of inner class HelloDocument.CallbackHandler because it is also standard JAAS fare. Unfortunately, there is a sleight deception in this simplicity. The Subject.doAs method call in last line of Listing 9 turns out to be specific to each J2EE application server. It is a detail not fully addressed in the J2EE specifications, so each vendor had to implement it as they saw fit. It is likely that the J2EE specifications will eventually resolve this so that you will not always need to have vendor-specific logic in your login calls. Of course, with a true J2EE client instead of a thick client, you would more than likely be doing your authentication with a J2EE container and letting the container handle the details.


Conclusion

This article walked you through the complete HelloDocument application. You saw how to get connected to an ObjectStore, how to create Folder, Document, and other types of objects, how to read and set property values, and how to upload and download content. Although these things together still make a fairly simple application, they introduce coding patterns that can be used for a great many things.

If you are just starting out with writing P8 code, you can use HelloDocument as a starting point. The complete source file is available for download via a link accompanying this article. You can experiment by changing it to try various things. In fact, if you have in mind a small application that you would like to turn out quickly, you would not be the first person to build it on top of a modified copy of HelloDocument.java. Go for it!

Have fun, and stay tuned for more articles in this series!


Download

DescriptionNameSize
Java source code for this articleHelloDocument.zip6KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Information management on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Information Management, Java technology
ArticleID=345856
ArticleTitle=Writing great code with the IBM FileNet P8 APIs, Part 1: Hello, Document!
publish-date=10162008