STAF Service Developer's Guide

Software Testing Automation Framework (STAF) Service Developer's Guide

December 7, 2015

Version 3.4.2


Contents


1.0 Introduction

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_sample
The 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
        HELP
This 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.

3.0 Service Overview

All services are implemented via a C language interface. However, STAF provides proxy services for other languages, such as Java and Perl.

3.1 Service Life Cycle

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.

3.1.1 Initialization

During initialization, the command parser(s) will be created for the request options supported by the service. If the service accepts any optional parameters which affect its operation, they will be available during the initialization step; where appropriate, the service should resolve these parameters for STAF variables. If the service requires any additional threads, they should be created during this step.

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.

3.1.2 Accepting Requests

When the service is accepting requests, it will determine the command that the request starts with, and call a corresponding method (handleHelp, handleList, etc.). These method(s) will then parse the incoming command request, and based on the option(s) specified will perform the custom functionality provided by your service.

3.2 Variable Resolution

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.

3.3 Return Codes

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+.

3.4 Trust

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.

3.5 Request Naming Conventions

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").

3.5.1 Recommended Command Names

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

All STAF services must include HELP and VERSION options.

3.5.2 Recommended Option Names

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

3.6 Choosing a language for a STAF Service

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.

3.7 Thread termination

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.

3.8 Case Sensitivity

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.

3.9 Command Parsers

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.

3.10 Service Logging

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.


4.0 STAF Service Activities

The following are typical activities that STAF Services should perform during its life cycle.

4.1 Construction

4.2 Initialization

4.3 Accepting Requests

4.4 Termination

4.5 Deconstruction

4.1 Creating Command Parsers

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.

LIST Result

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.

QUERY Result

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.

Other Requests Can Returned Marshalled Results Too

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.

5.1.1 Construction Phase

Java STAF Services typically need to take no action during the Construction phase:

    public DeviceService() {}

5.1.2 Initialization Phase

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:

5.1.3 Accepting Requests Phase

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)

Check the Trust Level

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

Parse the Request

The handleCommand method should then parse the incoming request with its parser:

    STAFCommandParseResult parsedRequest = fListParser.parse(info.request);

Get Options Values and Resolve Variables in Option Values

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 following methods can be called to resolve STAF variables in a request option's value:

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

Creating and Returning a Marshalled 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());

5.1.4 Termination Phase

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);
    }

5.1.5 Deconstruction Phase

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.jar
The 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 name

The 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: DeviceService

Or 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.DeviceService

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

How to Package Nested Jar Files

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.

How to Package Resources in the Jar File

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.dtd
Then 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.

How to Package Both STAF V3 and V2 Versions of a Service in a Single Jar File

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.device
The 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.DeviceService

The 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=device
The 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.class
The 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.jar
After 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.

5.4.2 View the JVM Log

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.

5.4.3 Use a Debugger/IDE

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:

5.4.3.1 Enabling Debugging for a Java Service

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:

-agentlib:jdwp=<sub-options>
Loads the JPDA reference implementation of JDWP. This library resides in the target VM and uses JVM TI and JNI to interact with it. It uses a transport and the JDWP protocol to communicate with a separate debugger application. Specific sub-options are described below.

For releases prior to 5.0 the -Xdebug and -Xrunjdwp options are used:

-Xdebug
Enables debugging

-Xrunjdwp:<sub-options>
Loads the JPDA reference implementation of JDWP. This library resides in the target VM and uses JVMDI and JNI to interact with it. It uses a transport and the JDWP protocol to communicate with a separate debugger application. Specific sub-options are described below.

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.

-agentlib:jdwp=transport=dt_socket,suspend=n,server=y,address=7778
This is an example for Java 5.0 or later of the options to pass to the JVM to listen for a socket connection on port 7778.

-Xdebug -Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=7777
This is an example for Java 1.4.x of the options to pass to the JVM to listen for a socket connection on port 7777.

See your JVM's documentation for more information on the JPDA and debugging parameters that should be passed as arguments to the JVM.

Examples:

  1. Here's an example if using Oracle Java 1.4.x of how to register the STAF Java Device sample service in the STAF.cfg file so that it will support remote Java debugging using port 7777 when debugging during the acceptRequest phase: Then, when STAFProc is started, the Java service will be registered.

    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:

  2. Here's an example if using Oracle Java 5.0 or later of how to register the STAF Java Device sample service in the STAF.cfg file so that it will support remote Java debugging using port 7778 when debugging during the acceptRequest phase:

    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:

5.4.3.2 Debugging a STAF Java Service Using Eclipse

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.

  1. Click on the Java project that contains the STAF service you want to debug.
  2. Click Run -> Debug. The "Debug" dialog will come up.
  3. Click on "Remote Java Application" then click the New button. Notice a new sub-element was created with the project name.
  4. Change the Host: and Port: to correlate to the values that were set when configuring your STAF Java Service as described in section 5.4.3.1 Enable Debugging for Java Services.
  5. Click Apply and then click Close.

To debug your STAF Java Service using Eclipse:

  1. Register your STAF Java service via one of the following methods::

  2. Go to Eclipse and set a breakpoint in your STAF service code.

  3. Click Run -> Debug. Find the project you want to debug and click on it and then click Debug. Note the Debug dialog window will just go away after you click the Debug button.

  4. Click on the the "Debug" perspective in the upper right corner of the IDE and verify that the debugger is attached to your STAF Java Service.

  5. Next, submit a request to STAF Java service (e.g. via the command line) to start debugging.

  6. Eclipse will indicate that it has stopped execution on the line breakpoint.

5.4.4 Provide a Service Log

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.

 

6.0 C++ STAF Services

6.1 How to Write a C++ STAF Service

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

6.1.1 STAFServiceGetLevelBounds Function

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;
}

6.1.2 Construction Phase

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:

6.1.3 Initialization Phase

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:

6.1.4 Accepting Requests Phase

C++ STAF Services must implement the STAFServiceAcceptRequest method

    STAFRC_t STAFServiceAcceptRequest(STAFServiceHandle_t serviceHandle,
                                      void *pRequestInfo, unsigned int reqLevel,
                                      STAFString_t *pResultBuffer)

The STAFServiceAcceptRequest arguments are:

Name Type Description
serviceHandle STAFServiceHandle_t (IN) The service's handle (obtained from STAFServiceConstruct)
pRequestInfo void * (IN) A pointer to a ServiceRequest data structure
reqLevel unsigned int (IN) The level of the ServiceRequest 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 STAFServiceRequestLevel30 structure contains the following fields:

Name Type Description
stafInstanceUUID STAFString_t The UUID of the instance of STAF that submitted the request
machine STAFString_t The logical interface identifier for the machine from which the request originated (if tcp interface, it's the long host name)
machineNickname STAFString_t The machine nickname of the machine from which the request originated
handleName STAFString_t The registered name of the requesting STAF handle
handle STAFHandle_t The STAF Handle of the requesting process
trustLevel unsigned int The trust level that the local machine has granted the requesting machine/user
isLocalRequest unsigned int Is the request from the local system?
diagEnabled unsigned int Indicates if diagnostics are enabled. 1=Enabled, 0=Disabled
request STAFString_t The actual request string
requestNumber STAFRequestNumber_t The request number
user STAFString_t 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 STAFString_t 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 STAFString_t 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 function (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.

STAFRC_t STAFServiceAcceptRequest(STAFServiceHandle_t serviceHandle,
                                  void *pRequestInfo, unsigned int reqLevel,
                                  STAFString_t *pResultBuffer)
{
    if (reqLevel != 30) return kSTAFInvalidAPILevel;

    STAFRC_t retCode = kSTAFUnknownError;

    try
    {
        STAFResultPtr result(new STAFResult(),
                             STAFResultPtr::INIT);        

        STAFServiceRequestLevel30 *pInfo =
            reinterpret_cast(pRequestInfo);

        DeviceServiceData *pData =
            reinterpret_cast(serviceHandle);

        // Determine the command request (the first word in the request)

        STAFString request(pInfo->request);
        STAFString action = request.subWord(0, 1).toLowerCase();
        
        // Call functions for the request

        if (action == "add")
            result = handleAdd(pInfo, pData);
        else if (action == "delete")
            result = handleDelete(pInfo, pData);
        else if (action == "query")
            result = handleQuery(pInfo, pData);
        else if (action == "list")
            result = handleList(pInfo, pData);
        else if (action == "help")
            result = handleHelp(pInfo, pData);
        else if (action == "version")
            result = handleVersion(pInfo, pData);
        else
        {
            STAFString errMsg = STAFString("'") + request.subWord(0, 1) +
                "' is not a valid command request for the " +
                pData->fShortName + " service" + sLineSep + sLineSep +
                sHelpMsg;

            result = STAFResultPtr(new STAFResult(
                kSTAFInvalidRequestString, errMsg), STAFResultPtr::INIT);

        }

        *pResultBuffer = result->result.adoptImpl();
        retCode = result->rc;
    }
    catch (STAFException &e)
    {
        retCode = e.getErrorCode();

        STAFString result;
        
        result += STAFString("In DeviceService.cpp: STAFServiceAcceptRequest")
            + 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;
        
        *pResultBuffer = result.adoptImpl();
    }
    catch (...)
    {
        STAFString error("DeviceService.cpp: STAFServiceAcceptRequest: "
                         "Caught unknown exception");
        *pResultBuffer = error.adoptImpl();
    }

    return retCode;
}

Check the Trust Level

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. Here is an example which uses the VALIDATE_TRUST macro:

    // Verify the requester has at least trust level 3

    VALIDATE_TRUST(3, pData->fShortName, "ADD", pData->fLocalMachineName);
This macro can be used if the STAFServiceRequestLevel30 class containing the request information is named pInfo. This macro compares the required trust level with the actual trust level (pInfo->trustLevel). If the requester has insufficient trust, it returns a STAFResult with the "Insufficient Trust Level" return code (25) and a result buffer containing a detailed information about the insufficient trust error.

The VALIDATE_TRUST macro accepts the following parameters

Name Type Description
requiredTrustLevel unsigned int The required trust level for this request
service STAFString The registered name for the service
request STAFString The option(s) identifying this request
localMachineName STAFString The logical identifer for the local machine (obtain by resolving variable STAF/Config/Machine} in the init method)

Three other macros can be used to validate trust instead, if needed. They are:

Parse the Request

The handleCommand method should then parse the incoming request with its parser:

    STAFCommandParseResultPtr parsedResult = pData->fListParser->parse(
        pInfo->request);

After the parse method executes, parsedResult will have the following instance variables:

Name Type Description
rc STAFRC_t This indicates whether the command was parsed successfully. Zero indicates a successful parse. Non-zero indicates an error.
errorBuffer STAFString If rc is non-zero, this will contain a textual description of the error.

Get Options Values and Resolve Variables in Option Values

After parsing the incoming request, the following functions can be called on the parsedRequest object:

Examples:

    STAFResultPtr resultPtr = resolveOp(pInfo, pData, parsedResult, "PRINTER");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString printer = resultPtr->result;

    resultPtr = resolveOp(pInfo, pData, parsedResult, "MODEM");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString modem = resultPtr->result;

    . . .

    STAFResultPtr resolveOp(STAFServiceRequestLevel30 *pInfo,
                            DeviceServiceData *pData,
                            STAFCommandParseResultPtr &parsedResult,
                            const STAFString &fOption, unsigned int optionIndex)
    {
        STAFString optionValue = parsedResult->optionValue(fOption, optionIndex);

        if (optionValue.find(sLeftCurlyBrace) == STAFString::kNPos)
        {
            return STAFResultPtr(new STAFResult(kSTAFOk, optionValue),
                                 STAFResultPtr::INIT);
        }

        return resolveStr(pInfo, pData, optionValue);
    }

    STAFResultPtr resolveStr(STAFServiceRequestLevel30 *pInfo,
                             DeviceServiceData *pData,
                             const STAFString &theString)
    {
        return pData->fHandlePtr->submit(sLocal, sVar, sResStrResolve +
                                         STAFString(pInfo->requestNumber) +
                                         sString +
                                         pData->fHandlePtr->wrapData(theString));
    }

Creating and Returning a Marshalled 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 C++ 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).

    STAFObjectPtr mc = STAFObject::createMarshallingContext();
    mc->setMapClassDefinition(pData->fListDeviceMapClass->reference());

    // Create an empty result list to contain the result

    STAFObjectPtr resultList = STAFObject::createList();

    // Add printer entries to the result list

    if (printers || all)
    {
        STAFMutexSemLock lock(*pData->fPrinterMapSem);
        
        DeviceMap::iterator iter;

        for (iter = pData->fPrinterMap.begin(); 
             iter != pData->fPrinterMap.end(); ++iter)
        {
            STAFObjectPtr resultMap = pData->fListDeviceMapClass->createInstance();
            resultMap->put("name",    iter->second->name);
            resultMap->put("type",    "Printer");
            resultMap->put("model",   iter->second->model);
            resultMap->put("serial#", iter->second->serialNumber);

            resultList->append(resultMap);
        }
    }

    // Set the result list as the root object for the marshalling context
    // and return the marshalled result

    mc->setRootObject(resultList);

    return STAFResultPtr(new STAFResult(kSTAFOk, mc->marshall()),
                         STAFResultPtr::INIT);

Here's a code snippet from the handleQuery method for the sample C++ 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).

    STAFObjectPtr mc = STAFObject::createMarshallingContext();
    mc->setMapClassDefinition(pData->fQueryDeviceMapClass->reference());

    // Create an empty result map to contain the result

    STAFObjectPtr resultMap = pData->fQueryDeviceMapClass->createInstance();

    // Find the specified printer/modem and add its info to the result map

    STAFMutexSemLock lock(*pData->fPrinterMapSem);
        
    DeviceMap::iterator iter = pData->fPrinterMap.find(printer); 
        
    if (iter == pData->fPrinterMap.end())
    {
        return STAFResultPtr(new STAFResult(kSTAFDoesNotExist, printer),
                             STAFResultPtr::INIT);
    }
        
    resultMap->put("model",   iter->second->model);
    resultMap->put("serial#", iter->second->serialNumber);
    
    // Set the result map as the root object for the marshalling context
    // and return the marshalled result

    mc->setRootObject(resultMap);

    return STAFResultPtr(new STAFResult(kSTAFOk, mc->marshall()), 
                         STAFResultPtr::INIT);

6.1.5 Termination Phase

C++ STAF Services must implement the STAFServiceTerm function

    STAFRC_t STAFServiceTerm(STAFServiceHandle_t serviceHandle,
                             void *pTermInfo, unsigned int termLevel,
                             STAFString_t *pErrorBuffer)

6.1.6 Deconstrucation Phase

C++ STAF Services must implement the STAFServiceDestruct function

    STAFRC_t STAFServiceDestruct(STAFServiceHandle_t *serviceHandle,
                                 void *pDestructInfo, unsigned int destructLevel,
                                 STAFString_t *pErrorBuffer)

All C++ STAF services must delete their service data structure during deconstruction.

    DeviceServiceData *pData =
        reinterpret_cast(*serviceHandle);

    delete pData;
    *serviceHandle = 0;

6.2 Building a C++ STAF Service

When registering a C++ service (either via the STAF configuration file or via a SERVICE service's ADD request), you will need to specify the name of the shared library or DLL which implements the service. For example, here's the SERVICE configuration line that could be added to the STAF configuration file to register a C++ STAF service called Sample whose DLL or shared library file is called STAFDeviceService and resides in the library path.

SERVICE CSample LIBRARY STAFDeviceService

6.3 Example: Building the Sample C++ STAF Service

To build the sample C++ 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 C++ service:

DeviceService.cpp
DeviceService.h   (header file for the sample service)
DeviceService.def (only for Windows)
makefile.staf

Your C++ STAF Service's .def file should contain:

    EXPORTS
    STAFServiceGetLevelBounds
    STAFServiceConstruct
    STAFServiceInit
    STAFServiceAcceptRequest
    STAFServiceTerm
    STAFServiceDestruct
The contents of DeviceService.cpp are shown in "Appendix C: C++ STAF Service Example".

The makefile.staf file is a make file that can be used to create a STAFDeviceService shared library 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=staf
The output when running the make file should include the following:
/cygdrive/d/build/src/staf
$ make PROJECTS=staf
*** Linking shared library STAFDeviceService.dll ***
   Creating library d:\build\rel\win32\staf\retail\lib\STAFDeviceService.lib and
   object d:\build\rel\win32\staf\retail\lib\STAFDeviceService.exp
The STAFDeviceService DLL / shared library can then be used to register the Sample C++ service. For example, assuming the STAFDeviceService.dll file is in the library path, you can add the following line to your STAF configuration file to register it:
SERVICE CSample LIBRARY STAFDeviceService
After restarting STAF, you could then make requests to this sample C++ service. For example:
D:\>staf local CSample help
Response
--------
DEVICE 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
HELP

7.0 Perl STAF Services

7.1 How to write a Perl STAF Service

Most Perl STAF services will be comprised of a single Perl script file.

Perl STAF services will only work with Perl 5.8 or later.

Here is an example of what the beginning of a Perl STAF service looks like:

    package DeviceService;                                               // 1
                                                                         // 2
    use PLSTAFService;                                                   // 3
    use PLSTAF;                                                          // 4
    use 5.008;                                                           // 5
    use threads;                                                         // 6
    use threads::shared;                                                 // 7
    use Thread::Queue;                                                   // 8
                                                                         // 9
    use strict;                                                          // 10
    use warnings;                                                        // 11
                                                                         // 12
    use constant kDeviceInvalidSerialNumber => scalar 4001;              // 13
    use constant kVersion => scalar "1.0.0";                             // 14
                                                                         // 15
    # In this queue the master threads queue jobs for the slave worker   // 16
    my $work_queue = new Thread::Queue;                                  // 17
    my $free_workers : shared = 0;                                       // 18

The first thing you need to do when creating a Perl STAF Service is to create a unique package for your Perl service (see line #1)

To interact with STAF within a Perl STAF service, the service must import the PLSTAFService and PLSTAF modules (see lines #3 and #4).

Next, all Perl STAF services must use Perl v5.8 or above so that interpreter threads will work. Using threads and threads::shared will allow your requests to modify data without corrupting it (see lines #5, #6, #7, and #8). You should also craete a Queue to which the master thread will queue incoming service requests (see lines #17 and #18).

7.1.1 Construction Phase

Perl STAF Services typically need to take no action during the Construction phase, so the implementation of a Construct sub can be left out entirely, or left as such:

    sub Construct {}

7.1.2 Initialization Phase

Perl STAF Services must implement the new() sub. They will be passed a hash of init data.

    sub new{...}

The hash will contain the following name-value pairs:

Name Type Description
ServiceType String The type of the service
ServiceName String The name of the service
Params String The parameters that have been passed to the service (via the PARMS option)
WriteLocation String The name of the root directory where the service should write all service-related data (if it has any).

Registering a Service

During initialization, the Perl STAF service should initialize variables for handling multiplpe threads:

    my $self =
    {
        threads_list => [],
        worker_created => 0,
        max_workers => 5, # do not create more than 5 workers
    };

Registering a Service

During initialization, the Perl STAF service needs to register with STAF

Here is an example of the STAF registration:

    our $fhandle
    ...
    sub new
    {
    ...
    	$fhandle = STAF::STAFHandle->new("STAF/Service/" . $fServiceName);
    ...
    }

Note that it is recommended that your service's handle name should be "STAF/Service/" plus the name of your service.

Some of the functions that should be done during the initialization phase are:

7.1.3 Accepting Requests Phase

Now your service is ready to accept requests. Perl STAF Services must implement the AcceptRequest sub, and it recommended that you create worker threads as follows:

    sub AcceptRequest
    {
        my ($self, $info) = @_;
        my %hash : shared = %$info;

        if ($free_workers <= 0 and
            $self->{worker_created} < $self->{max_workers})
        {
            my $thr = threads->create(\&Worker);
            push @{ $self->{threads_list} }, $thr;
            $self->{worker_created}++;
        }
        else
        {
            lock $free_workers;
            $free_workers--;
        }

        $work_queue->enqueue(\%hash);

        return $STAF::DelayedAnswer;
    }

    sub Worker
    {
        my $loop_flag = 1;

        while ($loop_flag)
        {
            eval
            {
                # get the work from the queue
                my $hash_ref = $work_queue->dequeue();

                if (not ref($hash_ref) and $hash_ref->{request} eq 'stop')
                {
                    $loop_flag = 0;
                    return;
                }

                my ($rc, $result) = handleRequest($hash_ref);

                STAF::DelayedAnswer($hash_ref->{requestNumber}, $rc, $result);

                # increase the number of free threads
                {
                    lock $free_workers;
                    $free_workers++;
                }
            }
        }

        return 1;
    }
    
    sub handleRequest
    {
        my $info = shift;

        my $lowerRequest = lc($info->{request});
        my $requestType = "";

        # get first "word" in request
        if($lowerRequest =~ m/\b(\w*)\b/)
        {
            $requestType = $&;
        }
        else
        {
            return (STAFResult::kInvalidRequestString,
                "Unknown DeviceService Request: " . ($info->{request}));
        }

        if ($requestType eq "list")
        {
            return handleList($info);
        }
        . . . 
        . . .
        elsif ($requestType eq "help")
        {
            return handleHelp();
        }
        elsif ($requestType eq "version")
        {
            return handleVersion();
        }
        else
        {
            return (STAFResult::kInvalidRequestString,
                 "Unknown DeviceService Request: " . $info->{request});
        }

        return (0, "");
    }

AcceptRequest will be passed a hash containing the following name-value pairs:

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 handleRequest sub, your service will typically determine the command that the request starts with, and call a corresponding handleCommand method (handleHelp, handleList, handleQuery, etc.)

Next, your service should implement the handleCommand methods.

    sub handleList{...}
    sub handleQuery{...}

Check the Trust Level

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. Here is an example:

    if (info->{"trustLevel"} < 2)
    {
        return (STAFResult::kAccessDenied,
            "Trust level 2 required for LIST request. Requesting " .
            "machine's trust level: " .  $info->{trustLevel});
    }

Parse the Request

The handleCommand method should then parse the incoming request with its parser:

    my $parsedRequest = $fListParser->parse($info->{request});

After the parse method executes, $parsedRequest will contain the following name-value pairs:

Name Description
rc This indicates whether the command was parsed successfully. Zero indicates a successful parse. Non-zero indicates an error.
errorBuffer If rc is non-zero, this will contain a textual description of the error.

Get Options Values and Resolve Variables in Option Value

After parsing the incoming request, the following methods can be called on the parsedRequest object:

Examples:

    my $resolveResult;
    my $printerValue;
    my $modemValue;

    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("printer"),
                                  $info->{requestNumber});

    if ($resolveResult->{rc} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $printerValue = $resolveResult->{result};

    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("modem"),
                                  $info->{requestNumber});

    if ($resolveResult->{rc} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $modemValue = $resolveResult->{result};

Note: You need to be explicit when sharing data between Perl v5.8 threads, and you can't assign an unshared variable to a shared one. This means that variables need to be recursively shared inside blessed objects. For example:

package DeviceData;

sub new
{
   my $type = shift;
   my $model : shared = shift;
   my $sn : shared = shift;

   my $obj = &threads::shared::share({});

   $obj->{model} = $model;
   $obj->{sn} = $sn;

   bless ($obj, $type);

   return $obj;
}

In this situation, if a DeviceData object is going to be shared between threads, or it is going to be put into a hash or array that is shared between threads, you need to share it's members.

Creating and Returning a Marshalled Result

Here is an example of creating and returning a marshalled result:

    # create a marshalling context with testList and one map class definition

    my $mc = STAF::STAFMarshallingContext->new();
    $mc->setMapClassDefinition($listDeviceMapClass);

    my @myDeviceList;

    # is 'printers' specified?
    $printersOption = $parsedRequest->optionTimes("printers");

    # is 'modems' specified?
    $modemsOption = $parsedRequest->optionTimes("modems");

    my $defaultList = 0;

    # if neither is specified, default is to show everything
    if ($printersOption == 0 && $modemsOption == 0)
    {
        $defaultList = 1;
    }

    # list all the printers
    if ($defaultList || ($printersOption > 0))
    {
        # XXX: does it make sense to have this lock here?
        # In perl, sometimes accessing vars in a way that
        # seems to be leave the variable unchanged on the surface
        # actually changes things behind the scenes, but it's
        # "hard to know when" according to perl docs
        lock(%printerMap);
        foreach my $key (sort keys %printerMap)
        {
           my $data = $printerMap{$key};
           my $deviceMap = $listDeviceMapClass->createInstance();
           $deviceMap->{'name'} = $key;
           $deviceMap->{'type'} = 'Printer';
           $deviceMap->{'model'} = $data->{'model'};
           $deviceMap->{'serial#'} = $data->{'sn'};
           push @myDeviceList, $deviceMap;
        }
    }

    # list all the modems
    if ($defaultList || ($modemsOption > 0))
    {
        # XXX: does it make sense to have this lock here?
        # see above comment
        lock(%modemMap);
        foreach my $key (sort keys %modemMap)
        {
           my $data = $modemMap{$key};
           my $deviceMap = $listDeviceMapClass->createInstance();
           $deviceMap->{'name'} = $key;
           $deviceMap->{'type'} = 'Modem';
           $deviceMap->{'model'} = $data->{'model'};
           $deviceMap->{'serial#'} = $data->{'sn'};
           push @myDeviceList, $deviceMap;
        }
    }

    $mc->setRootObject(\@myDeviceList);

    return (STAFResult::kOk, $mc->marshall());

7.1.4 Termination/Destruction Phase

Perl STAF Services must implement the DESTROY() sub. This sub should stop all threads, perform any service cleanup, unregister any Help data, and unregister with STAF. For example:

sub DESTROY
{
    my ($self) = @_;

    # Ask all the threads to stop, and join them.
    for my $thr (@{ $self->{threads_list} })
    {
        $work_queue->enqueue('stop');
    }

    # perform any cleanup for the service here

    unregisterHelpData(kDeviceInvalidSerialNumber);

    #Un-register the service handle
    $fHandle->unRegister();

# XXX:  The following block will cause a trap if multiple
# STAFCommandParsers have been created
#    for my $thr (@{ $self->{threads_list} })
#    {
#        eval { $thr->join() };
#        print STDERR "On destroy: $@\n" if $@;
#    }
}

7.2 Building a Perl STAF Service

There is no build required for Perl STAF services, since you simply use the .pl file you have created for your Perl service.

7.3 Registering a Perl STAF Service

When registering a Perl service (either via the STAF configuration file or via a SERVICE service's ADD request), you must use the PLSTAF service proxy library. You should also use the STAFEXECPROXY service proxy library so that if a Perl service has a fatal error which terminates the Perl interpreter, the STAFProc executable won't also be terminated. You can do this by specifying LIBRARY STAFEXECPROXY and by specifying OPTION PROXYLIBRARY=PLSTAF. For the EXECUTE option, specify the name of the .pm file (without the .pm extension) that implements the service. For OPTION USELIB, specify the directory that contains the .pm file.

For example, here's the SERVICE configuration line that could be added to the STAF configuration file to register a Perl STAF service as a service called Sample whose .pm file is named DeviceService.pm and resides in the C:\STAF\services directory:

SERVICE Sample LIBRARY STAFEXECPROXY EXECUTE DeviceService \
               OPTION PROXYLIBRARY=PLSTAF OPTION USELIB=C:\STAF\services
Or, to register this Perl STAF service dynamically after STAFProc has been started using the SERVICE service's ADD request:

STAF local SERVICE ADD SERVICE Sample LIBRARY STAFEXECPROXY EXECUTE DeviceService OPTION PROXYLIBRARY=PLSTAF OPTION USELIB=C:\STAF\services

Notes: In order to have STAF load a Perl service, the following environment variables must be set prior to starting STAFProc:

  1. Environment variable PERLLIB must be set to include the directory containing the PLSTAF.pm and PLSTAFService.pm files (located in the "bin" directory in the STAF installation root directory).

  2. The operating system's library path environment variable (e.g. PATH on Windows, LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on Mac OS X) must be set to include the directory containing the PLSTAF library (e.g. PLSTAF.dll on Windows, libPLSTAF.so on Linux, libPLSTAF.dylib on Mac OS X).

  3. When configuring Perl services on Linux with Perl 5.8, environment variable LD_LIBRARY_PATH must include the directory containing the Perl 5.8 libperl.so file. For example, if it was located in directory /opt/ActivePerl-5.8/lib/CORE:
        export LD_LIBRARY_PATH=/opt/ActivePerl-5.8/lib/CORE:$LD_LIBRARY_PATH

For more information on registering a Perl service, see sections "4.4.3 PLSTAF service proxy library" and "4.4.4 STAFEXECPROXY service proxy library" in the STAF V3 User's Guide. Also, see the Perl User's Guide for STAF V3 because you must first have STAF Perl support installed and configured correctly before you can register a STAF service written in Perl.

Once STAFProc has been started and the Perl STAF service has been registered, you can then make requests to this sample Perl service. For example:

C:\>STAF local Sample HELP
Response
--------
Sample 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

8.0 Service Loaders

Service loaders are external services whose purpose is to load services on-demand. They allow services to be loaded only when they have been requested, so they don't take up memory until needed. They also allow dynamic service registration when a request is made so that you don't have to change the STAF configuration file to register a service.

You write a service loader service just like you would write other STAF services. However, they are called differently (a user would not send requests to a service loader service).

Service loader services accept the following request, which should only be sent by STAF:

    LOAD SERVICE <service>

When a request is encountered for a service that doesn't exist, STAF will call each service loader, in the order they were configured, until the service exists or we run out of service loaders. If we run out of service loaders, then the standard RC:2 will be returned. Otherwise, the request will be sent to the newly added service.

If you are writing a single custom STAF service, you would not typically write a service loader service to load the service. However, if you are writing a set of custom STAF services, you may want to write a service loader service that can load all of your custom STAF services. This would allow your custom STAF service users to simply register the service loader service rather than registering all of the custom STAF services.

It is recommended that your write service loader services in C++.

A default service loader service (STAFDSLS) is shipped with STAF, and it can dynamically load the Log, Monitor, and ResPool services. This service will automatically be configured in your STAF.cfg file. It is not possible to dynamically add support to the default service loader service to load additional services.

You may create custom default serviceloader services, which can dynamically load your custom STAF services.

The serviceloader service must support the following service request:

LOAD SERVICE <service name>

If the serviceloader service determines that it can load the specified service, it should take whatever action necessary to access the service binary file(s), and then dynamically add the service via the SERVICE service.


Appendix A: Java STAFServiceInterfaceLevel30, STAFCommandParser, STAFCommandParseResult

Here is the Java STAFServiceInterfaceLevel30 interface found at /staf/lang/java/service/STAFServiceInterfaceLevel30.java:

package com.ibm.staf.service;

import com.ibm.staf.*;
import java.util.jar.JarFile;

// STAFServiceInterfaceLevel30 - This class defines the Level 30 interface for
//                               STAF Java services.
//
// This interface defines the following methods
//
//   init - This method is called once when STAF is in startup mode.  This
//          method allows the service to perform its initialization.
//
//   acceptRequest - This is the method called whenever the service needs to
//                   perform a request.  This is primary method by which
//                   programs/users interact with the service.
//
//   term - This method is called once when STAF is in shutdown mode.  This
//          method allows the service to perform any cleanup.

public interface STAFServiceInterfaceLevel30
{
    static public final int serviceTypeUnknown = 0;
    static public final int serviceTypeService = 1;
    static public final int serviceTypeServiceLoader = 2;
    static public final int serviceTypeAuthenticator = 3;

    static public class InitInfo
    {
        public String name;
        public String parms;
        public JarFile serviceJar;
        public int serviceType;
        public String writeLocation;

        public InitInfo(String name, String parms, JarFile serviceJar,
                        int serviceType, String writeLocation)
        {
            this.name = name;
            this.parms = parms;
            this.serviceJar = serviceJar;
            this.serviceType = serviceType;
            this.writeLocation = writeLocation;
        }
    }

    static public class RequestInfo
    {
        public String  stafInstanceUUID;
        public String  machine;
        public String  machineNickname;
        public String  handleName;
        public int     handle;
        public int     trustLevel;
        public boolean isLocalRequest;
        public int     diagEnabled;
        public String  request;
        public int     requestNumber;
        public String  user;
        public String  endpoint;
        public String  physicalInterfaceID;

        public RequestInfo(String stafInstanceUUID, String machine,
                           String machineNickname, String handleName,
                           int handle, int trustLevel,
                           boolean isLocalRequest, int diagEnabled,
                           String request, int requestNumber,
                           String user, String endpoint,
                           String physicalInterfaceID)
        {
            this.stafInstanceUUID = stafInstanceUUID;
            this.machine = machine;
            this.machineNickname = machineNickname;
            this.handleName = handleName;
            this.handle = handle;
            this.trustLevel = trustLevel;
            this.isLocalRequest = isLocalRequest;
            this.diagEnabled = diagEnabled;
            this.request = request;
            this.requestNumber = requestNumber;
            this.user = user;
            this.endpoint = endpoint;
            this.physicalInterfaceID = physicalInterfaceID;
        }
    }

    STAFResult init(InitInfo initInfo);
    STAFResult acceptRequest(RequestInfo reqInfo);
    STAFResult term();
}

Here are the public methods and variables for the Java STAFCommandParser class found in /staf/lang/java/service/STAFCommandParser.java:

// STAFCommandParser - This class implements the standard STAF parser used by
//                     all STAF services.

public class STAFCommandParser
{
    // Here are the constructors:
    // - The caseSensitive argument defaults to false if not specified
    // - Always specify "0" for the maxArgs argument.
    //
    public STAFCommandParser()
    public STAFCommandParser(int maxArgs)
    public STAFCommandParser(int maxArgs, boolean caseSensitive)

    // This class provides the follwoing static final variables

    // VALUEREQUIRED:    A value is required for the option
    // VALUENOTALLOWED:  A value is not allowed for the option
    // VALUEALLOWED:     A value may be specified for the option, but is
    //                   not required
    public static final int VALUEREQUIRED = 0;
    public static final int VALUENOTALLOWED = 1;
    public static final int VALUEALLOWED = 2;

    // This class provides the following methods

    // addOption: Adds options to the command parser
    //   Arguments:
    //     name             - The name of the option to be parsed
    //     maxAllowed       - The number of times the argument name may be
    //                        repeated in a space-separated string
    //     valueRequirement - Specifies if there is a value associated with
    //                        the name identifier.  It must be one of the
    //                        following:
    //                        - STAFCommandParser.VALUEREQUIRED 
    //                        - STAFCommandParser.VALUENOTALLOWED  
    //                        - STAFCommandParser.VALUEALLOWED
    //
    public void addOption(String name, int maxAllowed, int valueRequirement)

    // addOptionGroup:  Specifies constraints on how command parser options
    //                  can be specified.  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.
    //   Arguments:
    //     optionNames - Specifies a list of names that are to be treated as a
    //                   group
    //     min         - The minimum number of names that can be included from
    //                   the optionNames group in a space-separated string
    //                   passed to the STAFCommandParser  
    //     max         - The maximum number of names that can be included from
    //                   the optionNames group in a space-separated string
    //                   passed to the STAFCommandParser 
    //
    public void addOptionGroup(String optionNames, int min, int max)

    // addOptionNeed:  Specifies the dependency relationships for command
    //                 parser options.  The options names are specified in
    //                 space-separated strings.
    //   Arguments:
    //     needers - Specifies one or more "needer" option names (in a space-
    //               separated string) that need one of the options in the
    //               needees list.  If any of these options are specified,
    //               one of the options in the needees list must also be
    //               specified.
    //     needees - Specifies one or more "needee" option names (in a space-
    //               separated string) that be specified for the needer(s)
    //               options. 
    //
    public void addOptionNeed(String needers, String needees)

    // parse:  Parses the request string using the command parser
    //   Arguments:
    //     data - the request string
    //
    public STAFCommandParseResult parse(String data)
}

Here are the public methods and variables for the Java STAFCommandParseResult class found in /staf/lang/java/service/STAFCommandParseResult.java:


// STAFCommandParseResult - This class contains the results of parsing a
//                          command string with the STAFCommandParser.

public class STAFCommandParseResult
{
    // Here are the constructors
    // - The caseSensitive argument defaults to false if not specified

    STAFCommandParseResult()
    STAFCommandParseResult(boolean caseSensitive)

    // This class provides the following instance data
    //
    //   rc - This indicates whether the command was parsed successfully.  Zero
    //        indicates a successful parse.  Non-zero indicates an error.
    //
    //   errorBuffer - If rc is non-zero, this will contain a textual description
    //                 of the error.

    public int rc;
    public String errorBuffer;

    // This class provides the following methods

    // optionTimes: Returns the number of times the option was specified
    //              in the command.
    //   Arguments:
    //     name - the name of the option
    //
    public int optionTimes(String name)

    // optionValue: Returns the value of a specific instance (instanceNumber)
    //              of an option (name). If the given option instance number
    //              does not exist, an empty string is returned.
    //   Arguments:
    //     name - the name of the option
    //     instanceNumber - the number specifying the instance of the option
    //                      Note that instance numbers begin with 1 (not 0).
    //                      Optional.  Defaults to 1 if not specified.
    //
    public String optionValue(String name)
    public String optionValue(String name, int instanceNumber)
    
    // numInstances: Returns the total number of options specified.
    //               Note that this method, along with the instanceName and
    //               instanceValue methods, is useful when you need to do
    //               custom logic when parsing through the command options.
    //
    public int numInstances()

    // instanceName: Returns the name of the option for the given instance
    //               number.
    //   Arguments:
    //     instanceNumber - the number specifying the instance of the option
    //                      Note that instance numbers begin with 1 (not 0).
    //
    public String instanceName(int instanceNumber)

    // instanceValue: Returns the value of option for the given instance
    //                number.
    //   Arguments:
    //     instanceNumber - the number specifying the instance of the option
    //                      Note that instance numbers begin with 1 (not 0).
    //
    public String instanceValue(int instanceNumber)

Appendix B: Java STAF Service Example

Here is a complete example of a sample Java STAF service (DeviceService.java):

/*****************************************************************************/
/* Software Testing Automation Framework (STAF)                              */
/* (C) Copyright IBM Corp. 2001, 2004, 2005                                  */
/*                                                                           */
/* This software is licensed under the Eclipse Public License (EPL) V1.0.    */
/*****************************************************************************/

package com.ibm.staf.service.deviceservice;

import com.ibm.staf.*;
import com.ibm.staf.service.*;
import java.util.Calendar;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.Iterator;
import java.text.SimpleDateFormat;
import java.io.PrintWriter;
import java.io.StringWriter;

public class DeviceService implements STAFServiceInterfaceLevel30
{
    private final String kVersion = "3.4.0";

    private String fServiceName;
    private STAFHandle fHandle;
    private String fLocalMachineName = "";

    // Define any error codes unique to this service
    private static final int kDeviceInvalidSerialNumber = 4001;

    private final static SimpleDateFormat sTimestampFormat = 
        new SimpleDateFormat("yyyyMMdd-HH:mm:ss");

    // STAFCommandParsers for each request
    private STAFCommandParser fListParser;
    private STAFCommandParser fQueryParser;
    private STAFCommandParser fAddParser;
    private STAFCommandParser fDeleteParser;

    // Map Class Definitions used to create marshalled results
    public static STAFMapClassDefinition fListDeviceMapClass;
    public static STAFMapClassDefinition fQueryDeviceMapClass;

    private static String sHelpMsg;
    private String fLineSep;
    private TreeMap fPrinterMap = new TreeMap();
    private TreeMap fModemMap = new TreeMap();

    public DeviceService() {}

    public STAFResult init(STAFServiceInterfaceLevel30.InitInfo info)
    {
        try
        {
            fServiceName = info.name;
            fHandle = new STAFHandle("STAF/Service/" + info.name);
        }
        catch (STAFException e)
        {
            return new STAFResult(STAFResult.STAFRegistrationError,
                                  e.toString());
        }

        // ADD parser
        
        fAddParser = new STAFCommandParser();

        fAddParser.addOption("ADD", 1,
                             STAFCommandParser.VALUENOTALLOWED);

        fAddParser.addOption("PRINTER", 1,
                             STAFCommandParser.VALUEREQUIRED);

        fAddParser.addOption("MODEL", 1,
                             STAFCommandParser.VALUEREQUIRED);

        fAddParser.addOption("SN", 1,
                             STAFCommandParser.VALUEREQUIRED);

        fAddParser.addOption("MODEM", 1,
                             STAFCommandParser.VALUEREQUIRED);

        // this means you can have PRINTER or MODEM, but not both
        fAddParser.addOptionGroup("PRINTER MODEM", 0, 1);

        // if you specify ADD, MODEL is required
        fAddParser.addOptionNeed("ADD", "MODEL");

        // if you specify ADD, SN is required
        fAddParser.addOptionNeed("ADD", "SN");

        // if you specify PRINTER or MODEM, ADD is required
        fAddParser.addOptionNeed("PRINTER MODEM", "ADD");

        // if you specify ADD, PRINTER or MODEM is required
        fAddParser.addOptionNeed("ADD", "PRINTER MODEM");

        // LIST parser

        fListParser = new STAFCommandParser();

        fListParser.addOption("LIST", 1,
                              STAFCommandParser.VALUENOTALLOWED);

        fListParser.addOption("PRINTERS", 1,
                              STAFCommandParser.VALUENOTALLOWED);

        fListParser.addOption("MODEMS", 1,
                              STAFCommandParser.VALUENOTALLOWED);

        // QUERY parser

        fQueryParser = new STAFCommandParser();

        fQueryParser.addOption("QUERY", 1,
                               STAFCommandParser.VALUENOTALLOWED);

        fQueryParser.addOption("PRINTER", 1,
                               STAFCommandParser.VALUEREQUIRED);

        fQueryParser.addOption("MODEM", 1,
                               STAFCommandParser.VALUEREQUIRED);

        // This means you can have PRINTER or MODEM, but not both
        fQueryParser.addOptionGroup("PRINTER MODEM", 0, 1);

        // If you specify PRINTER or MODEM, QUERY is required
        fQueryParser.addOptionNeed("PRINTER MODEM", "QUERY");

        // If you specify QUERY, PRINTER or MODEM is required
        fQueryParser.addOptionNeed("QUERY", "PRINTER MODEM");

        // DELETE parser

        fDeleteParser = new STAFCommandParser();

        fDeleteParser.addOption("DELETE", 1,
                               STAFCommandParser.VALUENOTALLOWED);

        fDeleteParser.addOption("PRINTER", 1,
                                STAFCommandParser.VALUEREQUIRED);

        fDeleteParser.addOption("MODEM", 1,
                                STAFCommandParser.VALUEREQUIRED);

        fDeleteParser.addOption("CONFIRM", 1,
                               STAFCommandParser.VALUENOTALLOWED);

        // This means you must have PRINTER or MODEM, but not both
        fDeleteParser.addOptionGroup("PRINTER MODEM", 0, 1);

        // If you specify PRINTER or MODEM, DELETE is required
        fDeleteParser.addOptionNeed("PRINTER MODEM", "DELETE");

        // If you specify DELETE, PRINTER or MODEM is required
        fDeleteParser.addOptionNeed("DELETE", "PRINTER MODEM");

        // If you specify DELETE, CONFIRM is required
        fDeleteParser.addOptionNeed("DELETE", "CONFIRM");

        // 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");

        STAFResult res = new STAFResult();

        // 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 machine name variable for the local machine

        res = STAFUtil.resolveInitVar("{STAF/Config/Machine}", fHandle);

        if (res.rc != STAFResult.Ok) return res;
        
        fLocalMachineName = res.result;

        // 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";

        // Register Help Data

        registerHelpData(
            kDeviceInvalidSerialNumber,
            "Invalid serial number",
            "A non-numeric value was specified for serial number");

        return new STAFResult(STAFResult.Ok);
    }

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

    private STAFResult handleHelp(STAFServiceInterfaceLevel30.RequestInfo info)
    {
        // Verify the requester has at least trust level 1

        STAFResult trustResult = STAFUtil.validateTrust(
            1, fServiceName, "HELP", fLocalMachineName, info);

        if (trustResult.rc != STAFResult.Ok) return trustResult;

        // Return help text for the service

        return new STAFResult(STAFResult.Ok, sHelpMsg);
    }

    private STAFResult handleVersion(
        STAFServiceInterfaceLevel30.RequestInfo info)
    {
        // Verify the requester has at least trust level 1

        STAFResult trustResult = STAFUtil.validateTrust(
            1, fServiceName, "VERSION", fLocalMachineName, info);

        if (trustResult.rc != STAFResult.Ok) return trustResult;

        // Return the service's version

        return new STAFResult(STAFResult.Ok, kVersion);
    }

    private STAFResult handleAdd(STAFServiceInterfaceLevel30.RequestInfo info)
    {
        // Verify the requester has at least trust level 3

        STAFResult trustResult = STAFUtil.validateTrust(
            3, fServiceName, "ADD", fLocalMachineName, info);

        if (trustResult.rc != STAFResult.Ok) return trustResult;

        // Parse the request

        STAFCommandParseResult parsedRequest = fAddParser.parse(info.request);

        if (parsedRequest.rc != STAFResult.Ok)
        {
            return new STAFResult(STAFResult.InvalidRequestString,
                                  parsedRequest.errorBuffer);
        }

        // 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 model option's value

        res = STAFUtil.resolveRequestVar(
            parsedRequest.optionValue("model"), fHandle, info.requestNumber);

        if (res.rc != STAFResult.Ok) return res;
        
        String model = res.result;

        // Resolve any STAF variables in the sn option's value

        res = STAFUtil.resolveRequestVar(
            parsedRequest.optionValue("sn"), fHandle, info.requestNumber);

        if (res.rc != STAFResult.Ok) return res;

        String sn = res.result;

        // Verify that the serial number is numeric

        try
        {
            new Integer(sn);
        }
        catch (NumberFormatException e)
        {
            // Note that instead of creating a new error code specific for
            // this service, should use STAFResult.InvalidValue instead, but
            // wanted to demonstrate the registration of a service error code.
            return new STAFResult(kDeviceInvalidSerialNumber, sn);
        }

        // Add the device to the printer map or the modem map and
        // write an informational message to the service log

        if (!printer.equals(""))
        {
            synchronized (fPrinterMap)
            {
                fPrinterMap.put(printer, new DeviceData(model, sn));
            }

            String logMsg = "ADD PRINTER request.  Name=" + printer +
                            " Model=" + model + " Serial#=" + sn;

            fHandle.submit2(
                "local", "LOG", "LOG MACHINE LOGNAME " + fServiceName +
                " LEVEL info MESSAGE " + STAFUtil.wrapData(logMsg));
        }
        else if (!modem.equals(""))
        {
            synchronized (fModemMap)
            {
                fModemMap.put(modem, new DeviceData(model, sn));
            }

            String logMsg = "ADD MODEM request.  Name=" + modem +
                            " Model=" + model + " Serial#=" + sn;

            fHandle.submit2(
                "local", "LOG", "LOG MACHINE LOGNAME " + fServiceName +
                " LEVEL info MESSAGE " + STAFUtil.wrapData(logMsg));
        }

        return new STAFResult(STAFResult.Ok);
    }

    private STAFResult handleList(STAFServiceInterfaceLevel30.RequestInfo info)
    {
        // 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;

        // Parse the request

        STAFCommandParseResult parsedRequest = fListParser.parse(info.request);

        if (parsedRequest.rc != STAFResult.Ok)
        {
            return new STAFResult(STAFResult.InvalidRequestString,
                                  parsedRequest.errorBuffer);
        }

        // Check if specified printers or modems

        int printersOption = parsedRequest.optionTimes("printers");
        int modemsOption = parsedRequest.optionTimes("modems");

        boolean defaultList = false;

        if (printersOption == 0 && modemsOption == 0)
        {
            defaultList = true;
        }

        // 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);
            }
        }

        // Add modem entries to the result list

        if (defaultList || modemsOption > 0)
        {
            Iterator iter = fModemMap.keySet().iterator();

            while (iter.hasNext())
            {
                String key = (String)iter.next();

                DeviceData data = (DeviceData)fModemMap.get(key);

                Map resultMap = fListDeviceMapClass.createInstance();

                resultMap.put("name", key);
                resultMap.put("type", "Modem");
                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());
    }

    private STAFResult handleQuery(STAFServiceInterfaceLevel30.RequestInfo info)
    {
        // Verify the requester has at least trust level 2

        STAFResult trustResult = STAFUtil.validateTrust(
            2, fServiceName, "QUERY", fLocalMachineName, info);

        if (trustResult.rc != STAFResult.Ok) return trustResult;

        // Parse the request

        STAFCommandParseResult parsedRequest = fQueryParser.parse(info.request);

        if (parsedRequest.rc != STAFResult.Ok)
        {
            return new STAFResult(STAFResult.InvalidRequestString,
                                  parsedRequest.errorBuffer);
        }
        
        // 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;

        // 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/modem and add its info to the result map

        if (!printer.equals(""))
        {
            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);
            }
        }
        else if (!modem.equals(""))
        {
            if (fModemMap.containsKey(modem))
            {
                DeviceData data = (DeviceData)fModemMap.get(modem);
                
                resultMap.put("model", data.model);
                resultMap.put("serial#", data.sn);
            }
            else
            {
                return new STAFResult(STAFResult.DoesNotExist, modem);
            }
        }

        // 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());
    }

    private STAFResult handleDelete(STAFServiceInterfaceLevel30.RequestInfo info)
    {
        // Verify the requester has at least trust level 4

        STAFResult trustResult = STAFUtil.validateTrust(
            4, fServiceName, "DELETE", fLocalMachineName, info);

        if (trustResult.rc != STAFResult.Ok) return trustResult;

        // Parse the request

        STAFCommandParseResult parsedRequest = fDeleteParser.parse(info.request);

        if (parsedRequest.rc != STAFResult.Ok)
        {
            return new STAFResult(STAFResult.InvalidRequestString,
                                  parsedRequest.errorBuffer);
        }

        // Resolve any STAF variables in the print 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;

        // Find the device in the printer or modem map and remove it and
        // write an informational message to the service log

        if (!printer.equals(""))
        {
            synchronized (fPrinterMap)
            {
                if (fPrinterMap.containsKey(printer))
                    fPrinterMap.remove(printer);
                else
                    return new STAFResult(STAFResult.DoesNotExist, printer);
            }

            String logMsg = "DELETE PRINTER request.  Name=" + printer;

            fHandle.submit2(
                "local", "LOG", "LOG MACHINE LOGNAME " + fServiceName +
                " LEVEL info MESSAGE " + STAFUtil.wrapData(logMsg));
        }
        else if (!modem.equals(""))
        {
            synchronized (fModemMap)
            {
                if (fModemMap.containsKey(modem))
                    fModemMap.remove(modem);
                else
                    return new STAFResult(STAFResult.DoesNotExist, modem);
            }

            String logMsg = "DELETE MODEM request.  Name=" + modem;

            fHandle.submit2(
                "local", "LOG", "LOG MACHINE LOGNAME " + fServiceName +
                " LEVEL info MESSAGE " + STAFUtil.wrapData(logMsg));
        }

        return new STAFResult(STAFResult.Ok);
    }

    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);
    }

    // Register error codes for this service with the HELP service

    private void registerHelpData(int errorNumber, String info,
                                  String description)
    {
        STAFResult res = fHandle.submit2(
            "local", "HELP", "REGISTER SERVICE " + fServiceName +
            " ERROR " + errorNumber +
            " INFO " + STAFUtil.wrapData(info) +
            " DESCRIPTION " + STAFUtil.wrapData(description));
    }

    // Un-register error codes for this service with the HELP service

    private void unregisterHelpData(int errorNumber)
    {
        STAFResult res = fHandle.submit2(
            "local", "HELP", "UNREGISTER SERVICE " + fServiceName +
            " ERROR " + errorNumber);
    }

    public class DeviceData
    {
        public String model = "";
        public String sn = "";

        public DeviceData(String model, String sn)
        {
            this.model = model;
            this.sn = sn;
        }
    }
}

Appendix C: C++ STAFServiceInterface, STAFCommandParser, and STAFCommandParseResult

Here is the /staf/stafif/STAFServiceInterface.h interface:

#ifndef STAF_ServiceInterface 
#define STAF_ServiceInterface

#include "STAF.h"
#include "STAFString.h"

/*********************************************************************/
/* This header defines the interface by which STAF communicates with */
/* external services.  Before calling the service directly, STAF     */
/* will call STAFServiceGetLevelBounds to determine which structure  */
/* levels the service supports.  Currently, STAF supports the        */
/* following data structure levels:                                  */
/*                                                                   */
/*    ServiceInfo     - 30                                           */
/*    ServiceInit     - 30                                           */
/*    ServiceRequest  - 30                                           */
/*    ServiceTerm     - 0                                            */
/*    ServiceDestruct - 0                                            */
/*                                                                   */
/* In the cases where STAF only supports structure level 0, a NULL   */
/* pointer is passed into the service for the structure pointer.     */
/*********************************************************************/

#ifdef __cplusplus
extern "C" {
#endif

typedef unsigned int STAFRequestNumber_t;
typedef unsigned int STAFTrustLevel_t;
typedef void * STAFServiceHandle_t;

enum STAFServiceLevelID { kServiceInfo = 0, kServiceInit = 1,
                          kServiceAcceptRequest = 2, kServiceTerm = 3,
                          kServiceDestruct = 4 };

typedef enum
{
    kSTAFServiceTypeUnknown       = 0,  // Unknown service type
    kSTAFServiceTypeService       = 1,  // Regular service
    kSTAFServiceTypeServiceLoader = 2,  // Service Loader Service
    kSTAFServiceTypeAuthenticator = 3   // Authenticator Service
} STAFServiceType_t;

/**********************************************************************/
/* STAF passes in this structure on a STAFServiceConstruct call. The  */
/* data members have the following meanings:                          */
/*                                                                    */
/*   name          - The name of the service                          */
/*   exec          - 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 - This specifies a directory in which STAF is      */
/*                   allowed to write.                                */
/*   serviceType   - This specifies the type of service (e.g. regular */
/*                   service, service loader service, authenticator   */
/*                   service)                                         */
/*   numOptions    - This specifies how many options were specified   */
/*                   for this service in the STAF.cfg file            */
/*   pOptionName   - This is an array of "numOptions" STAFString_t's  */
/*                   which contain the names of the options specified */
/*                   in the STAF.cfg file                             */
/*   pOptionValue  - This is an array of "numOptions" STAFString_t's  */
/*                   which contain the values of the options          */
/*                   specified in the STAF.cfg file                   */
/**********************************************************************/
struct STAFServiceInfoLevel30
{
    STAFString_t name;
    STAFString_t exec;
    STAFString_t writeLocation;
    STAFServiceType_t serviceType;
    unsigned int numOptions;
    STAFString_t *pOptionName;
    STAFString_t *pOptionValue;
};


/*********************************************************************/
/* STAF passes in this structure on a STAFServiceInit call.  The     */
/* data members have the following meanings:                         */
/*                                                                   */
/*   parms         - The parameters specified for this service in    */
/*                   the STAF.cfg file                               */
/*   writeLocation - This specifies a directory in which STAF is     */
/*                   allowed to write.                               */
/*********************************************************************/
struct STAFServiceInitLevel30
{
    STAFString_t parms;
    STAFString_t writeLocation;
};


/*********************************************************************/
/* STAF passes in this structure on a STAFServiceAcceptRequest call. */
/* The data members have the following meanings:                     */
/*                                                                   */
/*   stafInstanceUUID - The UUID of the instance of STAF that        */
/*                      submitted the request                        */
/*   machine          - The logical interface identifier for the     */
/*                      machine from which the request originated    */
/*                      (if tcp interface, it's the long host name)  */
/*   machineNickname  - The machine nickname of the machine from     */
/*                      which the request originated                 */
/*   handleName       - The registered name of the STAF handle       */
/*   handle           - The STAF Handle of the requesting process    */
/*   trustLevel       - The trust level of the requesting process    */
/*   isLocalRequest   - Is the request from the local system         */
/*   diagEnabled      - Indicates if diagnostics are enabled or not  */
/*                      1=Enabled, 0=Disabled                        */
/*   request          - The actual request string                    */
/*   requestNumber    - The request number of the service request    */
/*   authenticator    - If the STAF Handle of the requesting process */
/*                      is authenticated, this is the name of its    */
/*                      Authenticator service, or "none" if the      */
/*                      handle is not authenticated.                 */
/*   userIdentifier   - If the STAF Handle of the requesting process */
/*                      is authenticated, this is the user identifier*/
/*                      which was authenticated, or "anonymous" if   */
/*                      the handle is not authenticated.             */
/*   user             - If the STAF Handle of the requesting process */
/*                      is authenticated, this is the                */
/*                      user (authenticator://userIdentifier) or     */
/*                      "none://anonymous" if the handle is not      */
/*                      authenticated.                               */
/*   endpoint         - The endpoint from which the request          */
/*                      originated in the following format:          */
/*                        interface://logicalInterfaceID@port        */
/*   physicalInterfaceID - The physical interface identifier for the */
/*                      machine from which the request originated    */
/*                      (if tcp interface, it's the long host name)  */
/*********************************************************************/
struct STAFServiceRequestLevel30
{
    STAFString_t        stafInstanceUUID;
    STAFString_t        machine;
    STAFString_t        machineNickname;
    STAFString_t        handleName;
    STAFHandle_t        handle;
    unsigned int        trustLevel;
    unsigned int        isLocalRequest;
    unsigned int        diagEnabled;
    STAFString_t        request;
    STAFRequestNumber_t requestNumber;
    STAFString_t        user;
    STAFString_t        endpoint;
    STAFString_t        physicalInterfaceID;
};


/*********************************************************************/
/* STAFServiceGetLevelBounds - This function is called to determine  */
/*                             what data structure levels a service  */
/*                             supports.                             */
/*                                                                   */
/* Accepts: (IN)  The data structure ID (one of the enumeration      */
/*                  values in STAFServiceLevelID)                    */
/*          (OUT) A pointer to the minimum structure level supported */
/*          (OUT) A pointer to the maximum structure level supported */
/*                                                                   */
/* Returns:  kSTAFOk, if successful                                  */
/*********************************************************************/
STAFRC_t STAFServiceGetLevelBounds(unsigned int levelID,
                                   unsigned int *minimum,
                                   unsigned int *maximum);


/*********************************************************************/
/* STAFServiceConstruct - This function is called to construct a     */
/*                        service.                                   */
/*                                                                   */
/* Accepts: (OUT) A Pointer to the service's handle (this is used in */
/*                  all subsequent calls by STAF)                    */
/*          (IN)  A pointer to a ServiceInfo data structure          */
/*          (IN)  The level of the ServiceInfo data structure        */
/*          (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)          */
/*                                                                   */
/* Returns:  kSTAFOk, if successful                                  */
/*********************************************************************/
STAFRC_t STAFServiceConstruct(STAFServiceHandle_t *pServiceHandle,
                              void *pServiceInfo, unsigned int infoLevel,
                              STAFString_t *pErrorBuffer);


/*********************************************************************/
/* STAFServiceInit - This function is called to initialize a         */
/*                   service.                                        */
/*                                                                   */
/* Accepts: (IN)  The service's handle (obtained from                */
/*                  STAFServiceConstruct)                            */
/*          (IN)  A pointer to a ServiceInit data structure          */
/*          (IN)  The level of the ServiceInit data structure        */
/*          (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)          */
/*                                                                   */
/* Returns:  kSTAFOk, if successful                                  */
/*********************************************************************/
STAFRC_t STAFServiceInit(STAFServiceHandle_t serviceHandle,
                         void *pInitInfo, unsigned int initLevel,
                         STAFString_t *pErrorBuffer);


/*********************************************************************/
/* STAFServiceAcceptRequest - This function is called to have the    */
/*                            service handle a request.              */
/*                                                                   */
/* Accepts: (IN)  The service's handle (obtained from                */
/*                  STAFServiceConstruct)                            */
/*          (IN)  A pointer to a ServiceRequest data structure       */
/*          (IN)  The level of the ServiceRequest data structure     */
/*          (OUT) A pointer to the request's result buffer (this     */
/*                  should also be set, even if it is an empty       */
/*                  string, as STAF will always try to destruct this */
/*                  string)                                          */
/*                                                                   */
/* Returns: The return code of the request (this should one of the   */
/*          return codes defined in STAFError.h or be 4000+)         */
/*********************************************************************/
STAFRC_t STAFServiceAcceptRequest(STAFServiceHandle_t serviceHandle,
                                  void *pRequestInfo, unsigned int reqLevel,
                                  STAFString_t *pResultBuffer);


/*********************************************************************/
/* STAFServiceTerm - This function is called to terminate a service. */
/*                                                                   */
/* Accepts: (IN)  The service's handle (obtained from                */
/*                  STAFServiceConstruct)                            */
/*          (IN)  A pointer to a ServiceTerm data structure          */
/*          (IN)  The level of the ServiceTerm data structure        */
/*          (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)          */
/*                                                                   */
/* Returns:  kSTAFOk, if successful                                  */
/*********************************************************************/
STAFRC_t STAFServiceTerm(STAFServiceHandle_t serviceHandle,
                         void *pTermInfo, unsigned int termLevel,
                         STAFString_t *pErrorBuffer);


/*********************************************************************/
/* STAFServiceDestruct - This function is called to destruct a       */
/*                       service.                                    */
/*                                                                   */
/* Accepts: (IN)  The service's handle (obtained from                */
/*                  STAFServiceConstruct)                            */
/*          (IN)  A pointer to a ServiceDestruct data structure      */
/*          (IN)  The level of the ServiceDestruct data structure    */
/*          (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)          */
/*                                                                   */
/* Returns:  0, if successful                                        */
/*          >0, if unsuccessful (this should be one of the errors    */
/*              defined in STAFError.h or be 4000+)                  */
/*********************************************************************/
STAFRC_t STAFServiceDestruct(STAFServiceHandle_t *serviceHandle,
                             void *pDestructInfo,
                             unsigned int destructLevel,
                             STAFString_t *pErrorBuffer);


/***********************************/
/* Define typedefs for use by STAF */
/***********************************/

typedef STAFRC_t (*STAFServiceGetLevelBounds_t)(unsigned int levelID,
    unsigned int *minimum, unsigned int *maximum);

typedef STAFRC_t (*STAFServiceConstruct_t)(
    STAFServiceHandle_t *pServiceHandle, void *pServiceInfo,
    unsigned int infoLevel, STAFString_t *pErrorBuffer);

typedef STAFRC_t (*STAFServiceInit_t)(STAFServiceHandle_t serviceHandle,
    void *pInitInfo, unsigned int initLevel, STAFString_t *pErrorBuffer);

typedef STAFRC_t (*STAFServiceAcceptRequest_t)(
    STAFServiceHandle_t serviceHandle, void *pRequestInfo,
    unsigned int reqLevel, STAFString_t *pResultBuffer);

typedef STAFRC_t (*STAFServiceTerm_t)(STAFServiceHandle_t serviceHandle,
    void *pTermInfo, unsigned int termLevel, STAFString_t *pErrorBuffer);

typedef STAFRC_t (*STAFServiceDestruct_t)(
    STAFServiceHandle_t *serviceHandle, void *pDestructInfo,
    unsigned int destructLevel, STAFString_t *pErrorBuffer);

#ifdef __cplusplus
}
#endif

#endif

Here are the public methods and variables for the C++ STAFCommandParser class found in /staf/stafif/STAFCommandParser.h and .cpp:

// STAFCommandParser - This class provides a parsing interface for STAF, in
//                     particular, STAF services

class STAFCommandParser
{
public:

    // This enum is used to determine if an option may, may not, or must
    // have a value associated with it

    enum ValueRequirement { kValueNotAllowed = 0, kValueAllowed = 1,
                            kValueRequired = 2};

    // The constructor accepts an array of options and a count of those
    // options, along with an indication of case sensitivity to option names

    STAFCommandParser(unsigned int maxArgs = 0, bool caseSensitive = false);


    void addOption(const STAFString &option, unsigned int numAllowed,
                   ValueRequirement valueReq);
    
    void addOptionGroup(const STAFString &group, unsigned int minAllowed,
                        unsigned int maxAllowed);

    void addOptionNeed(const STAFString &needers, const STAFString &needees);

    // Parses a given string.  Returns 0, if successful, > 0, otherwise.
    // ErrorBuffer will be set if unsuccessful.

    STAFCommandParseResultPtr parse(const STAFString &parseString);

    ~STAFCommandParser();

    STAFCommandParser_t getImpl() { return fParserImpl; }
};

Here are the public methods and variables for the C++ STAFCommandParseResult class found in /staf/stafif/STAFCommandParser.h and .cpp:

// STAFCommandParseResult - This class is returned from a STAFCommandParser.
//                          It represents the results of parsing a string via
//                          the STAFCommandParser.

class STAFCommandParseResult
{
public:

    // The return code from the parse
    STAFRC_t rc;

    // The description of the error when rc != 0
    STAFString errorBuffer;

    // Returns the number of times an option was specified
    unsigned int optionTimes(const STAFString &optionName);

    // Returns the value of a given instance of a given option.
    // Returns an empty string if no such option or instance exists.
    STAFString optionValue(const STAFString &optionName,
                           unsigned int number = 1);
    
    // Returns the total number of options specified in the string
    unsigned int numInstances();
    
    // Returns the name of the given option instance
    STAFString instanceName(unsigned int number);
    
    // Returns the value of the given option instance
    STAFString instanceValue(unsigned int number);
    
    // Returns the number of extra arguments
    unsigned int numArgs();
    
    // Returns a given argument.  Returns an empty string if no such argument
    // exists.
    STAFString arg(unsigned int number);

    ~STAFCommandParseResult();

    STAFCommandParseResult_t getImpl() { return fResultImpl; }

 private:

    friend class STAFCommandParser;

    STAFCommandParseResult(STAFCommandParseResult_t theResult,
        STAFRC_t theRC, const STAFString &theErrorBuffer)
        : rc(theRC), errorBuffer(theErrorBuffer), fResultImpl(theResult)
    { /* Do Nothing */ }
};

typedef STAFRefPtr STAFCommandParseResultPtr;

Appendix D: C++ STAF Service Example

Here is a complete example of a sample C++ STAF service (DeviceService.h and DeviceService.cpp):

Here is DeviceService.h:

/*****************************************************************************/
/* Software Testing Automation Framework (STAF)                              */
/* (C) Copyright IBM Corp. 2001                                              */
/*                                                                           */
/* This software is licensed under the Eclipse Public License (EPL) V1.0.    */
/*****************************************************************************/

#ifndef STAF_DeviceService
#define STAF_DeviceService

#ifdef __cplusplus
extern "C"
{
#endif

typedef enum STAFDeviceError_e
{
    // add service-specific return codes here
    kDeviceInvalidSerialNumber = 4001
} STAFDeviceError_t;


#ifdef __cplusplus
}
#endif

#endif

Here is DeviceService.cpp:

/*****************************************************************************/
/* Software Testing Automation Framework (STAF)                              */
/* (C) Copyright IBM Corp. 2001, 2004, 2005                                  */
/*                                                                           */
/* This software is licensed under the Eclipse Public License (EPL) V1.0.    */
/*****************************************************************************/

#include "STAF.h"
#include <deque>
#include <map>
#include "STAFMutexSem.h"
#include "STAFCommandParser.h"
#include "STAFServiceInterface.h"
#include "STAFUtil.h"
#include "DeviceService.h"

// Device Data - contains data for a device

struct DeviceData
{
    DeviceData()
    { /* Do Nothing */ }

    DeviceData(const STAFString &aName, const STAFString &aModel,
               const STAFString &aSN) :
         name(aName), model(aModel), serialNumber(aSN)
    { /* Do Nothing */ }

    STAFString   name;            // Device name
    STAFString   model;           // Device model
    STAFString   serialNumber;    // Device serial number
};

typedef std::deque<DeviceData> DeviceList;

typedef STAFRefPtr<DeviceData> DeviceDataPtr;

// DeviceMap -- KEY:   Device name,
//              VALUE: Pointer to DeviceData information

typedef std::map<STAFString, DeviceDataPtr> DeviceMap;

// DEVICE Service Data

struct DeviceServiceData
{
    unsigned int  fDebugMode;              // Debug Mode flag
    STAFString    fShortName;              // Short service name
    STAFString    fName;                   // Registered service name    
    STAFHandlePtr fHandlePtr;              // Device service's STAF handle
    STAFString    fLocalMachineName;       // Local machine name
    STAFCommandParserPtr fAddParser;       // DEVICE ADD command parser
    STAFCommandParserPtr fDeleteParser;    // DEVICE DELETE command parser
    STAFCommandParserPtr fQueryParser;     // DEVICE QUERY command parser
    STAFCommandParserPtr fListParser;      // DEVICE LIST command parser
    STAFCommandParserPtr fHelpParser;      // DEVICE HELP command parser
    STAFCommandParserPtr fVersionParser;   // DEVICE VERSION command parser
    STAFCommandParserPtr fParmsParser;     // DEVIC PARMS command parser

    // Map Class Definitions for marshalled results
    STAFMapClassDefinitionPtr fListDeviceMapClass; 
    STAFMapClassDefinitionPtr fQueryDeviceMapClass;

    STAFMutexSemPtr      fPrinterMapSem;   // Semaphore to control 
                                           //   access to the PrinterMap
    STAFMutexSemPtr      fModemMapSem;     // Semaphore to control 
                                           //   access to the ModemMap
    DeviceMap            fPrinterMap;      // Map of all printers
    DeviceMap            fModemMap;        // Map of all modems
};

typedef STAFRefPtr<DeviceData> DeviceServiceDataPtr;

// Static Variables

static STAFString sHelpMsg;
static STAFString sLineSep;
static const STAFString sVersionInfo("3.4.0");
static const STAFString sLocal("local");
static const STAFString sHelp("HELP");
static const STAFString sVar("VAR");
static const STAFString sResStrResolve("RESOLVE REQUEST ");
static const STAFString sString(" STRING ");
static const STAFString sLeftCurlyBrace(kUTF8_LCURLY);

// Prototypes

static STAFResultPtr handleAdd(STAFServiceRequestLevel30 *,
                               DeviceServiceData *);
static STAFResultPtr handleDelete(STAFServiceRequestLevel30 *,
                                  DeviceServiceData *);
static STAFResultPtr handleQuery(STAFServiceRequestLevel30 *,
                                 DeviceServiceData *);
static STAFResultPtr handleList(STAFServiceRequestLevel30 *,
                                DeviceServiceData *);
static STAFResultPtr handleHelp(STAFServiceRequestLevel30 *,
                                DeviceServiceData *);
static STAFResultPtr handleVersion(STAFServiceRequestLevel30 *,
                                   DeviceServiceData *);

static STAFResultPtr resolveStr(STAFServiceRequestLevel30 *pInfo, 
                                DeviceServiceData *pData,
                                const STAFString &theString);

static STAFResultPtr resolveOp(STAFServiceRequestLevel30 *pInfo, 
                               DeviceServiceData *pData,
                               STAFCommandParseResultPtr &parsedResult,
                               const STAFString &fOption,
                               unsigned int optionIndex = 1);

STAFResultPtr resolveOpLocal(DeviceServiceData *pData,
                             STAFCommandParseResultPtr &parsedResult,
                             const STAFString &fOption,
                             unsigned int optionIndex = 1);

static void registerHelpData(DeviceServiceData *pData,
                             unsigned int errorNumber,
                             const STAFString &shortInfo,
                             const STAFString &longInfo);

static void unregisterHelpData(DeviceServiceData *pData,
                               unsigned int errorNumber);

// Begin implementation

STAFRC_t STAFServiceGetLevelBounds(unsigned int levelID,
                                   unsigned int *minimum,
                                   unsigned int *maximum)
{
    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;
}


STAFRC_t STAFServiceConstruct(STAFServiceHandle_t *pServiceHandle,
                              void *pServiceInfo, unsigned int infoLevel,
                              STAFString_t *pErrorBuffer)
{
    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");
        *pErrorBuffer = error.adoptImpl();
    }    

    return kSTAFUnknownError;
}


STAFRC_t STAFServiceInit(STAFServiceHandle_t serviceHandle,
                         void *pInitInfo, unsigned int initLevel,
                         STAFString_t *pErrorBuffer)
{
    STAFRC_t retCode = kSTAFUnknownError;

    try
    {    
        if (initLevel != 30) return kSTAFInvalidAPILevel;

        DeviceServiceData *pData =
            reinterpret_cast<DeviceServiceData *>(serviceHandle);
        
        STAFServiceInitLevel30 *pInfo =
            reinterpret_cast<STAFServiceInitLevel30 *>(pInitInfo);        

        retCode = STAFHandle::create(pData->fName, pData->fHandlePtr);

        if (retCode != kSTAFOk)
            return retCode;
        
        //ADD options

        pData->fAddParser = STAFCommandParserPtr(new STAFCommandParser,
                                                 STAFCommandParserPtr::INIT);
        pData->fAddParser->addOption("ADD", 1,
                                     STAFCommandParser::kValueNotAllowed);
        pData->fAddParser->addOption("PRINTER", 1,
                                     STAFCommandParser::kValueRequired);
        pData->fAddParser->addOption("MODEM", 1,
                                     STAFCommandParser::kValueRequired);
        pData->fAddParser->addOption("MODEL", 1,
                                     STAFCommandParser::kValueRequired);
        pData->fAddParser->addOption("SN", 1,
                                     STAFCommandParser::kValueRequired);
        pData->fAddParser->addOptionNeed("PRINTER MODEM", "ADD");
        pData->fAddParser->addOptionNeed("ADD", "PRINTER MODEM");
        pData->fAddParser->addOptionNeed("ADD", "MODEL");
        pData->fAddParser->addOptionNeed("ADD", "SN");
        pData->fAddParser->addOptionGroup("PRINTER MODEM", 0, 1);
        
        //DELETE options

        pData->fDeleteParser = STAFCommandParserPtr(new STAFCommandParser,
                                                 STAFCommandParserPtr::INIT);
        pData->fDeleteParser->addOption("DELETE", 1,
                                        STAFCommandParser::kValueNotAllowed);
        pData->fDeleteParser->addOption("PRINTER", 1,
                                        STAFCommandParser::kValueRequired);
        pData->fDeleteParser->addOption("MODEM",  1,
                                        STAFCommandParser::kValueRequired);
        pData->fDeleteParser->addOption("CONFIRM", 1,
                                        STAFCommandParser::kValueNotAllowed);
        pData->fDeleteParser->addOptionGroup("PRINTER MODEM", 0, 1);
        pData->fDeleteParser->addOptionNeed("PRINTER MODEM", "DELETE");
        pData->fDeleteParser->addOptionNeed("DELETE", "PRINTER MODEM");
        pData->fDeleteParser->addOptionNeed("DELETE", "CONFIRM");
        
        //QUERY options

        pData->fQueryParser = STAFCommandParserPtr(new STAFCommandParser,
                                                 STAFCommandParserPtr::INIT);
        pData->fQueryParser->addOption("QUERY", 1,
                                       STAFCommandParser::kValueNotAllowed);
        pData->fQueryParser->addOption("PRINTER", 1,
                                       STAFCommandParser::kValueRequired);
        pData->fQueryParser->addOption("MODEM", 1,
                                       STAFCommandParser::kValueRequired);
        pData->fQueryParser->addOptionGroup("PRINTER MODEM", 0, 1);
        pData->fQueryParser->addOptionNeed("PRINTER MODEM", "QUERY");
        pData->fQueryParser->addOptionNeed("QUERY", "PRINTER MODEM");

        //LIST options

        pData->fListParser = STAFCommandParserPtr(new STAFCommandParser,
                                                  STAFCommandParserPtr::INIT);
        pData->fListParser->addOption("LIST", 1,
                                       STAFCommandParser::kValueNotAllowed);
        pData->fListParser->addOption("PRINTERS", 1,
                                      STAFCommandParser::kValueNotAllowed);
        pData->fListParser->addOption("MODEMS", 1,
                                      STAFCommandParser::kValueNotAllowed);

        //HELP options

        pData->fHelpParser = STAFCommandParserPtr(new STAFCommandParser,
                                                  STAFCommandParserPtr::INIT);
        pData->fHelpParser->addOption("HELP", 1,
                                      STAFCommandParser::kValueNotAllowed);

        //VERSION options

        pData->fVersionParser = STAFCommandParserPtr(new STAFCommandParser,
                                                  STAFCommandParserPtr::INIT);
        pData->fVersionParser->addOption("VERSION", 1,
                                         STAFCommandParser::kValueNotAllowed);

        // Construct map class for the result from a LIST request.

        pData->fListDeviceMapClass = STAFMapClassDefinition::create(
            "STAF/Service/Device/ListDevice");

        pData->fListDeviceMapClass->addKey("name",    "Name");
        pData->fListDeviceMapClass->addKey("type",    "Type");
        pData->fListDeviceMapClass->addKey("model",   "Model");
        pData->fListDeviceMapClass->addKey("serial#", "Serial Number");
        pData->fListDeviceMapClass->setKeyProperty(
            "serial#", "display-short-name", "Serial #");

        // Construct map class for the result from a QUERY request.

        pData->fQueryDeviceMapClass = STAFMapClassDefinition::create(
            "STAF/Service/Device/QueryDevice");

        pData->fQueryDeviceMapClass->addKey("model",   "Model");
        pData->fQueryDeviceMapClass->addKey("serial#", "Serial Number");

        // Resolve the line separator variable for the local machine

        STAFResultPtr result = pData->fHandlePtr->submit(
            "local", "VAR", "RESOLVE STRING {STAF/Config/Sep/Line}");

        if (result->rc != 0)
        {
            *pErrorBuffer = result->result.adoptImpl();
            return result->rc;
        }
        else sLineSep = result->result;
        
        // Resolve the machine name variable for the local machine

        result = pData->fHandlePtr->submit(
            "local", "VAR", "RESOLVE STRING {STAF/Config/Machine}");

        if (result->rc != 0)
        {
            *pErrorBuffer = result->result.adoptImpl();
            return result->rc;
        }
        else pData->fLocalMachineName = result->result;
        
        // Create mutex semaphores for the printer and modem data maps

        pData->fPrinterMapSem = STAFMutexSemPtr(
            new STAFMutexSem, STAFMutexSemPtr::INIT);
        pData->fModemMapSem = STAFMutexSemPtr(
            new STAFMutexSem, STAFMutexSemPtr::INIT);

        // Assign the help text string for the service

        sHelpMsg = STAFString("*** ") + pData->fShortName + " Service Help ***" +
            sLineSep + sLineSep +
            "ADD     < PRINTER <PrinterName> | MODEM <ModemName> > MODEL <Model> SN <Serial#>" +
            sLineSep +
            "DELETE  < PRINTER <printerName> | MODEM <ModemName> > CONFIRM" +
            sLineSep +
            "LIST    [PRINTERS] [MODEMS]" +
            sLineSep +
            "QUERY   PRINTER <PrinterName> | MODEM <ModemName>" +
            sLineSep +
            "VERSION" +
            sLineSep +
            "HELP";

        // Register Help Data

        registerHelpData(pData, kDeviceInvalidSerialNumber,
            STAFString("Invalid serial number"),
            STAFString("A non-numeric value was specified for serial number"));
    }

    catch (STAFException &e)
    {
        STAFString result;
        
        result += STAFString("In DeviceService.cpp: STAFServiceInit")
            + 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: STAFServiceInit: "
                         "Caught unknown exception");
        *pErrorBuffer = error.adoptImpl();
    }    

    return retCode;
}


