Programmer's Guide



Chapter 25   Triggered Object Basics

SilverStream triggered business objects are Java classes that run on the SilverStream Server and respond to a specific set of server-produced events. This chapter provides an overview of the different types of triggered objects, and is organized as follows:

About triggered business objects   Top of page

A triggered business object is a specific type of Java class that resides on the SilverStream Server. It has a set of properties that the server stores as metadata, and it implements one or more predefined SilverStream event listener interfaces.

The SilverStream Application Server produces events (or triggers) that objects can listen for. An object that implements one of these interfaces is called a listener for that trigger. For example, if you create a business object with a Mail trigger, that object is called a Mail Listener.

NOTE   Business objects are not applets, hence they are not subject to the same restrictions as applets. Because business objects run in a Java application, they can perform any actions on a database, server, or potentially, any other system object. For this reason you should be careful about granting permissions for business objects.

The table that follows summarizes the different types of server triggers. A business object can implement one or all of these triggers.

Type of trigger

Description

Scheduled

Fired at programmer-specified intervals. The intervals can be minutes, hours, days, weeks, months, and so on.

Scheduled objects implement the AgiScheduledListener interface.

Mail

Fired when the SilverStream Server checks for and receives mail for a known POP3 mail account.

Mail objects implement the AgiMailListener interface.

Table-modified

Associated with a specific database table. These triggers fire when any row in the table changes because of a SilverStream-related activity (for example, a row in the database table is inserted, deleted, or modified).

Table-modified objects implement the AgiTableListener interface.

NOTE   Table-modified objects are not implemented with database triggers.

Server events

Fired in response to server-wide events, such as server start, server stop, server error, user login, and user logout occurrences.

Server event objects implement the AgiServerEventListener interface.

Invoked

Triggered by calls to the invokeBusinessObject() method. Any SilverStream form, page, or business object can call this method. Invoked objects can also be triggered by any external program that makes an HTTP 1.1 request.

NOTE   You can also invoke an object from a non-SilverStream Java client, or invoke an object on a remote server. For more information, see Using Invoked Business Objects.

Invoked objects implement the AgiInvokedListener interface.

Data source

Fired when AgoDatas (AgcDatas, AgpDatas, and AgaDatas) are specified to get data from a business object rather than from a database table.

Use a data source object to write your own SQL query, or to access and/or update data from non-relational data or databases not supported by SilverStream.

    For more information, see Using Data Source Business Objects.

Pass-through data source

Fired when an instance of AgaData called AgaPassThrough is specified to pass data to the client based on a previously defined dataset.

Use a pass-through DSO to access data from an external (non-SilverStream) client, or to access a shareable dataset.

For more information, see Using Data Source Business Objects.

Servlet

Fired in response to HTTP requests to specific URLs.

Servlets implement the javax.servlet.Servlet interface and the AgiHTTPListener.

Cluster events

Fired when servers in a cluster start and stop.

Cluster event objects implement the AgiClusterListener interface.

Business objects and threads of execution   Top of page

Business objects run in threads. Each type of listener runs in a default thread, as shown the following table.

Object

Thread the object runs in

Scheduled listeners

Each object runs in its own thread

Server listeners

The SilverStream Server thread

Invoked listeners

The client thread servicing the HTTP request

Mail listeners

The single mail listener thread

Table listeners

The client thread servicing the table request

Cluster listeners

Synchronously in a cluster thread (which may or may not be the same thread for each execution)

Data Source listeners

The client thread servicing the request

Servlet listeners

The thread of the HTTP request

Creating triggered business objects   Top of page

You can create triggered business objects using the SilverStream Business Object Designer, or you can import objects you have created using an external tool.

To create a triggered business object:

  1. In the Main Designer, choose the Business Object Designer icon.

  2. Click the New button at the bottom of the right pane.

  3. Choose New Object from the menu.

    The Business Object Designer starts the Business Object Wizard.

Using the Business Object Designer   Top of page

