Blog de xerus

Aller au contenu | Aller au menu | Aller à la recherche

vendredi, février 19 2010

P2P anonyme avec I2P et iMule

Dixit wikipedia, I2P (« Invisible Internet Project ») est un réseau anonyme, offrant une simple couche logicielle que les applications peuvent employer pour envoyer de façon anonyme et sécurisée des messages entre elles.

I2P est donc un socle permettant à des logiciels de bénéficier de cet anonymat : c'est le cas de iMule, un client de la famille des *Mule mais anonyme, chiffré, et décentralisé.

Nous allons nous intéresser à l'installation de I2P et de iMule sous Arch linux.

Quelques liens

Les liens suivants m'ont aidé lors de la configuration d'I2P :

et pour iMule :

Installation de I2P

I2P est présent dans AUR, j'ai adopté le package dernièrement et je l'ai mis à jour.

#pacman -S i2p

On peut alors démarrer le service

#/etc/rc.d/i2prouter start

Par défaut la console I2P n'est accessible que depuis la machine locale. Editer le fichier /opt/i2p/clients.config et changer la ligne suivante :

 clientApp.0.args=7657 0.0.0.0 ./webapps/

Il est alors possible d'accéder à la console i2p depuis un autre poste : http://myserver:7657/config.jsp

Firewall

I2P supporte UPnP et Shorewall également mais comme expliqué sur la page correspondante de la documentation de Shorewall, UPnP est un désastre concernant la sécurité puisqu'il permet à des logiciels (ou des trojans) d'ouvrir eux-mêmes des ports du firewall. On va donc s'en passer ...

I2P utilise le port 123 en udp ainsi qu'un autre port aléatoire choisi lors du premier démarrage. Il faut donc tout d'abord relever ce port dans la console i2p

Relever le n° de port dans la console d'i2p :

port UDP i2p

Par défaut, le même port est utilisé en TCP. Il faut donc rajouter à /etc/shorewall/rules :

 #i2p
 ACCEPT          net             $FW             udp     123
 ACCEPT          net             $FW             udp     9315
 ACCEPT          net             $FW             tcp     9315

Après redémarrage de shorewall, i2p devrait afficher un "OK" dans la console d'administration :

firewall i2p ok

Utilisation de privoxy

Pour pouvoir accéder aux sites i2p, il faut utiliser le proxy i2p. Plutôt que de l'utiliser pour toutes les requêtes, on peut utiliser privoxy pour n'utiliser le proxy i2p que pour les domaines .i2p

#pacman -S privoxy

Editer /etc/privoxy/config et modifier

 listen-address  192.168.1.254:8118

de façon à ce que le proxy soit accessible depuis les autres machines du réseau (192.168.1.254 est l'IP de notre firewall)

Puis ajouter, toujours au même fichier :

 #this forwards all requests to .i2p domains to the local i2p
 #proxy without dns requests
 forward .i2p localhost:4444

Configurer le navigateur pour utiliser le proxy 192.168.1.254:8118

Imule

J'ai également créé un package dans aur pour Imule. Imule ne permet pas encore d'être lancé en tant que service headless comme amule. Il est donc nécessaire de l'installer sur une machine disposant de XOrg (ce qui n'est pas le cas de mon firewall). En fait je l'ai installé sur une machine virtuelle présente sur le serveur.

Pour pouvoir utiliser iMule, il y a un certain nombre de changements à faire au niveau de la console I2p. Tout d'abord, il faut activer SAM application bridge et puisque I2P ne tourne pas sur le même serveur que iMule, utiliser 0.0.0.0 plutôt que 127.0.0.0. Pour cela cliquez sur "Services I2P" dans la console I2P.

Config client I2P

Pour fonctionner, iMule doit connaître quelques noeuds du réseau iMule/I2P. Il va pour cela charger un fichier nodes.dat présent sur internet ou sur le réseau I2P. Il est donc nécessaire qu'iMule puisse utiliser le proxy i2p. Puisque iMule n'est pas sur le même serveur qu'I2P, il est nécessaire de remplacer 127.0.0.0 par 0.0.0.0 pour le proxy. Pour cela cliquer sur "Destinations locales" dans la console I2P. Configurer le proxy http I2P de façon à utiliser l'interface 0.0.0.0 :

proxy Http I2P

Dans iMule, j'ai utilisé le fichier dat suivant : http://mkex6401.free.fr/divers/nodes.dat

iMule

jeudi, février 4 2010

transmorph 3.0.2

I released a new version of transmorph.

Transmorph is a free java library used to convert a Java object of one type into an object of another type (with another signature, possibly parameterized).

With transmorph, you can easily convert from one type to another (with just one line):

[java]
convertedType = transmorph.convert(sourceObject, destinationType)

where destinationType can be a class, a Type, a TypeReference (parameterized class), a type signature...

For example :

 [java]
Transmorph converter = new Transmorph(new DefaultConverters());
int[][] arrayOfArrayOfInts = new int[][] { { 11, 12, 13 },{ 21, 22, 23 }, { 31 } };
String[][] arrayOfArrayOfStrings = converter.convert(arrayOfArrayOfInts, String[][].class);

We converted a multidimensional array of ints to a multidimensional array of Strings.

You can also convert to parameterized types (using TypeReference to overcome type erasure):

 [java]
Map map = new HashMap();
map.put("key1", new String[] { "value1-1", "value1-2" });
map.put("key2", new String[] { "value2-1", "value2-2" });
map.put("key3", null);
map.put("key4", new String[] { null, null });
map.put(null, new String[] { "value5-1", "value5-2" });
Map<String, List<String>> converted = converter.convert(map,new TypeReference<Map<String, List<String>>>() {});



DefaultConverters is a set of default converters but you can use the converters you want and write your own. There are actually more than 40 converters including :

   * BeanToBean and MapToBean
   * MapToMap, CollectionToCollection, CollectionToArray
   * EnumToEnum,StringToEnum
   * ...

You can check our junit tests for samples for each converter.

With transmorph, you can also :

  1. parse a type signature ("Map<String,List<String>>" or "Ljava.util.Map<Ljava.lang.String;Ljava.util.List<Ljava.lang.String;>;>;" and use it for conversion
  2. convert and inject properties into a bean (from a Map, another bean ...) using TransmorphBeanInjector

See out web page at http://transmorph.sourceforge.net

vendredi, janvier 29 2010

syslog-ng et shorewall

Dans la première partie de mon expérience avec arch-linux, j'ai indiqué comme j'utilisais shorewall. Quelques commandes shorewall sont assez intéressantes.

Utilisation de shorewall en ligne de commande

La première fois que je l'ai fait, c'était pour autoriser à nouveau mon poste client à accéder au serveur parce que je m'étais fait momentanément bannir par fail2ban (pour tester) ! La commande (sur le serveur)

 #shorewall allow ip banned

m'a tiré d'affaire.

Seulement, je me suis rendu compte que la plupart des commandes shorewall ne fonctionnaient pas et retournaient l'erreur suivante :

   LOGFILE (/var/log/shorewall.log) does not exist!

En fait, sous arch, syslog-ng est configuré pour que les logs de iptables aillent dans /var/log/iptables.log

Le plus simple est de modifier dans /etc/shorewall/shorewall.conf :

   LOGFILE=/var/log/iptables.log

Quelques commandes intéressantes

La commande

 #shorewall hits
   HITS DATE
   ---- ------
  24198 Jan 26
  18853 Jan 28
   3832 Jan 27
   2025 Jan 24
   1582 Jan 25
    934 Jan 29

   HITS  PORT SERVICE(S)
   ---- ----- ----------
    538  1433 ms-sql-s  
   1441   500 isakmp    
    139  1080 socks     
      9 10000 webmin    
     82  1025           
     46  8080 webcache  
      4 51198           
     38  1434 ms-sql-m  
      3 22165           
     19  3306 mysql     
      2   130
     18    23 telnet
      7    25 smtp
      4    53 domain
      4    21 fsp,ftp

Pour avoir la liste des macros disponibles :

 #shorewall show macros

Pour connaitre les fonctionnalités iptables disponibles :

 #sudo shorewall show capabilities

Je vous laisse découvrir les autres commandes !

mardi, janvier 12 2010

Serveur personnel avec arch-linux - Partie 3 : virtualisation

Dans la première partie, on a configuré le réseau (firewall, dnsmasq, bridge). Dans la seconde partie, on s'est intéressé à SSH.

Dans cette partie, on va utiliser libvirt et kvm pour gérer des machines virtuelles.

Installation

#yaourt libvirt

On va utiliser les permissions unix plutôt que polkit (pas trop de succès avec polkit) donc il faut décommenter la ligne concernant les droits unix comme indiqué dans le PKGBUILD. On va également installer kvm

#yaourt qemu-kvm

et quelques dépendances :

#pacman -S hal  avahi

Modifier /etc/libvirt/libvirt :

unix_sock_group = "libvirt"
unix_sock_ro_perms = "0770"
unix_sock_rw_perms = "0770"
auth_unix_ro = "none"
auth_unix_rw = "none"

On va crée le groupe libvirt (qui aura accès à libvirt)

#sudo groupadd libvirt
#sudo gpasswd -a cedric libvirt

et on démarre le service :

#sudo /etc/rc.d/libvirtd start

Configuration pour l'accès à distance

Il y a plusieurs possibilités :

  • par SSH : testé avec succès. Ce n'est pas très facile à mettre en place (nécessite l'utilisation de clés SSH)
  • Utiliser TCP/IP : testé avec succès. Facile à mettre en place. J'ai décrit cette méthode sur le wiki Using unencrypted TCP/IP socket
  • utiliser des certificats TLS. Cette méthode est très bien décrite dans la doc de libvirt Remote_certificates. C'est également la méthode que je vais décrire ici.

