The Handler Framework

From GCube System
Revision as of 10:45, 19 September 2009 by Fabio.simeoni (Talk | contribs) (Basics Concepts)

Jump to: navigation, search

One way to characterise the overall behaviour of a service is in terms of the tasks that the service is designed to carry out. Interacting with a target service in the process of satisfying a client request is a common example of a task; monitoring some of the local resources at regular intervals is another. In many cases, modelling such tasks with dedicated components improves their maintainability and reusability. In particular, the same task components can be customised and reused in different parts of the service implementation or assembled in some guise to form more complex tasks. In recognition of these benefits, gCF defines a framework for task definition and management that builds upon a standardisation of tasks as gCube handlers.

Overview

The Handler Framework is for the most part defined by the inheritance hierarchy contained in the package org.gcube.common.core.utils.handlers:

Handlers.jpg

The high-level roles of package components are summarised below:

  • GCUBEIHandler: the interface of all handlers.
  • GCUBEHandler: the abstract base implementation of GCUBEIHandler.
  • GCUBEComplexHandler: an abstract specialisation of GCUBEHandler for complex handlers, i.e handlers that manage other handlers.
  • GCUBEScheduledHandler: a concrete specialisation of GCUBEComplexHandler for handlers that schedule the execution of other handlers.
  • GCUBESequentialHandler: a concrete specialisation of GCUBEComplexHandler for handlers that chain the execution of other handlers.
  • GCUBEHandlerConnector: a concrete specialisation of GCUBEHandler for handlers that connect the inputs and outputs of handlers chained by a GCUBEScheduledHandler.
  • GCUBEParallelHandler: a concrete specialisation of GCUBEComplexHandler for handlers that parallelise the execution of other handlers.
  • GCUBEServiceHandler: an abstract specialisation of GCUBEHandler for handlers that engage in interactions with remote service port-types.
  • GCUBEStatefulServiceHandler: an abstract specialisation of GCUBEServiceHandler for handlers that engage in interactions with remote WS-Resources.
  • GCUBEStagingServiceHandler: an abstract specialisation of GCUBEStatefulServiceHandler for handlers that can stage the WS-resource with which they need to interact.

The framework also includes the following auxiliary packages:

  • org.gcube.common.core.utils.handlers.events: a package of components related to the management of events produced by handlers.
  • org.gcube.common.core.utils.handlers.lifetime: a package of components related to the management of the lifetime of handlers.

Basics Concepts

The basic behaviour of all handlers is defined by the GCUBEIHandler interface. The class GCUBEHandler provides a base implementation for it which most handlers are expected to directly or indirectly extend.

class MyHandler extends GCUBEHandler {
 
...
 
}

Note: Implementations of GCUBIHandler other than GCUBEHandler are possible but not expected. The existence of an interface for handlers is mainly intended for clients that wish to layer additional expectations on the handlers they process and reuse at the same time the hierarchy of predefined handlers.

The first and foremost method of all handlers is the run() method, which implements the task associated with the handler. This is in fact the only method of GCUBEIHandler that GCUBEHandler does not implement. The following is thus one of the simplest handlers:

class MyHandler extends GCUBEHandler {
 
 /** {@inheritDoc} **/
 public void run() throws Exception {
   System.out.println("carrying out a task..");
 }
 
}
 
...
 
MyHandler handler;
...
handler.run();

Note: The framework makes no assumption about how run() is implemented, expect for the fact that it may generate an exception. For some handlers it may block while for others it may return immediately. Again, for some handlers it may be invoked only once while for others it may be invoke an arbitrary number of times.

Most handlers will need inputs and produce outputs. Since run() does not cater for them, handlers need some form of state in which clients can put inputs and find outputs. Handlers are free to define any form of state and access to state (dedicated getters, setters, and constructors). The two forms of state discussed next, however, are predefined in GCUBEIHandler to enable generic state management strategies across handlers of different types. Their use is entirely optional.

The Handled Object

The first form of state is an arbitrary object upon or on behalf of which the handler is expected to execute, the handled object. GCUBEIHandler defines accessors for it and GCUBEHandler implements them (setHandled(object), getHandled()). GCUBEIHandler and GCUBEHandler are in fact parametric in the type of the handled object:

public interface GCUBEHandler<T> {
 ...
 public void setHandled(T h);
 public T getHandled(T h);
 ...
}

Note: The parametrisation allows the framework to be specialised in contexts where handlers are expected to handle objects of a give type, at no loss of static typechecking.

In spite of their name, not all handlers need a handler object of course. MyHandler above does not for example and derives GCUBEHandler as a raw type. To avoid compiler's warnings, it could have been defined as:

class MyHandler extends GCUBEHandler<Object> {...}

The following variation of MyHandler exemplifies the use of handled objects:

class MyHandler extends GCUBEHandler<String> {
 
 /** {@inheritDoc} **/
 public void run() throws Exception {
   System.out.println("executing with "+this.getHandled());
 }
 
}
 
...
 
MyHandler handler;
...
handler.setHandled("some task");
handler.run();

