Axis System Integration Guide
Beta 1 Version
Table of Contents
Introduction
Pluggable APIs
System Management
Logging/Tracing
Configuration
Handlers
Internationalization
Performance Monitoring
Encoding
WSDL Parser and Code Generator Framework
Introduction
The primary purpose of this guide is
to present how AXIS can be integrated into an existing web application
server, such as Tomcat or WebSphere, for example. AXIS has a number
of Pluggable APIs that are necessary for such an integration.
The reader may find useful background information in the
Architecture Guide.
Pluggable APIs
The following are the points that are pluggable in order to integrate AXIS
into a web application server.
System Management
What points are necessary to manage AXIS?
-
Justification/Rationale - why is this plug point necessary? Spec
compliance?
-
Interfaces
-
Mechanism
-
Life cycle
-
Exception handling - in general; plug-in shouldn't throw any exceptions
- does runtime ignore? Log?)
-
Multiple thread support? Ie., is synchronization required?
-
Configuration/reconfiguration
-
Default behavior if not plugged.
-
Example
Logging/Tracing
AXIS logging and tracing is based on the Logging component of the
Jakarta Commons
project, or the Jakarta Commons Logging (JCL) SPI.
The JCL provides a Log interface with thin-wrapper implementations for
other logging tools, including
Log4J,
Avalon LogKit,
and
JDK 1.4.
The interface maps closely to Log4J and LogKit.
Justification/Rationale
A pluggable logging/trace facility enables
AXIS to direct logging/trace messages to a host web application server's
logging facility.
A central logging facility with a single point of configuration/control
is superior to distinct logging mechanisms for each of a multitude of
middleware components that are to be integrated into
a web application server.
Integration
The minimum requirement to integrate with another logger
is to provide an implementation of the
org.apache.commons.logging.Log
interface.
In addition, an implementation of the
org.apache.commons.logging.LogFactory
interface
can be provided to meet
specific requirements for connecting to, or instantiating, a logger.
org.apache.commons.logging.Log
The Log
interface defines the following methods for use
in writing log/trace messages to the log:
log.fatal(Object message);
log.fatal(Object message, Throwable t);
log.error(Object message);
log.error(Object message, Throwable t);
log.warn(Object message);
log.warn(Object message, Throwable t);
log.info(Object message);
log.info(Object message, Throwable t);
log.debug(Object message);
log.debug(Object message, Throwable t);
log.trace(Object message);
log.trace(Object message, Throwable t);
log.isFatalEnabled();
log.isErrorEnabled();
log.isWarnEnabled();
log.isInfoEnabled();
log.isDebugEnabled();
log.isTraceEnabled();
Semantics for these methods are such that it is expected
that the severity of messages is ordered, from highest to lowest:
- fatal - Consider logging to console and system log.
- error - Consider logging to console and system log.
- warn - Consider logging to console and system log.
- info - Consider logging to console and system log.
- debug - Log to system log, if enabled.
- trace - Log to system log, if enabled.
org.apache.commons.logging.LogFactory
If desired, the default implementation of the
org.apache.commons.logging.LogFactory
interface can be overridden,
allowing the JDK 1.3 Service Provider discovery process
to locate and create a LogFactory specific to the needs of the application.
Review the Javadoc for the LogFactoryImpl.java
for details.
Mechanism
Life cycle
The JCL LogFactory implementation must assume responsibility for
either connecting/disconnecting to a logging toolkit,
or instantiating/initializing/destroying a logging toolkit.
Exception handling
The JCL Log interface doesn't specify any exceptions to be handled,
the implementation must catch any exceptions.
Multiple threads
The JCL Log and LogFactory implementations must ensure
that any synchronization required by the logging toolkit
is met.
Logger Configuration
Log
The default LogFactory
provided by JCL
can be configured to instantiate a specific implementation of the
org.apache.commons.logging.Log
interface
by setting the property org.apache.commons.logging.Log
.
This property can be specified as a system property,
or in the commons-logging.properties
file,
which must exist in the CLASSPATH.
Default logger if not plugged
The Jakarta Commons Logging SPI uses the
implementation of the org.apache.commons.logging.Log
interface specified by the system property
org.apache.commons.logging.Log
.
If the property is not specified or the class is not available then the JCL
provides access to a default logging toolkit by searching the CLASSPATH
for the following toolkits, in order of preference:
- Log4J
- JDK 1.4
- JCL SimpleLog
Configuration
How can AXIS fit into existing configuration systems?
-
Justification/Rationale - why is this plug point necessary? Spec
compliance?
-
Interfaces
-
Mechanism
-
Life cycle
-
Exception handling - in general; plug-in shouldn't throw any exceptions
- does runtime ignore? Log?)
-
Multiple thread support? Ie., is synchronization required?
-
Configuration/reconfiguration
-
Default behavior if not plugged.
-
Example
Handlers
What new handlers might a system integrator wish to implement?
-
Justification/Rationale - why is this plug point necessary? Spec
compliance?
-
Interfaces
-
Mechanism
-
Life cycle
-
Exception handling - in general; plug-in shouldn't throw any exceptions
- does runtime ignore? Log?)
-
Multiple thread support? Ie., is synchronization required?
-
Configuration/reconfiguration
-
Default behavior if not plugged.
-
Example
Internationalization
The plug point for internationalization isn't a framework, but simply a
property file of the strings used in AXIS.
-
Justification/Rationale
In order for readers of languages other than English to be comfortable
with AXIS, we provide a mechanism for the strings used in AXIS to be translated.
We do not provide any translations in AXIS; we merely provide a means by
which translators can easily plug in their translations.
-
Interfaces
AXIS uses the standard Java internationalization class: PropertyResourceBundle.
To make this class easy to use, there are a number of methods on JavaUtils
that are used to get the messages within the resource bundle.
public static java.util.ResourceBundle
getMessageResourceBundle();
public static String
getMessage(String key) throws java.util.MissingResourceException;
public static String
getMessage(String key, String var) throws java.util.MissingResourceException;
public static String
getMessage(String key, String var1, String var2) throws java.util.MissingResourceException;
public static String
getMessage(String key, String[] vars) throws java.util.MissingResourceException;
AXIS programmers can work with the resource bundle directly via a call
to JavaUtils.getMessageResourceBundle,
but the getMessage
methods should be used instead for two reasons:
-
It's a shortcut. It is cleaner to call
JavaUtils.getMessage("myMsg00");
than
JavaUtils.getMessageResourceBundle().getString("myMsg00");
-
The getMessage
methods enable messages with variables.
The getMessage methods
If you have a message with no variables
myMsg00=This is a string.
then simply call
JavaUtils.getMessage("myMsg00");
If you have a message with variables, use the syntax "{X}"
where X is
the number of the variable, starting at 0. For example:
myMsg00=My {0} is {1}.
then call:
JavaUtils.getMessage("myMsg00",
"name", "Russell");
and the resulting string will be: "My name is Russell."
You could also call the String array version of getMessage:
JavaUtils.getMessage("myMsg00",
new String[] {"name", "Russell"});
The String array version of getMessage
is all that is necessary, but the vast majority of messages will have 0,
1 or 2 variables, so the other getMessage
methods are provided as a convenience to avoid the complexity of the String
array version.
Note that the getMessage
methods throw MissingResourceException
if the resource cannot be found. And ParseException if there are
more {X} entries than arguments. These exceptions are RuntimeException's,
so the caller doesn't have to explicitly catch them.
The resource bundle properties file is org/apache/axis/utils/axisNLS.properties.
-
Mechanism
The Java internationalization mechanism - i.e., a ResourceBundle backed
by a properties file - and the java.text.MessageFormat class, are sufficient
for our needs.
Entries in the properties file must follow the pattern: <string><2-digit
suffix>.
Entries should be ordered in the properties file alphabetically by key.
Entries in the properties file must never be changed. If a code
change requires a message change, don't change the existing message; instead
create a new entry, incrementing the 2-digit suffix. This must be
done for two reasons: 1. You don't know whether the message
is being used elsewhere. 2. So the translator only has to be
aware of, and translate, the new strings. Without this restriction,
every time translators are given the properties file to translate, they
would have to translate all strings all the time.
We may occasionally want to trim the properties file of old data, but this
should only be done on major releases.
-
Default behavior
The default behavior, meaning what happens when a translated file doesn't
exist for a given locale, is to fall back on the English-language properties
file. If that file doesn't exist (unlikely unless something is seriously
wrong), AXIS with throw an exception with an English-language reason message.
-
Examples
In org.apache.axis.client.Call.invoke,
there is the following statement:
if ( operationName == null )
throw new AxisFault( "No operation name specified" );
We will have to add an entry into org/apache/axis/utils/axisNLS.properties.
Something like:
noOperation=No operation
name specified.
And change the code to read:
if ( operationName == null )
throw new AxisFault(JavaUtils.getMessage("noOperation"));
Performance Monitoring
How can we monitor the performance of AXIS?
-
Justification/Rationale - why is this plug point necessary? Spec
compliance?
-
Interfaces
-
Mechanism
-
Life cycle
-
Exception handling - in general; plug-in shouldn't throw any exceptions
- does runtime ignore? Log?)
-
Multiple thread support? Ie., is synchronization required?
-
Configuration/reconfiguration
-
Default behavior if not plugged.
-
Example
Encoding
How can a system integrator plug in other encoding mechanisms such as SOAP
1.2 or optimized XML-based encoding?
-
Justification/Rationale - why is this plug point necessary? Spec
compliance?
-
Interfaces
-
Mechanism
-
Life cycle
-
Exception handling - in general; plug-in shouldn't throw any exceptions
- does runtime ignore? Log?)
-
Multiple thread support? Ie., is synchronization required?
-
Configuration/reconfiguration
-
Default behavior if not plugged.
-
Example
WSDL Parser and Code Generator Framework
WSDL2Java is AXIS's tool to generate Java artifacts from WSDL. This
tool is extensible. If users of AXIS wish to extend AXIS, then they
may also need to extend or change the generated artifacts. For example,
if AXIS is inserted into some product which has an existing deployment
model that's different than AXIS's deployment model, then that product's
version of WSDL2Java will be required to generate deployment descriptors
other than AXIS's deploy.wsdd.
What follows immediately is a description of the framework. If
you would rather dive down into the dirt of examples,
you could learn a good deal just from them. Then you could come back
up here and learn the gory details.
There are three parts to WSDL2Java:
-
The symbol table
-
The parser front end with a generator framework
-
The code generator back end (WSDL2Java itself)
Symbol Table
The symbol table, found in org.apache.axis.wsdl.symbolTable, will contain
all the symbols from a WSDL document, both the symbols from the WSDL constructs
themselves (portType, binding, etc), and also the XML schema types that
the WSDL refers to.
NOTE: Needs lots of description here.
The symbol table is not extensible, but you can add fields to
it by using the Dynamic Variables construct:
-
You must have some constant object for a dynamic variable key. For
example: public static final String MY_KEY = "my key";
-
You set the value of the variable in your GeneratorFactory.generatorPass:
entry.setDynamicVar(MY_KEY, myValue);
-
You get the value of the variable in your generators: Object myValue
= entry.getDynamicVar(MY_KEY);
Parser Front End and Generator Framework
The parser front end and generator framework is located in org.apache.axis.wsdl.gen.
The parser front end consists of two files:
-
Parser
public class Parser {
public Parser();
public boolean isDebug();
public void setDebug(boolean);
public boolean isImports();
public void setImports(boolean);
public boolean isVerbose();
public void setVerbose(boolean);
public long getTimeout();
public void setTimeout(long);
public java.lang.String getUsername();
public void setUsername(java.lang.String);
public java.lang.String getPassword();
public void setPassword(java.lang.String);
public GeneratorFactory getFactory();
public void setFactory(GeneratorFactory);
public org.apache.axis.wsdl.symbolTable.SymbolTable
getSymbolTable();
public javax.wsdl.Definition getCurrentDefinition();
public java.lang.String getWSDLURI();
public void run(String wsdl) throws java.lang.Exception;
public void run(String context, org.w3c.dom.Document
wsdlDoc) throws java.io.IOException, javax.wsdl.WSDLException;
}
The basic behavior of this class is simple: you instantiate a
Parser, then you run it.
Parser parser = new Parser();
parser.run("myfile.wsdl");
There are various options on the parser that have accessor methods:
-
debug - default is false - dump the symbol table after the WSDL file has
been parsed
-
imports - default is true - should imported files be visited?
-
verbose - default is false - list each file as it is being parsed
-
timeout - default is 45 - the number of seconds to wait before halting
the parse
-
username - no default - needed for protected URI's
-
password - no default - needed for protected URI's
Other miscellaneous methods on the parser:
-
get/setFactory - get or set the GeneratorFactory on this parser - see below
for details. The default generator factory is NoopFactory, which
generates nothing.
-
getSymbolTable - once a run method is called, the symbol table will be
populated and can get queried.
-
getCurrentDefinition - once a run method is called, the parser will contain
a Definition object which represents the given wsdl file. Definition
is a WSDL4J object.
-
getWSDLURI - once the run method which takes a string is called, the parser
will contain the string representing the location of the WSDL file.
Note that the other run method - run(String context, Document wsdlDoc)
- does not provide a location for the wsdl file. If this run method
is used, getWSDLURI will be null.
-
There are two run methods. The first, as shown above, takes a URI
string which represents the location of the WSDL file. If you've
already parsed the WSDL file into an XML Document, then you can use the
second run method, which takes a context and the WSDL Document.
An extension of this class would ...
NOTE: continue this sentiment...
-
WSDL2
Parser is the programmatic interface into the WSDL parser. WSDL2
is the command line tool for the parser. It provides an extensible
framework for calling the Parser from the command line. It is named
WSDL2 because extensions of it will likely begin with WSDL2: WSDL2Java,
WSDL2Lisp, WSDL2XXX.
public class WSDL2 {
protected WSDL2();
protected Parser createParser();
protected Parser getParser();
protected void addOptions(org.apache.axis.utils.CLOptionDescriptor[]);
protected void parseOption(org.apache.axis.utils.CLOption);
protected void validateOptions();
protected void printUsage();
protected void run(String[]);
public static void main(String[]);
}
Like all good command line tools, it has a main method. Unlike
some command line tools, however, its methods are not static. Static
methods are not extensible. WSDL2's main method constructs an instance
of itself and calls methods on that instance rather than calling static
methods. These methods follow a behavior pattern. The main
method is very simple:
public static void main(String[] args) {
WSDL2 wsdl2 = new WSDL2();
wsdl2.run(args);
}
The constructor calls createParser to construct a Parser or an extension
of Parser.
run calls:
-
parseOption to parse each command line option and call the appropriate
Parser accessor. For example, when this method parses --verbose,
it calls parser.setVerbose(true)
-
validateOptions to make sure all the option values are consistent
-
printUsage if the usage of the tool is in error
-
parser.run(args);
If an extension has additional options, then it is expected to call
addOptions before calling run. So extensions will call, as necessary,
getParser, addOptions, run. Extensions will override, as necessary,
createParser, parseOption, validateOptions, printUsage.
The generator framework consists of 2 files:
-
Generator
The Generator interface is very simple. It just defines a generate
method.
public interface Generator
{
public void generate() throws java.io.IOException;
}
-
GeneratorFactory
public interface GeneratorFactory
{
public void generatorPass(javax.wsdl.Definition,
SymbolTable);
public Generator getGenerator(javax.wsdl.Message,
SymbolTable);
public Generator getGenerator(javax.wsdl.PortType,
SymbolTable);
public Generator getGenerator(javax.wsdl.Binding,
SymbolTable);
public Generator getGenerator(javax.wsdl.Service,
SymbolTable);
public Generator getGenerator(TypeEntry, SymbolTable);
public Generator getGenerator(javax.wsdl.Definition,
SymbolTable);
public void setBaseTypeMapping(BaseTypeMapping);
public BaseTypeMapping getBaseTypeMapping();
}
The GeneratorFactory interface defines a set of methods that the parser
uses to get generators. There should be a generator for each of the
WSDL constructs (message, portType, etc - note that these depend on the
WSDL4J classes: javax.xml.Message, javax.xml.PortType, etc); a generator
for schema types; and a generator for the WSDL Definition itself.
This last generator is used to generate anything that doesn't fit into
the previous categories
In addition to the getGeneratorMethods, the GeneratorFactory defines
a generatorPass method which provides the factory implementation a chance
to walk through the symbol table to do any preprocessing before the actual
generation begins.
Accessors for the base type mapping are also defined. These are
used to translate QNames to base types in the given target mapping.
In addition to Parser, WSDL2, Generator, and GeneratorFactory, the org.apache.axis.wsdl.gen
package also contains a couple of no-op classes: NoopGenerator and
NoopFactory. NoopGenerator is a convenience class for extensions
that do not need to generate artifacts for every WSDL construct.
For example, WSDL2Java does not generate anything for messages, therefore
its factory's getGenerator(Message, SymbolTable) method returns an instance
of NoopGenerator. NoopFactory returns a NoopGenerator for all getGenerator
methods. The default factory for Parser is the NoopFactory.
Code Generator Back End
The meat of the WSDL2Java back end generators is in org.apache.axis.wsdl.toJava.
Emitter extends Parser. org.apache.axis.wsdl.WSDL2Java extends WSDL2.
JavaGeneratorFactory implements GeneratorFactory. And the various
JavaXXXWriter classes implement the Generator interface.
NOTE: Need lots more description here...
WSDL Framework Extension Examples
Everything above sounds rather complex. It is, but that doesn't mean
your extension has to be.
Example 1 - Simple extension of WSDL2Java - additional artifact
The simplest extension of the framework is one which generates everything
that WSDL2Java already generates, plus something new. Example 1 is
such an extension. It's extra artifact is a file for each service
that lists that service's ports. I don't know why you'd want to do
this, but it makes for a good, simple example. See samples/integrationGuide/example1
for the complete implementation of this example.
-
First you must create your writer that writes the new artifact. This
new class extends org.apache.axis.wsdl.toJava.JavaWriter. JavaWriter
dictates behavior to its extensions; it calls writeFileHeader and writeFileBody.
Since we don't care about a file header for this example, writeFileHeader
is a no-op method. writeFileBody does the real work of this writer.
public class MyListPortsWriter extends JavaWriter {
private Service service;
public MyListPortsWriter(
Emitter emitter,
ServiceEntry sEntry,
SymbolTable symbolTable) {
super(emitter,
new QName(
sEntry.getQName().getNamespaceURI(),
sEntry.getQName().getLocalPart() + "Lst"),
"", "lst", "Generating service port list file", "service list");
this.service = sEntry.getService();
}
protected void writeFileHeader() throws IOException
{
}
protected void writeFileBody() throws IOException
{
Map portMap = service.getPorts();
Iterator portIterator
= portMap.values().iterator();
while (portIterator.hasNext())
{
Port p = (Port) portIterator.next();
pw.println(p.getName());
}
pw.close();
}
}
-
Then you need a main program. This main program extends WSDL2Java
so that it gets all the functionality of that tool. The main of this
tool does 3 things:
-
instantiates itself
-
adds MyListPortsWriter to the list of generators for a WSDL service
-
calls the run method.
That's it! The base tool does all the rest of the work.
public class MyWSDL2Java extends WSDL2Java {
public static void main(String args[]) {
MyWSDL2Java myWSDL2Java
= new MyWSDL2Java();
JavaGeneratorFactory
factory =
(JavaGeneratorFactory) myWSDL2Java.getParser().getFactory();
factory.addGenerator(Service.class,
MyListPortsWriter.class);
myWSDL2Java.run(args);
}
}
Example 2 - Not quite as simple an extension of WSDL2Java - change an artifact
In this example, we'll replace deploy.wsdd with mydeploy.useless.
For brevity, mydeploy.useless is rather useless. Making it useful
is an exercise left to the reader. See samples/integrationGuide/example2
for the complete implementation of this example.
-
First, here is the writer for the mydeploy.useless. This new class
extends org.apache.axis.wsdl.toJava.JavaWriter. JavaWriter dictates
behavior to its extensions; it calls writeFileHeader and writeFileBody.
Since we don't care about a file header for this example, writeFileHeader
is a no-op method. writeFileBody does the real work of this writer.
It simply writes a bit of a song, depending on user input.
Note that we've also overridden the generate method. The parser
always calls generate, but since this is a server-side artifact, we don't
want to generate it unless we are generating server-side artifacts (in
other words, in terms of the command line options, we've specified the
--serverSide option).
public class MyDeployWriter extends JavaWriter {
public MyDeployWriter(Emitter emitter, Definition
definition,
SymbolTable symbolTable) {
super(emitter,
new QName(definition.getTargetNamespace(), "deploy"),
"", "useless", "Generating deploy.useless", "deploy");
}
public void generate() throws IOException {
if (emitter.isServerSide())
{
super.generate();
}
}
protected void writeFileHeader() throws IOException
{
}
protected void writeFileBody() throws IOException
{
MyEmitter myEmitter
= (MyEmitter) emitter;
if (myEmitter.getSong()
== MyEmitter.RUM) {
pw.println("Yo! Ho! Ho! And a bottle of rum.");
}
else if (myEmitter.getSong()
== MyEmitter.WORK) {
pw.println("Hi ho! Hi ho! It's off to work we go.");
}
else {
pw.println("Feelings... Nothing more than feelings...");
}
pw.close();
}
}
-
Since we're changing what WSDL2Java generates, rather than simply adding
to it like the previous example did, calling addGenerator isn't good enough.
In order to change what WSDL2Java generates, you have to create a generator
factory and provide your own generators. Since we want to keep most
of WSDL2Java's artifacts, we can simply extend WSDL2Java's factory - JavaGeneratorFactory
- and override the addDefinitionGenerators method.
public class MyGeneratorFactory extends JavaGeneratorFactory
{
protected void addDefinitionGenerators() {
addGenerator(Definition.class,
JavaDefinitionWriter.class); // WSDL2Java's JavaDefinitionWriter
addGenerator(Definition.class,
MyDeployWriter.class); // our DeployWriter
addGenerator(Definition.class,
JavaUndeployWriter.class); // WSDL2Java's JavaUndeployWriter
}
}
-
Now we must write the API's to our tool. Since we've added an option
- song - we need both the programmatic API - an extension of Parser (actually
Emitter in this case since we're extending WSDL2Java and Emitter is WSDL2Java's
parser extension) - and the command line API.
Here is our programmatic API. It adds song accessors to Emitter.
It also, in the constructor, lets the factory know about the emitter and
the emitter know about the factory.
public class MyEmitter extends Emitter {
public static final int RUM = 0;
public static final int WORK = 1;
private int song = -1;
public MyEmitter() {
MyGeneratorFactory factory
= new MyGeneratorFactory();
setFactory(factory);
factory.setEmitter(this);
}
public int getSong() {
return song;
}
public void setSong(int song) {
this.song = song;
}
}
And here is our command line API. It's a bit more complex that
our previous example's main program, but it does 2 extra things:
-
accept a new command line option: --song rum|work (this is the biggest
chunk of the new work).
-
create a new subclass of Parser
public class WSDL2Useless extends WSDL2Java {
protected static final int SONG_OPT = 'g';
protected static final CLOptionDescriptor[]
options = new CLOptionDescriptor[]{
new CLOptionDescriptor("song",
CLOptionDescriptor.ARGUMENT_REQUIRED,
SONG_OPT,
"Choose a song for deploy.useless: work or rum")
};
public WSDL2Useless() {
addOptions(options);
}
protected Parser createParser() {
return new MyEmitter();
}
protected void parseOption(CLOption option)
{
if (option.getId() ==
SONG_OPT) {
String arg = option.getArgument();
if (arg.equals("rum")) {
((MyEmitter) parser).setSong(MyEmitter.RUM);
}
else if (arg.equals("work")) {
((MyEmitter) parser).setSong(MyEmitter.WORK);
}
}
else {
super.parseOption(option);
}
}
public static void main(String args[]) {
WSDL2Useless useless
= new WSDL2Useless();
useless.run(args);
}
}
Let's go through this one method at a time.
-
constructor - this constructor adds the new option --song rum|work.
(the abbreviated version of this option is "-g", rather an odd abbreviation,
but "-s" is the abbreviation for --serverSide and "-S" is the abbreviation
for --skeletonDeploy. Bummer. I just picked some other letter.
-
createParser - we've got to provide a means by which the parent class can
get our Parser extension.
-
parseOption - this method processes our new option. If the given
option isn't ours, just let super.parseOption do its work.
-
main - this main is actually simpler than the first example's main.
The first main had to add our generator to the list of generators.
In this example, the factory already did that, so all that this main must
do is instantiate itself and run itself.