Utilisation de certificats TLS

#yaourt gnutls

Il est nécessaire de démarrer le serveur avec l'option listen. Editer /etc/conf.d/libvirtd

LIBVIRTD_CONFIG=
LIBVIRTD_ARGS="--listen"
KRB5_KTNAME=/etc/libvirt/krb5.tab

Il faut ensuite générer :

  • un certificat CA auto-signé
  • le certificat du serveur
  • un certificat par client

Certificate Authority (CA) :

Génération de la clé privé :

 #certtool --generate-privkey > cakey.pem

Génération du certificat Créer ca.info

   cn = my home
   ca
   cert_signing_key
 #certtool --generate-self-signed --load-privkey cakey.pem --template ca.info --outfile cacert.pem

Copier le certificat dans /etc/pki/CA/cacert.pem sur le serveur et sur les clients

Certificat du serveur

Génération de la clé privé :

 #certtool --generate-privkey > serverkey.pem

Génération du certificat Créer server.info

   organization = my home
   cn = bump
   tls_www_server
   encryption_key
   signing_key

Signer le certificat avec la clé de la certificate autority

 #certtool --generate-certificate --load-privkey serverkey.pem --load-ca-certificate cacert.pem --load-ca-privkey cakey.pem --template server.info --outfile servercert.pem

Copier

  • serverkey.pem dans /etc/pki/libvirt/private/serverkey.pem sur le serveur
  • servercert.pem dans /etc/pki/libvirt/servercert.pem sur le serveur

Certificat des clients

Pour chaque client : Génération de la clé privée :

 
 #certtool --generate-privkey > clientkey.pem

Créer le certificat client.info :

   country = FR
   state = Paris
   locality = Paris
   organization = my home
   cn = antec
   tls_www_client
   encryption_key
   signing_key

et le signer :

 #certtool --generate-certificate --load-privkey clientkey.pem --load-ca-certificate cacert.pem --load-ca-privkey cakey.pem --template client.info --outfile clientcert.pem

Copier la clé et le certificat sur le client :

#cp clientkey.pem /etc/pki/libvirt/private/clientkey.pem
#cp clientcert.pem /etc/pki/libvirt/clientcert.pem

Connection à distance

#virsh -c qemu://bump/system

Création d'une VM

On peut utiliser pour celà virtinst

#yaourt virtinst

Création de la VM :

 #virt-install --connect qemu:///system --name seven --ram 1024 --os-type=windows --os-variant=win7 --vnc --vnclisten 0.0.0.0 --disk /var/lib/libvirt/images/seven.img,size=20,format=qcow2 --cdrom /home/cedric/isos/Windows7.iso

On crée ici une VM pour Windows7, avec 1Go de RAM, 20Go de disque dur au format qcow2 et on met l'iso du système comme cdrom.

A noter que l'on peut faire tout çà en utilisant virt-manager à partir du poste client ... Enfin en principe : virt-manager fait planter libvirt chez moi 3 fois sur 4.

On peut ensuite se connecter à la VM à partir d'un autre poste avec virt-viewer

#virt-viewer --connect qemu://bump/system seven

Réseau

Dans la première partie, nous avons créé un bridge (sorte de switch virtuel). Nous allons ici connecter la machine virtuelle à ce bridge.

Sur le serveur :

#sudo EDITOR=nano virsh edit seven

Modifier le fichier :

   <interface type='bridge'>
      <mac address='52:54:00:40:41:ee'/>
      <source bridge='br0'/>
      <target dev='vnet0'/>
   </interface>

Utilisation de rdesktop

Plutôt que d'utiliser virt-viewer, il est préférable de configuration l'accès à distance dans seven et d'utiliser rdesktop. On peut utiliser grdesktop par exemple ou remmina

Snapshots

Sur le serveur :

#cd /var/lib/libvirt/images/

Lister les snapshots

#sudo qemu-img snapshot -l seven2.img

Créer un snapshot :

#sudo qemu-img snapshot -c "snapshot label" seven.img

Revenir à un snapshot :

#sudo qemu-img snapshot -a "snapshot label" seven.img

Reste à voir

Lors du redémarrage du serveur (ce qui ne devrait pas arriver trop souvent), les VMs sont stoppés de façon abrupte. Il serait préférable de suspendre la VM (virsh save) et de la restaurer (virsh restore) au démarrage.

Remarques

Utilisation de SSH

L'utilisation de SSH pour la connection à distance nécessite l'utilisation de clés SSH. Voir Using SSH Keys Il faut également installer openbsd-netcat. netcat est nécessaire pour l'accès par ssh mais libvirt utilise nc et non nc.openbsd. Un lien est donc nécessaire :

#ln -s /usr/bin/nc.openbsd /usr/bin/nc

Plantage de libvirt

En cas de plantage de libvirt et si /etc/rc.d/libvirtd start ne fonctionne pas :

#sudo rm /var/run/libvirtd.pid
#sudo /etc/rc.d/libvirtd start

samedi, janvier 9 2010

Serveur personnel avec arch-linux - Partie 2 : SSH

Notre serveur n'est accessible que par SSH. On veut qu'il soit accessible à la fois par le réseau interne et depuis internet.

Configuration d'openssh

Là encore, il n'y a pas grand chose à ajouter par rapport à la page du wiki consacré à SSH.

Dans /etc/ssh/sshd_config, on fixe

...
PermitRootLogin no
StrictModes yes
LogLevel VERBOSE
AllowUsers myUser
...

histoire d'éviter d'empêcher la connection en tant que root. J'ai laissé le port sur la valeur standard 22. Mais on va mettre en place fail2ban pour éviter les attaques par dictionnaire. LogLevel VERBOSE est nécessaire pour fail2ban justement. Pour plus de sécurité, on ne permet qu'à l'utilisateur myUser de se connecter par ssh.

Il faut ajouter une ligne dans /etc/hosts.allow pour permettre les connections :

sshd: ALL

Dans la première partie, on a déjà configuré le firewall pour accepter les connections SSH, donc rien de plus à faire de ce côté

fail2ban

