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”.
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.
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.
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 {}
}
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:
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.
propagate()
) should be done when a component is alive, initialized and enabled.
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.
enable()
—notifies the component that it has been enabled again. This call may only be done after a
disable()
call.
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
Component
—
org.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 :).
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 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).
org.araneaframework.Environment
is another
important concept that represents the way Component
s
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
Component
s may have completely different
Environment
s.
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.
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 FormWidget
s 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.
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”.
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 String
s. 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.
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()) {
//...
}
Both HttpServletRequest
and
HttpServletResponse
are available as
InputData
and OutputData
extensions respectively.
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
:
Method | Description |
---|---|
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. |
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:
Method | Description |
---|---|
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
:
Method | Description |
---|---|
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. |
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
Service
—
org.araneaframework.core.BaseService
and a base class
for application development
org.araneaframework.core.BaseApplicationService
.
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.
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:
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.
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:
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.
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.
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
Widget
—org.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”.
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.
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
.
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.
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.
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).
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")
.
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(String eventId, InputData input) throws Exception;
}
The event listeners are registered as following:
addEventListener("myEvent", new EventListener() {
public void processEvent(String 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(String eventId, String eventParam, InputData input) throws Exception;
log.debug("Received event " + eventId + " with parameter " + parameter);
}}
In case you want to receive multiple event parameters, you may choose to implement
MultiParamEventListener
. The default separator used to split up the values
is a semi-comma. Here's an example:
addEventListener("myEvent", new MultiParamEventListener() {
public void processEvent(String eventId, String[] eventParams, InputData input) throws Exception;
log.debug("Received event " + eventId + " with " + eventParams.length + " parameter(s).");
}}
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);
}
// In case of receiving multiple parameters, you might prefer this:
public void handleEventMyEvent(String[] parameters) {
log.debug("Received event myEvent with " + parameters.length + " parameter(s).");
}
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. First, the proxy event listener tries to call
handleEvent<Event>()
,
then handleEvent<Event>(String param)
, and finally
handleEvent<Event>(String[] params)
, whichever succeeds first
(after the first event handler is found and finishes its work successfully, then the latter
ones are not called).
In the case of String[]
parameters, the array contains the parameter
value split up into array. The default value separator is a semi-comma, but you can override
it by adding a custom method getParameterSeparator(String value)
(also
supported by MultiParamEventListener
), which
should return a separator of the value. For, example you can provide the event param as
"123;456;789;"
, and with the array parameter type they would be provided as
new String[] { "123", "456", "789" }
.
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.
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(String actionId, InputData input, OutputData output) throws Exception;
}
and their registration is analogous to event listeners:
addActionListener("actionId", new SomeActionListener());
Similarily to event listeners, there are also two default implementations of
ActionListener
: StandardActionListener
and
MultiParamActionListener
. They are used like corresponding event listeners
except that ActionListeners are also provided with OutputData
to handle
response.
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”
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.
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”
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.
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");
}
}
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.
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:
Method | Description |
---|---|
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. |
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>.
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:
Method | Description |
---|---|
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”.
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.
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.TransitionHandler
s.
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”
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. (Since PopupWindowContext
is mainly used inside
BaseUIWidget
then the examples below will use the
getPopupCtx()
approach.)
Method | Description |
---|---|
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 Exception | Closes 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 like the following code example does:
...
<ui:body>
...
<ui:systemForm method="post">
...
<ui:registerPopups/>
...
</ui:systemForm>
...
</ui:body>
...
Standard implementation of PopupWindowContext
is described in
Section 3.5.9, “Popup Windows Filter”.
Here is an example on how the server component can provide file downloading operation:
getPopupCtx().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()
.)
Another way to provide the file downloading option is without a page load: the AJAX
solution. To make it work, one would have to register a
org.araneaframework.http.core.FileDownloadActionListener
as an
action listener in widget. The latter expects the popup window context and the file
to download or a callback to provide the file info. Since mostly one needs to
provide the file once it is requested, the following example demonstrates the
callback solution on the server side in a widget:
public void init() throws Exception {
// ... other init() code here ...
addActionListener("download", new FileDownloadActionListener(
getPopupCtx(), new DemoFileHandler()));
}
// Our custom implementation handler to provide file data to FileDownloadActionListener:
private class DemoFileHandler extends FileDownloadActionListener.FileDownloadHandler {
protected void prepareServiceData(String actionId, String actionParam, InputData input,
OutputData output) {
// The action parameter, in this case, is a list row ID.
if (actionParam != null) {
FileInfo file = (FileInfo) list.getRowFromRequestId(actionParam);
this.readData(file);
}
}
}
As you may notice, the listener is registered with the ID "download
"
(which is later used to invoke the listener), and the listener uses action parameter to
return the correct file from a ListWidget
. Here the file info is
stored as a FileInfo
object, but usually one has just bytes and the
file name. In the latter case, a simple solution is just to set the file data as
protected void prepareServiceData(String actionId, String actionParam,
InputData input, OutputData output) {
byte[] fileBytes = null;
// ... Retrieve the data ...
// Store the data in variables (you don't have to define the class variables):
this.file = fileBytes;
this.fileName = "demo.pdf";
this.contentType = "application/pdf";
}
The FileDownloadActionListener
contains also some other methods for
some specific purposes. Check the JavaDoc for that, if you want to customize the default
logic of that class.
The second part of AJAX file downloading is the client part invoking that file downloading action listener, which is quite easy:
<a href="javascript:false"
onclick="return AraneaPage.downloadFile('download','${widgetId}','${rowRequestId}');">
Click me to download the PDF!
</a>
So the main work is handled by AraneaPage.downloadFile
, which takes
the action listener ID, the widget path of the containing action listener, and an
optional parameter for the action listener as parameters. If the file downloading does
not work then always check first the onclick
value to make sure that
it really has correct request values!
However, the code above is not all that you need to know. The remaining part is
what happens when the request is done? The request callback that is used, is named
AraneaPage.fileDownloadActionCallback(transport)
. The default
implementation checks that result is not equal to "error
". If so then
it creates an invisible a
element, which points to the URL returned by
the file downloading action listener, and which is clicked . The value "error
"
is returned in case something goes wrong. To create a custom solution (for
response handling) replacing the default one, just override that method.
Another aspect that should be acknowledged is the file downloading action listener part,
especially how the URL is generated and returned. As it is shown above, the
FileDownloadActionListener
requires a PopupWindowContext
that is used to open a popup window to download the specified file in a separate thread.
The threads are closed when the next request is sent to the server. Therefore, the URL
returned by the action listener is actually the URL of the popup (that is different
with each request because with every request a new popup is opened).
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);
}
}
}
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:
Method | Description |
---|---|
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”.
Defines the standard methods for menu handlers (contexts). Most custom implementations
can extend the org.araneaframework.uilib.core.BaseMenuWidget
and
its buildMenu()
method.
Method | Description |
---|---|
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>
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.
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
.