Apache Muse - Deployment Descriptor
Overview
The Muse deployment descriptor is named muse.xml. Its location varies depending on the deployment platform being used, but its content is the same across platforms. The muse.xml file holds information about the WS-resources that you have implemented and is used to initialize the data structures needed to validate and route requests to the proper capability objects. In other words, it is this file that enables Muse to aggregate your disparate capability classes into a cohesive resource type(s).
The XML schema for muse.xml is described in the sections below. This file is also included in all projects generated by the WSDL2Java tool so that you may validate any changes you make to your descriptor. This section will refer to the muse.xml file that is generated as part of the sample project in the tutorial.
The root element of the Muse deployment descriptor is <muse/>. It contains a sequence of three child elements:
- <router/> - The configuration data for the core engine's router, which maps SOAP requests into actual Java method calls.
- <resource-type/> - The configuration data for the resources being exposed as web services. It is here that we bring together information from other deployment artifacts (such as WSDL and XSD) and Java code to provide the runtime with the information it needs to create and manipulate instances of a resource type.
- <custom-serializer/> - Optional feature that allows you to extend Muse's XML serialization system to handle your own complex types.
The Router
In the <router/> element, you will find configuration data for three things:
- <logging/> - The logging system where all messages are recorded.
- <java-router-class/> - The Java class that implements the router.
- <persistence/> - The (optional) persistence mechanism where router entries are stored.
Logging
Logging is done with the JDK logging API. The two elements under <logging/> - <log-file/> and <log-level/> -
allow you to specify where the Muse log file will be written and at what level of detail it should record, respectively.
The log file path should be relative to the application's working directory; in the case of the J2EE, this is the root of the WAR. The sample project will create a log file at /WEB-INF/services/muse/log/muse.log. The file will be overwritten each time the application is restarted.
The log level must be one of the values in the JDK log level enumeration: OFF, SEVERE, WARNING, etc. The complete list of values can be found in the descriptor's XML schema as well as the API documentation for java.util.logging.Level.
The default logging level is INFO. This will provide a neatly-printed error message and stack trace for any errors during initialization or request handling, as well as some basic confirmation messages during initialization. Increasing the logging level to FINE (one level higher) will cause Muse to record every SOAP request and response that it processes. Log messages from both the router and the resource instances will appear side-by-side.
The Router Class
The Muse router component maps WS-Addressing headers from a SOAP request into an actual Java method
call on a resource instance. When a SOAP request arrives at your application, the following steps are
taken by the service implementation class:
- Parse the SOAP into data structures and extract the WS-Addressing headers.
- Provide a per-request (per-thread) context where the router and other components can share information about the request.
- Hand off the WS-Addressing information and SOAP body (XML) to the router.
With all of the basic SOAP and addressing validation out of the way, the router can perform the following tasks:
- Search the collection of resource instances to find one that is associated with the given addressing data.
- Ask the resource if it supports the action specified in the message; if it does, ask the resource to invoke it using the given SOAP body as input.
- The router takes the return value of the method call (which is the XML for the response's SOAP body) and passes it back to the service so it can be packaged up in a SOAP envelope and returned to the caller.
Note that the resource handles all processing of the SOAP body - the router only deals with SOAP headers and the semantics of WS-Addressing. Thus, the implementation of the router is not likely to be extended and modified frequently, although it is easy to do so in through the deployment descriptor.
The <java-router-class/> element specifies the name of the Java implementation class for the router component. This class must implement the Muse interface org.apache.muse.core.routing.ResourceRouter. The default implementation of this interface is org.apache.muse.core.routing.SimpleResourceRouter, and that is what is specified in the sample descriptor. The API documentation for these types provides more details on how modify the behavior of the router using alternate implementations of the router sub-components or the router itself.
Persistence
The Muse deployment descriptor allows you to configure a persistence layer that will
store the router's entry table (the EPR-to-resource mappings) and reload it after
the host is restarted. This mechanism is generic in that it does not prescribe a certain
type of data store - anything from a simple file-based system to a more complex
database-oriented system can be used to store the entries. Muse provides a default
implementation of router persistence that is based on the file system - it stores one
file for each unique EPR in the router and sorts the files by resource type. You can
use this implementation to get started and replace it with something more performant
if needed.
To add persistence for the router entries, you must first add the optional <persistence/> element under <router/>. This element has two child elements itself:
- <persistence-location/> - The URI that describes where the router entries are stored.
Concrete persistence implementations will have different semantics for this URI depending on
what kind of data store they use.
- <java-persistence-class/> - The Java class that implements the
RouterPersistence API.
In the case of the default persistence implementation, the "location" is just the relative path of the directory where you want to store the entries. If you set the value of <persistence-location/> to router-entries, then an Axis2-based application would create the directory /WEB-INF/services/muse/router-entries.
The name of the Java class that holds the default implementation is org.apache.muse.core.routing.RouterFilePersistence. You can extend or replace this class in order to modify the type and behavior of the persistence mechanism.
The Resource Types
The <resource-type/> element is where you specify the deployment data about the resources you have defined using the Muse API and various web services artifacts. The vast majority of descriptor customization will occur here, although there will be plenty of cases where default values specified in the sample descriptor will fit your needs just fine. The elements found under <resource-type/> include:
- <context-path/> - The context path (unique URI) of the
resource type.
- <wsdl/> - The WSDL of the resource type.
- <java-id-factory-class/> - The Java class used to customize
the endpoint reference each resource instance.
- <java-resource-class/> - The Java class used to implement the
Muse resource, the job of which is to aggregate your capabilities.
- <capability/> - The unit of functionality for Muse resources.
A capability is defined by a unique URI and implemented by a Java
class.
The Context Path
Every resource type exposed as a web service must have its own URI and
its own WSDL port type. For applications that expose multiple resource
types, the <context-path/> element provides a way to map multiple
URI suffixes to the same web service application. Each resource type
will share a common URI which points to the application it is hosted by,
but the suffix will ensure that each type has its own SOAP endpoint from
a client's point-of-view.
In the sample project, the context path is/WsResource. This means that all EPRs that are created for instances of this resource type will have an address of:
http://[host]/wsn-producer/services/WsResourceEven in the case where there is more than one resource type (and thus, more than one URI), there is only one instance of the Muse service, and one instance of the Muse router. The <context-path/> element provides a function similar to the servlet mapping feature of J2EE's web.xml. This allows us to keep remote resource interfaces completely separate despite the fact that some resources may share application space.
The WSDL File
In the <wsdl/> element, we see two pieces of information - the
actual location of the WSDL file (in <wsdl-file/>) and the name of
the port type that defines the resource interface (in
<wsdl-port-type/>).
Just like the log file path, the WSDL file's path should be relative to the application's working directory. The sample WSDL is in /WEB-INF/services/muse/wsdl along with the schemas it imports. All of the import elements contained in the WSDL must have paths that are relative to it.
Muse parses the WSDL file with the WSDL4J API and finds the port type specified in the <wsdl-port-type/> element. It will make sure that every operation defined in the port type has an equivalent Java method in the resource by scanning its capability classes until it finds a match. The matching is done by taking the local name of the type used for the request message, converting the first character to lowercase, and comparing that to the method names returned by the Class.getMethods() method.
The Resource ID Factory Class
The implied resource pattern suggests the use of WS-Addressing reference
parameters in order to distinguish between multiple EPRs with the same
address. This is very important when deploying a resource type that can
have multiple instances because Muse's router relies on a mapping of
EPRs to resources when processing SOAP requests.
In order to facilitate the creation of unique EPRs without requiring users to construct the entire EPR, Muse provides the <java-id-factory-class/> element so that users can focus on the meat of the issue. The value of this element is a Java class that implements the org.apache.muse.core.routing.ResourceIdFactory interface. This interface has two simple methods: getIdentifierName() and getNextIdentifier(). The former allows users to specify what the name of the EPR reference parameter should be; the latter allows users to generate unique values for this parameter in whatever manner they see fit.
Muse provides two factory implementations that can be used in situations where the EPR's parameters do not have to map back to some existing set of identifiers or be pulled from some existing data store. The default classes are CounterResourceIdFactory and RandomResourceIdFactory. The former creates identifier values of the form MuseResource-N (where N is a monotonically increasing integer); the latter generates a random UUID using the JDK. Both classes use Muse's default parameter name, muse-wsa:ResourceIdentifier.
The Resource Class
The <java-resource-class/> element holds the name of the Java
class that represents the resource type. This class must implement the
org.apache.muse.core.Resource interface.
The resource class is tasked with instantiating and initializing all of the resource's capabilities and then delegating to them when the router asks for an operation to be invoked. It holds all of the common data structures that will be needed by its capabilities (the log writer, the resource manager, etc.) and provides you with the opportunity to take more control over initialization and destruction routines if working within capabilities just isn't flexible enough.
Resource is also the interface that capability classes use to access other capabilities. For example, a WS-resource capability may need to access the WSN NotificationProducer capability to subscribe another resource to events it is producing. Capabilities will often need to build upon each other in order to do their work, and the Resource interface is the central hub that allows them to do this with the proper context already in place.
The sample project uses the default implementation of Resource, which is org.apache.muse.core.SimpleResource. The initialize() and shutdown() methods initialize and shutdown the capabilities that are listed underneath the <resource-type/> element; the current implementation initializes capabilities in the order they are listed, but the design of the initialization process is meant to handle a non-deterministic ordering of the capabilities, and you should not write capability code that depends on deployment descriptor ordering. See the next section for more details on capability implementations.
Users who are building WS-resources will be most interested in the sub-class org.apache.muse.ws.resource.SimpleWsResource, which adds the concept of a state model that can be exposed using standard WS-ResourceProperties capabilities. This class and other WSRF-related APIs are discussed in the WSRF documentation.
The capability URIs are used by a Capability to query the Resource object that contains it and find out what other capabilities are available. The Resource provides a URI-to-Capability lookup system that allows a piece of code to access a specific piece of the resource's functionality. For example, an implementation of the WS-Notification NotificationProducer capability that creates instances of the SubscriptionManager resource type would need to access the WSRL ScheduledResourceTermination capability of the resource objects in order to set their wsrf-rl:TerminationTime property during initialization. It would do this by calling the Resource.getCapability() method on its Resource using the ScheduledResourceTermination URI. Once it had this capability object and cast it to the appropriate interface, it could call the methods or read the data it needed.
The capabilities that are derived from standard interfaces in OASIS or W3C specifications have standard or pseudo-standard URIs that should always be reused. Examples of capabilities with standard URIs would be the WSDM MUWS capabilities; examples of capabilities with pseudo-standard URIs would be those from WSRF or WSN - the specifications in these standards do not specifically define a "capability URI", but they provide concepts that are similar enough for Muse to reuse. The capabilities documentation provides the URIs for all of the standard capabilities.
Capability classes should have a Java interface that user code can reference when calling the capability's methods so that it is independent of a particular capability implementation. This allows users to provide alternate implementations of one or more capabilities without creating a domino effect of change and re-inventing a lot of code. The Muse implementation of the standard capabilities provides a consistent pattern that you can extend in your own code.
For example, the WS-MetadataExchange capability is made up of the following JAR files:
- muse-wsx-api.jar - Contains the Java interfaces for the
MetadataExchange capability, as well as a class with constants defined by the spec.
- muse-wsx-impl.jar - Contains Muse's default implementation
of the MetadataExchange capability. This can be replaced with an alternate
implementation, but users should adhere to the same Java interface to
maximize code portability. This archive also contains a simple web
services client that allows users to invoke the WS-MetadataExchange
operations with normal JDK types and conventions.
Creating Custom Serializers
When looking at the sample capability source code, you may notice that there is no code to serialize the parameters and return types of the operations to XML, even though they are transported via SOAP. Muse has XML serialization support for many basic types and a number of complex ones. These types include primitives, strings, dates, URIs, and QNames, and their array types.
Many times, however, users may want to include their own types in their method signatures, types that Muse will not know how to serialize. In this case, you can create a class that implements org.apache.muse.core.serializer.Serializer and make it available with the <custom-serializer/> element under <muse/>.
The basic rule of thumb when getting involved with the SOAP message processing of Muse is that the SOAP body should have one child element for each Java method parameter. This is not a standard requirement by any means, but it is a useful convention that Muse has adopted in order to make parsing as easy as possible. By using the one-element-one-parameter rule, Muse can easily provide you the XML for the complex type you need to parse while still handling the parameters whose types it understands. This lets you focus on your complex type without worrying about the rest of the SOAP body.
The Serializer interface has two methods - fromXML() and toXML() - that allow you to deserialize and serialize XML into a Java object for methods using it in their signature. In the case of toXML(), you will be given the QName of the element that is supposed to contain the data in the given object, and you are expected to use that QName lest the resulting SOAP message be incorrect.
Your serializer will also be available in other situations where you encounter an XML fragment that should be converted into your type; an example of such a situation would be the receipt of a WS-Notification message containing an event that is represented in Java by one or more data structures. Here, the event payload may not be deserialized at the time the method invocation is made, but later in some event processing code.