On va utiliser fail2ban. Lorsqu'un utilisateur (ou un bot SSH) va tenter de se connecter et qu'il va faire trop d'erreurs de mot de passe, il sera banni pour un certain temps. Cela va empêcher les attaques par dictionnaires. Cela n'empêche pas de choisir des mots de passe suffisamment sécurisés.

J'ai créé une entrée sur le wiki de arch concernant fail2ban : fail2ban. Il suffit donc de s'y référer pour configurer fail2ban.

A chaque fois qu'une IP sera bannie, on veut qu'un mail nous soit envoyé. Pour cela, on peut utiliser SSMTP. En ce qui me concerne voici la configuration de ssmtp.conf (remplacer your@mail.org pour votre adresse mail):

#
# /etc/ssmtp.conf -- a config file for sSMTP sendmail.
#
# The person who gets all mail for userids < 1000
# Make this empty to disable rewriting.
root=your@mail.org

# The place where the mail goes. The actual machine name is required
# no MX records are consulted. Commonly mailhosts are named mail.domain.com
# The example will fit if you are in domain.com and you mailhub is so named.
#mailhub=mail
mailhub=smtp.free.fr
# Where will the mail seem to come from?
#rewriteDomain=y
rewriteDomain=mydomain.org
# The full hostname
#hostname=localhost.localdomain
hostname=localhost
#UseTLS=YES

#AuthUser=
#AuthPass=

Pour /etc/ssmtp/revaliases

# sSMTP aliases
#
# Format:       local_account:outgoing_address:mailhub
#
# Example: root:your_login@your.domain:mailhub.your.domain[:port]
# where [:port] is an optional port number that defaults to 25.

root:your@mail.org:smtp.free.fr
cedric:your@mail.org:smtp.free.fr

vendredi, janvier 8 2010

Serveur personnel avec arch-linux - Partie 1 : configuration du réseau

Dans ce post et dans les suivants, je vais présenter comment installer un serveur personnel en utilisant la distribution arch linux. Au menu :

  • réseau : dnsmasq, shorewall ...
  • virtualisation avec libvirt et kvm
  • connection au VPN du boulot
  • ...

Tout d'abord, présentation du serveur. Il s'agit d'un Pentium Dual Core E5400 pour ne pas trop consommer mais suffisamment puissant pour de la virtualisation. Il possède deux interfaces réseaux :

  • une reliée à un switch sur lequel sont raccordés les autres ordinateurs du réseau, l'imprimante etc ... Tout est relié à ce switch et non pas à la freebox.
  • une reliée à la freebox (non configurée en mode routeur ou alors avec l'adresse de la DMZ renseignée à l'adresse de notre serveur).

Le serveur ne possèdera pas d'écran, on y accèdera uniquement par SSH.

Configuration du réseau

Renommer les interfaces réseau

Tout d'abord, je me suis rendu compte que les interfaces réseau changeaient régulièrement de nom durant les reboot (eth0 devenait eth1 et inversement). Pour résoudre ce problème, il suffit d'utiliser udev :

Ajouter à /etc/udev/rules.d/010_netinterfaces.rules

KERNEL=="eth*", SYSFS{address}=="00:26:18:a5:60:18", NAME="eth0"
KERNEL=="eth*", SYSFS{address}=="00:26:18:a5:62:fc", NAME="eth1"

On obtient la macaddress avec ifconfig -a

Réseau

Plutôt que de connecter directement une interface au switch du réseau interne, on va créer un bridge pour la prise en charge future des machines virtuelles. local network

Le bridge est comme un switch virtuel. br0 correspond au switch et à la connection du serveur sur ce switch. Ajouter eth1 au switch est comme brancher le cable du switch réel sur le switch virtuel. L'intérêt du bridge est qu'il sera aussi possible de "brancher" des machines virtuelles à ce switch.

J'utilise yaourt pour récupérer les paquets à la place de pacman

#yaourt bridge-utils
#yaourt dhcpcd

/etc/conf.d/bridges

bridge_br0="eth1"
BRIDGE_INTERFACES=(br0)

/etc/rc.conf

MODULES=(bridge)
eth1="eth1 0.0.0.0 promisc"                                             
eth0="dhcp"                                                             
br0="br0 192.168.1.254 netmask 255.255.255.0 broadcast 192.168.1.255"   
INTERFACES=(eth0 br0 eth1) 

eth0 récupère l'adresse IP donnée par free.

/etc/hosts

#<ip-address>   <hostname.domain.org>   <hostname>
192.168.1.254           bump.mydomain.org  bump

Firewall

J'utilise shorewall. Il est recommandé de se reporter à la documentation shorewall pour la configuration d'un firewall à deux interfaces.

Le mieux est de copier les fichiers d'exemple de /usr/share/shorewall/Samples/two-interfaces dans /etc/shorewall puis de les modifier :

/etc/shorewall/zones

#ZONE   TYPE    OPTIONS                 IN                      OUT
#                                       OPTIONS                 OPTIONS
fw      firewall
net     ipv4
loc     ipv4

On définit trois zones : fw, notre firewall, net qui correspond à internet, loc qui correspond à notre réseau local (futures VM comprises).

/etc/shorewall/interfaces

#ZONE   INTERFACE       BROADCAST       OPTIONS
net     eth0            detect          dhcp,tcpflags,nosmurfs,routefilter
loc     br0            detect          tcpflags,nosmurfs,routefilter,routeback

Pour des explications concernant la colonne OPTIONS, se référer à la page man (man interfaces)

/etc/shorewall/masq

#INTERFACE              SOURCE          ADDRESS         PROTO   PORT(S) IPSEC   MARK
eth0                    10.0.0.0/8,\
                        169.254.0.0/16,\
                        172.16.0.0/12,\
                        192.168.0.0/16

Puisque l'on veut partager la connection internet entre plusieurs machines, il est nécessaire d'utiliser l' IP Masquerading pour les adresses locales non-routables.

/etc/shorewall/policy

#SOURCE         DEST            POLICY          LOG LEVEL       LIMIT:BURST

loc             net             ACCEPT
$FW             net             ACCEPT
$FW             loc             ACCEPT
loc             $FW             ACCEPT
net             all             DROP            info
# THE FOLLOWING POLICY MUST BE LAST
all             all             REJECT          info

Le fichier policy correspond à la politique par défaut pour les connections d'une zone à l'autre. Par défaut on rejette toutes les connections venant du net.

/etc/shorewall/routestopped

#INTERFACE      HOST(S)                  OPTIONS
br0

Histoire de pouvoir accéder au firewall lorsque shorewall est stoppé.

/etc/shorewall/rules

#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE          ORIGINAL        RATE        USER/    MARK                                                                                                 
#                                                       PORT    PORT(S)         DEST            LIMIT       GROUP                                                                                                         
#                                                                                                            
#       Accept DNS connections from the firewall to the network                                              
#                                                                                                            
DNS(ACCEPT)     $FW             net                                                                          
#                                                                                                            
#       Accept SSH connections from the local network for administration
#
SSH(ACCEPT)     loc             $FW
#
#       Allow Ping from the local network
#
Ping(ACCEPT)    loc             $FW

#
# Drop Ping from the "bad" net zone.. and prevent your log from being flooded..
#

Ping(DROP)      net             $FW

ACCEPT          $FW             loc             icmp
ACCEPT          $FW             net             icmp
#
Web(ACCEPT)     net             $FW
SSH(ACCEPT)     net             $FW

On accepte l'accès à partir du net pour quelques protocoles : SSH, Web

Enfin, on fixe dans /etc/shorewall/shorewall.conf

IP_FORWARDING=On
STARTUP_ENABLED=Yes

Il ne faut pas oublier de rajouter shorewall à la liste des services DAEMONS dans le fichier /etc/rc.conf puis à démarrer le service

DAEMONS=(...network shorewall...)

dnsmasq

On termine cette première partie par notre serveur dhcp/cache DNS. Il fournira les adresses IP à nos machines locales. La page wiki sur dnsmasq est plutôt bien faite et je conseille de s'y référer.

