This chapter describes ways you can extend the functionality of the Silverstream data access model, as described in Data Access Basics. This chapter covers the following topics:
An AgiRowCursor represents a flat or hierarchal set of data that is organized in rows. The AgiRowCursor API is a simple, abstract model that provides a collection of data to SilverStream data consumer objects. Many SilverStream components accept an AgiRowCursor interface as a source of data. The SilverStream binding manager can provide bidirectional data binding between a component or JavaBean property and a field in an AgiRowCursor.
You can write your own AgiRowCursor to supply data to one of these controls, in order to do any of the following:
The goals of the AgiRowCursor API are to:
At a minimum, SilverStream requires that your custom data class implement these interfaces:
The SilverStream objects that implement AgiRowCursor (such as AgcData) provide a lot of prepackaged functionality, including data navigation, sorting, and manipulation. They also produce and listen for events. Writing a class that provides this level of functionality requires significant work. Fortunately, SilverStream supplies helper classes that you can use to get much of the functionality.
You can use SilverStream's Business Object Designer to create a custom AgiRowCursor. For information about using the Designer, see the Business Object Designer in the online Tools Guide.
The AgiRowCursor API consists of the interfaces and helper objects listed in this table.
For information about writing Java classes in SilverStream, see
Using Utility Classes, JAR Files, and JavaBeans.
The AgiRowCursor methods fall into the categories listed in this table.
Use navigation methods to traverse the tree of data rows. Each method returns a Boolean indicating whether the navigation succeeded (true) or failed (false). The method fails when it encounters the end of data. In other words, if a navigation method returns false, then the row cursor reference is unchanged, meaning it refers to the original data row before the call was made.
The methods throw exceptions if they fail because they were unable to retrieve the requested row.
The navigation methods have this declaration:
public boolean gotoXXXX() throws AgoSecurityException, AgoTransientSystemException, AgoUnrecoverableSystemException
This table describes the implementation requirements for the navigation methods.
Each row that a row cursor object can navigate has a set of properties you can access by name or by index. You can use the getPropertyCount()
, getPropertyName()
, and getPropertyIndex()
methods to translate between property name and property index.
There is a well-known property name defined by the AgiRowCursor class, AgiRowCursor.BANDNAME. Support for this property is optional. You should support it where there are multiple bands (that is, different sets of columns that may be supplied by different rows). This enables the View control to provide custom formatting for different bands. The AgiRowCursor.BANDNAME property may not have an associated index.
The following describes the data description methods:
getPropertyCount()
Returns the number of properties accessible by index. A return value of N is a guarantee that the getPropertyName()
method will return non-null, unique names for values between 0 and (N-1). For any row, there may be additional properties that are accessible by name only. For the root cursor, this method must return 0 (the root cursor has no properties). It has this declaration:
int getPropertyCount()
getPropertyName()
Returns the name of the property with the specified index. Returns null if the requested index is less than zero or not less than getPropertyCount()
. It has this declaration:
public String getPropertyName(int propindex)
getPropertyIndex()
Returns the index of the property with the specified name. This method might return -1 if the requested name does not have an associated index.
public int getPropertyIndex(String propname)
You must follow these rules when implementing the getProperty xxx methods.
getPropertyCount()
-1): getPropertyName(
index)
must not return null.
getPropertyIndex(getPropertyName(
index))
must return index
.
The implementation must guarantee that a cached mapping from name to index for one row in a band will work for all rows in a band.
These data access methods let you manipulate the properties of existing rows as well as adding new rows:
getProperty()
Returns the value of the specified property for the current row. The returned value may be null. It is common, but not required, that calls to getProperty()
for a given property name or index within a band return the same data type for all rows in that band. The getProperty()
method has these variants:
public Object getProperty(String propname)
public Object getProperty(int propindex)
insertAfter()
Inserts a new row after the current row. After the new row is inserted, the row cursor refers to the new row. It has this declaration:
public void insertAfter()
insertBefore()
Inserts a new row before the current row. After the new row is inserted, the row cursor refers to the new row. It has this declaration:
public void insertBefore()
appendChild()
Inserts a new row as the last child of the current row. After the new row is inserted, the row cursor refers to the new row. If the allowsChildren()
method would return false for the current row, this method might either silently return, or throw a Runtime Exception. It is up to the consumer of the row cursor to ensure that it is allowed to add a row. It has this declaration:
public void appendChild()
delete()
Deletes the current row. After the row is deleted, the location of the row cursor is undefined, but typically refers to the row after the deleted row. It has this declaration:
public void delete()
To delete all rows, use this technique:
while(gotoFirst())
delete();
setProperty()
Sets the value of the named property in the current row. It has this declaration:
boolean setProperty(String name,Object value)
makePrimaryVersion()
This method is used internally in support of SilverStream's built-in versioned table support. It is not intended to be implemented by a third-party AgiRowCursor object. It has this declaration:
public void makePrimaryVersion()
The events in this category allow you to add and remove listeners for the AgiRowCursor.
addAgiRowCursorListener()
Adds an event listener to this row cursor. It has this declaration:
void addAgiRowCursorListener(AgiRowCursorListener rcl)
removeAgiRowCursorListener()
Removes an event listener from this row cursor. It has this declaration:
void removeAgiRowCursorListener(AgiRowCursorListener rcl)
getRowSetEventProducer()
Obtains the cursor's AgiRowSetEventProducer. It has this declaration:
AgiRowSetEventProducer getRowSetEventProducer()
The two object methods equals()
and hashCode()
can be difficult to implement.
equals()
Returns true if two row cursors refer to the same cache of data and point to the same row.
hashCode()
Returns the same value for all copies of a row cursor and the same value for a row cursor regardless of the row to which it points. It is used internally by java.lang.Hashtable.
copy()
Returns a row cursor object that is equivalent to the original. Equivalent means that:
rc.equals(rc.copy())
always returns true. Furthermore, it requires that:
rc.getRowSetEventProducer() == rc.copy().getRowSetEventProducer()
Though the copy initially refers to the same row as the original, it navigates independently and has no AgiRowCursorListener. The copy()
method has this declaration:
AgiRowCursor copy()
The AgoRowCursorSupport helper object provides methods that fire data navigation events, while the AgoRowSetSupport helper object provides methods that fire data manipulation events. Within the implementation of the AgiRowCursor methods, you fire the appropriate event.
For example, the implementation of the gotoNext()
method in your class should fire a cursorChanged event. If the cursor change occurs successfully, gotoNext()
returns true.
All of AgiRowCursor's data navigation methods should result in a CursorPreChange and a CursorChanged event (in that order). You fire the CursorPreChange event by calling the AgoRowCursorSupport.fireCursorPreChange()
method. You fire the CursorChanged event by calling the AgoRowCursorSupport.fireCursorChanged()
method. An AgiRowCursor is allowed (but not required) to fire a CursorPreChange event even if the navigation subsequently fails.
AgiRowCursor's data manipulation methods should result in different events based on the action on the data. For example, a delete action should fire both a rowPreDelete and a rowDeleted event. The following table lists the AgiRowCursor data manipulation methods that you must implement, the events that each method must fire, and the AgoRowSetSupport method to call to fire it.
NOTE To listen for other types of events, you must write your own methods and events.
The SilverStream Server connects to databases using the standard JDBC connection object. It maintains and manages database connections for use by SilverStream application requests, such as requests from forms and pages for a dataset or update operations.
Connecting to a database can be an expensive operation. For this reason, SilverStream creates and maintains a pool of connections. The number of connections in the pool is configurable through the SilverStream Management Console.
For more information about database connection pools, see the chapter on Managing Database Connections in the SilverStream Administrator's Guide.
SilverStream maintains its database connections for internal usage. However, when you are writing business objects, you might want to access these connections directly. SilverStream provides a simple API that allows access to an underlying JDBC connection object. This programmatic access allows you unhindered access to database server facilities as well as access to the raw JDBC connection object. This provides complete extensibility, but requires careful coding and database connection management.
The SilverStream Server connects to the database using the account that you set up for that purpose. The server has the same permissions as that account. You must ensure that the access is appropriate for use by SilverStream applications. The JDBC connection object that you get from SilverStream's connection pool has the same account as the SilverStream Server. It does not reflect the permissions of the user making the request, but of the SilverStream Server itself.
The AgiDatabase interface provides access to the internal connection pool to allocate raw JDBC connection objects. You can obtain an AgiDatabase object using evt.getServer().getDatabase("dbName")
or the evt.getDatabase()
.
The evt.getDatabase()
method is preferred because it does not require that the database name be hard-coded.
CAUTION! Access to the internal connection pools is both powerful and dangerous because it gives you full access to the database. If you are not careful, your programs can cause data corruption and can also deadlock a SilverStream Server by holding or losing database connection objects.
You gain access to the database connection pool through the AgiDatabase.getconnection()
method. The getConnection()
method obtains the next available JDBC (database) connection object. You can specify the action to take when no connections are currently available. The getConnection()
method has this declaration:
Connection getConnection(boolean doWait)
where doWait specifies the action to take when no connection is available. When set to true, getConnection()
suspends until a connection is available. When set to false, getConnection()
returns with or without a connection. It returns null if no connection is available.
The Connection Pool Manager manages the connection object. Once the Connection Pool Manager allocates a connection, it cannot be reused until the manager releases it back to the pool. The system will deadlock due to lack of connection resources if connections are allocated and not released. Always return the connection object back to the pool with the releaseConnection()
method.
CAUTION! Improper use of JDBC connection objects exposes you to the danger of deadlocking the server. The SilverStream Server does not provide syntactic control to guarantee the release of allocated connections when you use this API, thus it is easy to hang the SilverStream Server because of a lack of available connections. You must write well-behaved code that releases its allocated connections in the shortest time possible.
You release a connection object by calling the AgiDatabase.releaseConnection()
method. Many databases require that you close the Statements and ResultSets as soon as possible. It is a good idea before you return a connection to the pool that you do the following: