Previous     Contents     Index     DocHome     Next     
iPlanet Application Server 6.0 Programmer's Guide (C++)



Chapter 8   Managing Session and State Information


This chapter describes sessions, which are made up of a continuous series of interactions between a user and an iPlanet Application Server application.

The following topics are included in this chapter:



What is a Session?

The term session is widely used to refer to a Web browser session, but in this manual, the term session refers more specifically to a series of user interactions that are tracked by an iPlanet Application Server application. The user's session with a Web browser or other client software might start before the iPlanet Application Server application begins tracking the user, and could continue after the application stops tracking the user.


Why Use Sessions?

You need not implement sessions if your application has no need to keep track of users or session-related data. Sessions are useful when you want to store information about each user's interaction with the application. For example:

  • Increase security by requiring the user to log in to a secured session before running certain portions of the application.

  • Record a history of which pages the user has visited during the session.

  • In an online shopping application, keep track of items in the user's shopping cart.


How Sessions Work

Each session that you track in an application has a session ID, which is typically assigned automatically by the iPlanet Application Server. The session ID enables the application to keep track of the session. Each time the user submits an action from the client, the session ID accompanies the request.

Instead of using the automatically-generated session IDs, you can take control of the session ID mechanism. For more information, see Assigning Your Own Session IDs.

Each session is associated with a set of data, such as the contents of the user's shopping cart. The session data is stored in an IGXValList object. As the session continues, this data is updated as needed by the AppLogics in the application. By using the session ID, the application is able to match up the correct data with the user session every time an AppLogic in the application needs to access the session data.

A session can also have security information associated with it. Often, the first screen in an application is a login screen. When the user clicks the Login button, an AppLogic verifies the user's security level, then starts a secured session. For more information about using sessions to secure an application, see Secure Sessions of , "Writing Secure Applications."

Avoid storing too much data in a session. Every time you save or retrieve the session-related data, the whole IGXValList object is involved. This can impact the performance of the application.

For each session, you can determine whether the session data is made available locally, within a cluster, or throughout the enterprise. You can also specify a timeout value. There are three different styles of timeout:

  • Destroy the session if the user does not interact with the application for a given number of seconds. This style of timeout is the default.

  • Destroy the session a given number of seconds from the time the session was created.

  • Destroy the session at a given date and time, specified in seconds.

For more details about how to specify distribution and timeouts, see the entry for CreateSession( ) in the iPlanet Application Server Foundation Class Reference.


Sessions and the iPlanet Application Server Foundation Class Library

The following parts of the iPlanet Application Server Foundation Class Library support sessions:

  • IGXSession2 interface

  • GXSession2 class

  • IGXSessionIDGen interface

  • Session-related functions in the GXAppLogic class

To track session information, the AppLogic code uses an instance of the IGXSession2 interface. Each time an AppLogic in the application is executed during the session, the AppLogic instantiates the IGXSession2 interface, as shown in the following illustration. This instance is a view of the actual session information. The session ID ensures that all these IGXSession2 instances in fact point to the same data.




Example
Suppose an iPlanet Application Server application is distributed to many machines. A user logs in through the login screen of the application. In response, an AppLogic runs on the iPlanet Application Server. This AppLogic creates a session, using an instance of IGXSession2, and creates the session ID. The AppLogic then sets some session-related data, and displays a main menu page to the user.

The user selects an action from the menu page. In response, another AppLogic is executed on one of the iPlanet Application Servers. This AppLogic could execute on any of the machines to which the application is distributed, depending on how load balancing has been configured for that application. The AppLogic retrieves the session that was created earlier, returning it in another instance of IGXSession2. Through this instance, the AppLogic can get the session data, which is the same data that was set earlier. The AppLogic manipulates the session data, saves it, and returns the next page to the user.

As the user continues with the application, the AppLogics in the application access the session as needed. Eventually, the user is finished with the application and logs off. In response, an AppLogic explicitly destroys the session that was created for this user. An instance of IGXSession2 is used to access the session, then the actual session data and the session ID are deleted, not just the IGXSession2 instance.

