apache > ws.apache
Apache Muse
 

Apache Muse - Programming Model

Overview

The design of Apache Muse is driven by four key concepts:

  • The Capability - A resource capability, like an OOP class, is a collection of data and operations that is exposed via web services. It is the atomic unit of design in Muse. Capabilities are aggregated to define resource types, and they are implemented as simple Java classes; the capability classes are inspected at initialization time to determine how they fit into the interface found in the resource's WSDL.

  • The Resource - A resource is a collection of capabilities exposed through a cohesive web service interface. It also serves as an interface for capabilities that wish to communicate with other capabilities.

  • The Implied Resource Pattern - Every resource has a unique endpoint reference (EPR). This data type is defined by the WS-Addressing standard to contain the basic networking and runtime-specific data needed to locate a resource and invoke its operations. The implied resource pattern allows users to add name-value pairs to an EPR in order to distinguish between multiple instances of a resource type with a common address (URL).

  • The Isolation Layer - Muse resources can be deployed in different environments - J2EE application servers, OSGi, etc. - but there is only one programming model. Users can implement a resource type and deploy it in different environments with only small changes to deployment artifacts and no changes to their code. This portability of code and education decreases the learning curve for new Muse adopters.

The following diagram shows how these four concepts work together:

Building Resources Through Capabilities

Muse programmers who wish to design and implement a web services interface for an IT resource must use an aggregation model, building up the interface using standards (OASIS, W3C, etc.) and custom definitions. Examples of standards that a user may leverage in building an interface would be the WS-ResourceLifetime ImmediateResourceTermination port type or the WSDM MUWS Description capability; the custom definitions may include port types or behaviors associated with product-specific features. The user aggregates these port types, capabilities, and behaviors using two methods: the WSDL file that will be read by clients and the Muse API that is used for the internal, server-side implementation.

The WSDL file that defines the resource's web services interface must define one port type for the resource that has all of its public operations and links to any artifacts a client would need in order to inspect and manipulate it. The WSDL contains no hint or reference to the way that the resource has been implemented. In WSDL 1.1, specifically, there is no notion of inheritance or aggregation - a resource designer who wishes to combine features in other port types into his new interface must cut-and-paste the contents of the port type into the new resource's port type. Clients reading the WSDL do not have any idea as to what specs defined which pieces of the interface.

All Muse resources are collections of smaller units of function called capabilities. The capability concept was initially described in WSDM MUWS 1.0 as a means of communicating to clients what data, operations, and behavior could be expected from the resource (over-and-above the basic contractual guarantees of the WSDL port type). Despite the use of capabilities to advertise behavior to clients, the way that capabilities are defined in the WSDM MUWS specification is reminiscent of the way Java developers break apart large pieces of software into smaller chunks of related tasks; the aggregation of capabilities like Identity, Configuration, and Relationships into a single web service interface suggests a similar model for server-side implementations. Muse has generalized the WSDM MUWS capability concept to make it easier for programmers to implement resources as reusable units of function rather than monolithic Java classes.

If the capability layer is where programmers implement the actual logic for a service, we still need a way to combine these units together in order to match the WSDL interface that clients are using to reason about the resource. The resource is the Muse concept that does this aggregation and provides to the underlying web services plumbing a single interface for the delegation of incoming SOAP requests. Thus, even within the Muse framework, the concept of capabilities as a service implementation strategy is sequestered from all but the final layer of code; the SOAP engine, isolation layer, and router are unaware of this programming model concept. As an example, an application server resource implemented with the WSN NotificationProducer, WSRL ImmediateResourceTermination, and custom AppServerDeployment capabilities would hide this aggregation like so:

The fact that the Subscribe operation is handled by the WSN NotificationProducer capability and the DeployApplication operation is handled by the custom AppServerDeployment capability is irrelevant to the resource router and all of the components that handled the request before it. Once the request has been delegated to the appropriate resource instance, only the resource knows that the operation is being implemented by another layer: the capability object that defines the equivalent Java method for the requested operation.

The Java object representing the resource instance will perform the task of delegating requests to the proper capability based on information that was discovered at initialization time. When a resource is created, the Muse framework uses the resource's WSDL to determine what contract the resource has promised to uphold and whether or not it can actually do so; this verification is done by looking through all of a resource's capability definitions (including their Java implementation classes) to make sure that all of the operations in the WSDL port type have equivalent Java methods in the collection of capabilities.

Note
In the case of WSRF-based resources, the framework will also ensure that the appropriate getter and setter methods are available for each resource property defined in the resource's WSRP document. Property delegation is described in more detail here.

