Le site francophone consacré au projet Eclipse et à OSGi
 
 

 

 

(publié le 6/4/2007 - Versions utilisées : Eclipse SDK 3.2.2 - JDK 5.0)

 

Tutorial : développer des plugins Eclipse (1ère partie)


Que ce soit pour enrichir l'environnement de développement ou bien pour développer des applications Eclipse RCP, la notion de plug-in est centrale.

Les plug-ins sont la concrétisation des deux principaux objectifs des concepteurs d'Eclipse : modularité et extensibilité.

Au cours de ce tutorial, nous allons dans un premier temps étudier la création et le test de plugins, puis nous aborderons la gestion des relations entre plugins via le mécanisme de dépendance. Dans un prochain tutorial nous étudierons les notions d'extension et de points d'extension.

 

Sommaire


 

 

Que sont les plug-ins Eclipse ?


Plug-ins et notion de modules

Le découpage d'une application en plusieurs modules présente des avantages aussi bien en développement qu'en exécution. Depuis ses débuts, Java propose un mécanisme basique pour permettre la livraison d'une application en plusieurs modules : les fichiers JAR. Pris en compte par la machine virtuelle, ils simplifient quelque peu la création de livrables mais ils ne permettent pas réellement la mise en oeuvre d'une approche 'composant' à cause de lacunes importantes :
- aucune gestion de versions.
- pas de vérification de pré-requis.
- liste des fichiers JAR définie statiquement dans le 'classpath' de l'application.
- aucun moyen pour décrire les relations entre les fichiers JAR.

Un point positif est que la machine virtuelle autorise une application à avoir sa propre stratégie de recherche et de chargement des classes via la mécanisme des 'Classloaders'. Complexes à mettre en oeuvre, leur utilisation n'est pas à la portée de tous.
Au fil des années des solutions sont apparues intégrant des mécanismes de modularisation plus sophistiquées : par exemple, les serveurs d'applications J2EE, via le support des formats WAR et EAR, offrent des fonctionnalités plus riches que l'utilisation de simples fichiers JAR : les applications, bien que s'exécutant dans la même JVM, sont isolées les unes des autres (elle peuvent notamment contenir les mêmes classes dans des versions différentes).

Côté Eclipse l'adoption d'une approche modulaire était un objectif fort dès la conception du framework. Ce dernier a donc été conçu autour d'un mécanisme de micro-noyau capable de gérer le cyle de vie des modules (découverte, chargement, déchargement, mise à jour, gestion des versions, contrôle des pré-requis, ...). Les modules d'Eclipse sont appelés 'Plug-ins'.

Depuis Eclipse 3, le micro-noyau propriétaire d'Eclipse a été réécrit pour se conformer à la spécification OSGi dont les fonctionnalités sont très similaires mais avec l'avantage de s'appuyer sur une spécification eprouvée. L'implémentation OSGi de la fondation Eclipse est nommée Equinox, c'est la base du framework Eclipse.

OSGi connaît un vif intérêt depuis 2006, avec notamment des utilisations, actuelles ou à l'étude, au coeur de plusieurs serveurs d'applications (WebSphere, Jonas, JBoss, ...). L'intégration d'OSGi en 'standard' dans Java SE est même envisagée via la JSR 277, mais cela reste hypothétique et n'aurait en réalité que peu d'impact sur l'adoption d'OSGi (cf encart 'JSR 277 vs JSR 291').

JSR 277 vs JSR 291

Fort du succès grandissant d'OSGi, IBM, avec notamment le soutien de la fondation Apache, de BEA ou encore d'Oracle, a proposé de faire d'OSGi une solution officiellement supportée par la communauté Java. Pour ce faire une JSR (Java Specification Request) a été proposée dans le cadre du JCP (Java Community Process). Nommée 'JSR 291', cette JSR provoque quelques remous entre les acteurs du monde Java avec notamment un rejet par Sun qui considère qu'OSGi étant déjà spécifiée en dehors du JCP, elle n'a pas sa place dans le cadre du JCP.

D'autant plus qu'entre temps, Sun conscient des lacunes des fichiers JAR, a proposé une autre JSR, la JSR 277, destinée à améliorer la modularisation des applications Java. Contrairement à OSGi, la JSR 277 ne propose pas déjà une spécification finalisée et encore moins d'implémentation. Sa cible est une intégration dans le JDK 7.0.

