Difference between revisions of "The Handler Framework"
(→Events) |
(→Events) |
||
Line 297: | Line 297: | ||
</source> | </source> | ||
− | '''Note:''' <code>LifetimeEvent</code>s mirror | + | '''Note:''' <code>LifetimeEvent</code>s mirror <code>State</code>s and are associated with the corresponding transitions. The exception is the initial state <code>Created</code>, which is not associated with a state transition and thus does not have a corresponding lifetime event. We discuss below how lifetime events are raised during state transitions. |
+ | |||
'''Note:''' <code>LifetimeEvent</code>s have a <code>GCUBEHandler</code> as their payload. This is of course the very handler the events are about. | '''Note:''' <code>LifetimeEvent</code>s have a <code>GCUBEHandler</code> as their payload. This is of course the very handler the events are about. | ||
Revision as of 11:19, 19 September 2009
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.
Contents
Overview
The Handler Framework is for the most part defined by the inheritance hierarchy contained in the package org.gcube.common.core.utils.handlers
:
The high-level roles of package components are summarised below:
GCUBEIHandler
: the interface of all handlers.
GCUBEHandler
: the abstract base implementation ofGCUBEIHandler
.
GCUBEComplexHandler
: an abstract specialisation ofGCUBEHandler
for complex handlers, i.e handlers that manage other handlers.
GCUBEScheduledHandler
: a concrete specialisation ofGCUBEComplexHandler
for handlers that schedule the execution of other handlers.
GCUBESequentialHandler
: a concrete specialisation ofGCUBEComplexHandler
for handlers that chain the execution of other handlers.
GCUBEHandlerConnector
: a concrete specialisation ofGCUBEHandler
for handlers that connect the inputs and outputs of handlers chained by aGCUBEScheduledHandler
.
GCUBEParallelHandler
: a concrete specialisation ofGCUBEComplexHandler
for handlers that parallelise the execution of other handlers.
GCUBEServiceHandler
: an abstract specialisation ofGCUBEHandler
for handlers that engage in interactions with remote service port-types.
GCUBEStatefulServiceHandler
: an abstract specialisation ofGCUBEServiceHandler
for handlers that engage in interactions with remote WS-Resources.
GCUBEStagingServiceHandler
: an abstract specialisation ofGCUBEStatefulServiceHandler
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 packageorg.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
, andState.Suspended
are predefined subclasses ofState
. 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: Created.INSTANCE
is the initial state of every handler derived from GCUBEHandler
.
Note: 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.
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. An attempt to perform an illegal transition results in an IllegalStateException
. The programmatic specification of legal transitions for predefined and user-defined states is discussed below.
- There is no guarantee that handlers will ever invoke
setState()
as they change state. Those that do can flag their commitment by implementing theLifetime
interface, also in packageorg.gcube.common.core.utils.handlers.lifetime
:
public interface Lifetime<T> extends GCUBEIHandler<T> { public State getState(); }
Note: GCUBEHandler
implements Lifetime
even though it does not declare it. In other words, it offers support for state management but does presume that subclasses will make use of 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 Topic
s, 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: LifetimeEvent
s mirror State
s and are associated with the corresponding transitions. The exception is the initial state Created
, which is not associated with a state transition and thus does not have a corresponding lifetime event. We discuss below how lifetime events are raised during state transitions.
Note: LifetimeEvent
s have a GCUBEHandler
as their payload. This is of course the very handler the events are about.
Monitors
Monitor
, also in package org.gcube.common.core.utils.handlers.events
, is a partial implementation for consumers of Event
s about Topic
s. 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 Monitor
s can subscribe to and unsubscribe from one or more Topic
s (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 Event
s about Topic
s 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]