STAFRC_t STAFServiceAcceptRequest(STAFServiceHandle_t serviceHandle,
                                  void *pRequestInfo, unsigned int reqLevel,
                                  STAFString_t *pResultBuffer)
{
    if (reqLevel != 30) return kSTAFInvalidAPILevel;

    STAFRC_t retCode = kSTAFUnknownError;

    try
    {
        STAFResultPtr result(new STAFResult(),
                             STAFResultPtr::INIT);        

        STAFServiceRequestLevel30 *pInfo =
            reinterpret_cast<STAFServiceRequestLevel30 *>(pRequestInfo);

        DeviceServiceData *pData =
            reinterpret_cast<DeviceServiceData *>(serviceHandle);

        // Determine the command request (the first word in the request)

        STAFString request(pInfo->request);
        STAFString action = request.subWord(0, 1).toLowerCase();
        
        // Call functions for the request

        if (action == "add")
            result = handleAdd(pInfo, pData);
        else if (action == "delete")
            result = handleDelete(pInfo, pData);
        else if (action == "query")
            result = handleQuery(pInfo, pData);
        else if (action == "list")
            result = handleList(pInfo, pData);
        else if (action == "help")
            result = handleHelp(pInfo, pData);
        else if (action == "version")
            result = handleVersion(pInfo, pData);
        else
        {
            STAFString errMsg = STAFString("'") + request.subWord(0, 1) +
                "' is not a valid command request for the " +
                pData->fShortName + " service" + sLineSep + sLineSep +
                sHelpMsg;

            result = STAFResultPtr(new STAFResult(
                kSTAFInvalidRequestString, errMsg), STAFResultPtr::INIT);

        }

        *pResultBuffer = result->result.adoptImpl();
        retCode = result->rc;
    }
    catch (STAFException &e)
    {
        retCode = e.getErrorCode();

        STAFString result;
        
        result += STAFString("In DeviceService.cpp: STAFServiceAcceptRequest")
            + 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;
        
        *pResultBuffer = result.adoptImpl();
    }
    catch (...)
    {
        STAFString error("DeviceService.cpp: STAFServiceAcceptRequest: "
                         "Caught unknown exception");
        *pResultBuffer = error.adoptImpl();
    }

    return retCode;
}


