Somewhere between Dev and Ops

cat /dev/ops | grep gc

Spring MongoDB en 5 minutes

| Commentaires

Créer sa base MongoDB

Bien qu’il soit assez simple d’installer MongoDB sur un poste de développement, avec l’arrivée du cloud et des SaaS, il est très simple et très rapide d’avoir une instance de Mongo. MongoHQ offre des solutions d’hébergement de Mongo à la demande, à plusieurs tarifs. L’offre starter est gratuite, mais apporte une limite de 16 Mo.

En plus d’héberger l’instance, MongoHQ offre également une interface graphique très pratique pour parcourir votre base, vos collections et leurs données, ou encore gérer les indexes.

Utiliser Spring Data MongoDB

Ajout des dépendances

La première chose à faire est d’ajouter la dépendance vers le projet Spring Mongo. Avec un projet Maven, cela se fera très simplement comme ceci :

1
2
3
4
5
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-mongodb</artifactId>
  <version>1.0.1.RELEASE</version>
</dependency>

Configuration de Spring Data MongoDB

Il faut ensuite configurer votre application Spring afin d’y ajouter les beans nécessaires pour utiliser Spring Data MongoDB. Ce dernier fournit une classe de configuration qu’il suffit d’étendre pour configurer le stricte nécessaire :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class MongoConfig extends AbstractMongoConfiguration {

    @Override
    public Mongo mongo() throws Exception {
        return new Mongo("staff.mongohq.com", 10073);
    }

    @Override
    public String getDatabaseName() {
        return "spring-data";
    }

    @Override
    public UserCredentials getUserCredentials() {
        return new UserCredentials("spring-data", "spring-data-passwd");
    }
}

Les deux premières méthodes sont obligatoires, la première représente la connexion vers le serveur MongoDB. Si vous utilisez mongohq, vous trouverez les informations nécessaires dans l’onglet “Database infos”

Information de connexion à Mongohq

La deuxième est le nom de la base à utiliser, celui que vous avez fournit à la création dans mongohq

La troisième quand à elle ne soit être surchargée que lorsqu’on utilise des bases sécurisées et donc pour lesquelles il faut s’authentifier. Une fois de plus, mongohq offre la possibilité de gérer les utilisateurs et leur mot de passe via l’interface web.

Enregistrer et lire des documents

Une fois la configuration préparée, il est possible d’injecter un bean de type MongoTemplate. Celui ci propose une série de méthodes pour accéder à MongoDB.

Par exemple, prenons une classe Contact.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Contact {

    private String firstname;

    private String lastname;

    private String email;

    private String phoneNumber;

    // Getter, Setter & Constructors

}

Afin d’enregistrer une instance de cette classe, il suffit d’utiliser la méthode save(Object) du MongoTemplate :

1
2
3
4
MongoTemplate template = context.getBean(MongoTemplate.class);

Contact contact = new Contact("Gildas", "Cuisinier", "gildas.cuisinier[at]domain.com", "+42 424242");
template.save(contact);

Présentation de xdt4j

| Commentaires

Introduction

Chez mon client, nous avons eu un besoin particulier : celui de pouvoir étendre des archetypes Maven. Autrement dit, pouvoir créer un archetype qui se baserait sur un autre, et qui apporterait uniquement le delta de différence.

Pour certain fichier, nous n’avons pas eu le choix, nous avons été obliger de fournir une nouvelle version qui remplacerait la version initiale, par exemple les fichiers binaires tels que les images.

Par contre, pour le fichiers XML, et en particulier les fichiers pom.xml ou encore le fichier archetype-metadata.xml, c’était plus problématique. En effet, en cas de modification de l’un des ces fichiers dans l’archetype de base, nous aurions été obligé de copier ceux-ci, et ré-introduire les delta à la main… ce qui ne serait pas très productifs.

Partant du fait que ces fichiers sont du XML, il me semblait plus logique de partir du fichier de base et de ne décrire que les transformations à lui apporter. Quand on parle de transformation en XML, on pense tout de suite à XSLT. Mais il faut avouer que s’il est très puissant, il n’est pas forcément le plus simple à appréhender.

J’ai donc cherché des alternatives plus simples. En discutant avec un consultant .Net, celui-ci m’a présenté le système qui est utilisé par Microsoft pour gérer les fichiers de configuration de IIS : XML Document Transform. J’ai tout de suite trouvé cette solution élégante pour répondre à notre problème.

