<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Akrogen Blog</title>
	<atom:link href="https://blog.developpez.com/akrogen/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.developpez.com/akrogen</link>
	<description></description>
	<lastBuildDate>Sat, 14 Nov 2009 22:06:24 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.42</generator>
	<item>
		<title>Conception d&#8217;un client Eclipse RCP et serveur OSGI avec Spring DM [step5]</title>
		<link>https://blog.developpez.com/akrogen/p8244/plugin-eclipse/rcp_springdm_step5</link>
		<comments>https://blog.developpez.com/akrogen/p8244/plugin-eclipse/rcp_springdm_step5#comments</comments>
		<pubDate>Thu, 29 Oct 2009 08:14:23 +0000</pubDate>
		<dc:creator><![CDATA[pascal.leclercq]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Vous pouvez trouver ce billet sur mon nouveau blog avec les informations mises a jour. Dans le billet précédant [step4] nous avons utilisé le registre de services OSGi pour consommer/fournir le service UserService. Nous avons montré que l&#8217;utilisation du registre de services OSGI, permettait de rendre opérationnel le lancement/arrêt du bundle org.akrogen.gestcv.services qui fournit le service UserService : lorsque le bundle service org.akrogen.gestcv.services est arrêté, le bundle client org.akrogen.gestcv.simpleosgiclient qui souhaite consommer le service UserService, [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Vous pouvez trouver <a href="http://angelozerr.wordpress.com/2009/11/11/rcp_springdm-step5" >ce billet sur mon nouveau blog</a> avec les informations mises a jour.</p>
<p>Dans le <a href="http://blog.developpez.com/akrogen/p8157/plugin-eclipse/rcp-springdm-step4/" >billet précédant [step4]</a> nous avons utilisé le registre de services OSGi pour consommer/fournir le service UserService. Nous avons montré que l&rsquo;utilisation du registre de services OSGI, permettait de rendre opérationnel le lancement/arrêt du bundle <b>org.akrogen.gestcv.services</b> qui fournit le service UserService : </p>
<ul>
<li>lorsque le bundle service <b>org.akrogen.gestcv.services</b> est arrêté, le bundle client <b>org.akrogen.gestcv.simpleosgiclient</b> qui souhaite consommer le service UserService, récupère une instance null.
  </li>
<li>lorsque le bundle service <b>org.akrogen.gestcv.services</b> est lancé, le bundle client <b>org.akrogen.gestcv.simpleosgiclient</b> qui souhaite consommer le service UserService, récupère l&rsquo;instance UserService fournit par le bundle services.
  </li>
</ul>
<p>Dans ce billet je vais expliquer 2 <b>&laquo;&nbsp;bonnes pratiques&nbsp;&raquo; à suivre dans les Bundle OSGi</b> : </p>
<ul>
<li><b>scinder les bundles services en 2 bundles services API et Implémentation</b>. Dans notre cas nous scinderons le bundle <b>org.akrogen.gestcv.services</b> en <a href="#apiImplBundleServices" >2 bundles services API et Implémentation</a>.
  </li>
<li><b>favoriser la gestion des dépendances entre bundles avec <a href="#ImportPackage" >Import Package</a></b> au lieu de <a href="#RequireBundle" >Require Bundle</a>, qui sera expliqué plus en détail dans la section <a href="#ImportPackage" >Import Package</a>.
  </li>
</ul>
<p>Voici un schéma de ce que nous allons effectuer dans ce billet : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_Step5Overview.png" width="593" height="617" alt="" /></p>
<p>Ce schéma montre que : </p>
<ul>
<li>le bundle service <b>org.akrogen.gestcv.services</b> est <a href="#apiImplBundleServices">scindé en 2 bundles service API et Implémentation</a>.</li>
<li>les <b>dépendances entres bundles</b> sont gérées via <a href="#ImportPackage" >Import Package</a> (et plus par <a href="#RequireBundle" >Require Bundle</a>).
  </li>
</ul>
<p><span id="more-66"></span></p>
<h1 id="ServicesVersionBundle" >Services &amp; version bundle</h1>
<h2>Livraison &amp; Bugs</h2>
<p>Ici nous allons décrire un scénario classique de livraison d&rsquo;une application. L&rsquo;application livrée est installée et tous les utilisateurs se connectent dessus. Malheureusement le <b>distribuable contient un service qui est buggé</b>. </p>
<h3>JEE &#8211; WAR, EAR</h3>
<p>Dans le cas de <b>JEE, un distribuable est sous forme de WAR et EAR</b>, et ceci pose les problèmes suivants : </p>
<ul>
<li>la <b>correction du bug engendre la livraison d&rsquo;un nouveau distribuable WAR ou EAR</b>. Autrement dit l&rsquo;<b>ensemble de l&rsquo;application doit être relivrée</b>. Il n&rsquo;est pas possible de livrer un petit distribuable qui contiendrait uniquement la partie service corrigée. Packager un EAR peut devenir extrèmement long et coûteux ce qui rend la correction d&rsquo;un EAR buggé extrêmement lente (éventuellement plusieurs jours).
  </li>
<li>l&rsquo;<b>installation du nouveau distribuable est tres lourde et engendre l&rsquo;arrêt du serveur</b>. Tous les utilisateurs seront pénalisés uniquement pour corriger l&rsquo;utilisation d&rsquo;un service (et il se peut que la plupart des utilisateurs n&rsquo;y aient même pas accès).
  </li>
</ul>
<h3>OSGi &#8211; JAR</h3>
<p>Dans le cas de <b>OSGi, un distribuable est sous forme de un ou plusieurs JAR par Bundle OSGi</b> : </p>
<ul>
<li>La <b>livraison d&rsquo;un JAR (Bundle) peut suffire pour corriger le problème</b>.</li>
<li>l&rsquo;<b>installation du nouveau distribuable (JAR) est très légère</b> (elle s&rsquo;effectue via la console OSGi par la commande install) et peut s&rsquo;<b>effectuer à chaud</b>. Ce &laquo;&nbsp;patch&nbsp;&raquo; est transparent pour les utilisateurs connectés.</li>
</ul>
<p>Un Bundle OSGi est identifié par son ID (Bundle-SymbolicName) et sa version (Bundle-Version). Le &laquo;&nbsp;patch&nbsp;&raquo; à livrer consiste à créer un bundle qui corrige le bug (du service) en incrémentant sa version. Il est ensuite possible de lancer un même Bundle (même ID Bundle-SymbolicName) avec des versions différentes. C&rsquo;est que je souhaite montrer dans cette section.</p>
<h2>Bug UserService</h2>
<p>Nous allons simuler une erreur dans le service UserServiceImpl du Bundle <b>org.akrogen.gestcv.services</b> de version <b>1.0.0.qualified</b> et créer un nouveau bundle &laquo;&nbsp;patch&nbsp;&raquo; <b>org.akrogen.gestcv.services</b> de version <b>1.0.1.qualified</b> qui corrige le problème. Nous verrons qu&rsquo;avec notre architecture, note bundle &laquo;&nbsp;patch&nbsp;&raquo; ne fonctionnera pas, car nous devons scinder le Bundle <b>org.akrogen.gestcv.services</b> en <a href="#apiImplBundleServices" >2 bundles services API et Implémentation</a>.</p>
<p>Vous pouvez télécharger l&rsquo;ensemble des projets expliqués dans cette section sur <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step5/org.akrogen.gestcv_step5-patch.zip">org.akrogen.gestcv_step5-patch.zip</a>. Nous allons partir des projets du billet précédant <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step4/org.akrogen.gestcv_step4-servicetracker.zip" >org.akrogen.gestcv_step4-servicetracker.zip</a>. </p>
<h3>org.akrogen.gestcv.services (1.0.1)</h3>
<p>Ici nous allons créer le bundle &laquo;&nbsp;patch&nbsp;&raquo; <b>org.akrogen.gestcv.services</b> de version <b>1.0.1</b>. Pour cela : </p>
<ul>
<li>Copiez le projet <b>org.akrogen.gestcv.services</b> et nommez le <b>org.akrogen.gestcv.services_patch</b>.
  </li>
<li>Modifier la version du bundle en <b>1.0.1.qualifier</b> dans le MANIFEST.MF :<br />
<code class="codecolorer text default"><span class="text">Bundle-Version: 1.0.1.qualifier</span></code>
  </li>
</ul>
<h3>org.akrogen.gestcv.services (1.0.0)</h3>
<p>Ici nous allons modifier le bundle <b>org.akrogen.gestcv.services</b> de version <b>1.0.0</b> pour simuler un bug dans la classe org.akrogen.gestcv.services.impl.<b>UserServiceImpl</b>. Pour cela, modifiez cette dernière comme suit : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public Collection&lt;User&gt; findAllUsers() { <br />
&nbsp; throw new RuntimeException(&quot;Error into findAllUsers method.&quot;); <br />
}</div></div>
</div>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et la console OSGi affiche : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; Start Bundle [org.akrogen.gestcv.domain] <br />
Start Bundle [org.akrogen.gestcv.services] <br />
Start Bundle [org.akrogen.gestcv.services] <br />
Start Bundle [org.akrogen.gestcv.simpleosgiclient] <br />
--- Get UserService from OSGi services registry with ServiceTracker --- <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
</div>
<p>Cette trace montre que les 2 bundles <b>org.akrogen.gestcv.services</b> de version différente ont été lancée. Elle montre que le service UserService est consommée, ce qui signifie que c&rsquo;est le bundle de version <b>1.0.1</b> qui a pris la main.</p>
<p>Tappez <b>ss</b> pour afficher la liste des bundles et leur ID : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">... <br />
38 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.services_1.0.1.qualifier <br />
39 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.services_1.0.0.qualifier</div></div>
</div>
<p>Nous allons maintenant arrêter le bundle <b>org.akrogen.gestcv.services</b> de version <b>1.0.1</b>. Pour cela tapez dans la console OSGi <b>stop 38</b> : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Stop Bundle [org.akrogen.gestcv.services] <br />
&nbsp;<br />
osgi&gt; --- Get UserService from OSGi services registry with ServiceTracker --- <br />
&nbsp;Cannot get UserService=&gt; UserService is null!</div></div>
</div>
<p>Cette trace montre que le service UserService n&rsquo;est plus disponible. Le bundle <b>org.akrogen.gestcv.services</b> de version <b>1.0.1</b> étant stoppé, il ne fournit plus le service UserService. Cependant le bundle (avec le bug) <b>org.akrogen.gestcv.services</b> de version <b>1.0.0</b> est activé et on s&rsquo;attend à ce que le service soit fournit par ce bundle. Mais ceci ne fonctionne pas? Je pense que le problème vient du fait que l&rsquo;interface UserService est contenu dans les 2 versions des bundles <b>org.akrogen.gestcv.services</b>, ce qui au passage n&rsquo;est pas très propre. Pour résoudre le problème, nous allons scinder le bundle <b>org.akrogen.gestcv.services</b> en <a href="#apiImplBundleServices" >2 bundles services API et Implémentation</a>.</p>
<h1 id="apiImplBundleServices">Bundle Services API/Implémentation</h1>
<p>Jusqu&rsquo;a maintenant le bundle <b>org.akrogen.gestcv.services</b> contient : </p>
<ul>
<li>l&rsquo;API de services représenté par l&rsquo;interface UserService.</li>
<li>l&rsquo;implémentation de l&rsquo;API de services UserServiceImpl.</li>
</ul>
<p>A ce stade l&rsquo;API et l&rsquo;implémentation de services sont stockés dans le même bundle et ce choix de conception est contraignant : </p>
<ul>
<li>impossible de faire cohabiter plusieurs bundles <b>org.akrogen.gestcv.services</b> de différentes versions (voir explication ci-dessus).</li>
<li>le cycle de vie de l&rsquo;interface UserService et de l&rsquo;implémentation UserServiceImpl peuvent être différent (ex : la correction d&rsquo;un bug de la classe UsertServiceImpl nécessite la création du bundle services avec une nouvelle version alors que l&rsquo;interface UserService ne bouge pas).
  </li>
<li>un intérêt de découpler les bundles API  et implémentation est de <b>permettre de fournir plusieurs implémentations d&rsquo;une même API</b>. En effet avec l&rsquo;utilisation du registry de services OSGi un <b>autre bundle peut ajouter une autre implémentation du service UserService</b>. Ce bundle pour fonctionner doit être lié au bundle actuel <b>org.akrogen.gestcv.services</b> qui fournit l&rsquo;API des services, mais qui enregistre aussi l&rsquo;implémentation UserServicesImpl. Il n&rsquo;est donc pas possible d&rsquo;utiliser l&rsquo;implémentation du service UserService de ce bundle sans devoir arrêter le bundle <b>org.akrogen.gestcv.services</b> (pour désenregsitrer UserServiceImpl) ou faire un filtre sur le service lors de sa récupération par le bundle client.
  </li>
</ul>
<p>En règle général, il est conseillé de <b>séparer l&rsquo;API Services et son Implémentation en 2 bundles distincts</b>. Nous allons dans notre cas
<ul>
<li>modifier le bundle <a href="#apiServices">org.akrogen.gestcv.services</a> pour qu&rsquo;il ne contienne que l&rsquo;interface UserService (API)
  </li>
<li>créer un nouveau bundle <a href="#implServices" >org.akrogen.gestcv.services.impl</a> qui contiendra l&rsquo;implémentation UserServiceImpl et qui enregsitrera cette instance dans le registre de services OSGi via ServiceTracker.
  </li>
</ul>
<p>Voici un schéma de ce que nous allons effectuer dans cette section : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGiServicesRegistryThreadAPIOverview.png" width="584" height="621" alt="" /></p>
<p>Ce schéma met en évidence les 2 bundles Services API <b>org.akrogen.gestcv.services</b> et Implémentation. </p>
<p>Vous pouvez télécharger <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step5/org.akrogen.gestcv_step5-api-services-bundle.zip">org.akrogen.gestcv_step5-api-services-bundle.zip</a> qui contient le code expliqué ci-dessous. Nous allons partir des projets du billet précédant <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step4/org.akrogen.gestcv_step4-servicetracker.zip" >org.akrogen.gestcv_step4-servicetracker.zip</a>.<br />
<b>org.akrogen.gestcv.services.impl</b> .</p>
<h2 id="implServices" >Implémentation &#8211; org.akrogen.gestcv.services.impl</h2>
<p>Ici nous allons créer le bundle <b>org.akrogen.gestcv.services.impl</b>, bundle d&rsquo;implémentation de services. Pour cela : </p>
<ul>
<li>copiez le projet <b>org.akrogen.gestcv.services</b> et nommez le nouveau projet <b>org.akrogen.gestcv.services.impl</b>.
 </li>
<li> Modifiez le Bundle-SymbolicName du MANIFEST.MF avec <b>org.akrogen.gestcv.services.impl</b> :<br />
<code class="codecolorer text default"><span class="text">Bundle-SymbolicName: org.akrogen.gestcv.services.impl</span></code>
  </li>
<li>Supprimez l&rsquo;interface org.akrogen.gestcv.services.<b>UserService</b>.
 </li>
<li>le bundle d&rsquo;implémentation de services ne doit d&rsquo;exposer aucun package (car il s&rsquo;occupe d&rsquo;enregistrer l&rsquo;implémentation de services UserServiceImpl dans le registre de services OSGi). Pour cela, supprimez dans le MANIFEST.MF, la section<br />
<code class="codecolorer text default"><span class="text">Export-Package: org.akrogen.gestcv.services</span></code>
  </li>
<li>le bundle doit faire référence au bundle domain et services API. Pour cela modifiez le MANIFEST.MF avec <a href="#RequireBundle" >Require-Bundle</a> :
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Require-Bundle: org.akrogen.gestcv.domain;bundle-version=&quot;1.0.0&quot;, <br />
&nbsp;org.akrogen.gestcv.services;bundle-version=&quot;1.0.0&quot;</div></div>
</li>
</ul>
<h2 id="apiServices" >API &#8211; org.akrogen.gestcv.services</h2>
<p>Ici nous allons modifier le bundle <b>org.akrogen.gestcv.services</b>, bundle d&rsquo;API de services. Pour cela : </p>
<ul>
<li>Supprimer la classe ServicesFactory.</li>
<li>Supprimer la classe UserServiceImpl.</li>
<li>Supprimer l&rsquo;enregistrement du service dans l&rsquo;Activator ce qui donne :
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.services.internal; <br />
&nbsp;<br />
import org.osgi.framework.BundleActivator; <br />
import org.osgi.framework.BundleContext; <br />
&nbsp;<br />
public class Activator implements BundleActivator { <br />
&nbsp;<br />
&nbsp; public void start(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Start Bundle [&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void stop(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Stop Bundle [&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
</div>
</li>
</ul>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et la console OSGi affiche : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; Start Bundle [org.akrogen.gestcv.domain] <br />
Start Bundle [org.akrogen.gestcv.services] <br />
Start Bundle [org.akrogen.gestcv.services.impl] <br />
Start Bundle [org.akrogen.gestcv.simpleosgiclient] <br />
--- Get UserService from OSGi services registry with ServiceTracker --- <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
</div>
<p>Cette trace montre que les 4 bundles sont lances dont les bundles API et Implémentation services. La consommation du service UserService s&rsquo;effectue ensuite correctement par le bundle client.</p>
<h1 id="ServicesImplVersionBundle" >Services Impl&#038; version bundle</h1>
<p>Maintenant que nous avons 2 bundles services API et Implémentation, nous allons recommencer notre test avec nos bundles services buggués et patchés en repartant des projets <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step5/org.akrogen.gestcv_step5-api-services-bundle.zip">org.akrogen.gestcv_step5-api-services-bundle.zip</a>. Vous pouvez télécharger <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step5/org.akrogen.gestcv_step5-api-services-bundle-patch.zip">org.akrogen.gestcv_step5-api-services-bundle-patch.zip</a> qui contient le code expliqué ci-dessous. </p>
<h2>org.akrogen.gestcv.services.impl (1.0.1)</h2>
<ul>
<li>Copiez le projet <b>org.akrogen.gestcv.services.impl</b> et nommez le <b>org.akrogen.gestcv.services.impl_patch</b>.
  </li>
<li>Modifier la version du bundle en <b>1.0.1.qualifier</b> dans le MANIFEST.MF :<br />
<code class="codecolorer text default"><span class="text">Bundle-Version: 1.0.1.qualifier</span></code>
  </li>
</ul>
<h2>org.akrogen.gestcv.services.impl (1.0.0)</h2>
<p>Modifiez la classe UserServiceImpl comme ceci : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public Collection&lt;User&gt; findAllUsers() { <br />
&nbsp; throw new RuntimeException(&quot;Error into findAllUsers method.&quot;); <br />
}</div></div>
</div>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et la console OSGi affiche une exception toutes les 5 sec en appelant UserService : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">--- Get UserService from OSGi services registry with ServiceTracker--- <br />
java.lang.RuntimeException: Error into findAllUsers method. <br />
&nbsp; at org.akrogen.gestcv.services.impl.UserServiceImpl.findAllUsers(UserServiceImpl.java:22) <br />
&nbsp; at org.akrogen.gestcv.simpleosgiclient.internal.FindAllUsersThread.displayUsers(FindAllUsersThread.java:74) <br />
&nbsp; at org.akrogen.gestcv.simpleosgiclient.internal.FindAllUsersThread.run(FindAllUsersThread.java:40)</div></div>
</div>
<p>Ceci permet d&rsquo;affirmer que le bundle (qui est buggé) de version <b>1.0.0.qualifier</b> est utilisé.</p>
<p>Tappez <b>ss</b> : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">... <br />
39 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.services.impl_1.0.0.qualifier <br />
40 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.services.impl_1.0.1.qualifier <br />
...</div></div>
</div>
<p>Ceci montre que les 2 bundles services sont lancés en même temps et qu&rsquo;il est possible de faire cohabiter plusieurs versions de bundles. Le bundle de version <b>1.0.0.qualifier</b> est utilisé car il est lancé avant celui de version <b>1.0.1.qualifier</b>. L&rsquo;ordre de lancement des bundles est important. Le conteneur OSGi n&rsquo;utilise pas l&rsquo;information &laquo;&nbsp;version&nbsp;&raquo; pour savoir quel bundle doit être utilisé en priorité.</p>
<p>Arrêter le bundle de version <b>1.0.0.qualifier</b> avec la commande (dans mon cas) <b>stop 39</b>. La console OSGi affiche : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">--- Get UserService from OSGi services registry with ServiceTracker --- <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
</div>
<p>Cette trace permet d&rsquo;affirmer que le bundle services patch (version 1.0.1) est maintenant utilisé. Ce scénario permet de montrer comment il est facile de fournir un livrable unitaire (bundle) et de l&rsquo;installer sans perturber les utilisateurs connectés à l&rsquo;application. L&rsquo;arrêt/stop de bundles est extrêmement intéresant : </p>
<ul>
<li><b>en développement</b> dans le cas d&rsquo;une grosse application qui mets un temps (parfois considérable) à démarrer. En effet avec OSGi, cela ne nécéssite pas de redémarrer le serveur pour tester uniquement le service en cours de développement.</li>
<li><b>en production</b> dans le cas d&rsquo;une livraison d&rsquo;un distribuable qui corrigerait un bug (comme ce que nous avons expliqué ci-dessus) et qui évite de pénaliser toute les utilisateurs connectés sur l&rsquo;application.
 </li>
</ul>
<h1 id="ImportPackageVsRequireBundle" >Import package vs Require Bundle</h1>
<p>Il existe plusieurs manières de gérer les dépendances entre les bundles OSGi dont : </p>
<ul>
<li id="RequireBundle"><b>Require Bundle</b>. ce type de dépendance est lié à un bundle donné. Elle permet ensuite d&rsquo;utiliser tous les packages exportès du bundle dans le bundle qui y fait référence. C&rsquo;est ce type de dépendance utilisé dans les Plugin Eclipses.</li>
<li id="ImportPackage"><b>Import Package</b> : ce type de dépendance n&rsquo;est pas lié à un bundle donné. Elle permet d&rsquo;indiquer que l&rsquo;on souhaite utiliser les classes stockés dans un package donné. Ce package donné est un package exporté par un bundle dont on ne connait pas son existence.</li>
</ul>
<p>OSGi préconnise d&rsquo;utiliser <b>Import Package</b> pour ne pas lier fortement les bundles entre eux. Ce type de dépendance permet ensuite de scinder un bundle A en plusieurs bundles (A1, A2, etc) sans que les autres bundles qui étaient lié à A soient impactés. Il existe cependant 2 inconvéniants avec Import Package par rapport à Require Bundle : </p>
<ul>
<li>les packages doivent être importés explicitement. Si un bundle que l&rsquo;on souhaite dépendre expose un nombre considérable de packages, ceci nécéssitera d&rsquo;importer un à un les packages alors qu&rsquo;avec Require Bundle le problème ne se pose pas.
  </li>
<li>split package : si 2 bundles exportent 2 packages identiques, rien ne garantit que le bundle qui importe le package utilise la classe que l&rsquo;on attend.
  </li>
</ul>
<p>Pour éviter les 2 problèmes avec Import Package, les Plugin Eclipse utilisent Require Bundle car il n&rsquo;y a aucune maîtrise des plugins qui sont lancés (on peut ajouter ses propres plugins avec n&rsquo;importe quel package). Nous avons utilisé jusqu&rsquo;à maintenant le type de dépendance <b>Require Bundle</b> que nous allons supprimer pour utiliser <b>Import Package</b> (nous maitrisons le choix des bundles à lancer). Vous pouvez télécharger <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step5/org.akrogen.gestcv_step5-import-package.zip">org.akrogen.gestcv_step5-import-package.zip</a> qui contient le code expliqué ci-dessous : </p>
<ul>
<li><b>Bundle org.akrogen.gestcv.services</b>:
<ul>
<li>Supprimmez la dépendances au bundle <b>org.akrogen.gestcv.domain</b> en supprimant la méta donnée <b>Require-Bundle</b> du MANIFEST.MF.
      </li>
<li>Importer le package <b>org.akrogen.gestcv.domain</b> :
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Import-Package: org.akrogen.gestcv.domain, <br />
&nbsp;org.osgi.framework;version=&quot;1.3.0&quot;</div></div>
</li>
</ul>
</li>
<li><b>Bundle org.akrogen.gestcv.services.impl</b>:
<ul>
<li>Supprimmez les dépendances les 2 bundles <b>org.akrogen.gestcv.domain</b> et <b>org.akrogen.gestcv.services</b> en supprimant la méta donnée <b>Require-Bundle</b> du MANIFEST.MF.
      </li>
<li>Importer le package <b>org.akrogen.gestcv.domain</b> et <b>org.akrogen.gestcv.services</b>:
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Import-Package: org.akrogen.gestcv.domain, <br />
&nbsp;org.akrogen.gestcv.services, <br />
&nbsp;org.osgi.framework;version=&quot;1.3.0&quot;</div></div>
</li>
</ul>
</li>
<li><b>Bundle org.akrogen.gestcv.simpleosgiclient</b>:
<ul>
<li>Supprimmez les dépendances les 2 bundles <b>org.akrogen.gestcv.domain</b> et <b>org.akrogen.gestcv.services</b> en supprimant la méta donnée <b>Require-Bundle</b> du MANIFEST.MF.
      </li>
<li>Importer le package <b>org.akrogen.gestcv.domain</b> et <b>org.akrogen.gestcv.services</b>:
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Import-Package: org.akrogen.gestcv.domain, <br />
&nbsp;org.akrogen.gestcv.services, <br />
&nbsp;org.osgi.framework;version=&quot;1.3.0&quot;, <br />
&nbsp;org.osgi.util.tracker;version=&quot;1.4.2&quot;</div></div>
</li>
</ul>
</li>
</ul>
<h1>Conclusion</h1>
<p>Dans ce billet nous avons mis en évidence et montrer l&rsquo;interêt d&rsquo;utiliser le registre de services OSGi (pour arrêter/stopper les bundles). Dans le prochain billet nous montrerons l&rsquo;interêt d&rsquo;utiliser Spring et plus particulièrement Spring DM pour pouvoir déclarer les services que l&rsquo;on souhaite fournir/consommer via le registre de services OSGi.</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Conception d&#8217;un client Eclipse RCP et serveur OSGI avec Spring DM [step4]</title>
		<link>https://blog.developpez.com/akrogen/p8157/plugin-eclipse/rcp_springdm_step4</link>
		<comments>https://blog.developpez.com/akrogen/p8157/plugin-eclipse/rcp_springdm_step4#comments</comments>
		<pubDate>Tue, 27 Oct 2009 15:16:52 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Vous pouvez trouver ce billet sur mon nouveau blog avec les informations mises a jour. Dans le billet précédant [step3] nous avons mis en place les 3 bundles OSGi Client org.akrogen.gestcv.simpleosgiclient, Services org.akrogen.gestcv.services et Domain org.akrogen.gestcv.domain. Le service UserService est récupéré via la factory de services ServicesFactory qui est un singleton. OSGi met en avant le fait que l&#8217;on puisse lancer/stopper des bundles à chaud sans devoir arrêter le conteneur OSGi. Nous verrons dans ce [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Vous pouvez trouver <a href="http://angelozerr.wordpress.com/2009/11/11/rcp_springdm_step4" >ce billet sur mon nouveau blog</a> avec les informations mises a jour.</p>
<p>Dans le <a href="http://blog.developpez.com/akrogen/p8146/plugin-eclipse/rcp-springdm-step3" >billet précédant [step3]</a> nous avons mis en place les <b>3 bundles OSGi</b> Client <b>org.akrogen.gestcv.simpleosgiclient</b>, Services <b>org.akrogen.gestcv.services</b> et Domain <b>org.akrogen.gestcv.domain</b>. Le service UserService est récupéré via la factory de services <b>ServicesFactory</b> qui est un <b>singleton</b>. OSGi met en avant le fait que l&rsquo;on puisse <b>lancer/stopper des bundles à chaud sans devoir arrêter le conteneur OSGi</b>. Nous verrons dans ce billet que le lancement/arrêt de nos bundles est à ce stade obsolète et par la suite comment y remédier en utilisant le <a href="#OSGIServicesRegistry" >registre de services OSGi</a>. Autrement dit ce billet aborde le registre de services OSGi en expliquant comment <b>fournir/consommer le service UserService via ce registre</b>. Je vous conseille de lire <a href="http://sardes.inrialpes.fr/ecole/livre/pub/Chapters/OSGI/osgi.html" >La plate-forme dynamique de services OSGi</a> pour plus d&rsquo;informations sur la notion de services OSGi.</p>
<p>Voici un schéma de ce que nous allons effectuer dans ce billet : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_Step4Overview.png" width="588" height="618" alt="" /></p>
<p>Ce schéma montre que : </p>
<ul>
<li>le bundle client <b>org.akrogen.gestcv.simpleosgiclient</b> consomme toutes les 5 sec le services UserService via la Thread <a href="#FindAllUsersThreadServiceTracker" >FindAllUsersThread</a>.</li>
<li>la factory de services <b>ServicesFactory a disparu</b>. L&rsquo;instance service <b>UserServive est enregistré et récupéré</b> via le <a href="#OSGIServicesRegistry" >registre de services OSGi</a> (en utilisant <a href="#ServiceTracker" >ServiceTracker</a>).</li>
</ul>
<p>Dans ce billet nous montrerons pas à pas comment nous arrivons au choix de conception décrit ci-dessus  : </p>
<ul>
<li> Dans la section <a href="#startStopBundle">start/stop bundle</a>, le <b>bundle client appele via une Thread toutes les 5 sec le service UserService via la factory ServicesFactory</b>. Nous montrerons qu&rsquo;avec le <b>singleton ServicesFactory, l&rsquo;arrêt du bundle services est obsolète</b>. Vous pouvez télécharger l&rsquo;ensemble des projets expliqués dans cette section sur <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step4/org.akrogen.gestcv_step4-thread.zip">org.akrogen.gestcv_step4-thread.zip</a>.
  </li>
<li> Dans la section <a href="#OSGIServicesRegistry" >OSGi Services Registry</a>, le <b>bundle service et client utilisent le registre de services OSGi</b> pour fournir/consommer le service UserService. Nous montrerons qu&rsquo;avec l&rsquo;utilisation du registre de services OSGi, l&rsquo;arrêt du bundle service a une influence. Vous pouvez télécharger l&rsquo;ensemble des projets expliqués dans cette section sur <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step4/org.akrogen.gestcv_step4-osgiservicesregistry.zip">org.akrogen.gestcv_step4-osgiservicesregistry.zip</a>.
  </li>
<li>Dans la section <a href="#ServiceTracker">ServiceTracker</a>, le ServiceTracker OSGi est utilisé dans le bundle client pour consommer le service UserService enregistré (par le bundle service) dans le registre de services OSGi. Ce tracker évite de solliciter tout le temps le registre de services OSGi pour récupérer le service UserService. Vous pouvez télécharger l&rsquo;ensemble des projets expliqués dans cette section sur <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step4/org.akrogen.gestcv_step4-servicetracker.zip">org.akrogen.gestcv_step4-servicetracker.zip</a>.
  </li>
</ul>
<p><span id="more-65"></span></p>
<h1 id="startStopBundle" >start/stop bundle</h1>
<p>Avec OSGi, il est possible de <b>lancer/stopper</b> (commande start/stop), <b>installer/désinstaller</b> (commande install/uninstall) des <b>bundles</b>. A mes débuts d&rsquo;OSGi je pensais par exemple que l&rsquo;arrêt (ou la désinstallation) d&rsquo;un bundle déchargeait les classes et que l&rsquo;on pouvait ainsi arrêter n&rsquo;importe quel bundle. Mais ce n&rsquo;est pas le cas et nous allons montrer par l&rsquo;exemple ci dessous que l&rsquo;arrêt ou la desinstallation d&rsquo;un bundle n&rsquo;a aucun effet avec notre architecture mise en place à ce stade.</p>
<p>Dans cette section, le bundle client <b>org.akrogen.gestcv.simpleosgiclient</b> va afficher toutes les 5 secondes dans la console OSGi, la liste de Users via le service UserService récupérer par la factory ServicesFactory. Nous verrons que l&rsquo;arrêt ou la désinstallation du bundle service <b>org.akrogen.gestcv.services</b> n&rsquo;aurra aucun effet une fois qu&rsquo;il est lancé (la liste des Users continuera à s&rsquo;afficher). Vous pouvez télécharger <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step4/org.akrogen.gestcv_step4-thread.zip">org.akrogen.gestcv_step4-thread.zip</a> qui contient le code expliqué ci-dessous. </p>
<p>Voici un schéma de ce que nous allons effectuer dans cette section : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_ThreadOverview.png" width="559" height="523" alt="" /></p>
<p>Ce schéma montre que : </p>
<ul>
<li>l&rsquo;appel de de UserService #findAllUsers() s&rsquo;effectue via la Thread org.akrogen.gestcv.simpleosgiclient.internal.<b>FindAllUsersThread</b>. Cette Thread appelera toutes les 5 sec le service.
  </li>
<li>cette Thread est initialisée et lancée (FindAllUsersThread#start()) dans la méthode start du BundleActivator.
  </li>
<li>cette Thread est arrétée (FindAllUsersThread#interrupt()) dans la méthode stop du BundleActivator.
  </li>
</ul>
<p>Nous utilisons une Thread pour appeler le service UserService car si nous avions appelé directement le service  UserService toutes les 5 sec dans la méthode start du BundleActivator, le bundle serait resté toujours à l&rsquo;état start.</p>
<h2>FindAllUsersThread/ServicesFactory</h2>
<p>Créez la classe org.akrogen.gestcv.simpleosgiclient.internal.<b>FindAllUsersThread</b> comme suit : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.simpleosgiclient.internal; <br />
&nbsp;<br />
import java.util.Collection; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
import org.akrogen.gestcv.services.ServicesFactory; <br />
import org.akrogen.gestcv.services.UserService; <br />
&nbsp;<br />
public class FindAllUsersThread extends Thread { <br />
&nbsp;<br />
&nbsp; private static final long TIMER = 5000; <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void run() { <br />
&nbsp; &nbsp; while (super.isAlive()) { <br />
&nbsp;<br />
&nbsp; &nbsp; &nbsp; try { <br />
&nbsp; &nbsp; &nbsp; &nbsp; // 1. Get UserService <br />
&nbsp; &nbsp; &nbsp; &nbsp; UserService userService = getUserService(); <br />
&nbsp; &nbsp; &nbsp; &nbsp; if (userService != null) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 2. Display users by using UserServive <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; displayUsers(userService); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; } catch (Throwable e) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace(); <br />
&nbsp; &nbsp; &nbsp; } finally { <br />
&nbsp; &nbsp; &nbsp; &nbsp; try { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sleep(TIMER); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } catch (InterruptedException e) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace(); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private UserService getUserService() { <br />
&nbsp; &nbsp; System.out <br />
&nbsp; &nbsp; &nbsp; &nbsp; .println(&quot;--- Get UserService from singleton ServicesFactory ---&quot;); <br />
&nbsp; &nbsp; return ServicesFactory.getInstance().getUserService(); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private void displayUsers(UserService userService) { <br />
&nbsp; &nbsp; Collection&lt;User&gt; users = userService.findAllUsers(); <br />
&nbsp;<br />
&nbsp; &nbsp; for (User user : users) { <br />
&nbsp; &nbsp; &nbsp; System.out.println(&quot;User [login=&quot; + user.getLogin() + &quot;, password=&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + user.getPassword() + &quot;]&quot;); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
}</div></div>
</div>
<p>Dans ce cas ci le service UserService est récupéré par la factory ServicesFactory : </p>
<p><code class="codecolorer text default"><span class="text">ServicesFactory.getInstance().getUserService()</span></code></p>
<h2>Activator client &#8211; FindAllUsersThread/ServicesFactory</h2>
<p>Ici nous allons initialiser et lancer la Thread FindAllUsersThread dans le BundleActivator client. pour cela, modifiez la classe org.akrogen.gestcv.simpleosgiclient.internal.<b>Activator</b> comme suit :  </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.simpleosgiclient.internal; <br />
&nbsp;<br />
import org.osgi.framework.BundleActivator; <br />
import org.osgi.framework.BundleContext; <br />
&nbsp;<br />
public class Activator implements BundleActivator { <br />
&nbsp;<br />
&nbsp; private FindAllUsersThread findAllUsersThread = null; <br />
&nbsp;<br />
&nbsp; public void start(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Start Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp;<br />
&nbsp; &nbsp; // Start Thread which call UserService#findAllUsers(); <br />
&nbsp; &nbsp; findAllUsersThread = new FindAllUsersThread(); <br />
&nbsp; &nbsp; findAllUsersThread.start(); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void stop(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Stop Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp;<br />
&nbsp; &nbsp; // Stop Thread which call UserService#findAllUsers(); <br />
&nbsp; &nbsp; findAllUsersThread.interrupt(); <br />
&nbsp; &nbsp; findAllUsersThread = null; <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
</div>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et la console OSGi doit afficher toutes les 5 sec ceci : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">--- Get UserService from singleton ServicesFactory --- <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
</div>
<p><b>Tappez ss</b> (Short Status) dans la console pour afficher les ID des bundles, la console affiche  : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">id &nbsp;State &nbsp; &nbsp; &nbsp; Bundle <br />
0 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.eclipse.osgi_3.5.0.v20090520 <br />
... <br />
3 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.domain_1.0.0.qualifier <br />
4 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.services_1.0.0.qualifier <br />
5 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.simpleosgiclient_1.0.0.qualifier <br />
...</div></div>
</div>
<p>Ici le bundle service <b>org.akrogen.gestcv.services_1.0.0.qualifier</b> est associé à l&rsquo;ID 4. Stoppez ce bundle en saisissant dans la console <b>stop 4</b>, la console affiche : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">stop 4 <br />
Stop Bundle [org.akrogen.gestcv.services]</div></div>
</div>
<p>Pour vérifier que le bundle service est bien arrêté, <b>tappez ss</b>, la console affiche  : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">id &nbsp;State &nbsp; &nbsp; &nbsp; Bundle <br />
... <br />
3 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.domain_1.0.0.qualifier <br />
4 &nbsp;RESOLVED &nbsp; &nbsp;org.akrogen.gestcv.services_1.0.0.qualifier <br />
...</div></div>
</div>
<p>Le bundle <b>org.akrogen.gestcv.services_1.0.0.qualifier</b> est à l&rsquo;éta RESOLVED, ce qui signifie qu&rsquo;il n&rsquo;est pas démarré. Cependant la console OSGi continue à afficher la liste des User toutes les 5 sec, alors que le bundle service est arrêté.</p>
<p>Désinstaller org.akrogen.gestcv.services via la commande <b>uninstall 4</b>. Pour vérifier que le bundle service est bien désinstallé, <b>tappez ss</b>, et vous verrez que le bundle ne s&rsquo;affiche plus dans la liste.</p>
<p>La console OSGi continue à afficher la liste des User toutes les 5 sec, alors que le bundle service est désinstallé. Ceci montre bien que <b>les commandes stop et uninstall ne décharge pas les classes</b>. Mais à quoi sert les commandes start/stop et install/uninstall?</p>
<h1 id="OSGIServicesRegistry" >OSGi Services Registry</h1>
<p>A ce stade notre service est récupéré via le singleton ServicesFactory qui rend obsolète l&rsquo;arrêt ou la désinstallation du bundle services dans lequel il est. OSGi propose un mécanisme de registre de services OSGi ou  n&rsquo;importe quel bundle peut enregistrer (fournir) un service et n&rsquo;importe quel bundle peut récupérer (consommer) un service. Grossièrement le registre de services OSGi peut être comparé à une Map dans laquelle on peut enregistrer/retrouver un service via un identifiant (généralement le nom de l&rsquo;interface du service). Les services qui sont enregistrés dans le registre de services OSGi est une classe Java classique.</p>
<p>Dans cette section nous allons utiliser ce registre de services OSGi pour enregistrer l&rsquo;instance UserServiceImpl avec comme identifiant le nom de l&rsquo;interface qu&rsquo;elle implémente <b>UserService.class.getName()</b>. Nous verrons ensuite les avantages d&rsquo;utiliser ce registre de services OSGi au lieu du singleton ServicesFactory. Spring DM utilise d&rsquo;ailleurs ce registre de services OSGi pour enregistrer des services déclarés dans un fichier XML spring. Vous pouvez télécharger <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step4/org.akrogen.gestcv_step4-osgiservicesregistry.zip">org.akrogen.gestcv_step4-osgiservicesregistry.zip</a> qui contient le code expliqué ci-dessous. </p>
<p>Voici un schéma de ce que nous allons effectuer dans cette section : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGiServicesRegistryOverview.png" width="590" height="543" alt="" /></p>
<p>Ce schéma montre que : </p>
<ul>
<li>le <a href="#servicesProvider" >bundle services joue le rôle de fournisseur</a> de services en enregistrant son instance UserServiceImpl dans le registre de services OSGi.</li>
<li>le <a href="#servicesConsumer" >bundle client joue le rôle de consommateur</a> de services en récupérant le service UserService enregistré dans le registre de services OSGi.</li>
<li>la classe ServicesFactory n&rsquo;est plus exposée.</li>
</ul>
<h2 id="servicesProvider" >Founisseur de services</h2>
<p>Dans le bundle services <b>org.akrogen.gestcv.services</b>, la classe ServicesFactory à ce stade est accéssible par n&rsquo;importe quel bundle pour pouvoir récupérer une instance de UserService.  En utilisant le registre de services OSGi, cette classe ne doit plus être accéssible. Pour cela, déplacer la classe ServicesFactory dans le package <b>org.akrogen.gestcv.services.internal</b>.</p>
<p>Nous allons ensuite enregistrer l&rsquo;instance UserServiceImpl dans le registre de services OSGi via l&rsquo;<b>identifiant UserService.class.getName()</b>. Pour cela, modifiez la classe org.akrogen.gestcv.services.internal.<b>Activator</b> comme suit : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.services.internal; &nbsp;<br />
&nbsp;<br />
import org.akrogen.gestcv.services.UserService; &nbsp;<br />
import org.osgi.framework.BundleActivator; &nbsp;<br />
import org.osgi.framework.BundleContext; &nbsp;<br />
import org.osgi.framework.ServiceRegistration; &nbsp;<br />
&nbsp;<br />
public class Activator implements BundleActivator { &nbsp;<br />
&nbsp;<br />
&nbsp; private ServiceRegistration reg = null; &nbsp;<br />
&nbsp;<br />
&nbsp; public void start(BundleContext context) throws Exception { &nbsp;<br />
&nbsp; &nbsp; System.out.println(&quot;Start Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); &nbsp;<br />
&nbsp;<br />
&nbsp; &nbsp; // Register UserService instance into OSGi Services Registry &nbsp;<br />
&nbsp; &nbsp; reg = context.registerService(UserService.class.getName(), ServicesFactory.getInstance().getUserService(), null); &nbsp;<br />
&nbsp; } &nbsp;<br />
&nbsp;<br />
&nbsp; public void stop(BundleContext context) throws Exception { &nbsp;<br />
&nbsp; &nbsp; System.out.println(&quot;Stop Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); &nbsp;<br />
&nbsp;<br />
&nbsp; &nbsp; // Unregister UserService instance from OSGi Services Registry &nbsp;<br />
&nbsp; &nbsp; if (reg != null) { &nbsp;<br />
&nbsp; &nbsp; &nbsp; reg.unregister(); &nbsp;<br />
&nbsp; &nbsp; &nbsp; reg = null; &nbsp;<br />
&nbsp; &nbsp; } &nbsp;<br />
&nbsp; } &nbsp;<br />
}</div></div>
</div>
<h2 id="servicesConsumer" >Consommateur de services</h2>
<p>Dans le bundle client <b>org.akrogen.gestcv.simpleosgiclient</b>, nous devons récupérer le service UserService dans le registre de services OSGi via l&rsquo;<b>identifiant UserService.class.getName()</b>. Pour cela, modifiez la méthode start de la classe org.akrogen.gestcv.services.internal.<b>Activator</b> comme suit : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void start(BundleContext context) throws Exception { &nbsp;<br />
&nbsp; System.out.println(&quot;Start Bundle [&quot;+ context.getBundle().getSymbolicName() + &quot;]&quot;); &nbsp;<br />
&nbsp; System.out.println(&quot;--- Get UserService from OSGi services registry ---&quot;); &nbsp;<br />
&nbsp;<br />
&nbsp; ServiceReference ref = context.getServiceReference(UserService.class.getName()); &nbsp;<br />
&nbsp; if (ref != null) { &nbsp;<br />
&nbsp;<br />
&nbsp; &nbsp; UserService userService = (UserService) context.getService(ref); &nbsp;<br />
&nbsp; &nbsp; if (userService != null) { &nbsp;<br />
&nbsp; &nbsp; &nbsp; //Display Users &nbsp;<br />
&nbsp; &nbsp; &nbsp; Collection&lt;User&gt; users = userService.findAllUsers(); &nbsp;<br />
&nbsp; &nbsp; &nbsp; for (User user : users) { &nbsp;<br />
&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(&quot;User [login=&quot; + user.getLogin() + &quot;, password=&quot; + user.getPassword() + &quot;]&quot;); &nbsp;<br />
&nbsp; &nbsp; &nbsp; } &nbsp;<br />
&nbsp; &nbsp; } else { &nbsp;<br />
&nbsp; &nbsp; &nbsp; System.out.println(&quot; Cannot get UserService=&gt; UserService is null!&quot;); &nbsp;<br />
&nbsp; &nbsp; } &nbsp;<br />
&nbsp; } else { &nbsp;<br />
&nbsp; &nbsp; System.out.println(&quot; Cannot get UserService=&gt; ServiceReference for UserService is null!&quot;); &nbsp;<br />
&nbsp; } &nbsp;<br />
}</div></div>
</div>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et la console OSGi affiche ceci : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; Start Bundle [org.akrogen.gestcv.domain] <br />
Start Bundle [org.akrogen.gestcv.services] <br />
Start Bundle [org.akrogen.gestcv.simpleosgiclient] <br />
--- Get UserService from OSGi services registry --- <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
</div>
<p>Cette trace montre que le services UserService a bien été consommé par le bundle client.</p>
<h2>start/stop bundle &#8211; OSGi Services Registry</h2>
<p>Ici nous allons ré-utiliser la Thread FindAllUsersThread pour consommer le service UserService récupéré toutes les 5 sec du registre de services OSGi. </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGiServicesRegistryThreadOverview.png" width="589" height="620" alt="" /></p>
<h3>FindAllUsersThread &#8211; OSGi Services Registry</h3>
<p>Nous allons modifier la Thread FindAllUsersThread pour qu&rsquo;elle recherche l&rsquo;instance UserService dans le registry de services OSGi. Pour cela modifiez la classe org.akrogen.gestcv.simpleosgiclient.internal.<b>FindAllUsersThread</b> comme suit : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.simpleosgiclient.internal; <br />
&nbsp;<br />
import java.util.Collection; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
import org.akrogen.gestcv.services.UserService; <br />
import org.osgi.framework.BundleContext; <br />
import org.osgi.framework.ServiceReference; <br />
&nbsp;<br />
public class FindAllUsersThread extends Thread { <br />
&nbsp;<br />
&nbsp; private static final long TIMER = 5000; <br />
&nbsp;<br />
&nbsp; private BundleContext context; <br />
&nbsp;<br />
&nbsp; public FindAllUsersThread(BundleContext context) { <br />
&nbsp; &nbsp; this.context = context; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void run() { <br />
&nbsp; &nbsp; while (super.isAlive()) { <br />
&nbsp;<br />
&nbsp; &nbsp; &nbsp; try { <br />
&nbsp; &nbsp; &nbsp; &nbsp; // 1. Get UserService <br />
&nbsp; &nbsp; &nbsp; &nbsp; UserService userService = getUserService(); <br />
&nbsp; &nbsp; &nbsp; &nbsp; if (userService != null) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 2. Display users by using UserServive <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; displayUsers(userService); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; } catch (Throwable e) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace(); <br />
&nbsp; &nbsp; &nbsp; } finally { <br />
&nbsp; &nbsp; &nbsp; &nbsp; try { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sleep(TIMER); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } catch (InterruptedException e) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace(); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private UserService getUserService() { <br />
&nbsp; &nbsp; System.out.println(&quot;--- Get UserService from OSGi services registry ---&quot;); <br />
&nbsp;<br />
&nbsp; &nbsp; ServiceReference ref = context.getServiceReference(UserService.class.getName()); <br />
&nbsp; &nbsp; if (ref != null) { <br />
&nbsp; &nbsp; &nbsp; UserService userService = (UserService) context.getService(ref); <br />
&nbsp; &nbsp; &nbsp; if (userService != null) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; return userService; <br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; System.out.println(&quot; Cannot get UserService=&gt; UserService is null!&quot;); <br />
&nbsp; &nbsp; } else { <br />
&nbsp; &nbsp; &nbsp; System.out.println(&quot; Cannot get UserService=&gt; ServiceReference for UserService is null!&quot;); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; return null; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private void displayUsers(UserService userService) { <br />
&nbsp; &nbsp; Collection&lt;User&gt; users = userService.findAllUsers(); <br />
&nbsp;<br />
&nbsp; &nbsp; for (User user : users) { <br />
&nbsp; &nbsp; &nbsp; System.out.println(&quot;User [login=&quot; + user.getLogin() + &quot;, password=&quot; + user.getPassword() + &quot;]&quot;); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
}</div></div>
</div>
<p>La Thread attend dans son constructeur le contexte du Bundle :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public FindAllUsersThread(BundleContext context) { <br />
&nbsp; this.context = context; <br />
}</div></div>
<p>qui est ensuite utilisé pour rechercher le service : </p>
<p><code class="codecolorer text default"><span class="text">ServiceReference ref = context.getServiceReference(UserService.class.getName());</span></code></p>
<h3>Activator client &#8211; FindAllUsersThread/OSGi Services Registry</h3>
<p>Modifiez dans le bundle client, la classe org.akrogen.gestcv.simpleosgiclient.internal.<b>Activator</b> pour utiliser la Thread FindAllUsersThread et lui passer le contexte du Bundle : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.simpleosgiclient.internal; <br />
&nbsp;<br />
import org.osgi.framework.BundleActivator; <br />
import org.osgi.framework.BundleContext; <br />
&nbsp;<br />
public class Activator implements BundleActivator { <br />
&nbsp;<br />
&nbsp; private FindAllUsersThread findAllUsersThread = null; <br />
&nbsp;<br />
&nbsp; public void start(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Start Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp;<br />
&nbsp; &nbsp; // Start Thread which call UserService#findAllUsers(); <br />
&nbsp; &nbsp; findAllUsersThread = new FindAllUsersThread(context); <br />
&nbsp; &nbsp; findAllUsersThread.start(); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void stop(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Stop Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp;<br />
&nbsp; &nbsp; // Stop Thread which call UserService#findAllUsers(); <br />
&nbsp; &nbsp; findAllUsersThread.interrupt(); <br />
&nbsp; &nbsp; findAllUsersThread = null; <br />
&nbsp; } <br />
}</div></div>
</div>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et la console OSGi affiche ceci toutes les 5 sec : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">--- Get UserService from OSGi services registry --- <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
</div>
<p>Ceci montre que le service UserService est appelé toutes les 5 sec. Arrêter le bundle services via la commande <b>stop 4</b>. La console OSGi affiche ceci toutes les 5 sec :  </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">--- Get UserService from OSGi services registry --- <br />
&nbsp;Cannot get UserService=&gt; ServiceReference for UserService is null!</div></div>
</div>
<p>Relancez le bundle services via la commande <b>start 4</b>, la console OSGi réaffiche a nouveau la liste de Users : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">--- Get UserService from OSGi services registry --- <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
</div>
<p>Ici nous avons montré que l&rsquo;utilisation du registre de services OSGi permet de rendre opérationnel le lancement/arrêt du bundle services. Autrement dit si ce bundle est arrêté, le service UserService n&rsquo;est plus disponible et si il est à nouveau lancé, le service UserService redevient disponible.</p>
<h1 id="ServiceTracker" >ServiceTracker</h1>
<p>Il est aussi possible de récupérer un service via le ServiceTracker d&rsquo;OSGi. Je ne vais pas rentrer dans le détail de cette classe, mais l&rsquo;idée de celle-ci est qu&rsquo;elle est capable de surveiller les enregistrements/désenregistrements d&rsquo;un service donné. Cette classe permet d&rsquo;éviter de solliciter le registre de service OSGi pour récupérer un service comme ce que nous avons fait avant : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">ServiceReference ref = context.getServiceReference(UserService.class.getName()); &nbsp;<br />
if (ref != null) { &nbsp;<br />
&nbsp; UserService userService = (UserService) context.getService(ref); <br />
... <br />
}</div></div>
</div>
<p>Avec le ServiceTracker, il faut l&rsquo;initialiser comme ceci : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">// Create and open the MovieFinder ServiceTracker &nbsp; <br />
ServiceTracker userServiceTracker = new ServiceTracker(context, UserService.class .getName(), null); &nbsp; <br />
userServiceTracker.open();</div></div>
</div>
<p>Puis pour récupérer le service UserService (que nous effectuons dans la Thread) il faut faire : </p>
<div style="border:black solid 1px;" ><code class="codecolorer text default"><span class="text">UserService userService = (UserService) userServiceTracker.getService();</span></code>
</div>
<p>Vous pouvez télécharger <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step4/org.akrogen.gestcv_step4-servicetracker.zip">org.akrogen.gestcv_step4-servicetracker.zip</a> qui contient le code expliqué ci-dessous. </p>
<h2>Activator &#8211; ServiceTracker</h2>
<p>Dans le bundle <b>org.akrogen.gestcv.simpleosgiclient</b>, <a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#AddImportPackage" >importez le package</a> <b>org.osgi.util.tracker</b>. Le MANIFEST.MF doit avoir son Import-Package comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Import-Package: org.osgi.framework;version=&quot;1.3.0&quot;, <br />
&nbsp;org.osgi.util.tracker;version=&quot;1.4.2&quot;</div></div>
<p>Modifiez la classe org.akrogen.gestcv.simpleosgiclient.internal.<b>Activator</b> pour initialiser un ServiceTracker sur UserService et passer l&rsquo;instance ServiceTracker dans la Thread FindAllUsersThread : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.simpleosgiclient.internal; <br />
&nbsp;<br />
import org.akrogen.gestcv.services.UserService; <br />
import org.osgi.framework.BundleActivator; <br />
import org.osgi.framework.BundleContext; <br />
import org.osgi.util.tracker.ServiceTracker; <br />
&nbsp;<br />
public class Activator implements BundleActivator { <br />
&nbsp;<br />
&nbsp; private ServiceTracker userServiceTracker = null; <br />
&nbsp;<br />
&nbsp; private FindAllUsersThread findAllUsersThread = null; <br />
&nbsp;<br />
&nbsp; public void start(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Start Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp;<br />
&nbsp; &nbsp; // Create and open the MovieFinder ServiceTracker <br />
&nbsp; &nbsp; userServiceTracker = new ServiceTracker(context, UserService.class.getName(), null); <br />
&nbsp; &nbsp; userServiceTracker.open(); <br />
&nbsp;<br />
&nbsp; &nbsp; // Start Thread which call UserService#findAllUsers(); <br />
&nbsp; &nbsp; findAllUsersThread = new FindAllUsersThread(userServiceTracker); <br />
&nbsp; &nbsp; findAllUsersThread.start(); <br />
&nbsp;<br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void stop(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Stop Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp;<br />
&nbsp; &nbsp; // Stop Thread which call UserService#findAllUsers(); <br />
&nbsp; &nbsp; findAllUsersThread.interrupt(); <br />
&nbsp; &nbsp; findAllUsersThread = null; <br />
&nbsp;<br />
&nbsp; &nbsp; // Close the MovieFinder ServiceTracker <br />
&nbsp; &nbsp; userServiceTracker.close(); <br />
&nbsp;<br />
&nbsp; } <br />
}</div></div>
</div>
<h2 id="FindAllUsersThreadServiceTracker" >FindAllUsersThread &#8211; ServiceTracker</h2>
<p>Modifiez la classe org.akrogen.gestcv.simpleosgiclient.internal.<b>FindAllUsersThread</b> pour utiliser le ServiceTracker (au lieu du BundleContext) pour récupérer le service UserService : </p>
<div style="border:black solid 1px;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.simpleosgiclient.internal; <br />
&nbsp;<br />
import java.util.Collection; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
import org.akrogen.gestcv.services.UserService; <br />
import org.osgi.util.tracker.ServiceTracker; <br />
&nbsp;<br />
public class FindAllUsersThread extends Thread { <br />
&nbsp;<br />
&nbsp; private static final long TIMER = 5000; <br />
&nbsp;<br />
&nbsp; private ServiceTracker userServiceTracker; <br />
&nbsp;<br />
&nbsp; public FindAllUsersThread(ServiceTracker userServiceTracker) { <br />
&nbsp; &nbsp; this.userServiceTracker = userServiceTracker; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void run() { <br />
&nbsp; &nbsp; while (super.isAlive()) { <br />
&nbsp;<br />
&nbsp; &nbsp; &nbsp; try { <br />
&nbsp; &nbsp; &nbsp; &nbsp; // 1. Get UserService <br />
&nbsp; &nbsp; &nbsp; &nbsp; UserService userService = getUserService(); <br />
&nbsp; &nbsp; &nbsp; &nbsp; if (userService != null) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 2. Display users by using UserServive <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; displayUsers(userService); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; } catch (Throwable e) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace(); <br />
&nbsp; &nbsp; &nbsp; } finally { <br />
&nbsp; &nbsp; &nbsp; &nbsp; try { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sleep(TIMER); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } catch (InterruptedException e) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace(); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private UserService getUserService() { <br />
&nbsp; &nbsp; System.out.println(&quot;--- Get UserService from OSGi services registry with ServiceTracker ---&quot;); <br />
&nbsp; &nbsp; UserService userService = (UserService) userServiceTracker.getService(); <br />
&nbsp; &nbsp; if (userService != null) { <br />
&nbsp; &nbsp; &nbsp; return userService; <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; System.out.println(&quot; Cannot get UserService=&gt; UserService is null!&quot;); <br />
&nbsp; &nbsp; return null; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private void displayUsers(UserService userService) { <br />
&nbsp; &nbsp; Collection&lt;User&gt; users = userService.findAllUsers(); <br />
&nbsp; &nbsp; for (User user : users) { <br />
&nbsp; &nbsp; &nbsp; System.out.println(&quot;User [login=&quot; + user.getLogin() + &quot;, password=&quot; + user.getPassword() + &quot;]&quot;); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
}</div></div>
</div>
<p>La Thread attend dans son constructeur l&rsquo;instance ServiceTracker :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public FindAllUsersThread(ServiceTracker userServiceTracker) { &nbsp;<br />
&nbsp; this.userServiceTracker = userServiceTracker; &nbsp;<br />
}</div></div>
<p>qui est ensuite utilisé pour rechercher le service :</p>
<p><code class="codecolorer text default"><span class="text">UserService userService = (UserService) userServiceTracker.getService();</span></code></p>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et la console OSGi doit afficher ceci : </p>
<div style="border:black solid 1px;background-color:#F6F6F6;" >
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">--- Get UserService from OSGi services registry with ServiceTracker --- <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
</div>
<p>qui montre que le service est récupéré dans le registre de services OSGi via le ServiceTracker.</p>
<h1>Conclusion</h1>
<p>Dans ce billet nous avons montrer comment <b>consommer/fournir le service UserService via le registre de services OSGi avec ServiceTracker</b>. Utiliser le registre de services OSGi permet ensuite de bénéficier du <b>lancement/arrêt des bundles</b> à chaud (sans devoir redémarrer le conteneur OSGi). Cette fonctionnalité est très intéressante, car elle permet par exemple d&rsquo;<b>installer et lancer un bundle &laquo;&nbsp;patch&nbsp;&raquo; qui corrigerait un bundle buggé sans devoir stopper le conteneur OSGi</b>. Je souhaitais dans un premier temps montrer en évidence ce scénario, mais l&rsquo;architecture actuelle de nos bundles ne le permet pas, car le bundle <b>org.akrogen.gestcv.services</b> doit être scindé en 2 bundles <b>API et Implémentation</b> que nous expliquerons dans le prochain billet.</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Conception d&#8217;un client Eclipse RCP et serveur OSGI avec Spring DM [step3]</title>
		<link>https://blog.developpez.com/akrogen/p8146/plugin-eclipse/rcp_springdm_step3</link>
		<comments>https://blog.developpez.com/akrogen/p8146/plugin-eclipse/rcp_springdm_step3#comments</comments>
		<pubDate>Tue, 13 Oct 2009 10:39:47 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Vous pouvez trouver ce billet sur mon nouveau blog avec les informations mises a jour. Dans le billet précédant [step2] nous avons créé le Bundle OSGi org.akrogen.gestcv.domain et préparé l&#8217;environnement OSGi (Target Platform). Dans ce billet nous allons créer les 2 Bundles OSGi Services org.akrogen.gestcv.services, et Client org.akrogen.gestcv.simpleosgiclient et gérer leur dépendances via leur fichier MANIFEST.MF. Voici un schéma de ce que nous allons effectuer dans ce billet : Ce schéma met en évidence plusieurs [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Vous pouvez trouver <a href="http://angelozerr.wordpress.com/2009/11/11/rcp_springdm_step3" >ce billet sur mon nouveau blog</a> avec les informations mises a jour.</p>
<p>Dans le <a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2" >billet précédant [step2]</a> nous avons créé le Bundle OSGi <b>org.akrogen.gestcv.domain</b> et préparé l&rsquo;environnement OSGi (<a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2/#TargetPlatform" >Target Platform</a>). Dans ce billet nous allons créer les 2 Bundles OSGi Services <a href="#services" >org.akrogen.gestcv.services</a>, et Client <a href="#client" >org.akrogen.gestcv.simpleosgiclient</a> et <b>gérer leur dépendances via leur fichier MANIFEST.MF</b>.</p>
<p>Voici un schéma de ce que nous allons effectuer dans ce billet : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_Step3Overview.png" width="557" height="454" alt="" /></p>
<p>Ce schéma met en évidence plusieurs notions : </p>
<ul>
<li>les dépendances entre les Bundle OSGi s&rsquo;effectuent via le fichier <b>META-INF/MANIFEST.MF</b> et plus par le Java Build Path classique.</li>
<li>l&rsquo;appel des services par le client ne se fait plus par un main Java mais par la <b>méthode start du BundleActivator</b>.</li>
</ul>
<p>En fin du billet nous reprendrons les 2 problèmes souléves avec le Java build Path classique  et qui seront résolus avec OSGi:</p>
<ul>
<li><a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1#ClassesNotProtected" >Classes non protégées</a> qui sera résolu <a href="#ClassNotProtected" >via les export package du MANIFEST.MF</a>.
  </li>
<li><a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1#ClassLoader" >ClassLoader</a> qui sera résolu <a href="#ClassLoader" >par la fait qu&rsquo;un Bundle OSGi a son propre ClassLoader</a>.
  </li>
</ul>
<p>Vous pouvez télécharger les projets <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step3/org.akrogen.gestcv_step3.zip">org.akrogen.gestcv_step3.zip</a> et <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step3/org.akrogen.gestcv_step3-commons-lang.zip">org.akrogen.gestcv_step3-commons-lang.zip</a> (zip qui contient les Bundle OSGi qui utilisent 2 versions de la librairie Apache <a href="http://commons.apache.org/lang/" >commans-lang*.jar</a> et qui montre en évidence le problème de ClassLoader résolu) présentés dans ce billet.</p>
<p><span id="more-64"></span></p>
<h1 id="services" >org.akrogen.gestcv.services</h1>
<p>Ici nous allons transformer le projet <a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1/#services" >Java classique org.akrogen.gestcv.services</a> en Bundle OSGi. Les sources Java de notre Bundle seront identiques à celui du projet Java classique. <a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#CreateBundle" >Créez le Bundle</a> <b>org.akrogen.gestcv.services</b> avec les paramètres suivants : </p>
<ul>
<li>champs ID: <b>org.akrogen.gestcv.services</b></li>
<li>champs Version: <b>1.0.0.qualifier</b>.</li>
<li>champs Name: <b>GestCV Services</b></li>
<li>champs Provider: <b>Akrogen</b></li>
<li>champs Execution Environment: <b>J2SE-1.5</b>.</li>
<li>option generate an activator, a Java class that controls the plug-in&rsquo;s life cycle <b>cochée</b>.</li>
<li>Activator : <b>org.akrogen.gestcv.services.Activator</b></li>
</ul>
<p>Ajoutez la dépendance (avec <a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#AddRequireBundle" >Require-Bundle</a>) au Bundle <b>org.akrogen.gestcv.domain</b> </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_ServicesDependencies3.png" width="544" height="504" alt="" /></p>
<p>Sauvegardez et vous pourrez vérifier que dans le MANIFEST.MF, il y a la dépendance Require-Bundle : </p>
<p><code class="codecolorer text default"><span class="text">Require-Bundle: org.akrogen.gestcv.domain;bundle-version=&quot;1.0.0&quot;</span></code></p>
<p>Créez l&rsquo;interface org.akrogen.gestcv.services.<b id="UserService" >UserService</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.services; <br />
&nbsp;<br />
import java.util.Collection; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
&nbsp;<br />
public interface UserService { <br />
&nbsp;<br />
&nbsp; Collection&lt;User&gt; findAllUsers(); <br />
}</div></div>
<p>Ce code ne compile pas car il n&rsquo;arrive pas à résoudre la classe org.akrogen.gestcv.domain.<b>User</b>. Nous avons pourtant mis une dépendance sur le Bundle <b>org.akrogen.gestcv.domain</b> via <b>Require-Bundle</b>. Ceci s&rsquo;explique par le fait que le Bundle <b>org.akrogen.gestcv.domain</b> n&rsquo;expose aucune classes et que par conséquent la classe org.akrogen.gestcv.domain.<b>User</b> n&rsquo;est pas accéssible. En effet par défaut un Bundle n&rsquo;expose aucune classes. Pour indiquer les classes que l&rsquo;on souhaite exposer à d&rsquo;autres Bundles, ceci s&rsquo;effectue en exportant les packages des classes que l&rsquo;on souhaite rendre accéssibles.</p>
<h2 id="ExportPackage" >Export Package</h2>
<p>Ici nous allons rendre accéssible les classes du package <b>org.akrogen.gestcv.domain</b> du Bundle <b>org.akrogen.gestcv.domain</b> en exportant le package <b>org.akrogen.gestcv.domain</b>. Cette information sera contenu dans le fichier MANIFEST.MF (balise Export-Package).</p>
<p>Nous allons modifier le MANIFEST.MF du Bundle <b>org.akrogen.gestcv.domain</b> via l&rsquo;onglet <b>Runtime</b> de l&rsquo;editor PDE: </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_DomainExportPackages1.png" width="536" height="435" alt="" /></p>
<p>Cliquez sur le bouton <b>Add&#8230;</b> qui ouvre la boite de dialogue qui propose tous les packages du Bundle.</p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_DomainExportPackages2.png" width="400" height="500" alt="" /></p>
<p>Sélectionnez le package <b>org.akrogen.gestcv.domain</b>, puis cliquez sur <b>OK</b>. Le liste <b>Export packaged</b> doit être renseigné avec le package <b>org.akrogen.gestcv.domain</b>. Sauvegardez et l&rsquo;interface UserService doit compiler à nouveau : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_ServicesDependencies3.png" width="544" height="504" alt="" /></p>
<p>Vous pouvez vérifiez que cette action d&rsquo;export package a mis à jour le fichier MANIFEST.MF avec ce contenu : </p>
<p><code class="codecolorer text default"><span class="text">Export-Package: org.akrogen.gestcv.domain</span></code></p>
<h2>Protection des Activator</h2>
<p>Maintenant que nous avons exportés le package <b>org.akrogen.gestcv.domain</b> dans le Bundle <b>org.akrogen.gestcv.domain</b>, n&rsquo;importe quel Bundle peut accéder aux classes du package <b>org.akrogen.gestcv.domain</b> qui à ce stade sont : </p>
<ul>
<li>la classe org.akrogen.gestcv.domain.<b>User</b> qui est le domaine Utilisateur.
  </li>
<li>la classe org.akrogen.gestcv.domain.<b>Activator</b> qui est le BundleActivator qui affiche une trace dans la console OSGi lorsque le bundle est démarré et stoppé.
  </li>
</ul>
<p>Le Bundle Activator ne doit pas être accéssible via un autre Bundle. Cette classe doit juste être accéssible par le conteneur OSGi. Hors à ce stade, il est possible d&rsquo;accéder à la classe org.akrogen.gestcv.domain.<b>Activator</b>, dans des classes d&rsquo;un autre Bundle. Pour vous rendre compte de ce problème, tappez Activator dans la classe UserService et vous pourrez constater qu&rsquo;il est possible d&rsquo;accéder à org.akrogen.gestcv.domain.<b>Activator</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_DomainExportPackagesActivatorPB.png" width="644" height="348" alt="" /></p>
<p>Pour régler, ce problème il faut mettre cette classe Activator dans un package protégé. Les plugins d&rsquo;Eclipse utilise souvent le package <b>internal</b> pour mettre les classes que le Bundle ne doit pas exposer. Nous allons procéder de la même manière et mettre la classe <b>Activator</b> dans le package <b>org.akrogen.gestcv.domain.internal</b>.</p>
<h3 id="RefactorMove">Refactor ->Move</h3>
<p>Ici nous allons effectuer un refactoring pour mettre la classe org.akrogen.gestcv.domain.<b>Activator</b> dans le package <b>org.akrogen.gestcv.domain.internal</b>. Le fait de passer par un refactoring Eclipse, permettra de mettre à jour aussi le fichier MANIFEST.MF (Bundle-Activator).</p>
<p>Sélectionnez la classe org.akrogen.gestcv.domain.<b>Activator</b>, puis cliquez sur le bouton droit de la souris pour accéder au menu contextuel. Accédez à l&rsquo;item <b>Refactor->Move&#8230;</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_DomainExportPackagesActivatorInternal1.png" width="783" height="448" alt="" /></p>
<p>La fenêtre de dialogue <b>Move</b> s&rsquo;ouvre : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_DomainExportPackagesActivatorInternal2.png" width="408" height="465" alt="" /></p>
<p>Cliquez sur le bouton <b>Create Package&#8230;</b>, la fenêtre de création de packages s&rsquo;ouvre : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_DomainExportPackagesActivatorInternal3.png" width="438" height="443" alt="" /></p>
<p>Saisissez dans le champs <b>Name</b>, la valeur <b>org.akrogen.gestcv.domain.internal</b>, puis cliquez sur <b>OK</b>, puis sur le bouton <b>Finish</b> . Le refactoring de changement de package s&rsquo;est effectué et le MANIFEST.MF a été modifie aussi avec la valeur suivante : </p>
<p><code class="codecolorer text default"><span class="text">Bundle-Activator: org.akrogen.gestcv.domain.internal.Activator</span></code></p>
<h2>Sources de org.akrogen.gestcv.services</h2>
<h3>MANIFEST.MF / Export-Package</h3>
<p><a href="#ExportPackage" >Exporter le package</a> <b>org.akrogen.gestcv.services</b> du Bundle <b>org.akrogen.gestcv.services</b> pour exposer uniquement aux autres Bundles l&rsquo;interface <a href="#UserService" >UserService</a> et la factory <a href="#ServicesFactory" >ServicesFactory</a>. Le fichier MANIFEST.MF du bundle <b>org.akrogen.gestcv.services</b> doit avoir ce contenu ajouté : </p>
<p><code class="codecolorer text default"><span class="text">Export-Package: org.akrogen.gestcv.services</span></code></p>
<h3>Activator</h3>
<p><a href="#RefactorMove" >Modifiez le package</a> de la classe Activator du Bundle org.akrogen.gestcv.services dans le package <b>org.akrogen.gestcv.services.internal</b>. Le fichier MANIFEST.MF du bundle <b>org.akrogen.gestcv.services</b> doit avoir ce contenu ajouté : </p>
<p><code class="codecolorer text default"><span class="text">Bundle-Activator: org.akrogen.gestcv.services.internal.Activator</span></code></p>
<p>Modifiez le code de cette classe comme suit pour tracer le démarrage/stoppage du Bundle :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public class Activator implements BundleActivator { <br />
&nbsp;<br />
&nbsp; /* <br />
&nbsp; &nbsp;* (non-Javadoc) <br />
&nbsp; &nbsp;* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) <br />
&nbsp; &nbsp;*/ <br />
&nbsp; public void start(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Start Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; /* <br />
&nbsp; &nbsp;* (non-Javadoc) <br />
&nbsp; &nbsp;* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) <br />
&nbsp; &nbsp;*/ <br />
&nbsp; public void stop(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Stop Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<h3>UserServiceImpl</h3>
<p>Créez la classe org.akrogen.gestcv.services.impl.<b>UserServiceImpl</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.services.impl; <br />
&nbsp;<br />
import java.util.ArrayList; <br />
import java.util.Collection; <br />
import java.util.List; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
import org.akrogen.gestcv.services.UserService; <br />
&nbsp;<br />
public class UserServiceImpl implements UserService { <br />
&nbsp;<br />
&nbsp; private Collection&lt;User&gt; users = null; <br />
&nbsp;<br />
&nbsp; public Collection&lt;User&gt; findAllUsers() { <br />
&nbsp; &nbsp; if (users == null) { <br />
&nbsp; &nbsp; &nbsp; users = createUsers(); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; return users; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private Collection&lt;User&gt; createUsers() { <br />
&nbsp; &nbsp; List&lt;User&gt; users = new ArrayList&lt;User&gt;(); <br />
&nbsp; &nbsp; users.add(new User(&quot;angelo&quot;, &quot;&quot;)); <br />
&nbsp; &nbsp; users.add(new User(&quot;djo&quot;, &quot;&quot;)); <br />
&nbsp; &nbsp; users.add(new User(&quot;keulkeul&quot;, &quot;&quot;)); <br />
&nbsp; &nbsp; users.add(new User(&quot;pascal&quot;, &quot;&quot;)); <br />
&nbsp; &nbsp; return users; <br />
&nbsp; } <br />
}</div></div>
<h3 id="ServicesFactory" >ServicesFactory</h3>
<p>Créez la classe org.akrogen.gestcv.services.<b>ServicesFactory</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.services; <br />
&nbsp;<br />
import org.akrogen.gestcv.services.impl.UserServiceImpl; <br />
&nbsp;<br />
public class ServicesFactory { <br />
&nbsp;<br />
&nbsp; private static ServicesFactory INSTANCE = new ServicesFactory(); <br />
&nbsp; private UserService userService = null; <br />
&nbsp;<br />
&nbsp; private ServicesFactory() { <br />
&nbsp;<br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public static ServicesFactory getInstance() { <br />
&nbsp; &nbsp; return INSTANCE; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public UserService getUserService() { <br />
&nbsp; &nbsp; if (userService == null) { <br />
&nbsp; &nbsp; &nbsp; userService = createUserService(); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; return userService; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private UserService createUserService() { <br />
&nbsp; &nbsp; UserService userService = new UserServiceImpl(); <br />
&nbsp; &nbsp; return userService; <br />
&nbsp; } <br />
}</div></div>
<h1 id="client" >org.akrogen.gestcv.simpleosgiclient</h1>
<p>Ici nous allons transformer le projet <a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1/#client" >Java classique org.akrogen.gestcv.simplemainclient</a> en Bundle OSGi. Les sources Java de notre Bundle seront identiques à celui du projet Java classique. <a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#CreateBundle" >Créez le Bundle</a> <b>org.akrogen.gestcv.simpleosgiclient</b> avec les paramètre suivants : </p>
<p>avec les paramètres suivants : </p>
<ul>
<li>champs ID: <b>org.akrogen.gestcv.simpleosgiclient</b></li>
<li>champs Version: <b>1.0.0.qualifier</b>.</li>
<li>champs Name: <b>GestCV Simple OSGi Client</b></li>
<li>champs Provider: <b>Akrogen</b></li>
<li>champs Execution Environment: <b>J2SE-1.5</b>.</li>
<li>option generate an activator, a Java class that controls the plug-in&rsquo;s life cycle <b>cochée</b> .</li>
<li>Activator : <b>org.akrogen.gestcv.simpleosgiclient.internal.Activator</b></li>
</ul>
<p>Ajoutez les dépendances (avec <a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#AddRequireBundle" >Require-Bundle</a>) aux Bundles <b>org.akrogen.gestcv.domain</b> et <b>org.akrogen.gestcv.services</b>.</p>
<p>Modifiez le code de la classe org.akrogen.gestcv.simpleosgiclient.internal.<b>Activator</b> comme suit pour tracer le démarrage/stoppage du Bundle :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.simpleosgiclient.internal; <br />
&nbsp;<br />
import java.util.Collection; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
import org.akrogen.gestcv.services.ServicesFactory; <br />
import org.akrogen.gestcv.services.UserService; <br />
import org.osgi.framework.BundleActivator; <br />
import org.osgi.framework.BundleContext; <br />
&nbsp;<br />
public class Activator implements BundleActivator { <br />
&nbsp;<br />
&nbsp; /* <br />
&nbsp; &nbsp;* (non-Javadoc) <br />
&nbsp; &nbsp;* &nbsp;<br />
&nbsp; &nbsp;* @see <br />
&nbsp; &nbsp;* org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext <br />
&nbsp; &nbsp;* ) <br />
&nbsp; &nbsp;*/ <br />
&nbsp; public void start(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Start Bundle [&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp; &nbsp; UserService userService = ServicesFactory.getInstance() <br />
&nbsp; &nbsp; &nbsp; &nbsp; .getUserService(); <br />
&nbsp; &nbsp; Collection&lt;User&gt; users = userService.findAllUsers(); <br />
&nbsp;<br />
&nbsp; &nbsp; for (User user : users) { <br />
&nbsp; &nbsp; &nbsp; System.out.println(&quot;User [login=&quot; + user.getLogin() + &quot;, password=&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + user.getPassword() + &quot;]&quot;); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; /* <br />
&nbsp; &nbsp;* (non-Javadoc) <br />
&nbsp; &nbsp;* &nbsp;<br />
&nbsp; &nbsp;* @see <br />
&nbsp; &nbsp;* org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) <br />
&nbsp; &nbsp;*/ <br />
&nbsp; public void stop(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Stop Bundle [&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<p>Avant de relancer, vérifier que les 3 bundles OSGi sont cochés dans la configuration GestCV OSGi : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_RunConfigurationStep3.png" width="1040" height="720" alt="" /></p>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et la console OSGi doit afficher ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; Start Bundle [org.akrogen.gestcv.domain] <br />
Start Bundle [org.akrogen.gestcv.services] <br />
Start Bundle [org.akrogen.gestcv.simpleosgiclient] <br />
User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
<p>Les Traces de type &laquo;&nbsp;Start Bundle&#8230;.&nbsp;&raquo; montre que la méthode start de chaque BundleActivator a été appelé.</p>
<h1>Problème résolu par OSGi</h1>
<h2 id="ClassNotProtected" >UserServiceImpl protégé</h2>
<p>Dans la classe Activator du Bundle <a href="#client" >org.akrogen.gestcv.simpleosgiclient</a>, si vous tappez User, la complétion ne propose plus la classe UserServiceImpl, car cette classe n&rsquo;est pas dans un package qui est exporté par le Bundle :   </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_UserServiceImpProtected.png" width="654" height="420" alt="" /></p>
<h2 id="ClassLoader">Bundle OSGi/ClassLoader</h2>
<p>Ici nous allons effectuer les mêmes tests que ceux effectués dans le <a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1" >billet [step1]</a> avec des projets Java classiques (dépendances classiques Java Build Path). Nous allons dans un premier temps montrer que chaque Bundle OSGi a son propre ClassLoader. Pour s&rsquo;en rendre compte nous allons afficher le ClassLoader dans la console Eclipse. </p>
<p>Modifiez les 3 classes Activator en modifiant la méthode start comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void start(BundleContext context) throws Exception { <br />
&nbsp; System.out.println(&quot;Start Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;], ClassLoader= &quot;+ Activator.class.getClassLoader()); <br />
}</div></div>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a> et vous verrez dans la console OSGi :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; Start Bundle [org.akrogen.gestcv.domain], ClassLoader= org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@b5dac4 <br />
Start Bundle [org.akrogen.gestcv.services], ClassLoader= org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@adb1d4 <br />
Start Bundle [org.akrogen.gestcv.simpleosgiclient], ClassLoader= org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@1484a05 <br />
...</div></div>
<p>qui montre que chaque Bundle OSGi a son propre ClassLoader.</p>
<p>Avec les dépendances classiques Java Build Path, nous avons vu qu&rsquo;il y avait un ClassLoader unique et nous avons mis en évidence ce problème avec l&rsquo;utilisation de versions différentes de la librairie Apache <a href="http://commons.apache.org/lang/" >commons-lang*.jar</a>. Nous allons effectuer la même chose avec nos Bundle OSGi :</p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGICommonsJar.png" width="567" height="430" alt="" /></p>
<p>Ce schéma montre que :</p>
<ul>
<li>le Bundle Client <a href="#client" >org.akrogen.gestcv.simpleosgiclient</a> pointera sur la librairie <b>commons-lang-1.0.jar</b> de version <b>1.0</b>. Cette librairie est stockée dans le repertoire lib (qu&rsquo;il faut créer) du Bundle.</li>
<li>le Bundle Services <a href="#services" >org.akrogen.gestcv.services</a> pointera sur la librairie <b>commons-lang-2.4.jar</b> de version <b>2.4</b>. Cette librairie est stockée dans le repertoire lib (qu&rsquo;il faut créer) du Bundle.</li>
</ul>
<p>Vous pouvez télécharger le zip <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step3/org.akrogen.gestcv_step3-commons-lang.zip">org.akrogen.gestcv_step3-commons-lang.zip</a> qui contient les Bundles avec les librairies <a href="http://commons.apache.org/lang/" >commans-lang*.jar</a>.</p>
<p>Pour rappel, la différence entre ces 2 versions de <a href="http://commons.apache.org/lang/" >commans-lang*.jar</a> est que la classe utilitaire  org.apache.commons.lang.<b>StringUtils</b> possède une méthode <b>StringUtils#isBlank(String String)</b> dans la version <b>2.4</b> et pas dans la version <b>1.0</b>. Nous souhaitons utiliser <b>StringUtils#isBlank(String String)</b> dans la couche Services.</p>
<h3 id="BundleAddJar">Runtime/Add Jar (commons-lang-2.4.jar)</h3>
<p>Ici nous allons ajouter au Bundle <a href="#services" >org.akrogen.gestcv.services</a> la librairie jar <b>commons-lang-2.4.jar</b>. Cette dépendance se retrouvera au final dans le MANIFEST.MF du Bundle (Bundle-ClassPath). Ouvrez le fichier MANIFEST.MF du Bundle et cliquez sur l&rsquo;onglet <b>Runtime</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGIAddJar1.png" width="864" height="436" alt="" /></p>
<p>Cliquez sur le bouton <b>Add&#8230;</b>, la fenêtre de sélection de JAR s&rsquo;ouvre : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGIAddJar2.png" width="346" height="415" alt="" /></p>
<p>Sélectionnez le JAR <b>lib/commons-lang-2.4.jar</b>, puis cliquez sur OK, la librairie est ajoutée : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGIAddJar3.png" width="863" height="428" alt="" /></p>
<p>Sauvegardez et vous pourrez voir que le MANIFEST.MF a été modifié en ajoutant ce contenu :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Bundle-ClassPath: lib/commons-lang-2.4.jar, <br />
&nbsp;.</div></div>
<h3>Utilisation commons dans la couche Services</h3>
<p>Modifiez la méthode start de la classe org.akrogen.gestcv.services.internal.<b>Activator</b>, BundleActivator de la couche Services pour utiliser la méthode utilitaire <b>StringUtils#isBlank(String str)</b> de <b>commons-lang-2.4.jar</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void start(BundleContext context) throws Exception { <br />
&nbsp; StringUtils.isBlank(&quot;&quot;); <br />
&nbsp; System.out.println(&quot;commons-lang-2.4 : StringUtils#isBlank() called.&quot;); <br />
&nbsp;<br />
&nbsp; System.out.println(&quot;Start Bundle [&quot;+ context.getBundle().getSymbolicName() + &quot;], ClassLoader= &quot; &nbsp;+ Activator.class.getClassLoader()); <br />
}</div></div>
<h3>Runtime/Add Jar (commons-lang-1.0.jar)</h3>
<p><a href="#BundleAddJar" >Ajoutez la librairie</a> <b>commons-lang-1.0.jar</b> dans le Bundle <a href="#client" >org.akrogen.gestcv.simplemainclient</a></p>
<h3>Utilisation commons dans la couche Cliente</h3>
<p>Modifiez la méthode start de la classe org.akrogen.gestcv.simpleosgiclient.internal.<b>Activator</b>, BundleActivator de la couche Cliente pour utiliser la méthode utilitaire <b>StringUtils#isEmpty(String str)</b> de <b>commons-lang-1.0.jar</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void start(BundleContext context) throws Exception { <br />
&nbsp; StringUtils.isEmpty(&quot;&quot;); <br />
&nbsp; System.out.println(&quot;commons-lang-1.0.jar : StringUtils#isEmpty() called.&quot;); <br />
&nbsp;<br />
&nbsp; System.out.println(&quot;Start Bundle [&quot;+ context.getBundle().getSymbolicName() + &quot;], ClassLoader= &quot; &nbsp;+ Activator.class.getClassLoader()); <br />
}</div></div>
<p><a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#Run" >Relancez (via GestCV OSGi ) Equinox</a>, la console OSGi affiche ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; Start Bundle [org.akrogen.gestcv.domain], ClassLoader= org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@51052d <br />
commons-lang-2.4 : StringUtils#isBlank() called. <br />
Start Bundle [org.akrogen.gestcv.services], ClassLoader= org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@2a6f16 <br />
commons-lang-1.0.jar : StringUtils#isEmpty() called. <br />
...</div></div>
<p>ce qui montre qu&rsquo;il est possible d&rsquo;avoir une version d&rsquo;une librairie par Bundle sans avoir de conflit.</p>
<h1>Conclusion</h1>
<p>A cette étape nous avons vu que les Bundle OSGi gérent leur dépendances (avec d&rsquo;autres Bundles, avec des JAR internes au Bundle) via le fichier MANIFEST.MF avec <a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#AddRequireBundle" >Require-Bundle</a> ou <a href="http://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp-springdm-step2#AddImportPackage" >Import-Package</a>. Un Bundle expose ses classes qu&rsquo;il souhaite rendre visible aux autres Bundles via <a href="#ExportPackage" >Export-Package</a> qui est aussi une information contenu dans le MANIFEST.MF. Un Bundle a son propre ClassLoader. Les Bundles sont gérés par un conteneur OSGi (Equinox) qui fournit une console OSGi qui permet d&rsquo;arrêter/stopper à chaud des Bundles.</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Conception d&#8217;un client Eclipse RCP et serveur OSGI avec Spring DM [step2]</title>
		<link>https://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp_springdm_step2</link>
		<comments>https://blog.developpez.com/akrogen/p8138/plugin-eclipse/rcp_springdm_step2#comments</comments>
		<pubDate>Thu, 08 Oct 2009 10:28:31 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Vous pouvez trouver ce billet sur mon nouveau blog avec les informations mises a jour. Dans le billet précédant [step1] nous avons mis en place les 3 couches Client/Services/Domaine dans 3 projets Java qui font références entre eux avec le classique Java Build Path. Nous avons vu que ce type de dépendance engendrait les 2 problèmes de classes non protégées et de ClassLoader. Dans ce billet nous allons créer et lancer notre premier Bundle OSGi, [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Vous pouvez trouver <a href="http://angelozerr.wordpress.com/2009/11/10/rcp_springdm_step2" >ce billet sur mon nouveau blog</a> avec les informations mises a jour.</p>
<p>Dans le <a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1" >billet précédant [step1]</a> nous avons mis en place les 3 couches <b>Client/Services/Domaine</b> dans 3 projets Java qui font références entre eux avec le classique <b>Java Build Path</b>. Nous avons vu que ce type de dépendance engendrait les 2 problèmes de <a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1#ClassesNotProtected" >classes non protégées</a> et de <a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1#ClassLoader" >ClassLoader</a>. Dans ce billet nous allons <a href="#CreateBundle">créer</a> et <a href="#LaunchBundle" >lancer</a> notre premier <b>Bundle OSGi</b>, autrement dit nous allons <b>transformer le projet Java</b> org.akrogen.gestcv.domain <b>en Bundle OSGi et préparer l&rsquo;environnement OSGI</b> (<a href="#TargetPlatform" >Target Platform</a>). Dans le prochain billet nous transformerons les 2 autres projets Java en Bundle OSGi et verrons comment OSGi règle les 2 problèmes cités en introduction.  </p>
<p>Voici un schéma de ce que nous allons effectuer dans ce billet : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_Step2Overview.png" width="557" height="451" alt="" /></p>
<p>Ce schéma met en évidence plusieurs notions : </p>
<ul>
<li><a href="#CreateBundle" ><b>Bundle OSGi</b></a> : le projet Java <b>org.akrogen.gestcv.domain</b> devient un Bundle OSGi qui est un projet Eclipse Plug-in.
  </li>
<li><a href="#TargetPlatform" ><b>Target Platform</b></a> : ensemble de Bundles OSGi (JARs) nécéssaires au Bundle OSGi que nous créons dans le workspace Eclipse. Dans notre cas nous allons dépendre du Bundle OSGi <b>org.eclipse.osgi</b> qui contient l&rsquo;API OSGi.
  </li>
<li><b>Conteneur OSGi</b> (<a href="http://www.eclipse.org/equinox/">Equinox</a>) : hébérge et orchestre les Bundles OSGi (gère leur cycle de vie) provenant de notre workspace Eclipse et de la Target Platform. Nous utiliserons <a href="http://www.eclipse.org/equinox/">Equinox</a> qui est celui par défaut utilisé par Eclipse.</li>
<li><a href="#Dependances" ><b>Dependances</b></a> : les dépendances entre les Bundle OSGi s&rsquo;effectuent via le fichier <a href="#MANIFEST_MF" ><b>META-INF/MANIFEST.MF</b></a> et <b>plus par le Java Build Path</b> classique.</li>
<li><a href="#BundleActivator" ><b>BundleActivator</b></a> (classe Activator) : permet d&rsquo;éxecuter du code lors du lancement/arrêt du Bundle.</li>
</ul>
<p>Nous nous appuierons sur l&rsquo;ensemble de plugins <a href="http://www.eclipse.org/pde/" >PDE</a> (Plug-in Development Environment) qui permet de créer des Plug-ins Eclipse mais aussi des Bundles OSGI (un Plug-in Eclipse est en fait un Bundle OSGI). </p>
<p>Vous pouvez télécharger les projets <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step2/org.akrogen.gestcv_step2.zip">org.akrogen.gestcv_step2.zip</a> présentés dans ce billet.</p>
<p><span id="more-63"></span></p>
<h1>Mes premiers pas avec OSGi</h1>
<p>A mes débuts en OSGi j&rsquo;ai du lire beaucoup d&rsquo;articles et de documentations pour me rendre compte de la puissance d&rsquo;OSGi. J&rsquo;aimerais mettre en évidence tout ce que j&rsquo;adore dans OSGi à travers d&rsquo;exemples concrets que j&rsquo;améliorerais au fur et à mesure pour comprendre par la suite le mécanisme de <a href="http://www.springsource.org/osgi" >Spring DM</a>. Ma démarche est d&rsquo;expliquer OSGi par le code généré par PDE et ensuite expliquer les concepts d&rsquo;OSGi et pas l&rsquo;inverse comme dans la plupart du temps. Si vous ne connaissez pas OSGI, je vous conseille de lire l&rsquo;excellent article <a href="http://t-templier.developpez.com/tutoriel/java/osgi/osgi1/" >Programmation par composant avec la technologie OSGi (1ère partie)</a>. Il est en français et donne une bonne vision de l&rsquo;architecture de OSGI et de son jargon (Bundle&#8230;). </p>
<p>Lorsque j&rsquo;ai démarré avec OSGi, la difficulté que j&rsquo;ai eu a été d&rsquo;avoir une vision d&rsquo;ensemble de son architecture et de son Jargon (Bundle OSGi, <a href="http://www.eclipse.org/equinox/">Equinox</a>, Target Platform&#8230;.). Je vais faire une comparaison (très très simpliste) avec une architecture JEE basée sur un serveur (Tomcat, Jetty&#8230;) pour s&rsquo;imprégner des notions d&rsquo;OSGi. Mais attention, je ne dis pas que OSGi est équivalent à JEE!!! Loin de là.</p>
<h2>Architecture JEE</h2>
<p>Dans une architecture JEE basée sur un serveur, ce dernier joue le rôle de conteneur qui hébérge des applications WEB et qui orchestre le cycle de vie des application WEB (appel des Filtres JEE, des Servlets d&rsquo;une application WEB ). Un serveur contient des librairies JAR sur lesquelles s&rsquo;appuient les application WEB. Par exemple Tomcat contient la librairie <b>servlet-api.jar</b> dans son répertoire <b>common/lib</b> qui est l&rsquo;API standard JEE qui fournit les interfaces javax.servlet.<b>Filter</b>, javax.servlet.http.<b>HttpServletRequest</b>, javax.servlet.http.<b>HttpServletSession</b>.</p>
<p>Il existe plusieurs implémentations de serveurs, comme Tomcat, Jetty, etc. qui implémentent en interne ces interfaces et les applications WEB n&rsquo;utilisent que ces interfaces ce qui permet de  déployer une application WEB (en théorie si l&rsquo;application WEB n&rsquo;utilise pas des fonctionnalités spécifiques au serveur) sur différents serveurs.</p>
<h2>Architecture OSGi</h2>
<p>Dans une architecture OSGi, il y a aussi un conteneur qui hébérge des Bundles OSGi et qui orchestre leur cycle de vie. Un conteneur OSGi contient uniquement (du moins dans Equinox) une libraire JAR qui est l&rsquo;API d&rsquo;OSGi. Cette librairie est un Bundles OSGi sur lesquels s&rsquo;appuient tous les autres Bundles OSGi. Il est par contre possible de choisir les librairies JAR (Bundle) à ajouter au conteneur en définissant le <a href="#TargetPlatform" >Target Platform</a>. Eclipse fournit la librairie <b>org.eclipse.osgi_3.5.0.v20090520.jar</b> dans son répertoire <b>plugins</b> qui contient l&rsquo;API standard OSGi qui fournit les interfaces OSGi org.osgi.framework.<b>BundleActivator</b>, org.osgi.framework.<b>BundleContext</b> et que nous allons utiliser dans notre Bundle. Ce JAR contient aussi l&rsquo;implémentation (Equinox) de l&rsquo;API OSGi.</p>
<p>2 types de Bundles peuvent donc être lancés via le conteneur OSGi : </p>
<ul>
<li>les <b>Bundles du workspace</b> Eclipse qui sont les Bundles en cours de développement.
   </li>
<li>les <b>Bundles définis dans la Target Platform</b> qui par défaut sont les tous ceux contenus dans le répertoire <b>plugins</b> d&rsquo;Eclipse.
  </li>
</ul>
<p>Il existe plusieurs implémentations de conteneurs OSGi comme <a href="http://www.eclipse.org/equinox/">Equinox</a>, <a href="http://felix.apache.org/site/index.html" >Felix</a>, qui implémentent en interne ces interfaces et les Bundles OSGi n&rsquo;utilisent que ces interfaces ce qui permet de déployer un Bundle OSGi (en théorie s&rsquo;il n&rsquo;utilise pas des fonctionnalités spécifiques au conteneur) sur différents conteneurs OSGi. Les Plug-ins Eclipse sont des Bundles OSGi qui sont orchestrés par <a href="http://www.eclipse.org/equinox/">Equinox</a>. C&rsquo;est ce conteneur OSGi que nous utiliserons et qui est proposé par défaut.</p>
<h2>Récapitulatif</h2>
<p>Voici un tableau récapitulatif (très simpliste) qui compare les notions de Serveur JEE et conteneur OSGi : </p>
<table border="1" >
<tr>
<th>Serveur JEE</th>
<th>OSGi</th>
</tr>
<tr>
<td>Spécification Serveur JEE (conteneur)</td>
<td>Spécification Conteneur OSGi</td>
</tr>
<tr>
<td>Implémentation Serveur (Tomcat, Jetty&#8230;)</td>
<td>Implémentation conteneur OSGi (<a href="http://www.eclipse.org/equinox/">Equinox</a>, <a href="http://felix.apache.org/site/index.html" >Felix</a>, &#8230;)</td>
</tr>
<tr>
<td>Application WEB (WAR)</td>
<td>Bundle OSGi (JAR)</td>
</tr>
<tr>
<td>JAR dans common/lib (pour Tomcat) et dans WEB-INF/lib du WAR</td>
<td>JARs installés dans le conteneur et ceux embarqués par le bundle</td>
</tr>
<tr>
<td>Interface javax.servlet.*</td>
<td>Interface org.osgi.framework.*</td>
</tr>
<tr>
<td>Implémentation interfaces JEE par le Serveur (Tomcat, Jetty&#8230;)</td>
<td>Implémentation interfaces OSGi par le conteneur OSGi (<a href="http://www.eclipse.org/equinox/">Equinox</a>, <a href="http://felix.apache.org/site/index.html" >Felix</a>,&#8230;)</td>
</tr>
<tr>
<td>distribuable sous forme *.war</td>
<td>distribuable sous forme *.jar</td>
</tr>
</table>
<h1 id="CreateBundle" >Création de notre 1er Bundle</h1>
<p><a href="http://www.eclipse.org/pde/" >PDE</a> (Plug-in Development Environment) est un ensemble de plugins Eclipse qui fournit des outils pour créer, développer, tester des plugins Eclipse, des applications RCP et des Bundle OSGi. Un Plug-in Eclipse est en fait un Bundle OSGi. Dans cette section nous allons nous occuper de <b>transformer notre projet Java</b> org.akrogen.gestcv.domain <b>en Bundle OSGi</b> (projet Plug-in). Il est possible d&rsquo;effectuer cette tâche en sélectionnant le projet Java, bouton droit pour ouvrir le menu contextuel, puis sélectionnez le menu <b>Configure/Convert to Plug-in Projects&#8230;</b> mais cette méthode demande quand même des manipulations à effectuer ensuite (notemment pour gérer les dépendances via OSGi). Je préfère donc recommencer depuis le début et passer par les wizards (PDE) de création de Bundle OSGi.</p>
<h2 id="CreateBundle" >Création Bundle</h2>
<p>Ici nous allons créer le Bundle (projet Plug-in) <b>org.akrogen.gestcv.domain</b>. Pour cela sélectionnez le menu <b>File/New/Other</b> puis <b>Plug-in Development/Plug-in Project</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_NewBundle1.png" width="500" height="500" alt="" /></p>
<p>Cliquez sur le bouton <b>Next</b>, puis : </p>
<ul>
<li>renseignez le champs Project name par <b>org.akrogen.gestcv.domain</b>.</li>
<li>sélectionnez le bouton radio <b>an OSGI framework</b> car nous souhaitons développer un Bundle OSGi et pas un Plug-in Eclipse, puis la combo <b>standard</b> car notre Bundle n&rsquo;a pas besoin de fonctionnalités spécifiques à <a href="http://www.eclipse.org/equinox/">Equinox</a> (conteneur OSGi aussi utilisé pour les Plug-in Eclipses). Il pourra ainsi fonctionner sur n&rsquo;importe quel conteneur OSGI.</li>
</ul>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_NewBundle2.png" width="500" height="596" alt="" /></p>
<p>Cliquez sur le bouton <b>Next</b>, le wizard qui permet de configurer le Bundle s&rsquo;ouvre en pré-remplissant la plupart des champs. Voici une copie d&rsquo;écran de ce wizard où j&rsquo;ai mis en rouge ce que j&rsquo;ai modifié : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_NewBundle3.png" width="500" height="596" alt="" /></p>
<ul>
<li>le champs <b>ID</b> est l&rsquo;identifiant de notre Bundle. Il est pré-rempli en utilisant le nom du projet.</li>
<li>le champs <b>Version</b> est la version de notre Bundle. Il est pré-remplit avec <b>1.0.0.qualifier</b>.</li>
<li>le champs <b>Name</b> est le nom de notre Bundle. Cette information n&rsquo;est pas très importante. Dans ce billet je l&rsquo;ai renseigné avec <b>GestCV Domain</b></li>
<li>le champs <b>Provider</b> est généralement le nom de la société qui créé le Bundle. Cette information n&rsquo;est pas très importante. Dans ce billet je l&rsquo;ai renseigné avec <b>Akrogen</b></li>
<li>le champs <b>Execution Environment</b> indique la version minimale de la JRE pour que le Bundle puisse être éxécuté.</li>
<li>l&rsquo;option <b>generate an activator, a Java class that controls the plug-in&rsquo;s life cycle</b> est cochée par défaut. Nous la laissons cochée pour générer <a href="#BundleActivator" >une classe Activator</a> dans ce Bundle, même si au final nous n&rsquo;en n&rsquo;aurrons pas vraiment besoin pour ce Bundle .</li>
</ul>
<p>Cliquez sur le bouton <b>Finish</b> pour générer le projet Bundle OSGi. </p>
<h2>Contenu Bundle créé</h2>
<p>Voici le contenu du projet Bundle OSGi générée par PDE : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGIProject.png" width="741" height="259" alt="" /></p>
<h3 id="MANIFEST_MF" >MANIFEST.MF</h3>
<p>Un projet Bundle OSGi (projet Plug-in) est un projet Java classique. PDE à généré le fichier classique de méta données <b>META-INF/MANIFEST.MF</b> mais avec des informations de type <b>Bundle*</b> (Bundle-name&#8230;). Ces informations sont les méta donnés du Bundle OSGi (provenant du wizard de création du Bundle). Elle permettent d&rsquo;indiquer l&rsquo;identifiant du Bundle (Bundle-SymbolicName), sa version (Bundle-Version) la JRE minimale dans laquelle le Bundle peut être exécuté (Bundle-RequiredExecutionEnvironment). Toutes ces méta donnés sont ensuite utilisées par le conteneur OSGi pour par exemple tester si le Bundle peut être lancé en testant la version de la JRE.</p>
<h3 id="BundleActivator" >BundleActivator</h3>
<p>PDE à généré la classe org.akrogen.gestcv.domain.<b>Activator</b> (dù à l&rsquo;option <b>generate an activator, a Java class that controls the plug-in&rsquo;s life cycle</b> coché dans le wizard PDE). Voici le code généré de cette classe :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.domain; <br />
&nbsp;<br />
import org.osgi.framework.BundleActivator; <br />
import org.osgi.framework.BundleContext; <br />
&nbsp;<br />
public class Activator implements BundleActivator { <br />
&nbsp;<br />
&nbsp; /* <br />
&nbsp; &nbsp;* (non-Javadoc) <br />
&nbsp; &nbsp;* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) <br />
&nbsp; &nbsp;*/ <br />
&nbsp; public void start(BundleContext context) throws Exception { <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; /* <br />
&nbsp; &nbsp;* (non-Javadoc) <br />
&nbsp; &nbsp;* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) <br />
&nbsp; &nbsp;*/ <br />
&nbsp; public void stop(BundleContext context) throws Exception { <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<p>Cette classe implémente l&rsquo;interface OSGi org.osgi.framework.<b>BundleActivator</b> qui permet de personaliser le lancement (start) et l&rsquo;arrêt du bundle (stop). En effet un Bundle OSGi suit un cycle de vie orchestré toujours par le conteneur d&rsquo;OSGi. Pour plus d&rsquo;information sur ce sujet je vous conseille de lire la section <a href="http://t-templier.developpez.com/tutoriel/java/osgi/osgi1/#L3.2" >3.2. Cycle de vie des bundles</a>.</p>
<p>Pour indiquer au conteneur OSGi que l&rsquo;on souhaite personnaliser le lancement/arrêt du Bundle via le BundleActivator org.akrogen.gestcv.domain.<b>Activator</b>, ceci s&rsquo;effectue via  la méta donnée <b>Bundle-Activator</b> du fichier <b>MANIFEST.MF</b> comme ceci : </p>
<p><code class="codecolorer text default"><span class="text">Bundle-Activator: org.akrogen.gestcv.domain.Activator</span></code></p>
<p>La classe org.akrogen.gestcv.domain.<b>Activator</b> implémente l&rsquo;interface OSGi org.osgi.framework.<b>BundleActivator</b>. Notre Bundle doit donc faire référence à un jar qui contient cette interface. Eclipse Galileo fournit le JAR <b>/plugins/org.eclipse.osgi_3.5.0.v20090520.jar</b> qui contient les interfaces OSGi. Ce JAR est contenu dans le répertoire <b>plugins</b> de Eclipse. C&rsquo;est en fait un Bundle OSGi dont on peut faire dépendre n&rsquo;importe quel Bundle (pour l&rsquo;utiliser nous devons le mettre dans la <a href="#TargetPlatform" >Target Platform</a>). Le JAR <b>/plugins/org.eclipse.osgi_3.5.0.v20090520.jar</b> qui est un Bundle à aussi un <b>MANIFEST.MF</b> comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">... <br />
Bundle-SymbolicName: org.eclipse.osgi; singleton:=true <br />
Export-Package: ... <br />
org.osgi.framework;version=&quot;1.5&quot; <br />
...</div></div>
<p>La méta-donnée <b>Export-Package</b> permet d&rsquo;indiquer quels sont les packages (et version) qui sont exportés,</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Export-Package: ... <br />
org.osgi.framework;version=&quot;1.5&quot; <br />
...</div></div>
<p>autrement dit <b>seules les classes qui sont incluses dans les packages exportés (e : org.osgi.framework) sont accéssibles via d&rsquo;autres Bundles</b>.</p>
<h3 id="Dependances" >Dépendances entre Bundle</h3>
<p>Il existe deux manières d&rsquo;utiliser les classes (exportès) d&rsquo;un autre Bundle : </p>
<ul>
<li><a href="#RequireBundle" >Require-Bundle</a> : ce type de dépendance permet de dépendre explicitement sur un Bundle identifié par son ID. Les classes de tous les packages exportés par le Bundle dont on souhaite dépendre sont accéssibles. Voici ce que donnerait ce type de dépendance :<br />
<code class="codecolorer text default"><span class="text">Require-Bundle: org.eclipse.osgi</span></code></li>
<li><a href="#ImportPackage" >Import-Package</a> : ce type de dépendance indique juste que l&rsquo;on souhaite importer un package particlier. Ici nous ne connaissons pas le Bundle qui expose ces packages. C&rsquo;est ce type de dépendance qui a été utilisé par PDE :<br />
<code class="codecolorer text default"><span class="text">Import-Package: org.osgi.framework</span></code>. Ce package provient du Bundle d&rsquo;ID <b>org.eclipse.osgi</b></li>
</ul>
<h4 id="ImportPackage" >Import-Package</h4>
<p>La gestion de dépendances de notre Bundle <b>org.akrogen.gestcv.domain</b> au Bundle <b>org.eclipse.osgi</b> s&rsquo;effectue via le MANIFEST.MF de la manière suivante : </p>
<p><code class="codecolorer text default"><span class="text">Import-Package: org.osgi.framework;version=&quot;1.3.0&quot;</span></code></p>
<p>Ceci signifie que l&rsquo;on souhaite utiliser les classes qui sont dans le package <b>org.osgi.framework</b> de version au minimum <b>1.3.0</b>. </p>
<p>Ce type de dépendance ne lie pas directement notre Bundle <b>org.akrogen.gestcv.domain</b> au Bundle <b>org.eclipse.osgi</b>. L&rsquo;avantage de cette technique est que si Eclipse effectue un refactoring en mettant les classes du package <b>org.osgi.framework</b> dans un autre Bundle que <b>org.eclipse.osgi</b>, notre Bundle continuera de fonctionner. Ceci signifie que l&rsquo;on souhaite utiliser les classes qui sont dans le package <b>org.osgi.framework</b> de version au minimum <b>1.3</b>. </p>
<h4 id="RequireBundle ">Require-Bundle</h4>
<p>PDE a généré <code class="codecolorer text default"><span class="text">Import-Package: org.osgi.framework;version=&quot;1.3.0&quot;</span></code> pour pouvoir utiliser les classes du package <b>org.osgi.framework</b> dans notre Bundle. Mais Il aurait été aussi possible d&rsquo;utiliser <b>Require-Bundle</b>Require-Bundle: org.eclipse.osgi</p>
<h1 id="PDEEditor" >Configuration de notre 1er Bundle (PDE Editor)</h1>
<p>Si vous souhaitez <a href="#LaunchBundle"> lancer directement le Bundle</a>, vous pouvez vous affranchir de lire cette section. PDE founit l&rsquo;<b>Editor PDE</b> qui s&rsquo;ouvre en éditant le fichier MANIFEST.MF. Il permet de gérer (dans notre cas de Bundle) le fichier MANIFEST.MF à travers plusieurs onglets.<br />
Si vous avez acceptez d&rsquo;ouvrir la perspective PDE ou si vous double cliquez sur le fichier MANIFEST.MF, l&rsquo;editor PDE s&rsquo;ouvre. Il est composé de plusieurs onglets : </p>
<ul>
<li>Onglet <a href="#Overview" >Overview</a>: donne une vue d&rsquo;ensemble du Bundle OSGi sur les méta-données autre que les dépendances.
  </li>
<li>Onglet <a href="#Dependencies" >Dependencies</a>: pemet de gérer les dépendances de type <a href="#RequireBundle" >Require-Bundle</a>. et <a href="#ImportPackage" >Import-Package</a>.
  </li>
<li>Onglet <a href="#Runtime" >Runtime</a>: pemet de gérer les packages exportés <b>Export-Package</b> (nous y reviendrons dans le prochain billet).
  </li>
<li>Onglet <a href="#OngletMANIFEST_MF" >MANIFEST.MF</a>: affiche le contenu du fichier MANIFEST.MF géré dans l&rsquo;editor.
  </li>
<li>Onglet <b>build.properties</b>: j&rsquo;y reviendrais lorsque nous en aurrons besoin.</li>
</ul>
<h2 id="Overview" >Onglet Overview</h2>
<p>Cet onglet donne une vue d&rsquo;ensemble du Bundle OSGi sur les méta-données autre que les dépendances : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_PDEOverview.png" width="1039" height="768" alt="" /></p>
<p>On retrouve les informations du Bundle comme l&rsquo;ID (Bundle-SymbolicName), l&rsquo;Activator (Bundle-Activator), la JRE (Bundle-RequiredExecutionEnvironment),&#8230; Les liens <b>Launch the framework</b> et <b>Launch the framework in Debug mode</b> permettent de lancer <a href="http://www.eclipse.org/equinox/">Equinox</a> et par conséquent lancer tous les Bundles du Workspace et ceux définis dans la <a href="#TargetPlatform" >Target Platform</a>.</p>
<h2 id="Dependencies" >Onglet Dependencies</h2>
<p>Cet onglet pemet de gérer les dépendances de type <a href="#RequireBundle" >Require-Bundle</a>. et <a href="#ImportPackage" >Import-Package</a>.</p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_PDEDependencies.png" width="1039" height="768" alt="" /></p>
<h3 id="AddRequireBundle" >Add Require-Bundle</h3>
<p>Ici nous allons dépendre explicitement du Bundle OSGi <b>org.eclipse.osgi</b> au lieu du package. Avant d&rsquo;effectuer cette action, supprimez la dépendance Import-Package sur <b>org.osgi.framework</b>. Pour cela, allez dans l&rsquo;onglet <b>Runtime</b>, sélectionnez <b>org.osgi.framework</b> dans la <b>liste Import Packages</b> puis cliquez sur le bouton <b>Remove</b>.</p>
<p>Cliquez sur le bouton <b>Add&#8230;</b> à coté de la <b>liste Required Plug-in</b>, ceci ouvre la fenêtre de dialogue qui propose tous les Bundles disponibles (ceux du Workspace et ceux de la <a href="#TargetPlatform" >Target Platform</a>. Recherchez le Bundle <b>org.eclipse.osgi</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_AddRequireBundle1.png" width="600" height="500" alt="" /></p>
<p>Cliquez sur <b>OK</b>, le Bundle est ajouté : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_AddRequireBundle2.png" width="548" height="509" alt="" /></p>
<p>Sauvegardez ensuite pour enregistrer le MANIFEST.MF. Le Bundle <b>org.akrogen.gestcv.domain</b> doit toujours compiler.</p>
<h3 id="AddImportPackage" >Add Import-Package</h3>
<p>Ici nous allons utiliser (à nouveau) le package <b>org.osgi.framework</b> au lieu du Bundle <b>org.eclipse.osgi</b>. Avant d&rsquo;effectuer cette action, supprimez la dépendance Require-Bundle sur <b>org.eclipse.osgi</b>. Pour cela, allez dans l&rsquo;onglet <b>Dependencies</b>, sélectionnez <b>org.eclipse.osgi</b> dans la <b>liste Required Plug-in</b> puis cliquez sur le bouton <b>Remove</b>. Cliquez sur le bouton <b>Add&#8230;</b> à coté de la <b>liste Imported Packages</b>, ceci ouvre la fenêtre de dialogue qui propose tous les packages disponibles (packages exportès par l&rsquo;ensemble des Bundle du Workspace et de la <a href="#TargetPlatform" >Target Platform</a>. Recherchez le package <b>org.osgi.framework</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_AddImportPackage1.png" width="398" height="501" alt="" /></p>
<p>Cliquez sur <b>OK</b>, le package est ajouté : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_AddImportPackage2.png" width="593" height="430" alt="" /></p>
<p>Sauvegardez ensuite pour enregistrer le MANIFEST.MF. Ici nous avons une dépendance sur la version <b>1.5.0</b> mais nous la changeons en version <b>1.3.0</b> (via l&rsquo;<a href="#OngletMANIFEST_MF" >onglet MANIFEST.MF</a>) car cette version nous suffit (c&rsquo;est celle d&rsquo;ailleurs générée par PDE).</p>
<h2 id="Runtime" >Onglet Runtime</h2>
<p>L&rsquo;onglet Runtime permet de sélectionner les packages à exporter et définir si besoin des dépendances à des (JAR) qui serait stockés dans le Bundle : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_Runtime.png" width="1019" height="768" alt="" /></p>
<h2 id="OngletMANIFEST_MF" >Onglet MANIFEST.MF</h2>
<p>Cet onglet affiche le contenu du fichier <b>MANIFEST.MF</b> géré dans l&rsquo;editor : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_PDEMANIFEST.MF.png" width="1039" height="768" alt="" /></p>
<h1 id="LaunchBundle" >Lancement de notre 1er Bundle</h1>
<p>A cette étape nous souhaitons lancer notre Bundle <b>org.akrogen.gestcv.domain</b> (qui ne fait rien pour l&rsquo;instant). Le lancement d&rsquo;un Bundle consiste à démarer le conteneur OSGi (<a href="http://www.eclipse.org/equinox/">Equinox</a>) en lui indiquant les Bundles qu&rsquo;il doit executer. Pour créér cette configuration, ouvrez le fichier MANIFEST.MF, puis cliquez sur le lien <b>Launch the framework in Debug mode</b> de l&rsquo;onglet Overview. </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_LauchDomainBundle.png" width="1070" height="768" alt="" /></p>
<p>Cette action lance <a href="http://www.eclipse.org/equinox/">Equinox</a> avec les Bundles du workspace et ceux définis dans <a href="#TargetPlatform" >la Target Platform</a> définit par défaut. Dans mon cas je suis dans un environnement Java5 et j&rsquo;ai l&rsquo;erreur suivante dans la console : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGIConsoleJavaSE-1.6.png" width="1270" height="112" alt="" /></p>
<p>Cette erreur est intéressante car elle montre que le conteneur OSGi a lancé un Bundle (lequel?) qui a définit dans son MANIFEST.MF son Bundle-RequiredExecutionEnvironment avec JavaSE-1.6. Ce Bundle vient de la Target Platform par défaut qui va en fait executer tous les Bundles (Plug-in généralement) de Eclipse (répertoire plugins). Dans notre cas nous n&rsquo;avons pas besoin de tous ces plugins pour éxécuter notre Bundle et c&rsquo;est pour cela que nous personnaliserons <a href="#TargetPlatform" >notre Target Platform</a>. Mais nous pouvons quand même continuer nos tests. </p>
<p>Dans la console OSGi, saisissez la commande <b>ss</b> (Short Status): </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGIConsoleSS.png" width="824" height="429" alt="" /></p>
<p>Cette commande permet d&rsquo;afficher la liste des Bundles qui ont été résolus, activés, non résolus. Vous pourrez constater que notre Bundle OSGi <b>org.akrogen.gestcv.domain</b> a le status <b>ACTIVE</b>, ce qui signifie que tout s&rsquo;est bien passé.</p>
<h2 id="Run" >Run</h2>
<p>A cette étape nous pouvons lancer notre Bundle. Si vous ne souhaitez pas paramétrer la <a href="#TargetPlatform" >Target Platform</a>, importer le launch <a href="#OSGiGestCV_launch" >OSGi GestCV.launch</a>. Mais si vous ne connaissez pas la notion de Target Platform, je vous conseille de lire ce qui suit.</p>
<p>Le clic sur le lien <b>Launch the framework in Debug mode</b> de l&rsquo;onglet Overview a en fait généré une Configuration de nom <b>OSGI Framework</b>. Pour éditer cette configuration, cliquez sur l&rsquo;icône du menu globale <b>Run</b>, puis <b>Run Configurations&#8230;</b>. </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_RunConfigurations.png" width="316" height="192" alt="" /></p>
<p>La fenêtre de dialogue <b>Run Configurations</b> s&rsquo;ouvre et montre la configuration de lancement d&rsquo;Equinox. On peut voir que les Bundles lancés sont ceux du workspace et ceux de la Target Platform (qui sont en fait tous les plugins de Eclipse).</p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_RunConfiguration.png" width="1040" height="720" alt="" /></p>
<p>Profitez pour renommez le nom de la Configuration <b>OSGI Framework</b> en <b>OSGI GestCV</b>.</p>
<h2 id="TargetPlatform" >Target Platform</h2>
<p>Comme nous l&rsquo;avons vu précedemment la Target Platform par défaut ne nous convient pas car elle contient tous les Bundles de Eclipse. Nous allons définir notre propre Target Platform qui sera ausi ensuite utillisée par notre Application RCP. Pour personnaliser notre Target Platform, accédez au menu <b>Window/Preferences</b> qui ouvre la fenêtre de Preferences puis accédez dans le Treeview au noeud <b>Plug-in Development/Target Platform</b>. La Target Platform par défaut est sélectionnée : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_TargetPlatform1.png" width="626" height="651" alt="" /></p>
<p>Cliquez sur le bouton <b>Add</b>, puis sélectionnez <b>Template</b> puis <b>Base RCP (Binary Only)</b>, qui est un template qui permet de définir notre Target Platform qu&rsquo;avec les Bundles OSGi nécéssaires pour une application RCP  : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_TargetPlatform2.png" width="664" height="647" alt="" /></p>
<p>Cliquez sur le bouton <b>Next</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_TargetPlatform3.png" width="664" height="647" alt="" /></p>
<p>Nommez la Target Platform <b>GestCV Target Platform</b>. L&rsquo;onglet locations indique qu&rsquo;il y a 31 Bundles qui sont concernés. Si vous cliquez sur l&rsquo;onglet Content, vous pourrez visualiser les 31 Bundles concernés : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_TargetPlatform4.png" width="664" height="647" alt="" /></p>
<p>Cliquez sur le bouton <b>Finish</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_TargetPlatform5.png" width="626" height="667" alt="" /></p>
<p>Cliquez sur le bouton <b>OK</b> le workspace se recompile. Relancez le Bundle, la console ne doit plus afficher d&rsquo;erreurs (car le Bundle JavaSE-1.6 n&rsquo;est plus dans la Target Platform) et le nombre de Bundle lancé a considérablement diminué : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_OSGIConsoleSSTP.png" width="847" height="325" alt="" /></p>
<p>Si vous revenez dans Run Configuration, vous pourrez constater que la plupart des Bundles (Plug-In) de Eclipse sont décochés : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_RunConfigurationWithCustomTP.png" width="1040" height="720" alt="" /></p>
<h2>Console OSGi</h2>
<p>Comme nous avons pu le voir, le conteneur OSGi peut être interrogé via des commandes (ex : ss) dans la console Eclipse. L&rsquo;article <a href="http://www.ibm.com/developerworks/library/os-ecl-osgiconsole/index.html" >Explore Eclipse&rsquo;s OSGi console</a> liste toutes les commandes disponibles.</p>
<p>Nous allons modifier la classe org.akrogen.gestcv.domain.<b>Activator</b> pour tracer le démmarage (start) et l&rsquo;arrêt (stop) de notre Bundle <b>org.akrogen.gestcv.domain</b>. Pour cela modifier le code de cette classe comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">... <br />
public class Activator implements BundleActivator { <br />
&nbsp; public void start(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Start Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp; &nbsp;<br />
&nbsp; public void stop(BundleContext context) throws Exception { <br />
&nbsp; &nbsp; System.out.println(&quot;Stop Bundle [&quot; + context.getBundle().getSymbolicName() + &quot;]&quot;); <br />
&nbsp; } <br />
}</div></div>
<p>Relancez le Bundle OSGi via la configuration <b>OSGi GestCV</b>,la console OSGi affiche ceci : </p>
<p><code class="codecolorer text default"><span class="text">osgi&gt; Start Bundle [org.akrogen.gestcv.domain]</span></code></p>
<p>Notre méthode org.akrogen.gestcv.domain.<b>Activator#start()</b> a été appelée. En effectuant <b>ss</b>, dans la console, un ID technique est associé à chaque Bundle. Dans mon cas j&rsquo;ai :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">0 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.eclipse.osgi_3.5.0.v20090520 <br />
7 &nbsp;ACTIVE &nbsp; &nbsp; &nbsp;org.akrogen.gestcv.domain_1.0.0.qualifier</div></div>
<p>Notre Bundle <b>org.akrogen.gestcv.domain</b> à un ID égale à 7 et le Bundle <b>org.eclipse.osgi</b> sur lequel on dépend a un ID égale à 0.</p>
<p>Saisissez dans la console <b>stop 7</b></p>
<p>La consle OSGi affiche :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; stop 7 <br />
Stop Bundle [org.akrogen.gestcv.domain]</div></div>
<p>Notre méthode org.akrogen.gestcv.domain.<b>Activator#stop()</b> a été appelée.</p>
<p>Saisissez dans la console <b>start 7</b></p>
<p>La consle OSGi affiche :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; start 7 <br />
Start Bundle [org.akrogen.gestcv.domain]</div></div>
<p>Ceci montre qu&rsquo;il est possible d&rsquo;arrêter/stopper des Bundles OSGi à chaud sans devoir redémarrer le conteneur OSGi. Si vous stoppez le Bundle <b>org.eclipse.osgi</b> via la commande <code class="codecolorer text default"><span class="text">stop 0</span></code>, la console affiche :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">osgi&gt; stop 0 <br />
osgi&gt; Stop Bundle [org.akrogen.gestcv.domain]</div></div>
<p>L&rsquo;arrêt du Bundle <b>org.eclipse.osgi</b> arrête aussi notre Bundle <b>org.akrogen.gestcv.domain</b> car il est lié à ce premier.</p>
<p>Saisissez dans la console <b>start 0</b>, la console affiche une erreur :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">ss <br />
Exception in thread &quot;OSGi Console&quot; java.lang.IllegalStateException: BundleContext is no longer valid <br />
&nbsp; at org.eclipse.osgi.framework.internal.core.BundleContextImpl.checkValid(BundleContextImpl.java:1000) <br />
&nbsp; at org.eclipse.osgi.framework.internal.core.BundleContextImpl.getService(BundleContextImpl.java:657) <br />
&nbsp; at org.eclipse.osgi.framework.internal.core.FrameworkConsole.getServices(FrameworkConsole.java:377) <br />
&nbsp; at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(FrameworkConsole.java:300) <br />
&nbsp; at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(FrameworkConsole.java:288) <br />
&nbsp; at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(FrameworkConsole.java:224) <br />
&nbsp; at java.lang.Thread.run(Unknown Source)</div></div>
<p>J&rsquo;explique cette erreur par le fait que nous avons arrêter le bundle primordial OSGi (qui définit l&rsquo;API OSGi).</p>
<h1 id="OSGiGestCV_launch" >OSGi GestCV.launch</h1>
<h2>Import Launch</h2>
<p>Dans le zip <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step2/org.akrogen.gestcv_step2.zip">org.akrogen.gestcv_step2.zip</a> vous trouverez un fichier <b>OSGi GestCV.launch</b> qui est un export du launch <a href="#Run" >OSGi GestCV</a> qui a été créé dans ce billet. Pour importer ce launch, accéder au menu  <b>File/Import</b> puis sélectionnez le noeud <b>Run/Debug->Launch Configurations</b></p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_ImportLaunchTP1.png" width="470" height="550" alt="" /></p>
<p>Cliquez sur le bouton <b>Next</b> puis sélectionnez le fichier launch <b>OSGi GestCV.launch</b> du zip : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_ImportLaunchTP2.png" width="510" height="550" alt="" /></p>
<p>Cliquez sur le bouton <b>Finish</b> pour importer le launch. Cet import a créé le launch <b>OSGi GestCV</b> qui permet entre autres de sélectionner que les bundles dont on a besoin dans la Target Platform. Pour l&rsquo;utiliser, cliquez sur l&rsquo;icône du menu globale <b>Run</b>, puis <b>Run Configurations&#8230;</b>. Le launch <b>OSGi GestCV</b> doit apparaître, avec tous les Bundles du workspace et ceux de la Target Platform personnalisée, sélectionnez le et cliquez sur le bouton <b>Run</b>.</p>
<h2>Export Launch</h2>
<p>L&rsquo;export d&rsquo;un launch de type <b>OSGi Framework</b> permet dans un projet de partager sa Target Platform avec d&rsquo;autres développeurs. Pour effectuer un export de launch de type <b>OSGi Framework</b>, accéder au menu  <b>File/Export</b> puis sélectionnez le noeud <b>Run/Debug->Launch Configurations</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_ExportLaunchTP1.png" width="479" height="644" alt="" /></p>
<p>Cliquez sur le launch <b>OSGi GestCV</b> puis sur le bouton <b>Finish</b> pour exporter ce launch dans un fichier <b>OSGi GestCV.launch</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_ExportLaunchTP2.png" width="479" height="644" alt="" /></p>
<h1>Conclusion</h1>
<p>Dans ce billet nous avons créé notre premier Bundle OSGi et configuré la Target Platform. Nous avons vu que le conteneur OSGi fournissait uen console OSGi qui permet de d&rsquo;administrer les Bundles (arrêt, lancement des Bundles). Dans le prochain billet nous allons transformer les 2 autres projets Jave Services et Client en Bundle OSGi, ce qui nous permettra de mettre beaucoup plus en valeur OSGi.</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Conception d&#8217;un client Eclipse RCP et serveur OSGI avec Spring DM [step1]</title>
		<link>https://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp_springdm_step1</link>
		<comments>https://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp_springdm_step1#comments</comments>
		<pubDate>Thu, 01 Oct 2009 14:03:17 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Vous pouvez trouver ce billet sur mon nouveau blog avec les informations mises a jour. Dans le billet précédant [step0], j&#8217;ai présenté ce que je souhaitais effectuer dans les billets intitulés Conception d&#8217;un client Eclipse RCP et serveur OSGI avec Spring DM. Pour rappel, mon idée est d&#8217;expliquer pas à pas comment créer une application cliente eclipse RCP qui communiquera avec des services hébérgés sur un serveur OSGI. L&#8217;application RCP affichera une liste d&#8217;utilisateurs User [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Vous pouvez trouver <a href="http://angelozerr.wordpress.com/2009/11/10/rcp_springdm_step1" >ce billet sur mon nouveau blog</a> avec les informations mises a jour.</p>
<p>Dans le <a href="http://blog.developpez.com/akrogen/p8105/plugin-eclipse/rcp-springdm-step0" >billet précédant [step0]</a>, j&rsquo;ai présenté ce que je souhaitais effectuer dans les billets intitulés <b>Conception d&rsquo;un client Eclipse RCP et serveur OSGI avec Spring DM</b>. Pour rappel, mon idée est d&rsquo;expliquer pas à pas comment créer une application cliente eclipse RCP qui communiquera avec des services hébérgés sur un serveur OSGI. L&rsquo;application RCP affichera une liste d&rsquo;utilisateurs <a href="#User" >User</a> récupérée via un service <a href="#UserService">UserService</a> qui sera hébérgé sur le serveur OSGI.  Dans ce billet nous n&rsquo;allons pas encore faire d&rsquo;OSGI, mais nous allons créer un client (Java main) qui va communiquer avec un service <a href="#UserService">UserService</a> qui retournera une liste de <a href="#User" >User</a>. Ce service qui est une interface sera récupéré via une factory de services <a href="#ServicesFactory">ServicesFactory</a>. Nous découperons chacune de ces couches (Client/Service/Domain (POJO User)) dans un projet Java distinct.</p>
<p>Voici un schéma de ce que nous allons effectuer dans ce billet : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_Step1Overview.png" width="557" height="445" alt="" /></p>
<p>Ce schéma met en évidence trois projets Java : </p>
<ul>
<li><a href="#domain" >org.akrogen.gestcv.domain</a> (POJO) : projet Java qui contient les POJO (domaine de l&rsquo;application) entre autres le POJO <a href="#User" >User</a>.</li>
<li><a href="#services" >org.akrogen.gestcv.services</a> (Services) : projet Java qui contient le service <a href="#UserService" >UserService</a> qui permet de retourner une liste d&rsquo;utilisateurs <a href="#User" >User</a>.</li>
<li><a href="#client" >org.akrogen.gestcv.simplemainclient</a> (Client) : projet Java qui consomme dans un main Java le service <a href="#UserService" >UserService</a>.</li>
</ul>
<p>Les dépendances entre les projets sont gérées classiquement via le <a href="#JavaBuildPathAddProject" >Java Build Path</a> du projet. Nous verrons en fin de ce billet les 2 problémes courant que nous rencontrons avec ce type de dépendance classique : </p>
<ul>
<li>il est <a href="#ClassesNotProtected" ><b>impossible</b> de rendre inaccéssible l&rsquo;utilisation de certaines classes</a> d&rsquo;un projet Java aux classes d&rsquo;un autre projet Java. Ce problème sera résolu via OSGI dans le prochain billet en indiquant les packages que l&rsquo;on souhaite exposer.</li>
<li><a href="#ClassLoader" >tous les projets Java partagent le même ClassLoader</a>, ce qui peut poser des problèmes lorsque l&rsquo;on souhaite utiliser des versions différentes d&rsquo;une librairie dans les projets Java. Ce problème sera résolu via OSGI dans le prochain billet car chaque Bundle OSGI a son propre ClassLoader.</li>
</ul>
<p>Vous pouvez télécharger les projets <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step1/org.akrogen.gestcv_step1.zip">org.akrogen.gestcv_step1.zip</a> et <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step1/org.akrogen.gestcv_step1-commons-lang.zip">org.akrogen.gestcv_step1-commons-lang.zip</a> (zip qui contient les projets Java qui utilisent 2 versions de la librairie Apache <a href="http://commons.apache.org/lang/" >commans-lang*.jar</a> et qui montre en évidence le problème de ClassLoader) présentés dans ce billet.</p>
<p><span id="more-62"></span></p>
<h1>Prérequis</h1>
<p>Pour la rédaction de mes billets, j&rsquo;ai <a href="http://www.eclipse.org/downloads/" >téléchargé la dernière version d&rsquo;Eclipse</a> qui actuellement est <b>Eclipse Galileo</b> (Eclipse 3.5). Je conseille plus exactement de télécharger la distribution <a href="http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/galileo/SR1/eclipse-rcp-galileo-SR1-win32.zip" >Eclipse for RCP/Plug-in Developers (183 MB )</a>. Cette distribution est faite pour développer des applications RCP, les sources de l&rsquo;API RCP y sont disponibles. Cependant je pense que dans un billet je vais devoir utiliser une application WEB classique. Un serveur (Tomcat) sera nécéssaire et j&rsquo;utiliserais sûrement la distribution <a href="http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/galileo/SR1/eclipse-jee-galileo-SR1-win32.zip" >Eclipse IDE for Java EE Developers (189 MB )</a>. Dans la suite des billets nous aurrons aussi besoin d&rsquo;une Base de Données (mais j&rsquo;y reviendrais).</p>
<h1 id="domain" >org.akrogen.gestcv.domain</h1>
<p>Le projet <b>org.akrogen.gestcv.domain</b> est le domaine applicatif du projet, il contiendra tous les POJO de notre projet.</p>
<h2 id="NewJavaProject" >Création Java Project</h2>
<p>Ici nous allons créer le projet Java <b>org.akrogen.gestcv.domain</b> pour cela sélectionnez le menu <b>File/New/Other</b> puis <b>Java Project</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaProject1.png" width="500" height="500" alt="" /></p>
<p>Cliquez sur Next et renseignez le champs Project name avec <b>org.akrogen.gestcv.domain</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaProject2.png" width="500" height="696" alt="" /></p>
<p>Cliquez sur <b>Finish</b> le projet Java est créé. </p>
<h2 id="User" >Classe User</h2>
<p>Créez la classe org.akrogen.gestcv.domain.<b>User</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.domain; <br />
&nbsp;<br />
public class User { <br />
&nbsp;<br />
&nbsp; private String login; <br />
&nbsp; private String password; <br />
&nbsp;<br />
&nbsp; public User(String login, String password) { <br />
&nbsp; &nbsp; setLogin(login); <br />
&nbsp; &nbsp; setPassword(password); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public String getLogin() { <br />
&nbsp; &nbsp; return login; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void setLogin(String login) { <br />
&nbsp; &nbsp; this.login = login; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public String getPassword() { <br />
&nbsp; &nbsp; return password; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void setPassword(String password) { <br />
&nbsp; &nbsp; this.password = password; <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<h1 id="services" >org.akrogen.gestcv.services</h1>
<p>Nous allons créer le projet Java <b>org.akrogen.gestcv.services</b> qui sera notre couche service qui contiendra le service <a href="#UserService">UserService</a> qui retournera la liste des <a href="#User">User</a>. Pour cela, <a href="#NewJavaProjec" >créez le projet Java</a> <b>org.akrogen.gestcv.services</b>. Ce projet doit faire référence au projet Java <a href="#domain" >org.akrogen.gestcv.domain</a> car il doit retourner une liste de POJO <a href="#User">User</a>.</p>
<h2 id="JavaBuildPathAddProject">Java Build Path/Add Project</h2>
<p>Ici nous allons ajouter au projet <a href="#services" >org.akrogen.gestcv.services</a> une dépendance sur le projet <a href="#domain" >org.akrogen.gestcv.domain</a>. Pour cela sélectionnez le projet <a href="#services" >org.akrogen.gestcv.services</a><br />
puis cliquez sur le bouton droit. Sélectionnez l&rsquo;item <b>Properties</b> puis le noeud <b>Java Build Path</b> et sélectionnez l&rsquo;onglet <b>Projects</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaBuildPath1.png" width="681" height="536" alt="" /></p>
<p>Cliquez sur le bouton <b>Add</b>. Ceci ouvre uen boîte de dialogue qui propose tous les projets du workspace dont on peut dépendre : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaBuildPath2.png" width="346" height="424" alt="" /></p>
<p>Sélectionnez le projet <a href="#domain" >org.akrogen.gestcv.domain</a>, puis cliquez sur OK. La dépendance est maintenant créée : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaBuildPath3.png" width="666" height="536" alt="" /></p>
<h2 id="UserService" >Interface UserService</h2>
<p>Créez l&rsquo;interface org.akrogen.gestcv.services.<b>UserService</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.services; <br />
&nbsp;<br />
import java.util.Collection; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
&nbsp;<br />
public interface UserService { <br />
&nbsp;<br />
&nbsp; Collection&lt;User&gt; findAllUsers(); <br />
}</div></div>
<h2>Implémentation UserServiceImpl</h2>
<p>Créez la classe org.akrogen.gestcv.services.impl.<b>UserServiceImpl</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.services.impl; <br />
&nbsp;<br />
import java.util.ArrayList; <br />
import java.util.Collection; <br />
import java.util.List; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
import org.akrogen.gestcv.services.UserService; <br />
&nbsp;<br />
public class UserServiceImpl implements UserService { <br />
&nbsp;<br />
&nbsp; private Collection&lt;User&gt; users = null; <br />
&nbsp;<br />
&nbsp; public Collection&lt;User&gt; findAllUsers() { <br />
&nbsp; &nbsp; if (users == null) { <br />
&nbsp; &nbsp; &nbsp; users = createUsers(); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; return users; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private Collection&lt;User&gt; createUsers() { <br />
&nbsp; &nbsp; List&lt;User&gt; users = new ArrayList&lt;User&gt;(); <br />
&nbsp; &nbsp; users.add(new User(&quot;angelo&quot;, &quot;&quot;)); <br />
&nbsp; &nbsp; users.add(new User(&quot;djo&quot;, &quot;&quot;)); <br />
&nbsp; &nbsp; users.add(new User(&quot;keulkeul&quot;, &quot;&quot;)); <br />
&nbsp; &nbsp; users.add(new User(&quot;pascal&quot;, &quot;&quot;)); <br />
&nbsp; &nbsp; return users; <br />
&nbsp; } <br />
}</div></div>
<p>On peut remarquer que les implémentations des services se retrouvent dans le packages *.impl. Ceci est une bonne règle de nommage qui permet de distinguer facilement les interfaces aux implémentations. Nous verrons dans le prochain billet que cette règle de nommage nous servira aussi pour rendre inaccéssible cette classe aux autres Bundles (projet Java) dans un environnement OSGI.</p>
<h2 id="ServicesFactory" >Factory de Services</h2>
<p>Créez la factory de services org.akrogen.gestcv.services.<b>ServicesFactory</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.services; <br />
&nbsp;<br />
import org.akrogen.gestcv.services.impl.UserServiceImpl; <br />
&nbsp;<br />
public class ServicesFactory { <br />
&nbsp;<br />
&nbsp; private static ServicesFactory INSTANCE = new ServicesFactory(); <br />
&nbsp; private UserService userService = null; <br />
&nbsp;<br />
&nbsp; private ServicesFactory() { <br />
&nbsp;<br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public static ServicesFactory getInstance() { <br />
&nbsp; &nbsp; return INSTANCE; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public UserService getUserService() { <br />
&nbsp; &nbsp; if (userService == null) { <br />
&nbsp; &nbsp; &nbsp; userService = createUserService(); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; return userService; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private UserService createUserService() { <br />
&nbsp; &nbsp; UserService userService = new UserServiceImpl(); <br />
&nbsp; &nbsp; return userService; <br />
&nbsp; } <br />
}</div></div>
<p>La factory de services est un singleton qui a une méthode <b>ServicesFactory#getUserService()</b> qui retourne une implémentation de l&rsquo;interface <a href="#UserService" >UserService</a>.</p>
<h1 id="client" >org.akrogen.gestcv.simplemainclient</h1>
<p><a href="#NewJavaProjec" >Créez le projet Java</a> <b>org.akrogen.gestcv.simplemainclient</b>. <a href="#JavaBuildPath" >Ajoutez les dépendances</a> aux 2 projets <a href="#domain" >org.akrogen.gestcv.domain</a> et <a href="#services" >org.akrogen.gestcv.services</a> .</p>
<p>Créez la classe org.akrogen.gestcv.simplemainclient.<b>SimpleMainClient</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.akrogen.gestcv.simplemainclient; <br />
&nbsp;<br />
import java.util.Collection; <br />
&nbsp;<br />
import org.akrogen.gestcv.domain.User; <br />
import org.akrogen.gestcv.services.ServicesFactory; <br />
import org.akrogen.gestcv.services.UserService; <br />
&nbsp;<br />
public class SimpleMainClient { <br />
&nbsp;<br />
&nbsp; public static void main(String[] args) { <br />
&nbsp; &nbsp; UserService userService = ServicesFactory.getInstance() <br />
&nbsp; &nbsp; &nbsp; &nbsp; .getUserService(); <br />
&nbsp; &nbsp; Collection&lt;User&gt; users = userService.findAllUsers(); <br />
&nbsp;<br />
&nbsp; &nbsp; for (User user : users) { <br />
&nbsp; &nbsp; &nbsp; System.out.println(&quot;User [login=&quot; + user.getLogin() + &quot;, password=&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + user.getPassword() + &quot;]&quot;); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
}</div></div>
<p>Ce Main Java utilise la méthode <b>UserService#findAllUsers()</b> pour afficher la liste de <a href="#User" >User</a>. A cette étape on peut lancer le client qui appelle le service qui retourne la liste d&rsquo;utilisateurs.</p>
<h2 id="RunAs" >Run As &#8211; Java Application</h2>
<p>Pour lancer le main de la classe SimpleMainClient, sélectionnez la classe, puis ouvrez le menu contextuel (en cliquant sur le bouton droit) et cliquez sur <b>Run As / Java Application</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_RunAsJavaApplication.png" width="732" height="487" alt="" /></p>
<p>La console eclipse doit afficher la liste des utilisateurs :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">User [login=angelo, password=] <br />
User [login=djo, password=] <br />
User [login=keulkeul, password=] <br />
User [login=pascal, password=]</div></div>
<h1>Problème Java Build Path</h1>
<p>Les dépendances entre les projets est effectuées via le <a href="##JavaBuildPathAddProject" >Java Build Path</a> classique qui lorsque l&rsquo;on lance le Main de SimpleMainClient construit le classpath avec ces projets. Nous allons voir les 2 problèmes avec ce type de dépendance.</p>
<h2 id="ClassesNotProtected" >UserServiceImpl Non protégé</h2>
<p>Dans la classe org.akrogen.gestcv.simplemainclient.<b>SimpleMainClient</b>, si vous tentez d&rsquo;utiliser la classe UserServiceImpl, vous pourrez constater que l&rsquo;autocomplétion propose cette classe : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_UserServiceImplNotProtected.png" width="476" height="374" alt="" /></p>
<p>Cette classe ne devrait jamais être utilisée par une classe autre que celle de la factory de Service. A ce stade la classe SimpleMainClient peut utiliser UserServiceImpl. Ceci est dangereux surtout si la version suivante du projet<br />
<a href="#services" >org.akrogen.gestcv.services</a> effectue un refactoring en renommant UserServiceImpl ou en changeant ces méthodes.</p>
<p>En pur Java il est impossible de régler ce problème si ce n&rsquo;est de déclarer la classe UserServiceImpl en private et de la mettre dans le même package que la classe ServicesFactory. Mais cette solution ne permet plus de mettre les classes dans des packages souhaités ce qui nous ramenerait à avoir un seul niveau de package pour toutes les interfaces et implémentations.</p>
<h2 id="ClassLoader" >ClassLoader unique</h2>
<p>Un ClassLoader permet comme son nom l&rsquo;indique de charger des classes . Dans notre cas les 3 projets partagent le même ClassLoader. Pour s&rsquo;en rendre compte nous allons afficher le ClassLoader dans la console Eclipse. </p>
<p>Modifiez la classe SimpleMainClient comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public static void main(String[] args) { <br />
&nbsp; &nbsp; System.out.println(&quot;SimpleMainClient ClassLoader : &quot; + SimpleMainClient.class.getClassLoader()); <br />
... <br />
}</div></div>
<p>et modifiez la classe ServicesFactory comme suit  :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">private ServicesFactory() { <br />
&nbsp; System.out.println(&quot;ServicesFactory ClassLoader : &quot; + ServicesFactory.class.getClassLoader()); <br />
}</div></div>
<p>Relancez et vous verrez dans la console :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">SimpleMainClient ClassLoader : sun.misc.Launcher$AppClassLoader@133056f <br />
ServicesFactory ClassLoader : sun.misc.Launcher$AppClassLoader@133056f</div></div>
<p>qui montre que les 2 classes partagent le même ClassLoader.</p>
<p>Ceci est problématique car chaque projet ne peut pas avoir sa propre version de jar d&rsquo;une librairie donnée. Nous allons illustrer le problème en utilisant 2 versions différentes de la librairie Apache <a href="http://commons.apache.org/lang/" >commons-lang*.jar</a> dans 2 projets : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_CommonsJar.png" width="563" height="425" alt="" /></p>
<p>Ce schéma montre que :</p>
<ul>
<li>le projet Java Client <a href="#client" >org.akrogen.gestcv.simplemainclient</a> pointera sur la librairie <b>commons-lang-1.0.jar</b> de version <b>1.0</b>. Cette librairie est stockée dans le repertoire lib (qu&rsquo;il faut créer) du projet.</li>
<li>le projet Java Services <a href="#services" >org.akrogen.gestcv.services</a> pointera sur la librairie <b>commons-lang-2.4.jar</b> de version <b>2.4</b>. Cette librairie est stockée dans le repertoire lib (qu&rsquo;il faut créer) du projet.</li>
</ul>
<p>Vous pouvez télécharger le zip <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/rcp_springdm/org.akrogen.gestcv/step1/org.akrogen.gestcv_step1-commons-lang.zip">org.akrogen.gestcv_step1-commons-lang.zip</a> qui contient les projets avec les librairies <a href="http://commons.apache.org/lang/" >commans-lang*.jar</a>.</p>
<p>La différence entre ces 2 versions de <a href="http://commons.apache.org/lang/" >commans-lang*.jar</a> est que la classe utilitaire  org.apache.commons.lang.<b>StringUtils</b> possède une méthode <b>StringUtils#isBlank(String String)</b> dans la version <b>2.4</b> et pas dans la version <b>1.0</b>. Nous souhaitons utiliser <b>StringUtils#isBlank(String String)</b> dans la couche Services.</p>
<h3 id="JavaBuildPathAddJar">Java Build Path/Add Jar (commons-lang-2.4.jar)</h3>
<p>Ici nous allons ajouter au projet <a href="#services" >org.akrogen.gestcv.services</a> la librairie jar <b>commons-lang-2.4.jar</b>. Pour cela sélectionnez le projet <a href="#services" >org.akrogen.gestcv.services</a> puis cliquez sur le bouton droit. Sélectionnez l&rsquo;item <b>Properties</b> puis le noeud <b>Java Build Path</b> puis sélectionnez l&rsquo;onglet <b>Libraries</b> : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaBuildPathLib1.png" width="681" height="536" alt="" /></p>
<p>Cliquez sur le bouton <b>Add JARs&#8230;</b></p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaBuildPathLib2.png" width="346" height="417" alt="" /></p>
<p>Puis cliquez sur OK, la librairie est ajoutée : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaBuildPathLib3.png" width="715" height="536" alt="" /></p>
<h3>Utilisation commons dans la couche Services</h3>
<p>Modifiez le code de ServicesFactory pour utiliser la méthode utilitaire <b>StringUtils#isBlank(String str)</b> de <b>commons-lang-2.4.jar</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">... <br />
private ServicesFactory() { <br />
&nbsp; StringUtils.isBlank(&quot;&quot;); <br />
} <br />
...</div></div>
<h3>Java Build Path/Add Jar (commons-lang-1.0.jar)</h3>
<p><a href="#JavaBuildPathAddJar" >Ajoutez la librairie</a> <b>commons-lang-1.0.jar</b> dans le projet <a href="#client" >org.akrogen.gestcv.simplemainclient</a></p>
<p>Puis changez l&rsquo;ordre de chargement des librairies pour que la librairie <b>commons-lang-1.0.jar</b> soit chargé AVANT le projet <a href="#services" >org.akrogen.gestcv.services</a> (Services), autrement dit avant le chargement de la libraire <b>commons-lang-2.4.jar</b>   : </p>
<p><img src="http://blog.developpez.com/media/OSGIGestCV_JavaBuildPathOrder.png" width="666" height="536" alt="" /></p>
<p>Relancez le Main SimpleMainClient et vous aurrez l&rsquo;erreur suivante :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">Exception in thread &quot;main&quot; java.lang.NoSuchMethodError: org.apache.commons.lang.StringUtils.isBlank(Ljava/lang/String;)Z <br />
&nbsp; at org.akrogen.gestcv.services.ServicesFactory.&lt;init&gt;(ServicesFactory.java:23) <br />
&nbsp; at org.akrogen.gestcv.services.ServicesFactory.&lt;clinit&gt;(ServicesFactory.java:19) <br />
&nbsp; at org.akrogen.gestcv.simplemainclient.SimpleMainClient.main(SimpleMainClient.java:23)</div></div>
<p>La méthode <b>StringUtils.isBlank(String str)</b> n&rsquo;est pas trouvée car la librairie <b>commons-lang-1.0.jar</b> est chargée AVANT la librairie <b>commons-lang-2.4.jar</b>. Il est donc impossible de faire cohabiter 2 versions différentes avec des dépendances classiques. Ce problème se trouve aussi dans les applications WEB lorsque l&rsquo;application WEB a une version d&rsquo;un JAR et que le serveur (Tomcat&#8230;) a une autre version du même JAR.</p>
<h1>Conclusion</h1>
<p>Dans ce billet nous avons mis en place un client java qui appelle un service. Nous avons vu les 2 problemes posés par des dependances classiques que nous règlerons dans le prochain billet à l&rsquo;aide de OSGI.</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Conception d&#8217;un client Eclipse RCP et serveur OSGI avec Spring DM [step0]</title>
		<link>https://blog.developpez.com/akrogen/p8105/plugin-eclipse/rcp_springdm_step0</link>
		<comments>https://blog.developpez.com/akrogen/p8105/plugin-eclipse/rcp_springdm_step0#comments</comments>
		<pubDate>Mon, 28 Sep 2009 14:30:47 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Vous pouvez trouver ce billet sur mon nouveau blog avec les informations mises a jour. Il y a 3 ans j&#8217;ai créé le projet GestCV, une application WEB de gestion de CV basé sur Spring, Hibernate, Struts1.x et AJAX. A cette époque je souhaitais utiliser et mettre en évidence toutes les technologies que j&#8217;adorais dans un véritable projet. Aujourd&#8217;hui j&#8217;ai décidé de me former au développement d&#8217;applications Eclipse RCP basé sur les API SWT et [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Vous pouvez trouver <a href="http://angelozerr.wordpress.com/2009/11/08/rcp_springdm_step0/" >ce billet sur mon nouveau blog</a> avec les informations mises a jour.</p>
<p>Il y a 3 ans j&rsquo;ai créé le projet <a href="http://gestcv.sourceforge.net/fr/index.html" >GestCV</a>, une application WEB de gestion de CV basé sur <a href="http://www.springsource.org/" >Spring</a>, <a href="https://www.hibernate.org/" >Hibernate</a>, <a href="http://struts.apache.org/1.x/" >Struts1.x</a> et AJAX. A cette époque je souhaitais utiliser et mettre en évidence toutes les technologies que j&rsquo;adorais dans un véritable projet.</p>
<p>Aujourd&rsquo;hui j&rsquo;ai décidé de me former au développement d&rsquo;applications <a href="http://wiki.eclipse.org/index.php/Rich_Client_Platform" >Eclipse RCP</a> basé sur les API <a href="http://www.eclipse.org/swt/" >SWT</a> et <a href="http://wiki.eclipse.org/index.php/JFace" >JFace</a> que j&rsquo;ai découvert à travers le développement du plugin Eclipse <a href="http://akrogen.sourceforge.net/fr/" >Akrogen</a>, du projet <a href="http://tk-ui.sourceforge.net/fr/" >TK-UI</a> et <a href="http://wiki.eclipse.org/JFace_Data_Binding/DOM" >JFace DOM Databinding</a>. <a href="http://wiki.eclipse.org/index.php/Rich_Client_Platform" >Eclipse RCP</a> me séduit de plus en plus pour les <a href="#webVSrcp" >raisons suivantes</a>. Avec <a href="http://www.developpez.net/forums/u319790/pascal-leclercq/" >Pascal Leclercq</a> nous avons créé ce mois-ci le projet <a href="http://code.google.com/p/gestcvosgi/" >OSGI GestCV</a> qui est une version RCP de GestCV (le projet est en phase d&rsquo;étude). Plus exactement ce projet est basé sur une <b>architecture client/serveur</b>, autrement dit : </p>
<ul>
<li>la <b>couche serveur fournira les services</b> utiles pour la gestion de CV (édition d&rsquo;un CV, création d&rsquo;un CV).et sera <b>basée sur <a href="http://www.osgi.org/Main/HomePage" >OSGI</a> via <a href="http://www.springsource.org/osgi">Spring DM</a></b>. Les services feront appels à la base de données via <a href="https://www.hibernate.org/" >Hibernate</a>.</li>
<li>la <b>couche cliente sera une application <a href="http://wiki.eclipse.org/index.php/Rich_Client_Platform" >Eclipse RCP</a></b> qui consommera les services hébérgés sur le serveur.</li>
</ul>
<p>Nous souhaitons utiliser OSGI dans la couche serveur pour démarrer/arrêter à chaud les services sans devoir redémarrer le serveur (très utile en développement comme en production). OSGI fournit d&rsquo;autres avantages que je tenterais d&rsquo;expliquer tout au long de ces billets. Je ne sais pas si nous aboutirons ce projet mais mon but premier est de me former aux technologies OSGI, Spring DM, RCP et de les expliquer par des exemples concrets et détaillés dans des billets. </p>
<p>Mon idée est de rédiger des billets qui expliqueront pas à pas comment <b>réaliser une application Eclipse RCP qui communique avec un serveur OSGI avec Spring DM</b>. Concrètement je vais tenter d&rsquo;expliquer <b>comment développer une petite application RCP qui affiche/met à jour une liste d&rsquo;utilisateurs récupérée par des services (OSGI) hébérgés sur le serveur</b>. Je tenterais en même temps d&rsquo;expliquer l&rsquo;interêt d&rsquo;OSGI.</p>
<p>Nous avons aussi envisagé d&rsquo;étudier plus tard (selon notre temps) ce que peut nous fournir le futur projet <a href="http://www.eclipse.org/e4/" >Eclipse E4</a> (moteur CSS (qui est à l&rsquo;origine celui de projet <a href="http://tk-ui.sourceforge.net/fr/" >TK-UI</a> ), Modeled Workbench, SWT Flex&#8230;) et <a href="http://www.eclipse.org/rap/" >RAP</a> (qui d&rsquo;après ce que j&rsquo;ai compris permet de déployer l&rsquo;application RCP en mode WEB ). </p>
<p>Si le sujet vous intéresse, vous pouvez commencer par lire le <a href="http://blog.developpez.com/akrogen/p8125/plugin-eclipse/rcp-springdm-step1" >billet [step1]</a> de la série de billets intitulée <b>Conception d&rsquo;un client Eclipse RCP et serveur OSGI avec Spring DM </b>.</p>
<p><span id="more-61"></span></p>
<h1 id="webVSrcp" >Application WEB vs RCP</h1>
<p>Application WEB (client léger) ou client lourd Eclipse RCP? C&rsquo;est une question que je me pose aujourd&rsquo;hui. Je ne me permettrais pas de dire que les applications RCP remplaceront les applications WEB, mais je pense que ca vaut le coup de s&rsquo;interesser à RCP avec l&rsquo;arrivée de <a href="http://www.eclipse.org/e4/" >Eclipse E4</a>. Les applications WEB ont pris une ampleur considérable ces dernières années et Javascript a repris une jeunesse avec AJAX qui permet de communiquer avec le serveur avec Javascript via <a href="http://fr.wikipedia.org/wiki/XMLHttpRequest" >XMLHTTPRequest</a>. Ce dernier permet d&rsquo;éviter par exemple de rafraîchir toute une page après avoir sélectionné une liste de pays qui doit rafraîchir la liste des villes associées au pays sélectionné, de fournir des fonctionnalités d&rsquo;aide à la saisie (autocomplétion) qui recherche des informations d&rsquo;une BD, de charger des images dont on a besoin lorsqu&rsquo;un utilisateur navigue dans une carte d&rsquo;une  application de cartographie. De nombreux frameworks comme <a href="http://code.google.com/intl/fr/webtoolkit/" >GWT</a>, <a href="http://struts.apache.org/2.x/index.html" >Struts2</a>, <a href="http://labs.jboss.com/richfaces" >RichFaces</a> (JSF), <a href="http://wicket.apache.org/" >Wicket</a>, .. propose par défaut des composants AJAX prêts à être employé. </p>
<h2>Richesse Interface Utilisateur</h2>
<p>Les widgets HTML (champs texte, checkbox&#8230;) étant relativement primitives (je ne comprends pas pourquoi W3C ne propose toujours pas des widgets comme le treeview ou la grid dans la norme HTML), Javascript est utilisé pour obtenir des widgets complexes (treeview, grid&#8230;). La réalisation de tel composant est complexe, malgré les nombreux frameworks Javascript qui fournissent des widgets prêtes à être employées (<a href="http://www.dojotoolkit.org/" >Dojo</a>, <a href="http://jquery.com/" >jQuery</a>&#8230;). Développer un composant AJAX (ou uniquement Javascript) est une tâche très côuteuse, ce que je je ne m&rsquo;imaginais pas avant d&rsquo;avoir commencer le projet <a href="http://jscontrolstags.sourceforge.net/" >JSControlsTags</a>, qui propose des composants AJAX d&rsquo;<a href="http://jscontrolstags.sourceforge.net/controls/autocomplete/autocomplete.html" >autocompletion</a>, <a href="http://jscontrolstags.sourceforge.net/demo/treeview/treeview.html" >treeview</a>, <a href="http://jscontrolstags.sourceforge.net/demo/swap/swap.html" >swap</a>&#8230;..Adapter un composant AJAX d&rsquo;un framework Javascript pour lui ajouter des fonctionnalités manquantes est aussi très compliqué. Une <b>interface utilisateur complexe basée sur Javascript peut souffrir de lenteur</b>, surtout aujourd&rsquo;hui avec IE6 (qui est malheureusement utilisé dans beaucoup d&rsquo;institution). En terme de développement, je pense que si on vous donne le choix de développer entre Java et Javascript, le choix est vite fait (GWT permet de gérer son interface utilisateur via des widgets Java, mais son côté boîte noire m&rsquo;effraie un peu).</p>
<p>Avec RCP les widgets sont riches car SWT (librairies de composants graphiques utilisés dans Eclipse) propose la plupart des widgets utilisés dans une application (comme le treeview (SWT Tree), les grid (SWT Table)&#8230;), il est aussi possible d&rsquo;étendre la biblihothèque en Java pour bénéficier de nouvelles widgets. <a href="http://www.eclipse.org/nebula/">Nebula</a> est un exemple de projet qui enrichit les widgets SWT. </p>
<h2>Déclaration Interface Utilisateur</h2>
<p>Dans une application WEB l&rsquo;interface utilisateur est déclarative via HTML. La présentation (couleur de font, gras, italique&#8230;) de l&rsquo;interface utilisateur peut être déleguée à une feuille de style CSS. Ces 2 solutions couplées est un véritable gain de temps pour la construction d&rsquo;interfaces utilisateurs (je ne m&rsquo;étais jamais rendu compte de cela avant d&rsquo;avoir coder des interfaces Swing ou SWT) et il est possible d&rsquo;avoir une vue d&rsquo;ensemble de l&rsquo;interface utilisateur en lisant le HTML.</p>
<p>Dans une application RCP, SWT est utilisé et la construction d&rsquo;interfaces utilisateurs est très pénible (les layouts sont par exemple le truc que je déteste le plus). L&rsquo;un des buts de <a href="http://www.eclipse.org/e4/" >Eclipse E4</a> est de simplifier le développement d&rsquo;interface utilisateur et proposent un <a href="http://wiki.eclipse.org/E4/CSS" >moteur CSS</a> (voir <a href="http://wiki.eclipse.org/E4/UI/Running_CSS_demos" >examples CSS</a> pour SWT (ou autres) et une solution déclarative (<a href="http://wiki.eclipse.org/E4/XWT/Running_the_demos" >XWT</a> (déclaration de son interface utilisateur via XML) , <a href="http://wiki.eclipse.org/E4/UI/Toolkit_Model/" >TM</a> (déclaration de son interface utilisateur via <a href="http://www.eclipse.org/modeling/emf/" >EMF</a>)).</p>
<h2>Métier client</h2>
<p>Selon l&rsquo;application à développer, le métier coté client peut est très important. Dans une application WEB, si le métier coté client consiste à effectuer des validations simples (ex : contrôle de validité d&rsquo;une saisie de date), Javascript peut suffire. Mais si le métier est beaucoup plus complexe, Javascript peut devenir horrible à maintenir. Il est plus judicieux de développer des services de validation coté serveur qui sont ensuite appelés via AJAX par exemple. Mais cette solution nécéssite un appel serveur.</p>
<p>En RCP ce problème n&rsquo;existe pas, car le client est Java. Il est donc possible d&rsquo;executer les services de validation coté client.</p>
<h2>Robustesse</h2>
<p>Dans une application WEB, il est très difficile de maîtriser le cycle de vie du client. Comment détecter par exemple que l&rsquo;utilisateur ferme son navigateur WEB? Comment détecter qu&rsquo;il navigue d&rsquo;une page à une autre via les boutons Suivant/Précédant? Ce manque de maîtrise du cycle de vie du client peut être problématique dans l&rsquo;application, car par exemple on ne peut pas avertir l&rsquo;utilisateur que ses données n&rsquo;ont pas été sauvegardé lors de la fermeture de son navigateur.</p>
<p>Avec RCP, le cycle de vie du client est maitrisée, on peut détecter par exemple que l&rsquo;application se ferme et effectuer une validation selon l&rsquo;état de l&rsquo;interface utilisateur.</p>
<h2>Déploiement</h2>
<p>En terme de déploiement, une application WEB a l&rsquo;avantage d&rsquo;être simple car seul la mise à jour du serveur suffit pour bénéficier d&rsquo;une nouvelle version de l&rsquo;applicatiopn. Les client (navigateur WEB ) n&rsquo;ont pas besoin de se mettre à jour. Avec une application de type client lourd, le déploiement est nécessaire (l&rsquo;application cliente doit être réinstallée). J&rsquo;ai entendu parler de <a href="http://wiki.eclipse.org/Equinox_p2_Getting_Started">P2</a> (qui remplace depuis Eclipse 3.4, l&rsquo;Update Manager eclipse qui permet d&rsquo;installer les plugins) qui apporterait beaucoup, mais je ne suis pas expert. C&rsquo;est une piste que je souhaiterais aussi étudier.</p>
<h1>Conclusion</h1>
<p>Je pense que le choix WEB vs RCP doit s&rsquo;effectuer entre robustesse, maintenance, performance de l&rsquo;application (coté client) et déploiement de l&rsquo;application (mais je n&rsquo;ai pas encore bien étudié <a href="http://wiki.eclipse.org/Equinox_p2_Getting_Started">P2</a>). Je pense que ca vaut le coup de s&rsquo;investir aujourd&rsquo;hui dans OSGI  car par exemple la plupart des serveurs (JBoss, Websphere, &#8230;.) implémente OSGI du moins en interne. De plus en plus de framework WEB commence à intègrer OSGI  (Wicket, Struts2). L&rsquo;arrivée de Eclipse E4 va permettre aussi beaucoup de chose. Ce sont toutes ces raisons qui me donnent envie de m&rsquo;investir dans RCP et OSGI. Enfin pour la partie OSGI nous avons fait le choix d&rsquo;utiliser Spring DM car étant basé sur Spring, nous pouvons bénéficier de la puissance de Spring pour les autres problématiques (IOC, AOP, Spring remoting). Mais je pense qu&rsquo;il ne faut pas exclure <a href="http://www.eclipse.org/ecf/" >ECF</a> et <b>OSGI Declarative Services</b>.</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Migration blog</title>
		<link>https://blog.developpez.com/akrogen/p8308/plugin-eclipse/migration_blog</link>
		<comments>https://blog.developpez.com/akrogen/p8308/plugin-eclipse/migration_blog#comments</comments>
		<pubDate>Wed, 11 Nov 2009 11:24:17 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[Voila ca sera mon dernier billet sur le blog Akrogen de DVP. J&#8217;ai décidé pour des raisons techniques de passer sur wordpress (c&#8217;est surout pour la coloration syntaxique du code Java, XML que j&#8217;ai decide de changer de blogs sur wordpress). Je ne vais pas détruire ce blog et laisser tous les billets mais j&#8217;ai migre tous les billets intitulés Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step*] sur wordpress. Je [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Voila ca sera mon dernier billet sur le blog Akrogen de DVP. J&rsquo;ai décidé pour des raisons techniques de passer sur wordpress (c&rsquo;est surout pour la coloration syntaxique du code Java, XML que j&rsquo;ai decide de changer de blogs sur <a href="http://angelozerr.wordpress.com/" >wordpress</a>). </p>
<p>Je ne vais pas détruire ce blog et laisser tous les billets mais j&rsquo;ai migre tous les billets intitulés <b>Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step*]</b> sur wordpress.</p>
<p>Je remercie encore DVP pour m&rsquo;avoir fourni ce blog qui m&rsquo;a beaucoup aidé à rencontrer plein de personnes passionnées.</p>
<p>URL du nouveau blog : http://angelozerr.wordpress.com/</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Conception d&#8217;un Editeur Eclipse de workflow XML [step 19]</title>
		<link>https://blog.developpez.com/akrogen/p8060/plugin-eclipse/conception_d_un_editeur_eclipse_de_work_19</link>
		<comments>https://blog.developpez.com/akrogen/p8060/plugin-eclipse/conception_d_un_editeur_eclipse_de_work_19#comments</comments>
		<pubDate>Wed, 16 Sep 2009 14:55:51 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[A l&#8217;étape du billet précédant [step18] nous avons mis en place les fonctionnalité de suppression de state, d&#8217;actions et connections dans la page Graphics GEF. A ce stade nous avons des outils de la palette GEF et des actions GEF qui permettent de mettre à jour le modèle EMF via des Command GEF. Plus exactement ce sont les EditPolciy installés sur chacun des EditPart GEF qui réagissent aux outils/actions et qui interprètent les Request GEF [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>A l&rsquo;étape du <a href="http://blog.developpez.com/akrogen/p8032/plugin-eclipse/conception-d-un-editeur-eclipse-de-work-18" >billet précédant [step18]</a> nous avons mis en place les fonctionnalité de suppression de state, d&rsquo;actions et connections dans la page Graphics GEF. A ce stade nous avons des outils de la palette GEF et des actions GEF qui permettent de mettre à jour le modèle EMF via des Command GEF. Plus exactement ce sont les EditPolciy installés sur chacun des EditPart GEF qui réagissent aux outils/actions et qui interprètent les Request GEF en Command GEF. Les EditPolicy pourrait mettre à jour directement le modlèle EMF sans passer par des Command GEF. Pourquoi utiliser des Command GEF? L&rsquo;editor GEF GaaphicalEditor gère en interne une <b>stack de Command GEF</b> (GraphicalEditor#getEditDomain()#getCommandStack()) qui dépile/empile les Command GEF executées dans l&rsquo;editor via les outils des palettes et les Actions GEF. </p>
<p>Cette <b>stack de Command GEF permet de gérer le undo/redo dans l&rsquo;editor GEF</b> en appelant pour chaque action GEF <b>UndoAction/RedoAction</b> la méthode <b>Command#undo()/redo()</b> de la dernière Command GEF exécutée. A ce stade nous n&rsquo;avons pas implémentées ces 2 méthodes dans nos Command GEF. Dans ce billet nous allons les implémenter pour gérer le undo/redo : </p>
<ul>
<li>après avoir ajouté un state/action via l&rsquo;outil de création de state/action en implémentant les méthodes undo/redo de la command GEF <a href="#CreateCommand" >CreateCommand</a></li>
<li>après avoir créé une connection entre un state/action via l&rsquo;outil de création de connections en implémentant les méthodes undo/redo de la command GEF <a href="#ConnectionCreateCommand" >ConnectionCreateCommand</a></li>
<li>après avoir supprimé un state/action via l&rsquo;action GEF DeleteAction en implémentant les méthodes undo/redo de la command GEF <a href="#DeleteCommand" >DeleteCommand</a></li>
<li>après avoir supprimé une connection via l&rsquo;action GEF DeleteAction en implémentant les méthodes undo/redo de la command GEF <a href="#DeleteConnectionCommand" >DeleteConnectionCommand</a></li>
</ul>
<p>Les fonctionnalités undo/redo seront accéssibles via : </p>
<ul>
<li><a href="#undoRedoWithContextuelMenu" >le menu contextuel</a> de la page GEF Graphics avec les entrée de menu &laquo;&nbsp;Undo&nbsp;&raquo; et &laquo;&nbsp;Redo&nbsp;&raquo;.</li>
<li><a href="#undoRedoWithGlobalMenu" >le menu global &laquo;&nbsp;Edit&nbsp;&raquo;</a> avec les entrée de menu &laquo;&nbsp;Undo&nbsp;&raquo; et &laquo;&nbsp;Redo&nbsp;&raquo;.</li>
<li id="#undoRedoWithKeyword" >la combinaison de touches &laquo;&nbsp;Ctrl+Z&nbsp;&raquo; et &laquo;&nbsp;Ctrl+Y&nbsp;&raquo;</li>
</ul>
<p>Vous pouvez télécharger le projet <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/gef/org.example.workflow/step19/org.example.workflow_step19.zip">org.example.workflow_step19.zip</a> présenté dans ce billet.</p>
<p><span id="more-60"></span></p>
<h1>GEF UndoAction/RedoAction</h1>
<p>GEF installe par défaut les actions org.eclipse.gef.ui.actions.<b>UndoAction</b> et org.eclipse.gef.ui.actions.<b>RedoAction</b> dans son registry d&rsquo;actions, qui permet d&rsquo;appeler via la CommandStack de l&rsquo;editor, les méthodes undo/redo de la dernière Command GEF exécutée. Voici le code de la méthode <b>UndoAction#run()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void run() { <br />
&nbsp; getCommandStack().undo(); <br />
}</div></div>
<p>Et voici la méthode <b>CommandStack#undo()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void undo() { <br />
&nbsp; //Assert.isTrue(canUndo()); <br />
&nbsp; Command command = (Command)undoable.pop(); <br />
&nbsp; notifyListeners(command, PRE_UNDO); <br />
&nbsp; try { <br />
&nbsp; &nbsp; command.undo(); <br />
&nbsp; &nbsp; redoable.push(command); <br />
&nbsp; &nbsp; notifyListeners(); <br />
&nbsp; } finally { <br />
&nbsp; &nbsp; notifyListeners(command, POST_UNDO); <br />
&nbsp; } <br />
}</div></div>
<p>Avant d&rsquo;implémenter les méthodes undo/redo de chacune de nos Command GEF, nous devons appeler les méthodes UndoAction#run() et RedoAction#run() par :</p>
<ul>
<li><a href="#undoRedoWithContextuelMenu" >le menu contextuel</a> de la page GEF Graphics avec les entrée de menu &laquo;&nbsp;Undo&nbsp;&raquo; et &laquo;&nbsp;Redo&nbsp;&raquo;.</li>
<li><a href="#undoRedoWithGlobalMenu" >le menu global &laquo;&nbsp;Edit&nbsp;&raquo;</a> avec les entrée de menu &laquo;&nbsp;Undo&nbsp;&raquo; et &laquo;&nbsp;Redo&nbsp;&raquo;.</li>
<li id="#undoRedoWithKeyword" >la combinaison de touches &laquo;&nbsp;Ctrl+Z&nbsp;&raquo; et &laquo;&nbsp;Ctrl+Y&nbsp;&raquo;</li>
</ul>
<h2 id="undoRedoWithContextuelMenu">Undo/Redo &#8211; Menu Contextuel</h2>
<p>Modifiez le code de la méthode <b>WorkflowContextMenuProvider#buildContextMenu(IMenuManager menu)</b> de la classe org.example.workflow.presentation.graphical.actions.<b>WorkflowContextMenuProvider</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void buildContextMenu(IMenuManager menu) { <br />
&nbsp; GEFActionConstants.addStandardActionGroups(menu); <br />
&nbsp;<br />
&nbsp; IAction action = getActionRegistry().getAction( &nbsp;ActionFactory.UNDO.getId()); <br />
&nbsp; menu.appendToGroup(GEFActionConstants.GROUP_UNDO, action); <br />
&nbsp;<br />
&nbsp; action = getActionRegistry().getAction(ActionFactory.REDO.getId()); <br />
&nbsp; menu.appendToGroup(GEFActionConstants.GROUP_UNDO, action); <br />
&nbsp;<br />
&nbsp; action = getActionRegistry().getAction(ActionFactory.DELETE.getId()); <br />
&nbsp; if (action.isEnabled()) <br />
&nbsp; &nbsp; menu.appendToGroup(GEFActionConstants.GROUP_EDIT, action); <br />
}</div></div>
<p>Ici nous ajoutons l&rsquo;action UndoAction dans le menu contextuel de la page GEF Graphics via le code :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">IAction action = getActionRegistry().getAction(ActionFactory.UNDO.getId()); <br />
&nbsp; menu.appendToGroup(GEFActionConstants.GROUP_UNDO, action);</div></div>
<p>Dans les exemples GEF la constante <b>GEFActionConstants.UNDO</b> est utilisée mais celle-ci est marquée depracated et conseille d&rsquo;utiliser la constante <b>ActionFactory.UNDO.getId()</b>.</p>
<p>Relancez le plugin et vous devez voir apparaître les 2 actions (désactivées) &laquo;&nbsp;Undo&nbsp;&raquo; et &laquo;&nbsp;Redo&nbsp;&raquo; dans le menu contextuel : </p>
<p><img src="http://blog.developpez.com/media/Workflow_UndoRedoActionDisabledTypeWithContextualMenu.png" width="942" height="741" alt="" /></p>
<h2 id="undoRedoWithGlobalMenu" >Undo/Redo &#8211; Menu global</h2>
<p>Pour lier les entrées de menu &laquo;&nbsp;Undo&nbsp;&raquo; et &laquo;&nbsp;Redo&nbsp;&raquo; appartenant au menu globale &laquo;&nbsp;Edit&nbsp;&raquo;, nous allons procéder de la même manière que l&rsquo;action DeleteAction. Pour cela modifiez la méthode <b>GraphicalWorkkflowActionBarContributor#buildActions()</b> de la classe org.example.workflow.presentation.graphical.actions.<b>GraphicalWorkkflowActionBarContributor</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">protected void buildActions() { <br />
&nbsp; addRetargetAction(new UndoRetargetAction()); <br />
&nbsp; addRetargetAction(new RedoRetargetAction()); <br />
&nbsp; addRetargetAction(new DeleteRetargetAction()); <br />
}</div></div>
<h2 id="undoRedoWithKeyword" >Undo/Redo &#8211; &laquo;&nbsp;Ctrl+Z&nbsp;&raquo;/&nbsp;&raquo;Ctrl+Y&nbsp;&raquo;</h2>
<p>Pour bénéficier du &laquo;&nbsp;Ctrl+Z&nbsp;&raquo; et &laquo;&nbsp;Ctrl+Y&nbsp;&raquo; nous n&rsquo;avons rien à faire car nous avons lier les entrées de menu &laquo;&nbsp;Undo&nbsp;&raquo; et &laquo;&nbsp;Redo&nbsp;&raquo; appartenant au menu globale &laquo;&nbsp;Edit&nbsp;&raquo; qui sont elle-même lié à ces combinaisons de touches. </p>
<h1>Command#undo() / Command#redo() </h1>
<p>A ce stade les appels de <b>CommandStack#undo()</b> et <b>CommandStack#redo()</b> via le menu contextuel, le menu globale ou &laquo;&nbsp;Ctrl+Z&nbsp;&raquo;, &laquo;&nbsp;Ctrl+Y&nbsp;&raquo; est opérationnel. Nous pouvons maintenant implémenter les méthodes <b>Command#undo()</b> / <b>Command#redo()</b> pour chacune de nos Command GEF. Par défaut la classe de base org.eclipse.gef.commands.<b>Command</b> implémente la méthode <b>Command#redo()</b> comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void redo() { <br />
&nbsp; execute(); <br />
}</div></div>
<p>autrement dit elle appelle la méthode <b>Command#execute()</b>, ce qui dans la plupart des cas fonctionne très bien et évite à la classe qui étend org.eclipse.gef.commands.<b>Command</b> d&rsquo;implémenter la méthode <b>Command#redo()</b>. La méthode <b>Command#undo()</b> est implémentée comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void undo() { } <br />
&nbsp;<br />
}</div></div>
<p>autrement dit elle ne fait rien.</p>
<h2 id="CreateCommand">Undo/Redo &#8211; CreateCommand</h2>
<p>Pour rappel notre classe org.example.workflow.presentation.graphical.commands.<b>CreateCommand</b> s&rsquo;occupe d&rsquo;ajouter un state/action aux workflow via sa méthode <b>CreateCommand#execute()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void execute() { <br />
&nbsp; if (getConnectableNode() instanceof StateType) { <br />
&nbsp; &nbsp; getWorkflow().getState().add((StateType)getConnectableNode()); <br />
&nbsp; } <br />
&nbsp; else { <br />
&nbsp; &nbsp; if (getConnectableNode() instanceof ActionType) { <br />
&nbsp; &nbsp; &nbsp; getWorkflow().getAction().add((ActionType)getConnectableNode()); <br />
&nbsp; &nbsp; } &nbsp;<br />
&nbsp; } <br />
}</div></div>
<p>La méthode <b>CreateCommand#undo()</b> doit effectuer son inverse, autrement dit supprimer le state/action qui a été ajouté au workflow. Pour cela implémentez la méthode <b>CreateCommand#undo()</b> comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void undo() { <br />
&nbsp; if (getConnectableNode() instanceof StateType) { <br />
&nbsp; &nbsp; getWorkflow().getState().remove((StateType)getConnectableNode()); <br />
&nbsp; } <br />
&nbsp; else { <br />
&nbsp; &nbsp; if (getConnectableNode() instanceof ActionType) { <br />
&nbsp; &nbsp; &nbsp; getWorkflow().getAction().remove((ActionType)getConnectableNode()); <br />
&nbsp; &nbsp; } &nbsp;<br />
&nbsp; } <br />
}</div></div>
<p>Ici nous n&rsquo;avons pas besoin d&rsquo;implémenter la méthode <b>CreateCommand#redo()</b> car elle est identique à <b>CreateCommand#execute()</b>.</p>
<p>Relancez le plugin, ajoutez un state via l&rsquo;ouil de création de state puis cliquez sur le menu contextuel, l&rsquo;item &laquo;&nbsp;Undo&nbsp;&raquo; doit être activé : </p>
<p><img src="http://blog.developpez.com/media/Workflow_UndoRedoActionEnabledTypeWithContextualMenu.png" width="942" height="740" alt="" /></p>
<p>Cliquez sur l&rsquo;item &laquo;&nbsp;Undo&nbsp;&raquo; pour annuler le state ajouté. Apuyez sur &laquo;&nbsp;Ctrl+Y&nbsp;&raquo; pour effectuer un Redo par le clavier, le state doit ré-apparaître (ceci permet de vérifier que le menu global est bien opérationnel).</p>
<h2 id="ConnectionCreateCommand">Undo/Redo &#8211; ConnectionCreateCommand</h2>
<p>Pour rappel notre classe org.example.workflow.presentation.graphical.commands.<b>ConnectionCreateCommand</b> s&rsquo;occupe de créer une connection entre un state et une action via sa méthode <b>ConnectionCreateCommand#execute()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void execute() { <br />
&nbsp; if (source instanceof StateType) { <br />
&nbsp; &nbsp; StateType state = (StateType) source; <br />
&nbsp; &nbsp; ActionType action = (ActionType) target; <br />
&nbsp; &nbsp; action.setFromState(state); <br />
&nbsp; } else { <br />
&nbsp; &nbsp; StateType state = (StateType) target; <br />
&nbsp; &nbsp; ActionType action = (ActionType) source; <br />
&nbsp; &nbsp; action.setToState(state); <br />
&nbsp; } <br />
}</div></div>
<p>La méthode <b>ConnectionCreateCommand#undo()</b> doit effectuer son inverse, autrement dit supprimer la connection. Pour cela implémentez la méthode <b>ConnectionCreateCommand#undo()</b> comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void undo() { <br />
&nbsp; if (source instanceof StateType) { <br />
&nbsp; &nbsp; ActionType action = (ActionType) target; <br />
&nbsp; &nbsp; action.setFromState(null); <br />
&nbsp; } else { <br />
&nbsp; &nbsp; ActionType action = (ActionType) source; <br />
&nbsp; &nbsp; action.setToState(null); <br />
&nbsp; } <br />
}</div></div>
<p>Ici nous n&rsquo;avons pas besoin d&rsquo;implémenter la méthode <b>ConnectionCreateCommand#redo()</b> car elle est identique à <b>ConnectionCreateCommand#execute</b>.</p>
<p>Relancez le plugin, liez un state et un action à l&rsquo;aide de l&rsquo;outil de création de connection puis testez que le undo/redo fonctionne correctement.</p>
<h2 id="DeleteCommand">Undo/Redo &#8211; DeleteCommand</h2>
<p>Pour rappel notre classe org.example.workflow.presentation.graphical.commands.<b>DeleteCommand</b> s&rsquo;occupe de supprimer le state/action selectionné via sa méthode <b>DeleteCommand#execute()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void execute() { <br />
&nbsp; getChildren().remove(getConnectableNode()); <br />
}</div></div>
<p>La méthode <b>DeleteCommand#undo()</b> doit effectuer son inverse, autrement dit ajouter le state/action qui a été supprimé du workflow. Pour cela implémentez la méthode <b>DeleteCommand#undo()</b> comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void undo() { <br />
&nbsp; getChildren().add(getConnectableNode()); <br />
}</div></div>
<p>Mias ceci ne suffit pas. En effet ce code fonctionne très bien pour les state/action qui ne sont pas liés par des connections. Dans le cas contraire nous perdons les informations de connections.</p>
<p>Pour gérer le undo correctement, il faut restaurer les connections existantes. Pour cela modifiez le code de DeleteCommand comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.graphical.commands; <br />
&nbsp;<br />
import java.util.ArrayList; <br />
import java.util.List; <br />
&nbsp;<br />
import org.eclipse.gef.commands.Command; <br />
import org.example.workflow.model.ActionType; <br />
import org.example.workflow.model.ConnectableNode; <br />
import org.example.workflow.model.Connection; <br />
import org.example.workflow.model.StateType; <br />
import org.example.workflow.model.WorkflowType; <br />
import org.example.workflow.model.util.ConnectionUtils; <br />
&nbsp;<br />
public class DeleteCommand extends Command { <br />
&nbsp;<br />
&nbsp; private WorkflowType workflow; <br />
&nbsp; private ConnectableNode connectableNode; <br />
&nbsp; private List&lt;Connection&gt; sourceConnections = new ArrayList&lt;Connection&gt;(); <br />
&nbsp; private List&lt;Connection&gt; targetConnections = new ArrayList&lt;Connection&gt;(); <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void execute() { <br />
&nbsp; &nbsp; getChildren().remove(getConnectableNode()); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void undo() { <br />
&nbsp; &nbsp; getChildren().add(getConnectableNode()); <br />
&nbsp; &nbsp; for (Connection connection : sourceConnections) { <br />
&nbsp; &nbsp; &nbsp; ConnectableNode source = connection.getSource(); <br />
&nbsp; &nbsp; &nbsp; ConnectableNode target = connection.getTarget(); <br />
&nbsp; &nbsp; &nbsp; ConnectionUtils.connect(source, target); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; for (Connection connection : targetConnections) { <br />
&nbsp; &nbsp; &nbsp; ConnectableNode source = connection.getSource(); <br />
&nbsp; &nbsp; &nbsp; ConnectableNode target = connection.getTarget(); <br />
&nbsp; &nbsp; &nbsp; ConnectionUtils.connect(source, target); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public WorkflowType getWorkflow() { <br />
&nbsp; &nbsp; return workflow; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void setWorkflow(WorkflowType workflow) { <br />
&nbsp; &nbsp; this.workflow = workflow; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public ConnectableNode getConnectableNode() { <br />
&nbsp; &nbsp; return connectableNode; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void setConnectableNode(ConnectableNode connectableNode) { <br />
&nbsp; &nbsp; this.connectableNode = connectableNode; <br />
&nbsp; &nbsp; sourceConnections.addAll(connectableNode.getSourceConnections()); <br />
&nbsp; &nbsp; targetConnections.addAll(connectableNode.getTargetConnections()); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; protected List getChildren() { <br />
&nbsp; &nbsp; if (connectableNode instanceof StateType) <br />
&nbsp; &nbsp; &nbsp; return workflow.getState(); <br />
&nbsp; &nbsp; if (connectableNode instanceof ActionType) <br />
&nbsp; &nbsp; &nbsp; return workflow.getAction(); <br />
&nbsp; &nbsp; return null; <br />
&nbsp; } <br />
}</div></div>
<p>Dans la méthode <b>DeleteCommand#setConnectableNode(ConnectableNode connectableNode)</b> on stocke les connections initialies sources et targets du node :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void setConnectableNode(ConnectableNode connectableNode) { <br />
&nbsp; this.connectableNode = connectableNode; <br />
&nbsp; sourceConnections.addAll(connectableNode.getSourceConnections()); <br />
&nbsp; targetConnections.addAll(connectableNode.getTargetConnections()); <br />
}</div></div>
<p>Puis dans la méthode <b>DeleteCommand#undo()</b> on reconnecte les node en utilisant les connections source et target initialie :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void undo() { <br />
&nbsp; getChildren().add(getConnectableNode()); <br />
&nbsp; for (Connection connection : sourceConnections) { <br />
&nbsp; &nbsp; ConnectableNode source = connection.getSource(); <br />
&nbsp; &nbsp; ConnectableNode target = connection.getTarget(); <br />
&nbsp; &nbsp; ConnectionUtils.connect(source, target); <br />
&nbsp; } <br />
&nbsp; for (Connection connection : targetConnections) { <br />
&nbsp; &nbsp; ConnectableNode source = connection.getSource(); <br />
&nbsp; &nbsp; ConnectableNode target = connection.getTarget(); <br />
&nbsp; &nbsp; ConnectionUtils.connect(source, target); <br />
&nbsp; } <br />
}</div></div>
<p>Relancez le plugin et supprimez l&rsquo;action a1 qui est lié aux states s1 et s2, puis effectuez un &laquo;&nbsp;Ctrl+Z&nbsp;&raquo;, l&rsquo;action a1 doit être à nouveau lié à s1 et s2.</p>
<h2 id="DeleteConnectionCommand">Undo/Redo &#8211; DeleteConnectionCommand</h2>
<p>Pour rappel notre classe org.example.workflow.presentation.graphical.commands.<b>DeleteConnectionCommand</b> s&rsquo;occupe de supprimer une connection entre un state et une action via sa méthode <b>DeleteConnectionCommand#execute()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void execute() { <br />
&nbsp; if (source instanceof StateType) { <br />
&nbsp; &nbsp; StateType state = (StateType) source; <br />
&nbsp; &nbsp; ActionType action = (ActionType) target; <br />
&nbsp; &nbsp; action.setFromState(state); <br />
&nbsp; } else { <br />
&nbsp; &nbsp; StateType state = (StateType) target; <br />
&nbsp; &nbsp; ActionType action = (ActionType) source; <br />
&nbsp; &nbsp; action.setToState(state); <br />
&nbsp; } <br />
}</div></div>
<p>La méthode <b>DeleteConnectionCommand#undo()</b> doit effectuer son inverse, autrement dit remettre à jour la connection. Pour cela implémentez la méthode <b>DeleteConnectionCommand#undo()</b> comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void undo() { <br />
&nbsp; if (connection.getSource() instanceof ActionType) { <br />
&nbsp; &nbsp; ActionType action = (ActionType) connection.getSource(); <br />
&nbsp; &nbsp; StateType state = (StateType)connection.getTarget(); <br />
&nbsp; &nbsp; action.setToState(state); <br />
&nbsp; } else { <br />
&nbsp; &nbsp; if (connection.getTarget() instanceof ActionType) { <br />
&nbsp; &nbsp; &nbsp; ActionType action = (ActionType) connection.getTarget(); <br />
&nbsp; &nbsp; &nbsp; StateType state = (StateType)connection.getSource(); <br />
&nbsp; &nbsp; &nbsp; action.setFromState(state); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
}</div></div>
<p>Relancez le plugin, supprimez un lien entre un state et une action à l&rsquo;aide de la touche &laquo;&nbsp;Suppr&nbsp;&raquo;, puis testez que le undo/redo fonctionne correctement.</p>
<h1>Conclusion</h1>
<p>Ici nous avons mis en place les fonctionnalités de Undo/Redo dans la page GEF Graphics. Les Command GEF permettent de gérer le Undo/Redo via la stack de Command. On ne <b>modifie jamais le modèle directement on passe toujours par une Command</b>. EMF.Edit utilise d&rsquo;ailleurs le même principe. Cependant nous sommes dans un contexte de multi page et le Undo/Redo fonctionne très bien lorsque l&rsquo;on reste sur une page (ex : on modifie le modèle EMF workflow via la page Graphics GEF, puis on effectue un Undo), mais pas lorsque l&rsquo;on modifie une page (ex : page Graphics GEF)  puis que l&rsquo;on clique sur une autre page (ex : page Source ou Selection) et que l&rsquo;on tente d&rsquo;effectuer un Undo. Ceci s&rsquo;explique par le fait que chaque page (Selection, Graphics GEF, Source XML) a sa propre stack de command. De plus les Command sont de diiférentes natures : </p>
<ul>
<li>la page Selection et Source gère en interne une Command de stack de type Command EMF.</li>
<li>la page GEF Graphics gère en interne une Command de stack de type Command GEF.</li>
</ul>
<p>Pour régler ce problème, il faudrait que notre editor multi page partage la même stack de command. Mais la difficulté est que les Command sont de différentes natures. Aujourd&rsquo;hui je n&rsquo;ai pas encore étudié cette problématique, mais d&rsquo;après ce que j&rsquo;ai pu voir dans JSF Webools, il transforme toutes les Command EMF en Command GEF. Dans leur cas seul la page Source génère des Command EMF : </p>
<ul>
<li>la classe org.eclipse.jst.jsf.facesconfig.ui.pageflow.command.<b>EMFCommandStackGEFAdapter</b> qui étent la classe GEF org.eclipse.gef.commands.<b>CommandStack</b> s&rsquo;occupe de transformer les Command EMF en Command GEF via la classe org.eclipse.jst.jsf.facesconfig.ui.pageflow.command.<b>EMFCommandGEFAdapter</b>.  Par exemple elle implémente la méthode GEF org.eclipse.gef.commands.<b>CommandStack#getUndoCommand()</b> comme ceci :
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public Command getUndoCommand() { <br />
&nbsp; if (emfCommandStack == null || emfCommandStack.getUndoCommand() == null) { <br />
&nbsp; &nbsp; return null; <br />
&nbsp; } <br />
&nbsp; &nbsp; return new EMFCommandGEFAdapter(emfCommandStack.getUndoCommand()); <br />
}</div></div>
<p>où emfCommandStack est la stack de command EMF du modèle SSE récupérée comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">model = StructuredModelManager.getModelManager().getExistingModelForEdit(doc); <br />
emfCommandStack = ((BasicCommandStack) this.model.getUndoManager().getCommandStack());</div></div>
<p>Un listener est ajouté à la commande de stack EMF : </p>
<p><code class="codecolorer text default"><span class="text">emfCommandStack.addCommandStackListener(this);</span></code></p>
<p>pour indiquer à la commande de stack GEF EMFCommandStackGEFAdapter que celle-ci change (ajout d&rsquo;une nouvelle commande GEF) et qu&rsquo;elle notifie tous ses listeners GEF org.eclipse.gef.commands.<b>CommandStackEvent</b> qu&rsquo;il y a un changement dans la stack :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void commandStackChanged(EventObject event) { <br />
&nbsp; this.notifyListeners(); <br />
}</div></div>
</li>
<li>la classe org.eclipse.jst.jsf.facesconfig.ui.pageflow.command.<b>EMFCommandGEFAdapter</b> étend la classe GEF org.eclipse.gef.commands.<b>Command</b> et attend dans son constructeur une Command EMF<br />
org.eclipse.emf.common.command.<b>Command</b>. Par exemple elle implémente la méthode GEF org.eclipse.gef.commands.<b>Command#undo()</b> comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void undo() { <br />
&nbsp; if (emfCommand == null) { <br />
&nbsp; &nbsp; return; <br />
&nbsp; } <br />
&nbsp; emfCommand.undo(); <br />
}</div></div>
<p>où emfCommand est la Command EMF passé dans le constructeur de EMFCommandGEFAdapter.
  </li>
</ul>
<p>L&rsquo;editor multi page FacesConfigEditor travaille toujours avec une stack de Command de type GEF. Si la page activée est celle de GEF, c&rsquo;est celle de GEF qui est utilisé, si c&rsquo;est une autre page, c&rsquo;est EMFCommandStackGEFAdapter qui est utilisé. FacesConfigEditor utilise la classe org.eclipse.jst.jsf.facesconfig.ui.pageflow.command.<b>DelegatingCommandStack</b> qui maintient la stack de Command GEF de la page active. L&rsquo;état dirty qui doit se baser sur la stack de command GEF de la page active est gérée par le listener GEF org.eclipse.jst.jsf.facesconfig.ui.FacesConfigEditor.<b>MultiPageCommandStackListener</b> qui implémente l&rsquo;interface GEF org.eclipse.gef.commands.<b>CommandStackListener</b>.</p>
<p>Je pense qu&rsquo;il est plus difficile (voir impossible) de ne travailler qu&rsquo;avec des Command EMF, autrement dit transformer les Command GEF en Command EMF car une Command GEF est une classe alors qu&rsquo;une Command EMF est une interface.</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
		<item>
		<title>Conception d&#8217;un Editeur Eclipse de workflow XML [step 18]</title>
		<link>https://blog.developpez.com/akrogen/p8032/plugin-eclipse/conception_d_un_editeur_eclipse_de_work_18</link>
		<comments>https://blog.developpez.com/akrogen/p8032/plugin-eclipse/conception_d_un_editeur_eclipse_de_work_18#comments</comments>
		<pubDate>Mon, 14 Sep 2009 16:29:59 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[A l&#8217;étape du billet précédant [step17] nous avons réglé le problème de la selection d&#8217;un EditPart GEF. Si vous mettez un point d&#8217;arrêt dans la méthode GraphicalEditor#selectionChanged(IWorkbenchPart part, ISelection selection), puis si vous sélectionnez un state, action de la page GEF Graphics vous pourrez constater que cette méthode est appelée : public void selectionChanged(IWorkbenchPart part, ISelection selection) { &#160; // If not the active editor, ignore selection changed. &#160; if (this.equals(getSite().getPage().getActiveEditor())) &#160; &#160; updateActions(selectionActions); } [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>A l&rsquo;étape du <a href="http://blog.developpez.com/akrogen/p7995/plugin-eclipse/conception-d-un-editeur-eclipse-de-work-17" >billet précédant [step17]</a> nous avons réglé le problème de la selection d&rsquo;un EditPart GEF. Si vous mettez un point d&rsquo;arrêt dans la méthode <b>GraphicalEditor#selectionChanged(IWorkbenchPart part, ISelection selection)</b>, puis si vous sélectionnez un state, action de la page GEF Graphics vous pourrez constater que cette méthode est appelée :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void selectionChanged(IWorkbenchPart part, ISelection selection) { <br />
&nbsp; // If not the active editor, ignore selection changed. <br />
&nbsp; if (this.equals(getSite().getPage().getActiveEditor())) <br />
&nbsp; &nbsp; updateActions(selectionActions); <br />
}</div></div>
<p>Mais la méthode updateActions n&rsquo;est jamais appelé. Nous expliquerons dans ce billet comment régler ce problème pour pouvoir ensuite implémenter les <b>fonctionnalités de suppression</b> : </p>
<ul>
<li><a href="#deleteActionState" >suppression des states/actions</a> sélectionnés.</li>
<li><a href="#deleteConnection" >suppression des connections</a> sélectionnées.</li>
</ul>
<p>La supppression pourra s&rsquo;effectuer via  : </p>
<ul>
<li>l&rsquo;entrée de menu &laquo;&nbsp;Delete&nbsp;&raquo; accessible par le <a href="#deleteWithContextualMenu" >menu contextuel</a> de la page GEF Graphics.</li>
<li><a href="#deleteWithSupprKey" >la touche &laquo;&nbsp;Suppr&nbsp;&raquo;</a>.</li>
<li>l&rsquo;entrée de menu &laquo;&nbsp;Delete&nbsp;&raquo; accessible par le <a href="#deleteWithGlobalMenu" >menu global &laquo;&nbsp;Edit&nbsp;&raquo;</a>.</li>
</ul>
<p>Voici une copie d&rsquo;écran qui affiche le menu contextuel qui permettra de supprimer un state sélectionné : </p>
<p><img src="http://blog.developpez.com/media/Workflow_DeleteActionTypeWithContextualMenu.png" width="942" height="747" alt="" /></p>
<p>Vous pouvez télécharger le projet <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/gef/org.example.workflow/step18/org.example.workflow_step18.zip">org.example.workflow_step18.zip</a> présenté dans ce billet.</p>
<p><span id="more-59"></span></p>
<h1 id="GEF_Actions" >GEF Actions</h1>
<p>Dans la page GEF, nous avons vu que notre modèle EMF est modifié via des Command GEF générée par nos EditPolicy installées qui réagissent à l&rsquo;outil sélectionné dans la palette GEF. Une autre manière est de <b>générer des Command GEF via des actions GEF</b>. Ces actions GEF étendent toutes la classe org.eclipse.jface.action.<b>Action</b> qui sont beaucoup utilisés dans l&rsquo;IDE Eclipse dans les menu (lorsque l&rsquo;on clique sur un item d&rsquo;un menu (ex : Copy), l&rsquo;item du menu est associé à une Action JFace. La classe de base d&rsquo;un editor GEF org.eclipse.gef.ui.parts.<b>GraphicalEditor</b> installe dans la méthode <b>GraphicalEditor#createActions()</b> par défaut 3 grands types d&rsquo;actions GEF:   </p>
<ul>
<li>les <b>action de selection</b> qui étendent org.eclipse.gef.ui.actions.<b>SelectAction</b>. Ce type d&rsquo;action génère des Command GEF lorsque le Viewer de GraphicalEditor (GraphicalViewer) est sélectionné, comme l&rsquo;action de delete.
 </li>
<li>les <b>action de stack</b> qui éténdent org.eclipse.gef.ui.actions.<b>StackAction</b>. qui permmettent de gérer le undo, redo&#8230;</li>
<li>les <b>action de properties</b> comme le save (je n&rsquo;ai pas encore bien compris a quoi sert cette action).</li>
</ul>
<h2 id="deleteAction" >DeleteAction</h2>
<p>L&rsquo;action qui nous intéresse dans notre cas est l&rsquo;<b>action de selection</b> delete org.eclipse.gef.ui.actions.<b>DeleteAction</b>. Cette action réagit à la selection d&rsquo;un EditPart et génère une Commande GEF de delete dans le cas ou l&rsquo;EditPart sélectionné à un EditPolicy qui rèagit à sa suppression (ex : dans notre cas nous installerons un EditPolicy sur les EditPart StateTypePart, ActionTypePart et ConnectionPart pour pouvoir les supprimer. </p>
<p>Lorsqu&rsquo;un EditPart GEF est sélectionné, une commande GEF DeleteCommand doit être instancié. Ceci est du à la méthode <b>DeleteAction#calculateEnabled()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">protected boolean calculateEnabled() { <br />
&nbsp; Command cmd = createDeleteCommand(getSelectedObjects()); <br />
&nbsp; if (cmd == null) <br />
&nbsp; &nbsp; return false; <br />
&nbsp; return cmd.canExecute(); <br />
}</div></div>
<p>Cette méthode est appelée pour vérifier si l&rsquo;action &laquo;&nbsp;Delete&nbsp;&raquo; doit être activée ou non. Lorsqu&rsquo;un EditPart GEF action/state est sélectionné, une commande GEF DeleteCommand devra être instanciée et l&rsquo;action &laquo;&nbsp;Delete&nbsp;&raquo; sera activée. L&rsquo;exéction de la commande GEF DeleteCommand s&rsquo;effectuera lorsque l&rsquo;action DeleteAction sera lancée via sa méthode <b>DeleteAction#run()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void run() { <br />
&nbsp; execute(createDeleteCommand(getSelectedObjects())); <br />
}</div></div>
<h2 id="GraphicalWorkflowEditor_selectionChanged" >GraphicalWorkflowEditor#selectionChanged</h2>
<p>Lorsqu&rsquo;un EditPart GEF est sélectionné, la méthode <b>DeleteAction#calculateEnabled()</b> doit être appelé pour activer/désactiver l&rsquo;action de delete en fonction de l&rsquo;EditPart GEF selectionné. Cette méthode est appelé lorsqu&rsquo;il y a une selection dans le viewer GEF par la méthode <b>GraphicalEditor#updateActions(List actionIds)</b>. Le code qui permet de rafraîchir les actions de selection lors d&rsquo;une selection dans le viewer de l&rsquo;editor GEF se trouve dans la classe GEF GraphicalEditor :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">/** <br />
&nbsp;* @see org.eclipse.ui.ISelectionListener#selectionChanged(IWorkbenchPart, ISelection) <br />
&nbsp;*/ <br />
public void selectionChanged(IWorkbenchPart part, ISelection selection) { <br />
&nbsp; // If not the active editor, ignore selection changed. <br />
&nbsp; if (this.equals(getSite().getPage().getActiveEditor())) <br />
&nbsp; &nbsp; updateActions(selectionActions); <br />
}</div></div>
<p>Cette méthode implémente l&rsquo;interface org.eclipse.ui.<b>ISelectionListener</b> qui est appelé (théoriquement) lorsque il y a une selection effectuée sur n&rsquo;importe quel éditeur du workbench Eclipse. GraphicalEditor joue le rôle d&rsquo;un listener de selection en s&rsquo;enregistrant dans le service de selection dans sa méthode init :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void init(IEditorSite site, IEditorInput input) throws PartInitException { <br />
&nbsp; ... <br />
&nbsp; getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(this); <br />
&nbsp; ... <br />
}</div></div>
<p>Le test dans la méthode <b>GraphicalEditor#selectionChanged</b> : </p>
<p><code class="codecolorer text default"><span class="text">if (this.equals(getSite().getPage().getActiveEditor()))</span></code></p>
<p>permet de tester si la sélection effectuée dans l&rsquo;IDE Eclipse, concerne bien l&rsquo;editor GEF en question. Dans un contexte ou l&rsquo;editor GEF n&rsquo;est pas inclus dans un multi page editor, ce code fonctionne très bien, mais qu&rsquo;il l&rsquo;est ce code <b>ne sera jamais éxécuté</b> car : </p>
<p><code class="codecolorer text default"><span class="text">getSite().getPage().getActiveEditor()</span></code> </p>
<p>retourne le multi page editor (dans notre cas WorkflowEditor) et pas l&rsquo;editor GEF qui est inclu dans une page du multi page editor (dans notre cas GraphicalWorkflowEditor). Pour régler ce problème il faut tester si la page courante sélectionnée est bien notre editor GEF. Pour connaître la page active d&rsquo;un editor, la classe de base org.eclipse.ui.part.<b>MultiPageEditorPart</b> (dont hérite notre multi page editor) fournit la méthode <b>MultiPageEditorPart#getActiveEditor()</b> mais qui est (pour je ne sais quelle raison) en <b>protected</b>. Pour pouvoir y accéder dans notre classe GraphicalWorkflowEditor, nous définissons une nouvelle <b>méthode public WorkflowEditor#getActivePageEditor()</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public IEditorPart getActivePageEditor() { <br />
&nbsp; return super.getActiveEditor(); <br />
}</div></div>
<p>Ici nous avons défini une nouvelle méthode (au lieu de surcharger la méthode <b>MultiPageEditorPart#getActiveEditor()</b> protected pour la rendre public) car dans la javdoc il y a : </p>
<p><code class="codecolorer text default"><span class="text">Subclasses should not override this method</span></code>.</p>
<p>Nous pouvons ensuite surcharger la méthode <b>GraphicalEditor#selectionChanged(IWorkbenchPart part, ISelection selection)</b> dans la classe GraphicalWorkflowEditor comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void selectionChanged(IWorkbenchPart part, ISelection selection) { <br />
&nbsp; // If not the active editor, ignore selection changed. <br />
&nbsp; IWorkbenchPartSite site = getSite(); <br />
&nbsp; if (parent.equals(site.getPage().getActiveEditor())) { <br />
&nbsp; &nbsp; // Multi page workflow editor is selected <br />
&nbsp; &nbsp; // If not the active page editor, ignore selection changed. <br />
&nbsp; &nbsp; WorkflowEditor workflowEditor = (WorkflowEditor)parent; &nbsp; &nbsp; &nbsp;<br />
&nbsp; &nbsp; if (this.equals(workflowEditor.getActivePageEditor())) { <br />
&nbsp; &nbsp; &nbsp; { <br />
&nbsp; &nbsp; &nbsp; &nbsp; super.updateActions(super.getSelectionActions()); <br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
}</div></div>
<p>Mettez un point d&rsquo;arrêt au debut de notre nouvelle méthode <b>GraphicalWorkflowEditor#selectionChanged(IWorkbenchPart part, ISelection selection)</b> et relancez le plugin. Si vous sélectionnez un state dans la page GEF, le point d&rsquo;arrêt s&rsquo;arrête.</p>
<h1 id="deleteActionState" >Delete ActionType-StateType</h1>
<p>Pour supprimer un state ou une action du workflow, nous allons créer une Commande GEF org.example.workflow.presentation.graphical.commands.<b>DeleteCommand</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.graphical.commands; <br />
&nbsp;<br />
import java.util.List; <br />
&nbsp;<br />
import org.eclipse.gef.commands.Command; <br />
import org.example.workflow.model.ActionType; <br />
import org.example.workflow.model.ConnectableNode; <br />
import org.example.workflow.model.StateType; <br />
import org.example.workflow.model.WorkflowType; <br />
&nbsp;<br />
public class DeleteCommand extends Command { <br />
&nbsp;<br />
&nbsp; private WorkflowType workflow; <br />
&nbsp; private ConnectableNode connectableNode; <br />
&nbsp; <br />
&nbsp; @Override <br />
&nbsp; public void execute() { <br />
&nbsp;<br />
&nbsp; &nbsp; getChildren().remove(getConnectableNode()); <br />
&nbsp; } <br />
&nbsp; <br />
&nbsp; public WorkflowType getWorkflow() { <br />
&nbsp; &nbsp; return workflow; <br />
&nbsp; } <br />
&nbsp; public void setWorkflow(WorkflowType workflow) { <br />
&nbsp; &nbsp; this.workflow = workflow; <br />
&nbsp; } <br />
&nbsp; public ConnectableNode getConnectableNode() { <br />
&nbsp; &nbsp; return connectableNode; <br />
&nbsp; } <br />
&nbsp; public void setConnectableNode(ConnectableNode connectableNode) { <br />
&nbsp; &nbsp; this.connectableNode = connectableNode; <br />
&nbsp; } <br />
&nbsp; <br />
&nbsp; protected List getChildren() { <br />
&nbsp; &nbsp; if (connectableNode instanceof StateType) <br />
&nbsp; &nbsp; &nbsp; return workflow.getState(); <br />
&nbsp; &nbsp; if (connectableNode instanceof ActionType) <br />
&nbsp; &nbsp; &nbsp; return workflow.getAction(); <br />
&nbsp; &nbsp; return null; <br />
&nbsp; } <br />
}</div></div>
<p>Cette commande GEF de delete doit être ensuite appelé via un EditPolicy qui doit étendre org.eclipse.gef.editpolicies.<b>ComponentEditPolicy</b>. En effet d&rsquo;après la javadoc : </p>
<p><cite>By default, ComponentEditPolicy understands being DELETEd from its container&#8230;</cite>. </p>
<p>Notre command GEF DeleteCommand doit être initialisée dans la méthode <b>ComponentEditPolicy#createDeleteCommand(GroupRequest deleteRequest)</b> que nous allons implémenter. Créez la classe org.example.workflow.presentation.graphical.policies.<b>WorkflowComponentEditPolicy</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.graphical.policies; <br />
&nbsp;<br />
import org.eclipse.gef.commands.Command; <br />
import org.eclipse.gef.editpolicies.ComponentEditPolicy; <br />
import org.eclipse.gef.requests.GroupRequest; <br />
import org.example.workflow.model.ConnectableNode; <br />
import org.example.workflow.model.WorkflowType; <br />
import org.example.workflow.presentation.graphical.commands.DeleteCommand; <br />
&nbsp;<br />
public class WorkflowComponentEditPolicy extends ComponentEditPolicy { <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; protected Command createDeleteCommand(GroupRequest deleteRequest) { <br />
&nbsp; &nbsp; WorkflowType parent = (WorkflowType)(getHost().getParent().getModel()); <br />
&nbsp; &nbsp; DeleteCommand deleteCmd = new DeleteCommand(); <br />
&nbsp; &nbsp; deleteCmd.setWorkflow(parent); <br />
&nbsp; &nbsp; deleteCmd.setConnectableNode((ConnectableNode)(getHost().getModel())); <br />
&nbsp; &nbsp; return deleteCmd; <br />
&nbsp; } <br />
}</div></div>
<p>Installez l&rsquo;EditiPolicy WorkflowComponentEditPolicy dans l&rsquo;EditPart Installez org.example.workflow.presentation.graphical.parts.<b>ConnectableNodePart</b> dans la méthode <b>ConnectableNodePart#createEditPolicies()</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
protected void createEditPolicies() { <br />
&nbsp; installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, <br />
&nbsp; &nbsp; &nbsp; new WorkflowNodeEditPolicy()); <br />
&nbsp; installEditPolicy(EditPolicy.COMPONENT_ROLE, <br />
&nbsp; &nbsp; &nbsp; new WorkflowComponentEditPolicy()); <br />
}</div></div>
<h1>Executer Commande delete</h1>
<p>L&rsquo;exéction de la commande GEF DeleteCommand s&rsquo;effectuera lorsque l&rsquo;action DeleteAction sera lancée via sa méthode <b>DeleteAction#run()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void run() { <br />
&nbsp; execute(createDeleteCommand(getSelectedObjects())); <br />
}</div></div>
<p>Il nous reste donc à lancer <b>DeleteAction#run()</b> qui peut s&rsquo;effectuer de plusieurs manières, autrement dit pouvoir supprimer un state, une action sélectionné : </p>
<ul>
<li><a href="#deleteWithContextualMenu" >par le menu contextuel</a> accéssible dans l&rsquo;editor GEF Graphics qui propose une entrée de menu &laquo;&nbsp;Delete&nbsp;&raquo;.</li>
<li><a href="#deleteWithSupprKey" >par la touche &laquo;&nbsp;Suppr&nbsp;&raquo;</a>.</li>
<li><a href="#deleteWithGlobalMenu" >par le menu globale</a> accéssible dans la barre de menu standard &laquo;&nbsp;Edit&nbsp;&raquo; l&rsquo;entrée de menu &laquo;&nbsp;Delete&nbsp;&raquo;.</li>
</ul>
<h2 id="deleteWithContextualMenu">Delete &#8211; Menu contextuel</h2>
<p>Dans cette section nous allons associer un menu contextuel à la page GEF Graphics et nous y ajouterons une entrée &laquo;&nbsp;Delete&nbsp;&raquo;. Nous avons vu que l&rsquo;action de delete GEF DeleteAction est ajouté par défaut au registry d&rsquo;actions de l&rsquo;editor GraphicalEditor. Pour ajouter un menu contextuel à une editor GEF, il est conseillé d&rsquo;implémenter la classe abstraite GEF org.eclipse.gef.<b>ContextMenuProvider</b>.</p>
<p>Créez la classe org.example.workflow.presentation.graphical.actions.<b>WorkflowContextMenuProvider</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.graphical.actions; <br />
&nbsp;<br />
import org.eclipse.gef.ContextMenuProvider; <br />
import org.eclipse.gef.EditPartViewer; <br />
import org.eclipse.gef.ui.actions.ActionRegistry; <br />
import org.eclipse.gef.ui.actions.GEFActionConstants; <br />
import org.eclipse.jface.action.IAction; <br />
import org.eclipse.jface.action.IMenuManager; <br />
import org.eclipse.ui.actions.ActionFactory; <br />
&nbsp;<br />
public class WorkflowContextMenuProvider extends ContextMenuProvider { <br />
&nbsp;<br />
&nbsp; private ActionRegistry actionRegistry; <br />
&nbsp;<br />
&nbsp; public WorkflowContextMenuProvider(EditPartViewer viewer, <br />
&nbsp; &nbsp; &nbsp; ActionRegistry registry) { <br />
&nbsp; &nbsp; super(viewer); <br />
&nbsp; &nbsp; setActionRegistry(registry); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void buildContextMenu(IMenuManager menu) { <br />
&nbsp; &nbsp; GEFActionConstants.addStandardActionGroups(menu); <br />
&nbsp;<br />
&nbsp; &nbsp; IAction action = getActionRegistry().getAction( <br />
&nbsp; &nbsp; &nbsp; &nbsp; ActionFactory.DELETE.getId()); <br />
&nbsp; &nbsp; if (action.isEnabled()) <br />
&nbsp; &nbsp; &nbsp; menu.appendToGroup(GEFActionConstants.GROUP_EDIT, action); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; private ActionRegistry getActionRegistry() { <br />
&nbsp; &nbsp; return actionRegistry; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void setActionRegistry(ActionRegistry registry) { <br />
&nbsp; &nbsp; actionRegistry = registry; <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<p>Cette classe éténd org.eclipse.gef.<b>ContextMenuProvider</b> et attend dans son constructeur un registry d&rsquo;actions (qui sera celui de l&rsquo;editor GraphicalEditor). La méthode <b>WorkflowContextMenuProvider#buildContextMenu(IMenuManager menu)</b> : </p>
<ol>
<li>appelle <code class="codecolorer text default"><span class="text">GEFActionConstants.addStandardActionGroups(menu);</span></code> qui ajoute les groupes de menu standards (EDIT, UNDO, COPY&#8230;).</li>
<li>ajoute l&rsquo;entrée de menu &laquo;&nbsp;Delete&nbsp;&raquo; dans le groupe EDIT si l&rsquo;action &laquo;&nbsp;Delete&nbsp;&raquo; peut être opérationnel (si une action/state est sélectionné).</li>
</ol>
<p>Dans les examples GEF, ils utilisent <b>IWorkbenchActionConstants.DELETE</b> pour rechercher l&rsquo;action de delete, mais cette constante est deprecated, il faut utiliser <b>ActionFactory.DELETE.getId()</b> à la place.</p>
<p>Pour indiquer à notre editor GEF que nous souhaitons utiliser notre menu contextuel WorkflowContextMenuProvider, modifiez la méthode <b>GraphicalWorkflowEditor#configureGraphicalViewer()</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
protected void configureGraphicalViewer() { <br />
&nbsp; super.configureGraphicalViewer(); <br />
&nbsp;<br />
&nbsp; GraphicalViewer viewer = getGraphicalViewer(); <br />
&nbsp; viewer.setEditPartFactory(WorkflowPartFactory.INSTANCE); <br />
&nbsp;<br />
&nbsp; ContextMenuProvider provider = new WorkflowContextMenuProvider( <br />
&nbsp; &nbsp; &nbsp; getGraphicalViewer(), getActionRegistry()); <br />
&nbsp; getGraphicalViewer().setContextMenu(provider); <br />
&nbsp; getSite().registerContextMenu( <br />
&nbsp; &nbsp; &nbsp; &quot;org.example.workflow.presentation.graphical.contextmenu&quot;, //$NON-NLS-1$ <br />
&nbsp; &nbsp; &nbsp; provider, getGraphicalViewer()); <br />
}</div></div>
<p>Relancez le plugin, sélectionnez une action/state et ouvrez le menu contextuel (clic droit de le souris), le menu contextuel doit s&rsquo;afficher avec une entré de menu &laquo;&nbsp;Delete&nbsp;&raquo; : </p>
<p><img src="http://blog.developpez.com/media/Workflow_DeleteActionTypeWithContextualMenu.png" width="942" height="747" alt="" /></p>
<p>Si vous sélectionnez cette action &laquo;&nbsp;Delete&nbsp;&raquo;, l&rsquo;action/state selectionné sera ensuite supprimé.</p>
<h2 id="deleteWithSupprKey" >Delete &#8211; Touche Suppr</h2>
<p>Nous souhaitons executer l&rsquo;action DeleteAction via la touche &laquo;&nbsp;Suppr&nbsp;&raquo;. Pour effectuer ceci, il suffit de mapper l&rsquo;action DeleteAction (identifié par ActionFactory.DELETE.getId()) avec la touche &laquo;&nbsp;Suppr&nbsp;&raquo; (identifié par SWT.DEL). Définissez la variable de type org.eclipse.gef.<b>KeyHandler</b> dans GraphicalWorkflowEditor qui contiendra notre mapping : </p>
<p><code class="codecolorer text default"><span class="text">private KeyHandler sharedKeyHandler;</span></code></p>
<p>Initialisez le mapping en ajoutant la méthode <b>GraphicalWorkflowEditor#getCommonKeyHandler()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">protected KeyHandler getCommonKeyHandler() { <br />
&nbsp; if (sharedKeyHandler == null) { <br />
&nbsp; &nbsp; sharedKeyHandler = new KeyHandler(); <br />
&nbsp; &nbsp; sharedKeyHandler <br />
&nbsp; &nbsp; &nbsp; &nbsp; .put(KeyStroke.getPressed(SWT.DEL, 127, 0), <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getActionRegistry().getAction(ActionFactory.DELETE.getId())); <br />
&nbsp; } <br />
&nbsp; return sharedKeyHandler; <br />
}</div></div>
<p>Pour utilisez notre mapping, ajoutez a la fin de la méthode <b>GraphicalWorkflowEditor#configureGraphicalViewer()</b> le code suivant :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">protected void configureGraphicalViewer() { <br />
&nbsp; ... <br />
&nbsp; viewer.setKeyHandler(new GraphicalViewerKeyHandler(getGraphicalViewer()).setParent(getCommonKeyHandler())); <br />
}</div></div>
<p>Relancez le plugin, sélectionnez une action/state puis appuyez sur la touche &laquo;&nbsp;Suppr&nbsp;&raquo; pour le supprimer.</p>
<h2 id="deleteWithGlobalMenu" >Delete &#8211; Menu globale</h2>
<p>Nous souhaitons maintenant mapper l&rsquo;action GEF DeleteActionAction via l&rsquo;entrée de menu &laquo;&nbsp;Delete&nbsp;&raquo; du menu globale standard &laquo;&nbsp;Edit&nbsp;&raquo;. Un EditorPart influe sur les actions disponibles dans la barre de menu en étendant généralement la classe org.eclipse.ui.part.<b>EditorActionBarContributor</b>. Cette barre de menu personnalisée pour l&rsquo;editor est souvent associée &laquo;&nbsp;déclarativement&nbsp;&raquo; dans le fichier plugin.xml à l&rsquo;aide de l&rsquo;attribut <b>contributorClass</b>. Par exemple dans notre editor de workflow nous avons une entré de menu &laquo;&nbsp;Workflow Editor&nbsp;&raquo; dans la barre de menu globale qui est disponible lorsque l&rsquo;editor de workflow est activé. Ce menu est gérée par la classe org.example.workflow.presentation.<b>WorkflowActionBarContributor</b> et est déclaré comme ceci dans le fichier plugin.xml :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">&lt;extension point=&quot;org.eclipse.ui.editors&quot;&gt; <br />
&nbsp; &nbsp; &nbsp; &lt;editor <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ... <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class=&quot;org.example.workflow.presentation.WorkflowEditor&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; contributorClass=&quot;org.example.workflow.presentation.WorkflowActionBarContributor&quot;&gt; <br />
&nbsp; &nbsp; &nbsp; &lt;/editor&gt; <br />
&nbsp; &nbsp;&lt;/extension&gt;</div></div>
<p>GEF fournit la classe abstraite org.eclipse.gef.ui.actions.<b>ActionBarContributor</b> qui éténd org.eclipse.ui.part.<b>EditorActionBarContributor</b> et qui est conseillé d&rsquo;utiliser pour des actions GEF associées à un editor GEF. Créez la classe org.example.workflow.presentation.graphical.actions.<b>GraphicalWorkkflowActionBarContributor</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.graphical.actions; <br />
&nbsp;<br />
import org.eclipse.gef.ui.actions.ActionBarContributor; <br />
import org.eclipse.gef.ui.actions.DeleteRetargetAction; <br />
import org.eclipse.jface.action.IToolBarManager; <br />
&nbsp;<br />
public class GraphicalWorkkflowActionBarContributor extends <br />
&nbsp; &nbsp; ActionBarContributor { <br />
&nbsp;<br />
&nbsp; protected void buildActions() { <br />
&nbsp; &nbsp; addRetargetAction(new DeleteRetargetAction()); <br />
&nbsp;<br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; public void contributeToToolBar(IToolBarManager toolBarManager) { <br />
&nbsp;<br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; protected void declareGlobalActionKeys() { <br />
&nbsp;<br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<p>Le code <code class="codecolorer text default"><span class="text">addRetargetAction(new DeleteRetargetAction());</span></code> dans la méthode buildActions, mappe l&rsquo;action de &laquo;&nbsp;Delete&nbsp;&raquo; du workbench avec l&rsquo;action DeleteAction GEF.</p>
<p>Maintenant nous devons associer cette barre personnalisée d&rsquo;actions lorsque la page GEF Graphics est activée. Les exemples GEF renseigne cette barre d&rsquo;actions déclarativement via l&rsquo;attribut <b>contributorClass</b> de l&rsquo;élement XML <b>editor</b> du fichier plugin.xml. Dans notre cas nous ne pouvons pas faire comme ceci car nous sommes dans un contexte de multi page, ce qui signifie que les barre d&rsquo;actions doivent se rafrîchir aussi en fonction de la page sélectionnée (et pas seulement au niveau de l&rsquo;editor). </p>
<p>Pour gérer ceci nous allons suivre les explication de la section <a href="http://www.eclipse.org/articles/Article-Integrating-EMF-GMF-Editors/index.html#completing_menus_and_toolbars" >Completing Menus and Toolbars</a> de l&rsquo;article GMF ou l&rsquo;idée générale est de modifier le code EMF pour pouvoir utiliser un org.eclipse.ui.part.<b>MultiPageEditorActionBarContributor</b> qui permet de gérer des barres d&rsquo;actions selon la page activée dans un editor multi page. Actuellement, l&rsquo;editor est associé à la barre d&rsquo;actions org.example.workflow.presentation.<b>WorkflowActionBarContributor</b> qui éténd org.eclipse.emf.edit.ui.action.<b>EditingDomainActionBarContributor</b></p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">&lt;extension point=&quot;org.eclipse.ui.editors&quot;&gt; <br />
&nbsp; &nbsp; &nbsp; &lt;editor <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ... <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; class=&quot;org.example.workflow.presentation.WorkflowEditor&quot; <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; contributorClass=&quot;org.example.workflow.presentation.WorkflowActionBarContributor&quot;&gt; <br />
&nbsp; &nbsp; &nbsp; &lt;/editor&gt; <br />
&nbsp; &nbsp;&lt;/extension&gt;</div></div>
<p>Nous allons créer la classe WorkflowMultipageActionBarContributor (comme TopicmapMultipageActionBarContributor) qui va éténdre org.eclipse.emf.edit.ui.action.<b>EditingDomainActionBarContributor</b> et gérer les barres d&rsquo;actions de EMF et celle de GEF en fonction de la page activée.</p>
<p>Copiez collez la classe de l&rsquo;article <a href="http://www.eclipse.org/articles/Article-Integrating-EMF-GMF-Editors/files/SubActionBarsExt.java" >SubActionBarsExt</a> dans le package <b>org.example.workflow.presentation</b>.</p>
<p>Créez la classe org.example.workflow.presentation.<b>WorkflowMultipageActionBarContributor</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation; <br />
&nbsp;<br />
import org.eclipse.emf.edit.domain.IEditingDomainProvider; <br />
import org.eclipse.ui.IActionBars; <br />
import org.eclipse.ui.IActionBars2; <br />
import org.eclipse.ui.IEditorPart; <br />
import org.eclipse.ui.IPropertyListener; <br />
import org.eclipse.ui.part.MultiPageEditorActionBarContributor; <br />
import org.example.workflow.presentation.graphical.GraphicalWorkflowEditor; <br />
import org.example.workflow.presentation.graphical.actions.GraphicalWorkkflowActionBarContributor; <br />
import org.example.workflow.presentation.parts.AbstractWorkflowEditorPart; <br />
&nbsp;<br />
/** <br />
&nbsp;* A special implementation of a <br />
&nbsp;* &lt;span&gt;::CODECOLORER_BLOCK_261::&lt;/span&gt; to switch between action bar<br />
&nbsp;* contributions for tree-based editor pages and diagram editor pages.<br />
&nbsp;* <br />
&nbsp;* @see MultiPageEditorActionBarContributor<br />
&nbsp;*/<br />
public class WorkflowMultipageActionBarContributor extends<br />
&nbsp; &nbsp; &nbsp; &nbsp; MultiPageEditorActionBarContributor {<br />
<br />
&nbsp; &nbsp; private IActionBars2 myActionBars2;<br />
<br />
&nbsp; &nbsp; private SubActionBarsExt myTreeSubActionBars;<br />
<br />
&nbsp; &nbsp; private SubActionBarsExt myDiagramSubActionBars;<br />
<br />
&nbsp; &nbsp; private SubActionBarsExt myActiveEditorActionBars;<br />
<br />
&nbsp; &nbsp; private IEditorPart myActiveEditor;<br />
<br />
&nbsp; &nbsp; private IPropertyListener myEditorPropertyChangeListener = new IPropertyListener() {<br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; public void propertyChanged(Object source, int propId) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (myActiveEditorActionBars != null) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (myActiveEditorActionBars.getContributor() instanceof WorkflowActionBarContributor) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((WorkflowActionBarContributor) myActiveEditorActionBars<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .getContributor()).update();<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; };<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; @Override<br />
&nbsp; &nbsp; public void init(IActionBars bars) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; super.init(bars);<br />
&nbsp; &nbsp; &nbsp; &nbsp; assert bars instanceof IActionBars2;<br />
&nbsp; &nbsp; &nbsp; &nbsp; myActionBars2 = (IActionBars2) bars;<br />
&nbsp; &nbsp; }<br />
<br />
&nbsp; &nbsp; @Override<br />
&nbsp; &nbsp; public void setActiveEditor(IEditorPart part) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (myActiveEditor != null) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myActiveEditor<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .removePropertyListener(myEditorPropertyChangeListener);<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; &nbsp; super.setActiveEditor(part);<br />
&nbsp; &nbsp; &nbsp; &nbsp; myActiveEditor = part;<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (myActiveEditor instanceof IEditingDomainProvider) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myActiveEditor.addPropertyListener(myEditorPropertyChangeListener);<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; }<br />
<br />
&nbsp; &nbsp; @Override<br />
&nbsp; &nbsp; public void setActivePage(IEditorPart activeEditor) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (activeEditor instanceof GraphicalWorkflowEditor) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setActiveActionBars(getDiagramSubActionBars(), activeEditor);<br />
&nbsp; &nbsp; &nbsp; &nbsp; } else {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (activeEditor instanceof AbstractWorkflowEditorPart) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setActiveActionBars(getTreeSubActionBars(), activeEditor);<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; }<br />
<br />
&nbsp; &nbsp; @Override<br />
&nbsp; &nbsp; public void dispose() {<br />
&nbsp; &nbsp; &nbsp; &nbsp; super.dispose();<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (myDiagramSubActionBars != null) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myDiagramSubActionBars.dispose();<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myDiagramSubActionBars = null;<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (myTreeSubActionBars != null) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myTreeSubActionBars.dispose();<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myTreeSubActionBars = null;<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; }<br />
<br />
&nbsp; &nbsp; /**<br />
&nbsp; &nbsp; &nbsp;* Switches the active action bars.<br />
&nbsp; &nbsp; &nbsp;*/<br />
&nbsp; &nbsp; private void setActiveActionBars(SubActionBarsExt actionBars,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; IEditorPart activeEditor) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (myActiveEditorActionBars != null<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;&amp; myActiveEditorActionBars != actionBars) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myActiveEditorActionBars.deactivate();<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; &nbsp; myActiveEditorActionBars = actionBars;<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (myActiveEditorActionBars != null) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myActiveEditorActionBars.setEditorPart(activeEditor);<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myActiveEditorActionBars.activate();<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; }<br />
<br />
&nbsp; &nbsp; /**<br />
&nbsp; &nbsp; &nbsp;* @return the sub cool bar manager for the tree-based editors.<br />
&nbsp; &nbsp; &nbsp;*/<br />
&nbsp; &nbsp; public SubActionBarsExt getTreeSubActionBars() {<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (myTreeSubActionBars == null) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myTreeSubActionBars = new SubActionBarsExt(getPage(),<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myActionBars2, new WorkflowActionBarContributor(),<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;org.example.workflow.presentation.TreeActionContributor&quot;);<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; &nbsp; return myTreeSubActionBars;<br />
&nbsp; &nbsp; }<br />
<br />
&nbsp; &nbsp; /**<br />
&nbsp; &nbsp; &nbsp;* @return the sub cool bar manager for the diagram editor.<br />
&nbsp; &nbsp; &nbsp;*/<br />
&nbsp; &nbsp; public SubActionBarsExt getDiagramSubActionBars() {<br />
&nbsp; &nbsp; &nbsp; &nbsp; if (myDiagramSubActionBars == null) {<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myDiagramSubActionBars = new SubActionBarsExt(<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getPage(),<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; myActionBars2,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new GraphicalWorkkflowActionBarContributor(),<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;org.example.workflow.presentation.graphical.actions.GraphicalWorkkflowActionBarContributor&quot;);<br />
&nbsp; &nbsp; &nbsp; &nbsp; }<br />
&nbsp; &nbsp; &nbsp; &nbsp; return myDiagramSubActionBars;<br />
&nbsp; &nbsp; }<br />
<br />
}<br />
<br />
Pour créer cette classe j'ai repris le code de la classe TopicmapMultipageActionBarContributor que j'ai adapté à notre besoin : <br />
<br />
&lt;ul&gt; <br />
&nbsp; &lt;li&gt;utilisation de WorkflowActionBarContributor au lieu de TopicmapActionBarContributor.&lt;/li&gt;<br />
&nbsp; &lt;li&gt;utilisation de GraphicalWorkkflowActionBarContributor au lieu de TopicMapDiagramActionBarContributor.&lt;/li&gt;<br />
&nbsp; &lt;li&gt; terst effectué sur le type d'editor AbstractWorkflowEditorPart car notre multi page contient une page source: <br />
&lt;code&gt;public void setActivePage(IEditorPart activeEditor) { <br />
&nbsp; if (activeEditor instanceof GraphicalWorkflowEditor) { <br />
&nbsp; &nbsp; setActiveActionBars(getDiagramSubActionBars(), activeEditor); <br />
&nbsp; } else { <br />
&nbsp; &nbsp; if (activeEditor instanceof AbstractWorkflowEditorPart) { <br />
&nbsp; &nbsp; &nbsp; setActiveActionBars(getTreeSubActionBars(), activeEditor); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
}</div></div>
</li>
</ul>
<p>Modifiez le fichier <b>plugin.xml</b> pour utiliser dans notre editor WorkflowEditor, le contribution de barres d&rsquo;actions <b>WorkflowMultipageActionBarContributor</b> au lieu de WorkflowActionBarContributor :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">... <br />
&nbsp; &lt;editor <br />
&nbsp; &nbsp; ... <br />
&nbsp; &nbsp; class=&quot;org.example.workflow.presentation.WorkflowEditor&quot; <br />
&nbsp; &nbsp; contributorClass=&quot;org.example.workflow.presentation.WorkflowMultipageActionBarContributor&quot;&gt; <br />
&nbsp; &lt;/editor&gt; <br />
...</div></div>
<p>Si vous relancez le plugin vous aurrez une erreur de ClassCastException sur WorkflowMultipageActionBarContributor : </p>
<p><img src="http://blog.developpez.com/media/Workflow_TestFail_ActionBarContributorClassCastException.png" width="943" height="744" alt="" /></p>
<p>Pour eviter cette erreur, modifiez la méthode <b>WorkflowEditor#getActionBarContributor()</b> pour récupérer de notre MultipageActionBarContributor la contribution de barres d&rsquo;action concernant l&rsquo;editor SelectionEditPart :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">/** <br />
&nbsp;* &lt;!-- begin-user-doc --&gt; &lt;!-- end-user-doc --&gt; <br />
&nbsp;* &nbsp;<br />
&nbsp;* @generated NOT <br />
&nbsp;*/ <br />
public EditingDomainActionBarContributor getActionBarContributor() { <br />
&nbsp; return (WorkflowActionBarContributor) ((WorkflowMultipageActionBarContributor) getEditorSite() <br />
&nbsp; &nbsp; &nbsp; .getActionBarContributor()).getTreeSubActionBars() <br />
&nbsp; &nbsp; &nbsp; .getContributor(); <br />
}</div></div>
<p>Relancez le plugin (vous n&rsquo;aurrez plus d&rsquo;erreur de ClassCastException), selectionnez une action/state et accéder au menu global <b>Edit/Delete</b> : </p>
<p><img src="http://blog.developpez.com/media/Workflow_DeleteActionTypeWithGlobalMenu.png" width="941" height="745" alt="" /></p>
<p>Cette entrée de menu est active lorsque un state/action est sélectionné. Si vous cliquez sur cette entrée de menu, le state/action sélectionné se supprime du workflow.</p>
<p>Si vous accédez à la page Selection et que vous tentez d&rsquo;ouvrir le menu contextuel, celui-ci ne s&rsquo;ouvre pas, pour remédier à ce problème, modifiez la méthode <b>AbstractWorkflowEditorPart#menuAboutToShow(IMenuManager manager)</b> de la classe org.example.workflow.presentation.parts.<b>AbstractWorkflowEditorPart</b> comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void menuAboutToShow(IMenuManager manager) { <br />
&nbsp; // pass the request to show the context menu on to the parent editor <br />
&nbsp; ((WorkflowActionBarContributor) ((WorkflowMultipageActionBarContributor) parentEditor <br />
&nbsp; &nbsp; &nbsp; .getEditorSite().getActionBarContributor()) <br />
&nbsp; &nbsp; &nbsp; .getTreeSubActionBars().getContributor()) <br />
&nbsp; &nbsp; &nbsp; .menuAboutToShow(manager); <br />
}</div></div>
<p>Relancez le plugin, le menu contentuel dans la page Selection doit être à nouveau opérationnel : </p>
<p><img src="http://blog.developpez.com/media/Workflow_ContextulaMenuSelectionPage.png" width="941" height="743" alt="" /></p>
<h1 id="deleteConnection" >Delete ConnectionType</h1>
<p>Pour supprimer une connection nous allons créer une Commande GEF org.example.workflow.presentation.graphical.commands.<b>DeleteConnectionCommand</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.graphical.commands; <br />
&nbsp;<br />
import org.eclipse.gef.commands.Command; <br />
import org.example.workflow.model.ActionType; <br />
import org.example.workflow.model.Connection; <br />
&nbsp;<br />
public class DeleteConnectionCommand extends Command { <br />
&nbsp;<br />
&nbsp; private Connection connection; <br />
&nbsp;<br />
&nbsp; public void setConnection(Connection connection) { <br />
&nbsp; &nbsp; this.connection = connection; <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void execute() { <br />
&nbsp; &nbsp; if (connection.getSource() instanceof ActionType) { <br />
&nbsp; &nbsp; &nbsp; ActionType action = (ActionType) connection.getSource(); <br />
&nbsp; &nbsp; &nbsp; action.setToState(null); <br />
&nbsp; &nbsp; } else { <br />
&nbsp; &nbsp; &nbsp; if (connection.getTarget() instanceof ActionType) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; ActionType action = (ActionType) connection.getTarget(); <br />
&nbsp; &nbsp; &nbsp; &nbsp; action.setFromState(null); <br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<p>Cette commande GEF de delete de connections doit être ensuite appelé via un EditPolicy qui doit étendre org.eclipse.gef.editpolicies.<b>ConnectionEditPolicy</b>. En effet d&rsquo;après la javadoc : </p>
<p><cite>A model-based EditPolicy for connections&#8230; By default, ConnectionEditPolicy understands only DELETE.</cite></p>
<p>Notre command GEF DeleteConnectionCommand doit être initialisée dans la méthode ConnectionEditPolicy#getDeleteCommand(GroupRequest deleteRequest) que nous allons implémenter. Créez la classe org.example.workflow.presentation.graphical.policies.<b>WorkflowConnectionEditPolicy </b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.graphical.policies; <br />
&nbsp;<br />
import org.eclipse.gef.commands.Command; <br />
import org.eclipse.gef.editpolicies.ConnectionEditPolicy; <br />
import org.eclipse.gef.requests.GroupRequest; <br />
import org.example.workflow.model.Connection; <br />
import org.example.workflow.presentation.graphical.commands.DeleteConnectionCommand; <br />
&nbsp;<br />
public class WorkflowConnectionEditPolicy extends ConnectionEditPolicy { <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; protected Command getDeleteCommand(GroupRequest request) { <br />
&nbsp; &nbsp; DeleteConnectionCommand cmd = new DeleteConnectionCommand(); <br />
&nbsp; &nbsp; Connection connection &nbsp;= (Connection) getHost().getModel(); <br />
&nbsp; &nbsp; cmd.setConnection(connection); <br />
&nbsp; &nbsp; return cmd; <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<p>Installez l&rsquo;EditiPolicy WorkflowConnectionEditPolicy dans l&rsquo;EditPart org.example.workflow.presentation.graphical.parts.<b>ConnectionPart</b> dans la méthode <b>ConnectionPart#createEditPolicies()</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
protected void createEditPolicies() { <br />
&nbsp; installEditPolicy(EditPolicy.CONNECTION_ROLE, new WorkflowConnectionEditPolicy()); <br />
}</div></div>
<p>Relancez le plugin, sélectionnez une connection de la page GEF et supprimez la à l&rsquo;aide du menu contextuel &laquo;&nbsp;Delete&nbsp;&raquo;, à l&rsquo;aide de la touche &laquo;&nbsp;Suppr&nbsp;&raquo; ou via le menu global &laquo;&nbsp;Edit/Delete&nbsp;&raquo;.</p>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Conception d&#8217;un Editeur Eclipse de workflow XML [step 17]</title>
		<link>https://blog.developpez.com/akrogen/p7995/plugin-eclipse/conception_d_un_editeur_eclipse_de_work_17</link>
		<comments>https://blog.developpez.com/akrogen/p7995/plugin-eclipse/conception_d_un_editeur_eclipse_de_work_17#comments</comments>
		<pubDate>Fri, 11 Sep 2009 16:12:07 +0000</pubDate>
		<dc:creator><![CDATA[azerr]]></dc:creator>
				<category><![CDATA[Plugin Eclipse]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[A l&#8217;étape du billet précédant [step16] nous avons finalisé la palette d&#8217;outils GEF qui permet de créer des states, des actions et des connections entre actions et states. Dans ce billet je souhaitais dans un premier temps expliquer comment mettre en place la fonctionnalité &#171;&#160;suppression de state, actions, connections&#160;&#187; dans la page GEF Graphics, à l&#8217;aide de la touche &#171;&#160;Suppr&#160;&#187; ou à l&#8217;aide d&#8217;une action d&#8217;un menu contextuel. Je me suis inspiré des exemples GEF [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>A l&rsquo;étape du <a href="http://blog.developpez.com/akrogen/p7984/plugin-eclipse/conception-d-un-editeur-eclipse-de-work-16" >billet précédant [step16]</a> nous avons finalisé la <b>palette d&rsquo;outils GEF</b> qui permet de créer des states, des actions et des connections entre actions et states. </p>
<p>Dans ce billet je souhaitais dans un premier temps expliquer comment mettre en place la fonctionnalité <b>&laquo;&nbsp;suppression de state, actions, connections&nbsp;&raquo;</b> dans la page GEF Graphics, à l&rsquo;aide de la <b>touche &laquo;&nbsp;Suppr&nbsp;&raquo;</b> ou à l&rsquo;aide d&rsquo;une <b>action d&rsquo;un menu contextuel</b>. Je me suis inspiré des exemples GEF qui sont basés sur un EditorPart simple (une seule page GEF) pour intégrer cette fonctionnalité de delete dans notre editor multi page de workflow (MultiPageEditorPart) et je me suis rendu compte que nous ne pouvions pas encore mettre en place cette fonctionnalité de delete avant d&rsquo;avoir régler le problème de sélection des EditPart GEF (pour ensuite les supprimer avec l&rsquo;action delete). La selection d&rsquo;EditPart GEF ne fonctionne pas dans notre cas car  :</p>
<ol>
<li>le <b>code générée par EMF concernant la sélection d&rsquo;items (ISelectionProvider)</b> dans l&rsquo;editor multi page WorkflowEditor <b>obstrue la selection d&rsquo;un item</b> provenant d&rsquo;une autre page non générée par EMF (qui n&rsquo;utilise pas ViewerPane EMF). Par exemple, dans le cas de notre editor GEF la selection d&rsquo;un EditPart GEF est obstruée. Pour plus d&rsquo;informations sur ce sujet, voir la section <a href="#GraphicalViewer_ISelectionProvider" >GEF GraphicalViewer &#8211; ISelectionProvider</a>.
  </li>
<li>nous sommes dans un contexte de multi page et que l&rsquo;editor GEF org.eclipse.gef.ui.parts.<b>GraphicalEditor ne fonctionne pas dans un multi page concernant le sujet de la selection d&rsquo;EditPart</b>. Nous réglerons ce problème dans le prochain billet.</li>
</ol>
<p>Pour mettre en évidence le premier problème, mettez un point d&rsquo;arrêt dans la méthode <b>GraphicalEditor#selectionChanged(IWorkbenchPart part, ISelection selection)</b>, sélectionnez un state, action de la page GEF Graphics et vous pourrez constater que cette méthode n&rsquo;est jamais appelé :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void selectionChanged(IWorkbenchPart part, ISelection selection) { <br />
&nbsp; // If not the active editor, ignore selection changed. <br />
&nbsp; if (this.equals(getSite().getPage().getActiveEditor())) <br />
&nbsp; &nbsp; updateActions(selectionActions); <br />
}</div></div>
<p>Nous expliquerons dans le prochain billet l&rsquo;importance de ce code qui réagit à la selection des EditPart GEF, et qui permet de gérer les actions de delete des EditPart GEF sélectionné.</p>
<p>Pour expliquer le 2ème problème, il faut que le premier soit résolu et mettez ensuite un point d&rsquo;arrêt à la fin de la méthode <b>GraphicalEditor#selectionChanged(IWorkbenchPart part, ISelection selection)</b> ( appel de updateActions) et vous pourrez constater que le point d&rsquo;arrêt ne s&rsquo;arrête jamais.</p>
<p>Pour résoudre le premier problème, je me suis appuyé sur l&rsquo;excellent article <a href="http://www.eclipse.org/articles/article.php?file=Article-Integrating-EMF-GMF-Editors/index.html" >Integrating EMF and GMF Generated Editors</a> qui explique pas à pas comment intégrer un editor GEF généré par <a href="http://www.eclipse.org/modeling/gmf/" >Graphical Modeling Framework (GMF)</a> dans un multi page editor généré par EMF.</p>
<p>A la fin de ce billet, lorsque nous sélectionnerons un state, action, connection, cette sélection s&rsquo;affichera dans la barre de statut (ce qui à ce stade ne fonctionne pas).</p>
<p><img src="http://blog.developpez.com/media/Workflow_Test_Debug_GEFEditPartSelected.png" width="943" height="744" alt="" /></p>
<p>Vous pouvez télécharger le projet <a href="ftp://ftp-developpez.com/angelo-zerr/tutoriels/eclipse/gef/org.example.workflow/step17/org.example.workflow_step17.zip">org.example.workflow_step17.zip</a> présenté dans ce billet.</p>
<p><span id="more-58"></span></p>
<h1 id="emfMultiPageEditorPart" >EMF &#8211; MultiPageEditorPart</h1>
<p>EMF est capable de générer à partir d&rsquo;un genmodel, un editor multi page <b>MultiPageEditorPart</b> constitué de plusieurs pages capable de mettre à jour un modèle EMF. Un MultiPageEditorPart est un <b>EditorPart constitué de plusieurs pages</b> qui peuvent être : </p>
<ul>
<li>un <b>Control SWT</b> qui peut être ajouté au multi page editor via la méthode <b>MultiPageEditorPart#addPage(Control control)</b>. Les pages générées par EMF sont basées sur un org.eclipse.emf.common.ui.<b>ViewerPane</b> qui construit en interne un Control SWT (accéssible via la méthode <b>ViewerPane#getControl()</b>) et qui est ajouté à l&rsquo;editor multi page.</li>
<li>un <b>EditorPart</b> (ex : notre EditorPart GEF) qui peut être ajouté au multi page editor via la méthode <b>MultiPageEditorPart#addPage(IEditorPart editor, IEditorInput input)</b></li>
</ul>
<p>Notre éditor multi page WorkflowEditor en est un exemple d&rsquo;editor multi page générée par EMF. Dans l&rsquo;introduction de ce billet nous avons évoqué le problème de selection d&rsquo;items. Pour regler ce problème nous devons effectuer des modifications du code générée par EMF qui va suivre les étapes décrites dans l&rsquo;article <a href="http://www.eclipse.org/articles/article.php?file=Article-Integrating-EMF-GMF-Editors/index.html" >Integrating EMF and GMF Generated Editors</a>.  Le lien <a href="http://www.eclipsezone.com/eclipse/forums/t107367.html" >Integrating EMF / GMF editors article &#8211; comments </a> est un commentaire effectué sur cet article qui est aussi très intéressant de lire.</p>
<p>L&rsquo;idée générale des modifications à effectuer dans le code générée par EMF est : </p>
<ul>
<li>d&rsquo;utiliser un <b>MultiPageSelectionProvider</b> au lieu de l&rsquo;implémentation de ISelectionProvider générée par EMF (le WorkflowEditor lui même).
  </li>
<li>Le MultiPageSelectionProvider impose le fait d&rsquo;avoir des EditorPart, ce qui implique de <b>transformer les ViewerPane EMF en EditorPart</b>.
  </li>
</ul>
<p>Dans ce billet je vais suivre les mêmes étapes que l&rsquo;article <a href="http://www.eclipse.org/articles/article.php?file=Article-Integrating-EMF-GMF-Editors/index.html" >Integrating EMF and GMF Generated Editors</a> : </p>
<ul>
<li><a href="#Dissecting_the_EMF_Editor" >Dissecting the EMF Editor</a> qui permettra d&rsquo;utiliser des EditorPart au lieu des ViewerPane.</li>
<li><a href="#Selection_Handling" >Selection Handling</a> qui permettra d&rsquo;utiliser un MultiPageSelectionProvider au lieu des WorkflowEditor qui implémente ISelectionProvider.</li>
<li><a href="#Combining_the_Editors" >Combining the Editors</a> qui corrigera le problème de la synchronisation de la page Selection avec la Outline.</li>
</ul>
<h2 id="Dissecting_the_EMF_Editor" >Dissecting the EMF Editor</h2>
<p>Ici nous allons effectuer la même chose que ce qui est expliqué dans la section <a href="http://www.eclipse.org/articles/Article-Integrating-EMF-GMF-Editors/index.html#dissecting_the_emf_editor" >Dissecting the EMF Editor</a> de l&rsquo;article GMF. L&rsquo;idée est de ne plus utiliser les ViewerPane EMF mais d&rsquo;utiliser les EditorPart. En effet nous souhaitons utiliser un MultiPageSelectionProvider dans notre editor, ce qui implique que chacune des pages doivent être des EditorPart (et pas des Control SWT). Le code <b>MultiPageSelectionProvider#getSelection()</b></p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public ISelection getSelection() { <br />
&nbsp; IEditorPart activeEditor = multiPageEditor.getActiveEditor(); <br />
&nbsp; if (activeEditor != null) { <br />
&nbsp; &nbsp; ISelectionProvider selectionProvider = activeEditor.getSite().getSelectionProvider(); <br />
&nbsp; &nbsp; if (selectionProvider != null) { <br />
&nbsp; &nbsp; &nbsp; return selectionProvider.getSelection(); <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
&nbsp; return StructuredSelection.EMPTY; <br />
}</div></div>
<p>montre que ce ISelectionProvider fonctionne qu&rsquo;avec des IEditorPart.</p>
<p>Nous allons transformer uniquement la page Selection basée sur un ViewerPane en EditorPart. Les autres pages n&rsquo;existeront plus dans notre editor. Mais nous allons quand même créer une classe de base (basé sur EditorPart) qui gère la plupart des problèmetque du ViewerPane, si jamais vous souhaitez ajouter les autres pages dans votre cas (je vous conseille de regarder l&rsquo;article qui explique comment faire). Cette classe de base est identique (presque) à TopicmapEditorPart expliqué dans l&rsquo;article.</p>
<p>Créer la classe org.example.workflow.presentation.parts.<b>AbstractWorkflowEditorPart</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.parts; <br />
&nbsp;<br />
import org.eclipse.core.runtime.IProgressMonitor; <br />
import org.eclipse.emf.common.command.BasicCommandStack; <br />
import org.eclipse.emf.common.notify.AdapterFactory; <br />
import org.eclipse.emf.edit.domain.EditingDomain; <br />
import org.eclipse.emf.edit.domain.IEditingDomainProvider; <br />
import org.eclipse.emf.edit.ui.dnd.EditingDomainViewerDropAdapter; <br />
import org.eclipse.emf.edit.ui.dnd.LocalTransfer; <br />
import org.eclipse.emf.edit.ui.dnd.ViewerDragAdapter; <br />
import org.eclipse.emf.edit.ui.provider.UnwrappingSelectionProvider; <br />
import org.eclipse.jface.action.IMenuListener; <br />
import org.eclipse.jface.action.IMenuManager; <br />
import org.eclipse.jface.action.MenuManager; <br />
import org.eclipse.jface.action.Separator; <br />
import org.eclipse.jface.viewers.StructuredViewer; <br />
import org.eclipse.swt.dnd.DND; <br />
import org.eclipse.swt.dnd.Transfer; <br />
import org.eclipse.swt.widgets.Menu; <br />
import org.eclipse.ui.IEditorInput; <br />
import org.eclipse.ui.IEditorSite; <br />
import org.eclipse.ui.PartInitException; <br />
import org.eclipse.ui.part.EditorPart; <br />
import org.example.workflow.presentation.WorkflowEditor; <br />
import org.example.workflow.presentation.WorkflowEditorPlugin; <br />
&nbsp;<br />
public abstract class AbstractWorkflowEditorPart extends EditorPart implements IMenuListener, IEditingDomainProvider { <br />
&nbsp;<br />
&nbsp; &nbsp; protected WorkflowEditor parentEditor; <br />
&nbsp; &nbsp; &nbsp;<br />
&nbsp; &nbsp; public AbstractWorkflowEditorPart(WorkflowEditor parent) { <br />
&nbsp; &nbsp; &nbsp; super(); <br />
&nbsp; &nbsp; &nbsp; this.parentEditor = parent; <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp;<br />
&nbsp; &nbsp; protected static String getString(String key) { <br />
&nbsp; &nbsp; &nbsp; return WorkflowEditorPlugin.INSTANCE.getString(key); <br />
&nbsp; &nbsp; } <br />
&nbsp;<br />
&nbsp; &nbsp; public EditingDomain getEditingDomain() { <br />
&nbsp; &nbsp; &nbsp; return parentEditor.getEditingDomain(); <br />
&nbsp; &nbsp; } <br />
&nbsp;<br />
&nbsp; &nbsp; protected BasicCommandStack getCommandStack() { <br />
&nbsp; &nbsp; &nbsp; return ((BasicCommandStack)getEditingDomain().getCommandStack()); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; &nbsp;<br />
&nbsp; &nbsp; protected AdapterFactory getAdapterFactory() { <br />
&nbsp; &nbsp; &nbsp; return parentEditor.getAdapterFactory(); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; &nbsp;<br />
&nbsp; &nbsp; protected void createContextMenuFor(StructuredViewer viewer) { <br />
&nbsp; &nbsp; &nbsp; MenuManager contextMenu = new MenuManager(&quot;#PopUp&quot;); <br />
&nbsp; &nbsp; &nbsp; contextMenu.add(new Separator(&quot;additions&quot;)); <br />
&nbsp; &nbsp; &nbsp; contextMenu.setRemoveAllWhenShown(true); <br />
&nbsp; &nbsp; &nbsp; contextMenu.addMenuListener(this); <br />
&nbsp; &nbsp; &nbsp; Menu menu= contextMenu.createContextMenu(viewer.getControl()); <br />
&nbsp; &nbsp; &nbsp; viewer.getControl().setMenu(menu); <br />
&nbsp; &nbsp; &nbsp; getSite().registerContextMenu(contextMenu, new UnwrappingSelectionProvider(viewer)); <br />
&nbsp;<br />
&nbsp; &nbsp; &nbsp; int dndOperations = DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK; <br />
&nbsp; &nbsp; &nbsp; Transfer[] transfers = new Transfer[] { LocalTransfer.getInstance() }; <br />
&nbsp; &nbsp; &nbsp; viewer.addDragSupport(dndOperations, transfers, new ViewerDragAdapter(viewer)); <br />
&nbsp; &nbsp; &nbsp; viewer.addDropSupport(dndOperations, transfers, new EditingDomainViewerDropAdapter(getEditingDomain(), viewer)); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; &nbsp;<br />
&nbsp; &nbsp; @Override <br />
&nbsp; &nbsp; public void doSave(IProgressMonitor monitor) { <br />
&nbsp; &nbsp; &nbsp; // nothing to do here - this is handled by the parent editor <br />
&nbsp; &nbsp; } <br />
&nbsp;<br />
&nbsp; &nbsp; @Override <br />
&nbsp; &nbsp; public void doSaveAs() { <br />
&nbsp; &nbsp; &nbsp; // nothing to do here - this is handled by the parent editor <br />
&nbsp; &nbsp; } <br />
&nbsp;<br />
&nbsp; &nbsp; @Override <br />
&nbsp; &nbsp; public void init(IEditorSite site, IEditorInput input) throws PartInitException { <br />
&nbsp; &nbsp; &nbsp; setSite(site); <br />
&nbsp; &nbsp; &nbsp; setInput(input); <br />
&nbsp; &nbsp; } <br />
&nbsp;<br />
&nbsp; &nbsp; @Override <br />
&nbsp; &nbsp; public boolean isDirty() { <br />
&nbsp; &nbsp; &nbsp; return getCommandStack().isSaveNeeded(); &nbsp; &nbsp; &nbsp; &nbsp; <br />
&nbsp; &nbsp; } <br />
&nbsp;<br />
&nbsp; &nbsp; @Override <br />
&nbsp; &nbsp; public boolean isSaveAsAllowed() { <br />
&nbsp; &nbsp; &nbsp; return true; <br />
&nbsp; &nbsp; } <br />
&nbsp;<br />
&nbsp; &nbsp; public void menuAboutToShow(IMenuManager manager) { <br />
&nbsp; &nbsp; &nbsp; // pass the request to show the context menu on to the parent editor <br />
&nbsp; &nbsp; &nbsp; ((IMenuListener)parentEditor.getEditorSite().getActionBarContributor()).menuAboutToShow(manager); <br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp;<br />
&nbsp; &nbsp; public abstract void setInput(Object input); <br />
&nbsp;<br />
&nbsp;<br />
}</div></div>
<p>Créer la classe org.example.workflow.presentation.parts.<b>SelectionEditorPart</b> comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">package org.example.workflow.presentation.parts; <br />
&nbsp;<br />
import org.eclipse.emf.edit.ui.celleditor.AdapterFactoryTreeEditor; <br />
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider; <br />
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider; <br />
import org.eclipse.jface.viewers.TreeViewer; <br />
import org.eclipse.swt.SWT; <br />
import org.eclipse.swt.layout.FillLayout; <br />
import org.eclipse.swt.widgets.Composite; <br />
import org.eclipse.swt.widgets.Tree; <br />
import org.example.workflow.presentation.WorkflowEditor; <br />
&nbsp;<br />
public class SelectionEditorPart extends AbstractWorkflowEditorPart { <br />
&nbsp;<br />
&nbsp; protected TreeViewer viewer; <br />
&nbsp;<br />
&nbsp; public SelectionEditorPart(WorkflowEditor parent) { <br />
&nbsp; &nbsp; super(parent); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void createPartControl(Composite parent) { <br />
&nbsp;<br />
&nbsp; &nbsp; viewer = new TreeViewer(parent, SWT.MULTI); <br />
&nbsp; &nbsp; Tree tree = viewer.getTree(); <br />
&nbsp; &nbsp; tree.setLayoutData(new FillLayout()); <br />
&nbsp;<br />
&nbsp; &nbsp; viewer.setContentProvider(new AdapterFactoryContentProvider( <br />
&nbsp; &nbsp; &nbsp; &nbsp; getAdapterFactory())); <br />
&nbsp; &nbsp; viewer.setLabelProvider(new AdapterFactoryLabelProvider( <br />
&nbsp; &nbsp; &nbsp; &nbsp; getAdapterFactory())); <br />
&nbsp;<br />
&nbsp; &nbsp; new AdapterFactoryTreeEditor(tree, getAdapterFactory()); <br />
&nbsp;<br />
&nbsp; &nbsp; createContextMenuFor(viewer); <br />
&nbsp; &nbsp; getEditorSite().setSelectionProvider(viewer); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void setFocus() { <br />
&nbsp; &nbsp; viewer.getTree().setFocus(); <br />
&nbsp; } <br />
&nbsp;<br />
&nbsp; @Override <br />
&nbsp; public void setInput(Object input) { <br />
&nbsp; &nbsp; viewer.setInput(input); <br />
&nbsp; } <br />
&nbsp;<br />
}</div></div>
<p>Cette classe gère la même chose que la page Selection basé sur un ViewerPane.</p>
<p>Dans la classe WorkflowEditor, définissez un champs selectionEditorPart</p>
<p><code class="codecolorer text default"><span class="text">private SelectionEditorPart selectionEditorPart;</span></code></p>
<p>Dans la méthode <b>WorkflowEditor#createPages()</b> supprimez tout le code qui utilise les ViewerPane et remplacez le par le code suivant :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;height:300px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">@Override <br />
public void createPages() { <br />
&nbsp; // Creates the model from the editor input <br />
&nbsp; // <br />
&nbsp; createModel(); <br />
&nbsp;<br />
&nbsp; // Only creates the other pages if there is something that can be edited <br />
&nbsp; // <br />
&nbsp; if (!getEditingDomain().getResourceSet().getResources().isEmpty()) { <br />
&nbsp; &nbsp; &nbsp; int pageIndex; <br />
&nbsp;<br />
&nbsp; &nbsp; try { <br />
&nbsp; &nbsp; &nbsp; // Create a page for the selection tree view. <br />
&nbsp; &nbsp; &nbsp; // <br />
&nbsp; &nbsp; &nbsp; selectionEditorPart = new SelectionEditorPart(this); <br />
&nbsp; &nbsp; &nbsp; pageIndex = addPage(selectionEditorPart, getEditorInput()); <br />
&nbsp; &nbsp; &nbsp; setPageText(pageIndex, getString(&quot;_UI_SelectionPage_label&quot;)); <br />
&nbsp; &nbsp; &nbsp; selectionEditorPart.setInput(getEditingDomain() <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .getResourceSet()); <br />
&nbsp; &nbsp; } catch (PartInitException e) { <br />
&nbsp; &nbsp; &nbsp; e.printStackTrace(); <br />
&nbsp; &nbsp; } <br />
&nbsp;<br />
&nbsp; &nbsp; // Add graphical page <br />
&nbsp; &nbsp; createAndAddGraphicalPage(); <br />
&nbsp; &nbsp; // Add source page <br />
&nbsp; &nbsp; createAndAddSourcePage(); <br />
...</div></div>
<p>Nous pouvons maintenant supprimer les méthodes <b>WorkflowEditor#setCurrentViewerPane(ViewerPane viewerPane)</b> et <b>WorkflowEditor#setFocus()</b>. La ré-implementation de <b>WorkflowEditor#isDirty()</b> peut être aussi enlevée.</p>
<h2 id ="Selection_Handling" >Selection Handling</h2>
<p>A cette étape si vous relancez le plugin et que vous sélectionnez un state, action dans le Treeview de la page Selection, la vue Properties n&rsquo;est pas rafraîchit correctement. La selection d&rsquo;items ne fonctionnent plus. Ici nous allons effectuer la même chose que ce qui est expliqué dans la section <a href="http://www.eclipse.org/articles/Article-Integrating-EMF-GMF-Editors/index.html#selection_handling">Selection Handling</a> de l&rsquo;article GMF. L&rsquo;idée est d&rsquo;utiliser un MultiPageSelectionProvider en tant que ISelectionProvider au lieu du WorkflowEditor lui même (qui implémente ISelectionProvider).</p>
<p>Dans la classe WorkflowEditor, ajoutez le champs de type org.eclipse.ui.part.<b>MultiPageSelectionProvider</b> : </p>
<p><code class="codecolorer text default"><span class="text">private MultiPageSelectionProvider selectionProvider;</span></code></p>
<p>Modifiez le constructueur de WorkflowEditor comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public WorkflowEditor() { <br />
&nbsp; super(); <br />
&nbsp; initializeEditingDomain(); <br />
&nbsp; &nbsp;selectionProvider = new MultiPageSelectionProvider(this); <br />
&nbsp; &nbsp; &nbsp; selectionProvider.addSelectionChangedListener(new ISelectionChangedListener() { <br />
&nbsp; &nbsp; &nbsp; &nbsp; public void selectionChanged(SelectionChangedEvent event) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setStatusLineManager(event.getSelection()); <br />
&nbsp; &nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; &nbsp; }); <br />
}</div></div>
<p>Ce code permet d&rsquo;instancier le MultiPageSelectionProvider. Un listener est branché dessus pour détecter les changement de selection (ex : selection d&rsquo;un item du Treeview de la page Selection, selection d&rsquo;un EditPart GEF) et afficher le resultat de la selection dans la barre de statut (en bas à gauche du workbench).</p>
<p>WorkflowEditor ne doit plus implémenter ISelectionProvider :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public class WorkflowEditor extends MultiPageEditorPart implements <br />
&nbsp; &nbsp; IEditingDomainProvider, IMenuListener, IViewerProvider, IGotoMarker { <br />
...</div></div>
<p>Maintenant nous pouvons supprimer tout le code qui implémente l&rsquo;interface ISelectionProvider : supprimez les champs <b>selectionChangedListener</b>, <b>selectionChangedListeners</b> et <b>editorSelection</b> et supprimez les méthodes <b>WorkflowEditor#addSelectionChangedListener(ISelectionChangedListener listener)</b> <b>WorkflowEditor#removeSelectionChangedListener(ISelectionChangedListener listener)</b>,  <b>WorkflowEditor#getSelection()</b> et <b>WorkflowEditor#setSelection(ISelection selection)</b>. </p>
<p>Commentez dans la méthode <b>WorkflowEditor#handleChangedResources()</b> le code suivant :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">//if (AdapterFactoryEditingDomain.isStale(editorSelection)) { <br />
//setSelection(StructuredSelection.EMPTY); <br />
//}</div></div>
<p>L&rsquo;utilisation du ISelectionProvider s&rsquo;effectue via le IEditorSite. Modifiez le code de la méthode <b>WorkflowEditor#init(IEditorSite site, IEditorInput editorInput)</b> pour pouvoir utiliser notre MultiPageSelectionProvider :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void init(IEditorSite site, IEditorInput editorInput) { <br />
... <br />
site.setSelectionProvider(selectionProvider); <br />
... <br />
}</div></div>
<p>Pour information, Un EditorPart possède un org.eclipse.ui.<b>IEditorSite</b> qui joue le rôle d&rsquo;interface entre le workbench et un editor part. IEditorSite est accessible via la méthode  EditorPart#getSite() (plus exactement WorkbenchPart#getSite()). C&rsquo;est à travers IEditorSite que l&rsquo;on renseigne par exemple : </p>
<ul>
<li>les menu contextuels (registerContextMenu) disponibles dans l&rsquo;EditorPart.
  </li>
<li>les actions disponibles (getActionBars) dans le menu globale du workbench (lorsque l&rsquo;editor est sélectionné).
  </li>
<li>le fournisseur de selection org.eclipse.jface.viewers.<a href="#ISelectionProvider" >ISelectionProvider</a> de l&rsquo;editor via la méthode <b>EditorPart#setSelectionProvider(ISelectionProvider provider)</b>.
  </li>
</ul>
<p>Modifiez la méthode <b>WorkflowEditor#handleActivate()</b> pour pouvoir utiliser notre MultiPageSelectionProvider :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">protected void handleActivate() { <br />
&nbsp; // Recompute the read only state. <br />
&nbsp; // <br />
&nbsp; if (editingDomain.getResourceToReadOnlyMap() != null) { <br />
&nbsp; &nbsp; editingDomain.getResourceToReadOnlyMap().clear(); <br />
&nbsp;<br />
&nbsp; &nbsp; // Refresh any actions that may become enabled or disabled. <br />
&nbsp; &nbsp; // <br />
&nbsp; &nbsp; selectionProvider.setSelection(selectionProvider.getSelection()); <br />
&nbsp; } <br />
...</div></div>
<p>Modifiez <b>WorkflowEditor#setCurrentViewer(Viewer viewer)</b> qui n&rsquo;a plus besoin de gérer la selection (branchement/débranchement des listener ISelectionChangedListener) :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void setCurrentViewer(Viewer viewer) { <br />
&nbsp; if (currentViewer != viewer) { <br />
&nbsp; &nbsp; currentViewer = viewer; <br />
&nbsp; &nbsp;} <br />
}</div></div>
<p>Relancez le plugin et assurez vous que la vue Outline n&rsquo;est pas ouverte, maintenant lorsque vous sélectionnez un EditPart GEF dans la page Graphics, la selection de ce dernier doit s&rsquo;afficher dans le barre de statut : </p>
<p><img src="http://blog.developpez.com/media/Workflow_Test_Debug_GEFEditPartSelected.png" width="943" height="744" alt="" /></p>
<p>Vous pouvez aussi vérifier que lorsque vous sélectionnez un item du Treeview dans la page Selection, la selection doit aussi s&rsquo;afficher dans la barre de statut.</p>
<h2 id="Combining_the_Editors" >Combining the Editors</h2>
<p>Ouvrez la vue Outline via le menu <b>Window/Show view/Outline</b>, mettez vous sur la page Selection, puis sélectionnez un item de la Outline. Le Treeview de la page Selection, n&rsquo;est pas synchronisé avec la vue Outline : </p>
<p><img src="http://blog.developpez.com/media/Workflow_Test_Debug_GEFEditPartSelectedOutlineNOK.png" width="943" height="744" alt="" /></p>
<p>Le problème vient de la méthode <b>WorkflowEditor#handleContentOutlineSelection()</b> qui teste entre autre si la variable currentViewerPane n&rsquo;est pas null. Dans notre cas elle est toujours null (car elle était mise à jour par le code générée que nous avons supprimé).</p>
<p>Ici nous allons effectuer la même chose que ce qui est expliqué dans la section <a href="http://www.eclipse.org/articles/Article-Integrating-EMF-GMF-Editors/index.html#combining_the_editors" >Combining the Editors</a> de l&rsquo;article GMF</p>
<p>Pour régler ce problème, supprimez le champs currentViewerPane </p>
<p><code class="codecolorer text default"><span class="text">&nbsp;//protected ViewerPane currentViewerPane;</span></code></p>
<p>et modifiez le code comme suit :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void handleContentOutlineSelection(ISelection selection) { <br />
&nbsp; if (!selection.isEmpty() &amp;&amp; selection instanceof IStructuredSelection) { &nbsp;<br />
&nbsp; &nbsp; &nbsp; List selectedElements = ((IStructuredSelection) selection).toList(); &nbsp;<br />
&nbsp; &nbsp; if (getActiveEditor() == selectionEditorPart) { <br />
&nbsp; &nbsp; &nbsp; // If the selection viewer is active, we want it to select the &nbsp;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // same selection as this selection. &nbsp;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; selectionProvider.setSelection(new StructuredSelection( &nbsp;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; selectedElements)); &nbsp;<br />
&nbsp; &nbsp; } <br />
&nbsp; &nbsp; else { <br />
&nbsp; &nbsp; &nbsp; if (getActiveEditor() instanceof AbstractWorkflowEditorPart) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; // For any other viewer, set the input directly. &nbsp;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((AbstractWorkflowEditorPart) getActiveEditor()).setInput(selectedElements.get(0)); &nbsp;<br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; } <br />
&nbsp; } <br />
}</div></div>
<p>Ce code gère la selection de la page Selection et des autres pages qui hériterait de AbstractWorkflowEditorPart. Ici nous ne gerons ni la page source, ni la page Graphics. Relancez le plugin et vous pourrez constater que la Treeview de la page Selection est synchronisée avec le Treeview de la vue Outline : </p>
<p><img src="http://blog.developpez.com/media/Workflow_Test_Debug_GEFEditPartSelectedOutlineOk.png" width="943" height="744" alt="" /></p>
<h1>GEF &#8211; Selection EdiPart</h1>
<p>Les modifications de code sont finies dans ce billet. Dans cette section je vais tenter d&rsquo;expliquer le problème de selection d&rsquo;EditPart GEF avec le code générée par EMF.</p>
<h2 id="GraphicalViewer_ISelectionProvider" >GEF GraphicalViewer &#8211; ISelectionProvider</h2>
<p>Nous avons modifié le code pour utiliser MultiPageSelectionProvider (et plus que WorkflowEditor implémente ISelectionProvider). Mais pourquoi devons nous faire ca? Ici je vais tenter d&rsquo;expliquer pourquoi l&rsquo;utilisation de MultiPageSelectionProvider règle le problème de selection des EditPart GEF.</p>
<p>Lorsqu&rsquo;un EditPart GEF est sélectionné (clic de la souris sur un state, action, connection de la page Graphics GEF), le viewer GEF GraphicalViewer associé à l&rsquo;editor GEF GraphicalEditor est capable de détecter l&rsquo;EditPart sélectionné et notifie les listeners qui ont été préalablement enregistrés qu&rsquo;un/plusieurs EditPart a/ont été sélectionnés. Le viewer GEF joue le rôle d&rsquo;un fournisseur de sélection (il implémente l&rsquo;interface org.eclipse.jface.viewers.<b>ISelectionProvider</b>) sur lequel on peut brancher des listeners org.eclipse.jface.viewers.<b>ISelectionChangedListener</b> qui souhaitent être informés des EditPart sélectionnés.</p>
<p>La méthode qui nous intéresse pour comprendre le probléme de selection dans GEF engendré par le code générée EMF est <b>AbstractEditPartViewer#fireSelectionChanged()</b> de la classe org.eclipse.gef.ui.parts.<b>AbstractEditPartViewer</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">protected void fireSelectionChanged() { <br />
&nbsp; &nbsp; Object listeners[] = selectionListeners.toArray(); <br />
&nbsp; SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection()); <br />
&nbsp; for (int i = 0; i &lt; listeners.length; i++) <br />
&nbsp; &nbsp; ((ISelectionChangedListener)listeners[i]) <br />
&nbsp; &nbsp; &nbsp; .selectionChanged(event); <br />
}</div></div>
<p>Cette méthode est appelée dès qu&rsquo;un ou plusieurs EditPart GEF sont sélectionnés et appelle pour chacun des listeners qui implémente la méthode <b>ISelectionChangedListener#selectionChanged(SelectionChangedEvent event)</b> de l&rsquo;interface ISelectionChangedListener. Si vous mettez un point d&rsquo;arrêt dans la méthode <b>AbstractEditPartViewer#fireSelectionChanged()</b>, vous pourrez constater que 2 listeners ISelectionChangedListener sont enregistrés : </p>
<ul>
<li>org.eclipse.gef.ui.parts.SelectionSynchronizer : qui d&rsquo;après la javadoc GEF est utilisé pour synchroniser les selection entre plusieurs viewer GEF (ce qui n&rsquo;est pas notre cas).</li>
<li>org.eclipse.ui.part.MultiPageEditorSite : qui est l&rsquo;IEditorSite de notre multi page editor. Plus exactement c&rsquo;est la méthode privée <b>MultiPageEditorSite#getSelectionChangedListener()</b> qui joue le rôle de listener.</li>
</ul>
<p>Le listener qui nous intéresse est le deuxième (MultiPageEditorSite). Voici le code de <b>MultiPageEditorSite#getSelectionChangedListener()</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">private ISelectionChangedListener getSelectionChangedListener() { <br />
&nbsp; if (selectionChangedListener == null) { <br />
&nbsp; &nbsp; selectionChangedListener = new ISelectionChangedListener() { <br />
&nbsp; &nbsp; &nbsp; public void selectionChanged(SelectionChangedEvent event) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; MultiPageEditorSite.this.handleSelectionChanged(event); <br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; }; <br />
&nbsp; } <br />
&nbsp; return selectionChangedListener; <br />
}</div></div>
<p>Lorsqu&rsquo;une selection d&rsquo;un EditPart GEF est effectuée, le méthode <b>MultiPageEditorSite#handleSelectionChanged</b> de la classe MultiPageEditorSite est appelée. Voici son code :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">protected void handleSelectionChanged(SelectionChangedEvent event) { <br />
&nbsp; ISelectionProvider parentProvider = getMultiPageEditor().getSite() <br />
&nbsp; &nbsp; &nbsp; .getSelectionProvider(); <br />
&nbsp; if (parentProvider instanceof MultiPageSelectionProvider) { <br />
&nbsp; &nbsp; SelectionChangedEvent newEvent = new SelectionChangedEvent( <br />
&nbsp; &nbsp; &nbsp; &nbsp; parentProvider, event.getSelection()); <br />
&nbsp; &nbsp; MultiPageSelectionProvider prov = (MultiPageSelectionProvider) parentProvider; <br />
&nbsp; &nbsp; prov.fireSelectionChanged(newEvent); <br />
&nbsp; } <br />
}</div></div>
<p>Le test qui nous intéresse est </p>
<p><code class="codecolorer text default"><span class="text">if (parentProvider instanceof MultiPageSelectionProvider) ...</span></code></p>
<p>Il vérifie que le fournisseur de sélection du multi page editor (dans notre cas WorkflowEditor) est de type MultiPageSelectionProvider. Ce test n&rsquo;est jamais vrai avec le code générée par EMF car le fournisseur de selection le multi page WorkflowEditor lui-même. En effet si on regarde le code générée par EMF de notre multi page editor WorkflowEditor, cette dernière implémente ISelectionProvider</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public class WorkflowEditor <br />
&nbsp; extends MultiPageEditorPart <br />
&nbsp; implements IEditingDomainProvider, ISelectionProvider, IMenuListener, IViewerProvider, IGotoMarker { <br />
...</div></div>
<p>et indique que le fournisseur de selection de son IEditorSite (qui est un MultiPageEditorSite)  est lui-même dans la méthode <b>WorkflowEditor#init(IEditorSite site, IEditorInput editorInput)</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void init(IEditorSite site, IEditorInput editorInput) { <br />
&nbsp; &nbsp;... <br />
&nbsp; &nbsp;site.setSelectionProvider(this); <br />
&nbsp; &nbsp;...</div></div>
<p>Par défaut un MultiPageEditorPart utilise un MultiPageSelectionProvider mais le code générée par EMF n&rsquo;utilise pas de MultiPageSelectionProvider ce qui rtend obsolète la sélection des EditPart GEF.</p>
<h2 id="GraphicalEditor_ISelectionChangedListener">GEF GraphicalEditor &#8211; ISelectionChangedListener</h2>
<p>Après avoir modifiez le code pour pouvoir utiliser un MultiPageSelectionProvider, mettez un point d&rsquo;arrêt dans la méthode <b>GraphicalEditor#selectionChanged#selectionChanged(IWorkbenchPart part, ISelection selection),</b> et vous pourrez constater que le point d&rsquo;arrêt s&rsquo;arrête : </p>
<p>Si on regarde la trace on peut voir que la méthode <b>MultiPageSelectionProvider#fireSelectionChanged()</b> est appelé qui appelle la méthode <b>MultiPageSelectionProvider#fireEventChange(final SelectionChangedEvent event, Object[] listeners)</b> :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void fireSelectionChanged(final SelectionChangedEvent event) { <br />
&nbsp; &nbsp; &nbsp; &nbsp; Object[] listeners = this.listeners.getListeners(); <br />
&nbsp; &nbsp; &nbsp; &nbsp; fireEventChange(event, listeners); <br />
}</div></div>
<p>La méthode  fireEventChange :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">private void fireEventChange(final SelectionChangedEvent event, Object[] listeners) { <br />
&nbsp; for (int i = 0; i &lt; listeners.length; ++i) { <br />
&nbsp; &nbsp; final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i]; <br />
&nbsp; &nbsp; SafeRunner.run(new SafeRunnable() { <br />
&nbsp; &nbsp; &nbsp; public void run() { <br />
&nbsp; &nbsp; &nbsp; &nbsp; l.selectionChanged(event); <br />
&nbsp; &nbsp; &nbsp; } <br />
&nbsp; &nbsp; }); <br />
&nbsp; } <br />
}</div></div>
<p>itère sur la liste des listeners enregistrés. </p>
<p>Le listener qui nous intéresse est le service de selection (AbstractSelectionService) qui est enregistré dans les listeners et qui appelle la méthode <b>GraphicalEditor#selectionChanged#selectionChanged(IWorkbenchPart part, ISelection selection),</b>.</p>
<p>GEF utilise le service de selection. Le GraphicalViewer GEF jour le rôle du fournisseur de selection ISelectionProvider et l&rsquo;editor GEF GraphicalEditor jour le rôle du consommateur de selection (ISelectionChangedListener). Ce dernier s&rsquo;abonne au service de selection dans sa méthode init :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">public void init(IEditorSite site, IEditorInput input) throws PartInitException { <br />
&nbsp; ... <br />
&nbsp; getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(this); <br />
&nbsp; ... <br />
}</div></div>
<p>Pour plus d&rsquo;information sur l&rsquo;utilisation du service de selection je vous conseille de lire l&rsquo;article <a href="http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html" >Eclipse Workbench: Using the Selection Service</a>.</p>
<p>L&rsquo;editor GEF GraphicalEditor enregistre le fournisseur de selection GraphicalViewer via le MultiPageEditorSite comme ceci :</p>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;width:435px;"><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">protected void hookGraphicalViewer() { <br />
&nbsp; getSelectionSynchronizer().addViewer(getGraphicalViewer()); <br />
&nbsp; getSite().setSelectionProvider(getGraphicalViewer()); <br />
}</div></div>
]]></content:encoded>
			<wfw:commentRss></wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>