STAFRC_t STAFServiceTerm(STAFServiceHandle_t serviceHandle,
                         void *pTermInfo, unsigned int termLevel,
                         STAFString_t *pErrorBuffer)
{
    if (termLevel != 0) return kSTAFInvalidAPILevel;

    STAFRC_t retCode = kSTAFUnknownError;

    try
    {
        retCode = kSTAFOk;

        DeviceServiceData *pData =
            reinterpret_cast<DeviceServiceData *>(serviceHandle);

        // Un-register Help Data

        unregisterHelpData(pData, kDeviceInvalidSerialNumber);
    }
    catch (STAFException &e)
    { 
        STAFString result;
        
        result += STAFString("In DeviceService.cpp: STAFServiceTerm")
            + 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: STAFServiceTerm: "
                         "Caught unknown exception");
        *pErrorBuffer = error.adoptImpl();
    }

    return retCode;
}


STAFRC_t STAFServiceDestruct(STAFServiceHandle_t *serviceHandle,
                             void *pDestructInfo, unsigned int destructLevel,
                             STAFString_t *pErrorBuffer)
{
    if (destructLevel != 0) return kSTAFInvalidAPILevel;

    STAFRC_t retCode = kSTAFUnknownError;

    try
    {
        DeviceServiceData *pData =
            reinterpret_cast<DeviceServiceData *>(*serviceHandle);

        delete pData;
        *serviceHandle = 0;

        retCode = kSTAFOk;
    }
    catch (STAFException &e)
    { 
        STAFString result;
        
        result += STAFString("In DeviceService.cpp: STAFServiceDestruct")
            + 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("DevicePoolService.cpp: STAFServiceDestruct: "
                         "Caught unknown exception");
        *pErrorBuffer = error.adoptImpl();
    }

    return retCode;
}


