Programmer's Guide



Chapter 7   Advanced Data Access

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:

Writing your own AgiRowCursor   Top of page

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:

Prerequisites for writing an AgiRowCursor   Top of page

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.

SilverStream API support   Top of page

The AgiRowCursor API consists of the interfaces and helper objects listed in this table.

API component

Description

AgiRowCursor

The data access interface.

Methods allow navigation through elements in a hierarchical collection of data, read/write access to data including creation of new data elements (Insert), and registration for data and navigation events.

AgiRowSetEventProducer

Represents the manager of the data obtained from an AgiRowCursor.

It is the source of data events, and provides methods for a listener (AgiRowSetListener) to register for data events.

AgiRowSetListener

Implemented by an object that wants to receive data events.

The object registers with the AgiRowSetEventProducer object that it obtains from an AgiRowCursor object. Data events include notification of data inserted, deleted, modified, or replaced.

AgiRowCursorListener

Implemented by an object that wants to receive navigation events.

The object registers with an AgiRowCursor object and is notified whenever a navigation method is invoked on that AgiRowCursor instance. SilverStream forms use this interface to support binding of components to the form's primary dataset.

AgoRowCursorSupport

Can be used by the implementor of an AgiRowCursor to manage listeners for navigation events.

AgoRowSetSupport

Can be used by the implementor of an AgiRowCursor to manage listeners for data events.

    For information about writing Java classes in SilverStream, see Using Utility Classes, JAR Files, and JavaBeans.

About the AgiRowCursor interface   Top of page

The AgiRowCursor methods fall into the categories listed in this table.

Category

Methods

Navigation

gotoNext(), gotoPrevious(), gotoFirst(), gotoLast(), gotoParent(), gotoChild(), gotoRoot(), hasChildren(), allowsChildren()

Data access

getProperty(), setProperty(), insertBefore(), insertAfter(), appendChild(), delete(), makePrimaryVersion()

Data description

getPropertyCount(), getPropertyName(), getPropertyIndex()

Event

addAgiRowCursorListener(), removeAgiRowCursorListener(), getRowSetEventProducer()

Object

equals(), hashCode(), copy()

Navigation methods

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.

Method

Description

gotoNext()

On success, the cursor refers to the next row in the same level of the hierarchy as it started. It fails (returns false) if the original cursor is the root cursor or is the last row of a hierarchy level.

gotoPrevious()

On success, the cursor refers to the previous row in the same level of the hierarchy as it started. It fails (returns false) if the original cursor is the root cursor, or is the first row of a hierarchy level.

gotoFirst()

On success, the cursor refers to the first row in the same level of the hierarchy as it started. If the row cursor, on invocation, is the root cursor, the resulting row cursor is the first top level row.

This call returns false only when the original cursor is the root cursor and there are no top-level data rows.

gotoLast()

On success, the cursor refers to the last row in the same level of the hierarchy as it started. It is equivalent to (but typically more efficient than):

  while(rc.gotoNext()); 

This call returns false only when the original cursor is the root cursor.

gotoParent()

On success, the cursor refers to the parent row of the original row. If the original row is a top-level row, the resulting cursor must be the root cursor.

This call returns false only when the original cursor is the root cursor.

gotoChild()

On success, the cursor refers to the first child row of the original row. If the original row is the root cursor row, the resulting cursor must be the first top-level row. This call returns false when the original row has no child rows.

You can use the hasChildren() method to determine whether gotoChild() will succeed, without actually changing the cursor.

hasChildren()

Indicates whether there are any child rows for the original row. On return the row cursor is unchanged. If this method returns true, there might be child rows for the original row.

If it returns false, then gotoChild() returns false. In the case where it is very expensive to determine whether a row has children (for example, a master-detail that would require a query), this method can return true even if a subsequent call to gotoChild() might return false. In such cases, it is recommended that once the gotoChild() returns false, subsequent calls to hasChildren() also return false (indicating that you now know that there are no child rows).

allowsChildren()

Indicates whether there can ever be any child rows for the original row. On return, the row cursor is unchanged. If this method returns true, then there might be child rows for the original row. If this method returns false, then it neither has children, nor is the creation of a child supported (see appendChild() later in this chapter).

Note: The View control uses this method to determine whether to display a tree control (or "twistie").

Data description 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.

Data access methods

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() 

Event methods

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() 

Object methods

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() 

Producing events   Top of page

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.

Data navigation events

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.

Data manipulation events

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.

This AgiRowCursor method

Should fire these events

By calling these AgiRowSetSupport methods

delete()

rowPreDelete
rowDeleted

fireRowPreDelete()
fireRowDeleted()

insertAfter()
insertBefore()

rowPreInsert
rowInserted

fireRowPreInsert()
fireRowInserted()

setProperty()

rowDataChanged

fireRowDataChanged()

NOTE   To listen for other types of events, you must write your own methods and events.

Accessing database connection pools   Top of page

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.

About database access permissions   Top of page

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.

Accessing connections   Top of page

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.

About the connection pool manager

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.

Releasing the connection   Top of page

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:






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