When you complete the wizard, or choose Cancel, the Business Object Designer appears. You use this design environment to write the Java code for the business object. You also use it to specify a dataset for the business object, and to add, remove, or change triggers using the Property Inspector. The Property Inspector provides access to object, trigger, and data properties.

    For more information, see the Business Object Designer in the online Tools Guide.

About the new business object   Top of page

When you save the object, SilverStream creates the class definition and adds the appropriate implements clause to the class definition. For example, if you specify a mail trigger for an object, the class definition will include the following:

  implements AgiMailListener 

The listener interfaces specify the events that the object can fire. These events are shown in the Business Object Designer in the event pull-down. The server contains classes that produce these events. These event producers are private SilverStream classes.

In addition, the object is registered with the server as a listener for the appropriate event; that is, it is added to a list of objects that are listening for specific events.

    To access online documented code examples of business objects, go to Application Techniques>Triggered Data Source Objects in the help system.

Importing triggered objects created outside of SilverStream   Top of page

You can create business objects in an external editor and then import either the Java source file or the class file to the Silver Stream Server. First you write a class that at least implements the appropriate interface for the triggered object. For example, for a mail object the class must implement AgiMailListener. Then you can use one of the import utilities in SilverStream's command line tool, SilverCmd, to import the object to the SilverStream Server.

    For information about setting up and using SilverCmd utilities, see SilverCmd Reference in the online Tools Guide.

Using the ImportClass utility

Use this SilverCmd utility to import a business object class file compiled in an external editor. The utility command options depend on the type of business object class you are importing.

    For more information see ImportClass in SilverCmd Reference.

Using the ImportSource utility

Use this SilverCmd utility to import a business object source file created in an external editor. The command options depend on the type of business object source you are importing.

    For information see ImportSource in SilverCmd Reference.

About event objects   Top of page

As described earlier, the SilverStream Server provides a discrete set of events or triggers to which business objects can respond. There is an event producer class for each of these triggers. The event producer is the source of the event.

When the server produces an event, it calls a method in the listener interface of all business objects registered for that event. The method (typically) has one parameter known as an event object. The event object passed to an event method contains or provides the following:

Each business object is passed a different event object with the data tailored for it.

Example of an event object   Top of page

Suppose that a business object is registered as a mail listener for a particular mailbox (for example, hr@silverstream.com). When the server receives mail for hr@silverstream.com, it responds as follows:

More about event objects   Top of page

Each time an event is produced, the event producer instantiates a new event object for each listener. For example, if you have three table listeners that are associated with the same database table, the server will instantiate three event objects and pass each individual object to each listener. The event objects that are passed to the registered listeners provide important information about the event that occurred. They also provide access to other parts of the application server's environment.

Obtaining information from the event object

All of the event objects are subclasses of the AgoBusinessObjectEvent, so they share many common methods. The event object provides access to datasets, database objects, database URLs, the server, the session object, the current user, the current group, and so on.

In addition, each event object provides access to the individual piece of information unique to the event that fired it. For example, the AgoMailEvent provides the actual mail message while the AgoTableEvent (for Table Listener objects) passes information about the database row that was changed.

You access the information by calling the appropriate method on the event object instance, which is referenced as evt. For example, if you want to obtain the current user, you call the getUser() method this way:

  evt.getUser() 

    For more about the information that you can obtain from each event object, see the AgoBusinessObjectEvent page in the SilverStream API section of the help system.

Business object life cycle   Top of page

You can specify the life cycle of a triggered business object at design time. The options are as follows:

Event-lifetime object

An event-lifetime business object can respond to a single event or set of events. This object is instantiated by the SilverStream Server when its associated event is triggered. When there is no longer a reference to the object, it becomes available for garbage collection like any other Java object.

Server-lifetime object

A server-lifetime object can respond to multiple events over its lifetime. This object is instantiated when the SilverStream Server starts or when the object is first saved. It responds to events as they occur. The SilverStream Server maintains a reference to this object during the life of the server. The server-lifetime object is never available for garbage collection.

Saving and publishing server-lifetime objects   Top of page