At this point, the session has ended, even though the user's session with their client software might still be active.



Starting a Session



You can start the user session at any point in the application. You can track all users from the moment they begin using the application. However, it is not necessary to track all users all the time, and doing so may be wasteful of system resources. It often makes sense to start a session only in response to a particular user action. For example:

  • The user browses through an online catalog looking at items available for sale. The first time the user asks to purchase an item, the purchasing AppLogic starts the user session to begin tracking which items the user wishes to buy.

  • An application might display information about a company, with different types of information available to different users. When a user requests to see sensitive information, such as employee records, the application requires a password from the user, then starts a secured session.

To start a session

  1. Call CreateSession( ) to create an IGXSession2 instance that refers to the session data for this application session. In the parameter list of CreateSession( ), you can specify the session timeout, distribution level, and name of the application with which the session is associated. For example:

    hr = CreateSession(GXSESSION_DISTRIB,/* distributed */

    0, /* no timeout, will be explicitly killed */

    "sessiontest", /* appname */

    NULL, /* not generating the sessionid */

    NULL, /* not generating the sessionid */

    &m_pSession); /* sess obj ref returned here */

  2. Use the GetSessionData( ) method to gain access to the IGXValList object that stores the data for this session. For example:

    hr = m_pSession->GetSessionData(&m_pVL);

  3. Use the methods of the IGXValList interface to retrieve or modify the data in the IGXValList object returned by GetSessionData( ). For example:

    GXSetValListInt(m_pVL, "execcounter", counter);

  4. If you made changes, call SetSessionData( ) to update the session data. For example:

    hr = SetSessionData(m_pVL);

  5. If you want the session to be available to other application components (servlets, for example), you must call SetSessionVisibility( ) before going on to the next step. See "Setting the Session's Visibility"Setting the Session's Visibility below for details. For example:

    hr = SetSessionVisibility("netscape.com", "/NASApp/myApp", true);

  6. Call SaveSession( ) in the GXAppLogic class to save the new data. For example:

    hr = SaveSession(NULL);

    If you call SetSessionData( ) several times before calling SaveSession( ), only the value from the last SetSessionData( ) call is saved.

    The method called SaveSession( ) exists in both the IGXSession2 interface and the GXAppLogic class. The method in the GXAppLogic class is a wrapper that calls the method in the interface, and performs some tasks that ensure that the session is accessible to future AppLogics. The SaveSession( ) method in the interface saves session data only. Therefore, be sure to call SaveSession( ) in the GXAppLogic class at least once after a session is created.


Setting the Session's Visibility

Since sessions are transmitted in a cookie, they are only available within the same URL name space where they were created, which disables their use by servlets, as servlets use a different URL addressing scheme.

The method signature for setSessionVisibility( ) is as follows:

HRESULT SetSessionVisibility(
   LPSTR domain,
   LPSTR path,
   BOOL isSecure)

domain. The domain in which the session is visible.

path. The path to which this session must be visible.

isSecure. If TRUE, the session is visible only to secure servers (HTTPS).

If you call SetSessionVisibility( ) before SaveSession( ), you can control the attributes of the session cookie to allow it to be transmitted to other name spaces, particularly for servlets. By default, the session is visible only to the URL that created the cookie. Use the path parameter to specify different URLs that will be visible. For example, the path /phoenix would match "/phoenixbird" and "/phoenix/bird.html". To make the entire server root visible, specify a path of "/", the most general value possible.

You must be part of the domain to set the domain. For example, if the domain is set to .netscape.com, then the session is visible to foo.netscape.com, bar.netscape.com, and so on. Domains must have at least two periods (.) in them.



Using an Existing Session



Once you have created and saved a session, you will need to gain access to the session data repeatedly as the user continues to interact with the application.

