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 Scope getScope();             /** @since 1.1 */
  public Environment getEnvironment(); /** @since 1.1 */
  public boolean isAlive();            /** @since 1.1 */

  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.

Component methods not dealing with lifecycle or messaging are accessible without getting at Component.Interface. Component.getScope() returns scoped identifier that uniquely identifies that Component in component hierarchy. Component.getEnvironment() returns information of Environment in which Component lives and Component.isAlive() allows component to check whether it is living at all :).

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);
  public Object requireEntry(Object key) throws NoSuchEnvironmentEntryException;
}

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 this chapter, but this is how most of the components in Aranea provide new contexts to the Environment.

Sometimes, however, one may want to make his or her component or widget independent from the specific Environment. This can be achieved by using org.araneaframework.core.RelocatableDecorator:

  Service child = new RelocatableDecorator(new MyWidget());
  addWidget("c", child);

For example, this technique is used when a user clones a thread (middle mouse button click on a link), and it is necessary to clone the state. Then each widget is cloned, and a new Environmnet is provided to them.

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).

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

  • getScopedData(Path scope) returns a java.util.Map with the data sent specially to component, which unique identifier in the component hierarchy is scope. To get at this data, one can use construction inputData.getScopedData(getScope().toPath()) from a component.
  • 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. If one wants to access multi-valued parameters in servlet environment StandardServletInputData.getParameterValues(String name) method should be used (returns String array). 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.

Analogically OutputData is Aranea abstraction for response. HttpOutputData being the subinterface and StandardServletOutputData implementation for servlet environments.

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

2.4.1. Extensions

Both InputData and OutputData 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 two 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 is 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 almost always accessed by a single thread, therefore there is rarely any need to think about synchronization. Usually one can assume that there is only one user using the widget at any time and program to service this user without any concern for concurrency. There is only one exception to this: one of the default Widget implementations is BaseApplicationWidget which allows registration of action listeners (see Section 2.7.3, “Action Listeners”) which can be invoked asynchronously when so desired.
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 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 then 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 events from the user to widgets. 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. 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 more than once (or not at all) during one request-response cycle. Typically it is called once for each widget that has a rendering template defined.

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, children and widget Environment.
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. Together with widget's parents identifiers this forms a unique identifier (scope) of widget in the component hierarchy.

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));
}

// This method handles the event that was registered in init().
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.

Actions are handled by the classes extending org.araneaframework.core.ActionListener

public interface ActionListener extends Serializable {
  public void processAction(Object actionId, InputData input, OutputData output) throws Exception;
}

and their registration is analogous to event listeners:

addActionListener("actionId", new SomeActionListener());

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 Environment 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 preprocessing. The BaseApplicationWidget has the following method that may be overridden to allow this processing:

protected void handleUpdate(InputData input) 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.

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.
showMessages(String type, Set<String> messages)Shows messages of type type to the user.
showInfoMessage(String message)Shows an error message to the user.
hideInfoMessage(String message)Hides an info message from user.
showWarningMessage(String message)Shows a warning message to the user.
hideWarningMessage(String message)Hides a warning message from user.
showErrorMessage(String message)Shows an informative message to the user.
hideErrorMessage(String message)Hides an error message from 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.
hideMessage(String type, String message);Removes a message message of type type.
hideMessages(String type, Set<String> messages);Removes messages of type type.
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).
Map<String, Collection> getMessages()Returns all present messages as a Map. Keys of the Map are the different message types encountered so far and under the keys are the messages in a Collection.

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”. For standard JSP tag which renders MessageContext messages to response, see <ui:messages>.

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.
void addLocaleChangeListener(LocaleChangeListener listener);Registers a listener (Component) that will be notified when locale is changed.
boolean removeLocaleChangeListener(LocaleChangeListener listener)Unregisters listener (Component) so that it will not be notified of locale changes anymore. Returns whether the listener was found to be present and actually removed.

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)));
...

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)),
  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);
...

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.

Transitions between the flows are performed by FlowContext.TransitionHandlers.

interface TransitionHandler extends Serializable {
   /**
    * @param eventType FlowContext.START .. FlowContext.RESET
    * @param activeFlow active flow at the moment of transition request
    * @param transition Serializable closure that needs to be executed for transition to happen
    */
   void doTransition(int eventType, Widget activeFlow, Closure transition);
}

