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:
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.
Fired at programmer-specified intervals. The intervals can be minutes, hours, days, weeks, months, and so on. Scheduled objects implement the AgiScheduledListener interface. | |
Fired when the SilverStream Server checks for and receives mail for a known POP3 mail account. | |
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. | |
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. | |
Triggered by calls to the 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. | |
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.
| |
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.
| |
Fired in response to HTTP requests to specific URLs. Servlets implement the javax.servlet.Servlet interface and the AgiHTTPListener. | |
Fired when servers in a cluster start and stop. Cluster event objects implement the AgiClusterListener interface. |
Business objects run in threads. Each type of listener runs in a default thread, as shown the following table.
Synchronously in a cluster thread (which may or may not be the same thread for each execution) | |
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:
The Business Object Designer starts the Business Object Wizard.
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.
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.
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.
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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.