// Handles device add entry requests

STAFResultPtr handleAdd(STAFServiceRequestLevel30 *pInfo, 
                        DeviceServiceData *pData)
{
    // Verify the requester has at least trust level 3

    VALIDATE_TRUST(3, pData->fShortName, "ADD", pData->fLocalMachineName);
    
    // Parse the request

    STAFCommandParseResultPtr parsedResult = 
        pData->fAddParser->parse(pInfo->request);

    if (parsedResult->rc != kSTAFOk)
    {
        return STAFResultPtr(new STAFResult(kSTAFInvalidRequestString,
                             parsedResult->errorBuffer), STAFResultPtr::INIT);
    }

    // Resolve any STAF variables in the printer option's value
   
    STAFResultPtr resultPtr = resolveOp(pInfo, pData, parsedResult, "PRINTER");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString printer = resultPtr->result;

    // Resolve any STAF variables in the modem option's value
   
    resultPtr = resolveOp(pInfo, pData, parsedResult, "MODEM");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString modem = resultPtr->result;
    
    // Resolve any STAF variables in the model option's value

    resultPtr = resolveOp(pInfo, pData, parsedResult, "MODEL");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString model = resultPtr->result;
    
    // Resolve any STAF variables in the sn option's value

    resultPtr = resolveOp(pInfo, pData, parsedResult, "SN");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString serialNumber = resultPtr->result;

    // Verify that the serial number is numeric
    
    if (!serialNumber.isDigits())
    {
        // Note that instead of creating a new error code specific for
        // this service, could use kSTAFInvalidValue instead.
        return STAFResultPtr(
            new STAFResult(kDeviceInvalidSerialNumber, serialNumber), 
            STAFResultPtr::INIT);
    }
    
    // Add the device to the printer map or the modem map and
    // write an informational message to the service log

    if (printer != "")
    {
        STAFMutexSemLock lock(*pData->fPrinterMapSem);
        
        pData->fPrinterMap.insert(DeviceMap::value_type(printer,
            DeviceDataPtr(new DeviceData(printer, model, serialNumber), 
            DeviceDataPtr::INIT)));

        STAFString logMsg = "ADD PRINTER request.  Name=" + printer +
                            " Model=" + model + " Serial#=" + serialNumber;

        pData->fHandlePtr->submit(
            sLocal, "LOG", "LOG MACHINE LOGNAME " + pData->fShortName +
            " LEVEL info MESSAGE " + pData->fHandlePtr->wrapData(logMsg));
    }
    else if (modem != "")
    {
        STAFMutexSemLock lock(*pData->fModemMapSem);
        
        pData->fModemMap.insert(DeviceMap::value_type(modem,
            DeviceDataPtr(new DeviceData(modem, model, serialNumber), 
            DeviceDataPtr::INIT)));

        STAFString logMsg = "ADD MODEM request.  Name=" + modem +
                            " Model=" + model + " Serial#=" + serialNumber;

        pData->fHandlePtr->submit(
            sLocal, "LOG", "LOG MACHINE LOGNAME " + pData->fShortName +
            " LEVEL info MESSAGE " + pData->fHandlePtr->wrapData(logMsg));
    }

    // Return an Ok result
    
    return STAFResultPtr(new STAFResult(kSTAFOk), STAFResultPtr::INIT);
}


// Handles device deletion requests

STAFResultPtr handleDelete(STAFServiceRequestLevel30 *pInfo, 
                           DeviceServiceData *pData)
{
    // Verify the requester has at least trust level 4

    VALIDATE_TRUST(4, pData->fShortName, "DELETE", pData->fLocalMachineName);
    
    // Parse the request

    STAFCommandParseResultPtr parsedResult = 
        pData->fDeleteParser->parse(pInfo->request);

    if (parsedResult->rc != kSTAFOk)
    {
        return STAFResultPtr(new STAFResult(kSTAFInvalidRequestString,
                             parsedResult->errorBuffer), STAFResultPtr::INIT);
    }
   
    // Resolve any STAF variables in the print option's value

    STAFResultPtr resultPtr = resolveOp(pInfo, pData, parsedResult, "PRINTER");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString printer = resultPtr->result;
    
    // Resolve any STAF variables in the modem option's value

    resultPtr = resolveOp(pInfo, pData, parsedResult, "MODEM");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString modem = resultPtr->result;

    // Find the device in the printer or modem map and remove it and
    // write an informational message to the service log

    if (printer != "")
    {
        STAFMutexSemLock lock(*pData->fPrinterMapSem);
        
        pData->fPrinterMap.erase(printer);

        STAFString logMsg = "DELETE PRINTER request.  Name=" + printer;

        pData->fHandlePtr->submit(
            sLocal, "LOG", "LOG MACHINE LOGNAME " + pData->fShortName +
            " LEVEL info MESSAGE " + pData->fHandlePtr->wrapData(logMsg));
    }
    else if (modem != "")
    {
        STAFMutexSemLock lock(*pData->fModemMapSem);
        
        pData->fModemMap.erase(modem);

        STAFString logMsg = "DELETE MODEM request.  Name=" + modem;

        pData->fHandlePtr->submit(
            sLocal, "LOG", "LOG MACHINE LOGNAME " + pData->fShortName +
            " LEVEL info MESSAGE " + pData->fHandlePtr->wrapData(logMsg));
    }    

    // Return an Ok result

    return STAFResultPtr(new STAFResult(kSTAFOk), STAFResultPtr::INIT);
}