After initialization, each flow may set the TransitionHandler which will handle navigation events performed while flow which set the TransitionHandler is active. This can be used to customize navigation logic—i.e. ask for confirmations when navigating away from flow containing unsaved data, restore window scroll position when returning to caller flow or checking for privileges before starting the next flow.

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

2.8.4. PopupWindowContext

Popup windows in Aranea are separate threads that are started using org.araneaframework.http.PopupWindowContext. Popups can be used, for example, to open new widgets or to upload files (using org.araneaframework.http.service.FileDownloaderService). To open a new widget in a popup, the widget must handle the entire page, and its subwidgets may handle certain specific parts of a page. This is similar to how a root widget handles the components in the main thread.

One can access the PopupWindowContext by getting it from the Environment. If it is accessed from a widget that extends BaseUIWidget, the getPopupCtx() method can be used.

Here is an example on how to the server enables the user to download a file:

  PopupWindowContext popupContext = (PopupWindowContext) getEnvironment().getEntry(PopupWindowContext.class);
  popupContext.open(new FileDownloaderService(selectedFile), new PopupWindowProperties(), null);

In the example above, the first parameter is the service that downloads the file to the user's computer, and the second one is the popup window properties. Sometimes one may want to also specify the widget that caused the popup to open. Therefore, the last parameter in the example is the opener, which usually is null, but may be provided as this (the caller widget). (The popup widget can access the opener by PopupWindowContext.getOpener().)

The following is an example from Aranea sample application (SamplePopupWidget) on how to open a popup widget (from a widget that extends BaseUIWidget):

  getPopupCtx().open(
    new LoginAndMenuSelectMessage("Demos.Simple.Simple_Form"), 
    new PopupWindowProperties(), this);

Here it must send a Message to the components that starts new widgets to produce the desired effect. The LoginAndMenuSelectMessage is a SeriesMessage that first uses the flow context from the child environment of the login widget to start a new root context. Then the menu select widget searches the menu widget to select the given menu item. Below are the codes for messages.

The code for the LoginAndMenuSelectMessage:

public class LoginAndMenuSelectMessage extends SeriesMessage {

  public LoginAndMenuSelectMessage(String menuPath) {
    super(new Message[] {
        new LoginMessage(), 
        new MenuSelectMessage(menuPath)});
  }

}

The code for the LoginMessage:

public class LoginMessage extends BroadcastMessage {

  protected void execute(Component component) throws Exception {
    if (component instanceof LoginWidget) {
      LoginWidget loginWidget = (LoginWidget) component;

      Environment childEnv = loginWidget.getChildEnvironment();

      FlowContext flow = (FlowContext) childEnv.getEntry(FlowContext.class);
      flow.replace(new RootWidget(), null);
    }
  }

}

The code for the MenuSelectMessage:

public class MenuSelectMessage extends BroadcastMessage {

  private String menuPath;

  public MenuSelectMessage(String menuPath) {
    this.menuPath = menuPath;
  }

  protected void execute(Component component) throws Exception {
    if (component instanceof MenuWidget) {
      MenuWidget w = (MenuWidget) component;
      w.selectMenuItem(menuPath);
    }
  }

}

