This chapter is about the triggered business objects, which respond to server, cluster, and scheduled events. It describes the following:
A server triggered business object implements the AgiServerListener interface. You can create the object using the SilverStream Business Object Designer, or you can create one externally. When you create the object in the Designer, SilverStream adds the implements AgiServerListener clause to the object's class definition and registers the object as a server listener.
For information about creating a triggered object externally and importing it into the SilverStream Server, refer to the SilverCmd Reference chapter in the online Tools Guide.
Server listeners respond to these server-related events:
You can create multiple objects that respond to the same server event. For example, you can create multiple objects that respond to server stopped events. However, SilverStream does not guarantee the order in which they execute.
Server error events fire when a catastrophic, but not fatal, error occurs on the server. For example, a catastrophic error occurs when a server in a clustered environment is unable to contact the Cache Manager.
An instance of the AgoServerErrorEvent object is passed into each server error event that is fired. You can query the event object about the exception that caused the server error using the getException()
method, as shown here:
Exception e = evt.getException();
For information, see the AgoServerErrorEvent page in the online SilverStream API help.
An instance of the AgoHttpErrorEvent object is passed into each HTTP error event that is fired. This object provides methods that allow you to handle different types of HTTP errors. In addition, a method in the AgiServerListener interface called httpError()
allows you to determine the status of the HTTP error. This method returns a Boolean. It is passed an AgoHttpErrorEvent event object, which contains the following:
The business object handling the error is responsible for filling out the reply message. This can be done by coding the HTML for the message itself, or by delegating to a dynamic HTML page (using AgiHttpServletRequest.delegateToPage).
The error handler can choose to handle some errors itself and allow other errors to "fall through" (based on, for example, the status code or other conditions). If the error handler handles the error, httpError()
returns true; otherwise it returns false.
As stated previously, if there are multiple business objects on the server that are triggered on server events, SilverStream does not guarantee the order in which they execute. In the case of server errors, the first httpError()
that returns true for a given error message will handle the error. The others are not called. Thus if you want to have multiple server-triggered business objects handling errors, the objects should handle unconnected sets of errors.
For more information about handling HTTP errors, see AgoHttpErrorEvent in SilverStream API help.
A serverStart event fires when the SilverStream Server starts up. An instance of an AgoServerStartEvent is passed to each serverStarted event that fires.
A serverStopped event fires when the SilverStream Server performs an orderly shutdown. An instance of an AgoServerStopEvent is passed into each serverStopped event that fires. In the event of a server crash, SilverStream does not guarantee that a serverStopped event will fire; in fact, it probably will not fire.
Unlike mail or table listener events, the AgoServerStartEvent and AgoServerStopEvent have no special data associated with them. However like all SilverStream event objects you can use them to access any information about the current session, the SilverStream Server, or an AgaData object.
In SilverStream, sessions and login are separate but related functions. A server session is created when a browser (or other HTTP client) connects to the application server. The session lasts across connections (via cookies) until the last connection is dropped and an idle timeout expires.
Each session has associated data that you can access with server-side code. For more information, see AgiSession in Silverstream API help.
The server connects all requests from the browser to the same session.Additionally, users can log in to each session. Session login can happen automatically if the server uses digital certificates, or directly, either with a browser login dialog or an API call to AgiServer.loginUser().
The user remains logged in until the session expires or until the user explicitly logs out.
For information about user login and access control, see the chapter on security in the Administrator's Guide.
The UserLogin event does not fire when sessions are created, but when the user tries to log in to a session. You can make the UserLogin Listener disallow the login by having the userLogin()
event method return false.
An instance of the AgoUserLoginEvent object is passed to each UserLogin event fired. You can access the entire request that caused the login by calling the getRequest()
method. The request that is returned is a javax.servlet.http.HttpServletRequest. The request might be null if there is no associated request, for example, if the AgiServer.loginUser()
is called. If there is a request, you might want to use it in your event processing to obtain the request header or other parts of the request.
An instance of the AgoUserLogoutEvent object is passed into each UserLogout event that fires.The userLogout event does not fire when sessions are destroyed, but rather when the user tries to log out of a session. However, when SilverStream destroys a logged in session, it first logs out the user. This causes the userLogout event to fire for any server listeners. If you are tracking user logins, it might be useful to track user logouts as well.
Faulty server listeners can cause problems in the SilverStream Server. For example, a serverStarted event that hangs will cause the server to hang at start-up, or a User Login event that rejects logins will prevent all users from logging in. You can use the -noserverlisteners startup parameter to start the server without starting any server listeners until you are able to fix the problem listener. You can find out about server problems by checking the AgErrorLog in the SilverMaster database, which provides information about errors that occur when the SilverStream Server starts.
A clustered business object implements the AgiClusterListener interface. When you create a triggered business object that responds to cluster events, the Business Object Designer creates the class, adds the implements AgiClusterListener clause to the class declaration, and registers the object as a cluster listener with the SilverStream Server. Cluster events fire when a SilverStream Server in the cluster is started (the cluster server started event) or when a SilverStream Server in the cluster stops (or is no longer reachable by the Cache Manager).
Clusters are the mechanism by which SilverStream implements load balancing. This table summarizes the cluster components.
For more information, see the chapter on clusters in the Administrator's Guide.
The Cache Manager is responsible for maintaining the integrity of a server cluster. It does this by contacting each server in the cluster at regular intervals. Each time it contacts a server, it passes a list that includes the name of every server in the cluster that it can currently reach. Each server in the cluster compares the current list with the last list received. If all of the names are the same, then no cluster events are fired. If the list is different (for example, one of the servers is no longer listed), then each server fires the clusterServerStopped event for all server listeners on that server.
The clusterServerStopped event is passed an AgoClusterServerStopEvent object. You can use the evt.getServerName()
method to obtain the name of the server that is no longer part of the cluster. The getServerNames()
method returns an enumeration of all of the servers currently available in the cluster.
You cannot use the clusterServerStopped event to distinguish between a server that has gone down and a server that has been purposely removed from the cluster.
The Cache Manager contacts the server regularly (every 30 seconds, by default). If a server goes down and comes up within one of these cycles, the Cache Manager does not notice and the event does not fire.
If a server is added to the cluster (which also causes the cache manager's list to be different) then the clusterServerStarted event fires for each business object. The clusterServerStarted event is passed an AgoClusterServerStartEvent object. Like the AgoClusterServerStoppedEvent object, you can use the evt.getServerName()
method to obtain the name of the server that started.
Sometimes a clustered environment might cause unexpected behavior in some listeners. This table lists each listener and describes its behavior in a clustered environment.
Runs on the server configured to check for mail. If you configure multiple servers in the cluster to check for mail for the same mailbox, then mail events are fired on all the configured servers, but each message is retrieved by just one server. The mail events then fire on the server that processes the mail. | |
Runs on each server in the cluster. For more information, see Managing scheduled listeners in a cluster. | |
A scheduled triggered business object implements the AgiScheduledListener interface. When you create a triggered business object using the SilverStream Business Object Designer, SilverStream adds the implements AgiScheduledListener clause to the object's class definition, and it registers the object as a scheduled listener.
You specify the schedule that triggers an object at design time with the Business Object Wizard or the Property Inspector. These are the schedule options:
Interval
You can specify one of these minute intervals: 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, and 30. Note that the business object runs at the evenly divisible minute of the hour. For example, a business object scheduled to run every 2 minutes will run every even minute of the hour.
Hourly
You can specify these hourly intervals: 1, 2, 3, 4, 6, 8, and 12. Note that the business object runs at the top of the hour.
Daily
You can specify the actual time at which you want the business object to run.
Weekly
You can specify the day of the week and the time to run.
Monthly
You can specify the day of the month to run and the time to run.
Dates and weekends
You can specify the date to start each business object, as well as the date to stop it. If you do not specify a start date, the business object is run the next time the specified schedule occurs (the next hour, the next day, and so on). You can also specify whether you want the business object to run on a weekend. Weekends are defined as Saturday and Sunday and cannot be configured.
NOTE The date must be a valid date, but it does not need to be greater than the current date.
When the schedule is reached, the SilverStream Server broadcasts the event to all of the scheduled listeners serially. For example, assume you have two hourly business objects. At the top of each hour, one object's scheduledReached event fires; when it is done the other's scheduledReached event fires. An instance of an AgoScheduledEvent is passed into each scheduleReached event that fires.
The AgoScheduledEvent does not have any special data associated with it, as do the mail and table listener events. You can use it to get access to any information about the current session, the SilverStream Server, or an AgaData object.
This section describes some techniques you can use to manage the execution of a scheduled listener in a clustered environment.
Suppose you log all of the daily transactions that occur on your SilverStream Server into a database table. This log might grow quickly, so you want to manage its size programmatically. You can use a scheduled listener that runs at a specific time each day. The schedule listener's job is to consolidate the logs into a report and to purge the data from the database table once the report is generated.
This strategy works efficiently until your site traffic becomes so intense that you need to create a clustered environment. In a clustered environment, scheduled listeners exist on each server in the cluster. This means that there are multiple scheduled listeners (one for each server) that are trying to consolidate and purge the database log at the same time each day. Because their actions conflict, the data in the log is unreliable.
Designating a single server
One solution is to designate one of the servers as responsible for generating the log and purging the database table. You can accomplish this by writing the name of the server that will do the reporting into a database table. The scheduled listeners can then compare the name of the designated server with the name of the server (using the evt.getServer()
method) that they are running on. If the name matches, they can run; if not, they abort.
This solution is only valid when your clustered environment is static. Since a server's membership in a cluster can be quite dynamic, there is no guarantee that the server that you designate to run the listener will be up at the time when the listener is scheduled to execute.
Assigning a server dynamically
The better solution is to dynamically designate the single server that will run the scheduled listener. One alternative is to use the database's ability to handle transactions, as described in the following procedure.
BEGIN TRANSACTION
SELECT SERVER_NAME FROM OBJECT_PER_CLUSTER
WHERE OBJECT_NAME = this.getClass().getName()
if ( no rows selected )
INSERT INTO OBJECT_PER_CLUSTER VALUES
(this.getClass().getName(), this_srver)
COMMIT TRANSACTION
return true;
else
ABORT TRANSACTION
return false;
This ensures that only the first scheduled listener that gets to the database will return True, and thus be the only one to run.
BEGIN TRANSACTION
DELETE FROM OBJECT_PER_CLUSTER
WHERE OBJECT_NAME = this.getClass().getName()
END TRANSACTION
Minimizing database activity
The primary disadvantage is that this technique involves a number of expensive transactions proportional to the number of the servers in the cluster. You can dramatically reduce the amount of database activity by making these changes:
The easiest way to implement this solution is by introducing a function that uses JDBC and database-specific SQL to encapsulate the first transaction. For example:
String testAndSet (String listenerName, String deadServerName)
This function returns the name of the server that was previously written into the OBJECT_PER_CLUSTER table. It is called the first time a business object runs and whenever the ServerStopped event is received. The result is cached and the business object checks this cached result every time it needs to run.