fbpx

Session Management in DFC 5.x and WDK

Introduction

Session management is an integral aspect of most client-server toolsets. Connections to remote machines are expensive to setup and teardown and operations over client-server connections are typically expensive. As a result it makes a lot of sense to limit the number of times a connection need be made and to perform data caching wherever possible to reduce communication overhead. This “extended” connection is modeled in most systems using sessions. The concept of sessions have existed in the Documentum client libraries starting with the basic DMCL and continues forth in each layer built on top of DMCL (including DFC).

Sessions in Documentum are a valuable and scarce resource. Each session requires memory on the client and the server and therefore there are a limited number of them. Historically this hasn’t been a very large issue as clients were typically single-user native applications. With the proliferation of content-based web applications, however, much larger numbers of users are connecting to docbases with each web application server servicing hundreds or thousands of users. Allocating a session to each of these users is an inefficient use of precious resources. The changes to session management in DFC 5.x aim to provide a mechanism for managing sessions that is convenient to the user of the client library while alleviating some of the burden on the server.

The DFC 4.x Mechanisms for Session Management

In DFC 4.x, session lifetime is very long. For single-user native applications this is not typically an issue. The user acquires a session and only after it has gone unused for a (relatively) long time is it collected to make system resources available for another user. A reference to the session object is typically held globally for the application. For web applications this isn’t acceptable. With tens or hundreds of users connecting to a web application server in a short period of time, system resources were rapidly exhausted. In most cases this isn’t necessary as each of these users is performing docbase activity for only brief moments (during the processing of their HTTP request).

The following example code shows a brief example of how a session (IDfSession) is acquired and used to retrieve an object from the docbase in DFC 4.x.

                
import com.documentum.fc.client.DfClient;
import com.documentum.fc.client.IDfClient;
import com.documentum.fc.client.IDfDocument;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLoginInfo;
import com.documentum.fc.common.IDfLoginInfo;

....

try {

	IDfClient dfClient = new DfClient();
	IDfLoginInfo loginInfo = new DfLoginInfo();
	loginInfo.setUser("myUserName");
	loginInfo.setDomain("myDomain");
	loginInfo.setPassword("myPassword");
	IDfSession dfSession = dfClient.newSession("myDocbase", loginInfo);

	IDfDocument myDocument = (IDfDocument) dfSession.getObjectByPath("/My Cabinet/My Document");

} catch (DfException dfe) {
	// log the error and/or do something useful
}

            

In the example, the client and the session are local to the block of code. Typically, they would be maintained globally to the application or in the case of a J2EE web application probably maintained in the HttpSession. Documentum provided no means for handling the sessions more efficiently.

Session Management in DFC 5.x

The IDfSessionManager

DFC 5.x introduced a way to more efficiently manage sessions. In order to do this, a session management abstraction was necessary and Documentum introduced the IDfSessionManager to fill this void.

The most important aspects of the IDfSessionManager are its facilities to act as an IDfSession factory and its ability to reclaim the sessions that it manufactures. This is accomplished through the methods newSession and release.

The example below is the equivalent (sanctioned) way to acquire a session and retrieve an object from the docbase using this session. The code above for DFC 4.x continues to work in DFC 5.x (ie DFC 5.x is backward compatible with respect to sessions) but this example demonstrates the correct way to do this in DFC 5.x.

                
import com.documentum.fc.client.DfClient;
import com.documentum.fc.client.IDfClient;
import com.documentum.fc.client.IDfDocument;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSessionManager;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLoginInfo;
import com.documentum.fc.common.IDfLoginInfo;

...

IDfClient dfClient = null;
IDfSessionManager dfSessionManager = null;
IDfSession dfSession = null;
try {
	dfClient = new DfClient();
	dfSessionManager = dfClient.newSessionManager();

	IDfLoginInfo loginInfo = new DfLoginInfo();
	loginInfo.setUser("myUserName");
	loginInfo.setDomain("myDomain");
	loginInfo.setPassword("myPassword");
	dfSessionManager.setIdentity("myDocbase", loginInfo);

	dfSession = dfSessionManager.newSession("myDocbase");

	IDfDocument myDocument = (IDfDocument) dfSession.getObjectByPath("/My Cabinet/My Document");

} catch (DfException dfe) {
	// log the error and/or do something useful
} finally {
	if (dfSession != null) dfSessionManager.release(dfSession);
}

            