MethodDescription
String open(Message startMessage, PopupWindowProperties properties, Widget opener)Uses a message that opens a widget inside a new popup.
String open(Service service, PopupWindowProperties properties, Widget opener)Uses a service that serves the data for a new popup.
String openMounted(String url, PopupWindowProperties properties)Opens the mount URL in a popup.
open(String url, PopupWindowProperties properties)Opens the given URL in a popup.
boolean close(String id) throws ExceptionCloses the popup with given ID (the ID is returend when the popup is created).
Widget getOpener()Provides the popup opener.
Map getPopups()Returns a map with popups (the key is the ID of the popup, and the value is an instance of PopupServiceInfo.

To enable popups at JSP layer, one must also register it inside the system form as the following code snippet does from root.jsp of the Aranea Demo Application:

...
  <ui:body>

    <div id="cont1">
      <ui:systemForm method="POST">
        <ui:register.../>
        <ui:registerPopups/>
...

Standard implementation of PopupWindowContext is described in Section 3.5.9, “Popup Windows Filter”.

2.8.5. OverlayContext

Supports running processes in "overlay" (in parallel FlowContext of the same session thread). It is used to allow construction of modal dialogs and modal processes. To start a process inside overlay, a widget calls one of the getOverlayCtx().start(...) methods.

The getOverlayCtx() method is defined in BaseUIWidget so all sub-classes should be able to access it. Others can retrieve it from the Environment like following: (OverlayContext) getEnvironment().getEntry(OverlayContext.class).

The first time the overlay mode is started, the start(...) must take a container (root) widget as its argument because everything that happens in overlay mode, is happening like in a separate window. For example, the code in Aranea Demo Application creates the overlay root widget and its content(s) like this:

  getOverlayCtx().start(
      new OverlayRootWidget(new ModalDialogDemoWidget(true)));

Notice that the start(...) method is used to start two widgets. The custom-made OverlayRootWidget acts like a root widget, which behind the scenes also specifies a flow container for the child widget (i.e. ModalDialogDemoWidget). Here is the sample code for the OverlayRootWidget:

public class OverlayRootWidget extends BaseUIWidget {

  private Widget child;

  public OverlayRootWidget(Widget child) {
    this.child = child;
  }

  protected void init() throws Exception {
    Assert.notNull(child);
    addWidget("c", new OverlayFlowContainer(child));
    setViewSelector("overlayRoot");
  }

  private class OverlayFlowContainer extends ExceptionHandlingFlowContainerWidget {

    public OverlayFlowContainer(Widget topWidget) {
        super(topWidget);
    }

    protected void renderExceptionHandler(OutputData output, Exception e) throws Exception {
      if (ExceptionUtils.getRootCause(e) != null) {
        putViewDataOnce("rootStackTrace", ExceptionUtils.getFullStackTrace(
            ExceptionUtils.getRootCause(e)));
      }        
      putViewDataOnce("fullStackTrace", ExceptionUtils.getFullStackTrace(e)); 
      ServletUtil.include("/WEB-INF/jsp/menuError.jsp", this, output);
    }

  }

}

OverlayContext provides the replace*, start* and reset* methods that act analogously to FlowContext corresponding methods, but affect only overlayed process. Additionally, following methods are available:

MethodDescription
boolean isOverlayActive()Returns whether some overlayed process is active.
setOverlayOptions(Map options)Sets the presentation options for overlayed processes.
Map getOverlayOptions()Returns the map with current presentation options for overlayed processes.
finish(Object result)Similar to FlowContext.finish(Object obj) but closes the entire OverlayContext not just the last flow widget.
cancel()Similar to FlowContext.cancel() but closes the entire OverlayContext not just the last flow widget.

To make overlay possible on the client-side, one must register it inside the system form as the following code snippet does from root.jsp of the Aranea Demo Application. In addition, the modalbox.css file must also be incorporated to enable the visual part of the overlay mode. In the example below, the file is explicitly defined (it refers to the modalbox.css provided by Aranea), although the necessary styles are also included if there are no attributes specified on the tag.

  ...
  <head>
        ...
        <ui:importStyles file="css/modalbox/modalbox.css" media="screen"/>
        ...
  </head>

  <ui:body>

    <div id="cont1">
      <ui:systemForm method="POST">
        <ui:register.../>
        <ui:registerOverlay/>
...

Notice the <ui:registerOverlay/> tag!

Standard implementation of OverlayContext is described in Section 3.5.19, “Overlay Container”.

2.8.6. MenuContext

Defines the standard methods for menu handlers (contexts). Most custom implementations can extend the org.araneaframework.uilib.core.BaseMenuWidget and its buildMenu() method.

MethodDescription
void selectMenuItem(String menuItemPath)Marks the menu item (identified by given path) as active.
MenuItem getMenu()Provides access to the entire menu.
void setMenu(MenuItem menu)Specifies the menu to use.

All menu items are represented as a tree of org.araneaframework.uilib.core.MenuItem objects that have its own label and a flow (a widget or a flow creator). An entire menu is also a MenuItem and its menus are declared with addSubMenuItem(MenuItem item). A MenuItem may not have a flow, if it represents a sub menu. An example menu might look like this:

  MenuItem menu = new MenuItem();
  demoMenu = menu.addMenuItem(null, new MenuItem("Demo_Menu", DemoWidget.class));
  demoMenu.addMenuItem(new MenuItem("Context_Menus", DemoContextMenuWidget.class));
  demoMenu.addMenuItem(new MenuItem("Easy_AJAX_Update_Regions", EasyAJAXUpdateRegionsWidget.class));
  demoMenu.addMenuItem(new MenuItem("Cooperative_Form", FriendlyUpdateDemoWidget.class));
  ...

To enable the menu widget, the root widget may initialize it. Then the menu can be accessed by view data. A simplified example for JSP (without style information) is below:

<ui:widgetContext id="menu">
  <c:forEach items="${viewData.menu.subMenu}" var="item">
    <c:if test="${item.value.selected}">
      <ui:eventLinkButton eventId="menuSelect" eventParam="${item.value.label}" labelId="${item.value.label}" styleClass="active"/>
    </c:if>

    <c:if test="${not item.value.selected}">
      <ui:eventLinkButton eventId="menuSelect" eventParam="${item.value.label}" labelId="${item.value.label}"/>
    </c:if>
  </c:forEach>
</ui:widgetContext>

2.8.7. ConfirmationContext

Aranea standard component chain enriches Environment with a context called ConfirmationContext. This can be used for executing some code conditionally, depending on user actions. Context interface is simple and consists of following methods:

public interface ConfirmationContext extends Serializable {

  void confirm(Closure onConfirmClosure, String message);

  String getConfirmationMessage();

}

There the org.apache.commons.collections.Closure is a simple interface to encapsulate business logic:

public interface Closure {

  public void execute(java.lang.Object input);

}

The input param is null for transitions handlers.

When confirmation is registered (with the confirm(...) method), rendering mechanism will present end-user with the browser standard message box (on page load) and ask for confirmation of requested action. Depending on users choice, action encapsulated in the onConfirmClosure param either will get executed or not.

Combined with FlowContext.TransitionHandler, confirmation could be asked whenever the user performs navigation that would make active flow unreachable and flow contains data that has not yet been saved.

getFlowCtx().setTransitionHandler(
  new CancelConfirmingTransitionHandler(
      new ShouldConfirmOnUnsavedData(),
      "Some data not saved yet. Continue anyway?"));

Here CancelConfirmingTransitionHandler (provided by Aranea) registers the confirmation whenever FlowContext.cancel() is called from active flow and org.apache.commons.collections.Predicate (that is used to check the custom condition before executing the event) ShouldConfirmOnUnsavedData (not provided by Aranea) evaluates to true. Only after the user confirms the navigation, the event will allow flow transition to be actually be performed.

ConfirmationContext and TransitionHandlers together are a reliable and convenient way of preventing end-users shooting themselves in the foot.

2.8.8. ManagedServiceContext, ThreadContext, and TopServiceContext

This section describes the contexts that are at the core of request handling.

org.araneaframework.framework.ManagedServiceContext represents a context that handles the requests of different threads (windows) in one session. The basic idea is that it routes requests to the right services. Here's an overview of the interface:

package org.araneaframework.framework;

public interface ManagedServiceContext extends Serializable {

  public Object getCurrentId();

  public Service addService(Object id, Service service);

  public Service addService(Object id, Service service, Long timeToLive);

  public Service getService(Object id);

  public void close(Object id);

}

org.araneaframework.framework.ThreadContext represents a context that makes popups possible. Without it the user would have the same session in both windows, because Aranea application has a state. ThreadContext, however, provides means to create a new (distinct) thread in the session. A proof that a ThreadContext is running in a web application is the fact that you can find something like this in the source code of a page:

<input name="araThreadServiceId" type="hidden" value="mainThread"/>

It means that the next request (submit) made will be bound to the mainThread.

org.araneaframework.framework.TopServiceContext further specifies the ManagedServiceContext by being the top-most (and thus accessible by all users), though it works like ThreadContext. The difference, however, lies in the fact that TopServiceContext is not session based. Therefore, it would handle threads when, for example, the user has not logged in. And one may see it in their Aranea application page as following:

<input name="araTopServiceId" type="hidden" value="application"/>

ThreadContext and TopServiceContext do not introduce new methods compared to ManagedServiceContext.