Skip Headers

Oracle Internet File System Developer Reference
Release 9.0.1.1.0

Part Number A90093-02
Go To Table Of Contents
Contents
Go To Index
Index

Go to previous page Go to next page

13
Creating Custom Servers

Oracle 9iFS allows you to build custom protocol servers and agents that can be managed identically to those included with Oracle 9iFS. This chapter describes the process of creating, testing, and deploying custom protocol servers and agents.

This chapter covers the following topics:

What Is a Server?

An Oracle 9iFS server is a Java application that uses the Oracle 9iFS application development API and executes on an Oracle 9iFS node.

Not all Oracle 9iFS applications are servers. To be a server, an application must follow a prescribed pattern, described in this chapter. By following this pattern, you create an application that can be run on a node and monitored and administered using the Oracle 9iFS management tools. This offers several benefits:

However, not all Oracle 9iFS applications should be servers. Examples of applications that are poorly suited for the server pattern include:

Protocol Servers and Agents

It is useful to classify servers as either protocol servers or agents.

Although protocol servers and agents serve different functions, the process of creating custom protocol servers and custom agents is quite similar. This chapter contains examples of each.

Server Status

Oracle 9iFS servers are simple-state machines. A server is always in one of six states:

Figure 13-1, "Server State Transitions" shows the allowed state transitions. The dashed line indicates an error condition.

Figure 13-1 Server State Transitions


Text description of servstat.gif follows.
Text description of the illustration servstat.gif

Server Management

Servers implement the oracle.ifs.management.domain.ServerInterface interface. This interface enumerates the six possible server statuses and declares a set of methods used to manage a server on an Oracle 9iFS node. Several of these ServerInterface methods request server state transitions:

Creating a Custom Server

This section discusses how to create a custom protocol server or agent. It begins by describing how to create a new Java class for the server, and then examines how to provide functionality frequently required by protocol servers and agents.

Creating a Server Class

Oracle 9iFS servers are subclasses of oracle.ifs.management.domain.Server, which implements the management ServerInterface. To create a new server, you can write a concrete subclass of Server. An easier approach, however, is to subclass oracle.ifs.management.domain.IfsServer, which is itself a Server subclass. IfsServer provides default implementations for many of the abstract methods declared by Server, reducing the amount of code you need to write.

This remainder of this chapter assumes that you subclass IfsServer to create a new protocol server or agent. For more information on Server, refer to the Javadoc for that class.

Example 13-1 FingerServer

FingerServer is a simple protocol server that provides a Finger-like protocol (similar to RFC 1288). Its source code is distributed with Oracle 9iFS as an example of how to build a custom protocol server.

FingerServer's main class, oracle.ifs.examples.servers.FingerServer, extends IfsServer.

public class FingerServer
    extends IfsServer

A server must provide a zero-argument constructor that declares it throws IfsException.

public FingerServer()
    throws IfsException

Generally, little work is performed by the server's constructor. When the constructor returns, the server has not been started, or even fully initialized.

Servers and Thread Safety

Protocol servers and agents often have a multiple concurrent threads. For example, a protocol server might have threads for:

An agent might have threads for:

In addition to these threads, servers must respond to ServerInterface method calls, such as start() and stop(), made asynchronously by the Oracle 9iFS management tools.

In carrying out the server's work, its threads may jointly operate on an Oracle 9iFS session or other state held by the server. Correct operation of a server requires this to be done in a thread-safe manner.

IfsServer provides tools to simplify development of a thread-safe server. It allows timers and ServerInterface methods running in multiple threads to enqueue requests in a per-server request queue. Similarly, incoming events can be enqueued in a per-server event queue. These requests and events can then be dequeued and processed by a single "administration thread" of the server. Delegating these requests and events to a single thread prevents race conditions in their processing.

The Server Lifecycle

Over the life of a server, five methods are called by the node:

Figure 13-2, "Server Lifecycle" illustrates the sequence in which a node invokes these five lifecycle methods, and the relationship between these methods and the server's status.

Figure 13-2 Server Lifecycle


