| Oracle Internet File System Developer Reference Release 9.0.1.1.0 Part Number A90093-02 |
|
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:
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:
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.
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.
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:
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.
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.
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.
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.
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.
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"
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.
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.
Throwables to be logged in the node's log file. These methods take an integer argument that specifies the logging level of the message. The valid logging levels are enumerated by constants in Server. The message will not be logged if the node's logging level is less than that of the message.
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.
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.
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.
connectSession() creates and returns the server's default session. If the server already has a default session (because this method was previously invoked), it is returned.
disconnectSession() disconnects the server's default session.
getSession() returns the server's default session, or null if the server has no default session.
checkSession() returns the server's default session, performing additional checks to ensure the session is still connected and valid.
IFS.SERVER.SESSION.User. The username. Required.
.SERVER.SESSION.ApplicationName. The ConnectOptions application name. Optional; defaults to the server's name.
IFS.SERVER.SESSION.LOCALE.Language. The ConnectOptions locale language. Optional.
IFS.SERVER.SESSION.LOCALE.Country. The ConnectOptions locale country. Optional.
IFS.SERVER.SESSION.LOCALE.Variant. The ConnectOptions locale variant. Optional.
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.
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.
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.
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.
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.
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().
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:
IFS.SERVER.TIMER.ActivationPeriod. How often the timer expires. Example values: 4h (every four hours), 90m (every 90 minutes), 60s (every 60 seconds), 3500 (every 3500 milliseconds).
IFS.SERVER.TIMER.InitialTimeOfDay. The time at which the timer first expires: Example values: 3:30:30 (3:30 a.m.), 20:00:00 (8:00 p.m.).
IFS.SERVER.TIMER.InitialDelay. The time before the first timer expiration. Example values: 4h (four hours), 90m (90 minutes), 60s (60 seconds), 3500 (3500 milliseconds).
Here are some sample timer configurations:
IFS.SERVER.TIMER.ActivationPeriod=10m
IFS.SERVER.TIMER.ActivationPeriod=10m
IFS.SERVER.TIMER.InitialDelay=1h
IFS.SERVER.TIMER.ActivationPeriod=10m
IFS.SERVER.TIMER.InitialTimeOfDay=3:30:00
IFS.SERVER.TIMER.ActivationPeriod=24h
IFS.SERVER.TIMER.InitialTimeOfDay=3:30:00
IfsServer provides a set of methods to control the timer:
startTimer() starts the server's timer.
stopTimer() stops the timer.
isTimerActive() gets whether the timer has been started.
getLastTimerExpiration() gets when the timer last expired.
getNextTimerExpiration() gets when the timer will next expire.
Each time the timer expires, the handleTimerExpired() method is called. Override this method to perform periodic operations.
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:
getId() returns the id of the LIBRARYOBJECT that posted the event.
getClassId() returns the id of the LIBRARYOBJECT's classobject.
getSessionId() returns the id of the session that posted the event.
getEventType() and getEventSubtype() get whether the LIBRARYOBJECT was created, updated, removed, or affected in some special way. Event types and subtypes are enumerated by constants in IfsEvent.
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.
registerEventHandler(LibraryObject, IfsEventHandler) method registers for events on a single LIBRARYOBJECT.
registerClassEventHandler(ClassObject, boolean, IfsEventHandler) method registers for events on any instance of a specified classobject (and, optionally, the subclasses of that classobject).
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().
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.
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()); }
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().
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:
IFS.SERVER.Class. The fully qualified classname of the server. Required.
IFS.SERVER.SESSION.User. The user for the server's default session. Required if the default session is used.
IFS.SERVER.SESSION.ApplicationName. The ConnectOptions application name for the default session. Optional; defaults to the name of the server.
IFS.SERVER.SESSION.LOCALE.Language, IFS.SERVER.SESSION.LOCALE.Country, and IFS.SERVER.SESSION.LOCALE.Variant. The ConnectOptions locale for the default session, as Java-defined codes for language, country, and variant. Optional; if unspecified, no ConnectOptions locale is set for the default session.
IFS.SERVER.TIMER.ActivationPeriod. The frequency of timer expirations. Required if the timer is used. Examples: 2500 (every 2.5 seconds), 5s (every 5 seconds), 10m (every 10 minutes), 2h (every 2 hours).
IFS.SERVER.TIMER.InitialDelay. The delay before the first timer expiration. Optional. If no values are specified for both this and IFS.TIMER.InitialTimeOfDay, defaults to the value of IFS.SERVER.TIMER.ActivationPeriod. Examples: 2500 (every 2.5 seconds), 5s (every 5 seconds), 10m (every 10 minutes), 2h (every 2 hours).
IFS.SERVER.TIMER.InitialTimeOfDay. The time of day of the first timer event. Optional. Overrides any value specified for IFS.SERVER.TIMER.InitialDelay. Examples: 3:30:00 (for 3:30 a.m.), 20:00:00 (for 8:00 p.m.).
IFS.SERVER.TIMER.HourSuffix, IFS.SERVER.TIMER.MinuteSuffix, and IFS.SERVER.TIMER.SecondSuffix. Time unit suffixes. Optional; defaults to "H", "M", and "S" respectively.
IFS.SERVER.TIMER.TimeFormat. Time format. Optional; defaults to "HH:mm:ss".
Your server can also introduce its own service configuration parameters.
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:
getProperties() returns all dynamic properties for the server as an AttributeValue array.
getProperty(String) gets the value of the specified property.
setProperty(AttributeValue) and setProperty(String, AttributeValue) set the value of the specified property.
isPropertyReadonly(String) returns whether or not the specified property can be changed by the administrator using the Oracle 9iFS management tools. By default, all dynamic properties are read-only. Override this method to make dynamic properties settable.
handlePropertyChangeRequest(AttributeValue) is called when a dynamic property is changed. Override this method to perform tasks in response to a property change. By default, the method simply changes the value of the requested property.
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:
oracle.ifs.common.ParameterTable containing the server configuration parameters.
Example 13-8 shows the main method for FingerServer. You can use this as a template for your own servers.
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
To deploy a custom server, you must:
IFS.SERVER.Class whose value is the fully qualified classname of your server.
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:
oracle.ifs.examples.servers.FingerServer is a simple protocol server that provides a Finger-like protocol (similar to RFC 1288).
oracle.ifs.examples.servers.LoggingAgent is a simple event-based agent.
Fully commented source code and server configuration files (for standalone testing) are provided for each server.
Additional sources of information on custom servers:
oracle.ifs.management.domain package.
|
|
![]() Copyright © 2001 Oracle Corporation. All Rights Reserved. |
|