Par chance, quelqu’un a eu la bonne idée de faire une première implémentation OpenSource ( en se basant sur tout les exemples qu’il à trouvé dans la documentation et sur internet. Son implémentation se basait sur des spécificités .Net, donc impossible de le porter tel quel en Java. Cependant, son jeu de test était assez complet.

Grâce à celui-ci tests, j’ai pu développer en TDD une implémentation en Java : xdt4j.

Utilisation

Le fonctionnement de XDT pour ajouter/modifier/supprimer des éléments dans un fichier XML est en réalité assez simple. Le document XML décrivant les transformations est en réalité un document XML qui se conforme à la structure du document de base, mais sur lequel des attributs XDT ont été ajoutés pour décrire la modification voulue.

Prenons par exemple un fichier pom.xml qui servira de base :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<project>
    <modelVersion>4.0.0</modelVersion>

    <groupId>be.hikage</groupId>
    <artifactId>xdt4j</artifactId>
    <version>1.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

  <dependencies>
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.16</version>
          <packaging>bundle</packaging>
      </dependency>
  </dependencies>

</project>

La première modification que l’on souhaite apporter : Modifier le nom de l’artefact. Pour cela, il suffit de créer un fichier XML ayant une structure identique, qui représentera la modification que l’on souhaite apporter.

Ici par exemple :

1
2
3
4
/tmp/temp_textmate.JcbHjN:2: warning: variable $KCODE is no longer effective; ignored
<project xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <artifactId xdt:Transform="Replace">xdt4j-children</artifactId>
</project>

Vous pouvez voir que pour exprimer les modifications, il suffit d’ajouter un attribut de type xdt:Transform. Ici la valeur “Replace” signifie que l’on souhaite simplement remplacer l’élement du document de base par celui-ci.

Une fois les deux documents XML prêts, l’API Java de xdt4j est des plus simple :

1
2
3
4
5
6
7
// Chargement des fichiers XML sous forme de Document dom4j
Document xmlBase = loadDocument(&quot;base.xml&quot;);
Document xmlTransform = loadDocument(&quot;transform.xml&quot;);

// Utilisation de xdt4j
XdtTransformer transformer = new XdtTransformer();
Document xmlResult = transformer.transform(xmlBase, xmlTransform);

Le résultat de la méthode XdtTransformer.transform() est une copie du document original, sur lequel les transformations ont été traités. Le document original n’est pas modifié.

Transformations et Locators

D’autres transformations existent. Avec la transformation “Insert”, il serait possible d’ajouter une dépendance de cette manière:

1
2
3
4
5
6
7
8
9
10
<project xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <dependencies>
      <dependency xdt:Transform="Insert">
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.16</version>
          <packaging>bundle</packaging>
      </dependency>
  </dependencies>
</project>

Il est également possible de supprimer des élements avec la transformation “Remove”. Pour supprimer la dépendance sur log4j par exemple :

1
2
3
4
5
6
<project xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <dependencies>
      <dependency xdt:Transform="Remove" xdt:Locator="Condition(groupId/text()='log4j')">     
      </dependency>
  </dependencies>
</project>

Vous remarquerez dans ce dernier exemple l’attribut xdt:Locator. Celui-ci permet de spécifier plus précisement sur quel élement l’on souhaite appliquer la modification.

En effet, la transformation “Remove” par défaut va supprimer le premier élément du document de base qui va correspondre à l’expression XPath de l’élement sur lequel la transformation est placée. L’attribut Locator permet d’effectuer un filtre plus précis via les valeurs possibles Condition, Match ou XPath.

Dans ce cas-ci, Condition prends comme paramètre une condition XPath relative à l’élement courant.

Conclusion

Dans notre cas, xdt4j est une excellente réponse à notre besoin : l’extension d’archetype.

Il n’est pas forcément complet pour tout les cas de figures, mais il est ouvert et disponible sur GitHub : https://github.com/hikage/xdt4j.

Toute participation est donc la bienvenue, que ce soit des patch, des avis, des besoins (ouvrez une issue sur GitHub).

Et bonne nouvelle, les releases de xdt4j sont disponible sur Maven Central :

1
2
3
4
5
<dependency>
  <groupId>be.hikage</groupId>
  <artifactId>xdt4j</artifactId>
  <version>1.0.2</version>
</dependency>

Les versions Snapshots de leurs coté sont disponibles sur le répository OpenSource de Sonatype : https://oss.sonatype.org/

Tutoriel : Comment créer un DMG

| Commentaires

Les utilisateurs de Mac utilisent souvent de type de fichier, qui sont en fait des images disques (comme des .iso) qui peuvent être montées et qui apparaitront comme un nouveau disque sous Mac OS X.

Ce type de fichier est très souvent utilisé sous Mac comme moyen d’installer des logiciels. Afin de fournir un Bundle pour OpenJDK pour Mac OS X, j’ai voulu regarder de plus près comment faire pour créer un tel fichier.

Feedback : Switch PC vers Mac

| Commentaires

Tentation, doute et ..

Ceci n’est pas un billet de plus pour faire de la propagande Apple, mais juste l’envie de partager mon expérience sur le switch de Pc vers Mac.

J’ai eu l’occasion de tester, très épisodiquement, Mac OS X chez mon premier employeur. J’ai trouvé le concept intéressant, plutôt bien fini esthétiquement et simple à utiliser. Mais en tant que développeur/administrateur système, j’ai également découvert avec horreur le clavier Apple..

Quelques temps plus tard, un ami proche, administrateur système de profession, à tenter le switch vers un portable Apple. Mais il a rapidement changé, en particulier à cause du clavier qui ne lui était pas fort pratique.

Suite à cela, moi qui était jusque la tenter de passer le pas, j’ai été fort refroidit. Et quand j’ai effectivement été forcé de changer de portable, je suis resté sur le marque PC qui ne m’a jamais fait défaut. Mais avec un léger regret tout de même.

Et finalement, fin 2009, j’ai franchi le cap et me suis commandé un Mac Book Pro 15”.

Créer un namespace Spring

| Commentaires

Une des fonctionnalités fort pratique avec Spring est la notion de namespace. Ceux-ci permettre de simplifier et réduire de manière significative une configuration XML.

Spring en possède plusieurs de bases : jms, jee, scheduling, jdbc, mvc, …

Cependant, il est tout à fait possible d’en créer des spécifiques à nos propres besoins, et sans trop de difficulté.

Créer le schéma xml

Lorsqu’on créer un namespace pour Spring, la toute première chose à faire est de définir les éléments de celui-ci. Et comme on parle de namespace XML, cela se traduit par un schema XSD.

Le plus simple pour créer une XSD est de préparer un exemple de XML valide, et d’ensuite créer le XSD à partir de celui-ci. Dans le cadre du projet de modèle, je voulais pouvoir importer un fichier et définir des variables :

1
2
3
<import-template location="classpath:be/hikage/template/template.xml">
       <variable name="variable1" value="valeur1" />
</import-template>

A partir de différents outils (Intellij Idea, XmlSpy, ..), il est possible de générer une XSD correspondant à notre exemple :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="import-template">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="variable">
          <xs:complexType>
            <xs:attribute name="name" type="xs:string" use="required"/>
            <xs:attribute name="value" type="xs:string" use="required"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
      <xs:attribute name="location" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
</xs:schema>

Cependant, cette XSD ne comporte pas encore de notion de namespace dédié. Pour cela, il faut lui ajouter l’attribut targetNamespace avec comme valeur l’identifiant de notre namespace. Dans mon cas, il s’agira de http://www.hikage.be/schema/import-template.

La XSD complètée sera donc :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.hikage.be/schema/import-template">
  <xs:element name="import-template">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="variable">
          <xs:complexType>
            <xs:attribute name="name" type="xs:string" use="required"/>
            <xs:attribute name="value" type="xs:string" use="required"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
      <xs:attribute name="location" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
</xs:schema>

On enregistre cette classe dans les sources du projets, par exemple dans le package be.hikage.namespaces.schemas.

Créer un NamespaceHandler

Une fois le schéma défini, il faut encore dire à Spring comment l’utiliser. Pour cela, il faut implémenter un NamespaceHandler, le plus souvent en étendant la classe NamespaceHandlerSupport :

1
2
3
4
5
6
7
8
9
10
11
public class TemplateNamespaceHandler extends NamespaceHandlerSupport {
   private static final String IMPORT_TEMPLATE_TAG = &quot;import-template&quot;;

   public void init() {
       // Spécifie que les éléments &lt;import-template&gt; seront traités
       // par la classe ImportTemplateBeanDefinitionParser
       registerBeanDefinitionParser(IMPORT_TEMPLATE_TAG,
          new ImportTemplateBeanDefinitionParser());

   }
}                                                                                                                                                                                              }