Text description of servlife.gif follows.
Text description of the illustration servlife.gif

Your protocol server or agent can implement these five methods to perform its processing. You must implement the run() method. IfsServer provides default implementations for the other four methods.

The initialize() method is called only once, immediately after the server is constructed when it is loaded onto the node. Each time the server is started, preRun() is called to perform server-specific initialization. Once preRun() returns, run() is called to perform the server's work. The run() method continues until either the server is requested to stop or the server encounters an error from which it cannot recover without stopping. When run() exits, postRun() is called to perform any cleanup. Once postRun() returns, the server is stopped. It remains stopped until either it is started again (causing preRun(), run(), and postRun() to be invoked in sequence, just like the first time), or it is unloaded from the node. When the server is unloaded from the node, dispose() is called to perform any final cleanup.

For IfsServer subclasses, run() should execute in the "administration thread" of the server, processing requests and events queued by other threads. To do this, implement run() as shown in Example 13-2, "Implementing the Run Method in the Administration Thread"

Example 13-2 Implementing the Run Method in the Administration Thread

public void run()
{
    //
    // The run method should return when stopRequested returns
    // true, or when an exception is thrown from which the 
    // IfsServer cannot recover.  This code is boilerplate.
    //
    
    while (!stopRequested())
    {
        try
        {
            handleRequests();
            processEvents();
            waitServer();
        }
        catch (Throwable t)
        {
            //
            // The run method is not allowed to throw an unchecked
            // exception, so if something goes wrong, just log it.
            // Notice that we catch Throwable, not Exception, so
            // that if an Error occurs, it gets logged.
            //
            
            log(LEVEL_LOW, t);
        }
    }
}

FingerServer's run() method is identical to this template.

The Runtime Environment of a Server

When a node loads a server, it provides the server with the following information:

IfsServer provides methods for a server to access this and other information.

Session Management

Servers can create and use Oracle 9iFS sessions in the normal way. However, a server should never start its own service. Instead, it should use the service specified by the getService() and getServiceName() methods. On a properly configured node, this service will have been started before the server is loaded. Example 13-3 shows how to create a session against this service.

Example 13-3 Creating a Session Against an Existing Service

LibraryService service = getService();

CleartextCredential credential =
    new CleartextCredential(username, password);

// The application name is displayed in Oracle 9iFS Manager.
// Setting it is optional, but allows administrators to see
// which servers created which sessions.
String serverName = getName();
ConnectOptions options = new ConnectOptions();
options.setApplicationName(serverName);

LibrarySession session = service.connect(credential, options);

When a server stops, it should ensure all sessions it created have been disconnected.

System Sessions

Servers often use system sessions to perform certain operations. A system session is a session that the server creates when it starts, prior to any user interaction. IfsServer provides methods to create and manage a single system session, called the default session.

Guest Sessions

Protocol servers often allow anonymous, or guest, access without requiring the user to authenticate. IfsServer allows a server to create guest sessions without a password. To do this, use the getCredential(String) method to obtain a credential. Example 13-4 demonstrates this technique.

Example 13-4 Creating a Session for the User Named "Guest"

LibraryService service = getService();
Credential credential = getCredential("guest");

// The application name is displayed in Oracle 9iFS Manager.
// Setting it is optional, but allows administrators to see
// which servers created which sessions.
String serverName = getName();
ConnectOptions options = new ConnectOptions();
options.setApplicationName(serverName);

LibrarySession session = service.connect(credential, options);

Because it can create a valid credential for any specified Oracle 9iFS user, a server should use the getCredential(String) method carefully.

Handling Requests

Starting and Stopping a Server

When a server is started, the preRun() method is called by the node. You can override this method to perform initialization appropriate for your server. For example, this method could create a system session and open the server socket of a protocol server. If preRun() returns without throwing an exception, run() is automatically called to instruct the server to perform its normal operation.

The run() method should continue until the server is requested to stop or the server encounters an error from which it cannot recover. The canonical run() loop, whose code appears in "The Server Lifecycle" section, calls stopRequested() to check whether the server should stop.