Le points positif est que même si ces deux spécifications ont un but commun (modulariser les applications Java), elles sont tout de même complémentaires notamment par l'approche dynamique que propose OSGi qui pourrait être un bon complément à la JSR 277. Les avis divergent donc plus sur la méthode : valider tout de suite OSGi dans le cadre du JCP ou attendre que la JSR 277 soit finalisée.

Liens :
- JSR 277 : Java Module System
- JSR 291 : Dynamic Component Support for Java SE

 

 

Structure d'un plug-in

La structure d'un plug-in peut être résumé ainsi : un plug-in est un fichier JAR classique contenant, en plus de ses classes Java, deux fichiers manifestes (les fichiers META-INF/MANIFEST.MF et plug-in.xml).

Le fichier MANIFEST.MF est exploité par le noyau d'Eclipse, Equinox, pour obtenir des informations sur le plug-in (version, liste des classes visibles, ...) qui serviront notamment à gérer le cycle de vie du plug-in et ses relations avec les autres plug-ins. La syntaxe de ce fichier est décrite dans la spécification OSGi.

Le fichier plugin.xml est propre à Eclipse (il ne fait pas partie d'OSGi), il sert à concrétiser les fonctionnalités d'extensibilité d'Eclipse. Via ce fichier, optionnel, des plug-ins vont déclarer des points d'extension et d'autres se brancher sur ces points d'extension. Nous expliquerons cette notion de points d'extension dans un prochain tutorial.

(NB: les plug-ins peuvent être livrés sous forme de fichiers JAR ou de répertoire. Depuis Eclipse 3.1, il est préférable d'utiliser des fichiers JAR).

 


Créer et tester un premier plug-in

Pour simplifier le développement de plug-ins, l'environnement de développement Eclipse (Eclipse SDK) intègre un outillage spécifique : le PDE (Plug-ins Development Environment). Basé sur l'outillage Java, le PDE complète l'environnement de développement Java avec des outils prenant en compte les étapes spécifiques au développement de plug-ins (édition des fichiers manifestes, lancement d'un environnement de test, ...).



Création d'un projet de plug-in

La première notion proposée par le PDE est un type de projet particulier : 'Plug-in Project'. La création d'un plug-in se fait donc en utilisant l'assistant de création d'un 'Plug-in Project' :

 

Dans la première page de l'assistant, l'information importante est le nom du projet. Pour ce nom la convention est d'utiliser les mêmes règles de nommage que pour les packages :

 

Pour la seconde page de l'assistant, les valeurs par défaut sont généralement acceptables :

- l'ID du plug-in reprend le nom du projet, cette valeur est importante car elle est notamment utilisée par certaines API.
- la version peut être librement modifiée.
- le nom du plug-in, n'est pas une information vraiment importante, mais elle peut être parfois visible par l'utilisateur (Menu Help->About->bouton 'Plug-ins details').
- même remarque pour le nom du fournisseur.
- le champ 'Classpath' est vide par défaut (depuis Eclipse 3.2). Nous étudierons plus tard la modification de cette valeur.

- La génération d'un 'Activator' est souhaitable, cette classe respecte une convention proposée par OSGi, elle permettera notamment de réagir à des événements liés au cycle de vie du plug-in (arrêt, démarrage, ...).
- Le fait d'indiquer que le plug-in contient une partie graphique ('This plug-in will make contributions to the UI') permet à l'assistant d'ajouter automatiquement les librairies graphiques d'Eclipse dans les dépendances du projet et aussi de choisir la classe dont héritera l'Activator ('org.eclipse.ui.plugin.AbstractUIPlugin' pour un plug-in graphique, 'org.eclipse.core.runtime.Plugin' sinon).

- Le dernier choix de cette assistant permet d'indiquer si ce plugin contiendra le point d'entrée d'une application Eclipse RCP. Ce choix n'a aucun impact sur la possibilité de réutilisation du plug-in dans une application Eclipse RCP : tous les plug-ins sont utilisables dans une application Eclipse RCP. Nous étudierons dans un prochain tutorial la construction d'une application Eclipse RCP, l'utilisation de ce même assistant permettera de demander la génération des classes servant de point d'entrée à l'application, le reste de l'application sera constitué de plug-ins standards.

 

La troisième page de l'assistant offre la possibilité de choisir à partir de quel modèle le projet sera créé, pour ce premier exemple nous utiliserons le modèle 'Hello, World'. Ce modèle permet de générer un plug-in qui ajoute un menu dans la barre de menu principale avec une entrée qui déclenchera l'ouverture d'une boîte de dialogue affichant un message :

 

La page suivante dépend du modèle choisi précédemment, elle permet de renseigner des informations utilisées pour générer le code du projet. Dans notre cas, utilisation du modèle 'Hello, World', les informations modifiables sont le noms du package, le nom de classe à déclencher lorsque le menu est sélectionné ainsi que le texte du message à afficher :

 

Une fois le bouton 'Finish' sélectionné, Eclipse propose d'ouvrir la perspective PDE (celle-ci est très ressemblante à la perspective Java) et ouvre l'éditeur des fichiers manifestes dans la zone d'édition.

La structure du projet généré est la suivante :

 

 


Tester un plug-in

Les plug-ins obéissent à un cycle de vie particulier. Notamment, les fichiers manifestes sont analysés pendant la phase de démarrage d'Eclipse. Pour que ce cycle de vie soit correctement respecté lors des tests, le PDE propose de lancer une deuxième instance d'Eclipse à partir de celle servant au développement des plug-ins. Cette deuxième instance est soit un Eclipse complet soit une application Eclipse RCP (nous étudierons ce 2ème cas dans un prochain tutorial).

 

Le menu défini par ce premier plug-in apparaît de la façon suivante dans l'Eclipse de test :

 

L'exécution du second Eclipse est configurable via le gestionnaire des configurations de lancement (Menu Run->Debug...'). L'onglet 'Main' indique notamment où se trouve le répertoire de travail de l'environnement de test :

 