Note: In practice, the handled object is often an instance of some distinguished component of the service implementation (e.g. a stateful resource). One ore more handlers are then used to implement tasks associated with the instance as if they were separate extensions of its class:

class SomeServiceComponent {.....}
...
class SomeComponentTask extends GCUBEHandler<SomeServiceComponent> {
 
 /** {@inheritDoc} **/
 public void run() throws Exception {
    ...
    ... this.getHandled().getSomeProperty()...
    ... // do something important .....
    ... this.getHandled().setSomeOtherProperty(...)...
    ... 
 }
 
}

Note: For convenience, GCUBEHandler has a constructor parametric in the handled object to which subclasses may wish to delegate.

class MyHandler extends GCUBEHandler<String> {
 
 public MyHandler(String s) {super(s);}
 ....
 
}

The Blackboard

A less constrained form of state associated with all handlers is in the forms of a map of arbitrary named objects, the blackboard. Again, GCUBEIHandler defines accessors for it GCUBEHandler implements them (getBlackboard(), setBlackboard(map), clearBlackboard()). The names of the objects that handlers expect to find in their state or else put in their state is defined by the convention (i.e. it must be part in the documentation of the handler).

class MyHandler extends GCUBEHandler<Object> {
 
 /** {@inheritDoc} **/
 public void run() throws Exception {
   this.getBlackboard().put("output",this.getBlackboard().get("input")+1);
 }
 
}
 
...
 
MyHandler handler;
...
Map<String,Object> map = new HashMap<String,Object>();
map.put("input",5);
handler.setBlackboard(map);
handler.run();
System.out.println(handler.getBlackboard().get("output"));

Note: This exemplifies the use of the blackboard but does not justify it. The blackboard is not intended for the exchange of information between handlers and their clients, where the handled object, getters, setters, and constructors fare better. It is primarily intended for the loosely-coupled exchange of information between handlers that are developed independently but under the same conventions. As we discuss below, this exchange may occur when handlers are chained in a GCUBESequentialHandler.

Miscellaneous Functionality

GCUBEIHandler defines a number of auxiliary methods that may be useful for the management of handlers and GCUEBHandler implements them. These include:

  • getID(): returns a unique identifier for the handler which may be used to discriminate between instances of the same class;
  • getName()/setName(string): returns/sets a name for the handler (by default, this is the unqualified name of its class). This is used by the framework primarily for logging purposes;
  • getSecurityManager()/setSecurityManager(securitymanager): returns/set a security manager for the execution of the handler. This methods allow security information to propagate from clients to handlers;
  • getScopeManager()/setScopeManager(scopemanager): returns/set a scope manager for the execution of the handler. This methods allow scope information to propagate from clients to handlers;
  • getLogger(): returns a logger for the execution of the handler. It is recommended that handlers make use of the predefined loggers rather than define their own logger (this allows the injection of different loggers and the transparent redirection of logs based on context of usage).

Lifetime and Event Management

The lifetime of a handler may be naturally divided in a number of phases, where the handler changes state from phase to phase. For example, the handler may be in a running state, it may have completed successfully, it may have failed, or it may be temporarily suspended.

Some clients may need to know the current state of the handler in order to correctly manage it. There is thus value in modelling its states explicitly. There is also value in modelling its state changes, as lifetime events which interested clients can be notified of and act upon. There is in fact value in notifying clients of arbitrary events that may occur during the execution of the handler, besides lifetime events.

We discuss below the facilities offered by the framework for handler lifetime and event management.

Lifetime Management

The Handler Framework offers the following abstractions for lifetime management.

  • The class State in package org.gcube.common.core.utils.handlers.lifetime is the abstract root of all the possible states of a handler's lifetime. State.Created, State.Running, State.Done, State.Failed, and State.Suspended are predefined subclasses of State. Each of these classes has a single instance that handlers and their clients can refer to. For example, Created is defined as follows:
public abstract class State {
  ...
  public static class Created extends State {
    public static final Created INSTANCE = new Created();
    protected Created(){...};
   ...
  }
 ...
}
  • GCUBEHandler defines a method that clients can invoke to obtain the current state of a handler (cf. getState()). It also defines a method that handler implementers can invoke to change the state of the handler (cf. setState(state)).
class MyHandler extends GCUBEHandler<...> {
 
 ...
 /** {@inheritDoc} **/
 public void run() throws Exception {
   this.setState(Running.INSTANCE);
   ...
   this.setState(Suspended.INSTANCE);
   ...
   this.setState(Running.INSTANCE);
   ...
   try {
     ....
   catch(Exception e) {
     ....
     this.setState(Failed.INSTANCE);
   }
   ...
   this.setState(Done.INSTANCE):
 }
 
 ...
}


Note that Created.INSTANCE is the initial state of every handler derived from GCUBEHandler. Note also that setState() enforces precise constraints on the state transitions of a handler, i.e. what states it can assume from the current one. The following state diagram illustrates the legal transitions for predefined states.

Handlerstates.jpg

The legal transitions conform to standard expectations, except perhaps for the transition from DONE.INSTANCE to RUNNING.INSTANCE that shows the possibility of 're-running' a handler that has completed successfully. The programmatic specification of legal transitions for predefined and user-defined states is discussed below.