To gain access to an ongoing user session

  1. Call GetSession( ). Use the return value of GetSession( ) to check whether a session has already been created. If no session exists, this method will return null. For example:

    IGXSession2 *m_pSession;

    hr = GetSession(0, /* flags */

    "sessiontest", /* appname */

    NULL, /* session id gen */

    &m_pSession); /* sess object ref returned here */

  2. Use the GetSessionData( ) method to gain access to the IGXValList object that stores the data for this session. This method retrieves the contents that were last saved in the distributed store with SaveSession( ).

    hr = m_pSession->GetSessionData(&m_pVL);

  3. Use the methods of the IGXValList interface to retrieve and set the data in the IGXValList object returned by GetSessionData( ). For example:

    GXSetValListInt(m_pVL, "execcounter", counter);

  4. If you have changed the data, call SetSessionData( ) to update the session data. For example:

       hr = SetSessionData(m_pVL);

  5. Call SetSessionVisibility( ) to change or set any visibility settings, if necessary. For more information, see Setting the Session's VisibilitySetting the Session's Visibility. For example:

       hr = SetSessionVisibility("netscape.com", "/NASApp/myApp", true);

  6. Call SaveSession( ) to save the new data. For example:

       hr = SaveSession(NULL);

    If you call SetSessionData( ) several times before calling SaveSession( ), only the value from the last SetSessionData( ) call is saved.



Removing a Session and Its Related Data

If you set a timeout for the session when you create it, you need not delete the session explicitly. It will be deleted automatically when its timer expires, that is, when the user has not interacted with the session for a given length of time.

However, in order to increase security and conserve system resources, you might want to delete a session as soon as possible after the user is finished with it. For example, if your application includes a Logout button, you can delete the session when the user clicks that button.

To destroy a session

To remove a session and its related data, call DestroySession( ). For example:

HRESULT hr;

hr = DestroySession(NULL);



Example AppLogic Using Sessions



The following AppLogic code uses a session to count the number of times this AppLogic executes. This code demonstrates how to create a session, access an existing session, retrieve and update session data, and destroy a session.

STDMETHODIMP

ExecCounter::Execute()

{

   HRESULT hr;

   CHAR SessID[128];

   CHAR AppName[128];

   ULONG Timeout;

   DWORD Flags;

   CHAR msg[512];

   // If the session is already underway, get the session

   hr = GetSession(0, /* flags */

      "sessiontest", /* appname */

      NULL, /* session id gen */

      &m_pSession); /* sess object ref returned here */

   // If there is no existing session, create a new one

   if (hr != GXE_SUCCESS)

      hr = CreateSession(GXSESSION_DISTRIB,

      /* not local,not limited to cluster */

      0, /* no timeout, will be explicitly killed */

      "sessiontest", /* appname */

      NULL, /* I'm not generating the sessionid */

      NULL, /* I'm not generating the sessionid */

      &m_pSession); /* sess obj ref returned here */

   // Retrieve session data

   hr = m_pSession->GetSessionData(&m_pVL);

   // Update the count of AppLogic executions

   LONG counter = GXGetValListInt(m_pVL, "execcounter");

   if (counter != 0)

      counter++;

   else

      counter = 1;

   if (counter % 10)

   {

      // On executions 1-9, set and save the new data

      GXSetValListInt(m_pVL, "execcounter", counter);

      m_pSession->SetSessionData(&m_pVL);

      hr = SetSessionVisibility("mysite.com", "/myApp", false);

      hr = SaveSession(NULL);

   }

   else

   // Destroy the session every 10 times

   {

      hr = DestroySession(NULL);

      Log("session destroyed");

   }

   return 0;

}



Using Custom Sessions



You can customize sessions to track application-specific information in each session, such as shopping cart contents, multiple database logins, pages visited, and so on. To do so, declare a subclass of the GXSession2 class. In your subclass, you can define simple accessor methods to read and write information in the IGXValList object associated with each session.

To create a custom session class:

  1. Subclass the GXSession2 class.

  2. In your AppLogic, implement the CreateSession( ) and GetSession( ) methods to use the custom class instead of GXSession2.

  3. Pass in the IGXSession2 interface in the subclass constructor.

  4. Implement other methods as necessary to perform the custom processing you desire.


Example
This example declares a customized session class, MySession, and an AppLogic that uses the custom class.

The following code is from the header file:

