Software Testing Automation Framework (STAF) Service Developer's Guide
December 7, 2015
Version 3.4.2
STAF services are reusable components that provide all the capability in STAF. Each STAF service provides a specific set of functionality and defines a set of requests that it will accept.
STAF allows you to write your own services to provide custom functionality that is not provided by existing STAF services. These custom services can be plugged into the STAF infrastructure. This document describes how you create STAF services, and includes implementation of a sample service in Java, C++, and Perl.
Custom STAF Services can be written in Java, C, C++, and Perl.
You should read the "API Reference" section of the STAF User's Guide before reading this document.
2.0 Sample Service
A sample service is provided with STAF and is referenced throughout this document. Implementations of the sample service are provided in both Java, C++, and Perl. The latest source code for the sample service can be obtained in the staf source tree at:
src/staf/services/sdg_sampleThe sample service is intended to be an example of how to implement a STAF service, and accepts the following command requests:
DeviceService Service Help ADD < PRINTER <PrinterName> | MODEM <ModemName> > MODEL <Model> SN <Serial#> DELETE < PRINTER <PrinterName> | MODEM <ModemName> > CONFIRM LIST [PRINTERS] [MODEMS] QUERY PRINTER <PrinterName> | MODEM <ModemName> VERSION HELPThis sample service allows users to maintain a list of printers and/or modems, to which they can add, delete, list, and query. Note that this sample service doesn't contain any "business logic" other than maintaining the list of devices. Thus, it demonstrates how to create the framework of the STAF service, to which you would then add your service's specific business logic.
All services are implemented via a C language interface. However, STAF provides proxy services for other languages, such as Java and Perl.
There are 5 phases in the life cycle of a STAF service. The STAF service proxies do not externalize all of these phases (for example, for Java and Perl services the construction and deconstruction phases are not externalized).
Phase | Description |
---|---|
Construction | The service exists in the STAF memory space, but is not yet ready to accept requests |
Initialization | The service is initializing (with appropriate parameters) |
Accepting Requests | The service is active and ready to accept requests |
Termination | The service is terminating |
Deconstruction | The service is removed from the STAF memory space |
Typically when creating custom STAF services, you will be adding code to the Initialization and Accepting Requests phases of the life cycle, and possibly the termination phase.
The service should resolve any STAF variables that it needs to reference, such as the STAF/Config/Sep/Line variable which you'll want to use when creating the help text for the service.
The service should create a static String variable, sHelpMsg, containing the help text for the service so that whenever a HELP request is submitted to the service, it can return sHelpMsg in the result.
If the service has any service-specific return codes, it should register these
return codes with the HELP service during this step.
In general, option values for Service requests should be resolved (since they may contain STAF variables). This document will demonstrate how to resolve option values for each Service language using various functions provided.
For Java services, see the "STAFUtil.resolveRequestVar" and "STAFUtil.resolveRequestVarAndCheckInt" functions called by the Java sample service.
For C++ services, see the "resolveOp" function provided in the C++ sample service.
For Perl services, see the "resolveVar" function provided in the Perl sample service.
If the service returns any service-specific return codes, it should register these return codes with the HELP service during its Initialization phase and unregister these return codes with the HELP service during its Termination phase.
You should try to not create new return codes for their service, if possible, since most error conditions can be handles with existing STAF return codes.
If you create a new return code for your service, the return code number must be 4000+.
STAF services should define trust levels for each type of incoming request. The trust level should be appropriate for the type of operation being requested. For example, a List or Query request should require a lower trust level, while a Create or Delete request should require a higher trust level. For operations that are normally tied to a specific machine/handle, services should allow a FORCE option (with a required trust level which is one greater than the operation requires) that allows another machine/handle to request the operation.
STAF services receive incoming requests that indicate the operation the service is to perform. The request contains a space-separated set of commands and options. The commands/options may not contain underscores or hyphens. You should use separate options rather than concatenate options (use "ADD PRINTER" instead of "ADDPRINTER").
When sending requests to a STAF service, the "first" option sent to the command should indicate the overall operation of the request. The existing STAF services utilize a common set of command names, which we recommend all STAF services use. The recommended trust level for each command name is included in the following table:
"First" Option | Description | Trust Level |
---|---|---|
ADD | Adds an item to the service | 4 |
REMOVE | Removes an item from the service | 4 |
CREATE | Creates a service item | 4 |
DELETE | Deletes a service item | 4 |
LIST | Returns a list of items from the service | 2 |
QUERY | Returns information about a specific item from the service | 2 |
REQUEST | Requests an item from the service | 3 |
RELEASE | Releases an item from the service | 3 |
GET | Returns the value of a configuration option for the service | 2 |
SET | Sets a configuration option for the service | 5 |
HELP | Returns information about the request syntax for the service | 1 |
VERSION | Returns the version number for the service | 1 |
FORCE | Indicates that a destructive command should be executed if the requester has a higher trust level than is normally required for the command |
CONFIRM | Used to indicate that a destructive command, such as DELETE, should be performed (if the CONFIRM option is not specified, the destructive command will not be performed) |
WAIT <timeout> | Indicates that the service should wait until the request has completed before returning to the requester (an optional timeout value, in milliseconds, can be specified). |
When choosing a language for implementing a STAF Service, if performance is the most important factor, then it is recommended that the service be implemented in C++. If cross-platform support is the most important factor, then it is recommended that the service be implemented in Java.
If a STAF service starts additional threads, it should ensure that these threads are terminated during the Termination phase. For example, if the service has started a thread to wait for queue messages, the service should send itself a queue message with a unique message which indicates that the additional thread should terminate.
STAF services should always be case-insensitive with regards to incoming service requests.
If the service deals with files, note that any files stored on disk follow the case-sensitivity rules for the machine's operating systems. For example, Windows file names are case-insensitive, but Unix file names are case-sensitive.
When specifying incoming requests for STAF services, there are 3 ways that the option can be specified:
Rather than having to deal with parsing all of the potential formats, STAF services should use the provided STAF command parsers to parse their incoming requests.
It may be useful to have your service write information about the requests
that it is processing to a STAF log. To do this, you can simply submit calls
to the LOG service and specify a LOG request.
It is recommended that the name of log to which your service writes be the
name of the service. It is also recommended that the log to which your service
writes to be a machine log (where you specify the option MACHINE on the LOG
request).
The sample service provided in this document logs information about each valid
ADD and DELETE request that it receives.
The following are typical activities that STAF Services should perform during its life cycle.
Every STAF service command should accept a pre-defined set of options. The options can optionally require a value. The options optionally may be repeated multiple times in the command request. STAF services should allow free ordering of the options (where the ordering of the options in the command request does not matter).
A STAF Service should create a command parser for each of the requests that it accepts. The "first" option sent to the command should indicate the overall operation of the request (such as "LIST" or "QUERY"). It is recommended that the request command be required to be the very first option specified in the command request:
staf local deviceservice list .... staf local deviceservice query ....Each request should then define a set of options that it accepts:
staf local deviceservice list printers staf local deviceservice list modems
All STAF service command parsers should be case-insensitive.
When adding options to a command parser, you specify the name of the option, the number of times the argument may appear in the request, and whether the option allows or requires a value.
After adding all of the options for your service, you can specify the
constraints on how the options can be specified. For example, you can
specify a group of options that are to be treated as a group,
typically to specify groups of mutually exclusive options.
You can also specify dependency relationships between names (where
one option requires that another option be specified).
4.2 Parsing Command Requests
Each incoming request should be parsed by its corresponding command parser After the parse has completed, your service can determine whether the command was parsed successfully via a return code and, if the parse was unsuccessful, a textual message about the error. After parsing the incoming request, the following information can be queried:
Now that you have seen all of the built-in capabilites that STAF"s command
parsers provide, you should understand why it is recommended that you use STAF's
command parser.
4.3 Marshalling Structured Data
STAF supports the automatic marshalling and unmarshalling of structured data. The act of marshalling takes a data structure and converts it into a string-based representation. The act of unmarshalling reverses this and converts the string-based representation back into a data structure. STAF supports the following generic data types with its marshalling:
Most languages support some form of the None, String, List, and Map data types. Note that for C/C++, STAF provides a STAFObject class to reprement a variety of structured data types. However, a map class and a marshalling context are likely new concepts.
A map class is really just a specialized map that is associated with a map class definition. The map class definition is used to reduce the size of a marshalling map class in comparison to a map containing the same data. It also contains information about how to display instances of the map class. You indicate that a map is an instance of a map class by setting the key "staf-map-class-name" to the name of the map class. And, when you unmarshall a data structure, if you see that a map has a key called "staf-map-class-name", you know that the map is really an instance of a map class. You get and set map class definitions using a marshalling context.
A marshalling context is simply a container for map class definitions and a data structure that uses (or is defined in terms of) them. In order to use a map class when marshalling data, you must add the map class definition to the marshalling context, set the root object of the marshalling context to the object you want to marshall, and then marshall the marshalling context itself. When you unmarshall a data structure, you will always receive a marshalling context. Any map class definitions referenced by map classes within the data structure will be present in the marshalling context.
When a string is unmarshalled into a data structure, it is possible that one of the string objects that is unmarshalled is itself the string form of another marshalled data structure. By default, STAF will recursively unmarshall these nested objects. However, each language has a way to disable these additional processing.
4.4 LIST/QUERY and Other Multi-Valued Results
Requests that your service supports, such as LIST or QUERY, that can generate multi-valued results should provide the result in the form of a marshalled data structure. If your service supports LIST and/or QUERY requests, these requests generate multi-valued results. In the case of a LIST request, a list data structure should be generated to represent the result. The list may contain a list of strings, maps, and/or map classes. In the case of a QUERY request, generally, a map class data structure should be created representing the result. Your service may have requests other that LIST or QUERY that also need to return multi-valued results.
Once the data structure is generated for the request during the accepting requests phase, the data structure needs to be set as the root of a marshalling context. Then you can marshall the marshalling context itself to create a string which can then be returned in the result buffer.
When structured data is returned in the result string, the STAF command will automatically unmarshall the data and print it in the most appropriate format. See section "5.2 STAF" in the STAF V3 User's Guide for more information on this.
When writing a service in C++, see sections "6.2.9 Data Structure and Marshalling APIs", "6.3.2 STAFObject", "6.3.3 STAFObjectIterator", and "6.3.4 STAFMapClassDefinition" in the STAF V3 User's Guide for more information on the C/C++ APIs provided by STAF for defining, manipulating, and marshalling data structures.
When writing a service in Java, see sections "3.2.1 Class STAFMapClassDefinition" and "3.2.2 Class STAFMarshallingContext" in the Java User's Guide for STAF V3 for more information on the Java APIs provided by STAF for defining, manipulating, and marshalling data structures.
When writing a service in Perl, see sections "4.2.1 Class STAF::STAFMapClassDefinition" and "4.2.2 Class STAF::STAFMarshallingContext" in the Perl User's Guide for STAF V3 for more information on the Perl APIs provided by STAF for defining, manipulating, and marshalling data structures.
In this example, assume the following commands to the sample service have been submitted (these commands will add 2 printers to the sample service's list of printers):
STAF local deviceservice ADD PRINTER "canon 1" MODEL S750 SN 34349 STAF local deviceservice ADD PRINTER Epson MODEL color500 SN 1023
If your service supports a LIST request, the result buffer should contain a marshalled <List> of an object, such as a String or a Map Class.
For example, the result buffer for the sample service's LIST request contains a marshalled <List> of <Map:STAF/Service/Device/ListDevice>, representing a list of the devices. The STAF/Service/ListDevice map class defines the field names, column headings (plus any short column headings indicated in parenthesis), and the order in which the fields are displayed.
Definition of map class STAF/Service/Device/ListDevice | |||
---|---|---|---|
Description: This map class represents information about a device. | |||
Key Name | Display Name | Type | Format / Value |
name | Name | <String> | |
type | Type | <String> | 'Printer' | 'Modem' |
model | Model | <String> | |
serial# | Serial Number
(Serial #) | <String> |
Examples
Request: STAF local deviceservice LIST PRINTERS
Result:
If the request is submitted from the command line, the result, in table format,
could look like:
Name Type Model Serial Number ------- ------- -------- ------------- Epson Printer color500 1023 canon 1 Printer S750 34349
List requests do not always have to return all of the raw data for the element. It should just return the basic information for the element.
If your service supports a QUERY request, the result buffer should contain a marshalled <Map Class> for which you define the field names, column headings, and the order in which the fields are displayed.
For example, the result buffer for the sample service's QUERY request contains a marshalled <Map:STAF/Service/Device/QueryDevice> representing detailed information about the specified device. The STAF/Service/QueryDevice map class defines the field names, column headings, and the order in which the fields are displayed.
Definition of map class STAF/Service/Device/QueryDevice | |||
---|---|---|---|
Description: This map class represents information about a device. | |||
Key Name | Display Name | Type | Format / Value |
model | Model | <String> | |
serial# | Serial Number | <String> |
Examples
Request: STAF local deviceservice QUERY PRINTER "canon 1"
Result:
If the request is submitted from the command line, the result, in table format,
could look like:
Model : S750 Serial Number: 34349
Request: STAF local deviceservice QUERY PRINTER epson
Result:
If the request is submitted from the command line, the result, in default format,
could look like:
Model : color500 Serial Number: 1023
Query requests should return all raw data for the specified item.
Note that service requests other than LIST and QUERY may also need to return multi-valued results in a marshalled structured data object. For example, the STAF PROCESS service's START request, when using the WAIT option, returns a marshalled object whose root object is a Map Class which represents the completion information for the process, including the process return code, key, and a list of returned files represented by a List of another Map Class. This shows that the structured data objects to be marshalled can contained nested maps, lists, etc.
So, for example, suppose a STAF local PROCESS START COMMAND "java TestA" RETURNSTDOUT RETURNSTDERR WAIT request is submitted from the command line, and assume that the process completed successfully and returned 0, that the standard output of the process was simply "Success !!!", and that the standard error of the process was blank. The result, in verbose format, could look like the following:
{ Return Code: 0 Key :Files : [ { Return Code: 0 Data : Success !!! } { Return Code: 0 Data : } ] }
5.0 Java STAF Services
5.1 How to write a Java STAF Service
Most Java STAF services will be comprised of a single Java class file. When you build your Java STAF service, it will be packaged in a special Jar file format so that STAF can easily load your service without requiring users of the service to update their CLASSPATH. It also allows you to use a separate JVM for your service.
Here is an example of what the beginning of a Java STAF service looks like:
package com.ibm.staf.service.deviceservice; // 1 // 2 import com.ibm.staf.*; // 3 import com.ibm.staf.service.*; // 4 // 5 public class DeviceService implements STAFServiceInterfaceLevel30 // 6
The first thing you need to do when creating a Java STAF Service is to create a unique package for your Java service (see line #1)
To access STAF within a Java STAF service, the service must import the STAF classes (see line #3 and line #4). These classes are defined in the JSTAF.jar file (which must be in your CLASSPATH).
Next, the Java service should implement the latest STAFServiceInterfaceLevel interface (see line #6). Note that the current Java interface for STAF V3.x is STAFServiceInterfaceLevel30.
Java STAF Services typically need to take no action during the Construction phase:
public DeviceService() {}
Java STAF Services must implement the init(STAFServiceInterfaceLevel30.InitInfo info) method
public STAFResult init(STAFServiceInterfaceLevel30.InitInfo info)
The STAFServiceInterfaceLevel30.InitInfo object contains the following fields:
Name | Type | Description |
---|---|---|
name | String | The name of the service |
parms | String | The parameters that have been passed to the service (via the PARMS option) |
serviceJar | JarFile | The service's jar file |
serviceType | int | The type of the service |
writeLocation | String | The name of the root directory where the service should write all service-related data (if it has any). |
Some of the functions that should be done during the initialization phase are:
During initialization, the Java STAF service needs to register with STAF.
Here is an example of the STAF registration:
try { fHandle = new STAFHandle("STAF/Service/" + info.name); } catch (STAFException e) { return new STAFResult(STAFResult.STAFRegistrationError, e.toString()); }
Note that it is recommended that your service's handle name should be "STAF/Service/" plus the name of your service.
During initialization, if your service supports any parameters, the service needs to process any parameters specified for the service via the PARMS option when registering the service in the STAF configuration file. Where appropriate, the service should resolve these parameters for STAF variables.
During initialization, the C++ STAF service should also register any error codes unique to this service with the Help service (if your service uses any unique error codes).
During initialization, if the service stores any persistent data, it should create a sub-directory named "service/<Service Name (lower-case)>", if it doesn't already exist, under the directory specified by the writeLocation field, that it can use to store its persistent data.
Temporary data can be stored in the tmp directory (or a sub-directory that you create within this directory) that exists in directory specified by the writeLocation field. This tmp directory will be deleted and recreated as an empty directory when STAF is restarted.
See section "4.14 Data Directory Structure" in the STAF User's Guide for more information on the structure of the STAF data directory and the standards for where data should be stored within it.
A Java STAF Service should create a command parser for each of the requests that it accepts (such as LIST, QUERY, ADD, etc.). Here is an example of the command parser creation:
private STAFCommandParser fListParser; private STAFCommandParser fQueryParser; ... public STAFResult init(STAFServiceInterfaceLevel30.InitInfo info) { ... fListParser = new STAFCommandParser(0, false); fQueryParser = new STAFCommandParser(0, false); ... }
The STAFCommandParser class has the following constructors:
public STAFCommandParser() public STAFCommandParser(int maxArgs) public STAFCommandParser(int maxArgs, boolean caseSensitive)
The STAFCommandParser constructors accept the following arguments:
Name | Type | Description |
---|---|---|
maxArgs | int | Always specify "0" for this argument |
caseSensitive | boolean | Indicates whether the command parser is case-sensitive. It is strongly recommended that your service's command parsers be case-insensitive. |
To add options to the command parser, use the addOption method:
public void addOption(String name, int maxAllowed, int valueRequirement)
The addOption method accepts the following arguments:
Name | Type | Description |
---|---|---|
name | String | The identifier string to be parsed |
maxAllowed | int | The number of times the argument name may be repeated in a space-separated string (0 is unlimited) |
valueRequirement | int | Specifies if there is a value associated with the name identifier
|
After adding all of the options for your service, you can specify the constraints on how the options can be specified:
public void addOptionGroup(String optionNames, int min, int max)
This method is used to specify a list of names that are to be treated as a group, and is typically used to specify groups of mutually exclusive options
The addOptionGroup method accepts the following parameters:
Name | Type | Description |
---|---|---|
optionNames | String | Specifies a list of names that are to be treated as a group |
min | int | The minimum number of names that can be included from the optionNames group in a space-separated string passed to the STAFCommandParser |
max | int | The maximum number of names that can be included from the optionNames group in a space-separated string passed to the STAFCommandParser |
You can specify the dependency relationships for options using the following method:
public void addOptionNeed(String needers, String needees)
This method is used to specify dependency relationships between names in space-separated strings passed to the STAFCommandParser:
The addOptionNeed method allows the following parameters:
Name | Type | Description |
---|---|---|
needers | String | If any of these options are specified, one of the options in the needees list must be specified |
needees | String | The list of needees, one of which must be specified for the needer(s) options |
Examples:
fQueryParser = new STAFCommandParser(); fQueryParser.addOption("QUERY", 1, STAFCommandParser.VALUENOTALLOWED); fQueryParser.addOption("PRINTER", 1, STAFCommandParser.VALUEREQUIRED); fQueryParser.addOption("MODEM", 1, STAFCommandParser.VALUEREQUIRED); fQueryParser.addOptionGroup("PRINTER MODEM", 0, 1); fQueryParser.addOptionNeed("PRINTER MODEM", "QUERY"); fQueryParser.addOptionNeed("QUERY", "PRINTER MODEM");
If a Java STAF service accepts requests such as LIST, QUERY, etc. that return multi-valued results, if any of the results should be represented in the form of a map class, then the service should create a map class definition for each of these maps in the initialization phase. The STAF/Service/ListDevice map class defines the field names, column headings, and the order in which the fields are displayed.
Here are examples of map class definitions for the results of LIST and QUERY request:
// Construct map class for the result from a LIST request. fListDeviceMapClass = new STAFMapClassDefinition( "STAF/Service/Device/ListDevice"); fListDeviceMapClass.addKey("name", "Name"); fListDeviceMapClass.addKey("type", "Type"); fListDeviceMapClass.addKey("model", "Model"); fListDeviceMapClass.addKey("serial#", "Serial Number"); fListDeviceMapClass.setKeyProperty( "serial#", "display-short-name", "Serial #"); // Construct map class for the result from a QUERY request. fQueryDeviceMapClass = new STAFMapClassDefinition( "STAF/Service/Device/QueryDevice"); fQueryDeviceMapClass.addKey("model", "Model"); fQueryDeviceMapClass.addKey("serial#", "Serial Number");Note that a "short" display name is specified for the "serial#" key in the map class definition for listing devices. A "short" display name is specified using the STAFMapClassDefinition class's addKey() method and by specifying the "display-short-name" property. Short display names may by used for column headings when the STAF executable displays the result in a tabular form if the total width of display names exceeds 80 characters.
The following methods can be called within the init() method to resolve STAF variables on the local machine:
Name | Type | Description |
---|---|---|
value | String | A string (e.g. request option's value) that may contain STAF variables to be resolved |
handle | STAFHandle | The STAF handle registered for use by your service |
Returns: A STAFResult, where STAFResult.rc is the return code and is STAFResult.Ok if successful, and where STAFResult.result is the resolved option value if successful and is an error message if not successful.
Name | Type | Description |
---|---|---|
option | String | The request option to be resolved |
value | String | A string (e.g. request option's value) that may contain STAF variables to be resolved |
handle | STAFHandle | The STAF handle registered for use by your service |
Returns: A STAFResult, where STAFResult.rc is the return code and is STAFResult.Ok if successful, and where STAFResult.result is the resolved option value if successful and is an error message if not successful.
Examples:
// Resolve the machine name variable for the local machine STAFResult res = STAFUtil.resolveInitVar( "{STAF/Config/Machine}", fHandle); if (res.rc != STAFResult.Ok) return res; fLocalMachineName = res.result; // Resolve the line separator variable for the local machine res = STAFUtil.resolveInitVar("{STAF/Config/Sep/Line}", fHandle); if (res.rc != STAFResult.Ok) return res; fLineSep = res.result; // Resolve the file separator variable for the local machine res = STAFUtil.resolveInitVar("{STAF/Config/Sep/File}", fHandle); if (res.rc != STAFResult.Ok) return res; String fileSep = res.result; // Resolve the value for the MAXATTEMPTS option in the parsed result // for the service parameters (PARMS) and verify that the value is // an integer. res = STAFUtil.resolveInitVarAndCheckInt( "MAXATTEMPTS", parsedResult.optionValue("MAXATTEMPTS"), fHandle); if (res.rc != STAFResult.Ok) return res; int fMaxAttempts = Integer.parseInt(res.result);
The service should create a static String variable, sHelpMsg, containing the help text for the service so that whenever a HELP request is submitted to the service, it can return sHelpMsg in the result. For example:
// Assign the help text string for the service sHelpMsg = "*** " + fServiceName + " Service Help ***" + fLineSep + fLineSep + "ADD < PRINTER <PrinterName> | MODEM <ModemName> > MODEL <Model> SN <Serial#>" + fLineSep + "DELETE < PRINTER <PrinterName> | MODEM <ModemName> > CONFIRM" + fLineSep + "LIST [PRINTERS] [MODEMS]" + fLineSep + "QUERY PRINTER <PrinterName> | MODEM <ModemName>" + fLineSep + "VERSION" + fLineSep + "HELP";
Now your service is ready to accept requests. Java STAF Services must implement the acceptRequest(STAFServiceInterfaceLevel30.RequestInfo info) method
public STAFResult acceptRequest(STAFServiceInterfaceLevel30.RequestInfo info)
The STAFServiceInterfaceLevel30.RequestInfo object contains the following fields:
Name | Type | Description |
---|---|---|
stafInstanceUUID | String | The UUID of the instance of STAF that submitted the request |
machine | String | The logical interface identifier for the machine from which the request originated (if tcp interface, it's the long host name) |
machineNickname | String | The machine nickname of the machine from which the request originated |
handleName | String | The registered name of the requesting STAF handle |
handle | int | The STAF Handle of the requesting process |
trustLevel | int | The trust level that the local machine has granted the requesting machine/user |
isLocalRequest | boolean | Is the request from the local system? |
diagEnabled | int | Indicates if diagnostics are enabled. 1=Enabled, 0=Disabled |
request | String | The actual request string |
requestNumber | int | The request number |
user | String | The user for the STAF Handle of the requesting process.
It has the following format: <Authenticator>://<User Identifier>
If the STAF Handle of the requesting process is not authenticated, this will be "none://anonymous". |
endpoint | String | The endpoint from which the request originated. It has the following
format: <Interface>://<Logical Interface ID>[<@Port>].
For example: tcp://client1.austin.ibm.com@6500, local://local |
physicalInterfaceID | String | The physical interface identifier for the machine from which the request originated (if tcp interface, it's the IP address) |
In the acceptRequest method, your service will typically determine the command that the request starts with, and call a corresponding handleCommand method (handleHelp, handleList, handleQuery, etc). It is also a good practice to have a try/catch block to catch any unexpected errors/exceptions when handling request commands and to return the stacktrace in the error message and to log the stack trace in the service's JVM log (along with the date/timestamp and the request that has being handled when the exception occurred).
public STAFResult acceptRequest(STAFServiceInterfaceLevel30.RequestInfo info) { // Try block is here to catch any unexpected errors/exceptions try { // Determine the command request (the first word in the request) String action; int spaceIndex = info.request.indexOf(" "); if (spaceIndex != -1) action = info.request.substring(0, spaceIndex); else action = info.request; String actionLC = action.toLowerCase(); // Call the appropriate method to handle the command request if (actionLC.equals("list")) return handleList(info); else if (actionLC.equals("query")) return handleQuery(info); else if (actionLC.equals("add")) return handleAdd(info); else if (actionLC.equals("delete")) return handleDelete(info); else if (actionLC.equals("help")) return handleHelp(info); else if (actionLC.equals("version")) return handleVersion(info); else { return new STAFResult( STAFResult.InvalidRequestString, "'" + action + "' is not a valid command request for the " + fServiceName + " service" + fLineSep + fLineSep + sHelpMsg); } } catch (Throwable t) { // Write the Java stack trace to the JVM log for the service System.out.println( sTimestampFormat.format(Calendar.getInstance().getTime()) + " ERROR: Exception on " + fServiceName + " service request:" + fLineSep + fLineSep + info.request + fLineSep); t.printStackTrace(); // And also return the Java stack trace in the result StringWriter sr = new StringWriter(); t.printStackTrace(new PrintWriter(sr)); if (t.getMessage() != null) { return new STAFResult( STAFResult.JavaError, t.getMessage() + fLineSep + sr.toString()); } else { return new STAFResult( STAFResult.JavaError, sr.toString()); } } }
Next, your service should implement the handleCommand methods.
private STAFResult handleList(STAFServiceInterfaceLevel30.RequestInfo info) private STAFResult handleQuery(STAFServiceInterfaceLevel30.RequestInfo info)
Each handleCommand method should first check the trust level for the incoming request to make sure that the requesting machine has the required trust level. Call the STAFUtil.validateTrust method to compare the required trust level with the actual trust level. If the requester has insufficient trust, it returns a STAFResult with the "Insuffient Trust Level" return code (25) and a result buffer containing an error message providing detailed information about the insufficient trust error.
Here is an example:
// Verify the requester has at least trust level 2 STAFResult trustResult = STAFUtil.validateTrust( 2, fServiceName, "LIST", fLocalMachineName, info); if (trustResult.rc != STAFResult.Ok) return trustResult;
The STAFUtil.validateTrust method accepts the following parameters
Name | Type | Description |
---|---|---|
requiredTrustLevel | int | The required trust level for this request |
service | String | The registered name for the service |
request | String | The option(s) identifying this request |
localMachineName | String | The logical identifer for the local machine (obtain by resolving variable {STAF/Config/Machine} in the init method by using the STAFUtil.resolveInitVar method) |
info | STAFServiceInterfaceLevel30.RequestInfo | Information about the request (including actual trust level, etc.) |
The handleCommand method should then parse the incoming request with its parser:
STAFCommandParseResult parsedRequest = fListParser.parse(info.request);
After the parse method executes, parsedResult will have the following instance variables:
Name | Type | Description |
---|---|---|
rc | int | This indicates whether the command was parsed successfully. Zero indicates a successful parse. Non-zero indicates an error. |
errorBuffer | String | If rc is non-zero, this will contain a textual description of the error. |
After parsing the incoming request, the following methods can be called on the parsedRequest object:
The optionTimes method accepts the following parameters
Name | Type | Description |
---|---|---|
name | String | The name of the option |
Returns: the number of times a particular option was specified
The optionValue method accepts the following parameters
Name | Type | Description |
---|---|---|
name | String | The name of the option |
Returns: the value of the first instance of an option. If no instance exists, an empty string is returned.
The optionValue method accepts the following parameters
Name | Type | Description |
---|---|---|
name | String | The name of the option |
instanceNumber | int | The instance number (one-indexed) of the option |
Returns: the value of a specific instance of an option. If the given instance does not exist, an empty string is returned.
Returns: the total number of options specified
This method, along with the following 2 methods, is useful when you need to do custom logic when parsing through the command options.
The instanceName method accepts the following parameters
Name | Type | Description |
---|---|---|
instanceNumber | int | The instance number (one-indexed) of the option |
Returns: The name of the option for the given instance number
The instanceValue method accepts the following parameters
Name | Type | Description |
---|---|---|
instanceNumber | int | The instance number (one-indexed) of the option |
Returns: The value of option for the given instance number
The following methods can be called to resolve STAF variables in a request option's value:
Name | Type | Description |
---|---|---|
value | String | A string (e.g. request option's value) that may contain STAF variables to be resolved |
handle | STAFHandle | The STAF handle registered for use by your service |
requestNumber | int | The request number (e.g. info.requestNumber) |
Returns: A STAFResult, where STAFResult.rc is the return code and is STAFResult.Ok if successful, and where STAFResult.result is the resolved option value if successful and is an error message if not successful.
Name | Type | Description |
---|---|---|
option | String | The request option to be resolved |
value | String | A string (e.g. request option's value) that may contain STAF variables to be resolved |
handle | STAFHandle | The STAF handle registered for use by your service |
requestNumber | int | The request number (e.g. info.requestNumber) |
Returns: A STAFResult, where STAFResult.rc is the return code and is STAFResult.Ok if successful, and where STAFResult.result is the resolved option value if successful and is an error message if not successful.
Examples:
// Resolve any STAF variables in the printer option's value STAFResult res = new STAFResult(); res = STAFUtil.resolveRequestVar( parsedRequest.optionValue("printer"), fHandle, info.requestNumber); if (res.rc != STAFResult.Ok) return res; String printer = res.result; // Resolve any STAF variables in the modem option's value res = STAFUtil.resolveRequestVar( parsedRequest.optionValue("modem"), fHandle, info.requestNumber); if (res.rc != STAFResult.Ok) return res; String modem = res.result; // Resolve any STAF variables in the MAXATTEMPTS option's value // and verify that the value is an integer. res = STAFUtil.resolveRequestVarAndCheckInt( "maxAttempts", parsedRequest.optionValue("maxAttempts"), fHandle, info.requestNumber); if (res.rc != 0) return res; int maxAttempts = Integer.parseInt(res.result);
If a request returns a multi-valued result, such as a LIST or QUERY request, you need to create a marshalled data structure to be returned in the result buffer.
Here's a code snippet from the handleList method for the sample Java service that generates a List of a Map Class to represent a list of printers and returns the marshalled result:
// Create a marshalling context and set any map classes (if any). STAFMarshallingContext mc = new STAFMarshallingContext(); mc.setMapClassDefinition(fListDeviceMapClass); // Create an empty result list to contain the result List resultList = new ArrayList(); // Add printer entries to the result list if (defaultList || printersOption > 0) { Iterator iter = fPrinterMap.keySet().iterator(); while (iter.hasNext()) { String key = (String)iter.next(); DeviceData data = (DeviceData)fPrinterMap.get(key); Map resultMap = fListDeviceMapClass.createInstance(); resultMap.put("name", key); resultMap.put("type", "Printer"); resultMap.put("model", data.model); resultMap.put("serial#", data.sn); resultList.add(resultMap); } } // Set the result list as the root object for the marshalling context // and return the marshalled result mc.setRootObject(resultList); return new STAFResult(STAFResult.Ok, mc.marshall());
Here's a code snippet from the handleQuery method for the sample Java service that generates a Map Class to represent detailed information about a printer and returns the marshalled result:
// Create a marshalling context and set any map classes (if any). STAFMarshallingContext mc = new STAFMarshallingContext(); mc.setMapClassDefinition(fQueryDeviceMapClass); // Create an empty result map to contain the result Map resultMap = fQueryDeviceMapClass.createInstance(); // Find the specified printer and add its info to the result map if (fPrinterMap.containsKey(printer)) { DeviceData data = (DeviceData)fPrinterMap.get(printer); resultMap.put("model", data.model); resultMap.put("serial#", data.sn); } else { return new STAFResult(STAFResult.DoesNotExist, printer); } // Set the result map as the root object for the marshalling context // and return the marshalled result mc.setRootObject(resultMap); return new STAFResult(STAFResult.Ok, mc.marshall());
Java STAF Services must implement the term() method. During this termination phase, the service should:
For example:
public STAFResult term() { try { // Un-register Help Data unregisterHelpData(kDeviceInvalidSerialNumber); // Un-register the service handle fHandle.unRegister(); } catch (STAFException ex) { return new STAFResult(STAFResult.STAFRegistrationError, ex.toString()); } return new STAFResult(STAFResult.Ok); }
Java STAF Services should take no action during the Deconstruction phase as the
Deconstruction phase is not externalized for Java services.
5.2 Building a Java STAF Service
When registering a Java service (either via the STAF configuration file or via a SERVICE service's ADD request), you will need to specify JSTAF for the LIBRARY option and for the EXECUTE option, you will neet to specify the fully-qualified name of the jar file that implements the service. Note that the jar file will be automatically added to the class path by JSTAF. For example, here's the SERVICE configuration line that could be added to the STAF configuration file to register a Java STAF service called Sample whose jar file is called sample.jar and resides in the C:\STAF\services directory:
SERVICE Sample LIBRARY JSTAF EXECUTE C:\STAF\services\STAFDeviceService.jarThe jar file that you create for a Java STAF Service must contain the class file(s) that comprise your service. The jar file must also include a manifest file which identifies which class within the JAR file is your service's entry point. This will be the class you created that implements STAFServiceInterfaceLevel30. You must provide this information with a manifest section that contain Name: and Service-Class headers, which have the form:
Manifest-Version: 1.0 Name: staf/service/info Service-Class: Service class nameThe value Service class name is the name of the class that is your service's entry point. Note that the Service class files must reside within a STAF-INF/classes directory in your jar file.
For example, if your service's entry class is DeviceService.class, your manifest file should contain the following:
Manifest-Version: 1.0 Name: staf/service/info Service-Class: DeviceServiceOr if DeviceService.class is in a package called com.ibm.staf.service.deviceservice, your manifest file should contain the following:
Manifest-Version: 1.0 Name: staf/service/info Service-Class: com.ibm.staf.service.deviceservice.DeviceServiceSimply use your favorite editor to create a file called MANIFEST.MF with the entries above.
Warning: The manifest text file must end with a new line or carriage return. The last line will not be parsed properly if it does not end with a new line or carriage return. Also, make sure to include a blank line before each class header line (e.g. lines beginning with "Name: (class file)").
You can create JAR files using the JDK jar tool. The Jar tool automatically puts a default manifest with pathname META-INF/MANIFEST.MF into any JAR file you create. To modify the default manifest when creating a jar file, you can specify the m command-line option to add custom information to the manifest during creation of a JAR file as follows:
jar cfm jar-file manifest-file input-file(s)where the options and arguments used in this command are defined as follows:
To create a jar file for a Java STAF Service called sample.jar file, that includes compressed versions of all of the class files in the STAF-INF/classes directory and updates the manifest file with the contents of the MANIFEST.MF file in the current directory, specify the following:
mkdir STAF-INF mkdir STAF-INF/classes javac -d STAF-INF/classes *.java jar cfm sample.jar MANIFEST.MF STAF-INF
Note that if you are using Java 1.5.0 or later (Oracle or IBM) to compile your Java STAF service, you will need to use at that version of Java or later when registering your service. If you attempt to use a JVM to register a Java STAF service that was built with an earlier version of Java, you will get the following error:
38:Error constructing service, JSTAF, Result: JSTAFSH.loadService(): Error loading the Java service. Verify you are using a valid version of Java (e.g. Oracle or IBM Java). java.lang.UnsupportedClassVersionError: com/ibm/staf/service/deviceservice/DeviceService (Unsupported major.minor version 49.0)
You can also package nested jar files within your service's jar file. If your service requires additional jar files, rather than having your service users download the jar file and add it to the CLASSPATH, you can nest the additional jar files within your service's jar file.
To include nested jar files, you must have the following entry in the service's MANIFEST.MF file:
Packaged-Jars: <list of space-separated jar file names, without the .jar suffix>
Here is an example of nesting xercesImpl.jar, xmlParserAPIs.jar, and jython.jar:
Packaged-Jars: xercesImpl xmlParserAPIs jython
Note that the nested jar files must reside in the STAF-INF/jars directory when creating the service jar file. For the example shown above, the service jar file would contain the following:
STAF-INF/jars/ STAF-INF/jars/xercesImpl.jar STAF-INF/jars/xmlParserAPIs.jar STAF-INF/jars/jython.jar
When you register your service with STAF, the embedded jar files should be located in:
{STAF/DataDir}/lang/java/service/<service-name>/jars
Note that you do not need to include the nested jar files in the CLASSPATH. STAF's class loader will be able to find the classes contained within the jar files.
You can also package resource files needed by your service within your service's jar file.
Put these resources in the STAF-INF/classes/resources directory when creating the service jar file. For example, if you packaged resource MyProperties.dtd, the service jar file would contain the following:
META-INF/MANIFEST.MF STAF-INF/classes/com/... STAF-INF/classes/resources/MyProperties.dtdThen from within your service, you can access this resource as follows:
ClassLoader cl = this.getClass().getClassLoader(); InputStream in = cl.getResourceAsStream("resources/MyProperties.dtd");When your service accesses packaged resources, be sure to use the class loader for your service as shown in the above code snippet.
You can package both STAF V3 and V2 versions of a service in a single jar file that can be used with either STAF V3 or STAF V2. To do this, in the manifest for the service, provide information for both versions of the service in two manifest sections as follows:
Note that the STAF V3 and V2 versions of the service must have unique class names as all the classes for both versions of the service are stored in the STAF-INF/classes directory.
For example:
Manifest-Version: 1.0 Name: staf/service3/info Service-Class: com.ibm.staf.service.deviceservice.DeviceService3 Name: staf/service/info Service-Class: com.ibm.staf.service.deviceservice.DeviceService
5.3 Example: Building the Sample Java STAF Service
To build the sample Java service provided with STAF, you first need to obtain its source code from the staf source tree in directory src/staf/services/sdg_sample. The following files are provided for the sample Java service:
DeviceService.java MANIFEST.MF makefile.deviceThe contents of DeviceService.java are shown in "Appendix B: Java STAF Service Example".
The manifest file for this sample Java service is contained in MANIFEST.MF and consists of the following:
Manifest-Version: 1.0 Name: staf/service/info Service-Class: com.ibm.staf.service.deviceservice.DeviceServiceThe makefile.device file is a make file that can be used to create a STAFDeviceService.jar file by typing the following from your build/src/staf directory. See the STAF Developer's Guide available from the STAF Documentation web page for more information on building STAF.
make PROJECTS=deviceThe output when running the make file should look like:
/cygdrive/d/build/src/staf $ make PROJECTS=device *** Compiling STAFDevice Service Java Sources *** *** Copying MANIFEST.MF *** *** Creating STAFDeviceService.jar ***
Alternatively, you can just copy the Java source code files from the staf source tree and compile/build the service manually. From the directory to which you copied the source files, run the following commands:
mkdir STAF-INF mkdir STAF-INF/classes javac -d STAF-INF/classes *.java jar cfm STAFDeviceService.jar MANIFEST.MF *To list the contents of the jar file, use the "jar tf" command:
$ jar tf STAFDeviceService.jar META-INF/ META-INF/MANIFEST.MF STAF-INF/ STAF-INF/classes/ STAF-INF/classes/com/ STAF-INF/classes/com/ibm/ STAF-INF/classes/com/ibm/staf/ STAF-INF/classes/com/ibm/staf/service/ STAF-INF/classes/com/ibm/staf/service/sdg_sample/ STAF-INF/classes/com/ibm/staf/service/deviceservice/ STAF-INF/classes/com/ibm/staf/service/deviceservice/DeviceService$DeviceData.class STAF-INF/classes/com/ibm/staf/service/deviceservice/DeviceService.classThe STAFDeviceService.jar file can then be used to register the Sample Java service. For example, assuming you put the STAFDeviceService.jar file in C:\STAF\services, you can add the following line to your STAF configuration file to register it:
SERVICE JSample LIBRARY JSTAF EXECUTE C:\STAF\services\STAFDeviceService.jarAfter restarting STAF, you could then make requests to this sample Java service. For example:
D:\>staf local JSample help Response -------- DeviceService Service Help ADD (PRINTER <printerName> | MODEM <modemName>) MODEL <model> SN <serial#> DELETE PRINTER <printerName> | MODEM <modemName> LIST [PRINTERS] [MODEMS] QUERY PRINTER <printerName> | MODEM <modemName> VERSION HELP
5.4 Debugging a Java STAF Service
This section gives you some tips on how to debug a STAF service that you've written in Java.
5.4.1 Run the STAF Java Service in a Separate JVM
Each STAF Java service runs in a JVM. You may run multiple STAF Java services in the same JVM. However, when developing and debugging a STAF Java service, it's usually best to run it in its own JVM by specifying OPTION JVMName=<serviceName> when registering each Java service. This means that each STAF Java service will have its own JVM Log and that if your service does something that kills the JVM, other STAF Java services that are running in other JVMs won't be effected. For more information on the JVMName option (and on other options used when registering a STAF Java service), see section 4.4.2 JSTAF service proxy library in the STAF V3 User's Guide.
All information logged by the JVM that a STAF Java service is running in is written to its JVM log. So, when a problem occurs with a STAF Java service, you should always check its JVM Log as it may contain information to help debug the problem.
Also, if you want to debug your STAF Java service by printing to stdout and/or stderr (e.g. System.out.println()), it's important to know that a STAF Java service's stdout/stderr are automatically redirected to your service's STAF JVM Log.
STAF stores JVM Log files in the {STAF/DataDir}/lang/java/jvm/<JVMName> directory. STAF retains a configurable number of JVM Logs (five by default) for each JVM. The current JVM log file is named JVMLog.1. For more information on how to view the JVM log for a Java service, see section 9.2 JVM Log Viewer Class in the STAF V3 User's Guide.
You can debug a STAF Java service using any debugger or IDE that uses JPDA (Java Platform Debugger Architecture). There are a lot of excellent debuggers and IDEs that use JPDA, including such widely recognized tools as Eclipse, Borland JBuilder, Oracle JDeveloper, Sun NetBeans, and many others. To debug your STAF Java service with a JPDA-compliant debugger, you need to run it in a Java VM with debug mode switched on and pass additional parameters to the JVM such as transport type, names of hosts and port number, and other information. All JPDA and debugging parameters must be passed as arguments to the JVM when registering your STAF Java service. For more information on how to debug your Java code using JPDA see:
To enable your STAF Java service to support remote Java debugging, you must pass arguments to the JVM used by your STAF service via the "J2" option described in section 4.4.2 JSTAF service proxy library in the STAF V3 User's Guide. The arguments that you specify depend on which JVM you're using and what version of the JVM. Once you enable the JVM your STAF Java service runs in to support remote Java debugging, you will be able use Eclipse or any other Java IDE or debugger that uses JPDA to debug your STAF Java Service.
Oracle's JVM implementations require command line options to load the JDWP agent for debugging. From 5.0 onwards the -agentlib:jdwp option is used to load and specify options to the JDWP agent. For releases prior to 5.0, the -Xdebug and -Xrunjdwp options are used (the 5.0 implementation also supports the -Xdebug and -Xrunjdwp options but the newer -agentlib:jdwp option is preferable as the JDWP agent in 5.0 uses the JVM TI interface to the VM rather than the older JVMDI interface).
If your debugger application uses the JDI Sun Command Line Launching Connector, the connector will use the -Xdebug and -Xrunjdwp options as the Connector may be used to connect to a pre-5.0 target VM.
If the target VM is 5.0 or newer the -agentlib:jdwp option is specified as follows:
For releases prior to 5.0 the -Xdebug and -Xrunjdwp options are used:
The sub-options are specified as follows:
-agentlib:jdwp=<name1>[=<value1>],<name2>[=<value2>]...
or
-Xrunjdwp:<name1>[=<value1>],<name2>[=<value2>]...
The following table describes the sub-options that can be used:
Name | Required? | Default Value | Description |
---|---|---|---|
transport | yes | none | Name of the transport to use in connecting to debugger application (e.g. dt_socket) |
server | no | "n" | If "y", listen for a debugger application to attach; if "n", attach
to the debugger application at the specified address.
When debugging a STAF Java service, always set it to "y". |
address | yes, if server=n
no, otherwise |
"" | Transport address for the connection. If server=n, attempt to attach to
debugger application at this address. If server=y, listen for a connection at
this address.
This is the port that will be open to allow for the Java debugger to attach to your STAF Java service. |
suspend | no | "y" | If "y", the JVM suspends the execution until a debugger connects to the debuggee
JVM.
When debugging a STAF Java Service init() issue, you'll probably want to set it to "y". Otherwise, setting it to "n" is recommended. |
See your JVM's documentation for more information on the JPDA and debugging parameters that should be passed as arguments to the JVM.
Examples:
service Device library JSTAF execute {STAF/Config/STAFRoot}/services/device/STAFDevice.jar \ OPTION JVMName=Device \ OPTION "J2=-Xdebug -Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=7777"
Or, to register this Java service dynamically via the SERVICE service, you could submit the following command (via the command line) assuming STAFProc is already running and the service is not already registered:
service Device library JSTAF execute {STAF/Config/STAFRoot}/services/device/STAFDevice.jar \ OPTION JVMName=Device \ OPTION "J2=-agentlib:jdwp=transport=dt_socket,suspend=n,server=y,address=7778"
Or, to register this Java service dynamically via the SERVICE service, you could submit the following command (via the command line) assuming STAFProc is already running and the service is not already registered:
The following steps can be used to configure Eclipse to enable remote Java debugging for your STAF Java service. Note that these steps are based on Eclipse 3.
To debug your STAF Java Service using Eclipse:
You may also want to consider building some debugging capabilities into your STAF service by having it write information to a service log. For example, services like STAX, EventManager, Cron, and Email all have a service log (this is not the service's JVM log, but a STAF log) to which they write information. So, you could have your service periodically submit a call to the STAF LOG service and submit a LOG request using a LOGNAME value which should contain the registered service name for your service. Then you could submit a QUERY request to the LOG service to view the contents of the service's log file. This allows you to have some debugging capabilities in your STAF service without having to rebuild it (after adding println's, etc). Of course, this could result in a lot of data being written to the service's log, so you would need to be aware of that when adding in the logging to your service. You can also use varying "log levels" in your STAF service, such as Debug and Error, so that you could configure the LOG serivce to only write Error records to the log file, and then dynamically change the LOG service's level mask to also write the Debug records to the log file (without having to reconfigure your STAF service). You can find more information in section 8.7 Log Service in the STAF V3 User's Guide.
A C++ STAF Service must adhere to the interface definition in STAFServiceInterface.h:
#include "STAFServiceInterface.h"
Each of the APIs that can be called can have varying levels associated with them depending on what level of information is to be returned.
Typically you need to include the following header files:
Header File Name | Description |
---|---|
STAF.h | Defines STAFHandle and STAFResult (used to register with STAF and submit requests to STAF) |
STAFMutexSem.h | Defines STAFMutexSem (used to enforce mutual exclusion to your service's data) |
STAFCommandParser.h | Defines STAFCommandParser and STAFCommandParserResult (use to parse your service's incoming requests) |
STAFServiceInterface.h | Defines the interface by which STAF communicates with external services |
DeviceService.h | Defines header information specific to your STAF service |
The very first include in your STAF service must be the STAF.h include file.
Note:
If you do a custom install of STAF (e.g. select the "Custom" instead of "Typical" install), and select to install "Service Developer Header Files and Libraries", these include files will be installed in the include directory in the root of the directory where you installed STAF. Or, you can download these include files from the CVS respository for STAF on SourceForge or you can download the entire source tree. See section "Obtaining the the STAF Source Code" in the STAF Developer's Guide available from the STAF Documentation web page for more information.
C++ STAF Services should define a structure titled ServiceServiceData, which will contain service-specific data, including STAFCommandParserPtr objects
struct DeviceServiceData { ... STAFString fName; // Registered service name STAFHandlePtr fHandlePtr; // Service's STAF handle STAFCommandParserPtr fListParser; STAFCommandParserPtr fQueryParser; ... };
All C++ STAF services must implement the following functions:
Function Name | Description |
---|---|
STAFServiceGetLevelBounds | This function is called to determine what data structure levels a service supports |
STAFServiceConstruct | This function is called to construct a service |
STAFServiceInit | This function is called to initialze a service |
STAFServiceAcceptRequest | This function is called to have the service handle a request |
STAFServiceTerm | This function is called to terminate a service |
STAFServiceDestruct | This function is called to destruct a service |
STAFRC_t STAFServiceGetLevelBounds(unsigned int levelID, unsigned int *minimum, unsigned int *maximum)
Typically, this function should contain:
{ switch (levelID) { case kServiceInfo: { *minimum = 30; *maximum = 30; break; } case kServiceInit: { *minimum = 30; *maximum = 30; break; } case kServiceAcceptRequest: { *minimum = 30; *maximum = 30; break; } case kServiceTerm: case kServiceDestruct: { *minimum = 0; *maximum = 0; break; } default: { return kSTAFInvalidAPILevel; } } return kSTAFOk; }
C++ STAF Services should implement the STAFServiceConstruct function.
STAFRC_t STAFServiceConstruct(STAFServiceHandle_t *pServiceHandle, void *pServiceInfo, unsigned int infoLevel, STAFString_t *pErrorBuffer)
The STAFServiceConstruct arguments are:
Name | Type | Description |
---|---|---|
pServiceHandle | STAFServiceHandle_t * | (OUT) A Pointer to the service's handle (this is used in all subsequent calls by STAF) |
pServiceInfo | vold * | (IN) A pointer to a ServiceInfo data structure |
infoLevel | unsigned int | (IN) The level of the ServiceInfo data structure (use the infoLevel to determine which STAFServiceInfoLevel structure to use, for example STAFServiceInfoLevel30) |
pErrorBuffer | STAFString_t * | (OUT) A pointer to an error string (this should only be set, and will only be freed by STAF, if the service returns a non-zero return code) |
The STAFServiceInfoLevel30 structure contains the following fields:
Name | Type | Description |
---|---|---|
name | STAFString_t | The name of the service |
exec | STAFString_t | The name of the executable that implements the service. This is used by proxy services that provide support for services in other languages. For example, this might be the Java class name that implements the service of the name of the Rexx script that implements the service. This value has no meaning for C/C++ services and may be ignored or used for any other purpose the service desires. |
writeLocation | STAFString_t | This specifies a directory in which STAF is allowed to write. |
serviceType | STAFServiceType_t | This specifies the type of service (e.g. regular service, service loader service, authenticator service). |
numOptions | unsigned int | This specifies how many options were specified for this service in the STAF.cfg file |
pOptionName | STAFString_t * | This is an array of "numOptions" STAFString_t's which contain the names of the options specified in the STAF.cfg file |
pOptionValue | STAFString_t * | This is an array of "numOptions" STAFString_t's which contain the values of the options specified in the STAF.cfg file |
During the Construction phase, C++ services should:
STAFRC_t rc = kSTAFUnknownError; try { if (infoLevel != 30) return kSTAFInvalidAPILevel; STAFServiceInfoLevel30 *pInfo = reinterpret_cast<STAFServiceInfoLevel30 *>(pServiceInfo); DeviceServiceData data; data.fDebugMode = 0; data.fShortName = pInfo->name; data.fName = "STAF/Service/"; data.fName += pInfo->name; for (unsigned int i = 0; i < pInfo->numOptions; ++i) { if (STAFString(pInfo->pOptionName[i]).upperCase() == "DEBUG") { data.fDebugMode = 1; } else { STAFString optionError(pInfo->pOptionName[i]); *pErrorBuffer = optionError.adoptImpl(); return kSTAFServiceConfigurationError; } } // Set service handle *pServiceHandle = new DeviceServiceData(data); return kSTAFOk; } catch (STAFException &e) { STAFString result; result += STAFString("In DeviceService.cpp: STAFServiceConstruct") + kUTF8_SCOLON; result += STAFString("Name: ") + e.getName() + kUTF8_SCOLON; result += STAFString("Location: ") + e.getLocation() + kUTF8_SCOLON; result += STAFString("Text: ") + e.getText() + kUTF8_SCOLON; result += STAFString("Error code: ") + e.getErrorCode() + kUTF8_SCOLON; *pErrorBuffer = result.adoptImpl(); } catch (...) { STAFString error( "DeviceService.cpp: STAFServiceConstruct: Caught " "unknown exception in STAFServiceConstruct()"); *pErrorBuffer = error.adoptImpl(); } return kSTAFUnknownError;
C++ STAF Services must implement the STAFServiceInit function
STAFRC_t STAFServiceInit(STAFServiceHandle_t serviceHandle, void *pInitInfo, unsigned int initLevel, STAFString_t *pErrorBuffer)
The STAFServiceInit arguments are:
Name | Type | Description |
---|---|---|
serviceHandle | STAFServiceHandle_t | (IN) The service's handle (obtained from STAFServiceConstruct) |
pInitInfo | void * | (IN) A pointer to a ServiceInit data structure |
initLevel | unsigned int | (IN) The level of the ServiceInit data structure |
pErrorBuffer | STAFString_t * | (OUT) A pointer to an error string (this should only be set, and will only be freed by STAF, if the service returns a non-zero return code) |
The STAFServiceInitLevel30 structure contains the following fields:
Name | Type | Description |
---|---|---|
parms | STAFString_t | The parameters specified for this service in the STAF.cfg file |
writeLocation | STAFString_t | This specifies a directory in which STAF is allowed to write. |
Some of the functions that should be done during the initialization phase are:
During initialization, the C++ STAF service needs to process any parameters specified for the service in the STAF.cfg file (if your service supports any parameters). Where appropriate, the service should resolve these parameters for STAF variables.
During initialization, the C++ STAF service should also register any error codes unique to this service with the Help service (if your service uses any unique error codes).
During initialization, if the service stores any persistent data, it should create a sub-directory named "service/<Service Name (lower-case)>", if it doesn't already exist, under the directory specified by the writeLocation field, that it can use to store its persistent data.
Temporary data can be stored in the tmp directory (or a sub-directory that you create within this directory) that exists in directory specified by the writeLocation field. This tmp directory will be deleted and recreated as an empty directory when STAF is restarted.
See section "4.14 Data Directory Structure" in the STAF 3 User's Guide for more information on the structure of the STAF data directory and the standards for where data should be stored within it.
A C++ STAF Service should create a command parser for each of the requests that it accepts (such as LIST, QUERY, ADD, etc.). Here is an example of the command parser creation:
pData->fListParser = STAFCommandParserPtr(new STAFCommandParser, STAFCommandParserPtr::INIT); pData->fQueryParser = STAFCommandParserPtr(new STAFCommandParser, STAFCommandParserPtr::INIT);
The STAFCommandParser class has the following constructor:
STAFCommandParser(unsigned int maxArgs = 0, bool caseSensitive = false);
The STAFCommandParser constructor accepts the following arguments:
Name | Type | Description |
---|---|---|
maxArgs | unsigned int | (In) Always specify "0" for this argument |
caseSensitive | bool | (In) Indicates whether the command parser is case-sensitive. It is strongly recommended that your service's command parsers be case-insensitive. |
To add options to the command parser, use the addOption function:
void addOption(const STAFString &option, unsigned int numAllowed, ValueRequirement valueReq);
The addOption function accepts the following arguments:
Name | Type | Description |
---|