// Handles device list requests

STAFResultPtr handleList(STAFServiceRequestLevel30 *pInfo, 
                         DeviceServiceData *pData)
{
    STAFString result;
    STAFRC_t rc = kSTAFOk;

    // Verify the requester has at least trust level 2

    VALIDATE_TRUST(2, pData->fShortName, "LIST", pData->fLocalMachineName);
    
    // Parse the request

    STAFCommandParseResultPtr parsedResult = 
        pData->fListParser->parse(pInfo->request);

    if (parsedResult->rc != kSTAFOk)
    {
        return STAFResultPtr(new STAFResult(kSTAFInvalidRequestString,
                             parsedResult->errorBuffer), STAFResultPtr::INIT);
    }
    
    // Check if specified printers or modems

    bool all = false;
    bool printers = false;
    bool modems = false;

    if (!(parsedResult->optionTimes("PRINTERS")) &&
        !(parsedResult->optionTimes("MODEMS")))
    {
        all = true;
    }
    
    if (parsedResult->optionTimes("PRINTERS"))
    {
        printers = true;
    }
    
    if (parsedResult->optionTimes("MODEMS"))
    {
        modems = true;
    }
    
    // Create a marshalling context and set any map classes (if any).

    STAFObjectPtr mc = STAFObject::createMarshallingContext();
    mc->setMapClassDefinition(pData->fListDeviceMapClass->reference());

    // Create an empty result list to contain the result

    STAFObjectPtr resultList = STAFObject::createList();

    // Add printer entries to the result list

    if (printers || all)
    {
        STAFMutexSemLock lock(*pData->fPrinterMapSem);
        
        DeviceMap::iterator iter;

        for (iter = pData->fPrinterMap.begin(); 
             iter != pData->fPrinterMap.end(); ++iter)
        {
            STAFObjectPtr resultMap = pData->fListDeviceMapClass->createInstance();
            resultMap->put("name",    iter->second->name);
            resultMap->put("type",    "Printer");
            resultMap->put("model",   iter->second->model);
            resultMap->put("serial#", iter->second->serialNumber);

            resultList->append(resultMap);
        }
    }
    
    // Add modem entries to the result list

    if (modems || all)
    {
        STAFMutexSemLock lock(*pData->fModemMapSem);
        
        DeviceMap::iterator iter;

        for (iter = pData->fModemMap.begin();
             iter != pData->fModemMap.end(); ++iter)
        {
            STAFObjectPtr resultMap = pData->fListDeviceMapClass->createInstance();
            resultMap->put("name",    iter->second->name);
            resultMap->put("type",    "Modem");
            resultMap->put("model",   iter->second->model);
            resultMap->put("serial#", iter->second->serialNumber);

            resultList->append(resultMap);
        }
    }    

    // Set the result list as the root object for the marshalling context
    // and return the marshalled result

    mc->setRootObject(resultList);

    return STAFResultPtr(new STAFResult(kSTAFOk, mc->marshall()),
                         STAFResultPtr::INIT);
}


// Handles device query requests

STAFResultPtr handleQuery(STAFServiceRequestLevel30 *pInfo, 
                          DeviceServiceData *pData)
{
    STAFString result;
    STAFRC_t rc = kSTAFOk;
    
    // Verify the requester has at least trust level 2

    VALIDATE_TRUST(2, pData->fShortName, "QUERY", pData->fLocalMachineName);
    
    // Parse the request

    STAFCommandParseResultPtr parsedResult = 
        pData->fQueryParser->parse(pInfo->request);

    if (parsedResult->rc != kSTAFOk)
    {
        return STAFResultPtr(new STAFResult(kSTAFInvalidRequestString,
                             parsedResult->errorBuffer), STAFResultPtr::INIT);
    }
    
    // Resolve any STAF variables in the printer option's value

    STAFResultPtr resultPtr = resolveOp(pInfo, pData, parsedResult, "PRINTER");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString printer = resultPtr->result;
    
    // Resolve any STAF variables in the modem option's value

    resultPtr = resolveOp(pInfo, pData, parsedResult, "MODEM");

    if (resultPtr->rc != 0) return resultPtr;

    STAFString modem = resultPtr->result;
    
    // Create a marshalling context and set any map classes (if any).

    STAFObjectPtr mc = STAFObject::createMarshallingContext();
    mc->setMapClassDefinition(pData->fQueryDeviceMapClass->reference());

    // Create an empty result map to contain the result

    STAFObjectPtr resultMap = pData->fQueryDeviceMapClass->createInstance();

    // Find the specified printer/modem and add its info to the result map

    if (printer != "")
    {
        STAFMutexSemLock lock(*pData->fPrinterMapSem);
        
        DeviceMap::iterator iter = pData->fPrinterMap.find(printer); 
        
        if (iter == pData->fPrinterMap.end())
        {
            return STAFResultPtr(new STAFResult(kSTAFDoesNotExist, printer),
                                 STAFResultPtr::INIT);
        }
        
        resultMap->put("model",   iter->second->model);
        resultMap->put("serial#", iter->second->serialNumber);
    }
    else if (modem != "")
    {
        STAFMutexSemLock lock(*pData->fModemMapSem);
        
        DeviceMap::iterator iter = pData->fModemMap.find(modem);

        if (iter == pData->fModemMap.end())
        {
            return STAFResultPtr(new STAFResult(kSTAFDoesNotExist, modem),
                                 STAFResultPtr::INIT);
        }

        resultMap->put("model",   iter->second->model);
        resultMap->put("serial#", iter->second->serialNumber);
    }

    // Set the result map as the root object for the marshalling context
    // and return the marshalled result

    mc->setRootObject(resultMap);

    return STAFResultPtr(new STAFResult(kSTAFOk, mc->marshall()), 
                         STAFResultPtr::INIT);
}


STAFResultPtr handleHelp(STAFServiceRequestLevel30 *pInfo, 
                         DeviceServiceData *pData)
{    
    // Verify the requester has at least trust level 1

    VALIDATE_TRUST(1, pData->fShortName, "HELP", pData->fLocalMachineName);
    
    // Return help text for the service

    return STAFResultPtr(new STAFResult(kSTAFOk, sHelpMsg),
                         STAFResultPtr::INIT);
}


STAFResultPtr handleVersion(STAFServiceRequestLevel30 *pInfo, 
                            DeviceServiceData *pData)
{    
    // Verify the requester has at least trust level 1

    VALIDATE_TRUST(1, pData->fShortName, "VERSION", pData->fLocalMachineName);
    
    // Return the version of the service

    return STAFResultPtr(new STAFResult(kSTAFOk, sVersionInfo), 
                         STAFResultPtr::INIT);
}


STAFResultPtr resolveOp(STAFServiceRequestLevel30 *pInfo, 
                        DeviceServiceData *pData,
                        STAFCommandParseResultPtr &parsedResult,
                        const STAFString &fOption, unsigned int optionIndex)
{
    STAFString optionValue = parsedResult->optionValue(fOption, optionIndex);

    if (optionValue.find(sLeftCurlyBrace) == STAFString::kNPos)
    {
        return STAFResultPtr(new STAFResult(kSTAFOk, optionValue),
                             STAFResultPtr::INIT);
    }

    return resolveStr(pInfo, pData, optionValue);
}


STAFResultPtr resolveStr(STAFServiceRequestLevel30 *pInfo,
                         DeviceServiceData *pData, 
                         const STAFString &theString)
{
    return pData->fHandlePtr->submit(sLocal, sVar, sResStrResolve +
                                     STAFString(pInfo->requestNumber) +
                                     sString +
                                     pData->fHandlePtr->wrapData(theString));
}


void registerHelpData(DeviceServiceData *pData, unsigned int errorNumber,
                      const STAFString &shortInfo, const STAFString &longInfo)
{
    static STAFString regString("REGISTER SERVICE %C ERROR %d INFO %C "
                                "DESCRIPTION %C");

    pData->fHandlePtr->submit(sLocal, sHelp, STAFHandle::formatString(
        regString.getImpl(), pData->fShortName.getImpl(), errorNumber,
        shortInfo.getImpl(), longInfo.getImpl()));
}


void unregisterHelpData(DeviceServiceData *pData, unsigned int errorNumber)
{
    static STAFString regString("UNREGISTER SERVICE %C ERROR %d");

    pData->fHandlePtr->submit(sLocal, sHelp, STAFHandle::formatString(
        regString.getImpl(), pData->fShortName.getImpl(), errorNumber));
}

Appendix E: Perl STAF Service Example

Here is a complete example of a sample Perl STAF service (DeviceService.pl):

#############################################################################
# Software Testing Automation Framework (STAF)                              #
# (C) Copyright IBM Corp. 2007                                              #
#                                                                           #
# This software is licensed under the Eclipse Public License (EPL) V1.0.    #
#############################################################################

package DeviceService;

use PLSTAFService;
use PLSTAF;
use 5.008;
use threads;
use threads::shared;
use Thread::Queue;

use strict;
use warnings;

use constant kDeviceInvalidSerialNumber => scalar 4001;
use constant kVersion => scalar "1.0.0";

# In this queue the master threads queue jobs for the slave worker
my $work_queue = new Thread::Queue;
my $free_workers : shared = 0;

our $fServiceName;         # passed in part of parms
our $fHandle;               # staf handle for service
our $fListParser;          # .
our $fQueryParser;         # parsers for different requests
our $fAddParser ;          # .
our $fDeleteParser;        # .
our $fLineSep;             # line separator
our %printerMap : shared;  # map to maintain list of printers
our %modemMap : shared;    # map to maintain list of modems

our $listDeviceMapClass;
our $queryDeviceMapClass;

sub new
{
    my ($class, $info) = @_;

    my $self =
    {
        threads_list => [],
        worker_created => 0,
        max_workers => 5, # do not create more than 5 workers
    };

    $fServiceName = $info->{ServiceName};

    $fHandle = STAF::STAFHandle->new("STAF/Service/" . $fServiceName);

    # Add Parser
    $fAddParser = STAFCommandParser->new();

    $fAddParser->addOption("ADD", 1, STAFCommandParser::VALUENOTALLOWED);
    $fAddParser->addOption("PRINTER", 1, STAFCommandParser::VALUEREQUIRED);
    $fAddParser->addOption("MODEL", 1, STAFCommandParser::VALUEREQUIRED);
    $fAddParser->addOption("SN", 1, STAFCommandParser::VALUEREQUIRED);
    $fAddParser->addOption("MODEM", 1, STAFCommandParser::VALUEREQUIRED);

    $fAddParser->addOptionGroup("PRINTER MODEM", 0, 1);

    # if you specify ADD, MODEM is required
    $fAddParser->addOptionNeed("ADD", "MODEL");

    # if you specify ADD, SN is required
    $fAddParser->addOptionNeed("ADD", "SN");

    # if you specify ADD, PRINTER or MODEM is required
    $fAddParser->addOptionNeed("PRINTER MODEM", "ADD");

    # if you specify PRINTER or MODEM, ADD is required
    $fAddParser->addOptionNeed("ADD", "PRINTER MODEM");

    # LIST parser
    $fListParser = STAFCommandParser->new();

    $fListParser->addOption("LIST", 1, STAFCommandParser::VALUENOTALLOWED);
    $fListParser->addOption("PRINTERS", 1, STAFCommandParser::VALUENOTALLOWED);
    $fListParser->addOption("MODEMS", 1, STAFCommandParser::VALUENOTALLOWED);

    # QUERY parser
    $fQueryParser = STAFCommandParser->new();

    $fQueryParser->addOption("QUERY", 1, STAFCommandParser::VALUENOTALLOWED);
    $fQueryParser->addOption("PRINTER", 1, STAFCommandParser::VALUEREQUIRED);
    $fQueryParser->addOption("MODEM", 1, STAFCommandParser::VALUEREQUIRED);

    # this means you can have PRINTER or MODEM, but not both
    $fQueryParser->addOptionGroup("PRINTER MODEM", 0, 1);

    # if you specify PRINTER or MODEM, QUERY is required
    $fQueryParser->addOptionNeed("PRINTER MODEM", "QUERY");

    # if you specify QUERY, PRINTER or MODEM is required
    $fQueryParser->addOptionNeed("QUERY", "PRINTER MODEM");

    # DELETE parser
    $fDeleteParser = STAFCommandParser->new();

    $fDeleteParser->addOption("DELETE", 1, STAFCommandParser::VALUENOTALLOWED);
    $fDeleteParser->addOption("PRINTER", 1, STAFCommandParser::VALUEREQUIRED);
    $fDeleteParser->addOption("MODEM", 1, STAFCommandParser::VALUEREQUIRED);
    $fDeleteParser->addOption("CONFIRM", 1,
                                STAFCommandParser::VALUENOTALLOWED);

    # this means you must have PRINTER or MODEM, but not both
    $fDeleteParser->addOptionGroup("PRINTER MODEM", 0, 1);

    # if you specify PRINTER or MODEM, DELETE is required
    $fDeleteParser->addOptionNeed("PRINTER MODEM", "DELETE");

    # if you specify DELETE, PRINTER or MODEM is required
    $fDeleteParser->addOptionNeed("DELETE", "PRINTER MODEM");

    # if you specify DELETE, CONFIRM is required
    $fDeleteParser->addOptionNeed("DELETE", "CONFIRM");

    # construct map class for the result from a LIST request.

    $listDeviceMapClass  =
        STAF::STAFMapClassDefinition->new('STAF/Service/Device/ListDevice');
    $listDeviceMapClass->addKey('name', 'Name');
    $listDeviceMapClass->addKey('type', 'Type');
    $listDeviceMapClass->addKey('model', 'Model');
    $listDeviceMapClass->addKey('serial#', 'Serial Number');
    $listDeviceMapClass->setKeyProperty(
        "serial#", "display-short-name", "Serial #");

    # construct map class for the result from a QUERY request.

    $queryDeviceMapClass  =
        STAF::STAFMapClassDefinition->new('STAF/Service/Device/QueryDevice');
    $queryDeviceMapClass->addKey('name', 'Name');
    $queryDeviceMapClass->addKey('type', 'Type');
    $queryDeviceMapClass->addKey('model', 'Model');
    $queryDeviceMapClass->addKey('serial#', 'Serial Number');
    $queryDeviceMapClass->setKeyProperty(
        "serial#", "display-short-name", "Serial #");

    my $lineSepResult = $fHandle->submit2($STAF::STAFHandle::kReqSync,
        "local", "var", "resolve string {STAF/Config/Sep/Line}");

    $fLineSep = $lineSepResult->{result};

    registerHelpData(kDeviceInvalidSerialNumber, "Invalid Serial Number",
                     "A non-numeric value was specified for serial number");

    return bless $self, $class;
}

