blob: 560a82df3a9ffebba7b129f10b5293e735b887b5 [file] [log] [blame]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Portal: Event Handling</title>
<link href="http://purl.org/DC/elements/1.0/" rel="schema.DC">
<meta content="Overview" name="DC.Subject">
<meta content="Carsten Ziegeler" name="DC.Creator">
</head>
<body>
<h1>Overview</h1>
<p>
This document gives an overview of the event handling of the portal engine.
</p>
<p>
The sample portal that comes with the Cocoon distribution contains several
working samples for event handling.
</p>
<h1>Introduction</h1>
<p>
The event handling is a central mechanism used in the portal engine. Every
change (changes in status or layout, links etc) is propagated through an
event. The portal uses the publisher/subscribe paradigm, so each component
that is interested in a specific event can subscribe itself for this type
of event. In addition each component can send out events.
</p>
<p>
The processing of a portal request (a request send to the Cocoon portal)
is divided into two phases: event handling and rendering. In the first phase
all events are processed. For example if the user clicks a link this
triggers an event that is published. Any receiver of this event might
in turn fire new events that are published as well.
</p>
<p>
When all events are processed, the first phase is finished and the second
phase, the rendering, is started. At this point of time all event handling
and all information exchange should be finished.
</p>
<h1>Events and the request/response cycle</h1>
<p>
In the Portal, an event is represented by a Java object. This event object
contains all necessary information to process the event. So, in most cases an
event contains the object to modify, what to modify and the value to set.
For example, the minimize event for minimizing a coplet, contains the
coplet, the information to change the window state and the value "minimize"
to set.
</p>
<p>
There are different types of events: a type for changing the window state,
a type for removing a coplet, a type for links that are clicked by the user etc.
Each event type is represented by a Java class (or interface).
</p>
<p>
A component that processes for example the window state request is subscribed
to this minimize event (or: the corresponding class/interface)
and when such an event is fired, it changes the window state of the
coplet to minimize. Every data this component needs is stored in
the event. This is a very important detail: the event is not directly processed
(in this case) by the object that is changed (the coplet) but by a central
subscribed component that changes the coplet. This is because of the
publisher/subscribe mechanism used: many components in the portal can subscribe
to the same event type if they are interested. So, each component that is
interested in an event needs all information about this event. That's why
all data is stored in the event itself.
</p>
<p>
Let's have a look how such an event is created:
</p>
<pre class="code">
Event event;
event = new ChangeCopletInstanceAspectDataEvent(
copletInstanceData,
"size",
SizingStatus.STATUS_MINIMIZEDD);
</pre>
<p>
<em>Event</em> is just a marker interface, the concrete implementation
<em>ChangeCopletInstanceAspectDataEvent</em> implements this interface
and requires three pieces of information: the CopletInstanceData,
the information about what to change (size) and the new value.
</p>
<p>
All events must implement the marker interface <em>Event</em>, so if
a component is interested in all Events it could subscribe itself
using this event type.
</p>
<p>
If you want to fire an event, you have to publish it. Therefore you
need the event manager, a central portal component. You can lookup this
component, fire the event and release the manager again. If you fire
the event, the event is directly published to all subscribed components.
</p>
<pre class="code">
EventManager manager = null;
try {
manager = serviceManager.lookup(EventManager.ROLE);
manager.getPublisher().publish(event);
} finally {
serviceManager.release(manager);
}
</pre>
<p>
As noted above, the event will be directly fired. But usually in a portal
application, events are not fired directly but are invoked by some user
action. This means, the user clicks on a link in the browser, the
request is targetted at Cocoon and the portal invokes (fires) the correct events.
</p>
<p>
For this, a link (or a form action) must know, which event it should
fire, if it is clicked. So, in other words, a link is associated with
a concrete event. But on the other site, an event is a Java object and we can only use
strings in URLs. So how does this work?
</p>
<p>
The part of the portal that generates the link, creates the event object
with all necessary data, transforms this event into a usable URI and this
URI is the target of the link. When the user clicks on this link, the portal
transforms the URI back into the Java event object and fires the event.
</p>
<p>
The transformation Event-&gt;URI-&gt;Event is done by another portal component,
the link service. Most portal components (apart from the event manager)
are available through another central component, the portal service. So
you need to have access to the portal service component. (Renderers e.g.
don't have to lookup the service by itself, they get it as a method
parameter).
</p>
<pre class="code">
PortalService service = null;
try {
service = serviceManager.lookup(PortalService.ROLE);
LinkService ls = service.getComponentManager().getLinkService();
String uri = getLinkURI( event );
// create a link that references the uri
} finally {
serviceManager.release(service);
}
</pre>
<p>
That's all you have to do: create the event object, get the link service,
transform the event into a URI using the service and then create the
(html) link using the URI. Everything else is handled by the portal for you.
</p>
<p>
In addition, you can transform several events into one single link. So
you can create links, the user can click, that do several things at
the same time (minimizing one coplet and maximizing another one etc.).
The link service offers corresponding methods for this.
</p>
<h1>Changing the State of a Coplet</h1>
<p>
In most cases you want to change the state of a coplet because the user
performed an action. The portal engine provides you with some events
that you can directly use.
</p>
<ul>
<li>CopletJXPathEvent</li>
<li>ChangeCopletInstanceAspectDataEvent</li>
</ul>
<p>
The <em>CopletJXPathEvent</em> requires again three pieces of information:
the coplet instance data to change, the JXPath expression that defines the
data to change and the value:
</p>
<pre class="code">
Event event = new CopletJXPathEvent(copletInstanceData,
"attributes/username",
username);
</pre>
<p>
In the previous chapter, we already saw an example of the usage of the
<em>ChangeCopletInstanceAspectDataEvent</em>.
</p>
<p>
It is of course possible that you write your own events for
changing the state of a coplet. But in this case make sure that
your own event implements the interface <em>CopletInstanceEvent</em>.
This helps the portal engine in tracking if a coplet has been changed.
</p>
<h1>Subscribing to Events</h1>
<p>
If you are interested in events, you can subscribe to a specific event
type. As events are Java objects, you subscribe for all events of a
specific interface or class (and all of the subclasses).
Subscribing is done using the event manager:
</p>
<pre class="code">
EventManager manager = null;
try {
manager = serviceManager.lookup(EventManager.ROLE);
manager.getRegister().subscribe( myComponent );
} finally {
serviceManager.release(manager);
}
</pre>
<p>
The component you subscribe must implement the Subscriber interface:
</p>
<pre class="code">
Subscriber interface:
public Class getEventType();
public void inform( Event event );
</pre>
<p>
The getEventType() method returns the class/interfaces of the events
the component is interested and each time such an event occurs,
the inform() method is invoked.
</p>
<p>
For example one central component in the portal subscribes for
all events dealing with coplets, so it returns <em>CopletInstanceEvent</em>
as the class (interface) in getEventType().
</p>
<h1>Inter Coplet Communication</h1>
<p>
A very interesting feature of the portal is inter-coplet communication.
The demo portal already has a simple sample where the name of an
image selected in an image gallery is transfered to a different coplet.
</p>
<p>
Now, there is only one (minor) problem: in the cocoon portal coplets
(or more precisly CopletInstanceData objects) are not components but
just data objects. So, a coplet can't directly register itself as
a subcriber for events.
</p>
<p>
Remember that we mentioned earlier on a central component that processes
the change events for coplets? So, this is basically one possibility: if
you want to pass information from one coplet to another one, create
a CopletJXPathEvent and pass the information to the other coplet.
</p>
<p>
Imagine a form coplet where the user can enter a city. When this form is
processed by the form coplet, it can generate one (or more) CopletJXPathEvents
and push the entered city information to a weather coplet and a hotel guide
coplet. So, these two coplets display the information about the selected
city.
</p>
<h1>The Coplet Transformer</h1>
<p>
Apart from the possibility to create events from within your Java code,
it's also possible to create events from within a pipeline by using
for example the coplet transformer. It listens for elements with the
namespace "http://apache.org/cocoon/portal/coplet/1.0".
</p>
<h2>The coplet element</h2>
<p>
The coplet element has nothing to do with events :) It can be used
to include information about the current coplet in the SAX stream:
</p>
<pre class="code">
...
&lt;coplet:coplet select="attributes/name"/&gt;
...
</pre>
<p>
The coplet element can only be used inside a coplet pipeline, but
not in the main portal pipeline. The select attribute defines an
JXPath expression that is used to fetch the value that is included
in the stream.
</p>
<h2>The link element</h2>
<p>
The link element creates a link that will trigger an event if the
user clicks this link:
</p>
<pre class="code">
...
&lt;coplet:link coplet="COPLET_ID" path="JXPATH" value="TO_SET"/&gt;
&lt;coplet:link layout="LAYOUT_ID" path="JXPATH" value="TO_SET"/&gt;
...
</pre>
<p>
This element generates an HTML link which will eiter trigger an
event to change a coplet instance data or a layout based on
the JXPath and the value provided.
</p>
<h1>Configuring Subscribers</h1>
<p>
In the previous chapters we saw one possibility to subscribe: dynamically
in some Java code. This requires that - of course - this code is executed
at some point of time. This is a solution for dynamic subscribers, which
means a subscriber that is only "available" if a specific feature of
the portal is used. If the feature is available, the "feature" subscribes
itself (or another component).
</p>
<p>
However, this adds an exta burdon to the development of own events and
their subscribers. Therefore it is possible to configure subscribers
in the cocoon.xconf. These subscribers are instantiated by the
portal engine on startup of Cocoon and subscribed by the portal
engine.
</p>
<p>
You have two possibilites, you can either subscribe Avalon components or
classes. In the first case, you configure the role of the component.
Then the portal engine looks up this component and subscribes it.
</p>
<p>
If you configure a class, the portal engine creates an instance of this
class using the no-argument constructor and subscribes this instance.
For convenience, this instance can implement the Avalon lifecycle
interface like LogEnabled or Serviceable.
</p>
<p>
The configuration takes place in the cocoon.xconf as a configuration for
the event manager:
</p>
<pre class="code">
...
&lt;component class="org.apache.cocoon.portal.event.impl.DefaultEventManager"
logger="portal"
role="org.apache.cocoon.portal.event.EventManager"&gt;
...
&lt;!-- add a new instance of each class as a subscriber: --&gt;
&lt;subscriber-classes&gt;
&lt;class name="org.apache.cocoon.portal.event.subscriber.impl.DefaultJXPathEventSubscriber"/&gt;
&lt;/subscriber-classes&gt;
&lt;!-- add each component as a subscriber (the component should be thread safe): --&gt;
&lt;subscriber-roles&gt;
&lt;role name="org.apache.cocoon.portal.samples.location.LocationEventSubscriber"/&gt;
&lt;/subscriber-roles&gt;
&lt;/component&gt;
...
</pre>
<p>
In the sample configuration above, one class is subscribed (the
<em>DefaultJXPathEventSubscriber</em>) and one Avalon component
(the <em>LocationEventSubscriber</em>).
</p>
<p>
So, if you write your own events and your own subscribers you can either dynamically
add them during execution or statically add them by configuration as shown above.
</p>
<h1>Further Information</h1>
<p>
The event.impl package contains all currently processed events, so you can
study the events and see how to create them. In general most events are
created inside the renderers, especially the renderer aspects that render
specific details (e.g. the sizing buttons for a coplet). So, you can have
a look at the code as well.
</p>
<p>
There are several transformers that help in creating events inside a Cocoon
pipeline. For example the <em>coplet transformer</em> can be used to
create links that contain events to change the status of a coplet or a layout
object. The gallery sample uses this transformer as a demo.
</p>
</body>
</html>