Etendre NamespaceHandlerSupport nécessite d’implémenter une méthode init dans laquelle il est nécessaire de lier les éléments racines (import-template dans notre cas) à un BeanDefinitionParser.

Créer le BeanDefinitionParser

Les BeanDefinitionParser sont certainements les classes les plus importantes dans la création d’un namespace car ce sont elles qui vont véritablement dire à Spring quel Beans devront être ajoutés dans le contexte.

Dans notre cas, la classe ImportTemplateBeanDefinitionParser va être responable de

  • Parser l’élément import-template et ses éléments fils (variable)
  • Lire le fichier spécifié dans l’attribut location
  • Injecter dans le contexte Spring les définition définies dans le fichier en remplacant les variables présente dans celui-ci

En pratique, cela se fait dans la méthode parse, qui est définie dans l’interface BeanDefinitionParser :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public BeanDefinition parse(Element element, ParserContext parserContext) {

  // On lit l'attribut location pour connaitre le fichier modèle
  String resource = element.getAttribute(LOCATION_ATTRIBUTE);

  // On récupère les &lt;variable name=&quot;cle&quot; value=&quot;valeur&quot; sous forme de
  // Map&lt;Cle, Valeur&gt;
  Map&lt;String, String&gt; variables = prepareReplacement(element);

  // On lit les BeanDefinition (représentation de la configuration d'un
  // Bean) à partir du fichier modèle
  Map&lt;String, BeanDefinition&gt; tempDefinitions = loadTemplateBeans(location);

  // On remplace les variables dans les définitions ( nom de beans,
  // attributs, valeurs )
  Map&lt;String, BeanDefinition&gt; beansDefinition = replaceVariable(
          tempDefinitions, variables);

  for (Map.Entry&lt;String, BeanDefinition&gt; entry : templateBeansDefinitions
          .entrySet()) {
      // On ajoute chaque beans dans le contexte Spring
      parserContext.getRegistry().registerBeanDefinition(entry.getKey(),
              entry.getValue());
  }

  return null;
}

}