L'onglet 'Arguments' permet d'indiquer les options à utiliser pour le lancement, il est conseillé d'ajouter l'option '-clean' pour forcer la relecture de tous les fichiers manifestes à chaque exécution de l'environnement de test. En effet il est fréquent de modifier les fichiers manifestes en phase de développement, pour éviter de relire tous les fichiers manifestes Eclipse gère un cache, l'option '-clean' efface ce cache et garantit la relecture complète des fichiers manifestes. A noter que la modification d'un fichier manifeste nécessite le redémarrage de l'Eclipse de test même si ce dernier est lancé en mode Debug, car ces fichiers ne sont analysés qu'au démarrage d'Eclipse.

 

L'onglet 'Plug-ins' permet de configurer la liste des plug-ins visibles dans l'environnement de test. Par défaut tous les plug-ins d'Eclipse et tous ceux en cours de développement sont pris en compte. En sélectionnant l'option 'Choose plug-ins and fragments to launch from the list', il est possible de restreindre les plug-ins visibles en test :

 

 


Dépendances entre plugins et utilisation de librairies


Notion de dépendances

Les plug-ins permettent de modulariser une application. Il est naturellement très fréquent de devoir permettre à un plug-in d'appeler le code contenu dans un autre.

Par défaut chaque plug-in est isolé : il ne peut accéder aux classes des autres plug-ins et les autres plug-ins ne peuvent accéder à ses classes. En modifiant le fichier MANIFEST.MF d'un plug-in, il est possible de rendre accessibles tout ou partie de ses classes. Les autres plug-ins qui souhaitent appeler ces classes accessibles devront l'indiquer explicitement dans leur fichier MANIFEST.MF.

Pour manipuler cette notion de dépendances, nous allons détailler pas à pas les 3 cas suivants :
- dépendances entre deux plug-ins.
- intégration d'une librairie (fichier JAR) dans un plug-in.
- création d'un plug-in à partir d'une librairie.

 


Mise en oeuvre d'une dépendance entre plug-ins

Pour tester la notion de dépendance nous allons créér un deuxième plug-in qui sera appelé par le premier :

 

Création d'un deuxième plug-in :