Finally, the resource is responsible for making sure that all of the capabilities receive proper notification of all of the resource lifecycle events so that they can take the appropriate measures during initialization and shutdown. The generic Capability interface defined by Muse has four basic lifecycle events:

  • Initialization - Capability.initialize() - This is when the capability can perform all self-contained startup tasks, things that do not involve the use of the other capabilities found in the resource.

  • Post-initialization - Capability.initializeCompleted() - This is when the capability can perform all remaining startup tasks; specifically, it can query and manipulate other capabilities with the knowledge that they are in a stable state.

  • Pre-shutdown - Capability.prepareShutdown() - This is when the capability can perform all shutdown tasks that involve querying and manipulating other capabilities. This is the proverbial last call - a chance for the capability to get what it needs from other components before they are shutdown and their stability is not guaranteed.

  • Shutdown - Capability.shutdown() - Armed with all of the data it needs from pre-shutdown, the capability can now perform all self-contained shutdown tasks, including persistence, configuration, or notifications.

The resource itself only has two lifecycle events (initialization and shutdown); it provides more granular events to the capabilities as part of its implementation because they contain the actual pieces of implementation code and require more detail in order to react to events in a cooperative manner. Programmers who are implementing their own capabilities using Muse's abstract base class can override the default implementations of these lifecycle methods in order to perform the appropriate tasks; the default implementations of these methods are no-ops.

Capability Example - WSRL ScheduledResourceTermination

Muse defines interfaces and default implementations for all of the WSRF, WSN, and WSDM capabilities. These should serve as a model for all programmers who are defining custom capabilities for product-specific features. Further, the interfaces should serve as defacto standards for users who wish to provide alternate implementations of the standard capabilities for their resource to use; such replacement is expected and designed for, but adhering to the Muse interfaces is important in order to ensure code portability. Let's look at one of the standard capabilities to see how Muse has defined and implemented it, how a user might provide an alternate implementation, and what it implies to creators of new capabilities.

The WS-ResourceLifetime (WSRL) spec (defined in the WSRF family of specs) defines two port types: ImmediateResourceTermination and ScheduledResourceTermination. Both of these map to individual Muse capabilities. We will focus on ScheduledResourceTermination in this example.

The ScheduledResourceTermination capability defines two resource properties and one operation:

  • wsrf-rl:CurrentTime - the time according to the system upon which the resource is deployed.

  • wsrf-rl:TerminationTime - the time at which the resource is scheduled to self-destruct. If this is null, the resource is currently immortal.

  • wsrf-rl:SetTerminationTime - the operation defined by WSRL to modify the wsrf-rl:TerminationTime property.

Note
In most cases, the WSRP SetResourceProperties operation should be used to modify resource properties; WSRF does not encourage the practice of creating individual Get/Set operations for every property. This operation represents a special case where special behavior and faults necessitated the definition of a new operation just for this property.

Muse defines a Java interface for this capability. The interface is found in the muse-wsrf-api module in the Muse distribution package; more specifically, it is in the JAR file named muse-wsrf-api.jar. The code looks like this (comments removed for brevity):

package org.apache.muse.ws.resource.lifetime;

import java.util.Date;

import javax.xml.namespace.QName;

import org.apache.muse.ws.resource.WsResourceCapability;
import org.apache.muse.ws.resource.basefaults.BaseFault;
import org.apache.muse.ws.resource.lifetime.faults.TerminationTimeChangeRejectedFault;
import org.apache.muse.ws.resource.lifetime.faults.UnableToSetTerminationTimeFault;

public interface ScheduledTermination extends WsResourceCapability
{
   QName[] PROPERTIES = new QName[]{
      WsrlConstants.CURRENT_TIME_QNAME,
      WsrlConstants.TERMINATION_TIME_QNAME
   };

   Date getCurrentTime()
      throws BaseFault;

   Date getTerminationTime()
      throws BaseFault;

   Date setTerminationTime(Date time)
      throws UnableToSetTerminationTimeFault, TerminationTimeChangeRejectedFault;
}

The first and most obvious thing to note is the setTerminationTime() method - this clearly maps to the WSRL SetTerminationTime operation. When the Muse resource receives a SOAP request for wsrl:SetTerminationTime, it will use the Muse framework to turn the XML parameters into the appropriate Java objects and then invoke the setTerminationTime() method on the ScheduledResourceTermination implementation.