There are three differences between the DFC 5.x example and the previous DFC 4.x example.

  1. Authentication information
    is set on the IDfSessionManager before the session is acquired by calling setIdentity.
  2. Acquisition of a session (via newSession) requires only the name of the docbase.
  3. The IDfSessionManager is used
    to release the session at the end of the example.

We’ll look at each of these in a bit more detail in the following sections.

Note:
Although it is possible to use sessions as they were used in DFC 4.x, it is highly recommended to utilize the more
efficient mechanisms provided in DFC 5.x. Mixing session management techniques in the same application is cause
for heart ache. Don’t do it.

IDfSessionManager.setIdentity()

The session manager maintains a mapping of docbase name to authentication information. Sessions are acquired much more
frequently in DFC 5.x than they were in previous versions since sessions are only kept for as long as they are needed
to perform a specific operation. In the past, a reference to the session was kept for the duration of a running program
so providing authentication information when acquiring the session was quite natural. With frequent session acquisition
(which is promoted with the new session management techniques in DFC 5.x), it would be quite burdensome to have to provide
this information each time a session is acquired.

Each docbase to which the application connects may require different authentication information.
IDfSessionManager.setIdentity() may be called multiple times to build the map of docbase name to authentication
information.

It’s often the case for an individual to have the same authentication information for multiple docbases within an organization.
A default set of authentication information may be provided by specifying the docbase name as "*".
When acquiring a new session, the specified docbase name is used to look up authentication information in the map.
If no matching authentication information is found, the session manager, looks for an entry for the docbase “*”
and if found, attempts to acquire a session using that authentication information.

This is shown in the following example.

                
...

dfSessionManager = dfClient.newSessionManager();

// Set the default login info
IDfLoginInfo loginInfo = new DfLoginInfo();
loginInfo.setUser("defaultUserName");
loginInfo.setDomain("defaultDomain");
loginInfo.setPassword("defaultPassword");
dfSessionManager.setIdentity("*", loginInfo);

// Set the login info for docbase "myDocbase"
loginInfo = new DfLoginInfo();
loginInfo.setUser("myDocbaseUserName");
loginInfo.setDomain("myDocbaseDomain");
loginInfo.setPassword("myDocbasePassword");
dfSessionManager.setIdentity("myDocbase", loginInfo);

// uses the specific login info provided for myDocbase
dfSession = dfSessionManager.newSession("myDocbase");

// uses the default login info provided for "*"
dfSession = dfSessionManager.newSession("otherDocbase");

...

            

The astute observer may have noticed that in order to acquire a session using newSession, you must specify
the name of a docbase. Typically, the session manager is maintained globally in an application (or in the HttpSession
in a web application). Along with the session manager, the application developer will need to maintain the name of the
docbase to which the application is connecting. In most applications this would be managed in the same manner that the
session manager is managed (global or in the HttpSession).

If the docbase name is not conveniently available in a “global” area it will need to be provided along with the session
manager in order to acquire a session. It’s not unusual to pass a session manager as a method argument. This might
happen if you are calling a piece of code from your web application that is not web application aware. In this case,
you wouldn’t want the non-web application aware code to attempt to retrieve the session manager from the HttpSession
so you would probably pass the session manager as an argument. Remember in cases like this, that you must also pass
the name of the docbase.

Note:
In the author’s experience, when developing interface specifications for services
called from web applications, the docbase name is often forgotten from the method signatures.

IDfSessionManager.release()

The pattern of session usage is quite different in DFC 5.x than in previous versions. It follows three steps:

  1. Acquire session
  2. Perform operation
  3. Release session (in a finally block)

With this pattern, sessions are released frequently. DFC utilizes session pooling to make the acquisition and release
of sessions inexpensive but in order for session pooling to work correctly, sessions must always be released back to the
pool. It is important to always release the session in a finally block to prevent an unexpected Exception
or Error from leaking a session.

Note:
IDfSessionManager.release() throws a java.lang.NullPointerException if
you attempt to release a null session so it’s important to always check to make sure that the session is not null before
you release it.

                
...

IDfSession dfSession = null;
try {
	// Step 1: Acquire session
	dfSession = dfSessionManager.newSession("myDocbase");

	// Step 2: Perform operation
	// Do some work using dfSession

} catch (DfException dfe) {
	// log the error and/or do something useful
} finally {
	// Step 3: Release session (in finally)
	if (dfSession != null) dfSessionManager.release(dfSession);
}

            

Objects and Sessions

With all of this session acquisition and release, how do I hang on to docbase objects if I need them for a long time?
The simple answer is not to do that. With one exception, you must always have a valid session to use an object
acquired from that session. Releasing a session makes that session invalid. So if you get an object from a session
and then release that session, the object you have is no longer valid.