- Utiliser l'assistant de création de projet de plug-in pour créer un plug-in nommé 'com.eclipsetotale.tutorial.horloge.texte', conserver les valeurs par défaut pour les différents champs de l'assistant. Dans la page de l'assistant permettant de choisir un modèles ('Templates'), décocher la case à cocher 'Create a plug-in using one of the templates'.

- Une fois le plug-in créé ajouter la classe suivante dans le plug-in :
(NB: pour créer cette classe la méthode la plus simple est de sélectionner le code ci-dessous, de le copier, puis dans Eclipse de sélectionner le répertoire 'src' du projet et d'utiliser l'option 'Paste' du menu contextuel. De cette façon Eclipse crée la classe et le package si nécessaire)

package com.eclipsetotale.horloge.texte;

import java.text.DateFormat;
import java.util.Date;

import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;

public class HorlogeTexte {
   private Font font;
   private Label heureLabel;
   boolean isVisible = true;

   public HorlogeTexte(final Composite parent) {
      final Display display = parent.getDisplay();
      parent.setLayout(new FillLayout());

      heureLabel = new Label(parent, SWT.CENTER);
      heureLabel.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
      heureLabel.setFont(this.getFont(parent));

      final Runnable updateUIRunnable = new Runnable() {
         public void run() {
            try {
               if (parent.isVisible()) {
                  String date = DateFormat.getTimeInstance().format(new Date());
                  heureLabel.setText(date);
               }
            } catch (Throwable e) {
               isVisible = false;
               dispose();
            }
         }
      };

      Thread thread = new Thread(new Runnable() {
         public void run() {
            while (isVisible) {
               display.asyncExec(updateUIRunnable);
               try {
                  Thread.sleep(1000);
               } catch (InterruptedException e) {
               }
            }
         }
      });
      thread.start();
   }

   private Font getFont(Composite parent) {
      if (font == null) {
         font = JFaceResources.getDefaultFont();
         FontData fdata = font.getFontData()[0];
         fdata.setHeight(18);
         fdata.setStyle(SWT.BOLD);
         font = new Font(parent.getDisplay(), fdata);
      }
      return font;
   }

   public void dispose() {
      font.dispose();
   }

}

 

Rendre les classes accessibles par d'autres plug-ins.

Par défaut, la classe précédemment créée ne peut pas être invoquée par un autre plug-in. Conformément à la spécification OSGi, un plug-in doit déclarer explicitement dans le fichier MANIFEST.MF les classes qu'ils souhaitent rendre visibles. La granularité choisie est celle du package, la liste des packages visibles est déclarée via la variable 'Exported-packages' du fichier MANIFEST.MF.

L'éditeur de fichiers manifestes proposés par le PDE, simplifie la mise à jour du fichier MANIFEST.MF. Pour 'exporter' la classe 'com.eclipsetotale.tutorial.horloge.texte.HorlogeTexte' suivre les étapes suivantes :
- Ouvrir l'éditeur de fichiers manifestes du plug-in 'com.eclipsetotale.tutorial.horloge.texte' (double-cliquer sur le fichier META-INF/MANIFEST.MF).
- Sélectionner l'onglet 'Runtime'.
- Utiliser le bouton 'Add...' associé à la section 'Exported packages' pour ajouter le package 'com.eclipsetotale.tutorial.horloge.texte' à la liste :

Suite à cette opération, le fichier MANIFEST.MF contient la ligne suivante :

Export-Package: com.eclipsetotale.tutorial.horloge.texte

 

 

Utiliser les classes à partir d'un autre plug-in.

Pour que notre premier plug-in puisse appeler la classe HorlogeTexte du second plug-in, il est nécessaire de déclarer explicitement dans le fichier MANIFEST.MF du premier plug-in qu'il y a une dépendance avec le deuxième plug-in.
Les dépendances se déclarent dans l'onglet 'Dependencies' de l'éditeur de fichier manifestes :

Ce qui équivaut à modifier la ligne suivante du fichier MANIFEST.MF :

Require-Bundle: org.eclipse.ui,
   org.eclipse.core.runtime,
   com.eclipsetotale.tutorial.horloge.texte

 