/etc/dnsmasq.conf :

interface=br0
bind-interfaces
dhcp-range=192.168.1.100,192.168.1.150,infinite

Notre serveur dhcp fournira des adresses IP comprises en 192.168.1.100 et 192.168.1.150. La durée de vie du bail que le DHCP va attribuer aux clients est ici infinie. Pour notre petit réseau, c'est très bien et cela permettra dans le futur de rediriger le traffic vers un ordinateur du réseau local pour certains protocoles (DNAT ou Port Forwarding).

Ajouter dnsmasq à la liste des services DAEMONS dans /etc/rc.conf

DAEMONS=(...network shorewall dnsmasq...)

Il ne reste plus alors qu'à configurer les clients pour utiliser dhcp.

mardi, septembre 15 2009

Building an RCP application including SWTbot tests, Cobertura coverage

Why this post ?

In this post, I will explain how I build an RCP application using eclipse PDE build. There are several samples on the web but quite often for toy applications with only one plugin and with no tests. This is not a ready-made template but you will perhaps find some good ideas to use in your build project.

The sample RCP application has

  • many features and plugins
  • tests, some of them use SWTBot for testing UI.

We want the build to :

  • set up development environment (provision source and binary artifacts)
  • create an executable application to be used in production
  • launch junit tests (some of which use swtbot) during the build and produce junit report and coverage report
  • create an executable test application that will run all the tests when launched and will produce a junit-tests.xml file with results (this can be useful to test the product in a specific environment)

Choosing a build system

We have the choice between :

