Software Testing Automation Framework (STAF) Service Developer's Guide
July 1, 2004
V 1.1.5
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 both Java and C++.
Custom STAF Services can be written in Java, C, and C++.
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 and C++. The latest source code for the sample service can be obtained in the staf source tree at:
src/staf/services/sdg_sampleThe sample service is intended to be an example of how to implement a STAF service, and accepts the following command requests:
DeviceService Service Help ADD (PRINTER <printerName> | MODEM <modemName>) MODEL <model> SN <serial#> DELETE PRINTER <printerName> | MODEM <modemName> CONFIRM LIST [PRINTERS] [MODEMS] QUERY PRINTER <printerName> | MODEM <modemName> VERSION HELPThis sample service allows users to maintain a list of printers and/or modems, to which they can add, delete, list, and query. Note that this sample service doesn't contain any "business logic" other than maintaining the list of devices. Thus, it demonstrates how to create the framework of the STAF service, to which you would then add your service's specific business logic.
All services are implemented via a C language interface. However, STAF provides a proxy service for other languages, such as Java.
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 services the
construction and deconstruction phases are not externalized).
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 |
Destruction | 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.
In general, option values for Service requests should be resolved (since they may contain STAF variables). This document will provide a sample function to resolve option values for each Service language (see the "resolveVar" function in the Java sample service, and the "resolveOp" function in the C++ sample service).
If the service returns any service-specific return codes, it should register these return codes with the HELP service during its Initialization phase and unregister these return codes with the HELP service during its Termination phase.
You should try to not create new return codes for their service, if possible, since most error conditions can be handles with existing STAF return codes.
If you create a new return code for your service, the return code number must be 4000+.
STAF services should define trust levels for each type of incoming request. The trust level should be appropriate for the type of operation being requested. For example, a List or Query request should require a lower trust level, while a Create or Delete request should require a higher trust level. For operations that are normally tied to a specific machine/handle, services should allow a FORCE option (with a required trust level which is one greater than the operation requires) that allows another machine/handle to request the operation.
STAF services receive incoming requests that indicate the operation the service is to perform. The request contains a space-separated set of commands and options. The commands/options may not contain underscores or hyphens. You should use separate options rather than concatenate options (use "ADD PRINTER" instead of "ADDPRINTER").
When sending requests to a STAF service, the "first" option sent to the command
should indicate the overall operation of the request. The existing STAF services
utilize a common set of command names, which we recommend
all STAF services use. The recommended trust level
for each command name is included in the following table:
ADD | Adds an item to the service | Trust Level 4 |
REMOVE | Removes an item from the service | Trust Level 4 |
CREATE | Creates a service item | Trust Level 4 |
DELETE | Deletes a service item | Trust Level 4 |
LIST | Returns a list of items from the service | Trust Level 2 |
QUERY | Returns information about a specific item from the service | Trust Level 2 |
REQUEST | Requests an item from the service | Trust Level 3 |
RELEASE | Releases an item from the service | Trust Level 3 |
GET | Returns the value of a configuration option for the service | Trust Level 2 |
SET | Sets a configuration option for the service | Trust Level 5 |
HELP | Returns information about the request syntax for the service | Trust Level 1 |
VERSION | Returns the version number for the service | Trust Level 1 |
FORCE | Indicates that a destructive command should be executed if the requester has a higher trust level than is normally required for the command |
CONFIRM | Used to indicate that a destructive command, such as DELETE, should be performed (if the CONFIRM option is not specified, the destructive command will not be performed) |
WAIT <timeout> | Indicates that the service should wait until the request has completed before returning to the requester (an optional timeout value, in milliseconds, can be specified). |
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 command, the command should return the data in a format where each line is an element in the list, and each line has the raw data for the element (with each raw data element separated by a semi-colon):
staf local deviceservice LIST PRINTERS Response -------- Epson;color500;1023 canon 1;S750;34349
List requests do not always have to return all of the raw data for the element (instead it should just return the basic information for the element).
If your service supports a Query command, the command should return only data for the specified item, in the following format:
staf local deviceservice QUERY PRINTER "canon 1" Response -------- Printer : canon 1 Model : S750 Serial# : 34349 staf local deviceservice QUERY PRINTER epson Response -------- Printer : epson Model : color500 Serial# : 1023
Query requests should return all raw data for the specified item.
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 recommened that the service be implemented in Java.
If a STAF service starts additional threads, it should ensure that these threads are terminated during the Termination phase. For example, if the service has started a thread to wait for queue messages, the service should send itself a queue message with a unique message which indicates that the additional thread should terminate.
STAF services should always be case-insensitive with regards to incoming service requests.
If the service deals with files, note that any files stored on disk follow the case-sensitivity rules for the machine's operating systems. For example, Windows file names are case-insensitive, but Unix file names are case-sensitive.
When specifying incoming requests for STAF services, there are 3 ways that the option can be specified:
Rather than having to deal with parsing all of the potential formats,
STAF services should use the provided STAF command parsers to parse
their incoming requests.
The following are typical activities that STAF Services should perform during its life cycle.
Every STAF service command should accept a pre-defined set of options. The options can optionally require a value. The options optionally may be repeated multiple times in the command request. STAF services should allow free ordering of the options (where the ordering of the options in the command request does not matter).
A STAF Service should create a command parser for each of the requests that it accepts. The "first" option sent to the command should indicate the overall operation of the request (such as "LIST" or "QUERY"). It is recommended that the request command be required to be the very first option specified in the command request:
staf local deviceservice list .... staf local deviceservice query ....Each request should then define a set of options that it accepts:
staf local deviceservice list printers staf local deviceservice list modems
All STAF service command parsers should be case-insensitive.
When adding options to a command parser, you specify the name of the option, the number of times the argument may appear in the request, and whether the option allows or requires a value.
After adding all of the options for your service, you can specify the
constraints on how the options can be specified. For example, you can
specify a group of options that are to be treated as a group,
typically to specify groups of mutually exclusive options.
You can also specify dependency relationships between names (where
one option requires that another option be specified).
6.0 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.
7.0 Java STAF Services
7.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 STAFServiceInterfaceLevel4 // 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 is STAFServiceInterfaceLevel4.
Java STAF Services typically need to take no action during the Construction phase:
public DeviceService() {}
Java STAF Services must implement the init(STAFServiceInterfaceLevel4.InitInfo info) method
public int init(STAFServiceInterfaceLevel4.InitInfo info)
The STAFServiceInterfaceLevel4.InitInfo object contains the following fields:
String name | The name of the service |
String parms | The parameters that have been passed to the service (via the PARMS option) |
JarFile jarFile | The service's jar file |
During initialization, the Java STAF service needs to register with STAF and create the command parsers for the options that it accepts. Also, if any parameters have been passed to the service (via the PARMS option), they are available to the service during the Initialization phase. Here is an example of the STAF registration:
try { fHandle = new STAFHandle("STAF/Service/" + info.name); } catch (STAFException e) { return STAFResult.STAFRegistrationError; }
Note that it is recommended that your service's handle name should be "STAF/Service/" plus the name of your service.
A Java STAF Service should create a command parser for each of the requests that it accepts (such as LIST, QUERY, ADD, etc.). Here is an example of the command parser creation:
private STAFCommandParser fListParser; private STAFCommandParser fQueryParser; ... public int init(STAFServiceInterfaceLevel4.InitInfo info) { ... fListParser = new STAFCommandParser(0, false); fQueryParser = new STAFCommandParser(0, false); ... }
The STAFCommandParser class has the following constructors:
public STAFCommandParser() public STAFCommandParser(int maxArgs) public STAFCommandParser(int maxArgs, boolean caseSensitive)
The STAFCommandParser constructors accept the following arguments:
int maxArgs | Always specify "0" for this argument |
boolean caseSensitive | Indicates whether the command parser is case-sensitive. It is strongly recommended that your service's command parsers be case-insensitive. |
To add options to the command parser, use the addOption method:
public void addOption(String name, int maxAllowed, int valueRequirement)
The addOption method accepts the following arguments:
String name | The identifier string to be parsed |
| ||||||
int maxAllowed | The number of times the argument name may be repeated in a space-separated string |
| ||||||
int valueRequirement | Specifies if there is a value associated with the name identifier |
|
After adding all of the options for your service, you can specify the constraints on how the options can be specified:
public void addOptionGroup(String optionNames, int min, int max)
This method is used to specify a list of names that are to be treated as a group, and is typically used to specify groups of mutually exclusive options
The addOptionGroup method accepts the following parameters:
String optionNames | Specifies a list of names that are to be treated as a group |
int min | The minimum number of names that can be included from the optionNames group in a space-separated string passed to the STAFCommandParser |
int 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 addOptionNeed(String needers, String needees)
This method is used to specify dependency relationships between names in space-separated strings passed to the STAFCommandParser:
The addOptionNeed method allows the following parameters:
String needers | If any of these options are specified, one of the options in the needees list must be specified |
String needees | The list of needees, one of which must be specified for the needer(s) options |
Examples:
fQueryParser = new STAFCommandParser(); fQueryParser.addOption("QUERY", 1, STAFCommandParser.VALUENOTALLOWED); fQueryParser.addOption("PRINTER", 1, STAFCommandParser.VALUEREQUIRED); fQueryParser.addOption("MODEM", 1, STAFCommandParser.VALUEREQUIRED); fQueryParser.addOptionGroup("PRINTER MODEM", 0, 1); fQueryParser.addOptionNeed("PRINTER MODEM", "QUERY"); fQueryParser.addOptionNeed("QUERY", "PRINTER MODEM");
Now your service is ready to accept requests. Java STAF Services must implement the acceptRequest(STAFServiceInterfaceLevel4.RequestInfo info) method
public STAFResult acceptRequest(STAFServiceInterfaceLevel4.RequestInfo info)
The STAFServiceInterfaceLevel4.RequestInfo object contains the following fields:
String localMachine | The machine name of the local machine |
String machine | The machine name (including domain name) which submitted the request |
String effectiveMachine | The machine which submitted the request |
String processName | The name of the process which submitted the request |
int handle | The handle which submitted the request |
int trustLevel | The trust level that the local machine has granted the requesting machine |
int diagEnabled | A flag indicating whether diagnostics are enabled (0=Disabled, 1=Enabled) |
String request | The service request which has been submitted |
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.)
String lowerRequest = info.request.toLowerCase(); StringTokenizer requestTokenizer = new StringTokenizer(lowerRequest); String request = requestTokenizer.nextToken(); // call the appropriate method to handle the command if (request.equals("list")) { return handleList(info); } else if (request.equals("query")) { return handleQuery(info); }
Next, your service should implement the handleCommand methods.
private STAFResult handleList(STAFServiceInterfaceLevel4.RequestInfo info) private STAFResult handleQuery(STAFServiceInterfaceLevel4.RequestInfo info)
Each handleCommand method should first check the trust level for the incoming request to make sure that the requesting machine has the required trust level. Here is an example:
if (info.trustLevel < 2) { return new STAFResult(STAFResult.AccessDenied, "Trust level 2 required for LIST request. Requesting " + "machine's trust level: " + info.trustLevel); }
The handleCommand method should then parse the incoming request with its parser:
STAFCommandParseResult parsedRequest = fListParser.parse(info.request);
After the parse method executes, parsedResult will have the following instance variables:
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. |
After parsing the incoming request, the following methods can be called on the parsedRequest object:
optionTimes(String name)
The optionTimes method accepts the following parameters
String name | The name of the option |
Returns: the number of times a particular option was specified
optionValue(String name)
The optionValue method accepts the following parameters
String name | The name of the option |
Returns: the value of the first instance of an option.
If no instance exists, an empty string is returned.
optionValue(String name, int instanceNumber)
The optionValue method accepts the following parameters
String name | The name of the option |
int instanceNumber | The instance number (one-indexed) of the option |
Returns: the value of a specific instance of an option.
If the given instance does not exist, an empty string is returned.
numInstances()
Returns: the total number of options specified
This method, along with the following 2 methods, is useful when you need to
do custom logic when parsing through the command options.
instanceName(int instanceNumber)
The instanceName method accepts the following parameters
int instanceNumber | The instance number (one-indexed) of the option |
Returns: the name of the option for the given instance number
instanceValue(int instanceNumber)
The instanceValue method accepts the following parameters
int instanceNumber | The instance number (one-indexed) of the option |
Returns: the value of option for the given instance number
Examples:
STAFResult resolveResult = new STAFResult(); String printerValue; String modemValue; resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("printer"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } printerValue = resolveResult.result; resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("modem"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; }
Java STAF Services must implement the term() method and should unregister with STAF during the termination phase.
public int term() { try { fHandle.unRegister(); } catch (STAFException ex) { return STAFResult.STAFRegistrationError; } return STAFResult.Ok; }
Java STAF Services should take no action during the Deconstruction phase
7.2 Building a Java STAF Service
When registering a Java service (either via the STAF configuration file or via a SERVICE service's ADD request), you will need to specify JSTAF for the LIBRARY option and for the EXECUTE option, you will neet to specify the fully-qualified name of the jar file that implements the service. Note that the jar file will be automatically added to the class path by JSTAF. For example, here's the SERVICE configuration line that could be added to the STAF configuration file to register a Java STAF service called Sample whose jar file is called sample.jar and resides in the C:\STAF\services directory:
SERVICE Sample LIBRARY JSTAF EXECUTE C:\STAF\services\STAFDeviceService.jarThe jar file that you create for a Java STAF Service must contain the class file(s) that comprise your service. The jar file must also include a manifest file which identifies which class within the JAR file is your service's entry point. This will be the class you created that implements STAFServiceInterfaceLevel4. You must provide this information with a manifest section that contain Name: and Service-Class headers, which have the form:
Manifest-Version: 1.0 Name: staf/service/info Service-Class: Service class nameThe value Service class name is the name of the class that is your service's entry point. Note that the Service class files must reside within a STAF-INF/classes directory in your jar file.
For example, if your service's entry class is DeviceService.class, your manifest file should contain the following:
Manifest-Version: 1.0 Name: staf/service/info Service-Class: DeviceServiceOr if DeviceService.class is in a package called com.ibm.staf.service.deviceservice, your manifest file should contain the following:
Manifest-Version: 1.0 Name: staf/service/info Service-Class: com.ibm.staf.service.deviceservice.DeviceServiceSimply use your favorite editor to create a file called MANIFEST.MF with the two 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
You can also include nested jar files within your service's jar file. If you 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/Config/STAFRoot}/data/staf/jstaf/service/<service-name>
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.
7.3 Example: Building the Sample Java STAF Service
To build the sample Java service provided with STAF, you first need to obtain its source code from the staf source tree in directory src/staf/services/sdg_sample. The following files are provided for the sample Java service:
DeviceService.java MANIFEST.MF makefile.deviceThe contents of DeviceService.java are shown in "Appendix B: Java STAF Service Example".
The manifest file for this sample Java service is contained in MANIFEST.MF and consists of the following:
Manifest-Version: 1.0 Name: staf/service/info Service-Class: com.ibm.staf.service.deviceservice.DeviceServiceThe makefile.device file is a make file that can be used to create a STAFDeviceService.jar file by typing the following from your build/src/staf directory. See the STAF Developer's Guide available from the STAF Documentation web page for more information on building STAF.
make PROJECTS=deviceThe output when running the make file should look like:
/cygdrive/d/build/src/staf $ make PROJECTS=device *** Compiling STAFDevice Service Java Sources *** *** Copying MANIFEST.MF *** *** Creating STAFDeviceService.jar ***
Alternatively, you can just copy the Java source code files from the staf source tree and compile/build the service manually. From the directory to which you copied the source files, run the following commands:
mkdir STAF-INF mkdir STAF-INF/classes javac -d STAF-INF/classes *.java jar cfm STAFDeviceService.jar MANIFEST.MF *To list the contents of the jar file, use the "jar tf" command:
$ jar tf STAFDeviceService.jar META-INF/ META-INF/MANIFEST.MF STAF-INF/ STAF-INF/classes/ STAF-INF/classes/com/ STAF-INF/classes/com/ibm/ STAF-INF/classes/com/ibm/staf/ STAF-INF/classes/com/ibm/staf/service/ STAF-INF/classes/com/ibm/staf/service/sdg_sample/ STAF-INF/classes/com/ibm/staf/service/deviceservice/ STAF-INF/classes/com/ibm/staf/service/deviceservice/DeviceService$DeviceData.class STAF-INF/classes/com/ibm/staf/service/deviceservice/DeviceService.classThe STAFDeviceService.jar file can then be used to register the Sample Java service. For example, assuming you put the STAFDeviceService.jar file in C:\STAF\services, you can add the following line to your STAF configuration file to register it:
SERVICE JSample LIBRARY JSTAF EXECUTE C:\STAF\services\STAFDeviceService.jarAfter restarting STAF, you could then make requests to this sample Java service. For example:
D:\>staf local JSample help Response -------- DeviceService Service Help ADD (PRINTER <printerName> | MODEM <modemName>) MODEL <model> SN <serial#> DELETE PRINTER <printerName> | MODEM <modemName> LIST [PRINTERS] [MODEMS] QUERY PRINTER <printerName> | MODEM <modemName> VERSION HELP
8.0 C++ STAF Services
8.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:
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:
STAFServiceGetLevelBounds | This function is called to determine what data structure levels a service supports |
STAFServiceConstruct | This function is called to construct a service |
STAFServiceInit | This function is called to initialze a service |
STAFServiceAcceptRequest | This function is called to have the service handle a request |
STAFServiceTerm | This function is called to terminate a service |
STAFServiceDestruct | This function is called to destruct a service |
STAFRC_t STAFServiceGetLevelBounds(unsigned int levelID, unsigned int *minimum, unsigned int *maximum)
Typically, this function should contain:
{ switch (levelID) { case kServiceInfo: case kServiceInit: case kServiceAcceptRequest: { *minimum = 2; *maximum = 2; break; } case kServiceTerm: case kServiceDestruct: { *minimum = 0; *maximum = 0; break; } default: { return kSTAFInvalidAPILevel; } } return kSTAFOk; }
The purpose of this function XXX
STAFRC_t STAFServiceConstruct(STAFServiceHandle_t *pServiceHandle, void *pServiceInfo, unsigned int infoLevel, STAFString_t *pErrorBuffer)
The STAFServiceConstruct arguments are:
STAFServiceHandle_t *pServiceHandle | (OUT) A Pointer to the service's handle (this is used in all subsequent calls by STAF) |
void *pServiceInfo | (IN) A pointer to a ServiceInfo data structure |
unsigned int infoLevel | (IN) The level of the ServiceInfo data structure (use the infoLevel to determine which STAFServiceInfoLevel structure to use, for example STAFServiceInfoLevel2) |
STAFString_t *pErrorBuffer | (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 STAFServiceInfoLevel2 structure contains the following fields:
STAFString_t name | The name of the service |
STAFString_t writeLocation | This specifies a directory in which STAF is allowed to write. |
unsigned int 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. |
STAFString_t numOptions | This specifies how many options were specified for this service in the STAF.cfg file |
STAFString_t *pOptionName | This is an array of "numOptions" STAFString_t's which contain the names of the options specified in the STAF.cfg file |
STAFString_t *pOptionValue | 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:
try { if (infoLevel != 2) return kSTAFInvalidAPILevel; STAFServiceInfoLevel2 *pInfo = reinterpret_cast(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); } catch (STAFException &e) { STAFString result; result += STAFString("In DeviceService.cpp: STAFServiceConstruct") + kUTF8_SCOLON; result += STAFString("Name: ") + e.getName() + kUTF8_SCOLON; result += STAFString("Location: ") + e.getLocation() + kUTF8_SCOLON; result += STAFString("Text: ") + e.getText() + kUTF8_SCOLON; result += STAFString("Error code: ") + e.getErrorCode() + kUTF8_SCOLON; *pErrorBuffer = result.adoptImpl(); } catch (...) { STAFString error( "DeviceService.cpp: STAFServiceConstruct: Caught " "unknown exception in STAFServiceConstruct()"); *pErrorBuffer = error.adoptImpl(); } return kSTAFUnknownError;
C++ STAF Services must implement the STAFServiceInit function
STAFRC_t STAFServiceInit(STAFServiceHandle_t serviceHandle, void *pInitInfo, unsigned int initLevel, STAFString_t *pErrorBuffer)
The STAFServiceInit arguments are:
STAFServiceHandle_t serviceHandle | (IN) The service's handle (obtained from STAFServiceConstruct) |
void *pInitInfo | (IN) A pointer to a ServiceInit data structure |
unsigned int initLevel | (IN) The level of the ServiceInit data structure |
STAFString_t *pErrorBuffer | (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 STAFServiceInitLevel2 structure contains the following fields:
STAFString_t parms | The parameters specified for this service in the STAF.cfg file |
STAFString_t writeLocation | This specifies a directory in which STAF is allowed to write. |
During initialization, the C++ STAF service needs to process any parameters specified for the service in the STAF.cfg file. The C++ STAF service should also register any error codes unique to this service with the Help service.
In addition, if the service stores any persistent data, it should create a sub-directory named "service/<Service Name (lower-case)>", if it doesn't already exist, under the directory specified by the writeLocation field, that it can use to store its persistent data.
A C++ STAF Service should create a command parser for each of the requests that it accepts (such as LIST, QUERY, ADD, etc.). Here is an example of the command parser creation:
pData->fListParser = STAFCommandParserPtr(new STAFCommandParser, STAFCommandParserPtr::INIT); pData->fQueryParser = STAFCommandParserPtr(new STAFCommandParser, STAFCommandParserPtr::INIT);
The STAFCommandParser class has the following constructor:
STAFCommandParser(unsigned int maxArgs = 0, bool caseSensitive = false);
The STAFCommandParser constructor accepts the following arguments:
unsigned int maxArgs | (In) Always specify "0" for this argument |
bool caseSensitive | (In) Indicates whether the command parser is case-sensitive. It is strongly recommended that your service's command parsers be case-insensitive. |
To add options to the command parser, use the addOption function:
void addOption(const STAFString &option, unsigned int numAllowed, ValueRequirement valueReq);
The addOption function accepts the following arguments:
const STAFString &option | (In) The option name |
| ||||||
unsigned int numAllowed | (In) The number of times the argument name may be repeated in a space-separated string (0 is unlimited) |
| ||||||
ValueRequirement valueReq | (In) Specifies if there is a value associated with the name identifier |
|
After adding all of the options for your service, you can specify the constraints on how the options can be specified
void addOptionGroup(const STAFString &group, unsigned int minAllowed, unsigned int maxAllowed);
This function is used to specify a list of names that are to be treated as a group, and is typically used to specify groups of mutually exclusive options
The addOptionGroup function accepts the following parameters:
const STAFString &group | specifies a list of names that are to be treated as a group |
unsigned int minAllowed | the minimum number of names that can be included from the optionNames group in a space-separated string passed to the STAFCommandParser |
unsigned int maxAllowed | the maximum number of names that can be included from the optionNames group in a space-separated string passed to the STAFCommandParser |
void addOptionNeed(const STAFString &needers, const STAFString &needees);
This function is used to specify dependency relationships between names in space-separated strings passed to the STAFCommandParser
The addOptionNeed function allows the following parameters:
const STAFString &needers | if any of these options are specified, one of the options in the needees list must be specified |
const STAFString &needees | the list of needees, one of which must be specified for the needer(s) options |
Examples:
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");
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:
STAFServiceHandle_t serviceHandle | (IN) The service's handle (obtained from STAFServiceConstruct) |
void *pRequestInfo | (IN) A pointer to a ServiceRequest data structure |
unsigned int reqLevel | (IN) The level of the ServiceRequest data structure |
STAFString_t *pErrorBuffer | (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 STAFServiceRequestLevel2 structure contains the following fields:
STAFString_t localMachine | The long name of the machine on which the request is actually being processed |
STAFString_t machine | The long name of the machine from which the request originated |
STAFString_t effectiveMachine | The effective name of the machine from which the request originated |
STAFString_t processName | The registered name of the requesting process |
STAFString_t handle | The STAF Handle of the requesting process |
unsigned int trustLevel | The trust level of the requesting process |
int diagEnabled | A flag indicating whether diagnostics are enabled (0=Disabled, 1=Enabled) |
STAFString_t request | The actual request string |
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.)
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 (trustLevel < 4) { return STAFResultPtr(new STAFResult(kSTAFAccessDenied), STAFResultPtr::INIT); }
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:
STAFRC_t rc | This indicates whether the command was parsed successfully. Zero indicates a successful parse. Non-zero indicates an error. |
STAFString errorBuffer; | If rc is non-zero, this will contain a textual description of the error. |
After parsing the incoming request, the following functions can be called on the parsedRequest object:
unsigned int optionTimes(const STAFString &optionName);
The optionTimes method accepts the following parameters
const STAFString &optionName | The name of the option |
STAFString optionValue(const STAFString &optionName, unsigned int number = 1);
The optionValue method accepts the following parameters
const STAFString &optionName | The name of the option |
unsigned int number | The instance number (one-indexed) of the option |
Returns: the value of a specific instance of an option. If the given instance does not exist, an empty string is returned.
unsigned int numInstances();
Returns: the total number of options specified.
This method, along with the following 2 methods, is useful when you need to
do custom logic when parsing through the command options
STAFString instanceName(unsigned int number);
The instanceName method accepts the following parameters
number | The instance number (one-indexed) of the option |
Returns: the name of the option for the given instance number
STAFString instanceValue(unsigned int number);
The instanceValue method accepts the following parameters
number | The instance number (one-indexed) of the option |
Returns: the value of option for the given instance number
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(STAFServiceRequestLevel2 *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(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData, const STAFString &theString) { if (STAFString(pInfo->machine) == STAFString(pInfo->localMachine)) { return pData->fHandlePtr->submit(sLocal, sVar, sResStrHandle + STAFString(pInfo->handle) + sResStrResolve + pData->fHandlePtr->wrapData(theString)); } return pData->fHandlePtr->submit(sLocal, sVar, sResStrGResolve + pData->fHandlePtr->wrapData(theString)); }
C++ STAF Services must implement the STAFServiceTerm function
STAFRC_t STAFServiceTerm(STAFServiceHandle_t serviceHandle, void *pTermInfo, unsigned int termLevel, STAFString_t *pErrorBuffer)
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;
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
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 STAFServiceDestructThe 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=stafThe 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.expThe 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 STAFDeviceServiceAfter 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
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.
Here is the /staf/lang/java/service/STAFServiceInterfaceLevel4.java interface:
package com.ibm.staf.service; import com.ibm.staf.*; import java.util.jar.JarFile; // STAFServiceInterfaceLevel4 - This class defines the Level 4 inteface 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. package com.ibm.staf.service; import com.ibm.staf.*; import java.util.jar.JarFile; // STAFServiceInterfaceLevel4 - This class defines the Level 4 inteface 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 STAFServiceInterfaceLevel4 { static public class InitInfo { public String name; public String parms; public JarFile serviceJar; public InitInfo(String name, String parms, JarFile serviceJar) { this.name = name; this.parms = parms; this.serviceJar = serviceJar; } } static public class RequestInfo { public String localMachine; public String machine; public String effectiveMachine; public String processName; public int handle; public int trustLevel; public int diagEnabled; public String request; public RequestInfo (String localMachine, String machine, String effectiveMachine, String processName, int handle, int trustLevel, int diagEnabled, String request) { this.machine = machine; this.localMachine = localMachine; this.effectiveMachine = effectiveMachine; this.processName = processName; this.handle = handle; this.trustLevel = trustLevel; this.diagEnabled = diagEnabled; this.request = request; } } int init(InitInfo initInfo); STAFResult acceptRequest(RequestInfo reqInfo); int term(); }
Here is a complete example of a sample Java STAF service (DeviceService.java):
/*****************************************************************************/ /* Software Testing Automation Framework (STAF) */ /* (C) Copyright IBM Corp. 2001 */ /* */ /* This software is licensed under the Common Public License (CPL) V1.0. */ /*****************************************************************************/ package com.ibm.staf.service.deviceservice; import com.ibm.staf.*; import com.ibm.staf.service.*; import java.util.TreeMap; import java.util.Iterator; import java.util.StringTokenizer; public class DeviceService implements STAFServiceInterfaceLevel4 { private final String kVersion = "1.1.0"; private static final int kDeviceInvalidSerialNumber = 4001; private String fServiceName; private STAFHandle fHandle; private STAFCommandParser fListParser; private STAFCommandParser fQueryParser; private STAFCommandParser fAddParser; private STAFCommandParser fDeleteParser; private String fLineSep; private TreeMap fPrinterMap = new TreeMap(); private TreeMap fModemMap = new TreeMap(); public DeviceService() {} public int init(STAFServiceInterfaceLevel4.InitInfo info) { int rc = STAFResult.Ok; try { fServiceName = info.name; fHandle = new STAFHandle("STAF/SERVICE/" + info.name); } catch (STAFException e) { return STAFResult.STAFRegistrationError; } // 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"); fLineSep = fHandle.submit2("local", "var", "resolve {STAF/Config/Sep/Line}").result; // Register Help Data registerHelpData( kDeviceInvalidSerialNumber, "Invalid serial number", "A non-numeric value was specified for serial number"); return rc; } public STAFResult acceptRequest(STAFServiceInterfaceLevel4.RequestInfo info) { String lowerRequest = info.request.toLowerCase(); StringTokenizer requestTokenizer = new StringTokenizer(lowerRequest); String request = requestTokenizer.nextToken(); // call the appropriate method to handle the command if (request.equals("list")) { return handleList(info); } else if (request.equals("query")) { return handleQuery(info); } else if (request.equals("add")) { return handleAdd(info); } else if (request.equals("delete")) { return handleDelete(info); } else if (request.equals("help")) { return handleHelp(); } else if (request.equals("version")) { return handleVersion(); } else { return new STAFResult(STAFResult.InvalidRequestString, "Unknown DeviceService Request: " + lowerRequest); } } private STAFResult handleHelp() { return new STAFResult(STAFResult.Ok, "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"); } private STAFResult handleVersion() { return new STAFResult(STAFResult.Ok, kVersion); } private STAFResult handleAdd(STAFServiceInterfaceLevel4.RequestInfo info) { // Check whether Trust level is sufficient for this command. if (info.trustLevel < 3) { return new STAFResult(STAFResult.AccessDenied, "Trust level 3 required for ADD request. Requesting " + "machine's trust level: " + info.trustLevel); } STAFResult result = new STAFResult(); STAFResult resolveResult = new STAFResult(); String resultString = ""; STAFCommandParseResult parsedRequest = fAddParser.parse(info.request); String printerValue = ""; String modemValue = ""; String modelValue = ""; String snValue = ""; if (parsedRequest.rc != STAFResult.Ok) { return new STAFResult(STAFResult.InvalidRequestString, parsedRequest.errorBuffer); } resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("printer"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } printerValue = resolveResult.result; resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("modem"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } modemValue = resolveResult.result; resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("model"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } modelValue = resolveResult.result; resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("sn"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } snValue = resolveResult.result; // Verify that the serial number is numeric try { new Integer(snValue); } catch (NumberFormatException e) { // Note that instead of creating a new error code specific for // this service, could use STAFResult.InvalidValue instead. return new STAFResult(kDeviceInvalidSerialNumber, snValue); } if (!printerValue.equals("")) { synchronized (fPrinterMap) { fPrinterMap.put(printerValue, new DeviceData(modelValue, snValue)); } } else if (!modemValue.equals("")) { synchronized (fModemMap) { fModemMap.put(modemValue, new DeviceData(modelValue, snValue)); } } return new STAFResult(STAFResult.Ok, resultString); } private STAFResult handleList(STAFServiceInterfaceLevel4.RequestInfo info) { // Check whether Trust level is sufficient for this command. if (info.trustLevel < 2) { return new STAFResult(STAFResult.AccessDenied, "Trust level 2 required for LIST request. Requesting " + "machine's trust level: " + info.trustLevel); } STAFResult result = new STAFResult(STAFResult.Ok, ""); String resultString = ""; STAFCommandParseResult parsedRequest = fListParser.parse(info.request); int printersOption; int modemsOption; if (parsedRequest.rc != STAFResult.Ok) { return new STAFResult(STAFResult.InvalidRequestString, parsedRequest.errorBuffer); } printersOption = parsedRequest.optionTimes("printers"); modemsOption = parsedRequest.optionTimes("modems"); boolean defaultList = false; if (printersOption == 0 && modemsOption == 0) { defaultList = true; } if (defaultList || printersOption > 0) { Iterator iter = fPrinterMap.keySet().iterator(); while (iter.hasNext()) { String key = (String)iter.next(); DeviceData data = (DeviceData)fPrinterMap.get(key); resultString += key + ";" + data.model + ";" + data.sn + "\n"; } } if (defaultList || modemsOption > 0) { Iterator iter = fModemMap.keySet().iterator(); while (iter.hasNext()) { String key = (String)iter.next(); DeviceData data = (DeviceData)fModemMap.get(key); resultString += key + ";" + data.model + ";" + data.sn + "\n"; } } return new STAFResult(STAFResult.Ok, resultString); } private STAFResult handleQuery(STAFServiceInterfaceLevel4.RequestInfo info) { // Check whether Trust level is sufficient for this command. if (info.trustLevel < 2) { return new STAFResult(STAFResult.AccessDenied, "Trust level 2 required for QUERY request. Requesting " + "machine's trust level: " + info.trustLevel); } STAFResult result = new STAFResult(STAFResult.Ok, ""); String resultString = ""; STAFResult resolveResult = new STAFResult(); STAFCommandParseResult parsedRequest = fQueryParser.parse(info.request); String printerValue; String modemValue; if (parsedRequest.rc != STAFResult.Ok) { return new STAFResult(STAFResult.InvalidRequestString, parsedRequest.errorBuffer); } resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("printer"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } printerValue = resolveResult.result; resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("modem"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } modemValue = resolveResult.result; if (!printerValue.equals("")) { if (fPrinterMap.containsKey(printerValue)) { String printer = printerValue; DeviceData data = (DeviceData)fPrinterMap.get(printer); resultString = "Printer : " + printer + "\n"; resultString += "Model : " + data.model + "\n"; resultString += "Serial# : " + data.sn + "\n"; } else { return new STAFResult(STAFResult.DoesNotExist, printerValue); } } else if (!modemValue.equals("")) { if (fModemMap.containsKey(modemValue)) { String modem = modemValue; DeviceData data = (DeviceData)fModemMap.get(modem); resultString = "Modem : " + modem + "\n"; resultString += "Model : " + data.model + "\n"; resultString += "Serial# : " + data.sn + "\n"; } else { return new STAFResult(STAFResult.DoesNotExist, printerValue); } } return new STAFResult(STAFResult.Ok, resultString); } private STAFResult handleDelete(STAFServiceInterfaceLevel4.RequestInfo info) { // Check whether Trust level is sufficient for this command. if (info.trustLevel < 4) { return new STAFResult(STAFResult.AccessDenied, "Trust level 4 required for DELETE request. Requesting " + "machine's trust level: " + info.trustLevel); } STAFResult result = new STAFResult(STAFResult.Ok, ""); String resultString = ""; STAFResult resolveResult = new STAFResult(); STAFCommandParseResult parsedRequest = fDeleteParser.parse(info.request); String printerValue; String modemValue; if (parsedRequest.rc != STAFResult.Ok) { return new STAFResult(STAFResult.InvalidRequestString, parsedRequest.errorBuffer); } resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("printer"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } printerValue = resolveResult.result; resolveResult = resolveVar(info.localMachine, parsedRequest.optionValue("modem"), info.handle); if (resolveResult.rc != STAFResult.Ok) { return resolveResult; } modemValue = resolveResult.result; if (!printerValue.equals("")) { synchronized (fPrinterMap) { if (fPrinterMap.containsKey(printerValue)) { fPrinterMap.remove(printerValue); } else { return new STAFResult(STAFResult.DoesNotExist, printerValue); } } } else if (!modemValue.equals("")) { synchronized (fModemMap) { if (fModemMap.containsKey(modemValue)) { fModemMap.remove(modemValue); } else { return new STAFResult(STAFResult.DoesNotExist, modemValue); } } } return new STAFResult(STAFResult.Ok, resultString); } public int term() { try { // Un-register Help Data unregisterHelpData(kDeviceInvalidSerialNumber); // Un-register the service handle fHandle.unRegister(); } catch (STAFException ex) { return STAFResult.STAFRegistrationError; } return STAFResult.Ok; } // this method will resolve any STAF variables that // are contained within the Option Value private STAFResult resolveVar(String machine, String optionValue, int handle) { String value = ""; STAFResult resolvedResult = null; if (optionValue.indexOf("{") != -1) { resolvedResult = fHandle.submit2(machine, "var", "handle " + handle + " resolve " + optionValue); if (resolvedResult.rc != 0) { return resolvedResult; } value = resolvedResult.result; } else { value = optionValue; } return new STAFResult(STAFResult.Ok, value); } // Register error codes for the STAX 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 the STAX 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; } } }
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 - 1 */ /* ServiceInit - 1 */ /* ServiceRequest - 1 */ /* 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 void * STAFServiceHandle_t; enum STAFServiceLevelID { kServiceInfo = 0, kServiceInit = 1, kServiceAcceptRequest = 2, kServiceTerm = 3, kServiceDestruct = 4 }; /**********************************************************************/ /* 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. */ /* 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 STAFServiceInfoLevel2 { STAFString_t name; STAFString_t exec; STAFString_t writeLocation; unsigned int numOptions; STAFString_t *pOptionName; STAFString_t *pOptionValue; }; struct STAFServiceInfoLevel1 { STAFString_t name; STAFString_t exec; 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 STAFServiceInitLevel2 { STAFString_t parms; STAFString_t writeLocation; }; struct STAFServiceInitLevel1 { STAFString_t parms; }; /*********************************************************************/ /* STAF passes in this structure on a STAFServiceAcceptRequest call. */ /* The data members have the following meanings: */ /* */ /* localMachine - The long name of the machine on which the */ /* request is actually being processed */ /* machine - The long name of the machine from which the */ /* request originated */ /* effectiveMachine - The effective name of the machine from which */ /* the request originated (see the discussion */ /* of long vs. short names in the STAF User's */ /* Guide) */ /* processName - The registered name of the requesting */ /* process */ /* handle - The STAF Handle of the requesting process */ /* trustLevel - The trust level of the requesting process */ /* diagEnabled - Indicates if diagnostics are enabled or not */ /* 1=Enabled, 0=Disabled */ /* request - The actual request string */ /*********************************************************************/ struct STAFServiceRequestLevel2 { STAFString_t localMachine; STAFString_t machine; STAFString_t effectiveMachine; STAFString_t processName; STAFHandle_t handle; unsigned int trustLevel; unsigned int diagEnabled; STAFString_t request; }; struct STAFServiceRequestLevel1 { STAFString_t localMachine; STAFString_t machine; STAFString_t effectiveMachine; STAFString_t processName; STAFHandle_t handle; unsigned int trustLevel; STAFString_t request; }; /*********************************************************************/ /* 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 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 Common Public License (CPL) 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 */ /* */ /* This software is licensed under the Common Public License (CPL) V1.0. */ /*****************************************************************************/ #include "STAF.h" #include <deque> #include <map> #include "STAFMutexSem.h" #include "STAFCommandParser.h" #include "STAFServiceInterface.h" #include "DeviceService.h" // Device Data - contains data for a device struct DeviceData { DeviceData() { /* Do Nothing */ } DeviceData(const STAFString &aName, const STAFString &aType, const STAFString &aSN) : name(aName), type(aType), serialNumber(aSN) { /* Do Nothing */ } STAFString name; // Device name STAFString type; // Device type 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 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 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 sLineSep; static const STAFString sVersionInfo("1.1.0"); static const STAFString sLocal("local"); static const STAFString sHelp("help"); static const STAFString sVar("var"); static const STAFString sResStrHandle("HANDLE "); static const STAFString sResStrResolve(" RESOLVE "); static const STAFString sResStrGResolve("GLOBAL RESOLVE "); static const STAFString sLeftCurlyBrace(kUTF8_LCURLY); // Prototypes static STAFResultPtr handleAdd(STAFServiceRequestLevel2 *, DeviceServiceData *); static STAFResultPtr handleDelete(STAFServiceRequestLevel2 *, DeviceServiceData *); static STAFResultPtr handleQuery(STAFServiceRequestLevel2 *, DeviceServiceData *); static STAFResultPtr handleRelease(STAFServiceRequestLevel2 *, DeviceServiceData *); static STAFResultPtr handleList(STAFServiceRequestLevel2 *, DeviceServiceData *); static STAFResultPtr handleHelp(STAFServiceRequestLevel2 *, DeviceServiceData *); static STAFResultPtr handleVersion(STAFServiceRequestLevel2 *, DeviceServiceData *); static STAFResultPtr resolveStr(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData, const STAFString &theString); static STAFResultPtr resolveOp(STAFServiceRequestLevel2 *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: case kServiceInit: case kServiceAcceptRequest: { *minimum = 2; *maximum = 2; 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 != 2) return kSTAFInvalidAPILevel; STAFServiceInfoLevel2 *pInfo = reinterpret_cast<STAFServiceInfoLevel2 *>(pServiceInfo); DeviceServiceData data; data.fDebugMode = 0; data.fShortName = pInfo->name; data.fName = "STAF/Service/"; data.fName += pInfo->name; for (unsigned int i = 0; i < pInfo->numOptions; ++i) { if (STAFString(pInfo->pOptionName[i]).upperCase() == "DEBUG") { data.fDebugMode = 1; } else { STAFString optionError(pInfo->pOptionName[i]); *pErrorBuffer = optionError.adoptImpl(); return kSTAFServiceConfigurationError; } } // Set service handle *pServiceHandle = new DeviceServiceData(data); return kSTAFOk; } catch (STAFException &e) { STAFString result; result += STAFString("In DeviceService.cpp: STAFServiceConstruct") + kUTF8_SCOLON; result += STAFString("Name: ") + e.getName() + kUTF8_SCOLON; result += STAFString("Location: ") + e.getLocation() + kUTF8_SCOLON; result += STAFString("Text: ") + e.getText() + kUTF8_SCOLON; result += STAFString("Error code: ") + e.getErrorCode() + kUTF8_SCOLON; *pErrorBuffer = result.adoptImpl(); } catch (...) { STAFString error( "DeviceService.cpp: STAFServiceConstruct: Caught " "unknown exception in STAFServiceConstruct()"); *pErrorBuffer = error.adoptImpl(); } return kSTAFUnknownError; } STAFRC_t STAFServiceInit(STAFServiceHandle_t serviceHandle, void *pInitInfo, unsigned int initLevel, STAFString_t *pErrorBuffer) { STAFRC_t retCode = kSTAFUnknownError; try { if (initLevel != 2) return kSTAFInvalidAPILevel; DeviceServiceData *pData = reinterpret_cast<DeviceServiceData *>(serviceHandle); STAFServiceInitLevel2 *pInfo = reinterpret_cast<STAFServiceInitLevel2 *>(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); // Get line separator STAFString getRequest = "RESOLVE {STAF/Config/Sep/Line}"; STAFResultPtr result = pData->fHandlePtr->submit("LOCAL", "VAR", getRequest); if (result->rc != 0) { *pErrorBuffer = result->result.adoptImpl(); return result->rc; } else sLineSep = result->result; pData->fPrinterMapSem = STAFMutexSemPtr(new STAFMutexSem, STAFMutexSemPtr::INIT); pData->fModemMapSem = STAFMutexSemPtr(new STAFMutexSem, STAFMutexSemPtr::INIT); // 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 in STAFServiceServiceInit()"); *pErrorBuffer = error.adoptImpl(); } return retCode; } STAFRC_t STAFServiceAcceptRequest(STAFServiceHandle_t serviceHandle, void *pRequestInfo, unsigned int reqLevel, STAFString_t *pResultBuffer) { if (reqLevel != 2) return kSTAFInvalidAPILevel; STAFRC_t retCode = kSTAFUnknownError; try { STAFResultPtr result(new STAFResult(), STAFResultPtr::INIT); STAFServiceRequestLevel2 *pInfo = reinterpret_cast<STAFServiceRequestLevel2 *>(pRequestInfo); DeviceServiceData *pData = reinterpret_cast<DeviceServiceData *>(serviceHandle); 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 { result = STAFResultPtr(new STAFResult(kSTAFInvalidRequestString, request.subWord(0, 1)), STAFResultPtr::INIT); } *pResultBuffer = result->result.adoptImpl(); retCode = result->rc; } catch (STAFException &e) { 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 in STAFServiceAcceptRequest()"); *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 in STAFServiceTerm()"); *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 in STAFServiceDestruct()"); *pErrorBuffer = error.adoptImpl(); } return retCode; } // Handles device add entry requests STAFResultPtr handleAdd(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData) { STAFString result; STAFRC_t rc = kSTAFOk; if (pInfo->trustLevel < 3) { return STAFResultPtr(new STAFResult(kSTAFAccessDenied, STAFString()), STAFResultPtr::INIT); } STAFCommandParseResultPtr parsedResult = pData->fAddParser->parse(pInfo->request); if (parsedResult->rc != kSTAFOk) { return STAFResultPtr(new STAFResult(kSTAFInvalidRequestString, parsedResult->errorBuffer), STAFResultPtr::INIT); } 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; resultPtr = resolveOp(pInfo, pData, parsedResult, "MODEL"); if (resultPtr->rc != 0) return resultPtr; STAFString model = resultPtr->result; 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); } if (printer != "") { STAFMutexSemLock lock(*pData->fPrinterMapSem); pData->fPrinterMap.insert(DeviceMap::value_type(printer, DeviceDataPtr(new DeviceData(printer, model, serialNumber), DeviceDataPtr::INIT))); } else if (modem != "") { STAFMutexSemLock lock(*pData->fModemMapSem); pData->fModemMap.insert(DeviceMap::value_type(modem, DeviceDataPtr(new DeviceData(modem, model, serialNumber), DeviceDataPtr::INIT))); } // Return an Ok result return STAFResultPtr(new STAFResult(kSTAFOk, result), STAFResultPtr::INIT); } // Handles device deletion requests STAFResultPtr handleDelete(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData) { STAFString result; STAFRC_t rc = kSTAFOk; if (pInfo->trustLevel < 4) { return STAFResultPtr(new STAFResult(kSTAFAccessDenied, STAFString()), STAFResultPtr::INIT); } STAFCommandParseResultPtr parsedResult = pData->fDeleteParser->parse(pInfo->request); if (parsedResult->rc != kSTAFOk) { return STAFResultPtr(new STAFResult(kSTAFInvalidRequestString, parsedResult->errorBuffer), STAFResultPtr::INIT); } 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; if (printer != "") { STAFMutexSemLock lock(*pData->fPrinterMapSem); pData->fPrinterMap.erase(printer); } else if (modem != "") { STAFMutexSemLock lock(*pData->fModemMapSem); pData->fModemMap.erase(modem); } // Return an Ok result return STAFResultPtr(new STAFResult(kSTAFOk, result), STAFResultPtr::INIT); } // Handles device list requests STAFResultPtr handleList(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData) { STAFString result; STAFRC_t rc = kSTAFOk; if (pInfo->trustLevel < 2) { return STAFResultPtr(new STAFResult(kSTAFAccessDenied, STAFString()), STAFResultPtr::INIT); } STAFCommandParseResultPtr parsedResult = pData->fListParser->parse(pInfo->request); if (parsedResult->rc != kSTAFOk) { return STAFResultPtr(new STAFResult(kSTAFInvalidRequestString, parsedResult->errorBuffer), STAFResultPtr::INIT); } 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; } if (printers || all) { STAFMutexSemLock lock(*pData->fPrinterMapSem); DeviceMap::iterator printerIterator; for(printerIterator = pData->fPrinterMap.begin(); printerIterator != pData->fPrinterMap.end(); ++printerIterator) { result += printerIterator->second->name + ";" + printerIterator->second->type + ";" + printerIterator->second->serialNumber + sLineSep; } } if (modems || all) { STAFMutexSemLock lock(*pData->fModemMapSem); DeviceMap::iterator modemIterator; for(modemIterator = pData->fModemMap.begin(); modemIterator != pData->fModemMap.end(); ++modemIterator) { result += modemIterator->second->name + ";" + modemIterator->second->type + ";" + modemIterator->second->serialNumber + sLineSep; } } // Return an Ok result return STAFResultPtr(new STAFResult(kSTAFOk, result), STAFResultPtr::INIT); } // Handles device query requests STAFResultPtr handleQuery(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData) { STAFString result; STAFRC_t rc = kSTAFOk; if (pInfo->trustLevel < 2) { return STAFResultPtr(new STAFResult(kSTAFAccessDenied, STAFString()), STAFResultPtr::INIT); } STAFCommandParseResultPtr parsedResult = pData->fQueryParser->parse(pInfo->request); if (parsedResult->rc != kSTAFOk) { return STAFResultPtr(new STAFResult(kSTAFInvalidRequestString, parsedResult->errorBuffer), STAFResultPtr::INIT); } 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; if (printer != "") { STAFMutexSemLock lock(*pData->fPrinterMapSem); DeviceMap::iterator printerIterator; printerIterator = pData->fPrinterMap.find(printer); if (printerIterator == pData->fPrinterMap.end()) { return STAFResultPtr(new STAFResult(kSTAFDoesNotExist, printer), STAFResultPtr::INIT); } result += "Printer : " + printerIterator->second->name + sLineSep; result += "Model : " + printerIterator->second->type + sLineSep; result += "Serial# : " + printerIterator->second->serialNumber + sLineSep; } if (modem != "") { STAFMutexSemLock lock(*pData->fModemMapSem); DeviceMap::iterator modemIterator; modemIterator = pData->fModemMap.find(modem); if (modemIterator == pData->fModemMap.end()) { return STAFResultPtr(new STAFResult(kSTAFDoesNotExist, modem), STAFResultPtr::INIT); } result += "Modem : " + modemIterator->second->name + sLineSep; result += "Model : " + modemIterator->second->type + sLineSep; result += "Serial# : " + modemIterator->second->serialNumber + sLineSep; } // Return the result of the query return STAFResultPtr(new STAFResult(kSTAFOk, result), STAFResultPtr::INIT); } STAFResultPtr handleHelp(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData) { if (pInfo->trustLevel < 1) { return STAFResultPtr(new STAFResult(kSTAFAccessDenied, STAFString()), STAFResultPtr::INIT); } STAFString help("DEVICE Service Help" + sLineSep + sLineSep); help += "ADD (PRINTER <printerName> | MODEM <modemName>)"; help += " MODEL <model> SN <serial#>"; help += sLineSep; help += "DELETE (PRINTER <printerName> | MODEM <modemName>) CONFIRM"; help += sLineSep; help += "LIST [PRINTERS] [MODEMS]" + sLineSep; help += "QUERY PRINTER <printerName> | MODEM <modemName>"; help += sLineSep; help += "VERSION" + sLineSep; help += "HELP" + sLineSep; return STAFResultPtr(new STAFResult(kSTAFOk, help), STAFResultPtr::INIT); } STAFResultPtr handleVersion(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData) { if (pInfo->trustLevel < 1) { return STAFResultPtr(new STAFResult(kSTAFAccessDenied, STAFString()), STAFResultPtr::INIT); } return STAFResultPtr(new STAFResult(kSTAFOk, sVersionInfo), STAFResultPtr::INIT); } STAFResultPtr resolveOp(STAFServiceRequestLevel2 *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(STAFServiceRequestLevel2 *pInfo, DeviceServiceData *pData, const STAFString &theString) { if (STAFString(pInfo->machine) == STAFString(pInfo->localMachine)) { return pData->fHandlePtr->submit(sLocal, sVar, sResStrHandle + STAFString(pInfo->handle) + sResStrResolve + pData->fHandlePtr->wrapData(theString)); } return pData->fHandlePtr->submit(sLocal, sVar, sResStrGResolve + 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)); }
This is the end of the document.