J’ai volontairement simplié le code concernant les méthodes loadTemplateBeans() et replaceVariable() par soucis de simplicité.

Dans le cas de la méthode loadTemplateBeans, j’utilise la classe XmlBeanDefinitionReader de Spring qui va lire et créer les BeanDefinitions à ma place. Un BeanDefinition est le modèle interne à Spring pour représenter un Bean : son nom, la classe, les variables constructeurs, les propriétés à configurer, …

Enregistrer le NamespaceHandler et le schéma

Nous avons maintenant notre schéma ainsi que les classes nécessaire pour que Spring puisse traiter correctement un fichier de ce type :

1
2
3
4
5
6
7
8
9
10
11
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:hikage="http://www.hikage.be/schema/import-template"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.hikage.be/schema/import-template http://www.hikage.be/schema/import-template/import-template-1.0.xsd">

   <hikage:import-template location="classpath:template.xml">
       <hikage:variable name="env" value="dev"/>
   </hikage:import-template>

</beans>

Pour cela, il est nécessaire de créer deux fichiers dans META-INF :

  • spring.handlers : qui va associé un namespace à son NamespaceHandler
  • spring.schemas : qui va associé l’URL utilisé dans xsd:schemaLocation à la XSD dans le classpath

Le fichier spring.handlers :

1
http://www.hikage.be/schema/import-template=be.hikage.springtemplate.TemplateNamespaceHandler
  • Clé : Le namespace défini dans le targetNamespace de la XSD
  • Valeur : la FQN du NamespaceHandler

Le fichier spring.schemas :

1
http://www.hikage.be/schema/import-template/import-template.xsd=be/hikage/springtemplate/import-template-1.0.xsd
  • Clé : L’URI définie dans le schemaLocation
  • Valeur : Le chemin de la XSD dans le classpath

Compléments

Cela dit, cet exemple est assez basique. Les possibilités d’un namespace sont plus étendue. Il est également possible d’améliorer le schema XSD et le code du BeanDefinitionParser pour mieux s’intégrer dans les IDEs.

En attendant, pour plus d’informations :

<li>Le code complet de l'<a href="http://code.google.com/p/spring-import-template/">Import-Template</a> vous permettra de voir en détail comment créer un namespace</li>
<li>La <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/extensible-xml.html">documentation officielle </a>sur la création d'un namespace</li>