That all seems simple enough but it’s easy to get lazy and hang on to a reference to an object that you shouldn’t be
using anymore. This is especially the case if you’re used to keeping long-term references to objects since that’s
perfectly acceptable in DFC 4.x and before. The trickiest aspect of this deviant coding behavior is that your application
may run perfectly fine written in this incorrect style.

Thankfully, there’s a way to force this bad style to break so you can catch it before it catches you. Setting the
DebugSessionManager Java system property (provide -DDebugSessionManager as an argument when starting your VM) forces
sessions to become invalid when they are released. Running with this flag is much less efficient because DFC can’t
reuse sessions (which it normally does and is the reason the incorrect code you wrote still worked) so you don’t want
to use this switch on a production system. It is highly recommended to always use this switch on development platforms,
however, so you will find problems early on with the way sessions are being used.

As the author of this article it’s easy to tell you not to hang on to object references for long periods of time.
As a DFC developer, it’s often not quite that easy. It’s very common to need to refer to an object over and over
throughout the lifetime of your application.

Consider a simple web application that enables the updating of object attributes. The application validates
the information and sets the attributes on the object. If a validation fails (trying to set a string on a numeric
attribute for example), the application should display this failure to the user.
The user may set attributes a couple of times before she successfully saves the changes.

If you have been reading between the lines a little, you have
probably realized that to implement this simple little application, you need to maintain state across HTTP requests.
Coming from a DFC 4.x world, your first though may be to get a reference to the object when the application
initializes and keep a reference to it across HTTP requests. This isn’t a valid approach in DFC 5.x because you
should be acquiring the session via a session manager and releasing it (at the very least) at the end of each HTTP
request. As described above, a reference to an object obtained using one of these “transient” sessions is not valid
once the session has been released.

The application developer must therefore retrieve the object on each request. Typically this would be done via object ID.
Each time you retrieve an object this way from a new session, the information is loaded from the docbase. Any
state set on the object during a previous HTTP request which was not saved (via IDfPersistentObject.save()
or IDfSysObject.checkin()) will not exist on the newly retrieved object.

This makes the application somewhat more complicated to write than in the DFC 4.x world in which you can use the DFC object
to store state that hasn’t been saved across HTTP requests. This inconvenience is the price you pay for better
scalability.

IDfTypedObject.setSessionManager()

This poorly named method provides an optional solution to the transient state problem described above. This method dissociates
a DFC object from its session. This method copies all of the state associated with the underlying DMCL object into
the Java object (DFC usually operates in a pass-through mode in which all methods are delegated to the underlying
DMCL object). The DFC object is now valid without a valid session. When an operation is performed which requires
a session (such as save()), the DFC object uses the associated session manager to acquire a session and perform the
operation.

Note:
This is an expensive operation as it copies all state to the Java object. The method has a peculiar name because it
was never really intended to be a feature of DFC. It was added as a convenience for an internal Documentum application
developer and ended up becoming part of the published DFC API. Whether to manage an application-specific state mechanism
across calls or to use the dissociative IDfTypedObject.setSessionManager() method depends on what the
application is doing. In many cases, it’s probably fine to use IDfTypedObject.setSessionManager().
The author typically shies from this approach in lieu of a more application-specific state management technique
based upon the recommendation of the DFC development team.

WDK Session Mangement

The changes to session management in DFC 5.x were made primarily to improve the scalability of web applications supporting
hundreds or thousands of concurrent users. The development of the WDK application toolkit was the primary driver for
this feature and as such WDK makes use of the new DFC 5.x session management features. The specifics of what is going on
inside WDK are hidden from the application developer by a couple of simple-to-use interfaces but it’s often useful to
understand what is going on under the covers in order to make sense of what is happening in your application.

WDK bahaves just as the theoretical web application that was described
earlier. An IDfSessionManager is stored in the HttpSession. Although the session manager
is not meant to be retieved from the HttpSession directly by the user (it should be retrieved via the
com.documentum.web.formext.session.SessionManagerHttpBinding interface), it works just like the simple
web application described above. If a session is needed for an HTTP request, it is acquired from the
IDfSessionManager and released at the end of the request (unless it is acquired for the entire session
as we’ll see in Component.getDfSession).

com.documentum.web.formext.session.SessionManagerHttpBinding