// Define my own session class that inherits from GXSession2.

class MySession : public GXSession2

{

   public:

      // Constructor and destructor

      MySession(IGXSession2 *pSess);

      virtual ~MySession();

      // Declare two custom methods in this subclass

   public:

      LONG IncCounter();

      LONG GetCounter();

};

// AppLogic that keeps track of a counter but uses its own

// session class

class MySessionExecCounter : public GXAppLogic

{

   // ... constructor, destructor, and Execute() methods

   // ...

   public:

      // In order to use my own session class, I define and

      // use these methods to get and create sessions

      STDMETHOD(GetSession) (

         DWORD dwFlags,

         LPSTR pAppName,

         IGXSessionIDGen *pIDGen

      );

      STDMETHOD(CreateSession) (

         DWORD dwFlags,

         ULONG dwTimeout,

         LPSTR pAppName,

         LPSTR pSessionID,

         IGXSessionIDGen *pIDGen

      );

      // I can also define simpler versions of GetSession and

      // CreateSession if some of the arguments are well-

      // known for my application, such as appname, timeout

      STDMETHOD(GetSession) (

      );

      STDMETHOD(CreateSession) (

      );

};

The following code is from the .cpp file, showing the implementation of the custom class and the AppLogic that uses it:

MySession::MySession(IGXSession2 *pSess) :

GXSession2(pSess)

{

}// Implementation of the custom method IncCounter()

// to count the number of executions of the session AppLogic.

LONG MySession::IncCounter()

{

   IGXValList *pVL;

   LONG Counter = -1;

   GetSessionData(&pVL);

   if (pVL)

   {

      counter = GXGetValListInt(pVL, "execcounter");

      if (counter != 0)

         counter++;

      else

         counter = 1;

      // The counter is stored in the session data GXValList

      // in an item named "execcounter"

      GXSetValListInt(pVL, "execcounter", counter);

      SetSessionData(pVL);

      pVL->Release();

   }

   return counter;

}

// Implementation of the custom method GetCounter()

// to retrieve the counter.

LONG

MySession::GetCounter()

{

   IGXValList *pVL;

   LONG counter = -1;

   GetSessionData(&pVL);

   if (pVL)

   {

      counter = GXGetValListInt(pVL, "execcounter");

      if (!counter)

         counter = -1;

      pVL->Release();

   }

   return counter;

}

// Implementation of the AppLogic that uses the custom

// session class. This AppLogic counts how often it executes.

// ...

// constructor and destructor here ...

// Simple version of my GetSession, which calls the

// more complex version of my GetSession.

STDMETHODIMP

MySessionExecCounter::GetSession()

{

   return GetSession(0, "sessiontest", NULL);

}

// Simple version of my CreateSession calls the more complex

// version of my CreateSession.

STDMETHODIMP

MySessionExecCounter::CreateSession()

{

   return CreateSession(GXSESSION_DISTRIB, 0,

      "sessiontest", NULL, NULL);

}

// Complex version of my GetSession.

STDMETHODIMP

MySessionExecCounter::GetSession(DWORD dwFlags,

   LPSTR pAppName, IGXSessionIDGen *pIDGen)

{

   HRESULT hr;

   IGXSession2 *pSession = NULL;

   hr = GXAppLogic::GetSession(dwFlags, pAppName, pIDGen,

      &pSession);

// Here is where the custom class MySession is used.

   m_pSession = new MySession(pSession);

   pSession->Release();

}

// Complex version of my CreateSession.

STDMETHODIMP

MySessionExecCounter::CreateSession(DWORD dwFlags,

    ULONG dwTimeout, LPSTR pAppName, LPSTR pSessionID,

   IGXSessionIDGen *pIDGen)

{

   HRESULT hr;

   IGXSession2 *pSession = NULL;

   hr = GXAgent::CreateSession(dwFlags, dwTimeout,

        pAppName, pSessionID, pIDGen, &pSession);

// Here is where the custom class MySession is used.

   m_pSession = new MySession(pSession);

   pSession->Release();

}

STDMETHODIMP