However, I chose Eclipse PDE build and not Buckminster for the following reasons :

  • we use ivy at work and ivy is not supported by Buckminster (maven is supported though)
  • although documentation is better than before thanks to the Buckminster book, the examples are still quite basic
  • I do not know how to run tests using Buckminster (SWTBot tests, code coverage). This seems to be a work in progress (see https://bugs.eclipse.org/bugs/show_...)

That said, PDE build system is hard to use and give sometimes cryptic error messages.

Others links

Some other related projects that may be worth trying :

Some doc/presentations :

SwtBot releng project that can be used as sample :

  • http://github.com/ketan/swtbot/tree/master/org.eclipse.swtbot.releng/

Creating products and features

I created two products

  • one for production
  • one for test. This way, we can run the tests no only on the build machine and the developpement machine but also run them in a specific environment.

The production product (defined in cetl-product.product file) contains the following features :

<features>
      <feature id="net.entropysoft.cetl.feature"/>
      <feature id="net.entropysoft.transmorph.feature"/>
      <feature id="net.entropysoft.dashboard.feature"/>
      <feature id="net.entropysoft.jmx.feature"/>
[...]
      <feature id="org.eclipse.wst.xml_ui.feature"/>
      <feature id="org.eclipse.sdk"/>
      <feature id="org.eclipse.gef"/>
      <feature id="org.eclipse.emf.edit.ui"/>
      <feature id="org.eclipse.emf.edit"/>
      <feature id="org.eclipse.emf.ecore"/>
      <feature id="org.eclipse.emf.common"/>
      <feature id="org.eclipse.emf.common.ui"/>
      <feature id="org.eclipse.emf.ecore.edit"/>
      <feature id="org.eclipse.xsd.edit"/>
      <feature id="org.eclipse.xsd"/>
      <feature id="org.eclipse.wst.common.fproj"/>
   </features>

The test product (defined in cetl-product-test.product) contains the same features except that it adds the test plugins, swtbot and everything else needed to run the tests :

<feature id="net.entropysoft.cetl.test.feature"/>
      <feature id="net.entropysoft.cetl.testcomponents.feature"/>
      <feature id="org.eclipse.swtbot"/>
      <feature id="org.eclipse.swtbot.eclipse"/>
      <feature id="org.eclipse.swtbot.eclipse.test"/>

Note that we include org.eclipse.swtbot.eclipse.test feature here. This feature comes from the swtbot "Headless Testing Framework".

Checkout of the plugins and features

All our plugin and features are at the same level

net.entropysoft.cetl.feature
net.entropysoft.cetl.plugin
net.entropysoft.cetl.plugin.test
net.entropysoft.cetl.product
net.entropysoft.cetl.releng
net.entropysoft.cetl.test.feature
...

You can use a team project set to checkout your projects easily. You can then use svnGetProjectSet task from ant4eclipse if you want to checkout your projects from the team project set.

the releng project

the releng project will contain (most of the directories are created during the build) :

baseLocation/ : the target platform (created during the build)
  features/
    org.eclipse.rcp_3.5.0.v20090519-9SA0FwxFv6x089WEf-TWh11/
    ... 
  plugins/
    org.eclipse.core.runtime_3.5.0.v20090525.jar
    ...
eclipse/ : an eclipse install (created during the build)
ivy/ 
  apache-ivy-2.1.0-rc2 : an ivy install (created during the build)
lib/ : the dependencies retrieved by ivy (created during the build)
  bundles/
    slf4j-api.jar
    ...
  test
    cobertura.jar
    ...
  ...
product/
  buildConfiguration/ : configuration directory
    build.properties : copied from org.eclipse.pde.build and modified according to our needs
  buildDirectory/ : directory the build will take place in (created during the build)
product-test/
  buildConfiguration/ : configuration directory for test product
    build.properties : copied from org.eclipse.pde.build and modified according to our needs  
  buildDirectory/ : directory the build will take place in (created during the build)
test/
  cetl-studio-test/
  junit-results/
  workspace/
build-product-test.xml
build-product.xml
build.properties
build.xml
dependencies.xml
ivy.xml
materialize.xml
test.xml

I used the same terms (baseLocation, buildDirectory, buildConfiguration) than in PDE product build

build-product-test.xml, build-product.xml, build.xml, dependencies.xml, materialize.xml and test.xml are ant build files. Build.xml import all the others. This way, we have clear and short xml files instead of one big ant file.

Materializing target platform

The target platform (aka baseLocation) will be used to build the product but will be also used during developpement.

It contains all the pre-built features and plug-ins that our product requires.

ant target

The following ant target will provision the binary artifacts against which to build.

<target name="materializeTargetPlatform" description="materialize the target platform" depends="retrieve-ivy-dependencies">
		<delete dir="${baseLocation}" />
		<unzip dest="${baseLocation}" overwrite="true" src="${eclipse.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
		<unzip dest="${baseLocation}" overwrite="true" src="${eclipse-RCP-delta-pack.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
		<unzip dest="${baseLocation}" overwrite="true" src="${gef-runtime.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
		<unzip dest="${baseLocation}" overwrite="true" src="${emf-runtime.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
 
[...Some other dependencies from eclipse.org...]
 
		<unzip dest="${baseLocation}" overwrite="true" src="${swtbot.eclipse.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
		<unzip dest="${baseLocation}" overwrite="true" src="${swtbot.eclipse.headless.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
 
		<!-- bundles we depends on and that are in ivy repository -->
		<copy overwrite="true" todir="${baseLocation}/plugins">
			<fileset dir="lib/bundles" />
		</copy>
 
		<!-- don't need other directories than plugins and features. And if we create a .target from this dir it will not be correct if
		 we keep other directories -->
		<delete includeemptydirs="true">
			<fileset dir="${baseLocation}">
				<exclude name="plugins/**" />
				<exclude name="features/**" />
			</fileset>
		</delete>
	</target>

It just uncompress eclipse zip file, RCP-delta-pack, SWTBot and some other packages that our product requires.

${eclipse.zip}, ${eclipse-RCP-delta-pack.zip}, ${swtbot.eclipse.zip} ... refer to a corresponding file. I put all these files in a shared directory (And I do not delete them when I use a newer version so that I can rebuild an old version of the product).

Some things that are worth noting :

  • The RCP delta pack is mandatory as it includes the org.platform.launchers.feature which contains the launchers and root files necessary for a product.
  • swtbot.eclipse.zip and swtbot.eclipse.headless.zip are necessary as we use the target platform to build both production product and test product
  • I remove all directories other than plugins and features as it caused problems when I created a .target file

ivy

"materializeTargetPlatform" target depends on "retrieve-ivy-dependencies" because we use ivy at work and some of our dependencies here are managed by ivy.

For the curious persons, here is an excerpt of the ivy file :

<ivy-module version="1.0">
    <info organisation="entropysoft" module="net.entropysoft.cetl.releng">
    	<description homepage="http://www.entropysoft.net">Content ETL</description>
    </info>
    <configurations>
	  <conf name="bundles" visibility="private" transitive="false" description="osgi bundles required for compiling the module. non transitive"/>
	  <conf name="components-plugin-libs" visibility="private" transitive="false" description="libraries required for net.entropysoft.components.plugin. non transitive"/>
	  <conf name="test" visibility="private" description="dependencies required for the test compilation and execution phases."/>
[...]
    </configurations>
	<publications>
	</publications>
    <dependencies>
[...]
        <dependency org="entropysoft" name="components" rev="latest.integration" conf="components-plugin-libs->default"/>
 
        <dependency org="org.slf4j" name="jcl-over-slf4j" rev="1.5.8" conf="bundles->default"/>
        <dependency org="org.slf4j" name="slf4j-simple" rev="1.5.8" conf="bundles->default"/>
        <dependency org="org.slf4j" name="slf4j-api" rev="1.5.8" conf="bundles->default"/>
 
        <!-- we use cobertura to instrument our plugins -->
        <dependency org="net.sourceforge.cobertura" name="cobertura" rev="1.9.2" conf="test->default"/>
    </dependencies>
</ivy-module>

We use slf4j in our product. slf4j is already OSGI compatible. But if you need an an OSGI-ready version of a library, you can try at either :

Using the target platform during developement

See the following article to know why creating a target platform is a good thing : Why create a custom target platform?

I created a .target file and set it as target platform. As location, I choose "installation" and ${project_loc}/../net.entropysoft.cetl.releng.baseLocation (as I put the target file in net.entropysoft.cetl.product)

target definition

Updating the workspace

This step is very specific to your product but you probably have some things to copy, create or modify to have your workspace up and ready.

Here we copy some jars to a plugin and update the help (we use DocBook for the documentation and we have to convert it to eclipse help and to pdf ...)

<target name="materializeWorkspace" description="materialize workspace" depends="retrieve-ivy-dependencies">
		<copy overwrite="true" todir="../net.entropysoft.components.plugin">
			<fileset dir="lib/components-plugin-libs" />
		</copy>
		<antcall target="updateHelpPlugin" />
[...]
	</target>

The developement environment

<target name="setup" depends="materializeTargetPlatform,materializeWorkspace" />

This time, we have all we need to setup our environment. Once eclipse, subclipse and swtbot have been installed, all we need is to import the team project set and to launch the ant build with setup target. After selecting the .target and refreshing all the projects, they should all be able to compile.

Building the product

Create the build directory

First we create the build directory that is the directory the build will take place in. We copy our plugins and features respectively in "plugins" and "features" subdirectories.

The createBuildDirectory macro is defined as :

<macrodef name="createBuildDirectory">
		<attribute name="buildDirectory" />
		<attribute name="buildVersion"/>
		<sequential>
			<delete failonerror="false" includeemptydirs="true">
				<fileset dir="@{buildDirectory}">
					<exclude name="plugins/**/**.*" />
					<exclude name="features/**/**.*" />
				</fileset>
			</delete>
			<sync todir="@{buildDirectory}/features" includeemptydirs="true">
				<fileset dir="../">
					<include name="net.entropysoft.*.feature/**" />
[...]
				</fileset>
			</sync>
			<sync todir="@{buildDirectory}/plugins" includeemptydirs="true">
				<fileset dir="../">
					<include name="net.entropysoft.*.plugin/**" />
					<include name="net.entropysoft.*.plugin.test/**" />
					<include name="net.entropysoft.cetl.product/**" />
					<include name="net.entropysoft.cetl.help/**" />
[...]
					<!-- also exclude the generated class files -->
					<exclude name="*/bin/**" />
				</fileset>
			</sync>
		</sequential>
	</macrodef>

using productBuild.xml from pde build

We then invoke the productBuild.xml provided by pde build (see Building an RCP application from a product configuration file)

<property name="product.buildDirectory" value="${basedir}/product/buildDirectory" />
	<property name="product.buildConfiguration" value="${basedir}/product/buildConfiguration" />
 
	<!-- make sure to call targets materializeTargetPlatform and materializeWorkspace before -->
	<target name="product-build" depends="failIfBaseLocationUnavailable,extract-eclipse" description="creates product">
		<createBuildDirectory builddirectory="${product.buildDirectory}" buildversion="${buildVersion}"/>
		<java classname="org.eclipse.equinox.launcher.Main" fork="true" failonerror="true" dir="${basedir}">
			<arg value="-application" />
			<arg value="org.eclipse.ant.core.antRunner" />
			<arg value="-buildfile" />
			<arg value="${eclipseDirectory}/plugins/org.eclipse.pde.build_${pdeBuildPluginVersion}/scripts/productBuild/productBuild.xml" />
 
			<arg value="-DbaseLocation=${baseLocation}" />
			<arg value="-Dbuilder=${product.buildConfiguration}" />
			<arg value="-Dbasedir=${basedir}" />
			<arg value="-DbuildDirectory=${product.buildDirectory}" />
			<arg value="-DJ2SE-1.5=${jdk15Dir}/jre/lib/rt.jar" />
			<arg value="-DJavaSE-1.6=${jdk16Dir}/jre/lib/rt.jar" />
			<arg value="-DbuildVersion=${buildVersion}"/>
 
			<!-- there is a binary cycle due to slf4j -->
			<arg value="-DallowBinaryCycles=true" />
 
			<classpath>
				<fileset dir="${eclipseDirectory}/plugins">
					<include name="org.eclipse.equinox.launcher_*.jar" />
				</fileset>
			</classpath>
		</java>
	</target>

build configuration

product/buildConfiguration/build.properties is a modified copy of the template build.properties from org.eclipse.pde.build/templates/headless-build

product=net.entropysoft.cetl.product/cetl-product.product
runPackager=true
# The prefix that will be used in the generated archive.
archivePrefix=cetl-studio
# The location underwhich all of the build output will be collected.
collectingFolder=${archivePrefix}
# The list of {os, ws, arch} configurations to build.  This 
# value is a '&' separated list of ',' separate triples.  For example, 
#     configs=win32,win32,x86 & linux,motif,x86
# By default the value is *,*,*
configs=win32, win32, x86
# Type of build.  Used in naming the build output.  Typically this value is
# one of I, N, M, S, ...
buildType=I
# ID of the build.  Used in naming the build output.
buildId=cetl-studio-${buildVersion}
# Label for the build.  Used in naming the build output
buildLabel=cetl-studio 
# Timestamp for the build.  Used in naming the build output
timestamp=007
#Os/Ws/Arch/nl of the eclipse specified by baseLocation
baseos=win32
basews=win32
basearch=x86
filteredDependencyCheck=false
resolution.devMode=false
skipBase=true
skipMaps=true
skipFetch=true
# Specify the output format of the compiler log when eclipse jdt is used
logExtension=.log
# Whether or not to fail the build if there are compiler errors
javacFailOnError=true
# Enable or disable verbose mode of the compiler
javacVerbose=false
extraVMargs=-XX:MaxPermSize=512m
# produce a properly installed, fully p2 enabled, product
p2.gathering=true

extract-eclipse target just creates eclipse directory with a full eclipse installation we use to run the build.

Build the test product

productTest-build target

productTest-build is very similar to product-build. baselocation is still the same but buildDirectory and builder need to be changed.

<target name="productTest-build" depends="failIfBaseLocationUnavailable,extract-eclipse" description="creates test product">
		<createBuildDirectory builddirectory="${product-test.buildDirectory}" buildversion="${buildVersion}" />
		<java classname="org.eclipse.equinox.launcher.Main" fork="true" failonerror="true" dir="${basedir}">
			<arg value="-application" />
			<arg value="org.eclipse.ant.core.antRunner" />
			<arg value="-buildfile" />
			<arg value="${eclipseDirectory}/plugins/org.eclipse.pde.build_${pdeBuildPluginVersion}/scripts/productBuild/productBuild.xml" />
 
			<arg value="-DbaseLocation=${baseLocation}" />
			<arg value="-Dbuilder=${product-test.buildConfiguration}" />
			<arg value="-Dbasedir=${basedir}" />
			<arg value="-DbuildDirectory=${product-test.buildDirectory}" />
			<arg value="-DJ2SE-1.5=${jdk15Dir}/jre/lib/rt.jar" />
			<arg value="-DJavaSE-1.6=${jdk16Dir}/jre/lib/rt.jar" />
			<arg value="-DbuildVersion=${buildVersion}" />
 
			<!-- there is a binary cycle due to slf4j -->
			<arg value="-DallowBinaryCycles=true" />
 
			<classpath>
				<fileset dir="${eclipseDirectory}/plugins">
					<include name="org.eclipse.equinox.launcher_*.jar" />
				</fileset>
			</classpath>
		</java>
		<antcall target="removeSwtbotJunit3Plugins" />
		<antcall target="modifyTestProductIni" />
	</target>

build configuration

What is different is the product-test/buildConfiguration/build.properties file :

product=net.entropysoft.cetl.product/cetl-product-test.product
runPackager=true
archivePrefix=cetl-studio-test
collectingFolder=${archivePrefix}
configs=win32, win32, x86
buildType=I
buildId=cetl-studio-test-${buildVersion}
buildLabel=cetl-studio-test
timestamp=007
baseos=win32
basews=win32
basearch=x86
filteredDependencyCheck=false
resolution.devMode=false
skipBase=true
skipMaps=true
skipFetch=true
logExtension=.log
javacDebugInfo=true
javacFailOnError=true
javacVerbose=false
# Extra arguments for the compiler. These are specific to the java compiler being used.
compilerArg=-g
extraVMargs=-XX:MaxPermSize=512m
p2.gathering=true

javacDebugInfo is set to true and compilerArg is set to -g (this seems to be necessary for cobertura reports to be useful)

Removing swtbot junit3 feature & plugin

The ant target removeSwtbotJunit3Plugins is used to remove org.eclipse.swtbot.eclipse.junit3.headless and org.eclipse.swtbot.ant.optional.junit3 plugins as I use Junit4 in my tests and that these plugins must not be used together with org.eclipse.swtbot.eclipse.junit4.headless and org.eclipse.swtbot.ant.optional.junit4.

This is needed as there is one feature for both swtbot junit3 and junit4 plugins. This may change in next swtbot release.

<target name="removeSwtbotJunit3Plugins">
		<!--  JUnit 3.x and 4.x don't play well together. We remove org.eclipse.swtbot.eclipse.junit3.headless and org.eclipse.swtbot.ant.optional.junit3 from the plugins dir. -->
		<!-- see http://wiki.eclipse.org/SWTBot/Ant -->
		<zip destfile="${product-test.buildDirectory}/tmp.jar">
			<zipfileset src="${product-test.buildDirectory}/cetl-studio-test/cetl-studio-test-${buildVersion}-win32.win32.x86.zip">
				<exclude name="**/org.eclipse.swtbot.eclipse.junit3.headless*/**" />
				<exclude name="**/org.eclipse.swtbot.ant.optional.junit3*.jar" />
			</zipfileset>
		</zip>
		<move file="${product-test.buildDirectory}/tmp.jar" tofile="${product-test.buildDirectory}/cetl-studio-test/cetl-studio-test-${buildVersion}-win32.win32.x86.zip" />
	</target>

Modifying product ini file

This step is necessary if you want that your test product run all the tests when launched and produce a junit-tests.xml file with results. I think this can be useful to test the product in a specific environment.

First I think I could set the program arguments in cetl-product-test.product :

-application
org.eclipse.swtbot.eclipse.junit4.headless.swtbottestapplication
formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,junit-results.xml
-testPluginName
net.entropysoft.cetl.plugin.test
-className
net.entropysoft.AllTestsSuite

but I got an exception during build :

java echo !MESSAGE Invalid action syntax: 
addProgramArg(programArg:formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,junit-results.xml).
java echo !STACK 0
java echo java.lang.IllegalArgumentException: Invalid action syntax: 
addProgramArg(programArg:formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,junit-results.xml).
java echo at 
org.eclipse.equinox.internal.p2.engine.InstructionParser.parseAction(InstructionParser.java:97)

It seems that we cannot have a coma in the arguments otherwise we got this exception (at least with eclipse-3.5, this may be corrected in newer versions).

So I generate the ini file and include it in the test product zip file :

<target name="modifyTestProductIni">
		<!-- We should set the arguments in cetl-product-test.product but we have an error during the build because of the coma in formatter line-->
		<echo file="${product-test.buildDirectory}/cetl-studio-test/cetl-studio.ini">
-application
org.eclipse.swtbot.eclipse.junit4.headless.swtbottestapplication
formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,junit-results.xml
-testPluginName
net.entropysoft.cetl.plugin.test
-className
net.entropysoft.AllTestsSuite
-vmargs
-XX:MaxPermSize=512M
-Xms40m
-Xmx512m
		</echo>
		<zip destfile="${product-test.buildDirectory}/cetl-studio-test/cetl-studio-test-${buildVersion}-win32.win32.x86.zip" update="true">
			<zipfileset file="${product-test.buildDirectory}/cetl-studio-test/cetl-studio.ini" prefix="cetl-studio-test" />
		</zip>
	</target>

Running the tests

For the tests, we can use http://github.com/ketan/swtbot/blob... from swtbot. However, I chose here not to use it.

Install the test product

We just uncompress the test product we just created :

<target name="install-product-test">
		<delete dir="${testDirectory}" />
		<unzip src="${product-test.buildDirectory}/cetl-studio-test/cetl-studio-test-${buildVersion}-win32.win32.x86.zip" dest="${testDirectory}" />
	</target>

Instrument the classes to be tested

<target name="instrument-product-test" depends="taskdef-cobertura">
		<mkdir dir="${testDirectory}/cobertura-data" />
		<cobertura-instrument maxmemory="128M" datafile="${testDirectory}/cobertura-data/cobertura.ser">
			<includeClasses regex="net\.entropysoft\..*\.plugin\..*" />
			<instrumentationClasspath>
				<fileset dir="${testDirectory}/cetl-studio-test/plugins">
					<include name="net.entropysoft.*.jar" />
					<include name="net.entropysoft.*/**/*.class" />
[...]
				</fileset>
			</instrumentationClasspath>
		</cobertura-instrument>
	</target>

Generate another ini file for our test

We need to modify the ini file of the product to take the instrumentation into account. All instrumented classes will need to be able to access classes from cobertura.jar, we can do that by adding cobertura.jar to the bootclasspath.

I also set the vm to use.

<target name="generateTestIniFile">
		<echo file="${testDirectory}/cetl-studio-test/cetl-studio.ini">
-data
${testDirectory}/workspace
-application
org.eclipse.swtbot.eclipse.junit4.headless.swtbottestapplication
formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,${testDirectory}/junit-results/junit-results.xml
formatter=org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter,${testDirectory}/junit-results/junit-results.txt
-testPluginName
net.entropysoft.cetl.plugin.test
-className
${testSuiteClass}
-nosplash
-suppressErrors
-consolelog
-vm
${jdk15Dir}/bin/java.exe
-vmargs
-Xbootclasspath/p:${basedir}/lib/test/cobertura.jar
-Dnet.sourceforge.cobertura.datafile="${testDirectory}/cobertura-data/cobertura.ser"
-XX:MaxPermSize=512M
-Xms40m
-Xmx512m
		</echo>
	</target>

Running the tests

Just execute the test product with the modified ini file :

<target name="run-tests" depends="generateTestIniFile">
		<delete dir="${testDirectory}/workspace" />
		<mkdir dir="${testDirectory}/junit-results" />
 
		<exec executable="${testDirectory}/cetl-studio-test/cetl-studio.exe" dir="${basedir}" logError="true" failonerror="false" output="${testDirectory}/output.txt"/>
	</target>

reports

Nothing special here :

<target name="junit-report">
		<mkdir dir="${testDirectory}/junit-report" />
		<junitreport todir="${testDirectory}/junit-report">
			<fileset dir="${testDirectory}/junit-results">
				<include name="*.xml" />
			</fileset>
			<report format="frames" todir="${testDirectory}/junit-report" />
		</junitreport>
	</target>
<target name="cobertura-report" depends="taskdef-cobertura">
		<mkdir dir="${testDirectory}/coverage-report" />
		<cobertura-report format="html" datafile="${testDirectory}/cobertura-data/cobertura.ser" destdir="${testDirectory}/coverage-report">
			<fileset dir="../net.entropysoft.cetl.plugin/src" />
[...]
		</cobertura-report>
	</target>

Putting all together

I defined an "all" target to be called by CruiseControl, luntbuild, hudson, continuum or whatever continuous build tool you use :

<target name="all" depends="
		materializeTargetPlatform,
		materializeWorkspace,
		product-build,
		productTest-build,
		install-product-test,
		instrument-product-test,
		run-tests,
		junit-report,
		cobertura-report,
		publish,
		fail-if-errors,
		createSvnTag" />
<target name="fail-if-errors">
		<loadfile property="test.results" srcFile="${testDirectory}/junit-results/junit-results.xml" />
		<fail message="JUnit tests failed!">
			<condition>
				<not>
					<contains string="${test.results}" substring='errors="0" failures="0"' />
				</not>
			</condition>
		</fail>
	</target>

samedi, février 7 2009

Transmorph 2.0.0 released

Transmorph strong points are :

* support conversion for primitives and objects
* support conversion to multidimensional arrays
* support conversion to parameterized collections and types
* support conversion for beans that contain bi-directional relationships
* you can choose exactly which converters you want to use
* jars for JDK 1.4 and JDK 1.5
* no dependencies
* 40 converters included
* easy to add more converters
* objects can be modified after conversion using modifiers
* can convert to a type given either its java type (Class) or signature
* easy to use

Features added in version 2.0.0 (February 3, 2009)

 *  support for dotted style signatures (Ljava.util.Map<Ljava.lang.String;Ljava.util.List<Ljava.lang.String;>;>;)
 * new conversion context.
     It contains a pool of created objects to support data objects that contain bi-directionalrelationships.
     It also contains the used converter (useful for debugging)
 * new converters (BeanToBean, EnumToEnum, ImmutableIdentityConverter, StringToQName, StringToTimeZone)
 * added the notion of modifiers than can be applied after conversion : CanonicalizeFile, LowerCaseString, ResolveFile, TrimString, UppercaseString
 * many converters improved

vendredi, janvier 9 2009

transmorph 1.0.0 released : Another Object Conversion Framework

Transmorph is a new free java library (Apache 2 License) I created to convert a Java object of one type into an object of another type (with another signature, possibly parameterized).

Transmorph strong points are :

  • support conversion for primitives and objects
  • support conversion to multidimensional arrays
  • support conversion to "parameterized collections" and types
  • jars for JDK 1.4 and JDK 1.5
  • no dependencies
  • easy to add more converters
  • can convert to a type given either its java type (Class) or signature

Once configured, you can do the following conversions, for example :

// int[] to a List<Integer>
List<Integer> listOfInts = (List<Integer>) converter.convert(new int[] { 0, 1, 2, 3, 4, 5 },   List.class, new Class[] { Integer.class });
 
// Map<String,String[]> to a Map<String, List<String>
Map<String, List<String>> converted = (Map<String, List<String>>)converter.convert(map,"Ljava/util/Map<Ljava/lang/String;Ljava/util/List<Ljava/lang/String;>;>;");
 
// int[] => LinkedList<Integer> (ArrayToListConverter)
LinkedList linkedList = (LinkedList) converter.convert(new int[] { 0, 1, 2, 3, 4, 5 },   LinkedList.class,  new Class[] { Integer.class });
 
// int[][] => String[][] (ArrayToArrayConverter)
int[][] arrayOfArrayOfInts = new int[][] { { 11, 12, 13 },   { 21, 22, 23 }, { 31 } };
String[][] arrayOfArrayOfStrings = (String[][]) converter.convert(arrayOfArrayOfInts,  (new String[0][0]).getClass());

lundi, décembre 29 2008

IsbnExtractor : now on sourceforge

ISBNExtractor is now on sourceforge.

This site describes it and explain how to use it : http://isbnextractor.sourceforge.net

Here is a sample to use the library to get the isbn from a pdf file :

File file = new File(pdfFilePath);
FileISBNExtractor fileISBNExtractor = new FileISBNExtractor();
ISBNCandidates isbnCandidates = fileISBNExtractor.getIsbnCandidates(file);
ISBN isbn = isbnCandidates.getHighestScoreISBN();

mercredi, novembre 26 2008

PdfIsbnExtractor

Some time ago, I developed PdfIsbnExtractor. This is a program which can extract the isbn(s) from a pdf file. There are generally several candidates so each isbn candidate is given a score depending of the location of the isbn in the book, the isbn format ... It uses either pdttotext or pdfbox) to extract text from the pdf.