  • Of course, there is no guarantee that handlers will ever invoke setState() as they change state. Those that do can flag their commitment by implementing the Lifetime interface, also in package org.gcube.common.core.utils.handlers.lifetime:
public interface Lifetime<T> extends GCUBEIHandler<T> {
 public State getState();
}

Note that GCUBEHandler implements Lifetime even though it does not declare it.

Event Management

All handlers are event producers. They accept subscriptions for and fire notifications of events in one more topics of interests, first and foremost lifetime events. The handling of topics, events, subscriptions, and notifications relies of course on the the Event Framework (if you are not already familiar with the Event Framework this is a good time to have a look).

Topics

The class Topic in package org.gcube.common.core.utils.handlers.events implements GCUBETopic as the abstract root of all topics associated with the execution of handlers. Topic.LifetimeTopic is a predefined subclass of Topic that 'groups' lifetime events.

public abstract class Topic implements GCUBETopic {
    ...
    public static final class LifetimeTopic extends Topic {
       public static final LifetimeTopic INSTANCE = new LifetimeTopic();
       protected LifetimeTopic(){};
    }
 
}

Note that, for convenience, LifetimeTopic has a single instance called LifetimeTopic.INSTANCE.

Events

The class Event, also in package org.gcube.common.core.utils.handlers.events, extends GCUBEEvent as the abstract root of all events that are about Topics, whatever their payload. Event.LifetimeEvent is an abstract subclass of Event that models lifetime events. Event.Running, Event.Suspended, Event.Done, and Event.Failed are four concrete subclasses of LifetimeEvent:

abstract public class Event<TOPIC extends Topic,PAYLOAD> extends GCUBEEvent<TOPIC,PAYLOAD>{
 
	...
       public abstract static class LifetimeEvent extends Event<LifetimeTopic,GCUBEHandler<?>>{}
 
	public static class Running extends LifetimeEvent {...}
	public static class Suspended extends LifetimeEvent {...}
	public static class Done extends LifetimeEvent {...}
	public static class Failed extends LifetimeEvent {...}
}

Note that LifetimeEvent has a GCUBEHandler as its payload. This is of course the very handler the event is about.

Monitors

Monitor, also in package org.gcube.common.core.utils.handlers.events, is a partial implementation for consumers of Events about Topics. Concrete consumers may extend it and override its callbacks of interest. There are callbacks for each of the predefined lifetime events (onRunning(event), onCompletion(event), onFailure(event),onSuspension(event)), as well as a catch-all callback for any event (onAnyEvent(event)):

public abstract class Monitor implements GCUBEConsumer<Topic,Object> {
          ...
	 protected void onRunning(Running e) {}
	 protected void onCompletion(Done e) {}
	 protected void onFailure(Failed e) {}
	 protected void onSuspension(Suspended e) {}
	 protected void onAnyEvent(Event<?,?> e){}
}

Note that all the specific callbacks receive the corresponding event and that their implementation does nothing by default. Note also the generic callback onAnyEvent() receives an equally generic event. Consumer will pass to onAnyEvent() all the events that it receives, including lifetime events (but only after having more specific callbacks return).

Subscriptions

GCUBEIHandler defines method whereby Monitors can subscribe to and unsubscribe from one or more Topics (cf. subscribe(monitor, topics*), unsubscribe(monitor, topics*)). The following example shows the common case of a monitor of Done and Failed events. The monitor is defined as an anonymous subclass of Monitor and subscribed for the Lifetime topic.

Monitor myMonitor = new Monitor() {
   /** {@inheritDoc} **/
   protected void onCompletion(Done e) {...}
   /** {@inheritDoc} **/
   protected void onFailure(Failed e) {...}
}
...
 
MyHandler handler;
...
handler.subscribe(myMonitor, LifetimeTopic.INSTANCE);
handler.run();

Note that subscribe() and unsubscribe() accept zero or more topic parameters. When none are specified, the methods assume implicitly a single topic, Lifetime. In other words subscriptions are by default to lifetime events and the subscription above could have been more simply setup as handler.subscribe(myMonitor).

Notifications

GCUBEHandler defines a protected method that handlers can invoke to report the occurrence Events about Topics to Monitors that have previously subscribed for them (cf. notify(topic,event)).

Handlers developers will in fact need to invoke notify() only for events in custom topics (see below). For the common case of lifetime events, notify() is invoked transparently by the implementation of setState() in GCUBEHandler.

Extensions

The Handler Framework allows to extend the states of a class of handlers and the events that may occur during their execution.


[in progress]

General-Purpose Handlers

[coming soon]

Sequential Handlers

[coming soon]

Parallel Handlers

[coming soon]

Scheduling Handlers

[coming soon]

Handlers for remote Interactions

[coming soon]

Service Handlers & the Client Interface

[coming soon]

Staging Service Handlers

[coming soon]