// Main task of the AppLogic. Gets or creates the session,

// then updates the counter using custom methods from the

// custom session class.

MySessionExecCounter::Execute()

{

   HRESULT hr;

   CHAR SessID[128];

   CHAR AppName[128];

   ULONG Timeout;

   DWORD Flags;

   CHAR msg[512];

   // Check whether a session already exists

   hr = GetSession();

   // If no session exists, create one

   if (hr != GXE_SUCCESS)

   {

      hr = CreateSession();

   }

   // Update the counter to indicate the AppLogic just

   // executed one more time. Uses the IncCounter() method

   // from the custom session class.

   LONG counter = m_pSession->IncCounter();

   SaveSession(NULL);

   // Display result

   sprintf(msg, "<HTML> <BODY> Applogic execution count = %d

              </BODY></HTML>", counter);

   return Result(msg);

}


Assigning Your Own Session IDs

Normally, iPlanet Application Server automatically generates a unique ID for each session as it is created. In most situations, you will find this automatic mechanism serves your needs. However, if you prefer to assign the session IDs yourself, you can do so using one of the following techniques:

  • Implement the IGXSessionIDGen interface. This technique is illustrated in the code example below. Or,

  • Generate IDs using some programmatic mechanism, then pass the generated IDs whenever you call CreateSession( ).


Morphing Session IDs

You can change the session ID each time it is passed between the client and the iPlanet Application Server. This is called morphing the session ID. A continuously changing session ID helps to avoid unauthorized access to the session.

To morph the session ID, implement the GenerateVariantID( ) and MapToBaseID( ) methods in the IGXSessionIDGen interface. Every time you call SaveSession( ), iPlanet Application Server automatically calls GenerateVariantID( ) to create a morphed ID to be sent out to the browser. Every time you call GetSession( ), iPlanet Application Server automatically calls MapToBaseID( ) in order to match the morphed ID to the original ID and find the correct existing session.


Example
This example shows a customized session ID generation class, MySessIDGen, which generates and morphs session IDs.

The following code is from the header file:

// Class that implements the IGXSessionIDGen interface

class MySessIDGen : public IGXSessionIDGen

{

// ...

// Below are the three IGXSessionIDGen methods of interest

// in custom session ID generation and morphing.

public:

   STDMETHOD(GenerateSessID) (

      /* [in] */ DWORD dwFlags,

      /* [in] */ ULONG nSessID,

      /* [out] */ LPSTR SessID

   );

   STDMETHOD(GenerateVariantID) (

      /* [in] */ LPCSTR pBaseID,

      /* [in] */ DWORD dwFlags,

      /* [in] */ ULONG nVariantID,

      /* [out] */ LPSTR pVariantID

   );

   STDMETHOD(MapToBaseID) (

      /* [in] */ LPCSTR pVariantID,

      /* [in] */ DWORD dwFlags,

      /* [in] */ ULONG nBaseID,

      /* [out] */ LPSTR pBaseID

   );

};

The following code is from the .cpp file, showing the implementation of the custom class and an AppLogic object that uses it:

// IGXSessionIDGen methods

// GenerateSessID() creates unique session IDs

STDMETHODIMP

MySessIDGen::GenerateSessID(DWORD dwFlags, ULONG nSessID,

   LPSTR pSessID)

{

   if (!pSessID)

      return GXE_INVALID_ARG;

      // Simple scheme: just use a high resolution counter

      // as the id. This example is Solaris-specific.

      // The base session id is "<counter>"

   WORD64 HiResCounter;

   HiResCounter = gethrtime();

   CHAR id[64];

   sprintf(id, "%lld", HiResCounter);

   strncpy(pSessID, id, nSessID);

   pSessID[nSessID-1] = '\0';

   return NOERROR;

}

// GenerateVariantID() creates the morphed ID to be passed

// to the Web browser. The variant session id for

// "<counter>" will be "<newcounter>.<counter>"

STDMETHODIMP

MySessIDGen::GenerateVariantID(LPCSTR pBaseID, DWORD dwFlags,

ULONG nVariantID, LPSTR pVariantID)

{

   if (!pBaseID || !pVariantID ||

       nVariantID <= GXStrLen(pBaseID))

         return GXE_INVALID_ARG;

   CHAR id[64];

   WORD64 HiResCounter;

   HiResCounter = gethrtime();

   sprintf(id, "%lld.%s", HiResCounter, pBaseID);

   strncpy(pVariantID, id, nVariantID);

   pVariantID[nVariantID-1] = '\0';

   return NOERROR;

}

// MapToBaseID() finds the original session ID to correspond

// to the morphed one which is passed back from the Web

// browser

STDMETHODIMP

MySessIDGen::MapToBaseID(LPCSTR pVariantID, DWORD dwFlags,

ULONG nBaseID, LPSTR pBaseID)

{

   if (!pVariantID || !pBaseID)

      return GXE_INVALID_ARG;

   int founddot = 0;

   LPSTR p = strchr(pVariantID, '.');

   if (!p)

      p = pVariantID;

   else

   {*p++ = '\0';

     founddot = 1;

   }

   CHAR id[64];

   sprintf(id, "%s", p);

   strncpy(pBaseID, id, nBaseID);

   pBaseID[nBaseID-1] = '\0';

   If (founddot) *--p = '.';

      return NOERROR;

}

// AppLogic that uses the custom ID generation class. This

// AppLogic maintains a count of the number of times it has

// executed.

IDGenExecCounter::IDGenExecCounter() {

//...

STDMETHODIMP

IDGenExecCounter::Execute()

{

// Check whether there is already a session.

// The custom session ID generation class is passed in the

// third parameter to GetSession().

hr = GetSession(0,             /* flags */

                "sessiontest", /* appname */

                m_pIDGen,      /* session id gen */

                 &m_pSession);   /* sess object reference */

                                 /* returned here */

// If there is no existing session, create one.

if (hr != GXE_SUCCESS)

{

// The custom session ID generation class is passed in the

// third parameter to CreateSession().

hr = CreateSession(GXSESSION_DISTRIB, 

                 0,               /* no timeout */

                 "sessiontest",   /* appname */

                 NULL,            /* sessionid */

                 m_pIDGen,        /* session id gen */

                 &m_pSession);  /* sess object reference */

                                 /* returned here */

}

m_pSession->GetSessionData(&m_pVL);

// Increment the execution counter

LONG counter = GXGetValListInt(m_pVL, "execcounter");

   if (counter != 0)

      counter++;

   else

      counter = 1;

GXSetValListInt(m_pVL, "execcounter", counter);

m_pSession->SetSessionData(m_pVL);

SaveSession(NULL);

// Display result

sprintf(msg, "<HTML> <BODY> Applogic execution count = %d </BODY></HTML>", counter);

return Result(msg);

}



Viewing the Number of Active Sessions



You can programmatically retrieve the current number of active sessions in the server. This could be used to write this number to a log along with a date in order to map application usage, for example, or to display on an application management screen.

To view the number of active sessions at a given instant, you use the method GXContextGetSessionCount() from the class GXContext.

Context is a member of the AppLogic class; it's available thru "m_pContext" member variable in C++ AppLogics. You can pass it directly to GXContextGetSessionCount(). The prototype is as follows:

GXContextGetSessionCount(
    IGXContext *pContext,
    DWORD dwFlags,
    LPSTR pAppName,
    ULONG *pCount);

pContext . Pointer to the current server context object

dwFlags . Currently not used

pAppName . Application's name

pCount . Where count is returned

For more information, see the section on the GXContext class in the Foundation Class Reference.



Using the State Layer



The state layer is a distributed data storage mechanism which you can use to store the state of an application. The application state is a collection of application variables whose scope is global within the application. In contrast, a session is a collection of application variables whose scope is a user session.

Information in the state layer can be organized in a hierarchical structure, or tree, as shown in the following illustration:



Each node on a tree consists of the following:

  • An IGXValList object containing data for that node.

  • Characteristics of the node itself. The characteristics you can specify for each node include whether its contents expire after a specified time and whether it is distributed enterprise-wide, within a cluster, or local to one process.

Each application running in an iPlanet Application Server system can have its own state tree. The application data is accessible to any code in the application, no matter which server or machine that code happens to be running on, because the state is distributed. The state data is accessible as long as the code is within the scope of distribution that you specified when setting up the state node (from one process up to enterprise-wide). iPlanet Application Server takes care of synchronizing access to data in the state layer so that read and write conflicts do not occur when several distributed processes are trying to work with the same data.

For example, iPlanet Application Server uses the state layer to store session-related data. Each session has one node in the tree structure. The session nodes are grouped under the application that those users are using, as shown in the following illustration:

By storing session data in the state layer, multiple iPlanet Application Servers can access the same session data, depending on the scope of distribution specified when the session was created.

The state layer is preferable to using global application variables when you need distributed data. Unlike the state layer, a global variable is bound to a particular process on one machine. In a load-balanced installation, the next time the user invokes the same AppLogic, the AppLogic might run on a different machine and would not have access to the same value of the global variable.

The iPlanet Application Server supports creating and using the state layer through the following parts of the iPlanet Application Server Foundation Class Library:

  • IGXState2 interface. Each node on the state tree is an instance of this interface, which provides methods for creating and deleting nodes and getting the name and other characteristics of a node.

  • The GetStateTreeRoot( ) method in the GXAppLogic class. This returns an IGXState2 instance which is the starting point for navigating a given state tree.


Example
The following AppLogic code updates the information in a state node:

STDMETHODIMP

SetState::Execute()

{

   HRESULT hr;

   CHAR msg[256];

   // Get the state tree. Its name is "grammy" and it

   // should be a distributed tree.

   // This state tree is available to any AppLogic in any

   // session belonging to the "grammy" application.

   hr = GetStateTreeRoot(GXSTATE_DISTRIB,

                         "grammy", &m_pStateRoot);

   IGXState2 *pState = NOERROR;

   hr = m_pStateRoot->GetStateChild("Best Female Vocal",

                                     &pState);

   if (hr != NOERROR || !pState)

   {

      // the "grammy/Best Female Vocal" state node

      // doesn't exist; let's create it

      hr = m_pStateRoot->CreateStateChild("Best Female

Vocal", /* name */

                 120, /* timeout in secs */

                 GXSTATE_DISTRIB, /* flags */

                     &pState);          /* return node pointer */

   }

   if (hr == NOERROR && pState)

   // the "grammy/Best Female Vocal" state node

   // exists; get the data.

   {

      IGXValList *pVL = NULL;

      pState->GetStateContents(&pVL);

      GXSetValListString(pVL, "winner",

                         "Whitney Houston");

      GXSetValListString(pVL, "runner up",

                         "Barbara Streisand");

      pState->SaveState(0);

      pVL->Release();

      sprintf(msg, "<HTML> <BODY> SetState applogic set

         this info in the \"grammy/Best Female Vocal\" state

         node: winner-Whitney Houston, runner up-Barbara

         Streisand </BODY></HTML>");

   }

   pState->Release();

}

return Result(msg);

}

The following AppLogic code gets information from an existing state node:

STDMETHODIMP

GetState::Execute()

{

   HRESULT hr;

   CHAR msg[256];

   // get the state tree

   hr = GetStateTreeRoot(GXSTATE_DISTRIB,

                         "grammy", &m_pStateRoot);

   IGXState2 *pState = NOERROR;

   hr = m_pStateRoot->GetStateChild("Best Female Vocal",

                                     &pState);

   IGXValList *pVL = NULL;

   pState->GetStateContents(&pVL);

   LPSTR w, r;

   w = GXGetValListString(pVL, "winner");

   r = GXGetValListString(pVL, "runner up");

   sprintf(msg, "<HTML> <BODY> GetState applogic got this

      info in the \"grammy/Best Female Vocal\" state node:

      winner-%s, runner up-%s </BODY></HTML>", w, r);

   pVL->Release();

   }

pState->Release();

return Result(msg);


Adding a Node to a State Tree

When you create a new state node, you specify its characteristics, including a timeout value. There are three different styles of timeout:

  • Destroy the node if it is not accessed for a given number of seconds. This style of timeout is the default.

  • Destroy the node a given number of seconds from the time the node was created.

  • Destroy the node at a given date and time, specified in seconds.

You can assign a non-zero timeout value only to child nodes that are leaf nodes. Parent nodes can only have a timeout value of 0.

For more details about how to specify timeouts, see the entry for CreateStateChild( ) in the iPlanet Application Server Foundation Class Reference.

To add a node to a state tree

  1. If you already have data to store in the new node, place the data in an IGXValList object. For example:

       IValList val = GX.CreateValList();

       val.setValString("winner", "Whitney Houston");

       val.setValString("runner up", "Barbara Streisand");

       IGXValList *pVL = GXCreateValList();

       pVL->SetValString("winner", "Whitney Houston");

       pVL->SetValString("runnerup", "Barbara Streisand");

  2. Call GetStateTreeRoot( ) for the tree to which you want to add a node. For example, the following code gets the root node for the Grammy tree:

       hr = GetStateTreeRoot(GXSTATE_DISTRIB,

                             "grammy", &m_pStateRoot);

  3. Call GetStateChild( ) to traverse to the next node.

       hr = m_pStateRoot->GetStateChild("Best Female Vocal",

                                         &pState);

  4. If necessary, repeat Step 3 until you reach the node under which you want to place a new node.

  5. Call CreateStateChild( ) to set up the name, timeout, and other characteristics of the new node.

       hr = m_pStateRoot->CreateStateChild("Best Female Vocal",

                  120, /* timeout in secs */

                  GXSTATE_DISTRIB,    /* flags */

                  &pState);           /* return node pointer */

  6. If you have data for the node, call SetStateContents( ) to store the data in the new node.

       hr = pState->SetStateContents(pVL);

       child.setStateContents(val);

  7. Save the data in the distributed store.

       pState->SaveState(0);

    If you call SetStateContents( ) several times before calling SaveState( ), only the value from the last SetStateContents( ) call is saved.


Storing Data in an Existing Node in a State Tree

After you have created a state node, you can access it and update the values in it by using the following procedure:

To store data in a node

  1. Call GetStateTreeRoot( ).

       hr = GetStateTreeRoot(GXSTATE_DISTRIB,

                             "grammy", &m_pStateRoot);

  2. Call GetStateChild( ) to traverse to the next node in the hierarchy.

       hr = m_pStateRoot->GetStateChild("Best Female Vocal",

                                         &pState);

  3. If necessary, repeat Step 2 until you have traversed to the node in which you want to store the data.

  4. Call GetStateContents( ) to retrieve a reference to the session data, and modify the values. For example:

       IGXValList *pVL = NULL;

       pState->GetStateContents(&pVL);

       GXSetValListString(pVL, "winner",

                          "Whitney Houston");

       GXSetValListString(pVL, "runner up",

                          "Barbara Streisand");

  5. Call SaveState( ) to replace the old values with the new. For example:

       pState->SaveState(0);


Deleting a Node From a State Tree
When you no longer need a state node, you can delete it by using the procedure described in this section. You can delete a parent node only after deleting its child nodes.

To delete a node

  1. Call GetStateTreeRoot( ).

       hr = GetStateTreeRoot(GXSTATE_DISTRIB,

                             "grammy", &m_pStateRoot);

  2. Call GetStateChild( ) to traverse to the next node.

       hr = m_pStateRoot->GetStateChild("Best Female Vocal",

                                         &pState);

  3. If necessary, repeat Step 2 until you reach the parent of the node you want to delete. (Don't traverse to the node you want to delete. Stay on its parent.)

  4. Call DeleteStateChild( ).

       pState->DeleteStateChild("Streisand");


Previous     Contents     Index     DocHome     Next     
Copyright © 2000 Sun Microsystems, Inc. Some preexisting portions Copyright © 2000 Netscape Communications Corp. All rights reserved.

Last Updated April 26, 2000