For now pdfIsbnExtractor is used as a plugin to Tellico)

You can get it at http://www.kde-files.org/content/show.php/PdfIsbnExtractor?content=90081

dimanche, novembre 23 2008

Mise à jour vers dotclear2

J'ai mis à jour le blog vers dotclear2. Comme il n'existait pas d'ebuild pour gentoo, j'en ai créé un pour l'occasion.

Voir http://bugs.gentoo.org/show_bug.cgi?id=248296

samedi, octobre 18 2008

Mise à jour du site de WinHP

WinHp est un logiciel que j'avais réalisé il y a pas mal de temps et auquel je n'ai plus touché depuis 2002. Il permet d'éditer des fichiers pour HP48/49/50. Il a eu et a encore pas mal de succès (au Brésil notamment) : 123353 downloads à ce jour si j'en crois sourceforge.

Du coup, j'ai mis à jour le site web et j'utilise maintenant un wiki : mediawiki. C'est ainsi plus facile pour le mettre à jour et pour que les utilisateurs puissent participer.

La page de WinHP : http://winhp.sourceforge.net/wiki/index.php/Main_Page

jeudi, août 21 2008

Paludis

J'utilise paludis à la place d'emerge sur une de mes machines gentoo. J'en suis plutôt satisfait (plus rapide notamment lors du calcul des dépendances qu'emerge qui mettait un temps fou chez moi). Je n'avais pas trouvé de remplaçant à autounmask pour le moment. C'est chose faite avec gimme.rb

