Chapter 2. Components, Widgets and Services

2.1. Introduction

Aranea framework and component model are very simple and implemented purely in Plain Old Java. There are no XML mappings, code generation or bytecode enhancement. The whole component model consists mainly of five interfaces: org.araneaframework.Component, org.araneaframework.Service, org.araneaframework.Widget, org.araneaframework.Environment, org.araneaframework.Message and some conventions regarding their usage and implementation.

This chapter describes the core Aranea abstractions in detail generally not necessary to just develop application code so it can be skipped during the first reading. It is quite dry on the examples, but its understanding is crucial to develop Aranea extensions. To get a quick understanding of how to program applications with widgets read Section 2.7, “Application Widgets”.

2.2. Coding Conventions

2.2.1. Checked versus Unchecked Exceptions

It is our firm belief that checked exceptions are unnecessary in Controller and therefore Aranea will in most cases allow to just declare your overriding method as throws Exception. On the other hand no framework interfaces throw checked exceptions so the exception-handling boilerplate can be delegated to a single error-handling component.

2.2.2. Public versus Framework Interfaces

Since the application programmer implements the same components that are used for framework extension, it is important to discourage the access to public framework interfaces (which are necessarily visible in the overridden classes). Thus a simple convention is applied for core framework interfaces, which is best illustrated with the following example.

public interface Service extends Component, Serializable {
  public Interface _getService();

  public interface Interface extends Serializable {
    public void action(Path path, InputData input, OutputData output) throws Exception;
  }
}

As one can see, the real interface methods are relocated to an inner interface named Interface, that can be accessed using a method _get<InterfaceName>(), which starts with an underscore to discourage its use. As a rule of a thumb, in Aranea the methods starting with an underscore should only be used, when one really knows what one is doing.

2.2.3. Components and Their Orthogonal Properties

Aranea has three main types of components: org.araneaframework.Component, org.araneaframework.Service and org.araneaframework.Widget. These components also have a number of orthogonal properties (like Viewable, Composite), which are represented by interfaces that need to be implemented. Since some particular API methods expect a particular type of component with a particular property (e.g. ViewableWidget) one would either have to abandon static type safety or define a lot of meaningless interfaces that would clutter the Javadoc index and confuse the source code readers. The approach chosen in Aranea is to make such interfaces internal to the property, like in the following example.

public interface Viewable extends Serializable {
  public Interface _getViewable();

  interface Interface extends Serializable {
    public Object getViewModel() throws Exception;
  }

  public interface ViewableComponent extends Viewable, Component, Serializable {}
  public interface ViewableService extends ViewableComponent, Service, Serializable {}
  public interface ViewableWidget extends ViewableService, Widget, Serializable {}
}

2.3. Components and Environment

org.araneaframework.Component represents the unit of encapsulation and reuse in Aranea. Components are used to both provide plug-ins and extensions to the framework and to implement the actual application-specific code. A component has (possibly persistent) state, life cycle, environment and a messaging mechanism.

public interface Component extends Serializable {
  public Component.Interface _getComponent();

  public interface Interface extends Serializable {
    public void init(Environment env) throws Exception;
    public void destroy() throws Exception;
    public void propagate(Message message) throws Exception;
    public void enable() throws Exception;
    public void disable() throws Exception;
  }
}

The component life cycle goes as follows:

  1. init() —notifies the component that it should initialize itself passing it the Environment . A component can be initialized only once and the environment it is given stays with it until it is destroyed.
  2. All other calls (like propagate() ) should be done when a component is alive, initialized and enabled.
  3. disable() —notifies the component that it will be disabled and will not receive any calls until it is enabled again. A component is enabled by default.
  4. enable() —notifies the component that it has been enabled again. This call may only be done after a disable() call.
  5. destroy() —notifies the component that it has been destroyed and should release any acquired resources and such. A component can be destroyed only once and should be initialized before that.

Further in the text we will refer to an initialized and not destroyed component instance that has a parent as live and one that has not been disabled or has been re-enabled as enabled.

Aranea provides a base implementation of the Componentorg.araneaframework.core.BaseComponent. This implementation mainly enforces contracts (including life cycle and some basic synchronization). A base class for application development org.araneaframework.core.BaseApplicationComponent is also available.

2.3.1. Composite Pattern and Paths

Composite pattern refers to a design approach prominent (specifically) in the GUI modeling when objects implementing the same interface are arranged in a hierarchy by containment, where the nodes of the tree propagate calls in some way to the leafs of the tree. It is shown on Figure 2.1, “Composite design pattern”.

Composite design pattern

