Project

General

Profile

Server and Context Hooks

Hooks are customized code classes that deal with initialization or termination events.

Depending on your application you may need to set up such hooks to execute code when a server or session is initialized or terminated.

Search Algorithm

The context hooks nodes are located inside the runtime container while the server hooks are located inside the server/server_id container. If given with a relative path the order in which the node will be looked up will be:

  1. /server/<serverID>/runtime/<account_or_group>/<id>
  2. /server/<serverID>/runtime/default/<id>
  3. /server/default/runtime/<account_or_group>/<id>
  4. /server/default/runtime/default/<id>

or

  1. /server/<serverID>/<id>
  2. /server/default/<id>

Server Hooks

When the FWD server starts and ends, custom application code can be invoked to handle initialization, cleanup or other useful logic. One common use is to export custom APIs (see the chapter on Integrating External Applica- tions) during the server startup hook.

To accomplish this, in the startup hook, the RemoteObject.registerStaticNetworkServer() or RemoteObject.registerNetworkServer() method is called to “load" and export a set of APIs.

The calls occur on the same thread, the code has the security context of the server (usually this means unlimited permissions) and the calls occur sequentially in the order in which they were read from the directory.

Any custom code that must be called at server startup or shutdown must implement the com.goldencode.p2j.main.InitTermListener interface.

The server hooks can be set under the node server-hooks which represents a list of Strings containing the sever hook fully qualified class names.

Server hook directory example:

<node class="strings" name="server-hooks">
   <node-attribute name="values" value="com.acme.myapp.mypkg.SomeServerHook"/>
   <node-attribute name="values" value="com.another.app.pkg.AnotherServerHook"/>
</node>

This section can appear in either /server/<server_id>/ (where <server_id> is the name of the server) or if not found there, /server/default/ is checked. It can exist in both locations but the only the server-specific path will be honored if the hooks list is there.

Once the server has almost completed initialization, but before any clients have been allowed to connect, each hook's InitTermListener.initialize() method is called. The calls occur on the same thread, the code has the security context of the server (usually this means unlimited permissions) and the calls occur sequentially inthe order in which they were read from the directory.

When the FWD server shuts down, each hook's InitTermListener.terminate(Throwable) method will be called.

Server hook class example

Consider an example. We have some user objects created on a session startup and deleted when session finishes. But because of unexpected errors these objects can remain even if they are not longer needed. For thi case we can write a server hook which deletes all unnecessary user objects on server shutdown:

public class UserObjectServerHook
implements InitTermListener
{
   private static final Log LOG = LogFactory.getLog(UserObjectServerHook.class);
   public void initialize()
   {
   }
   public void terminate(Throwable t)
   {
      Database database = new Database("some_database");
    SessionFactory sessionFactory = DatabaseManager.getSessionFactory(database);
    Session session = sessionFactory.openSession();
    Transaction tr = null;
    try
    {
       // delete all user objects
       tr = session.beginTransaction();
       Connection connection = session.connection();
       Statement statement = connection.createStatement();
       statement.execute("delete from user_object");
       tr.commit();
    }
    catch(Throwable thr)
    {
       if (tr != null)
       {
          try
          {
             tr.rollback();
          }
          catch(HibernateException exc)
          {
             LOG.error(exc);
          }
       }
       LOG.error(thr);
    }
    finally
    {
       session.close();
    }
   }

}

Note that you cannot create a record in a server hook because IdentityManager cannot return a primary key outside of a session. This problem will be addressed in a future FWD runtime release.

Context Hooks

GES: Details here.

When a session starts and ends, custom application code can be invoked to handle session initialization, cleanup or other useful logic. The class that will handle this can be set trough context-hook directory node.

When business logic in a session is about to be started, an instance of the hook class is initiated. The referenced class must have a default constructor (a constructor that takes no parameters) so that the FWD server can instantiate it. An error will occur otherwise.

Any custom code that must be called at session initialization or termination must implement the com.golden­code.p2j.main.InitTermListener interface.

A context hooks can be set under the node context-hook which represents a String containing the context hook fully qualified class name.

Directory Configuration Example:

<node class="container" name="runtime">
   <node class="container" name="default">
      <node class="string" name="context-hook">
         <node-attribute name="value" value="com.acme.myapp.cfg.MyContextHook"/>
      </node>
   </node>
</node>

This section can appear under the following paths (in the order of preference):

  • /server/<server_id>/runtime/<account_or_group>/
  • /server/<server_id>/runtime/default/
  • /server/default/runtime/<account_or_group>/
  • /server/default/runtime/default/

When business logic in a session is about to be started, an instance of the hook class is initiated. The referenced class must have a default constructor (a constructor that takes no parameters) so that the FWD server can instantiate it. An error will occur otherwise.