The server-lifetime object reinstantiates each time that you save it or use the SilverStream Publish command. This ensures that the server always has the latest object. When you use the Publish command on an existing object, SilverStream un-registers the first object (which makes it available for garbage collection) and registers the new object.

Note that when you save or publish the object the class object is also updated, and so is the Class Loader for the package in which the saved object resides. This means that all class objects in the package are updated, and any static variables are re-initiated.

For more information, see Business objects, class loaders, and JAR files.

Object life-cycle and threads   Top of page

Any business object can be called simultaneously by multiple threads. The behavior of that object depends on the life cycle of the object.

Assume that there are four threads, each invoking business objects b1 and b2, as shown in the figure that follows. Each invocation of the business object is passed a unique event object. If the objects are event-lifetime objects, then the server instantiates seven instances of the business objects; four of b1 and three of b2.

Because the event-lifetime instances do not share any data, no synchronization is required. However if the object is a server-lifetime object, only two business objects are instantiated at the time the server starts up, as shown in the next figure. The business objects b1 and b2 each have unique instances of the event object passed to it. Because the objects can share data, they must be explicitly synchronized.

Server-lifetime objects and thread safety

In some cases, server-lifetime objects provide better performance because there is no overhead associated with instantiating the object each time that you need to use it. However coding these objects requires that you ensure that they are thread-safe.

Problems with thread safety occur when more than one thread at a time accesses the same resource (such as an Object, a variable, a file, and so on) in inconsistent ways. Thread safety is a concern usually when you use class (static) or object variables (any variables that are declared outside of methods). But it can be a problem even if you do not use these Java constructs. For example, suppose two threads try to access the same file. One thread sets it to be writable and then changes it while other thread deletes it. Suppose the second thread deletes it, after the other sets it to writable, but before it changes it.

Synchronization in Java

Java provides a built-in solution to the issue of thread safety. Use the synchronized keyword to force a lock to be obtained and held while a region of code is executed. You can synchronize entire methods by using the synchronized keyword on the method declaration or you can synch smaller regions using this declaration.

  synchronized (this){ 
// your code here
}

You can synchronize on any object, not just "this". For example, you might synchronize on a list or array while you are making changes to it, such as sorting it.

NOTE   Sever-lifetime objects never die, but the thread of execution does. If the object encounters a catastrophic error (for example, a null pointer exception) the thread dies, not the object.

Session persistence   Top of page

You can also create objects that support session persistence. Session persistence is the ability to store data across a user session, as you would in a traditional shopping cart application. For example, you might want to maintain a list of products and a subtotal for the cost of the products as the user navigates across a multi-page or multi-form application.

Accessing the HttpSession object

To achieve session persistence in a business object, you must first obtain the session object from the server. This is a standard Java object that represents a single session between client and server. The Session object provides all the methods provided in the standard Java extension interface javax.servlet.HttpSession, such as getValue() and putValue().

You have access to the session object from all event objects using the evt.getSession() method. Some events objects, such as those passed to Invoked Listeners, have an implicit session associated; that is, the session of the caller that invoked the business object. Other events, such as ServerListeners, use the default server session in its place. Once you have the HttpSession object, you can use the methods putValue() and getValue() on the Session object to store and retrieve the desired data.

This example creates a session persistent object.

  HttpSession s=evt.getSession(); 
s.putValue(key,obj);
obj=s.getValue(key);

Another option for providing persistent data in lightweight objects is to store the data in the database. Each time the object is instantiated, you can access its data from the database.

Triggered objects and data   Top of page

Triggered business objects can access a range of datasets. The datasets can include database tables, database views, or queries that include columns from multiple tables (and can also include computed columns). You can specify the source of the dataset as a database table or a data source business object. Unlike forms and pages, triggered business objects do not include member variables that provide automatic data access. However, you can easily provide access to datasets at design time through the event object.

Specifying the dataset    Top of page

You create a dataset for a business object at design time by choosing the Data icon from the Business Object Designer toolbar. You specify the contents of the dataset by setting properties on the AgaData object(s) that you specify for the business object. You specify the AgaData object's dataset using the Property Inspector.