SessionManagerHttpBinding is the mechanism used to retrieve the session manager in a WDK application. In components
there is a convenience method as we’ll see below but in actions or other non-component WDK code,
SessionManagerHttpBinding is the mechanism to get the session manager and the docbase name which are used
to acquire a session.

Note:
Remember that the session acquired using the session manager obtained from SessionManagerHttpBinding.getSessionManager()
must be released back to the session manager as soon as possible. Do not retain it across Http requests.

You may have noticed that SessionManagerHttpBinding has static methods for obtaining the session manager and the
docbase name. You may have also noticed that these methods do not take an HttpSession as an argument.
That’s a convenience since you may not have access to an HttpSession (such as from within a WDK action).
Behind the scenes, the SessionManagerHttpBinding is pulling the session manager from the HttpSession. It accomplishes
this by mapping the current thread to a particular HTTP request which it uses to get the HttpSession.

This solves the common case nicely but it is problematic if you are executing multi-threaded code within your
request processing code (unusual) as the SessionManagerHttpBinding only works for the request servicing thread.
This isn’t typically an issue and you can always obtain the session manager using the SessionManagerHttpBinding
and pass it to any thread you spawn but there’s nothing in the interface that indicates that you need to do this
so it’s something to be aware of.

Note:
The author has also run across code that was originally written for use in a WDK application which later migrated
to a non-WDK environment (ie. into a docbase method running in the method server). This code which
was attempting to obtain a session manager using the SessionManagerHttpBinding obviously failed but there is nothing in the interface
that requires an HttpSession so it’s possible to unknowingly use SessionManagerHttpBinding to retrieve a session
manager from an HttpSession that is non-existent (of course it fails at runtime but the code will compile).
You wouldn’t normally have WDK libraries on your method server but the author has seen it done so it’s something
to watch out for. As a rule, obtain your session manager as close to components, actions, etc as possible and pass
it on to other code rather than having that other code use the SessionManagerHttpBinding.

Component.getDfSession()

Much of the code written in WDK applications is in the implementation of components. And here, more than any other
place is where sessions are needed. Conveniently, com.documentum.web.formext.component.Component provides
Component.getDfSession() and Component.getDfSession(int sessionLifetime) to facilitate acquiring
a session. Internally to Component, the session manager and docbase name is obtained using the
SessionManagerHttpBinding class. As an added bonus, sessions obtained via calls to
Component.getDfSession() are automatically released when the component completes rendering so there is no
need to explicitly release the session. Sweet!

Of course that convenience comes at the expense of obscuring what is happening behind the scenes so it’s important to
remember when calling the Component.getDfSession() methods that you must not hang on to objects obtained
from those sessions for longer than the session’s lifetime.

Session’s lifetime? The authors of WDK realized that it is not uncommon for application developers to want to
get a session for longer than a request. Component.getDfSession(int sessionLifetime) may take
Component.REQUEST_LIFETIME or Component.COMPONENT_LIFETIME as an argument.
REQUEST_LIFETIME is just what you would expect and is how you would typically write a web application.
The session is good for that request only at which time it is released. This is the most efficient mechanism but
is unfortunately not the default behavior.

COMPONENT_LIFETIME stores the session on the component and it is not released until the component’s lifetime
ends (which could be a long time). This has the added benefit of allowing the application developer to store references
to objects obtained from that session on the component and utilize them across requests but that’s a heavy price to pay
indeed. Consider managing your own state or using IDfTypedObject.setSessionManager if you need to do this
because sessions are very expensive in a multi-user web application environment.

Note:
Component.getDfSession()
calls Component.getDfSession(Component.COMPONENT_LIFETIME) so always use Component.getDfSession(Component.REQUEST_LIFETIME) whenever possible. The convenience method in this case is
convenient indeed, but leads to inefficient code. Specifying the extra argument is easy to forget but the little bit of extra
thought required to get a request-scoped session will be effort well spent when it comes time to performance test and
tune your application.

Conclusion

This article covered the basics of session management in DFC 5.x and how those changes are exposed for use in the WDK toolkit.
Follow these simple rules and your applications will behave correctly and be far more scalable than in DFC 4.x:

  • Always release your sessions (in a finally)
  • Don’t use objects obtained from a session which has been released
  • Use the DebugSessionManager Java system property in development environments
  • Always call Component.getDfSession(Component.REQUEST_LIFETIME) whenever possible instead of
    Component.getDfSession()

Like this article?

Share on facebook
Share on Facebook
Share on twitter
Share on Twitter
Share on linkedin
Share on Linkdin
Share on pinterest
Share on Pinterest

Leave a comment