Once the hook has been instantiated, the InitTermListener.initialize() method is called. The calls occur on the same thread, the code has the security context of the corresponding user/process.

When business logic finishes its work, hook's InitTermListener.terminate(Throwable) method is called. If it was finished due to an abnormal condition, the related cause will be passed as the parameter. That parameterwill be null if the session has finished normally. The calls occur on the same thread, the code has the security

context of the corresponding user/process.

Context hook class example

Consider an example. We need to create an user object each time a session is started, and deleted it when session finishes. It can be implemented using this session hook:

public class UserObjectContextHook implements
                                  InitTermListener
{
   private static final Log LOG = LogFactory.getLog(UserObjectContextHook.class);
   private static final Database DATABASE = new Database("some_database");
   private static final String TABLE_NAME = "user_object";
   // context-specific storage for the user object ids
   private static final ContextLocal<UserObjectContext> context = new ContextLocal<UserObjectContext>()
   {
      protected UserObjectContext initialValue()
      {
         return (new UserObjectContext());
      }
   };
   public void initialize()
   {
      Persistence persistence = PersistenceFactory.getInstance(DATABASE);
      SessionFactory sessionFactory = DatabaseManager.getSessionFactory(DATABASE);
      Session session = sessionFactory.openSession();
      RecordIdentifier ident = null;
      Transaction tr = null;
      try
      {
         // get the primary key for the new record and lock it
         Long pk = (Long) persistence.nextPrimaryKey(TABLE_NAME);
         ident = new RecordIdentifier(TABLE_NAME, pk);
         persistence.lock(LockType.EXCLUSIVE_NO_WAIT, ident, true);
         // create new user object
         tr = session.beginTransaction();
         UserObjectImpl userObject = new UserObjectImpl();
         userObject.setId(pk);
         // ... // assign required properties of the user object
         session.save(userObject);
         tr.commit();
         // store id of the newly created user object
         context.get().id = pk;
      }
      catch (Throwable thr)
      {
         if (tr != null)
         {
            try
            {
               tr.rollback();
            }
            catch (HibernateException exc)
            {
               LOG.error(exc);
            }
         }
         LOG.error(thr);
      }
      finally
      {
         if (ident != null)
         {
            try
            {
               // release the lock
               persistence.lock(LockType.NONE, ident, true);
            }
            catch (LockUnavailableException exc)
            {
               // unexpected
            }
         }
      }
      session.close();
   }
   public void terminate(Throwable t)
   {
      // get the id of the created user object
      Long id = context.get().id;
      if (id != null)
      {
         Persistence persistence = PersistenceFactory.getInstance(DATABASE);
         SessionFactory sessionFactory = DatabaseManager.getSessionFactory(DATABASE);
         Session session = sessionFactory.openSession();
         RecordIdentifier ident = new RecordIdentifier(TABLE_NAME, id);
         Transaction tr = null;
         try
         {
         // load user object by id
            UserObjectImpl userObject = (UserObjectImpl) session.load(UserObjectImpl.class, id);
            if (userObject != null)
            {
               // lock the user object
               persistence.lock(LockType.EXCLUSIVE_NO_WAIT, ident, true);
               // delete the user object
               tr = session.beginTransaction();
               session.delete(userObject);
               tr.commit();
            }
         }
         catch (Throwable thr)
         {
            if (tr != null)
            {
               try
               {
                  tr.rollback();
               }
               catch(HibernateException exc)
               {
                 LOG.error(exc);
               }
            }
           LOG.error(thr);
         }
         finally
         {
            try
            {
               // release the lock
               persistence.lock(LockType.NONE, ident, true);
            }
            catch (LockUnavailableException exc)
            {
               // unexpected
            }
            session.close();
         }
      }
   }
}

/**
 * Container for storing id of the created user object.
 */
private static class UserObjectContext
{
   private Long id = null;
}

Note that you cannot create or delete a record using com.goldencode.p2j.persist.Persistence.save or com.goldencode.p2j.persist.Persistence.delete in a session hook initialize method because of runtime errors.

This problem will be addressed in a future FWD runtime release. So far you can use org.hibernate.Session.save or org.hibernate.Session.delete functions (as in the examples above).

Reference

Here is the list of the related database options and their path you can set up inside the directory:

Option ID Data Type Default Value Required Details
/server/<server_id>/server_hooks String List N/A No This represents the list of custom application code that will handle FWD server initialization or termination.

This is launched once the server has almost completed initialization, but before any clients have been allowed to connect.
/server/<server_id>/runtime/<account_or_group>/context-hook String N/A No
This represents the custom application code that will handle session initialization or termination.

This is launched once the session is established.

© 2004-2017 Golden Code Development Corporation. ALL RIGHTS RESERVED.