Une fois cette dépendance déclarée, nous pouvons modifier le code de la classe 'com.eclipsetotale.tutorial.premierplugin.actions.SampleAction' générée lors de la création du premier plug-in. La méthode run(IAction) est celle déclenchée lorsque l'utilisateur sélectionne le menu correspondant. Réimplémenter cette méthode de la façon suivante :

public void run(IAction action) {
   Display display = Display.getCurrent();
   Shell fenetre = new Shell(display, SWT.SHELL_TRIM | SWT.TOOL);
   fenetre.setSize(160, 55);
   new HorlogeTexte(fenetre);
   fenetre.open();

}

(NB: lors de l'ajout des imports bien sélectionner ceux commençant par 'org.eclipse')



Tester, une fenêtre affichant l'heure doit s'ouvrir.

 

La déclaration d'une dépendance est le moyen privilégié de indiquer qu'un plug-in souhaite accéder aux classes visibles d'un autre plug-in. En plus d'autoriser l'accès aux classes du plug-in indiqué en pré-requis, le noyau d'Eclipse validera que ce plug-in est bien présent lors de l'exécution et éventuellement choisira la version du plug-in la plus adaptée (en fonction des règles indiquées en utilisant le bouton 'Properties' de l'onglet 'Dependencies').

Il existe un autre moyen d'accéder aux classes d'un plug-in sans que ce dernier soit déclaré comme 'Required plug-ins'. Il s'agit de la propriété 'Imported-packages' d'OSGi, accessible dans l'onglet 'Dependencies', elle est rarement utilisée par les plug-ins Eclipse mais elle permet d'assouplir la relation entre deux plug-ins : le plug-in qui importe des packages n'a pas besoin de connaître le nom de celui qui les exportent, la résolution se fait lors de l'exécution (ce qui est peut être problèmatique si aucun plug-in n'exportent les bons packages ou si plusieurs les exportent).

 


Intégration d'une librairie dans un plug-in

Lors de l'écriture de plug-ins, il est relativement courant de vouloir s'appuyer sur une librairie existante (par exemple proposée par un projet open-source). La première possibilité est d'intégrer directement le fichier JAR dans le plug-in. Etudions ce cas via la création d'un nouveau plug-in utilisant un fichier JAR fourni par le sous-projet Nebula de la fondation Eclipse.

 

Créer un nouveau projet de plug-in nommé 'com.eclipsetotale.tutorial.horloge.nebula' (ne pas utiliser de template).

Télécharger le fichier JAR suivant : nebula_cdatetime_0.9.0.jar

Créer un sous-répertoire 'lib' dans le projet et y placer le fichier JAR.

Pour que les classes de ce fichiers JAR soient utilisables il faut ajouter le fichier au 'classpath' du plug-in. L'onglet 'Runtime' de l'éditeur de fichier manifestes permet de faire cet ajout en utilisant le bouton 'Add' associé à la section 'Classpath' :

La ligne suivante est ajoutée au fichier 'MANIFEST.MF' :

Bundle-ClassPath: lib/nebula_cdatetime_0.9.0.jar,
.

 

Pour tester la bonne intégration du JAR, ajouter la classe suivante dans le projet de plug-in.

package com.eclipsetotale.tutorial.horloge.nebula;

import java.util.Date;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.nebula.widgets.cdatetime.CDT;
import org.eclipse.swt.nebula.widgets.cdatetime.CDateTime;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;

public class HorlogeNebula {
   private CDateTime heureCDT;
   boolean isVisible = true;

   public HorlogeNebula(final Composite parent) {
      final Display display = parent.getDisplay();
      parent.setLayout(new GridLayout());

      heureCDT = new CDateTime(parent, CDT.COMPACT | CDT.BORDER | CDT.SIMPLE | CDT.TIME_MEDIUM | CDT.CLOCK_12_HOUR);

      final Runnable updateUIRunnable = new Runnable() {
         public void run() {
            try {
               if (parent.isVisible()) {
                  heureCDT.setSelection(new Date());
                  heureCDT.setFormat(CDT.TIME_MEDIUM);
               }
            } catch (Throwable e) {
               isVisible = false;
            }
         }
      };

      Thread thread = new Thread(new Runnable() {
         public void run() {
            while (isVisible) {
               display.asyncExec(updateUIRunnable);
               try {
                  Thread.sleep(1000);
               } catch (InterruptedException e) {
               }
            }
         }
      });
      thread.start();
   }

}

 

Rendre cette classe visible par les autres plug-ins. Procéder de la même façon que pour le plug-in 'com.eclipsetotale.tutorial.horloge.texte' : dans l'onglet 'Runtime', ajouter le package 'com.eclipsetotale.tutorial.horloge.nebula' dans la section 'Exported packages'.

 

Ajouter le plug-in 'com.eclipsetotale.tutorial.horloge.nebula' dans la liste des plug-ins pré-requis du plug-in 'com.eclipsetotale.tutorial.premierplugin' (utiliser l'onglet 'Dependencies' de l'éditeur de fichier manifestes, section 'Required plug-ins').

 

Modifier le code de le la classe 'SampleAction' du premier plug-in pour lui faire utiliser la classe 'HorlogeNebula', voici le code de la méthode run(IAction) :

public void run(IAction action) {
   Display display = Display.getCurrent();
   Shell fenetre = new Shell(display, SWT.SHELL_TRIM | SWT.TOOL);
   fenetre.setSize(220, 240);
   new HorlogeNebula(fenetre);
   fenetre.open();

}

 

Tester, la fenêtre suivante doit s'ouvrir :

 


Transformation d'une librairie en plug-in

L'intégration de fichiers JAR directement dans les plug-ins pose notamment le problème de la duplication de ces fichiers dans plusieurs plug-ins. Il est donc généralement préférable de créer un plug-in spécifiquement pour chaque librairie et d'utiliser la notion de dépendance pour en donner l'accès aux autres plug-ins.

Eclipse propose un assistant particulier pour automatiser la création d'un plug-in à partir de fichiers JAR. Voici les étapes pour créer un plug-in à partir du fichier JAR précédemment téléchargé.

 

Créer un nouveau projet à partir du menu 'File->New->Project...' en sélectionnant le type 'plug-in from existing JAR archives' :

 

Dans la deuxième page de l'assistant utiliser le bouton 'Add External...' pour indiquer le fichier JAR :

 

Dans la troisième page, indiquer le nom du plug-in et cliquer sur 'Finish' :

 

L'assistant crée le projet de plug-in en récupérant le contenu du JAR, le fichier MANIFEST.MF est initialisé : les packages contenus dans le JAR sont automatiquement ajoutés à la liste 'Exported packages' de l'onglet 'Runtime'. L'assistant ne définit pas de plug-ins dans la liste 'Required plug-ins' de l'onglet dependencies, dans notre cas le JAR contient des classes qui font appel à la librairie SWT d'Eclipse, ajouter le plug-ins 'org.eclipse.swt' comme pré-requis :

 

Effectuer les opération suivantes sur le plug-in 'com.eclipsetotale.tutorial.horloge.nebula' :
      - supprimer le répertoire lib.
      - dans l'onglet 'Runtime' de l'éditeur du fichier MANIFEST.MF supprimer le contenu de la section 'Classpath'.
      - dans l'onglet 'Dependencies' ajouter le plug-in 'org.eclipse.nebula.cdatetime' à la liste des 'Required plug-ins'.

 

Tester. Le résultat doit être le même que précédemment, l'intérêt est de disposer d'un plug-in réutilisable par d'autres plug-ins sans devoir dupliquer le fichier JAR dans tous les plug-ins souhaitant utiliser cette librairie.

 


Conclusion

La notion de plug-in intégrée au framework Eclipse permet le développement et la livraison d'applications modulaires. Pour faciliter le développement des plug-ins, Eclipse propose un outillage complet : le PDE (Plug-ins Development Environment).
Les plug-in sont des fichiers JAR enrichis de deux fichiers manifestes (plugin.xml et MANIFEST.MF). En exploitant les informations contenus dans les fichiers manifestes, le noyau d'Eclipse, Equinox, gère le cycle de vie des plugins et leurs dépendances.

Dans ce premier tutorial, nous avons vu comment le framework Eclipse assurait la modularité, dans un prochain tutorial nous étudierons la notion de point d'extension qui concrétise le deuxième objectif des concepteurs d'Eclipse : l'extensibilité.

 

 


 

 


 

 

(c) EclipseTotale - contact(arobase)eclipsetotale.com