The Property Inspector lets you provide values for these properties:

Name

The name of the data object. You use this name to refer to the object programmatically.

Load data

Lets you specify the source of the data for the AgaData object. It can be from a database table or from a business object.

Table

If you specify a database table for the Load Data property, use this property to choose the table name.

Column expressions

Lets you specify the names of the data columns. You can reference these names using the setProperty() and getProperty() methods.

Where clause

Lets you narrow the result set returned by the business object. This specifies the SQL where clause. If you do not specify the where clause, the query returns every row and column in the named dataset.

Order by

Lets you specify the order in which the server returns data to the object.

Populating AgaData   Top of page

At runtime, the SilverStream Server populates the AgaData and passes it to the business object through the event object. The following is a summary of that process.

  1. One of the trigger events occurs. If the object is not persistent, then the server instantiates it.

  2. The server builds the query for the object's dataset(s), then submits the query to the database server.

  3. The SilverStream Server gets the dataset from the database server and populates the object's AgaData object(s) with a copy of the database rows.

  4. The object manipulates the data in some way.

  5. The object calls the AgaData.updateRows() to update the database. If you do not call this method, any changes you have made will not be stored in the database.

  6. The SilverStream Server creates a SQL query that contains all of the operations that occurred on the AgaData object. It submits the query to the database server as a single transaction.

About the AgaData   Top of page

The AgaData object, like AgcData (for forms) and AgpData (for pages), implements the AgiRowCursor and AgiRowSetManager interfaces. AgiRowCursor lets you traverse the dataset while AgiRowSetManager lets you manipulate it.

The AgaData instance is specific to the event instance and not the business object instance, because business objects can persist across multiple invoke calls. If the data persisted with the object, and not the current event, you might not obtain the expected result.

Obtaining an AgaData

Each event object contains a unique AgaData instance. You obtain the instance from the event object using the getAgaData() method, which has this declaration:

  AgaData getAgaData(String name) 

Where name specifies the name of the AgaData to obtain. It must match the name specified in the Property Inspector.

Suppose you had an invoked listener with an AgaData object called AgaData1. This is how you would declare and obtain it.

  AgaData myData=evt.getAgaData("AgaData1"); 

Once you obtain the AgaData object, you can manipulate it as you can with any other SilverStream dataset.

Multiple AgaDatas

You can make any number of AgaData objects available to the business object at design time. Like all triggers, table listeners and mail listeners have access to AgaData objects. However these particular triggers also have built-in data. Table listeners have access to individual rows being changed, while mail listeners have access to e-mail messages.

Navigating, manipulating, and updating the dataset

AgaData implements the AgiRowCursor interface, which makes all of the dataset navigation and manipulation methods available to you. Once you have made changes to the AgData object, call AgaData.updateRows() to update the changes to the database. For more information see About the AgiRowCursor data model.

Triggered objects and security   Top of page

You can set permissions for business objects using the SilverStream Management Console (SMC). The permission options are Read, Write, and Execute, as described below.

Read

Allows you to read the object's design information and Java code. This is a design time permission.

Write

Allows you to change the object's design information and Java code. This is a design time permission.

As noted earlier in this chapter, business objects run in a Java application, so they can perform any actions on a database, server, or potentially, any other system object. For this reason you should be careful about granting the "write" permission for business objects.

Execute

Allows you to run the object. This is a design time permission. To the operating system, all business objects run as the SilverStream Server and have the same operating system permissions in the runtime environment as the server. Security access based on a login/password key or other source only affects data access through AgaDatas. This security access does not prevent you from doing something like opening a JDBC connection or writing your own SQL statements.

Objects such as Scheduled, ServerStart/Stop, and Mail listeners run as the SilverStream "Anonymous" user. Anonymous, by default, has no special privileges. Because these objects run as Anonymous, you must be careful how you set permissions. For example, if you have a server listener that accesses a database table during its execution, Anonymous must have the appropriate Select access to that table or the server listener will not be able to read the table.

    For more information about security, see the chapter on security in the Administrator's Guide.

