Tensin

Aller au contenu Aller au menu Aller à la recherche

mardi, août 21 2012

Eclipse, astuces & optimisations

EclipseCi-dessous les slides d’une présentation réalisée pour le boulot et détaillant un certain nombre d’astuces, de raccourcis-claviers, d’optimisations, de plugins, que l’on gagne à connaître quand on utilise Eclipse comme IDE de développement Java.

Il y a un peu de tout, chaque développeur en connaît forcément une bonne partie, mais peut-être pas tout. Cette présentation ne se veut aucunement exhaustive, c’est juste un moyen de centraliser un certain nombre d’informations à même de permettre de mieux utiliser l’outil.

Elle est essentiellement centrée sur Eclipse 3.7 Indigo, mais tout ce qui y figure est toujours valide pour Eclipse 3.8/4.2 Juno.

Au programme : des plugins injustement méconnus et pourtant très utiles comme Grep Console, les raccourcis-claviers indispensables (recherche incrémentale, quick-edit, etc.), des optimisations (sur le content-assist, la purge du cache OSGI, certaines désactivations, la bonne cohabitation Eclipse / Maven), etc.

Présentation Eclipse

La présentation en HTML5/javascript étant propulsée par deck.js, un navigateur récent (Chrome ou FireFox) est obligatoire. Une version .pdf, non interactive (pas de vidéos, pas d’images aggrandissables) est toutefois disponible en complément ici.

mardi, avril 10 2012

En finir avec les répertoires target de Maven sous Eclipse

Pour ne plus être pollué par le contenu des répertoires target de Maven (contenant notamment les classes compilées) lorsque l’on travaille sous Eclipse, il suffit de les marquer en “derived”. Ceci permet d’indiquer à Eclipse que le contenu de ce répertoire ne doit pas être pris en compte lors des recherches, lors des mécanismes de navigation type CTRL+T, etc. C’est un vrai gain de temps sur de gros projets (sans compter que le résultat des recherches est dès lors plus clair).

Propriété "derived" sur une ressource Eclipse

Cette manipulation n’est aujourd’hui pas automatisable depuis Maven (même si d’antédiluviens bugs y font référence). Le problème étant dès lors qu’à chaque fois qu’une opération “clean” est effectuée côté Maven, le répertoire est effacé, et Eclipse perd l’état derived, qu’il faut donc réappliquer.

La solution pour s’en sortir est de paramétrer Maven, côté pom.xml, pour qu’il n’efface plus le répertoire target, mais uniquement son contenu.

Pour ce faire il suffit de mettre à jour la configuration du plugin maven-clean-plugin.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-clean-plugin</artifactId>
  <version>2.4.1</version>
  <configuration>
    <excludeDefaultDirectories>true</excludeDefaultDirectories>
      <filesets>
        <fileset>
          <directory>target</directory>
          <includes>
            <include>**/*</include>
          </includes>
        </fileset>
      </filesets>
  </configuration>
</plugin>

Le résultat est visible dans la log de Maven :

------------------------------------------------------------------------ [INFO] Building sonos-root [INFO] task-segment: [clean] [INFO] ------------------------------------------------------------------------ [INFO] [clean:clean {execution: default-clean}] [INFO] Deleting /home/users/sergio/Java/workspaces/eclipse/perso-java/sonos-root/target (includes = [**/*], excludes = []) [INFO] [cobertura:clean {execution: clean}] [INFO] ------------------------------------------------------------------------

Cette paramétrie gagne bien sûr à être mutualisée à travers différents projets en la définissant dans pom parent root.

lundi, août 2 2010

Extraire en java les données stockées dans JIRA

JIRACette semaine, j’ai passé un peu de temps au boulot à travailler sur l’extraction de données stockées dans JIRA, en Java. Pour rappel, JIRA est un système de suivi de bugs, un système de gestion des incidents, et un système de gestion de projets, fourni par Atlassian, et c’est bien sûr le système que l’on utilise dans ma boîte. Dans le cadre d’un nouveau projet dont je m’occupe, pour lequel je stocke sous JIRA l’ensemble des tâches à réaliser par toute l’équipe, j’ai besoin d’extraire toutes les informations liées à chacune de ces tâches pour construire en automatique un planning GANTT.

JIRA (Atlassian)

Remarque : il y a bien un plugin officiel pour JIRA orienté “gestion de projet”, il s’agit de Greenhopper, malheureusement il est payant et il n’est pas envisagé aujourd’hui de s’en équiper chez nous.