When the server is requested to stop, handleStopRequest() is called. By default, this method simply causes subsequent calls to stopRequested() to return true. You can override handleStopRequest() to conditionally veto stop requests.

When run() exits, whether normally or by throwing an exception, postRun() is called. The postRun() method is also called if preRun() throws an exception. You can override the postRun() method to perform cleanup activities. For example, this method should disconnect any sessions created by the server, close any sockets it opened, and stop any threads it started. When postRun() returns, the server should be fully stopped and have released any system resources acquired as it ran.

Suspending and Resuming a Server

A server can optionally support suspend/resume functionality. The method supportsSuspendResume() indicates whether the server can be suspended. The method returns false by default. You can override it to enable suspend/resume.

Example 13-5 Enabling Suspend/Resume Using an Override

public boolean supportsSuspendResume()
    throws IfsException
{
    //
    // We call super, since it performs some error checking on
    // our behalf, such as making sure this IfsServer has not
    // been disposed.
    //
    
    super.supportsSuspendResume();

    return true;
}

When an administrator suspends or resumes the server, handleSuspendRequest() and handleResumeRequest() are called. The behavior of a suspended server is server-defined. Override these two methods to implement behavior appropriate for your server.

Disposing a Server

The dispose() method is called when a server is requested to unload itself from the node. You can override this method, either to perform final cleanup tasks or to veto the dispose request. Unless you wish to veto the dispose request, be sure to call super.dispose().

Using a Timer

IfsServer provides a timer that your server can use to trigger periodic operations. The behavior of the timer is controlled by three server configuration parameters:

Here are some sample timer configurations:

IfsServer provides a set of methods to control the timer:

Each time the timer expires, the handleTimerExpired() method is called. Override this method to perform periodic operations.

Responding to Events

Whenever a LIBRARYOBJECT is created, updated, or removed from the Oracle 9iFS repository, an event is posted on that object. Sessions can register to receive events generated by both their own actions and those of other sessions.

Events are instances of oracle.ifs.common.IfsEvent. Its methods include:

Servers, and agents in particular, are often event-driven. To receive events, the agent's system session (or some other session held by the agent) registers for events of interest by calling the registerEventHandler and registerClassEventHandler methods of LibrarySession.

The IfsEventHandler argument specifies a callback object. Whenever the session receives an event for which it has registered, it will call handleEvent(IfsEvent) on this object. For servers, the easiest approach is for the server to itself implement the IfsEventHandler interface.

public class MyAgent
    extends IfsServer
    implements IfsEventHandler

Servers usually register for events in their preRun() methods. For each call to registerEventHandler or registerClassEventHandler in preRun(), there should be a corresponding call to deregisterEventHandler or deregisterClassEventHandler in postRun().

Example 13-6 Registering for Events in the preRun() Method