Pour ceux qui ne connaissent pas, cela permet de rajouter automatiquement les packages à unmasker dans keywords.conf pour pouvoir installer un package donné.

mercredi, mars 19 2008

Tests unitaires des interfaces utilisateurs sous Eclipse

Cela faisait longtemps que je recherchais de quoi tester mes vues et mes dialogues sous eclipse. swtbot permet de le faire simplement.

Exemple de test : (un dialogue avec un CLabel et un Tree)

public class ItemSelectionDialogTest extends TestCase {
 
 	public void testConnected() throws Exception {
 		SWTEclipseBot bot = new SWTEclipseBot();
 		SWTBotShell botShell = bot.shell("Java - Eclipse SDK");
 		Display display = Activator.getStandardDisplay();
 		final ItemSelectionDialog itemSelectionDialog = new ItemSelectionDialog(
 				(Shell) botShell.widget);
 		UIThreadRunnable.asyncExec(display, new UIThreadRunnable.VoidResult() {
 			public void run() {
 				itemSelectionDialog.open();
 			}
 		});
 		bot.shell("Item Selection").activate();
 		SWTBotTree botTree = bot.tree();
 		assertTrue(botTree.rowCount() > 1);
 		SWTBotCLabel botCLabel = bot.clabel("none");
 		assertNull(botCLabel.getImage());
 		botTree.getTreeItem("Alfresco2").expand().select("Guest Home");
 		botCLabel = bot.clabel("Guest Home");
 		assertNotNull(botCLabel.getImage());
 		bot.button("OK").click();
 		Item item = itemSelectionDialog.getSelectedItem();
 		assertNotNull(item);
 		assertEquals("Guest Home", item.getName());
 	}
 }