Business objects, class loaders, and JAR files   Top of page

Business objects have access to any classes that reside in the same database as the business object. If you need to access a class that resides elsewhere, you can add it to a JAR file. You can build JAR files from other objects and media on the server, or you can import existing JAR files.

When one of your business objects uses a JAR file and you make changes to it, the business object will get the new JAR file the next time the object is instantiated.

    For information about building Jars, see Using Utility Classes, JAR Files, and JavaBeans.

About class loaders   Top of page

There is a class loader associated with each package, and each triggered business object has a set of class loaders associated with it; one for the package the business object is in, and one for each JAR file used by the business object.

The class loaders are refreshed whenever they need to get newer versions of the objects they are responsible for. Therefore, when a JAR file is re-saved (or rebuilt), the class loader for that jar file is refreshed. If a triggered business object is re-saved then the class loader for the package the business object resides in is refreshed. This causes all other objects in the package to be refreshed as well. This behavior will prevent IllegalAccessExceptions from being thrown when making package-protected calls between objects within a package when one has been saved the another has not.

Saving JAR files

If a JAR file is re-saved and it's class loader is refreshed, other objects such as business objects, pages, and forms that depend on that jar will be refreshed in order to get the newest version of the JAR. This results in the refresh of the packages of those objects as well. So saving a JAR file causes a refresh of every object in every package where there is at least one user of the JAR.

Deserializing business objects    Top of page

If you need to deserialize a SilverStream-specific object like a triggered or utility business object, a page, or a Hashtable that contains a utility business object, you should use the AgoObjectInputStream class instead of the standard Java ObjectInputStream.

You can use the regular java.io.ObjectOutputStream to save the Object, but you must use an AgoObjectInputStreamWithLoader to deserialize it. Otherwise you will experience problems loading the object classes.

    For an example showing how to serialize and deserialize a business object, go to Application Techniques>Triggered Data Source Objects in the help system: Serializing and Deserializing Business Objects.

About the AgiDataRunner interface   Top of page

The AgiDataRunner interface lets you simulate the behavior of SilverStream's Version 1.0 Agent classes. It provides methods that simulate the beforeAgent processing, agent data, and afterAgent processing events.This table describes the methods.

Method

Description

runBefore()

Called before the first row of the AgaData is set.

runData()

Called once for each row of the AgaData. For invoked triggers, for table triggers, and for HTTP requests that also operate on data, the method is called for each database row that the agent processes. For objects with mail triggers, it is called for each message that is processed.

runAfter()

Called after the last row of the AgaData is set.

For example, if the object is a triggered business object, with a scheduled trigger that processes 20 rows in a dataset, then the runAfter() method is called once after the 20 rows are processed. If the object is triggered by the receipt of e-mail, then the runAfter() method is called once after all of the mail messages have been processed.

You can create an object that implements this interface and pass that object to the AgoBusinessObjectEvent.runAgData() method along with an object implementing AgiRowCursor. The runAgData() calls the methods in this interface and advances the AgiRowCursor, as agents did in Version 1.0.

You can implement this interface in one of two ways: when prompted for interfaces by the Business Object Wizard, or using the Property Inspector. Specify the full package name for AgiDataRunner, which is com.sssw.busobj.AgiDataRunner.

AgiDataRunner also provides methods to cancel a business object. They are described in this table.

Method

Description

cancelAgent()

Cancels the running of this AgiDataRunner. If you cancel the AgiDataRunner, runAgData() stops looping over the rows and terminates. It does not call the runAfter() method.

The method in which the cancelAgent() method is called will complete before the agent is cancelled. You can reset the cancelled status of your agent by calling the uncancelAgent() method, but you must call uncancelAgent() in the same event that you called cancelAgent().

isAgentCancelled()

Returns a Boolean true if cancelAgent() was called for this AgiDataRunner.

uncancelAgent()

Uncancels an agent that was cancelled by the cancelAgent() method.

You must call the uncancelAgent() method in the same event as the cancelAgent() method.






Copyright © 2000, SilverStream Software, Inc. All rights reserved.