![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() | ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Composer Enterprise Server User
CHAPTER 4
Most of the time, you will find Composer's native deployment facilities and packaging options more than adequate to meet the architectural requirements of your business applications. But if your development needs are such that it's essential to be able to manipulate Composer-built services on a programmatic level, you will need to know how to write code that leverages Composer's Framework API for low-level Java integration.
The Composer framework is a set of classes (in source code form) for working with, or extending, Composer runtime objects. Its features are discussed in some detail later in this chapter.
In many cases, you can create your own custom service-trigger objects without hand-writing any "setup" code. Novell exteNd Director has code-generation wizards that can create servlet, EJB, JSP, and Java stub files for you, which you can then customize. (See the Deployment chapter of the main Composer User's Guide for more detailed information on how to use these wizards.) But to fully understand the generated skel-code, you need to be familiar with the basic architectural assumptions and API requirements of Composer's runtime layer. The information in this chapter will give you the essential background info you need in order to create classes that interact with Composer runtime objects.
NOTE: This chapter is aimed at intermediate-level (or higher) Java programmers who are interested in understanding the application programming interface for code-level access to Composer runtime objects. To benefit from this chapter, you should be thoroughly familiar with servlet and bean programming, and J2EE app server runtime idioms in general.
This chapter will be of help to you if you need to:
Invoke Composer services programmatically from your own Java classes
Augment existing Composer "data input" functionality by providing your own support for transports, protocols, or data formats not natively supported by Composer
Create service triggers that respond to events not natively supported by Composer's existing trigger types
Obtain direct access to a service's output art runtime so that you can perform custom post-processing of data or do some kind of custom dispatching of data, etc.
NOTE: The following discussion deals with runtime issues only. A software development kit (SDK) for creating your own pluggable design-time artifacts in Composer is available by special request through the Novell exteNd marketing organization.
The core functionality of Composer Enterprise Server is provided by the classes in xcs-all.jar (in Composer's \lib directory, under the app server install path), plus the three dozen or so accompanying technology-specific JAR files in the \lib directory. The classes in xcs-all.jar provide all of the essential "core services" your deployed Composer apps need in order to run on the server, including:
Data conversion (preprocessing) in advance of service execution
Basic support functions, like XML parsing, XSL processing, etc.
Support for various kinds of connectivity (LDAP, JDBC, etc.)
Instantiation and execution of service objects is done through decoding and deserialization of the metadata stored in your deployment archives. When you create a service or component in Composer (design time), you are actually creating an XML file that wrappers the actions in your service or component's Action Model. If you've ever examined the contents of a Composer-created deployment archive, you will probably have noticed that it contains no compiled classes (except if the deployment involves EJBs).
Instead of bytecode, each action in each component's Action Model consists of a metadata description. Composer Enterprise Server understands how to convert that description into executable code at runtime. The classes that do this are opaque: They are not exposed in the Framework API (see below), except for the main execute( ) method of GXSServiceComponent
.
Invocation of a Composer service typically occurs through a servlet. But (again) you'll notice there are no servlets in your deployment WAR or EAR. Invocation is handled by a "master servlet" already residing on the server. Your deployment archive contains only a metadata description of how to call the server-resident "trigger servlet." (That description is in the web.xml file in the WAR.) The metadata description contains initialization parameters for the servlet. Those parameters include the name of the service that needs to be run, the name of the "converter class" that should be used for preprocessing arriving data, whether to instantiate the service as an EJB, etc.
From Composer Enterprise Server's point of view, the events that typically accompany invocation of a Composer service include the following:
A request arrives at the app server: e.g., XML arrives via HTTP POST. The server notifies the appropriate servlet, in this case GXSServiceRunnerEx (a pre-installed, always-present Composer Enterprise Server class that handles most servlet-based requests for Composer services).
The service-runner servlet uses Composer Enterprise Server's GXSServiceFactory class to obtain an instance of the desired kind of service (represented by the GXSServiceComponent shown above).
The service runner calls on the appropriate converter class (one of several core Composer Server utility classes) to fetch arriving data and put it in String or String-array format. Converter classes are discussed in more detail below.
Finally, the service runner calls the service component's execute( ) method. In the typical case, this method returns a Java String containing the XML output of the service. (Various overloaded versions of the method exist, each with its own return type.)
Once the service has finished executing, the servlet performs any necessary post-processing on the output data (for example, last-minute XSL transformations), in its processResponse( ) method.
There are many possible variations on the scheme just described. The above diagram describes one common scenario, involving servlets and HTTP requests. It is intended to illustrate important Composer architectural idioms, such as:
The use of a "service runner" object (in this case, a servlet) to run a Composer service
The use of a factory to obtain the instantiated service. Delegation through a factory object makes it possible for Composer to do behind-the-scenes housekeeping (including things like cache management) in a way that's transparent to the service runner. It also simplifies working with EJB deployments, since the service factory can obtain a service as a regular Java object or as an EJB, based on the request parameters.
The separation of data-prefetch logic from service invocation logic by means of converter classes (which handle the details of collecting XML input from various kinds of HTTP payloads)
Obviously, not all data travels by HTTP, and it's not always convenient to invoke services from a servlet. Other scenarios need to be taken into account.
One useful variation on the above invocation scheme is afforded by the GXSServiceComponentBean class, wherein a bean implements the IGXSServiceRunner interface. The GXSServiceComponentBean provides extra isolation between the client/request layer and the invocation-target layer, so that it becomes possible for a single type of Java object (the bean) to field requests from many potential types of client objects (servlets, JSPs, arbitrary Java objects). Experienced developers will recognize features of the well-known Proxy and Facade design patterns in this approach.
Remote access to Composer services can also occur through EJBs. The GXSEJBServiceComponent class implements javax.ejb.EnterpriseBean, IGXSServiceRunner, java.io.Serializable, and javax.ejb.SessionBean. Likewise, there is an EJB equivalent of GXSServiceComponent, called GXSEJBService. Enterprise Java Beans make possible the use of any number of well-known design patterns.
In addition to the familiar "request-response" paradigm, of course, it's possible to enlist Composer services in other operational flows. For example, you might have a Composer service that starts up in response to a scheduling daemon of some kind and executes at regular timed intervals. It might not use any input data; it may or may not produce any output. Perhaps it performs a recurring maintenance function. This type of specialized invocation scenario can be supported through the use of a custom trigger object (your own, or derived from a framework object) that implements the IGXSServiceRunner interface.
Source code for many of the classes and interfaces just mentioned can be found in the Composer Enterprise Server framework distribution archive, xcs-src.jar (see next section). The main classes are discussed in more depth below, but for definitive information you should consult the source code or the Javadoc.
To facilitate working with Composer deployment and runtime objects, Novell provides a set of framework classes that can be used to create custom Service Triggers for Composer services, alter the Composer JSP tag library, change the way data is passed, etc. This framework comprises a runtime API for working with Composer services.
You will find the framework files in the AppServer\Composer\lib path under your main \exteNd install directory. Look for these two files:
api-xs.zip: This archive contains the JavaDoc files (HTML) for the framework API.
xcs-src.jar: This archive contains Java source code for the approximately 130 classes that make up the framework. (Included in this set of files are the sources for the custom JSP tag library that comes with Composer. For a description of the tag library, see the appendix in the main Composer User's Guide.)
Unless you have unusually far-reaching requirements, it's unlikely that you will work with more than a handful of the 130+ classes in xcs-src.jar. Nevertheless, a great deal of useful example code can be found there for working with Composer services using servlet technology, EJB technology, SOAP, JSP taglib, transaction managers, etc.
Some of the more interesting packages include:
com.sssw.b2b.xs.deploy.wl70: Helper classes to install J2EE components into the WebLogic Server 7.0, utilizing capabilities of the DeployerRuntimeMBean class.
com.sssw.b2b.xs.deploy.ws50: Support classes for deploying to WebSphere, utilizing AppManager features.
com.sssw.b2b.xs.bean: This package contains Java beans that can instantiate and utilize a deployed Composer service. The classes provide for separation of input conversion from component execution.
com.sssw.b2b.xs.ejb: This package provides an EJB session bean class for obtaining remote access to Composer service components, as well as home and remote interfaces for same.
com.sssw.b2b.xs.service.conversion: Contains various helper classes for obtaining XML data by way of various transports and packagings. (These classes will be discussed in further detail below.)
com.sssw.b2b.xs.mail: Contains classes that make an entry point from SMTP/MIME/POP3 to deployed services.
com.sssw.b2b.xs.deploy2.tc4: Deploy handlers for Tomcat 4.1 platform.
com.sssw.b2b.xs.soap: Provides an implementation of a service trigger that responds to SOAP requests, utilizing Novell exteNd WSSDK technology.
com.sssw.b2b.xs.util: A grabbag of utility classes, including classes to manipulate JARs, a vulture class that watches a certain directory for incoming files, a class to represent the manifest.mf file found in Java archives, and classes with miscellaneous static convenience methods.
See the file called constant-values.html in xcs-src.jar for a comprehensive list of constants used in the framework classes.
The framework allows you to use your own objects to instantiate and execute Composer services. This capability can be important for many development scenarios. For example:
You can use your own objects to perform custom pre-processing of data (perhaps converting non-XML data to XML) before passing it to a Composer service.
You can post-process a service's output in some custom fashion, perhaps altering its mime-type.
The framework makes it easy to augment Composer's invocation layer. For example, you might have legacy CGI scripts (in Python or PHP, say) that need to be able to call Composer services directly.
If your development efforts involve operating-system-level calls, you may have C++/Java crossover points that require direct access to Composer services.
The framework also makes it easier to customize your deployments to take advantage of special app-server services. This can sometimes be important if you're deploying to a platform that's not currently supported by Novell, or you need to "bridge across" to a non-J2EE server API of some kind.
For performance profiling, you may want to create test routines that can call Composer services directly (eliminating servlet-engine and network-stack overhead) so that you can benchmark different cache configurations, for example, without clouding the results with non-cache-related issues (browser/router/proxy latencies and such).
If you need to implement certain design patterns in your J2EE projects, it might be necessary (or convenient) to extend various framework classes.
The framework affords a great deal of flexibility in choosing how to invoke a service. A few of the possible choices are depicted in the diagram below.
The choice of how to set up your invocation layer will probably be dictated by architectural concerns related to:
Whether you are composing large, distributed web apps with reusable components, or small, "low-cost" apps that are self-contained
Whether you need to support remote invocation across machines (via RMI rather than SOAP)
Whether your data will mostly arrive by HTTP as opposed to other transports
The invocation patterns shown in the foregoing diagram are all supported, in one way or another, by the design-time deployment options of Director and Composer. If you are using the framework, it's presumably because you need to customize some aspect of the invocation layer (by extending one or more of the classes shown). That's what this discussion will focus on.
Most (but not all) Composer services operate on input data of some kind. Composer services expect to receive input data (if any) in one of the following forms:
If your input data will be arriving via HTTP, you may find it convenient to use or extend one of the framework's existing converter classes, which are designed to handle the most common HTTP transport scenarios. (See the Javadoc and/or source code for the com.sssw.b2b.xs.service.conversion framework package.)
Whether your input data arrive by HTTP or not, and whether you choose to use the framework converter classes or not, your code must be prepared to pass input data to your service in one of the formats described above.
When referring to a service name within a framework object (such as a service runner servlet), you should use only the full-context name of your service: That is to say, you should combine the deployment context with the service component name.
The following is an example of a fully qualified service name:
com.yourcompany.composer.ProductInquiry
Where:
NOTE: Novell recommends, as a best practice, that you include "composer" in the deployment context of every Composer-created artifact, and "director" in the context of every Director-built artifact. This is not only to provide namespace separation of artifacts that might be built by different development team members working remotely, but to make debugging easier. (At stack-trace time, it's valuable to be able to see, at a glance, which product the artifact was created in.)
You will generally use the static createService( ) method of the GXSServiceFactory object to obtain a reference to a so-called service component This overloaded methods comes in three flavors, with signatures as follows:
IGXSServiceComponent createService(java.lang.String fullServiceName) IGXSServiceComponent createService(IGXSServiceRunner aOriginator) IGXSServiceComponent createService(javax.naming.InitialContext aContext, java.lang.String aJNDIName)
The first case is simplest: You can obtain a (non-EJB) service by name. In the second case, the caller (an IGXSServiceRunner) passes a reference to itself; the factory inspects the caller's properties to obtain initialization parameters, then instantiates and configures the service.
The third method produces a service component as an EJB (assuming the service was deployed that way to begin with). The factory needs to know the initial JNDI context and JNDI Name of the service's home interface in order to obtain a reference to the EJB (or its accessor object). After that, the factory takes care of any communication with the EJB container.
The code for executing a service directly is straightforward. First, obtain an instance of the desired service by means of a service factory object. Then call the execute( ) method of the service object. The execute method returns the service's output document(s) as native XML in String form.
Code for calling a service can be as simple as:
String inputDoc =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><root/>";
String outputDoc = "";
String serviceName = "com.acme.composer.ProductInquiry";
try {
// Obtain an instance of the desired service:
IGXSServiceComponent myService =
GXSServiceFactory.createService( serviceName );
// Execute the service:
outputDoc = myService.execute( inputDoc );
}
catch( GXSException gxsEx )
{
// Do something with exception
}
Using this kind of code, you can invoke a Composer service from any kind of custom Java object (not just a servlet). Of course, it's the caller's job to obtain the input data for the service, so it can be passed directly in the execute method. In the bare-minimal code shown above, you are passing a single input document as a native-XML string. If you need to pass more than one document, perhaps as a DOM object (i.e., an object of type org.w3c.dom.Document), you can call one of the other variants of execute( ) or executeEx( ); see the discussion under "Data-Passing Options" below.
Instead of calling execute( ) on a factory-obtained service instance, you might find that a more flexible and architecturally robust way of doing things is to delegate service operations through an accessor object: namely, a bean. (Not an EJB, but a regular Java bean.) In this strategy, you instantiate a general-purpose bean directly, use the bean's setter methods to specify the desired service name, input document(s), and other parameters, then call execute( ) on the bean. (The bean then delegates the call to the service.)
The framework provides a utility bean for this purpose, in a class called GXSServiceComponentBean. Code for utilizing this bean typically looks similar to that shown below.
private static final String SERVICE_NAME = "com.composer.MyService"; // Legal values here are "Normal" or "EJB": private static final String SERVICE_TYPE = "Normal"; // Instantiate the bean GXSServiceComponentBean lService = new GXSServiceComponentBean(); // Configure it lService.setInputXMLDoc( aXML ); lService.setServiceName( SERVICE_NAME ); lService.setServiceType( SERVICE_TYPE ); // Now execute the service: try { lService.execute(); } catch ( GXSException e ) { System.out.println( e ); } // Obtain the service's output: String myOutput = lService.getOutputXMLDoc();
The bean mechanism offers a great deal of flexibility. The bean itself is generic: It can be "configured" dynamically to bind to any service. It implements the IGXSServiceRunner interface, which means that through a variety of setter methods, you can specify XSL resource info, converter class name, and other config parameters for the service before invoking it. Likewise, you can use a wide variety of "getters" to obtain information back from the service after it executes. In addition, the GXSServiceComponentBean class has utility methods, such as getXPath( ) and findDocByPartName( ), that can be helpful in manipulating output data.
The service-runner bean (GXSServiceComponentBean) allows you to specify, via setServiceType( ), whether to use EJB access to obtain and execute the target service (assuming it was deployed in EJB fashion), or non-EJB ("Normal") access. This hides some of the complexity of working with services deployed as EJBs.
The custom tag library used in Director-generated (and Composer-generated) JSP code is built around usage of the GXSServiceComponentBean object. (Source code for the tag library itself is part of the framework.)
NOTE: The GXSServiceComponentBean class inherits from a utility class called GXSServiceComponentBase (which in turn implements the service-runner interface). Consult the source code and/or Javadoc for these two classes to learn more about the numerous setter, getter, and utility methods they offer.
The execute( ) method on GXSServiceComponent is overloaded to allow you to pass and receive XML data in various ways. Variants of this method exist to allow passing more than one input document (as either a String array or an array of DOM objects), or passing input as a java.io.Reader. In each case, the return type mimics the input type.
There is also an overloaded method called executeEx( ) that differs from execute( ) in that it returns a GXSExResponse object, which is a lightweight wrapper object for responses from SOAP services that might involve one or more output parts and/or header parts.
The various signatures of execute( ) and executeEx( ) are shown below, along with a brief description of the intended usage..
java.lang.String execute()
Executes a Composer service that does not expect an input document.
org.w3c.dom.Document execute(org.w3c.dom.Document aInputDoc)
Executes the Composer service using the supplied DOM.
org.w3c.dom.Document execute(org.w3c.dom.Document[] aInputDocs)
Executes the Composer service using the supplied mulitple DOMs.
java.io.Reader execute(java.io.Reader xmlIn)
Executes the Composer service using the supplied XML Reader.
java.lang.String execute(java.lang.String xmlIn)
Executes the Composer service using the supplied XML string.
java.lang.String execute(java.lang.String[] aInpDocs)
Executes the Composer service using the supplied XML strings.
GXSExResponse executeEx(java.lang.String[] aInpDocs)
Executes the Composer service using the supplied XML strings.
GXSExResponse executeEx(java.lang.String[] aInpDocs, java.lang.String[] aInpHdrDocs)
Executes the Composer service using the supplied XML strings.
A service trigger, broadly speaking, is any object responsible for obtaining a service instance and executing it. In the framework, the principal trigger objects are GXSServiceRunnerand GXSServiceComponentBean. The former is a servlet; the latter is a general-purpose bean.
The GXSServiceRunner class inherits from javax.servlet.http.HttpServlet and implements the IGXSServiceRunner interface (as well as java.io.Serializable). The GXSServiceRunnerEx class differs from GXSServiceRunner in its ability to deal with one or more input documents.
GXSServiceComponentBean inherits from GXSServiceComponentBase. Both implement IGXSServiceRunner as well as java.io.Serializable. The parent class, GXSServiceComponentBase, has many getter and setter methods, allowing you to fine-tune its functionality dynamically. It is not limited to handling HTTP requests.
If you are implementing a trigger that handles data arriving via HTTP, a convenient starting point may be GXSServiceRunner or GXSServiceRunnerEx.
Of course, strictly speaking, it is not necessary for you to extend any of the framework's preexisting service-runner classes in order to execute a service. In fact, it's not even necessary for your custom trigger object to implement IGXSServiceRunner. (See "Executing the Service" for example code that neither extends nor implements framework classes.) Even so, you should understand how these classes and interfaces work.
The interface that all framework service-runner objects implement is IGXSServiceRunner. This interface has two methods, called getServiceProperty( ) and getClassLoader( ), plus numerous predefined public/static properties (Strings) that are used for parameter discovery at runtime. The getServiceProperty( ) method takes a String as an argument; the String should match one of the static property strings defined on IGXSServiceRunner. The getServiceProperty( ) method uses the String passed to it as a key to look up information about the service environment.
For example, one of the properties is called SERVICE_NAME. The hard-coded (final) value of IGXSServiceRunner.SERVICE_NAME is "servicename." If your service-runner object receives this value in a call to getServiceProperty( ), the method should return the name of the service that will be called.
The getServiceProperty( ) method is called by various objects that, from time to time, might receive a reference to your service-runner and might need to look up information about the service your runner intends to run. For example, the GXSServiceFactory object has an overloaded method called createService( ). One of the createService( ) methods takes an IGXSServiceRunner argument. Using the passed-in service-runner reference, the factory object can inspect properties on the caller to determine how to configure a service instance before returning it to the caller. This same mechanism is used by various data-converter objects in the framework.
As it turns out, your service runner does not need to define lookup values (nor "get" methods) for all of the String properties in the IGXSServiceRunner interface. Some of the properties are relevant only in specialized scenarios involving (for example) digitally signed XML in SOAP transactions. For most common scenarios, the only "discovery" properties you must make available before every call to a service factory's createService( ) method are the SERVICE_NAME and SERVICE_TYPE properties. (The latter allows the factory or converter object to discover whether the caller is expecting an EJB, or non-EJB service.)
A bare-minimal implementation of IGXSServiceRunner is shown below:
class MyServiceRunner implements IGXSServiceRunner { private String mFullServiceName; MyServiceRunner(String fullServiceName) { mFullServiceName = fullServiceName; } public String getServiceProperty(String aName) { if( aName == IGXSServiceRunner.SERVICE_NAME ) return mFullServiceName; else if( aName == IGXSServiceRunner.SERVICE_TYPE ) return IGXSServiceRunner.SERVICE_TYPE_NORMAL; else return null; } public ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } }
Note that if getServiceProperty( ) is called with an argument other than SERVICE_NAME or SERVICE_TYPE, the method returns null. It is important to return null here, because the Composer runtime objects that call getServiceProperty( ) implement default behaviors of various kinds based on a null return value being encountered. If you return a dummy value (such as "Not supported"), you will get unpredictable results.
In addition to getServiceProperty( ), your service runner needs to provide an implementation of getClassLoader( ) for use by factory objects. The implementation shown in above is appropriate for most cases.
If your code will be handling HTTP requests, you might want to extend GXSServiceRunnerEx. This is the framework's all-purpose servlet for triggering Composer services.
The following code shows how to extend GXSServiceRunnerEx. It implements a custom Java class called MyComposerServiceRunner.
package com.composer; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; import com.sssw.b2b.xs.*; import com.sssw.b2b.xs.service.GXSServiceRunnerEx; public class MyComposerServiceRunner extends GXSServiceRunnerEx { static final String CONTENT_TYPE = "text/html"; // Overload the following method if you want to // override the default converter class architecture protected String[] processRequestEx(HttpServletRequest aReq) throws ServletException { return super.processRequestEx( aReq ); } // Overload the following method if you want to // override default response architecture public void processResponse( String xmlOut, HttpServletRequest req, HttpServletResponse res ) throws GXSException { super.processResponse( xmlOut, req, res ); } }
The processRequestEx( ) and processResponse( ) methods offer convenient hooks for implementing your own special data pre- and post-processing logic. The above code merely delegates execution to the parent's default implementations of these methods. Remove the "super" calls and insert your own code to take over control of pre- and post-processing.
The example class shown above inherits from GXSServiceRunner, which in turn inherits from HttpServlet. Normal servlet control flow applies. In GXSServiceRunner, the following flow occurs:
The doPost( ) method calls initService( ), which obtains the desired service via GXSServiceFactory.createService(this).
Also within doPost( ), a method named performProcessRequest( ) is called.
performProcessRequest( ) calls processRequest( ), which in turn obtains the input data for the service. (To obtain the data, a GXSInputConverterBean is instantiated. The bean, in turn, inspects the CONVERTER_CLASS_NAME property of the service runner to determine which converter class to use.) The service's execute( ) method is then called.
When processRequest( ) returns, the method processResponse( ) executes. This is where data post-processing can be performed. It is also where any OutputStreams that are opened from the HttpServletResponse should be closed.
NOTE: The default implementation of processResponse( ) contains code for converting XML to HTML (using server-side XSL transformation), based on the value of the HTML_INDICATOR property set by the service runner. Study the source code for GXSServiceRunner if you want to see how this kind of data post-processing can be done.
It's important to understand that the default implementation of GXSServiceRunner depends on framework methods (specifically, methods belonging to GXSServiceFactory and GXSInputConverterBean) in which the service runner itself is an argument to the method. When a reference to the service runner is passed this way, it's because the factory object needs access to the caller's properties. The properties in question usually involve configuration parameters of some kind.
For example, when GXSServiceRunner calls the GXSServiceFactory method createService( ), passing `this' as an argument, the factory uses the servlet reference to find out the name of the service to obtain and the type of service (EJB or non-EJB). These pieces of information ultimately come from the servlet's initialization parameters (in particular, the params called "servicename" and "xcs_servicetype"). The initialization parameters, in turn, are specified in the web.xml file in the servlet's WAR module.
The following listing shows what the web.xml servlet entry for the MyComposerServiceRunner class might look like. This example assumes that the target Composer service is called HelloWorld and that the framework-supplied GXSInputFromHttpParams converter class will be used to obtain data from the HTTP request.
<servlet> <servlet-name> MyComposerServiceRunner </servlet-name> <display-name /> <servlet-class> com.composer.MyComposerServiceRunner </servlet-class> <init-param> <param-name>servicename</param-name> <param-value>com.composer.HelloWorld</param-value> </init-param> <init-param> <param-name>xcs_servicetype</param-name> <param-value>NORMAL</param-value> </init-param> <init-param> <param-name>transform_into_html</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>rootname</param-name> <param-value>greeting</param-value> </init-param> <init-param> <param-name>converterclassname</param-name> <param-value> com.sssw.b2b.xs.service.conversion.GXSInputFromHttpParams </param-value> </init-param> </servlet>
Note that the "servicename" init param specifies the complete (full-context) name of the target service, in this case com.composer.HelloWorld.
Other parameters are supplied as well, such as "rootname" (to specify the root element name of the XML document that the converter class will create as input to the service), a "transform_into_html" flag to indicate to GXSServiceRunner whether to attempt XSL transformation of the output data in processResponse( ), and so on.
The important point to note is that if you intend to extend GXSServiceRunner or GXSServiceRunnerEx, you should ensure that the web.xml file for your servlet class specifies, at a minimum, the init params "servicename", "xcs_servicetype", and "converterclassname" (and valid values for them), as shown above. The other initialization parameters are optional.
The framework factories "understand" a large number of possible init parameter types: see the properties defined on the IGXSServiceRunner interface for a full list. Some of the more commonly used params are shown in the following table. (Required params are in bold.)
If your servlet class will be used in an environment where the web.xml init-param mechanism can't be relied upon, you should provide custom implementations of the following methods:
getServiceName()
to bind the servlet to the Composer service component (mandatory in all cases)
getRootName()
to return the name of the root element to be used if the converter class will be GXSInputFromHttpParams (otherwise "root" will be used by default)
getServiceType()
should return a string value of "NORMAL" or "EJB", indicating the type of service component that will be invoked (mandatory in all cases)
getConverterClassName()
should return the name of a class that implements the IGXSInputConversion
interface (not mandatory in every case, but recommended as a best practice)
getOutputHTMLIndicator()
should return true if the output of the service will be transformed into HTML using the default implementation of processResponse( ); false if it will be XML. (Again, (not mandatory in every case, but recommended as a best practice.)
The framework's servlet-based service runner class (GXSServiceRunnerEx) makes use of so-called converter classes to obtain data arriving via HTTP. These classes are intended to provide a clean separation of "data marshalling and unmarshalling" logic from service invocation logic.
The converter classes in the framework implement the IGXSExInputConversion interface (which in turn extends IGXSInputConversion). This interface has only one method:
String[] processMultipleRequests(HttpServletRequest aReq)
As you can see, this method essentially converts a servlet request to an array of XML strings.
NOTE: Since you cannot implement the IGXSExInputConversion interface if your custom service runner class does not use (or cannot supply) a HttpServletRequest object, this discussion applies only if you are extending GXSServiceRunnerEx (or if you are implementing a custom servlet that will eventually get passed to factory objects). If your trigger class does not inherit from HttpServlet, you can implement your own scheme for fetching data, and simply pass the data to the service's execute( ) method.
Most of the framework's converter classes implement a constructor that takes a IGXSServiceRunner argument so that the converter can obtain initialization parameters (or other information) from the caller. Study the source code for the framework converter classes if you want to see examples of this technique in use.
The framework contains a number of predefined converter classes (that is, classes that implement the IGXSExInputConversion interface). The names of these classes can be specified in servlet init-params, or supplied to the setConverterClassName( ) method of GXSServiceComponentBase.
Converter classes available in the framework include:
GXSInputFromHttpContent — Obtains XML directly from the request's InputStream
GXSInputFromHttpMultiPartRequest — Obtains XML from multipart form data
GXSConvertHttpMPReqNonBuff — Same as above, but uses a non-buffered MultipartRequest. (Note: The MultipartRequest class is defined in the framework. See the relevant Javadoc and/or source code for details.)
GXSInputFromHttpParams — Obtains XML by parsing query parameters off the tail end of the URL in an HTTP GET. Those params are assembled into an XML document on the fly.
GXSInputFromHttpSpecificParam — Assumes that a form has been POSTed, with a field called `xmlfile' that contains XML.
GXSInputFromJavaObject — This is actually a convenience object for use by Composer JSP taglib methods. It is constructed using a reference to a GXSServiceComponentBean. The bean needs to be able to point to a XML String whose variable name is located in an init param called `xmldoc'. See source code for details.
GXSInputFromSoapContent — Obtains XML strings from elements under the BODY element of a SOAP request. Every element is accumulated into a String[ ].
You should study the source code for these classes to see how they work before implementing any but the most trivial of custom converter classes. Depending on what kinds of data conversion you need to do, you may be able to extend an existing converter class (and save yourself a lot of coding).
A custom converter class will look something like this.
public class MyConverterClass implements IGXSInputConversion { // Attribute that holds the service // runner for querying parameters. IGXSServiceRunner mRunner = null; // Constructor to take the IGXSServiceRunner // so that the class can retrieve params public MyConverterClass( IGXSServiceRunner aRunner ) { mRunner = aRunner; } // the processRequest method should take // an HttpServletRequest as // a parameter and return an XML doc as a String: public String processRequest( HttpServletRequest aReq ) throws GXSConversionException { String lsExpandedDoc = null; // ( create or obtain XML . . . ) return lsExpandedDoc; } }
The Enterprise Java Bean (EJB) API implements a container architecture designed to facilitate clean separation of logic, data-access, and presentation layers while also providing connection pooling, transaction management, persistence, access control ( via Roles), "naming services" (JNDI), and remote invocation mechanisms, so as to free applications from having to implement or manage such features individually.
Composer services can be deployed as EJBs. In Composer Enterprise Edition, a simple drag-and-drop UI exists for designating EJB associations at design time (see the separate Composer User's Guide), such that when you choose a service to deploy as an EJB session bean, you can specify whether it is to be Stateful or Stateless, the transaction participation level (Mandatory, Never, Supports, etc.), and the JNDI name of the service.
Since EJBs cannot be instantiated directly by use of constructors, you must use the GXSServiceFactory's static createService( ) method to obtain a reference to a service. The signature of the method in question is:
public static IGXSServiceComponent createService(javax.naming.InitialContext aContext, java.lang.String aJNDIName) throws GXSException
The returned service object is of type IGXSServiceComponent, which means it supports all of the various execute( ) overloaded signatures discussed previously.
An alternative to using the GXSServiceFactory is to utilize the GXSServiceComponentBean class, which can act as a kind of "proxy object" for interacting with Composer services. Example code for using this JavaBean was given earlier (under "Delegating Service Calls Through GXSServiceComponentBean"). To use this bean as an EJB-service accessor, follow the procedure discussed before, but specify "EJB" in setServiceType( ), and in addition to calling setServiceName( ) with the name of the deployed service, use setJndiServiceName( ) to specify the JNDI name that you supplied for the service at deployment time. If you are implementing the IGXSServiceRunner interface yourself, you should provide an implementation of getJndiServiceName( ) in your service runner and vector to it from getServiceProperty( ) when the latter gets a request for JNDI_NAME.
The EJB remote interface, called IGXSEJBServiceComponent, is located in the com.sssw.b2b.xs.ejb
package. When deploying an Composer service as an EJB, you will assign a JNDI name to the EJB. It is this name rather than the qualified Composer service name that will be used to get a reference to the EJBs home interface. The name of the EJB home interface for creating the IGXSEJBServiceComponent is IGXSEJBServiceHome
.
When specifying the JNDI name for an EJBs home interface remember that, for the Novell exteNd Application Server, the string "sssw://host/RMI/" needs to be prepended. For example, if you were deploying an EJB into an Application Server called main.server
, and the JNDI name for the EJB happens to be com/acme/inventory/ProductInquiry
, then the fully qualified JNDI name would be sssw://main.server/RMI/com/acme/inventory/ProductInquiry.
Once the home interface has been retrieved, much like the GXSServiceFactory's createService()
method, a method called create()
can be invoked which will return the remote interface of the EJB. (This is the closest thing to "instantiating" an EJB that exists in the EJB world.) The remote interface contains several execute methods, as described below:
java.lang.String execute()
Method to execute a Composer service that does not expect an input document.
java.lang.String execute(java.lang.String inXML)
Method to execute the Composer service against a single XML document.
java.lang.String execute(java.lang.String[] asInputStrs)
Method to execute the Composer service component using multiple input documents.
GXSExResponse executeEx(java.lang.String[] asInputStrs)
Executes the Composer service using the supplied XML strings.
GXSExResponse executeEx(java.lang.String[] aInpDocs, java.lang.String[] aInpHdrDocs)
Executes the composer service component using the supplied XML strings as inputs and headers.
You will notice that the Reader or Document versions of execute( ) available in the IGXSServiceComponent
are not available in the EJB remote interface. This is because neither Reader nor Document is serializable and thus neither one is able to appear in a remote method.
If you want low-level control over EJB access, you will want to know about a factory class called GXSEJBAccessor, located in the com.sssw.b2b.xs.sssw
package. It contains two methods to obtain an EJB's home interface from a Novell exteNd Application Server.
One method can be used within a server that does not require authentication; the second provides two extra parameters for username and password.
When using the factory, there is no need to fully qualify the JNDI name assigned to the EJB. The factory creates the fully qualified hostname with the supplied parameters. In the following example, the JNDI name of the EJB is com/acme/inventory/ProductInquiry
, the Novell exteNd Application Server name is main.server
and the ports are at their installation default of 80 for HTTP and 54890 for RMI.
import com.sssw.b2b.xs.ejb; import com.sssw.b2b.xs.sssw.GXSEJBAccessor; public void doSomeEJBStuff() throws java.rmi.RemoteException { IGXSEJBServiceHome srvcHome = GXSEJBAccessor.getHomeBean( "com.sssw.b2b.xs.ejb.IGXSEJBServiceHome", "com/acme/inventory/ProductInquiry", "main.server", 80, 54890 ); IGXSEJBServiceComponent ejbSrvc = srvcHome.create(); // Do something with the service component }
Copyright © 2003 Novell, Inc. All rights reserved. Copyright © 1997, 1998, 1999, 2000, 2001, 2002, 2003 SilverStream Software, LLC. All rights reserved. more ...