SWTBot est encore récent mais il parait vraiment bien fait. L'auteur a présenté SWTBot à l'eclipsecon2008

dimanche, novembre 12 2006

Pratique de .NET 2 et C#2

Je conseille vivement ce bouquin de Patrick Smacchia. C'est un bouquin qui ne reste pas à la surface des choses. Il explique assez précisément des points pointus de C# et dotnet. Quelques exemples de points qui m'ont vraiment plu :

  • Introduction au langage IL. Quelques pages qui permettent d'avoir un premier aperçu
  • le chapitre sur la gestion de la sécurité (CAS). Pas facile à digérer mais une bonne référence à mon avis
  • l'explication sur les méthodes anonymes et la notion de variable capturée, de fermeture.
  • dans la même veine, l'explication des itérateurs avec le mot clé yield
  • le chapitre sur les transactions qui explique la gestion des transactions sous .net et windows
  • la manipulation des pointeurs en C#. A ne pas utiliser tous les jours mais c'est intéressant à connaître.

Bon, bien sûr tout n'est pas parfait :

  • très nombreuses fautes d'orthographes à tel point que cela gêne parfois la lecture
  • une demi-page à peine sur Mono : le bouquin est clairement orienté .net sous windows
  • la comparaison C# 2.0/Cpp. L'auteur s'adresse souvent aux développeurs Cpp pour mettre en avant les différences avec le Cpp. On sent que l'auteur est un ancien développeur Cpp mais cela n'apporte finalement pas grand chose.

D'autres thèmes sont abordés mais moins détaillés et c'est heureux car d'autres livres y sont consacrés : msbuild, les windows Forms, XML, asp.net 2.0, les services webs

mardi, novembre 7 2006

Sécurité et Sharepoint 2007

On va voir ici comment rechercher un utilisateur et assigner à cet utilisateur des rôles pour un document donné.

Voici le code en question, qui serait expliqué plus bas :

SPSite spSite = SPControl.GetContextSite(Context);
 using (SPWeb spWeb = spSite.RootWeb)
 {
    SPFile spFile = spWeb.GetFile("Documents/Proposal for my company.doc");
    SPListItem spListItem = spFile.Item;
    bool reachMaxCount;
    IList<SPPrincipalInfo> principalInfos = SPUtility.SearchPrincipals(spWeb, "cédric", SPPrincipalType.All, SPPrincipalSource.All, null, 100, out reachMaxCount);
    if (principalInfos.Count == 1)
    {
        SPPrincipalInfo spPrincipalInfo = principalInfos[0];
        string loginName = spPrincipalInfo.LoginName;
        string email = spPrincipalInfo.Email;
        string name = spPrincipalInfo.DisplayName;
        string notes = string.Empty;
        SPRoleAssignment roleAssignment = new SPRoleAssignment(loginName, email, name, notes);
        SPRoleDefinition roleDefinition1 = spWeb.RoleDefinitions.GetByType(SPRoleType.Contributor);
        SPRoleDefinition roleDefinition2 = spWeb.RoleDefinitions.GetByType(SPRoleType.WebDesigner);
        roleAssignment.RoleDefinitionBindings.Add(roleDefinition1);
        roleAssignment.RoleDefinitionBindings.Add(roleDefinition2);
        if (!spListItem.HasUniqueRoleAssignments)
        {
            spListItem.BreakRoleInheritance(true);
        }
        spListItem.RoleAssignments.Add(roleAssignment);
        spListItem.Update();
     }
 }

On fait attention à ne pas gaspiller les ressources :

using (SPWeb spWeb = spSite.RootWeb)

Voir à ce sujet http://msdn2.microsoft.com/en-us/li...

La recherche se fait par SPUtility.SearchPrincipals :

   IList<SPPrincipalInfo> principalInfos = SPUtility.SearchPrincipals(spWeb, "cédric", SPPrincipalType.All, SPPrincipalSource.All, null, 100, out reachMaxCount);

Cette méthode n'est pas (encore) documentée dans les MSDN.

  • SPWeb :
  • input

On recherche ici un utilisateur ou un groupe "cédric". Une recherche avec "a" aurait retourné tous les principals contenant la lettre "a".

SearchPrincipals est notamment utilisé par la fenêtre de recherche d'utilisateurs de sharepoint 2007.

  • SPPrincipalType

On a choisi ici de rechercher tous les types de principal mais on aurait pu se limiter aux utilisateurs par exemple (SPPrincipalType.User)

  • SPPrincipalSource

On utilise ici toutes les sources possibles mais on aurait pu se limiter aux utilisateurs Windows du domaine (SPPrincipalSource.Windows) ou à un membership provider (MembershipProvider) par exemple.

Un membership provider permet de mettre en place une authentification qui ne repose pas sur l'authentification windows mais sur des utilisateurs stockés en base de données par exemple). Voir à ce sujet l'article de Sahil Malik : Enabling Custom Authentication for SharePoint 2007

Le reste du code est déjà plus classique et est expliqué par exemple à http://www.sharepointblogs.com/ssa/...

mardi, octobre 24 2006

Sharepoint & versioning

Sharepoint 2007 a une API qui est loin d'être claire. La documentation n'aide pas beaucoup.

La récupération des versions d'un document dans une document library n'est pas aisée ...

La version courante
  • le contenu : SPFile.OpenBinary()
  • les propriétés : SPFile.Item
  • le label de version : SPFile.UIVersionLabel
Les autres versions

Je pensais pouvoir récupérer le SPFile correspondant à la version en question et de là récupérer ses propriétés de la façon suivante :

SPFileVersion spFileVersion = spFile.Versions[0];
SPWeb spWeb = spFileVersion.File.Item.Web;
SPFile spVersionFile = spWeb.GetFile(spFileVersion.Url);

mais spVersionFile.Item est null ...

En fait il faut faire, à partir du spFileVersion :

  • le contenu : spFileVersion.OpenBinary()
  • les propriétés : spFileversion.File.Item.Versions[spFileVersion.ID]
  • le label de version : spFileVersion.VersionLabel

lundi, octobre 23 2006

wikipedia et Navigation popups

Pour ceux qui ne connaissent pas, les navigations popups dans wikipedia c'est plutôt pas mal : juste en plaçant son curseur sur le lien d'un article, le début de l'article est affiché en popup. Très pratique ...

Pour rajouter cette fonctionnalité il faut éditer la page monobook.js de son profil.

http://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups

samedi, octobre 21 2006

teamcity & delayed commit

En ce qui concerne l'intégration continue, je n'ai jamais eu l'occation de tester teamcity (http://www.jetbrains.com/teamcity/) mais il me semble qu'il a certaines fonctionnalités uniques particulièrement intéressantes dont le delayed commit :

delayed commit : le code soumis n'est pas commité immédiatement, tous les tests sont d'abord lancés et seulement si les tests ont passé, les modifications sont soumises au gestionnaire de source (cvs, subversion ...). Sinon le développeur est informé que son code n'a pas passé la série de tests.

Bon au boulot, le build complet prend une heure environ et certains tests échouent sans raison de temps en temps (tests Selenium) mais c'est quand même une très bonne idée et j'espère qu'elle sera reprise par les outils open-source

- page 1 de 2