public void preRun()
    throws Exception
{
    LibrarySession session = connectSession();

    // Register for events on the root folder.
    Folder rootFolder = session.getRootFolder();
session.registerEventHandler(rootFolder, this); // Register for events on all Documents (and subs). Collection c = session.getClassObjectCollection(); ClassObject co = (ClassObject)c.getItems("DOCUMENT"); session.registerEventHandler(co, true, this); } public void postRun() { try {
LibrarySession session = getSession(); // Deregister for events on the root folder. Folder rootFolder = session.getRootFolder();
session.deregisterEventHandler(rootFolder, this); // Deregister for events on all Documents (and subs). Collection c = session.getClassObjectCollection(); ClassObject co = (ClassObject)c.getItems("DOCUMENT"); session.deregisterEventHandler(co, true, this); disconnectSession(); } catch (Exception e) { log(LEVEL_LOW, e); } }

To process events in a thread-safe manner, the server's handleEvent(IfsEvent) method should delegate event processing to the server's administration thread. To do this, have handleEvent(IfsEvent) call queueEvent(IfsEvent). This will cause processEvent(IfsEvent) to be called in the administration thread.

Example 13-7 Processing Events in a Thread-safe Manner

public void handleEvent(IfsEvent event)
    throws IfsException
{
    // Ignore "FREE" events (for example).
    // But process all other event types.

    if (event.getEventType() != IfsEvent.EVENTTYPE_FREE)
    {
        queueEvent(event);
    }
}

public void processEvent(IfsEvent event)
    throws Exception
{
    // This is where the server actually handles the event.
    // (This executes in the server's administration thread.)

    // For example, log the event.
    log(LEVEL_MEDIUM, "Event received: " + event.getId());
}

Priority Management

A server can allow its priority to be managed. A server's priority can be from 1 to 10, inclusive. Servers with higher priority execute in preference to servers with lower priority.

The supportsPriority() method indicates whether a server supports priority management. By default, this method returns true. You can override this method to disable priority management.

When an administrator sets a server's priority, handlePriorityChangeRequest() is called. Override this method to perform tasks upon a priority change, such as setting the priority of all threads created by the server. To obtain the new priority of the server, call getPriority().

Service Configuration Parameters

When a server is loaded on a node, it is provided a set of service configuration parameters that can be retrieved by calling getParameterTable(). These service configuration parameters do not change over the life of the server.

IfsServer uses the following service configuration parameters:

Your server can also introduce its own service configuration parameters.

Dynamic Properties

A server can optionally publish one or more named dynamic properties, whose values can change as the server executes. An administrator can monitor, and optionally change, the values of a server's dynamic properties using the Oracle 9iFS management tools.

IfsServer provides a set of methods for managing dynamic properties:

Testing a Custom Server

In addition to running on an Oracle 9iFS node, a custom server can run as a standalone Java application. Standalone mode is convenient for preliminary testing of custom servers; however, it should not be used for production systems.

Normally, a server obtains its server configuration parameters from the node. However, for standalone mode, the server obtains this information from a file of name/value pairs.

To run your custom server in standalone mode, you need to add a Java static main(String[]) method to your IfsServer subclass. In addition to providing an application entry point, this method must perform several tasks that would normally be handled by the node. These are:

Example 13-8 shows the main method for FingerServer. You can use this as a template for your own servers.

Example 13-8 The Main Method for FingerServer

public static void main(String[] args)
    throws Exception
{
    //
    // If something goes wrong we want verbose exception messages.
    //
    
    IfsException.setVerboseMessage(true);

    //
    // Construct a parameter table from command-line arguments.
    //
    
    ParameterTable pt = new ParameterTable(args, "parameterfile");

    //
    // Extract some additional parameters required for standalone
    // operation.
    //

    String serviceName =
        pt.getString("IFS.SERVER.PROTOCOL.FINGER.TEST.Service");
        
    String schemaPassword = pt.getString(
        "IFS.SERVER.PROTOCOL.FINGER.TEST.SchemaPassword");

    //
    // Start a service against which to run a FingerServer.
    //
    
    LibraryService.startService(serviceName, schemaPassword);

    //
    // Construct, initialize, and start a FingerServer.
    //

    FingerServer server = new FingerServer();

    server.initialize("Finger", serviceName, schemaPassword,
        pt, null, LEVEL_HIGH);

    server.start();
}

With a main method, you can start your server in standalone mode. For example, to start FingerServer in standalone mode, type the following command line:

java -mx64M oracle.ifs.examples.servers.FingerServer
  parameterfile=FingerServer.def
  IFS.SERVER.PROTOCOL.FINGER.TEST.Service=IfsDefault
  IFS.SERVER.PROTOCOL.FINGER.TEST.SchemaPassword=ifssys

Deploying a Custom Server

To deploy a custom server, you must:

Custom HTTP Servers

The process for creating and deploying a custom Oracle 9iFS server that runs inside the Apache web server is identical to that described in this chapter. However, there are some additional considerations for HTTP servers:

Examples

Oracle 9iFS provides two examples of custom servers:

Fully commented source code and server configuration files (for standalone testing) are provided for each server.

More Information

Additional sources of information on custom servers:


Go to previous page Go to next page
Oracle
Copyright © 2001 Oracle Corporation.

All Rights Reserved.
Go To Table Of Contents
Contents
Go To Index
Index