Figure 2.1. Composite design pattern

Composite is one of the main patterns used in Aranea. It is mainly used to create a Hierarchical Controller using Component containment. In terms of Component interface Composite is used to propagate life cycle events and route messages (see Section 2.3.3, “Messaging Components”).

The flavor of the Composite pattern as used in Aranea typically means that every contained component has some kind of an identifier or name that distinguishes it from other children of the same parent (note that the child is not typically aware of its identifier). This identifiers are used to route messages and events and can be combined to form a full identifier which describes a "path" from the root component to the child in question. This paths are represented by a Iterator-like interface org.araneaframework.Path.

public interface Path extends Cloneable, Serializable {
  public Object getNext();
  public Object next();
  public boolean hasNext();
}

Each next() call will return the identifier of the next child in the path to the descendant in question. Default implementation (org.araneaframework.core.StandardPath) uses simple string identifiers like "a" or "b" and combines them using dots forming full paths like "a.b.c".

A Composite component may want to make its children visible to the outside world by implementing the org.araneaframework.Composite interface:

public interface Composite extends Serializable {
  public Composite.Interface _getComposite();
  public interface Interface extends Serializable {
    public Map getChildren();
    public void attach(Object key, Component comp);
    public Component detach(Object key); 
  }
}

This interface allows to both inspect and manipulate component children by attaching and detaching them from the parent component.