The interface also has two getter operations - getCurrentTime() and getTerminationTime() - that allow internal server-side code to read the resource property values without calling the generic WSRP GetResourceProperty operation and having to parse through XML to read and validate the data. The Muse WSRP implementation handles the plumbing necessary to turn GetResourceProperty calls into invocations of these getter methods.

Note
Because these operations are not defined in the resource's WSDL, remote clients cannot invoke them directly. This is true of all Java methods, whether they be in a capability's interface, super-interface, or java.lang.Object. The definition of getter methods for properties is simply a means to allow authors of the server-side implementation to use WSRF resource properties in a way that is more natural to Java programmers than DOM-based reading and writing.

Finally, there is an array constant defined that has a list of properties defined by this capability. This is a convenience item that will be referenced when we discuss the implementation class.

Muse provides a default implementation of this interface that aims to be "good enough" for most of its users. Experience has taught us that it is very hard to provide an implementation of any capability that is satisfactory to even 90% of users, and any framework that tries to require a certain implementation will suffer death by rewrite or Not-Invented-Here Syndrome. Thus, the following review of the Muse implementation of WSRL ScheduledResourceTermination capability is meant to highlight some of the issues capability authors face and what they can expect when making custom capabilities or creating alternatives to standards such as this one.

All of Muse's standard capability implementations are prefixed with the term Simple. In this example, the SimpleScheduledTermination class provides an implementation of scheduled destruction that relies on Java's timing mechanism: java.util.Timer and java.util.TimerTask. The property values are represented as java.util.Date objects. In the case of getCurrentTime(), the implementation relies on the fact that a new Date object represents the system's current time by default; the implementation of the wsrf-rl:TerminationTime-related methods requires more sophisticated logic using the aforementioned timer, but still the code is fairly straightforward.

package org.apache.muse.ws.resource.lifetime.impl;

//
// imports and header comments removed for brevity
//

public class SimpleScheduledTermination
   extends AbstractWsResourceCapability implements ScheduledTermination
{
   //
   // when there is a valid wsrl:TerminationTime, this timer will
   // be set to execute a task that calls Resource.shutdown()
   //
   private Timer _terminationTimer = null;

   public QName[] getPropertyNames()
   {
      return PROPERTIES;
   }

   public void initialize()
      throws SoapFault
   {
      super.initialize();

      //
      // create the timer that can be used for acting on the
      // termination time, but don't start it
      //
      TimerTask scheduledDestruction = new DestroyTimerTask(getWsResource());
      _terminationTimer = new Timer(scheduledDestruction);
   }

   public Date getCurrentTime()
   {
      return new Date();
   }

   public Date getTerminationTime()
   {
      return _terminationTimer.getScheduledTime();
   }

   public Date setTerminationTime(Date time)
   {
      //
      // no value - cancel timer (we are now immortal)
      //
      if (time == null)
         _terminationTimer.cancel();

      //
      // real value - if no previous value, start timer
      //
      else if (_terminationTimer.getScheduledTime() == null)
         _terminationTimer.schedule(time);

      //
      // real value - timer is started, so stop and start
      //
      else
         _terminationTimer.reschedule(time);

      return time;
   }

   public void shutdown()
      throws SoapFault
   {
      _terminationTimer.cancel();
      super.shutdown();
   }

   class DestroyTimerTask extends TimerTask
   {
      private WsResource _resource = null;

      public DestroyTimerTask(WsResource resource)
      {
         _resource = resource;
      }

      /**
       *
       * Invokes the resource's shutdown() operation.
       *
       */
      public void run()
      {
         try
         {
            _resource.shutdown();
         }

         catch (SoapFault fault)
         {
            LoggingUtils.logError(_resource.getLog(), fault);
         }
      }
   }
}

Note the use of the initialize() lifecycle method - here we perform the (self-contained) task of creating the timer that will be used to trigger self-destruction. The timer is not started because by default there is no value for wsrf-rl:TerminationTime. Conversely, the shutdown() operation cancels the timer (if it has not already been fired), just to make sure no duplicate shutdown events are sent to the resource instance.

The final snippet of code worth mentioning is the getPropertyNames() method - this is an interface defined by the framework to tell the Muse WSRF implementation which properties the capability is responsible for implementing. When WSRP calls are made for one of these properties, the WSRP implementation will know to delegate to this capability object for the reading and writing of values. All WSRF-based capabilities should override this method to provide the proper list of properties. It is not required that capability authors use this convention for implementing getPropertyNames(), but it is more efficient than creating a new array each time the method is called.