I. Partage d'état versus système d'évènements▲
Il y a deux façons de communiquer avec la JSR 286 : le partage
d'état et les évènements. Nous ne détaillerons que la deuxième
solution dans ce document.
Nous exposons dans cette première section
les principes de ces deux
solutions. Nous justifierons pourquoi les
évènements nous semblent
plus intéressants.
Le partage d'état :
chaque portlet déclare les paramètres qu'il partage avec les autres
portlets (ceux qu'il met à disposition et ceux auxquels il veut
accéder). Au moment où un portlet effectue son rendu, il rend ses
paramètres visibles et peut accéder à ceux des autres.
Les évènements :
chaque portlet déclare les évènements qu'il lève et les évènements
qu'il écoute. Quand un évènement est levé, les portlets écouteurs
traitent l'évènement levé.
Discussion :
dans un cas où les paramètres échangés ne servent qu'à l'affichage,
la première solution est suffisante. Cette solution est facile à
mettre en place mais ne convient que pour des portlets fortement
liés. Avec la seconde solution par contre, le système est proche de
celui des
events
et des
listeners
. Du coup, chaque portlet écouteur a nécessairement un comportement
par défaut au cas où l'évènement qu'il écoute n'est pas lié. Les
portlets sont donc beaucoup moins dépendants.
La seconde solution me paraît beaucoup plus intéressante et baucoup
plus proche d'une vision composant. Nous allons maintenant montrer
par l'exemple comment mettre cette solution en place.
II. Systéme d'évènement, en pratique▲
En pratique comment ça marche ?
L'exemple que je vous propose est très basique mais suffisant pour
exposer le fonctionnement des évènements. Nous allons développer
deux portlets : PortletA et PortletB.
Nous choisissons de faire deux projets distincts et deux war
distincts pour bien montrer que dans le cas de la communication par
évènements, les portlets sont indépendants.
Le PortletA sera l'écouteur, le PortletB lèvera l'évènement.
Un évènement est identifié par un nom. Le nom est de type String.
L'évènement peut prendre une valeur, celle-ci étant aussi de type
String. Au final, on peut passer tout objet Serializable par
évènement d'un portlet à l'autre. Dans l'exemple, nous nous
contenterons d'une simple String.
Le fonctionnement du système d'évènements en portlet rappelle le
système des
events
et des
listeners
classiques en java. Mais il ressemble aussi au système de passage de
paramètres des requêtes HTTP.
Erreurs à éviter :
- ne pas utiliser la bonne librairie pour les portlets. La communication inter-portlet, c'est la JSR 286, c'est la lib portlet 2.0 ;
-
ne pas utiliser le bon taglib dans ses JSP. Encore une fois, on a
besoin de portlet2.0.
Sélectionnez
<%@ taglib
uri
=
"http://java.sun.com/portlet_2_0"
prefix
=
"portlet"
%>
-
ne pas déclarer dans les descripteurs que l'on fait des portlets
2.0. Le noeud portlet-app du fichier portlet.xml devrait être :
Sélectionnez
<portlet-app
xmlns
=
"http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
version
=
"2.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
>
Un lien pour récupérer la lib portlet2.0.
Chaque Portlet va devoir déclarer les paramètres qu'il léve et ceux
qu'il écoute.
Cette déclaration se fait par le biais d'un noeud <event-definition>
pour déclarer l'évènement (son nom et son type) et par le biais de
noeuds <supported-publishing-event>
pour déclarer l'évenement levé et <supported-processing-event>
pour déclarer l'évènement écouté.
Dans notre exemple, l'évènement s'appelle totoString.
Les descripteurs (portlet.xml) des portlets A et B deviennent :
portletA
portlet.xml :
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app
xmlns
=
"http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
version
=
"2.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd
http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
>
<portlet>
<portlet-name>
PortletA</portlet-name>
<portlet-class>
tuto.portlet.PortletA</portlet-class>
<supports>
<mime-type>
text/html</mime-type>
<portlet-mode>
VIEW</portlet-mode>
</supports>
<portlet-info>
<title>
Recevra des infos de Portlet B</title>
</portlet-info>
<supported-processing-event>
<qname>
totoString</qname>
</supported-processing-event>
</portlet>
<event-definition>
<qname>
totoString</qname>
<value-type>
java.lang.String</value-type>
</event-definition>
</portlet-app>
portletB
portlet.xml :
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app
xmlns
=
"http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
version
=
"2.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd
http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
>
<portlet>
<portlet-name>
PortletB</portlet-name>
<portlet-class>
tuto.portlet.PortletB</portlet-class>
<supports>
<mime-type>
text/html</mime-type>
<portlet-mode>
VIEW</portlet-mode>
</supports>
<portlet-info>
<title>
Enverra des infos à Portlet A</title>
</portlet-info>
<supported-publishing-event>
<qname>
totoString</qname>
</supported-publishing-event>
</portlet>
<event-definition>
<qname>
totoString</qname>
<value-type>
java.lang.String</value-type>
</event-definition>
</portlet-app>
Au niveau du code des portlets maintenant. La PortletA va simplement
afficher HelloWorld par défaut et afficher la valeur de l'évènement
totoString si celui-ci est levé.
Ce qui donne pour le code du PortletA :
public
class
PortletA extends
GenericPortlet implements
EventPortlet {
protected
void
doView
(
RenderRequest rRequest, RenderResponse rResponse) throws
IOException, PortletException
{
if
(
rRequest.getParameter
(
"stringEvent"
)!=
null
)//Action avec Event
{
rResponse.setContentType
(
"text/html"
);
PrintWriter writer =
rResponse.getWriter
(
);
writer.write
(
"Event value of totoString : "
+
rRequest.getParameter
(
"stringEvent"
));
writer.close
(
);
}
else
//Action sans Event
{
rResponse.setContentType
(
"text/html"
);
PortletRequestDispatcher prd;
prd =
getPortletContext
(
).getRequestDispatcher
(
rResponse.encodeURL
(
"/WEB-INF/jsp/portletA.jsp"
));
prd.include
(
rRequest, rResponse);
}
}
public
void
processEvent
(
EventRequest request, EventResponse response) {
Event event =
request.getEvent
(
);
String _toto =
"vide"
;
if
(
event.getName
(
).equals
(
"totoString"
)) {
// traitement de l'event
_toto =
(
String) event.getValue
(
);
response.setRenderParameter
(
"stringEvent"
, _toto);
}
}
}
Vous observerez l'utilisation d'une méthode appelée processEvent.
Celle-ci est appelée automatiquement si un évènement est levé. Il
faut par contre tester dans cette méthode quel est le nom de
l'event.
Dans mon cas, j'ai besoin de passer la valeur de l'event pour le
rendu. Donc je crée un paramètre que je nomme ici stringEvent. Et
dans ma méthode doView j'utilise effectivement la valeur de ce
paramètre pour l'affichage.
Je ne copie pas le contenu de portletA.jsp qui ne fait qu'afficher
HelloWorld. Vous le trouverez dans les sources liées à ce tutoriel.
Passons au PortletB maintenant. Le PortletB va lever l'évènement
totoString quand l'utilisateur cliquera sur un lien.
J'utilise la méthode processAction pour traiter le clic sur le lien
et cette méthode appelle la méthode sendEvent.
public
class
PortletB extends
GenericPortlet {
protected
void
doView
(
RenderRequest rRequest, RenderResponse rResponse)
throws
IOException, PortletException {
rResponse.setContentType
(
"text/html"
);
PortletRequestDispatcher prd;
prd =
getPortletContext
(
).getRequestDispatcher
(
rResponse.encodeURL
(
"/WEB-INF/jsp/portletB.jsp"
));
prd.include
(
rRequest, rResponse);
}
public
void
processAction
(
ActionRequest request, ActionResponse response)
throws
PortletException, IOException {
System.out.println
(
"processAction"
);
this
.sendEvent
(
response);
}
public
void
sendEvent
(
ActionResponse response)
{
String _toto =
"passée en event"
;
response.setEvent
(
"totoString"
, _toto);
System.out.println
(
"envoi Event toto"
);
}
}
Le contenu de portletB.jsp ne fait qu'afficher le lien cliquable
déclenchant l'action du portlet. Vous retrouverez ce fichier dans
les sources liées à ce tutoriel.
III. Une fois déployé dans JBoss Portal▲
Les deux portlets sont déployés dans JBoss Portal. Pour mes tests,
je mets les deux portlets dans une même page l'un en dessous de
l'autre. Ce qui donne ceci :
Si l'on clique sur le lien, on observera que uniquement le portletA
est rafraîchit. Ce qui donne au final :
IV. Passer un objet en évènement▲
Et la suite? Et bien la suite c'est d'être capable de transmettre autre chose que des String en évènement. En fait, on voudrait passer des objets. Il n'y a pas grand chose à faire. Il suffit de respecter les deux points suivants :
- Que la classe des objets à passer en évènement soit serializable.
- Que la classe soit annotée en utilisant l'annotation @XmlRootElement de JAXB .
import
java.io.Serializable;
import
javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public
class
User implements
Serializable
{
}
Lien pour récupérer la lib JAXB.
Bien sur, il faut toujours respecter ce qui est dit précédement, à savoir implémenter la méthode sendEvent d'un côté, la méthode processEvent de l'autre et déclarer l'évènement dans portlet.xml.
V. A lire▲
Un ensemble de cours sur developpez au sujet des portlets. Le portail de discussion sur les portlets et les conteneurs de portlets de developpez.
VI. Remerciements▲
Je tiens à remercier Kraft et Fabien qui m'accompagnent dans la rédaction de la plupart de mes tutoriels. Ce sont mes premiers testeurs. Ainsi que la communauté Developpez pour les relectures et corrections d'ortographe, en particulier : Baptiste Wicht, furr, jacques_jean, pottiez, caro-line. Je remercie tout particuliérement Ricky81 pour m'avoir proposé de devenir rédac Developpez et m'avoir encouragé à aller au bout de la rédaction de ce tutoriel.