As most of the Aranea abstractions are built to be used with the Composite concept we will illustrate it in greater detail when examining other abstractions and their implementation. Further on we will assume that any Component has a parent that contains it and every child has some kind of name in relation to the parent unless noted otherwise (obviously there is at least one Composite that does not have a parent, but we don't really care about that at the moment).

2.3.2. Environment

org.araneaframework.Environment is another important concept that represents the way Components interact with the framework. Environment interface is rather simple:

public interface Environment extends Serializable {
  public Object getEntry(Object key);
}

It basically provides means of looking up entry objects by their key. A typical usage of the Environment can be illustrated with an example.

...
MessageContext msgCtx = (MessageContext) getEnvironment().getEntry(MessageContext.class);
msgContext.showInfoMessage("Hello world!");
...

As one can see from the example Environment will typically allow to look up implementations of the interfaces using their Class as the key (this is in fact an Aranea convention in using and extending the Environment). The interfaces serving as keys for Environment entries are referred to as contexts. It is thus not unlike JNDI or some other directory lookups that allow to hold objects, however unlike them Environment is very specific to the Component it is given to, and can be influenced by its parents. In fact, all contexts available in the Environment will be provided to the Component by its parents or ancestors (in the sense of containment rather than inheritance). Thus, two different Components may have completely different Environments.

A default implementation of Environment is org.araneaframework.core.StandardEnvironment. It provides for creating an Environment from a java.util.Map, or extending an existing environment with map entries.

A component can provide an environment entry to its descendant, by providing it to the initializer of its direct child. For instance the MessageContext could be provided by the following message component:

public class MessageFilterService implements MessageContext, Component, Service {
  protected Service childService;
  public void setChildService(Service childService) {
    this.childService = childService;
  }

  public void init(Environment env) {
    childService.init(
      new StandardEnvironment(env, MessageContext.class, this);
  }

  //MessageContext implementation...
  public String showInfoMessage(String message) {
    //Show message to user...
  }  

  //...
}

After that the childService, its children and so on will be able to use the MessageContext provided by MessageFilterService. Of course this can be done simpler as shown in examples in chapter

2.3.3. Messaging Components

So far, we have looked at the component management and environment. However what makes the component hierarchy such a powerful concept is messaging. Basically, messaging allows us to send any events to any component in the hierarchy (including all components or a specific one). The messaging is incorporated using the org.araneaframework.Message interface

public interface Message extends Serializable {
  public void send(Object id, Component component) throws Exception;
}

and Component.propagate(Message message) method. The default behavior of the propagate() method should be to send the message to all component children, passing the send() method the identifier of the child and the child itself. It is up to the message what to do with the child further, but typically Message just calls the propagate() method of the child passing itself as the argument after possibly doing some custom processing (the double-dispatch OO idiom).

A standard Message implementation that uses double-dispatch to visit all the components in hierarchy is org.araneaframework.core.BroadcastMessage. It usage can be illustrated with the following example:

...
Message myEvent = new BroadcastMessage() {
  public void execute(Component component) throws Exception {
  if (component instanceof MyEventListener)
    ((MyEventListener) component).onMyEvent(data);
  }
}
myEvent.send(null, rootComponent);
...

This code will call all the components in the hierarchy that subscribed to the event and pass them a certain data parameter. As one can see, when calling Message.send() we will typically pass null as the first parameter, since it is needed only when propagating messages further down the hierarchy. Note that messages can be used to gather data from the components just as well as for passing data to them. For example one could construct message that gathers all FormWidgets from the widget hierarchy:

public static class FormWidgetFinderMessage extends BroadcastMessage {
  List formList = new ArrayList();

  protected void execute(Component component) throws Exception {
    if (component instanceof org.araneaframework.uilib.form.FormWidget) {
      formList.add(component);
    }
  }

  public List getAllForms() { return formList; }
}

Another standard Message implementation is org.araneaframework.core.RoutedMessage, which allows us to send a message to one specific component in the hierarchy as in the following example:

...
Message myEvent = new RoutedMessage("a.b.c") {
  public void execute(Component component) throws Exception {
    ((MyPersonalComponent) component).myMethod(...);
  }
}
myEvent.send(null, rootComponent);
...

This code will send the message to the specific component with path "a.b.c" and call myMethod() on it.

2.3.4. State and Synchronization

The handling of persistent state in Aranea is very simple. There are no scopes and every component state is saved until it is explicitly removed by its parent. This does not mean that all of the components are bound to the session, but rather that most components will live a period of time appropriate for them (e.g. framework components will live as long as the application lives, GUI components will live until user leaves them, and so on). This provides for a very flexible approach to persistence allowing not to clutter memory with unused components.

The end result is that typically one needs not worry about persistence at all, unless one is programming some framework plug-ins. All class fields (except in some cases transient fields) can be assumed to persist while the host object is live.

However such handling does not guarantee that the component state is anyhow synchronized. As a matter of fact most of the framework components outside the user session should be able to process concurrent calls and should take care of the synchronization themselves. However application components are typically synchronized by the framework. More information on the matter will follow in Section 2.7, “Application Widgets”.

2.4. InputData and OutputData

InputData is Aranea abstraction for a request, which hides away the Servlet API and allows us to run Aranea on different containers (e.g. in a portlet or behind a web service).

Both InputData and OutputData have a scope, which is information about the path to the current component. When they are propagated through the component hierarchy the following methods are used to construct the scope:

MethodDescription
pushScope(Object step)Adds child identifier to the end of the current scope, should always be used before the call to the child.
Object popScope()Removes the last identifier from the scope, should always be used a call to a child.
Path getScope()Returns the current scope as a Path.
restoreScope(Path scope)Restore the scope back to a previously saved one.

Note

These methods should be used only when using Composite components, as framework components are scoped differently.

InputData also provides access to the data sent to the component. This data comes in two flavours:

  • getScopedData() returns a java.util.Map with the data sent specially to this component, which is associated with the current scope.
  • getGlobalData() returns a java.util.Map with the data sent to the application generally.

In case Aranea is running on top of a servlet both these maps will contain only Strings (or in case you submitted more than one value for a specific key it may contain a String[]). In case of the usual path and scope implementation (as dot-separated strings) global data will contain the submitted parameters with no dots in them and scoped data will contain the parameters prefixed with the current component scope string.

The main function of OutputData is to propagate attributes that are used when delegating rendering to a templating engine or by children to access some general information during rendering. The following methods are used to propagate these attributes:

MethodDescription
pushAttribute(Object key, Object value)Registers the attribute value under the key key. If some other component have already propagated an attribute under this key, then it will be temporary overridden.
Object popAttribute(Object key)Deregisters the attribute from under the key key. If the attribute was temporary overridden it will restore the previous attribute value.
Object getAttribute(Object key)Returns the last object registered under key key.
Map getAttributes()Returns all currently registered attributes.

Finally, as InputData and OutputData are typically connected, they can be retrieved from the other *Data structure using correspondingly getOutputData() and getInputData() methods.

Warning

Since the sibling *Data structure is not propagated through the component hierarchy is not guaranteed to be scoped correctly!

2.4.1. Extensions

InputData an OutputData both implement a way to extend their functionality without wrapping or extending the objects themselves. This is achieved by providing the following two methods:

void extend(Class interfaceClass, Object extension)
Object narrow(Class interfaceClass);

The following example should give an idea of applying these methods:

input.extend(FileUploadExtension.class, new FileUploadExtension(input));

...

FileUploadExtension fileUploadExt = 
  (FileUploadExtension) input.narrow(FileUploadExtension.class);
if (fileUploadExt.uploadSucceeded()) {
  //...
}

Note

Both HttpServletRequest and HttpServletResponse are available as InputData and OutputData extensions respectively.

2.4.2. HttpInputData and HttpOutputData

Although all of the core Aranea abstractions are independent of the Servlet API and web in general, we also provide a way to manipulate low-level HTTP constructs. To that goal we provide to interfaces, HttpInputData and HttpOutputData, which extend respectively InputData and OutputData.

Let's examine the HttpInputData. First of all it provides methods that are similar to the ones found in the HttpServletRequest:

MethodDescription
Iterator getParameterNames()Returns an iterator over names of the parameters submitted with the current request.
String[] getParameterValues(String name)Returns the array of values of the particular parameter submitted with the current request.
String getCharacterEncoding()Returns the character encoding that is used to decode the request parameters.
setCharacterEncoding(String encoding)Sets the character encoding that is used to decode the request parameters. Note that this must be called before any parameters are read according to the Servlet specification.
String getContentType()Returns the MIME content type of the request body or null if the body is lacking.
Locale getLocale()Returns the preferred Locale that the client will accept content in, based on the Accept-Language header. If the client request doesn't provide an Accept-Language header, this method returns the default locale for the server.

Note

Unlike InputData methods the parameters are presented as is and include both global and scoped parameters (the scoped ones are prefixed by the full name of the enclosing widget).

However next methods are a bit different from the HttpServletRequest alternatives:

MethodDescription
String getRequestURL()Returns the target URL of the current request.
String getContainerURL()Returns an URL pointing to the Aranea container (in most cases the dispatcher servlet).
String getContextURL()Returns an URL pointing to the Aranea container context (in most cases the web application root).
String getPath()Returns the path on the server starting from the dispatcher servlet that has been submitted as the part of the request target URL.
pushPathPrefix(String pathPrefix)Consumes the path prefix allowing children to be mapped to a relative path.
popPathPrefix()Restores the previously consumed path prefix.

The interesting part here are the methods that deal with the path. The problem is that unlike most common cases Aranea components form a hierarchy. Therefore if a parent is mapped to path prefix "myPath/*" and its child is mapped to a path prefix "myChildPath/*" if the path handling were absolute the child would never get the mapped calls. This is due to the child being really mapped to the path "myPath/myChildPath". Therefore the parent must consume the prefix "myPath/" using method pushPathPrefix() and then the child will be correctly matched to the relative path "myChildPath".

HttpOutputData contains methods that are comparable to the ones found in HttpServletResponse:

MethodDescription
String encodeURL(String url)Encodes the URL to include some additional information (e.g. HTTP session identifier). Note that Aranea may include some information not present in the servlet spec.
sendRedirect(String location)Sends an HTTP redirect to a specified location URL.
OutputStream getOutputStream()Returns an OutputStream that can be used to write to response. Note that unlike the Servlet specification, Aranea permits to use stream and writer interchangeably.
PrintWriter getWriter()Returns a PrintWriter that can be used to write to response. Note that unlike the Servlet specification, Aranea permits to use stream and writer interchangeably.
setContentType(String type)Sets the MIME content type of the output. May include the charset, e.g. "text/html; charset=UTF-8".
Locale getLocale()Returns the locale associated with the response.
String getCharacterEncoding()Returns the character encoding used to write out the response.
void setCharacterEncoding(String encoding)Sets the character encoding used to write out the response.

2.5. Services

org.araneaframework.Service is a basic abstraction over an event-driven Controller pattern that inherits life cycle, environment and messaging from the Component. The difference from the Component is as follows:

public interface Service extends Component, Serializable {
  public Interface _getService();

  public interface Interface extends Serializable {
    public void action(Path path, InputData input, OutputData output) throws Exception;
  }
}

The method action() is similar to the service() method in the Servlet API, InputData being an abstraction over a request and OutputData being an abstraction over a response (see Section 2.4, “InputData and OutputData”). Thus a service will both process the request parameters and render itself during this method call. However unlike servlets services can be Composite and may be defined both statically (on application startup) or dynamically (adding/removing new services on the fly).

Services are the basic working horses of the Aranea framework. They can generally be both synchronized and unsynchronized depending on the context. Services may also have persistent state and their lifetime is explicitly managed by their parent (see Section 2.3.4, “State and Synchronization”). The service life cycle is very simple—as long as the service is live and enabled it can receive action() calls, possibly several at a time.

Aranea provides a base implementation of the Serviceorg.araneaframework.core.BaseService and a base class for application development org.araneaframework.core.BaseApplicationService.

2.5.1. Filter Services

One of the most common ways to use the services it to create a filter service, that wraps a child service and provides some additional functionality and/or environment entries. To that purpose Aranea provides a filter base class — org.araneaframework.framework.core.BaseFilterService. This class implements all of the Service methods, by default just delegating them to the corresponding child methods. A common thing to do is override the action() method to add functionality and getChildEnvironment() to add environment entries, as shown in the following example:

public class StandardSynchronizingFilterService 
  extends BaseFilterService {
  
  protected Environment getChildEnvironment() {
    return new StandardEnvironment(
        getEnvironment(), 
        SynchronizingContext.class, 
        new SynchronizingContext() {});
  }
  
  protected synchronized void action(
      Path path, 
      InputData input, 
      OutputData output) throws Exception {
    super.action(path, input, output);
  }
}

More information on services and other components that make up the framework can be found in Chapter 3, Framework and Configuration.

2.6. Widgets

Widget is the main abstraction used to program applications in Aranea. Widget is specifically any class extending the org.araneaframework.Widget interface and adhering to a number of conventions. More generally, widgets are components that function both as controllers and GUI elements, and that have the following properties:

Synchronized
The widget is always accessed by a single thread, therefore there is never any need to think about synchronization. One can assume that there is only one user using the application and program to service this user without any concern for concurrency.
Stateful
When programming widgets there is no need to concern oneself with juggling the HttpSession attributes or similar low-level mechanics. Widget state (meaning the class fields) is guaranteed to be preserved as long as the widget is alive. One can just use these fields to save the necessary data without any external state management, thus adhering to the rules of object-oriented encapsulation.

The latter two properties make widgets ideal for programming custom application components.

Widgets extend services with a request-response cycle:

public interface Widget extends Service, Serializable {
  public Interface _getWidget();
  
  public interface Interface extends Serializable {
    public void update(InputData data) throws Exception;
    public void event(Path path, InputData input) throws Exception;
    public void process() throws Exception;
    public void render(OutputData output) throws Exception;
  } 
}

Although widgets extend services, a widget will function during one request either as a widget or as a service—that is if a widget receives an action() call no other request-response cycle calls can occur.

The widget request-response cycle proceeds as follows:

  1. update() —this method is called for all the widgets in the hierarchy. It allows widgets to read the data from request and possibly store some conversation state or at least temporary information to render the next view.
  2. event() —this method is called on only one widget in the hierarchy. It allows to send widgets events from the user. The path is used to route the event to the correct widget and is empty when the event is delivered to its endpoint. This method is optional in the widget request-response cycle.
  3. process() —this method is called on all the widgets in the hierarchy. It allows widgets to process the result of event() and update() calls before rendering.
  4. render() —the way this method is called depends on how widgets are rendered (see Section 2.6.1, “ViewModel and Rendering” ). It may be called only after process() and may be called more than once (or not at all) during one request-response cycle.

Aranea provides a base implementation of the Widgetorg.araneaframework.core.BaseWidget and a base class for application development org.araneaframework.core.BaseApplicationWidget. More on the last one can be found in Section 2.7, “Application Widgets”.

2.6.1. ViewModel and Rendering

The default model of both widget and service rendering is that they render themselves. However, in most cases the widget might want to delegate the rendering to some templating language. In some other cases the widget might be rendered externally, without calling render() at all. Further on, we will describe these three cases in detail.

Self-rendering
In the most basic situation the widget will just use OutputData for rendering by casting it into e.g. HttpOutputData . In such a case the widget will just write out markup and return from the render() method optionally rendering children as well. The data for rendering will be drawn from the widget fields as well as (possibly) OutputData attributes.
Using templates for rendering
The most common case in application widgets is to delegate rendering to a templating language. A widget may basically choose to render itself in arbitrary templating language as Aranea does not impose any restrictions. In fact, one widget may be rendered with one templating language, while another one with a completely different language. The template can gain access to the widget using the knowledge of the widget's full name (which is gathered in the OutputData scope). It is then possible to acquire the widget View Model, which is a read-only representation of the widget state. For that the widget should implement org.araneaframework.Viewable :
public interface Viewable extends Serializable {
  public Interface _getViewable();

  interface Interface extends Serializable {
    public Object getViewModel() throws Exception;
  }  
}
View model is put together by the widget being rendered and should contain all the data necessary to render the widget.
External rendering

Finally, a widget render() method may not be called altogether and a Viewable widget may be rendered externally using the available View Model. This is the case with some reusable widgets which are rendered using e.g. JSP tags.

2.7. Application Widgets

This section explains how to program applications using widgets as the main abstraction.

A typical application widget class will extend org.araneaframework.uilib.core.BaseUIWidget. This widget represents the usual custom application component that is rendered using Aranea custom JSP tags. BaseUIWidget inherits most of its functionality from org.araneaframework.core.BaseApplicationWidget the difference between the two being only that BaseUIWidget assumes to be connected with a JSP page (or another templating toolkit).

2.7.1. Children Management

BaseApplicationWidget provides a number of methods for managing child widgets:

public abstract class BaseApplicationWidget ... {
  ...
  public void addWidget(Object key, Widget child);
  public void removeWidget(Object key);
  public void enableWidget(Object key);
  public void disableWidget(Object key);
  ...
}

As one can see, every added child has an identifier which should be unique among its siblings. This identifier is used when rendering and sending events to the widget in question, to identify it among its peers.

Typically, children are added when created:

addWidget("myChildWidget", new MyChildWidget("String parameter", 1));

An added child will be initialized, will receive updates and events and may be rendered. A widget can be active only if added to a parent. It will live as long as the parent, unless the parent explicitly removes it:

removeWidget("myChildWidget");

Removing a child widget will destroy it and one should also dispose of any references that may be pointing to it, to allow the child to be garbage collected.

A usual idiom is to save a reference to the newly created and added child using a parent widget field:

public class MyWidget extends BaseUIWidget {
  private MyChildWidget myChildWidget;
  
  protected void init() {
    myChildWidget = new MyWidget("String parameter", 1);
    addWidget("myChildWidget", myChildWidget);
  }
}

This allows to call directly child widget methods and does not anyhow significantly increase memory usage, so this technique may be used everywhere when needed.

Disabling a child (disableWidget("myChildWidget")) will stop it from receiving any events or rendering, but will not destroy it. It can later be reenabled by calling enableWidget("myChildWidget").

2.7.2. Event Listeners

Registering event listeners allows widgets to subscribe to some specific user events (widget will receive only events specially sent to it). The distinction comes by the "event identifier" that is assigned to an event when sending it. The events are handled by the classes extending org.araneaframework.core.EventListener:

public interface EventListener extends Serializable {
  public void processEvent(Object eventId, InputData input) throws Exception;
}

The event listeners are registered as following:

addEventListener("myEvent", new EventListener() {
  public void processEvent(Object eventId, InputData input) throws Exception {
    log.debug("Received event: " + eventId);
  }
}

Of course, the event listener does not have to be an anonymous class and can just as well be an inner or even a usual public class. A standard base implementation org.araneaframework.core.StandardEventListener is provided that receives an optional String event parameter:

addEventListener("myEvent", new StandardEventListener() {
  public void processEvent(Object eventId, String eventParam, InputData input) throws Exception;
    log.debug("Received event " + eventId + " with parameter " + parameter);
  }
}

Another useful way to process events is to register a proxy event listener (org.araneaframework.core.ProxyEventListener) that will proxy the event to a method call, e.g.:

protected void init() {
  addEventListener("myEvent", new ProxyEventListener(this));
}

public void handleEventMyEvent(String parameter) {
    log.debug("Received event myEvent with parameter " + parameter);
}

The convention is that the proxy event listener translates an event "<event>" into a method call handleEvent<event> making the first letter of <event> uppercase. The "String parameter" is optional and can be omitted.

A useful feature is the method setGlobalEventListener(EventListener listener) that allows to register a listener that will receive all events sent to the widget. In fact BaseUIWidget does that by default, and typically you will use the individual event listeners only when you want to override this default behaviour. This allows to just define correct method names (handleEvent<event>) and all events will be translated to the calls to these methods. Certainly this can also be cancelled by calling clearGlobalEventListener(), or overridden by adding your own global event listener.

2.7.3. Action Listeners

Registering action listeners allows widgets to subscribe to some specific user generated actions. Actions differ from events in that widget lifecycle execution for whole component tree is not triggered upon request—actions are just sent to the receiving widget's ActionListener, which is SOLELY responsible for generating the whole response. For rich UI components this allows a quick conversations with server, without requiring full form submits and generating whole view.

2.7.4. Environment

Every initialized widget has a reference to org.araneaframework.Environment available through the getEnvironment() method. Environment allows to look up framework services (called contexts):

MessageContext msgCtx = (MessageContext) getEnvironment().getEntry(MessageContext.class);
msgCtx.showInfoMessage("Hello world!");

As one can see from the examples, contexts are looked up using their interface Class object as key. All framework services in Aranea are accessible only using the environment.

To find out more about InputData and OutputData see Section 2.3.2, “Environment”

2.7.5. Overridable Methods

The main method that is typically overridden in a widget is init(). As widget does not get an environment before it is added and initialized it is impossible to access framework services in the constructor, therefore most of the initialization logic moves to the custom init() method. A dual overridable method is destroy(), though it is used much less.

In addition to event processing it is sometimes useful to do some kind of pre- and post-processing. The BaseApplicationWidget has the following methods that may be overridden to allow this processing:

protected void handleUpdate(InputData input) throws Exception {}
protected void handleProcess() throws Exception {}

handleUpdate() is called before event listeners are notified and allows to read and save request data preparing it for the event. More importantly, this method is called even when no event is sent to the current widget allowing one to submit some data to any widget. handleProcess() is called after the event listeners are notified and again is called event if current widgets receives no events at all. It allows to prepare the widget for rendering, post-processing the request results without concern whether or not events have been delivered.

2.7.6. InputData and OuputData

In Aranea one usually does not need to handle request manually in custom application widgets. Even more, the request is not accessible by default. The usual way to submit custom data to a widget and read it is using Aranea Forms (see Chapter 5, Forms and Data Binding). However, when one needs to access the submitted data, one can do that using the org.araneaframework.InputData. This class can be used as follows:

...
String myData1 = 
  (String) getInputData().getScopedData().get("myData1");
String globalSubmittedParameter = 
  (String) getInputData().getGlobalData().get("globalSubmittedParameter");
...

getInputData() is a BaseApplicationWidget method that returns the input data for the current request (one can also use the input parameter given to event listener directly).

org.araneaframework.OutputData is accessible through the getOutputData() method of BaseWidget or directly as the output parameter passed to render() method.

To find out more about InputData and OutputData see Section 2.4, “InputData and OutputData”

2.7.7. View Model and Rendering

BaseApplicationWidget also contains methods that facilitate transferring data to the presentation layer. This is achieved using a View model—an object containing a snapshot of the widget current state. The most typical way to use the view model it to add data to it:

...
putViewData("today", new Date());
putViewData("currentUser", userBean);
...

View data is typically accessible in the presentation layer as some kind of a variable (e.g. a JSP EL variable) for the current widget. If the data becomes outdated one can override it using putViewData() call or remove it using the removeViewData() call. In case one needs to put view data that would last one request only there is an alternative method:

...
putViewDataOnce("now", new Date());
...

Finally widget instance is also visible to the view, so one of the ways to make some data accessible is just to define a JavaBean style getter:

...
public Date getNow() {
  return new Date();
}
...

BaseUIWidget allows to render the current widget using a JSP page. To do that one needs to select a view as follows:

...
setViewSelector("myWidget/form");
...

This code makes the widget render itself using the JSP situated in WEB-INF/jsp/myWidget/form.jsp (of course the exact place is configurable). It is also possible to render the widget using other template technologies with the same view selector by overriding the render() method in the base project widget.

2.7.8. Putting It All Together

A typical application custom widget will look like that:

public class TestWidget extends BaseUIWidget {

  private static final Logger log = Logger.getLogger(TestWidget.class);
  
  private Data data;
  
  protected void init() throws Exception {
    //Sets the JSP for this widget to "/WEB-INF/jsp/home.jsp"
    setViewSelector("home");
    
    //Get data from the business layer
    data = ((TestService) lookupService("testService")).getData("test parameter");
    
    //Make the data accessible to the JSP for rendering
    putViewData("myData", data);    
  }
  
  /*
   * Event listener method that will process "test" event.
   */
  public void handleEventTest() throws Exception {
    getMessageCtx().showInfoMessage("Test event received successfully");       
  }
}

2.8. Standard Contexts

Contexts are the Aranea way to access framework services. They can be looked up from the environment as shown in Section 2.7.4, “Environment”. This section describes the most common Aranea contexts that should be available in any typical configuration. All these contexts are also available directly through BaseUIWidget methods as shown further on.

2.8.1. MessageContext

org.araneaframework.framework.MessageContext allows to show messages to the user. The messages can be of several types, including predefined error and informative types. Typically messages will be shown somewhere in the application (exact way is application-specific). MessageContext is available through a BaseUIWidget method getMessageCtx() and is typically used as follows:

getMessageCtx().showInfoMessage("Hello world!");

MessageContext divides messages by type (with predefined "info", "warning" and "error" types available) and life span (usual or permanent). Usual messages are shown to user once and then cleared, while permanent messages will be shown to user until explicitly cleared by the programmer:

MethodDescription
showMessage(String type, String message)Shows a message message of type type to the user. Message is cleared after the user sees it once.
showInfoMessage(String message)Shows an error message to the user.
showWarningMessage(String message)Shows a warning message to the user.
showErrorMessage(String message)Shows an informative message to the user.
clearMessages()Clears all non-permanent messages.
showPermanentMessage(String type, String message)Shows a permanent message message of type type to the user. The message will be shown until hidden explicitly.
hidePermanentMessage(String message)Clears the specific permanent message, under all message types where it might be present.
clearPermanentMessages()Clears all of the permanent messages.
clearAllMessages()Clears all messages (both permanent and usual).

Note

Messages should already be localized when passed to the MessageContext, it does not do any further processing. Use LocalizationContext described in Section 2.8.2, “LocalizationContext” to do the actual localization of the added message.

For information on implementation of the MessageContext see Section 3.5.8, “User Messages Filter”.

2.8.2. LocalizationContext

org.araneaframework.framework.LocalizationContext allows to get and set current session locale, localize strings and messages, and lookup resource bundles. The context is available through the BaseUIWidget method getL10nCtx(). Typically it is used as follows:


...
String message = getL10nCtx().localize("my.message.key");
getMessageCtx().showInfoMessage(message);
...

LocalizationContext provides the following methods:

MethodDescription
Locale getLocale()Returns the current session locale.
setLocale(Locale locale)Sets the current session locale.
String localize(String key)Localizes a string returning one that corresponds to the current locale.
ResourceBundle getResourceBundle()Returns a resource bundle corresponding to the current locale.
ResourceBundle getResourceBundle(Locale locale)Returns a resource bundle corresponding to arbitrary locale.
String getMessage(String code, Object[] args)Localizes the code and uses it to format the message with the passed arguments. The format of the localized message should be acceptable by java.text.MessageFormat.
String getMessage(String code, Object[] args, String defaultMessage)Localizes the code and uses it to format the message with the passed arguments. The format of the localized message should be acceptable by java.text.MessageFormat. If the localized message cannot be resolved uses defaultMessage instead.

For information on implementation of the LocalizationContext see Section 8.1.2, “Spring Localization Filter”.

2.8.3. FlowContext

A common need in a web programming is to support navigation style known as flows—interactive stateful processes that can navigate to each other passing arguments when needed. A more complex case is when we also have flow nesting—a flow can call a subflow, and wait for it to finish, then reactivate again. In this case we can have at any given moment a stack of flows, where the top one is active, and the next one will reactivate when the top one finishes. It is also useful if nested flows can return resulting values when they finish.

Flow diagram

Figure 2.2. Flow diagram

org.araneaframework.framework.FlowContext is the Aranea context that provides support for nested flow navigation. Aranea flow is a widget that is running in the flow container (using the FlowContext.start() method. Aranea abstraction for the nested state is that of a function—the nested flow takes in some parameters and when finished may return some value or signal that no value can be returned. The context is available as getFlowCtx() method of BaseUIWidget and allows to start flows, finish flows and return the resulting value.

To start a new flow one needs to create a widget as usual. The widget may take some parameters in the constructor—they are considered to be the incoming parameters of the flow:

...
getFlowCtx().start(new TestFlow(new Long(5)), null, null);
...

This call will start a new nested flow for the widget TestFlow making the current flow inactive. TestFlow will render and receive event until it explicitly returns control to the starting flow. Note that this code will start the flow and then return the control, so it is important not to do anything in the same method after starting a new flow.

To end the flow successfully one needs to do as follows:

...
getFlowCtx().finish(new Long(8));
...

This call will finish the current flow (in our case TestFlow) and return the control to the starting flow and its widget.

Often one needs to handle the return from the flow, processing the returned result. This corresponds to our abstraction of a method, however since Java does not support continuations we chose to allow the caller to register a handler when starting the flow by passing a FlowContext.Handler:

...
getFlowCtx().start(new TestFlow(new Long(5)), null, 
  new FlowContext.Handler() {
    public void onFinish(Object result) {
      getMessageCtx().showInfoMessage("TestFlow returned value " + result);
    }
    public void onCancel() {
      //Ignore cancelled flow
    }
  });
...

A less common but nevertheless useful feature is to configure the starting flow after it has been initialized. For that the caller needs to pass a FlowContext.Configurator:

...
getFlowCtx().start(new TestFlow(new Long(5)), 
  new FlowContext.Configurator() {
     public void configure(Component comp) {
      ((TestFlow) comp).setStrategy(TestFlow.ATTACK);
     }
  }, null);
...

Finally FlowContext also allows to replace the current flow instead of deactivating it by using the replace() method and to cancel the current flow by using the cancel() method.

For standard implementation, please see Section 3.5.19, “Root Flow Container”