Atlassian fournit donc plusieurs mécanismes pour interagir avec JIRA (en action - création de nouvelles tâches par ex. - et/ou en extraction). A ma connaissance il y a :

  • l’API XML-RPC ;
  • l’API SOAP ;
  • les flux XML et RSS directement depuis le serveur JIRA ;
  • les Jelly Scripts.

En préambule, il est conseillé de jeter un oeil sur la page How JIRA works.

XML-RPC

XML-RPC est un protocole d’interaction (Remote Procedure Call) permettant d’inteagir par XML avec le système. En lui même, c’est donc juste un ensemble de méthodes à utiliser par des appels XML. Autrement dit utilisable avec n’importe quel langage.

Une documentation JIRA présente ce protocole : JIRA, XML-RPC Overview.

Une implémentation (non-officielle) est par ailleurs disponible en Java et fonctionne parfaitement bien : il s’agit de la librairie Swizzle [http://swizzle.codehaus.org/Home ].

J’avais déjà réalisé un traitement Java avec cette librairie il y a quelques mois, pour récupérer l’ensemble des fiches JIRA liées à une version projet et créer en automatique de nouvelles fiches.

L’utilisation de la librairie Swizzle est cependant très simple, exemple de connexion :

  1. try {
  2. org.codehaus.swizzle.jira.Jira jira = new Jira(url);
  3. jira.login(username, password);
  4. } catch (MalformedURLException e) {
  5. throw new BaseException(e);
  6. } catch (Exception e) {
  7. throw new BaseException(e);
  8. }

Et un exemple de récupération de données sur une recherche libre (il y a bien sûr d’autres méthodes pour récupérer les données) :

  1. Vector v = new Vector();
  2. v.add("CLE_DU_PROJET_JIRA"); // le ou les projets sur lesquels faire la recherche
  3. String critereDeRecherche = ""; // On ramène toutes les fiches JIRA
  4. int nbFichesARamener = 1000; // nombre max de fiches JIRA à récupérer
  5. List issues = jira.getIssuesFromTextSearchWithProject(v, critereDeRecherche, nbFichesARamener);

Ensuite la plupart des informations se retrouvent sur l’objet Issue correspondant. Il y a bien sûr des objets Java pour représenter les Versions JIRA, les filtres, les utilisateurs, le projet JIRA, etc.

Le seul problème vient du fait que toutes les opérations / informations disponibles sous JIRA ne le sont pas au travers de l’API XML-RPC, notamment dans le cas qui m’intéresse, les informations relatives aux sous-tâches et aux champs personnalisés ne sont pas exportées, donc obligé de faire autrement, d’où le switch sur la 2e API proposée par Atlassian, via SOAP.

SOAP

Il s’agit d’une API externe également, avec cette fois une implémentation Java “officielle” fournie par Atlassian (ainsi qu’une implémentation Python, au passage).

La mise en place est moins évidente qu’avec Swizzle, puisqu’il en faut ici :

  • récupérer le projet SOAP client en ligne ;
  • récupérer le fichier WSDL sur le serveur JIRA sur lequel on veut se mapper (fichier décrivant l’ensemble des services disponibles) ;
  • utiliser un profile Maven pour générer à partir de ce fichier WDSL les classes Java (dans “target/generated-classes/*.class”) correspondant aux services disponibles ;
  • toujours avec Maven, mettre à jour le .classpath Eclipse ;
  • pouvoir enfin utiliser les objets natifs Java permettant d’interagir avec JIRA via SOAP ;
  • plus en bonus, mettre en place un packaging sous forme de .jar de ces classes générées pour ne pas avoir à refaire ces manipulations à chaque fois (à faire par soi même) ;

Toutes ces opérations sont détaillées dans la documentation en ligne Atlassian : Creating a SOAP Client pour la présentation générale de l’API SOAP et dans le README du projet exemple pour le détail des manipulations techniques.

S’il n’y a rien de compliqué, c’est moins simple que de simplement ajouter une dépendance comme Swizzle, mais cela a l’avantage de construire une API de services correspondant réellement aux services disponibles sur le serveur JIRA utilisé, quelle que soit la version de ce dernier.

L’usage de ce client Java SOAP est très simple également et ressemble bigrement à l’API XML-RPC, exemple pour la connexion :

  1. String url = "http://jira.domaine.tld/";
  2. String username = "login de servitude";
  3. String password = "password du compte de servitude";
  4. (...)
  5. JiraSoapServiceService jiraSoapServiceLocator = new JiraSoapServiceServiceLocator();
  6. JiraSoapService jiraSoapService = null;
  7. String authToken; // il est important de garder ce auth token, toutes les méthodes en auront besoin ensuite
  8. RemoteType[] remoteTypes; // on va conserver en local la liste des types disponibles au niveau du projet
  9. // pour pouvoir ensuite facilement retrouver à quoi correspondant le type d'une tâche
  10. RemoteStatus[] remoteStatuses // Idem pour les status
  11. try {
  12. if (url == null) {
  13. jiraSoapService = jiraSoapServiceLocator.getJirasoapserviceV2();
  14. } else {
  15. jiraSoapService = jiraSoapServiceLocator.getJirasoapserviceV2(url);
  16. log.info("SOAP Session service endpoint at " + url.toExternalForm());
  17. }
  18.  
  19. authToken = getJiraSoapService().login(username, password);
  20. remoteTypes = getJiraSoapService().getIssueTypes(getAuthToken());
  21. remoteStatuses = getJiraSoapService().getStatuses(getAuthToken());
  22. } catch (ServiceException e) {
  23. throw new BaseException("ServiceException during SOAPClient contruction", e);
  24. } catch (RemoteAuthenticationException e) {
  25. throw new BaseException("RemoteAuthenticationException during SOAPClient contruction", e);
  26. } catch (RemoteException e) {
  27. throw new BaseException("RemoteException during SOAPClient contruction", e);
  28. } catch (java.rmi.RemoteException e) {
  29. throw new BaseException("RemoteException during SOAPClient contruction", e);
  30. }

Et pour comparaison, la récupération de données dans JIRA (qui ressemble toujours bigrement à la méthode précédente) :

  1. RemoteIssue[] issues;
  2. int nbFichesARamener = 1000; // nombre max de fiches JIRA à récupérer
  3. try {
  4. issues = getJiraSoapService().getIssuesFromTextSearchWithProject(getAuthToken(), new String[] { project.getKey() }, "", nbFichesARamener);
  5. } catch (RemoteException e) {
  6. throw new BaseException(e);
  7. } catch (java.rmi.RemoteException e) {
  8. throw new BaseException(e);
  9. }

Malheureusement, toujours les mêmes limitations : de nombreuses informations ne sont pas disponibles.

Visiblement je ne suis pas le seul à le déplorer, on trouve de nombreuses issues ouvertes chez Atlassian sur ce sujet … certaines depuis 2005 (voir par ex. ce thread sur Stackoverflow.com et celui-ci dans le forum JIRA et un patch a même été développé).

Donc à ce stade après avoir passé quelques heures à jouer avec le mécanisme SOAP, je n’ai toujours pas moyen de récupérer toutes les informations dont j’ai besoin.

Si les solutions officielles et “propres” fournies par les API JIRA ne fonctionnent pas, il va alors falloir revenir à quelque chose de plus simple : les exports XML.

Bonus : un tutorial très détaillé pour l’utilisation de l’api JIRA SOAP, Vérifier le JIRA avant de faire la release.

XML

Un peu partout dans JIRA, on peut trouver des liens “RSS” et “XML” qui exportent les données JIRA correspondant à la recherche courante. En regardant le contenu, on voit que cette fois il y a bien absolument toutes les informations nécessaires, y compris les champs supplémentaires et les informations sur les sous-tâches (à l’exception notable de la “date de démarrage”, mais je peux m’en passer).

Il suffit donc de mettre en place une récupération par HTTPClient pour récupérer le flux XML, et de le parser (dans mon cas avec la très bonne API simple-xml, qui permet de facilement mapper dans les deux sens du XML vers des beans java par simples annotations) pour récupérer toutes les infos souhaitées. Ca marche mais c’est moins rigoureux, il faut notamment récupérer les informations de type “date” sous une forme textuelle et faire un parsing dessus (autrement dit le format des dates peut changer à la faveur d’une montée de version du serveur, par ex.). Mais vu que je n’ai pas trop le choix …

Il faut bien sûr toujours avoir un compte qui puisse accéder par HTTP aux pages JIRA (dans mon cas, un “compte de service” que j’ai créé uniquement à cet usage, pour ne pas avoir à utiliser mon identifiant réseau propre).

Exemple de récupération du flux XML par HTTPClient :

  1. String host = "jira.domaine.tld";
  2. String username = "login de servitude";
  3. String password = "password du compte de servitude";
  4.  
  5. final GetMethod method = new GetMethod(url);
  6. try {
  7.  
  8. final HttpClient client = new HttpClient();
  9. client.getHostConfiguration().setHost("host", 80, "http");
  10. final Credentials defaultcreds = new UsernamePasswordCredentials(username, password);
  11. client.getState().setCredentials(new AuthScope(host, 80, AuthScope.ANY_REALM), defaultcreds);
  12. method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));
  13.  
  14. int statusCode = client.executeMethod(method);
  15.  
  16. InputStream responseBody = method.getResponseBodyAsStream();
  17. InputStreamReader reader = new InputStreamReader(responseBody);
  18.  
  19. Serializer serializer = new Persister();
  20. JIRAXMLRss rss = serializer.read(JIRAXMLRss.class, reader);
  21.  
  22. return rss.getChannel().getItems(); // Retourne une liste de beans "maison" alimentés depuis le XML par SimpleXML
  23. } catch (HttpException e) {
  24. throw new BaseException(e);
  25. } catch (IOException e) {
  26. throw new BaseException(e);
  27. } catch (Exception e) {
  28. throw new BaseException(e);
  29. } finally {
  30. method.releaseConnection();
  31. // autres releases ...
  32. }

Mise en place.

Au final - les quelques lignes de code ci-dessus n’étant que des exemples, comme j’avais déjà codé le tout, j’ai fini par garder les 3 implémentations et pouvoir ainsi facilement en switcher selon les usages (je ne détaille pas plus avant, ce post se voulant seulement une présentation des manières d’accéder à JIRA en Java).

Annexes

Jelly scripts

Les scripts Jelly sont des scripts XML exécutables, permettant de réaliser des opérations à partir d’un formalisme XML. C’est un projet Apache, implémenté dans JIRA, mais surtout pour des opérations de type export / import. Je connaissais pour avoir déjà été amené à utiliser ce mécanisme dans le cadre d’une migration Bugzilla vers JIRA lorsque l’on a switché sur ce dernier, mais les fonctions proposées sont plus orientées traitement de masse et semblent ne pas tout couvrir non plus, j’ai donc laissé tomber.

Des exemples de Jelly scripts.

Extension du serveur JIRA.

Comme je n’ai pas la main sur le serveur JIRA (impossible d’y modifier quoi que ce soit de mon côté), je n’ai pas pu déployer cette extension qui aurait corrigé mon problème : Jira Extended Webservice.

C’est un patch qui apporte la gestion des sous-tâches sur l’API SOAP, développé par un utilisateur externe à Atlassian. Voir également le Google Group correspondant.

Bonus

En side point, une présentation vidéo intéressante sur la construction du client lourd JIRA : Killer JIRA CLI.

vendredi, mai 21 2010

Installation APR sous TomCat

J’utilise le serveur d’applications TomCat 6, aussi bien en développement sur poste local sous Eclipse/WTP qu’en environnement Linux (pour faire tourner Hudson ou Sonar).

Une petite optimisation permettant de gagner un peu de temps au démarrage est d’installer les APR : ApachePortableRuntime.

Avant :

INFO: Server startup in 8477 ms

Une fois APR installé :

INFO: Server startup in 7908 ms

Donc un gain pas énorme au final, mais vu que la manipulation d’installation n’est pas très compliquée, autant ne pas s’en priver.

La doc se trouve ici : http://tomcat.apache.org/native-doc/ Site officiel du module APR : http://apr.apache.org/

Installation de APR et APR-UTILS

Installation de APR :

tar xvzf ../apr-1.3.8.tar.gz 
cd apr-1.3.8/
./configure 
make && sudo make install

Installation de APR-UTILS :

tar xzvf ../apr-util-1.3.9.tar.gz 
cd apr-util-1.3.9/
./configure --with-apr=/usr/local/apr/bin/
make
sudo make install

Installation de Tomcat APR

cd jni/native/
./configure --with-apr=/usr/local/apr/bin/apr-1-config --with-java-home=/home/applications/java/jdk1.6.0_15 --with-ssl=no --prefix=/home/applications/apache/tomcat/6.0.18
make
make install

Démarrage autonome de TomCat

Il suffit de mettre en place partie setenv.sh comme indiqué dans la documentation pour lancement manuel de tomcat

Démarrage de TomCat sous Eclipse/WTP

Il suffit, dans les préférences du serveur sous WTP (Web Tools Platform), d’indiquer sous “Open Launch Configuration” / “Environnement” la même valeur de la variable LD_LIBRARY_PATH que ci-dessus (soit le chemin donné à la compilation jni ci-dessus sous “prefix”).

Exemple sur mon poste : Eclipse (paramétrie APR Tomcat)

Contenu du répertoire lib de Tomcat :

~/applications/apache/tomcat/6.0.18/lib> lr
total 6260
-rw-r--r-- 1 domain users   49019 2008-07-22 02:01 tomcat-i18n-ja.jar
-rw-r--r-- 1 domain users   42702 2008-07-22 02:01 tomcat-i18n-fr.jar
-rw-r--r-- 1 domain users   45686 2008-07-22 02:01 tomcat-i18n-es.jar
-rw-r--r-- 1 domain users  197443 2008-07-22 02:01 tomcat-dbcp.jar
-rw-r--r-- 1 domain users  742085 2008-07-22 02:01 tomcat-coyote.jar
-rw-r--r-- 1 domain users   83787 2008-07-22 02:01 servlet-api.jar
-rw-r--r-- 1 domain users   72183 2008-07-22 02:01 jsp-api.jar
-rw-r--r-- 1 domain users 1395264 2008-07-22 02:01 jasper-jdt.jar
-rw-r--r-- 1 domain users  511634 2008-07-22 02:01 jasper.jar
-rw-r--r-- 1 domain users  102282 2008-07-22 02:01 jasper-el.jar
-rw-r--r-- 1 domain users   27690 2008-07-22 02:01 el-api.jar
-rw-r--r-- 1 domain users  228471 2008-07-22 02:01 catalina-tribes.jar
-rw-r--r-- 1 domain users 1129912 2008-07-22 02:01 catalina.jar
-rw-r--r-- 1 domain users  123115 2008-07-22 02:01 catalina-ha.jar
-rw-r--r-- 1 domain users   49145 2008-07-22 02:01 catalina-ant.jar
-rw-r--r-- 1 domain users   10805 2008-07-22 02:01 annotations-api.jar
-rw-r--r-- 1 domain users   18501 2009-07-21 18:01 tomcat-devloader.jar
drwxr-xr-x 2 domain users    4096 2009-11-06 15:54 pkgconfig
-rwxr-xr-x 1 domain users  613512 2009-11-06 15:54 libtcnative-1.so.0.1.16
lrwxrwxrwx 1 domain users      23 2009-11-06 15:54 libtcnative-1.so.0 -> libtcnative-1.so.0.1.16
lrwxrwxrwx 1 domain users      23 2009-11-06 15:54 libtcnative-1.so -> libtcnative-1.so.0.1.16
-rwxr-xr-x 1 domain users     939 2009-11-06 15:54 libtcnative-1.la
-rw-r--r-- 1 domain users  880544 2009-11-06 15:54 libtcnative-1.a

Démarrage d’Eclipse / WTP

Si tout s’est bien passé on aura :

6 nov. 2009 15:59:20 org.apache.catalina.core.AprLifecycleListener init
INFO: Loaded APR based Apache Tomcat Native library 1.1.16.
6 nov. 2009 15:59:20 org.apache.catalina.core.AprLifecycleListener init
INFO: APR capabilities: IPv6 [true], sendfile [true], accept filters [false], random [true].
6 nov. 2009 15:59:20 org.apache.catalina.core.AprLifecycleListener lifecycleEvent
INFO: Failed to initialize the SSLEngine.
6 nov. 2009 15:59:20 org.apache.coyote.http11.Http11AprProtocol init
INFO: Initialisation de Coyote HTTP/1.1 sur http-8080
6 nov. 2009 15:59:20 org.apache.coyote.ajp.AjpAprProtocol init
INFO: Initializing Coyote AJP/1.3 on ajp-8009
6 nov. 2009 15:59:20 org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 832 ms
6 nov. 2009 15:59:20 org.apache.catalina.core.StandardService start
INFO: D�marrage du service Catalina
6 nov. 2009 15:59:20 org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.18
INFO: Using JVM encoding charset = UTF-8