sub AcceptRequest
{
    my ($self, $info) = @_;
    my %hash : shared = %$info;

    if ($free_workers <= 0 and
        $self->{worker_created} < $self->{max_workers})
    {
        my $thr = threads->create(\&Worker);
        push @{ $self->{threads_list} }, $thr;
        $self->{worker_created}++;
    }
    else
    {
        lock $free_workers;
        $free_workers--;
    }

    $work_queue->enqueue(\%hash);

    return $STAF::DelayedAnswer;
}

sub Worker
{
    my $loop_flag = 1;

    while ($loop_flag)
    {
        eval
        {
            # get the work from the queue
            my $hash_ref = $work_queue->dequeue();

            if (not ref($hash_ref) and $hash_ref->{request} eq 'stop')
            {
                $loop_flag = 0;
                return;
            }

            my ($rc, $result) = handleRequest($hash_ref);

            STAF::DelayedAnswer($hash_ref->{requestNumber}, $rc, $result);

            # increase the number of free threads
            {
                lock $free_workers;
                $free_workers++;
            }
        }
    }

    return 1;
}

sub handleRequest
{
    my $info = shift;

    my $lowerRequest = lc($info->{request});
    my $requestType = "";

    # get first "word" in request
    if($lowerRequest =~ m/\b(\w*)\b/)
    {
        $requestType = $&;
    }
    else
    {
        return (STAFResult::kInvalidRequestString,
            "Unknown DeviceService Request: " . ($info->{request}));
    }

    if ($requestType eq "list")
    {
        return handleList($info);
    }
    elsif ($requestType eq "query")
    {
        return handleQuery($info);
    }
    elsif ($requestType eq "add")
    {
        return handleAdd($info);
    }
    elsif ($requestType eq "delete")
    {
        return handleDelete($info);
    }
    elsif ($requestType eq "help")
    {
        return handleHelp();
    }
    elsif ($requestType eq "version")
    {
        return handleVersion();
    }
    else
    {
        return (STAFResult::kInvalidRequestString,
            "Unknown DeviceService Request: " . $info->{request});
    }

    return (0, "");
}

sub handleVersion
{
    return (STAFResult::kOk, kVersion);
}

sub handleHelp
{
    return (STAFResult::kOk,
          "DeviceService 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");

}

sub handleAdd
{
    my $info = shift;

    if($info->{trustLevel} < 3)
    {
        return (STAFResult::kAccessDenied,
           "Trust level 3 required for ADD request. Requesting " . 
           "machine's trust level: " . $info->{trustLevel});
    }

    my $result;
    my $resolveResult;
    my $resultString = "";
    my $printerValue = "";
    my $modemValue = "";
    my $modelValue = "";
    my $snValue;

    # parse request
    my $parsedRequest = $fAddParser->parse($info->{request});

    # check results of parse
    if($parsedRequest->{"rc"} != STAFResult::kOk)
    {
        return (STAFResult::kInvalidRequestString,
            $parsedRequest->{"errorBuffer"});
    }

    # resolve the value after 'printer' if necessary
    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("printer"),
                                  $info->{requestNumber});

    # check results of resolve
    if ($resolveResult->{"rc"} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $printerValue = $resolveResult->{"result"};

    # resolve the value after 'modem' if necessary
    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("modem"),
                                  $info->{requestNumber});

    # check the results of resovle
    if ($resolveResult->{"rc"} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $modemValue = $resolveResult->{"result"};

    # resolve the value after 'model' if necessary
    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("model"),
                                  $info->{requestNumber});

    if ($resolveResult->{"rc"} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $modelValue = $resolveResult->{"result"};

    # resolve the value after 'sn' if necessary
    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("sn"),
                                  $info->{requestNumber});

    if ($resolveResult->{"rc"} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    # make sure the result is a number
    if($resolveResult->{result} =~ m/^[0-9]+$/)
    {
        $snValue = $resolveResult->{"result"};
    }
    else
    {
        return (kDeviceInvalidSerialNumber, $snValue);
    }

    # add the printer to the map
    if ($printerValue ne "")
    {
        lock(%printerMap);
        $printerMap{$printerValue} =
            DeviceData->new($modelValue, $snValue);
    }
    # if no printer value, add the modem to the map
    elsif ($modemValue ne "")
    {
        lock(%modemMap);
        $modemMap{$modemValue} = DeviceData->new($modelValue, $snValue);
    }
    else
    {
        # this should only happen when the option value resolves
        # to an empty string
        return (STAFResult::kInvalidRequestString,
            "Device name resolved to empty string");
    }

    return (STAFResult::kOk, $resultString);
}

sub handleList
{
    my $info = shift;

    if($info->{trustLevel} < 2)
    {
        return (STAFResult::kAccessDenied,
            "Trust level 2 required for LIST request. Requesting " .
            "machine's trust level: " .  $info->{trustLevel});
    }

    my $result = (STAFResult::kOk, "");
    my $resultString = "";

    my $printersOption;
    my $modemsOption;

    # parse request
    my $parsedRequest = $fListParser->parse($info->{request});

    # check result of parse
    if ($parsedRequest->{rc} != STAFResult::kOk)
    {
        return (STAFResult::kInvalidRequestString,
            $parsedRequest->{errorBuffer});
    }

    # create a marshalling context with testList and one map class definition

    my $mc = STAF::STAFMarshallingContext->new();
    $mc->setMapClassDefinition($listDeviceMapClass);

    my @myDeviceList;

    # is 'printers' specified?
    $printersOption = $parsedRequest->optionTimes("printers");

    # is 'modems' specified?
    $modemsOption = $parsedRequest->optionTimes("modems");

    my $defaultList = 0;

    # if neither is specified, default is to show everything
    if ($printersOption == 0 && $modemsOption == 0)
    {
        $defaultList = 1;
    }

    # list all the printers
    if ($defaultList || ($printersOption > 0))
    {
        # XXX: does it make sense to have this lock here?
        # In perl, sometimes accessing vars in a way that
        # seems to be leave the variable unchanged on the surface
        # actually changes things behind the scenes, but it's
        # "hard to know when" according to perl docs
        lock(%printerMap);
        foreach my $key (sort keys %printerMap)
        {
           my $data = $printerMap{$key};
           my $deviceMap = $listDeviceMapClass->createInstance();
           $deviceMap->{'name'} = $key;
           $deviceMap->{'type'} = 'Printer';
           $deviceMap->{'model'} = $data->{'model'};
           $deviceMap->{'serial#'} = $data->{'sn'};
           push @myDeviceList, $deviceMap;
        }
    }

    # list all the modems
    if ($defaultList || ($modemsOption > 0))
    {
        # XXX: does it make sense to have this lock here?
        # see above comment
        lock(%modemMap);
        foreach my $key (sort keys %modemMap)
        {
           my $data = $modemMap{$key};
           my $deviceMap = $listDeviceMapClass->createInstance();
           $deviceMap->{'name'} = $key;
           $deviceMap->{'type'} = 'Modem';
           $deviceMap->{'model'} = $data->{'model'};
           $deviceMap->{'serial#'} = $data->{'sn'};
           push @myDeviceList, $deviceMap;
        }
    }

    $mc->setRootObject(\@myDeviceList);

    return (STAFResult::kOk, $mc->marshall());
}

sub handleQuery
{
    my $info = shift;

    # check whether Trust level is sufficient for this command.
    if ($info->{trustLevel} < 2)
    {
        return (STAFResult::kAccessDenied,
            "Trust level 2 required for QUERY request. Requesting " .
            "machine's trust level: " .  $info->{trustLevel});
    }

    my $result = (STAFResult::kOk, "");
    my $resultString = "";

    my $resolveResult;

    my $printerValue;
    my $modemValue;

    # parse request
    my $parsedRequest = $fQueryParser->parse($info->{request});

    # check result of parse
    if ($parsedRequest->{rc} != STAFResult::kOk)
    {
        return (STAFResult::kInvalidRequestString,
                              $parsedRequest->{errorBuffer});
    }

    # resolve value after 'printer' if necessary
    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("printer"),
                                  $info->{requestNumber});

    if ($resolveResult->{rc} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $printerValue = $resolveResult->{result};

    # resolve the result after 'modem' if necessary
    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("modem"),
                                  $info->{requestNumber});

    if ($resolveResult->{rc} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $modemValue = $resolveResult->{result};

    # create a marshalling context with testList and one map class definition

    my $mc = STAF::STAFMarshallingContext->new();
    $mc->setMapClassDefinition($queryDeviceMapClass);

    my $deviceMap = $queryDeviceMapClass->createInstance();

    # look up the information associated with $printerValue or $modemValue
    if ($printerValue ne "")
    {
        lock(%printerMap);

        if (defined $printerMap{$printerValue})
        {
            my $printer = $printerValue;
            my $data = $printerMap{$printerValue};
            $deviceMap->{'name'} = $printer;
            $deviceMap->{'type'} = 'Printer';
            $deviceMap->{'model'} = $data->{'model'};
            $deviceMap->{'serial#'} = $data->{'sn'};
        }
        else
        {
            return (STAFResult::kDoesNotExist,
                $printerValue);
        }
    }
    elsif ($modemValue ne (""))
    {
        lock(%modemMap);

        if (defined $modemMap{$modemValue})
        {
            my $modem = $modemValue;
            my $data = $modemMap{$modem};
            $deviceMap->{'name'} = $modem;
            $deviceMap->{'type'} = 'Modem';
            $deviceMap->{'model'} = $data->{'model'};
            $deviceMap->{'serial#'} = $data->{'sn'};
        }
        else
        {
            return (STAFResult::kDoesNotExist,
                $modemValue);
        }
    }
    else
    {
        # this should only happen when the option value resolves to
        # an empty string
        return (STAFResult::kInvalidRequestString,
            "Device name resolved to empty string");
    }

    $mc->setRootObject($deviceMap);

    return (STAFResult::kOk, $mc->marshall());
 }

sub handleDelete
{
    my $info = shift;

    # check whether Trust level is sufficient for this command.
    if ($info->{trustLevel} < 4)
    {
        return (STAFResult::kAccessDenied,
            "Trust level 4 required for DELETE request. Requesting " .
            "machine's trust level: " .  $info->{trustLevel});
    }

    my $result = (STAFResult::kOk, "");

    my $resultString = "";
    my $resolveResult;

    my $printerValue;
    my $modemValue;

    # parse request
    my $parsedRequest = $fDeleteParser->parse($info->{request});

    # check results of parse
    if ($parsedRequest->{rc} != STAFResult::kOk)
    {
        return (STAFResult::kInvalidRequestString,
                              $parsedRequest->{errorBuffer});
    }

    # resolve value after 'printer' if necessary
    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("printer"),
                                  $info->{requestNumber});

    if ($resolveResult->{rc} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $printerValue = $resolveResult->{result};

    # resolve value after 'modem' if necessary
    $resolveResult = resolveVar($info->{isLocalRequest},
                                  $parsedRequest->optionValue("modem"),
                                  $info->{requestNumber});

    if ($resolveResult->{rc} != STAFResult::kOk)
    {
        return $resolveResult;
    }

    $modemValue = $resolveResult->{result};

    # delete printer or modem if it can find it
    if ($printerValue ne "")
    {
        lock(%printerMap);

        if(defined $printerMap{$printerValue})
        {
            delete $printerMap{$printerValue};
        }
        else
        {
            return (STAFResult::kDoesNotExist,
                $printerValue);
        }
    }
    elsif ($modemValue ne "")
    {
        lock(%modemMap);

        if(defined $modemMap{$modemValue})
        {
            delete $modemMap{$modemValue};
        }
        else
        {
            return (STAFResult::kDoesNotExist, $modemValue);
        }
    }
    else
    {
        return (STAFResult::kInvalidRequestString,
            "Device name resolved to empty string");
    }

    return (STAFResult::kOk, $resultString);
}

# This method will resolve any STAF variables that
# are contained within the Option Value
sub resolveVar
{
    my ($machine, $optionValue, $requestNumber) = @_;
    my $value = "";
    my $resolvedResult;

    # look for something starting with '{'
    if ($optionValue =~ m/^\{/)
    {
        $resolvedResult =
            $fHandle->submit2($machine, "var", "resolve request " . 
                $requestNumber . " string " . $optionValue);

        if ($resolvedResult->{rc} != 0)
        {
            return $resolvedResult;
        }

        $value = $resolvedResult->{result};
    }
    else
    {
        $value = $optionValue;
    }

    return STAF::STAFResult->new(STAFResult::kOk, $value);
}

# Register error codes for the Device Service with the HELP service
sub registerHelpData
{
   my ($errorNumber, $info, $description) = @_;

   my $res = $fHandle->submit2($STAF::STAFHandle::kReqSync, "local", "HELP",
                      "REGISTER SERVICE " . $fServiceName .
                      " ERROR " . $errorNumber .
                      " INFO " . STAF::WrapData($info) .
                      " DESCRIPTION " .
                      STAF::WrapData($description));
}

#Un-register error codes for the Device Service with the HELP service
sub unregisterHelpData
{
   my $errorNumber = shift;

   my $res = $fHandle->submit2($STAF::STAFHandle::kReqSync, "local", "HELP",
                   "UNREGISTER SERVICE " . $fServiceName .
                   " ERROR " . $errorNumber);
}

sub DESTROY
{
    my ($self) = @_;

    # Ask all the threads to stop, and join them.
    for my $thr (@{ $self->{threads_list} })
    {
        $work_queue->enqueue('stop');
    }

    # perform any cleanup for the service here

    unregisterHelpData(kDeviceInvalidSerialNumber);

    #Un-register the service handle
    $fHandle->unRegister();

# XXX:  The following block will cause a trap if multiple
# STAFCommandParsers have been created
#    for my $thr (@{ $self->{threads_list} })
#    {
#        eval { $thr->join() };
#        print STDERR "On destroy: $@\n" if $@;
#    }
}

package DeviceData;

sub new
{
   my $type = shift;
   my $model : shared = shift;
   my $sn : shared = shift;

   my $obj = &threads::shared::share({});

   $obj->{model} = $model;
   $obj->{sn} = $sn;

   bless ($obj, $type);

   return $obj;
}

1;

End Of Document

This is the end of the document.