Thématiques principales

Affichage des articles dont le libellé est compilation. Afficher tous les articles
Affichage des articles dont le libellé est compilation. Afficher tous les articles

lundi 2 avril 2018

InvocationHandler

Nous avions parlé des annotations dans un articles precedents mais nous nous etions arreter au traitements des annotations utilisé dans le code ou dans les class mais pas celles utiliser en runtime.

Je vous propose dans cet article de réemployer l’exemple traité en gardant l’annotation Relation mais de la compléter par exemple d’une annotation Tuple qui permet d'insérer des données dans la table construite par l’annotation Relation. (Au passage on adaptera un peu cette dernière de façon à ce que la Relation ait les attribut qui vont bien…)

Commençons par l’ajout de l’attribut dans notre précédente annotation, au passage on la passe en mode RUNTIME:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Relation {
 
 String value();
}


Ensuite on va adapter le processeur. On va externaliser la partie en interface avec la BD dans une classe utilitaire permettant de faire la construction de la table avec l’attribut qui va bien:


public void createRelation(Element e) {
  Relation relation = e.getAnnotation(Relation.class);
  String rqt = this.CREATE + e.getSimpleName().toString() + "( " + relation.value() + ATTRIBUT + ");";
  System.out.println("Requete :" + rqt);
  try {
   PreparedStatement statement = DriverManager.getConnection(DB_URL, USER, PASS).prepareStatement(rqt);
   System.out.println("Mon element : " + e.getSimpleName().toString());
   statement.executeQuery();
  } catch (SQLException e1) {
   e1.printStackTrace();
  }
 }

Voila, normalement on aura un attribut name dans notre table lors du build.

Maintenant on veut qu'à l'exécution, pour chaque objet de type Helloworld on insere dans la table le nom passé en parametre du setter. .

Pour cela on va creer l’annotation AddValue (ok c’est pas original) de façon a pouvoir la placer sur cette methode:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AddValue {

}


Ainsi on l’utilisera ainsi:


@Relation(value = "name")
public class HelloWorld {
 private String name;
 
 @AddValue
 public void setName(String name)
 {
  this.name=name;
 }
}


Mais me direz vous, cela ne permet pas vraiment de mapper un quelconque processus d’insertion en base de la donnée! Effectivement car il manque la partie processeur de cette annotation.

Le problème ici c’est que l’annotation doit être traité pendant l'exécution. Alors comment faire?

Et bien tout simplement en utilisant deux patterns, la factory et le pattern proxy. Le premier va nous permettre de masquer l’utilisation du second qui sera couplé à l’invocation handler! Le voila celui la!

Alors avant cela voici ce que nous espérons pouvoir faire:


IHelloWorld o=MyAnnotFactory.mapObject(IHelloWorld.class,HelloWorld.class);
o.setName("Thomas");


Et ceci doit nous permettre de l'insertion de Thomas dans la base lors de l’affectation. Pour cela, la factory va utiliser une interface et le prototype de la classe pour construire une instance qui sera en réalité un proxy.


 public static  T mapObject(Class i,Class t) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
 {
  T proxy=(T) Proxy.newProxyInstance(MyAnnotFactory.class.getClassLoader(),new Class[]{i}, new MyInvocationHandler(t));
  return proxy;
 }


Ainsi on utilise l’api Java Proxy permettant de construire ce proxy a partir de d’une interface identifiant au proxy quels sont les méthodes a wrapper et pour lesquelles, le proxy devra appeler l’InvocationHandler à la rescousse. Voyons comment, ce n’est pas tres compliqué:


public class MyInvocationHandler implements InvocationHandler {

 private Object object;

 public MyInvocationHandler(Class t) {
  try {
   this.object = t.newInstance();
  } catch (InstantiationException | IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 @Override
 public Object invoke(Object arg0, Method method, Object[] arg2) throws Throwable {
  System.out.println("Methode :" + method.getName());
  Method m=object.getClass().getMethod(method.getName(),method.getParameterTypes());
  for (Annotation annot : m.getAnnotations()) {
   System.out.println(annot); 
   if (annot.annotationType().equals(AddValue.class)) {
    System.out.println("je pousse en base");
    AnnotDBUtils.getInstance().pushData(object,arg2);
    return object;
   }
  }
  return method.invoke(object, arg2);
 }
}


Alors tout d’abord, le proxy doit continuer à faire croire que l’on manipule l’objet en question. (la factory etant la pour le tour de passe passe…) Du coup, a peine appelé, l’invocation handler va se charger de se construire un objet du type masqué par le proxy afin de l’utiliser lors de l'appelle effectif des méthodes sur le proxy.



Ceci va permettre de tromper l’utilisateur mais en plus cela va nous permettre de compléter ce comportement. Ainsi le contenu de la méthode invoke décrit la validation de la présence de l’annotation AddValue qui lorsqu’elle est présente va appeler une méthode push Data de notre utilitaire de BD que voici:


 public void pushData(Object object, Object[] arg2) {
  Relation relation = object.getClass().getAnnotation(Relation.class);
  String rqt = this.INSERT + "\"" + object.getClass().getSimpleName().toLowerCase() + "\" (" + relation.value()
    + ")" + VALUES + "('" + arg2[0] + "');";
  System.out.println("Requete :" + rqt);
  try {
   PreparedStatement statement = DriverManager.getConnection(DB_URL, USER, PASS).prepareStatement(rqt);
   statement.executeUpdate();
  } catch (SQLException e) {
   e.printStackTrace();
  }
 }


Voilà alors bien sur a chaque appele de la méthode setName, l’Invocation Handler va tenter de pousser la nouvelle donnée en BD sans chercher à savoir si c’est cohérent logique ou autre. Pour aller plus loin, il faudrait donner des moyens de vérification si la donnée n’est pas déjà en base ou ajouter un attribut servant de clef primaire…. mais la ca complique beaucoup les chose, et bon on va pas reimplementer JPA… même si l’idée est la même!

Du coup on va s'arrêter là mais j’imagine que vous avez compris l'intérêt de ce pattern surtout lorsqu’il est couplé aux annotations!

Références:

[1] http://alain-defrance.developpez.com/articles/Java/J2SE/implementation-dynamique-java-proxy-invocationhandler/
[2] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/11/design-pattern-proxy.html
[3] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-factory.html

lundi 12 mars 2018

Les annotations


Nous avions parlé des annotations lorsque nous avions traité des evolutions Java 5 [1]. Aujourd’hui je vous propose de nous intéresser à en faire quelques unes et de réaliser le ou les processeurs d’annotations qui vont bien pour les traiter dans les contextes de la compilation puis at runtime. Nous tâcherons de traiter les différents cas que nous pourrions rencontrer en définissant des annotations sur des cibles variées.

Tout d’abord, premier point, une annotation c’est quoi? Alors pour présenter cela, je vais reprendre l’exemple que nous avions vu alors dans [1]:

@SuppressWarnings(value = { "static-method" })
public <S> S functionGen(S s)
{
 return s;
}

Comme vous l’avez deviné, l’annotation ici est évidemment SuppressWarnings. Elle est identifiable via un @ et prend ici une liste de paramètres réduits à un seul. Le détail nous importe peu, ce qui est intéressant est l’information que l’annotation va transporter.

Une annotation a un nom, potentiellement des variables, et si ce n’est pas visible ici, possèdes des informations sur son contexte d’utilisation, et de sa porté. Ces deux derniers éléments sont positionnés lors de la définition de l’annotation.

Son contexte d’utilisation permet de préciser où celle ci pourra être utilisé. Ici dans l’exemple, l’annotation est positionné sur la méthode mais celle-ci peut être positionné sur une classe, une donnée membre ou même un package, tous ces éléments n'étant pas en exclusion mutuelle. Potentiellement une même annotation peut être utilisé sur plusieurs type de structures différentes.

De façon complète, il est possible de positionner une annotation (sauf définit contrairement) sur:

  • les classes 
  • les méthodes 
  • les interfaces 
  • les variables 
  • les packages


Au delà du cadre de son utilisation, la porté a par contre plus d’importance sur l’utilisation fonctionnelle de l’annotation. En effet, la porté définit à quel moment l’annotation doit être traitée comme une information utile au programme java. Celle ci peut être soit intégré qu’au code source (et ne sera plus visible ensuite, on utilise ce type d’annotation essentiellement pour donner de l’information au développeur ou aux analyseurs de code), soit intégré au code binaire (permettant au compilateur de réaliser des tâches supplémentaires) soit intégrée pour être traité au runtime afin de permettre d'adjonction de comportement dynamique (beaucoup de framework utilise aujourd’hui ce type d’API)

Bien sur ces différents cas d’utilisation des annotations nécessitent des mécanismes et des traitements différents. Autant on comprend vite comment va fonctionner les traitements d’annotations associé au code source qui auront surtout un rôle informatif. Par contre lorsque les annotations sont traité lors de la phase de compilation, il sera nécessaire d’utiliser le mécanisme des processeurs d’annotations (l’API Pluggable Annotation Processor). Enfin lorsque celles ci sont définies pour être traité at runtime, il faudra user de l’introspection et de certains type de pattern afin de profiter intelligemment des possibilités des annotations.

Mais avant de pouvoir traiter les annotations, je vous propose de reprendre tous ces points en définissant notre propre annotation.

Prenons d’abord l’exemple d’une annotation fournissant un lien documentaire explicitant la référence fonctionnelle ou technique amont. Pour la définir, nous allons créer une pseudo interface en utilisant le caractère @ avant le mot clef interface. Nous y déclarent une propriété document et une propriété chapitre et une propriété page.

Ces informations portent le côté fonctionnel, il faut maintenant définir le côté technique pour spécifier le contexte d’utilisation et de traitement.

Pour cela il faut utiliser des méta-annotations (une annotation pour annotations):

  • @Documented : Permet de préciser la présence de l’annotation dans la java doc de l'élément portant celle ci 
  • @Inherit : Permet de transmettre les propriétés informationnelles de l’annotation aux classes filles de l'élément portant celle-ci 
  • @Retention : précise le niveau de traitement (source, bitcode ou runtime) 
  • @Target: précise la ou les cibles sur lesquels l’annotation peut être apposé.


Dans le contexte très basique de cet exemple, nous nous limiterons a simplement a specifier la Retention a source.

@Retention(RetentionPolicy.SOURCE)
public @interface Reference
{
 String document();
 String chapitre();
 int page();
}

Voilà dans nos classes nous pourrons alors par exemple préciser à quelle partie de la documentation de la spécification cliente les classes font références:

@Reference(document=”docAmont.doc”,chapitre=”Service Client”,page=44)
public class ServiceClient
{
 public void service();
}

Imaginons maintenant une nouvelle annotation nécessitant un traitement plus profond à effectuer lors de la compilation. Par exemple une annotation @Relation qui associe a une classe permettra la production d’une table du même nom dans une bd (pour faire simple, nous passerons sur la production des attributs de relation mais on imagine facilement qu’il faudrait faire une annotation pour les données membres de la classe)

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Relation
{
}

Ainsi, sur une classe de notre application, nous placerons tout simplement notre annotation ainsi:

@Relation
public class HelloWorld
{
}

Alors bien sur si ceci nous permet de sémantiquement faire un lien entre notre classe et la relation d’une BD, il nous faut procéder à la mise en oeuvre d’un Processeur d’Annotation dont le but sera de récupérer toutes les classes postant celle-ci et exécutera un CREATE TABLE en sql sur la BD en question.

Pour réaliser un processeur d’annotation, il n’y a rien de complexe, il suffit de produire un simple artifact maven de type jar dans lequel nous trouverons:

  • une classe RelationProcessor dérivant la classe AbstractProcessor portant les annotations SupportedAnnotationTypes (précisant quelles annotations nous traitons) et SupportedSourceVersion (précisant dans quel version java la compilation aura lieu) 
  • un fichier de conf dans le répertoire META-INF/services nommé javax.annotation.processing.Processor et contenant le nom complet de notre classe processor.

A noter qu’il sera nécessaire de désactiver l’utilisation des processeurs d’annotations pour le packaging de notre propre processeur (sinon celui ci tentera se traiter lui même), il s’agit de l’option de configuration -proc:none du maven-compiler-plugin. Ensuite il suffira de faire une dépendance à la compilation pour que notre logiciel, lors de sa compile trouve par inspection le fichier de conf de notre jar contenant le processeur et l'exécute.

Voyons comment cela se presente:

Tout d’abord nous allons definir un pom module pour plus de simplicité:

   <modules>
    <module>tc-annotation</module>
    <module>tc-processor</module>
    <module>tc-helloworld</module>
   </modules>


Le premier module contient nos annotation, le second contiendra le processeur et le troisieme sera notre classe helloworld

Le module d’annotation n’a rien de special, c’est un projet maven classique produisant un jar (donc l’utilisation du plugin maven-compiler-plugin est largement suffisant) et contenant la définition de notre annotation:

package annot;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Relation {}



Le module processor est un peu plus complexe, comme nous l’avons vu il faut definir une classe heritant de la classe AbstractProcessor. Cette classe va recuperer l’ensemble des elements portant notre annotation et envera une requete en BD pour creer la table associée.

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({ "annot.Relation" })
public class HWProcessor extends AbstractProcessor {

    public static final String JDBC_DRIVER = "org.postgresql.Driver";
    public static final String DB_URL = "jdbc:postgresql://localhost:5432/test";
    public static final String USER = "postgres";
    public static final String PASS = "";
    
    public static String CREATE="CREATE TABLE ";

    @Override
    public boolean process(Set arg0, RoundEnvironment arg1) {
        System.out.println("HWProcessor running");
        Connection conn =null;
        try {
            Class.forName(JDBC_DRIVER);
            conn = this.getConnection();
        
        for (Element e : arg1.getRootElements()) {
            if (e.getAnnotation(Relation.class) != null) {
                this.createRelation(e,conn);
            }
        }
        } catch ( SQLException | ClassNotFoundException e1) {
            System.out.println(e1);
            return false;
        }
        return true;
    }
    
    public void createRelation(Element e,Connection conn) throws SQLException
    {
        PreparedStatement statement=conn.prepareStatement(this.CREATE+ e.getSimpleName().toString()+"();");
        System.out.println("Mon element : "+e.getSimpleName().toString());
        statement.executeQuery();
    }
    
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(DB_URL, USER, PASS);
       }

}


a noter de ne pas oublier d’ajouter le fichier javax.annotation.processing.Processor

au niveau du build maven, il vous faudra ajouter la ligne -proc:none a la configuration du plugin maven-compiler-plugin. Ne pas oublié non plus de bien sur ajouter les dependances a l’artifact contenant l’annotation et ici en plus a postgres puisqu’il s’agit de la BD que nous utilisons.

 <build>
  <plugins>
   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
     <compilerargument>-proc:none</compilerargument>
    </configuration>
   </plugin>
  </plugins>
 </build>

 <dependencies>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-annotation</artifactid>
   <version>0.1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
   <groupid>postgresql</groupid>
   <artifactid>postgresql</artifactid>
   <version>9.1-901-1.jdbc4</version>
  </dependency>
 </dependencies>


Enfin il reste a utiliser notre annotation dans notre projet. Donc evidement on depose l’annotation sur la classe:

import annot.Relation;

@Relation
public class HelloWorld {}


Enfin il faudra bien sur configurer notre build. Alors soit on precise dans le plugin maven-compiler-plugin que l’on va utiliser un artifact contenant un processeur d’annotation en eventuellement precisant le processeur :

   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
     <testsource>1.8</testsource>
     <testtarget>1.8</testtarget>
     <annotationprocessorpaths>
      <path>
       <groupid>org.tc.test</groupid>
       <artifactid>tc-processor</artifactid>
       <version>0.1.0-SNAPSHOT</version>
      </path>
     </annotationprocessorpaths>
     <!-- Soit les deux lignes suivantes soit le fichier dans le repertoire 
      services du jar contenant le processor -->
     <annotationprocessors>
      <annotationprocessor>proc.HWProcessor</annotationprocessor>
     </annotationprocessors>
     <!-- Ne pas faire de fork quand on utilise un processeur -->
     <!-- <fork>true</fork> -->
    </configuration>
   </plugin>



Mais cela, on peut aussi se reposer sur la capacité du jdk a decouvrir le processeur et ne declarer l’artifact que comme une dependance de compilation et ne pas mettre les champs annotationProcessors et annotationProcessorPaths (simplifiant largement la complexité du pom au passage). on prefere donc:

<dependencies>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-annotation</artifactid>
   <version>0.1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
   <groupid>org.tc.test</groupid>
   <artifactid>tc-processor</artifactid>
   <version>0.1.0-SNAPSHOT</version>
   <scope>compile</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-compiler-plugin</artifactid>
    <version>3.7.0</version>
    <configuration>
     <verbose>true</verbose>
     <source></source>1.8
     <target>1.8</target>
    </configuration>
   </plugin>
  </plugins>
 </build>



Voila il ne reste plus qu’a builder notre module:

mvn clean install


ok ca marche:

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] tc-annotation-0.1.0-SNAPSHOT ....................... SUCCESS [  4.078 s]
[INFO] tc-processor-0.1.0-SNAPSHOT ........................ SUCCESS [  1.929 s]
[INFO] tc-helloworld-0.1.0-SNAPSHOT ....................... SUCCESS [  1.223 s]
[INFO] tc-annotation-module-0.1.0-SNAPSHOT ................ SUCCESS [  0.038 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------



Voyons voir les log de notre processeur:

HWProcessor running
Mon element : HelloWorld
org.postgresql.util.PSQLException: Aucun résultat retourné par la requête.


Ok donc ca a l’air de s’etre bien passé vérifions la BD:

 psql -U postgres -w -d test -c "SELECT table_name from information_schema.tables where table_name='helloworld';"
 table_name
------------
 helloworld
(1 ligne)


Bonne nouvelle notre table est la dans le schéma!!

Bien voila, nous venons de passer en revu les deux ca d’utilisation que sont les annotations sur les sources et sur les classes. Leur objectifs ne sont clairement pas les même et si le premier cas ne nécessite pas beaucoup de travail, on peut voir qu’il faudra en fournir un peu plus sur le second en élaborant des processeur d’annotations.

Maintenant il reste un dernier cas d’utilisation, les annotations utilisés at runtime. Cependant désolé mais je ne traiterai pas de leur utilisation dans cet article. En effet, d’une part car cela relève globalement de la même approche introspective que pour la conception d’un processeur mais en employant plutôt quelques patterns bien senti comme l’Invocation Handler. Ce dernier est d’ailleur l’objet d’un prochain article et nous en profiterons alors pour jouer avec les dites annotations at runtime et nous verrons que le gros de travail sur le sujet a globalement été réalisé ici.

Références :

[1] Evolution Java 5

lundi 12 février 2018

Maven : Management

Un petit article pour comprendre quelques subtilités dans l’utilisation de maven et des éléments de build qui se finissent par Management.

En effet lorsque l’on construit ses poms, on a souvent besoin de définir certaines modalités particulieres des builds. Pour cela, on utilise des plugins. De la même façon lorsque l’on veut utiliser un composant, il est nécessaire de déclarer une dépendance à ce composant. Dans les deux cas la déclaration de ces éléments, une fois réalisé, est immuable.

Ainsi quoiqu’il arrive que la définition soit faites dans le pom parent ou dans le pom cible, lors du build, les éléments seront appelés tel quel moyennant les éventuelles surcharges mais il seront appelés.

En utilisant les éléments Management, c’est a dire dependencyManagement [1] ou pluginManagement [2], maven nous permet de définir des configurations potentiellement utilisables dans les poms enfants.

Par exemple dans un pom parent, en déclarant:


<dependencyManagement>
 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.10</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-all</artifactId>
   <version>1.9.5</version>
   <scope>test</scope>
  </dependency>
 </dependencies>
</dependencyManagement>


nous permet de déclarer dans notre pom cible:


<dependencies>
 <dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
 </dependency>
 <dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
 </dependency>
</dependencies>


sans avoir à déclarer une version particulière, celle ci étant celle donnée dans le pom parent et donc considérée comme préconisé.

Et rien n'empêche de déclarer une autre version, si l’on se sent suffisamment à l’aise pour assumer les éventuels conflits.

De la meme maniere, on pourra factoriser divers phases de builds que l’on appelera ou precisera selon le besoin dans les pom fils.

Par exemple un pom fils declarera de facon tres minimaliste:


<plugins>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <configuration>
    <archive>
     <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
    </archive>
   </configuration>
 </plugin>
 <plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
 </plugin>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
 </plugin>
 <plugin>
  <artifactId>jdeb</artifactId>
  <groupId>org.vafer</groupId>
 </plugin>
</plugins>


alors que la définition des configurations de ces plugins est juste un peu plus complexe:


<pluginManagement>
<plugins>
 <plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <version>2.3.7</version>
  <extensions>true</extensions>
  <configuration>
   <instructions>
    <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
    <Bundle-Name>${project.name}</Bundle-Name>
    <Bundle-Version>${project.version}</Bundle-Version>
    <Bundle-Vendor>TC</Bundle-Vendor>
   </instructions>
  </configuration>
  <executions>
   <execution>
    <id>bundle-manifest</id>
    <phase>process-classes</phase>
    <goals>
     <goal>manifest</goal>
    </goals>
   </execution>
  </executions>
 </plugin>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <version>2.3.1</version>
  <configuration>
   <archive>
    <manifest>
     <addClasspath>true</addClasspath>
     <classpathPrefix>./</classpathPrefix>
    </manifest>
    <manifestEntries>
     <Class-Path>./ ./${project.artifactId}-${project.version}/</Class-Path>
    </manifestEntries>
   </archive>
  </configuration>
  <executions>
   <execution>
    <id>assembly</id>
    <phase>prepare-package</phase>
    <goals>
     <goal>jar</goal>
    </goals>
    <configuration>
     <classifier>assembly</classifier>
     <includes>
      <include>**/*.class</include>
      <include>**/*.properties</include>
      <include>**/*-context.xml</include>
     </includes>
    </configuration>
   </execution>
  </executions>
 </plugin>
 <plugin>
  <artifactId>jdeb</artifactId>
  <groupId>org.vafer</groupId>
  <version>1.5</version>
  <executions>
   <execution>
    <phase>package</phase>
    <goals>
     <goal>jdeb</goal>
    </goals>
    <configuration>
     <deb>${project.build.directory}/${project.artifactId}-${build}_${versiontimestamp}_all.deb</deb>
     <verbose>true</verbose>
     <controlDir>${project.build.directory}/${project.artifactId}-${project.version}/DEBIAN</controlDir>
     <dataSet>
      <data>
       <src>${project.build.directory}/${project.artifactId}-${project.version}/opt</src>
       <type>directory</type>
       <mapper>
        <type>perm</type>
        <prefix>/opt</prefix>
       </mapper>
      </data>
     </dataSet>
    </configuration>
   </execution>
  </executions>
 </plugin>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
  <version>2.5.3</version>
  <configuration>
   <appendAssemblyId>true</appendAssemblyId>
   <attach>true</attach>
   <finalName>${project.artifactId}-${project.version}</finalName>
   <ignoreDirFormatExtensions>true</ignoreDirFormatExtensions>
   <outputDirectory>${project.build.directory}</outputDirectory>
   <archive>
    <manifest>
     <addClasspath>true</addClasspath>
     <classpathPrefix>./</classpathPrefix>
    </manifest>
   </archive>
  </configuration>
  <executions>
   <!-- Packaging debian -->
   <execution>
    <id>assembly-debian</id>
    <phase>prepare-package</phase>
    <goals>
     <goal>single</goal>
    </goals>
    <configuration>
     <appendAssemblyId>false</appendAssemblyId>
     <descriptors>
      <descriptor>src/main/assembly/assembly-debian.xml</descriptor>
     </descriptors>
    </configuration>
   </execution>
   <execution>
    <id>assembly-targz</id>
    <phase>package</phase>
    <goals>
     <goal>single</goal>
    </goals>
    <configuration>
     <appendAssemblyId>false</appendAssemblyId>
     <descriptors>
      <descriptor>src/main/assembly/assembly-targz.xml</descriptor>
     </descriptors>
    </configuration>
   </execution>
  </executions>
 </plugin>
</plugins>
</pluginManagement>


Mais cela revient a déclarer ce qu'il aurait de toute façon du être déclaré dans tous les pom fils (donc de multiple fois)

References:

[1] https://maven.apache.org/pom.html#Dependency_Management
[2] https://maven.apache.org/pom.html#Plugin_Management

dimanche 28 janvier 2018

Gradle

Lors de la production de systèmes logiciels, il ne fait aucun doute que la phase la plus importante est la compilation qui nécessite l’utilisation d'outils tels que gcc ou javac. Aujourd’hui pourtant limiter la production d’un logiciel a la compilation n’est plus suffisant. De nombreuses phases périphériques à la compilation sont devenu indispensables, comme l'exécution des tests unitaires ou des tests fonctionnels, la production automatique de documentations ou de code, le packaging multi plateforme, ou même la mise en production. Ceci s'appelle l'intégration continu au sein duquel on va trouver divers outils à différents niveau d’abstraction (Jenkins[1] , artifactory [2]) ou differents roles (Sonar [3], RobotFramework [4]). Nous reviendrons sur ces outils, mais nous en avions déjà vu un nommé Maven dans un précédent article [5] dont le but est justement de couvrir l’ensemble des phases du build.

Aujourd’hui nous allons nous intéresser à ce que l’on pourrait appeler un peu abusivement son concurrent: Gradle [7].

A mon sens Gradle en est plutot le successeur et nous verrons pourquoi. En effet, même si personnellement j’utilise encore beaucoup Maven, les raisons sont surtout historique et que faire la migration de l’un vers l’autre, ne peut être qu’un bien [9] mais cela repose sur une bonne disponibilité en temps.

Voyons ce que propose Gradle par dela Maven. Pour faire simple et rapide sur l'intérêt de Gradle est qu’il permet de dépasser le côté monolithe de Maven. En effet, Maven repose sur un socle stable et très sain d’un process de build. Du moins lorsque ce processus est classique et standard. Gradle, tout en conservant ce socle permet d'être plus souple et dynamique car contrairement a Maven qui repose sur xml pour décrire le contenu du build (donc a une vision configuration du process), Gradle lui repose sur groovy qui est lui un langage de programmation.

En fait si l’on reprend le titre de la publication de Leon Osterweil:  “software processes are software too” [8], nous sommes face à un véritable changement de paradigme dans la façon de gérer la production de logiciel ou celle-ci est également vu comme une partie intégrante du logiciel lui même.

Par exemple, Google a adopté Gradle pour la production de ses applicatifs Android et l’on peut assister que cette adoption a été largement suivi par une très bonne intégration de ce nouvel outils dans les IDE standard de la communauté. Ainsi developper une application android sous Gradle n’est vraiment pas un probleme [11] (nous y reviendrons dans un futur article)

Initialisation

Avant de nous pencher sur android, regardons déjà comment faire le build d’une application java standard avec Gradle.

Il faut commencer par télécharger Gradle [12] et d’ajouter son GRADLE_HOME/bin au PATH du système. Ceci permettra de lancer gradle en ligne de commande :

gradle -v

Votre installation est bonne? Ok nous allons alors pouvoir initialiser notre premier projet Gradle en lancant la commande init:

gradle init

A la console normalement vous devriez obtenir les log suivant

Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details
Support for running Gradle using Java 7 has been deprecated and is scheduled to be removed in Gradle 5.0. Please see https://docs.gradle.org/4.3/userguide/java_plugin.html#sec:java_cross_compilation for more details.
BUILD SUCCESSFUL in 7s
2 actionable tasks: 2 executed

Cette commande va tout d’abord lancer un demon pour accélérer les build suivant du projet et va preinitialiser pour votre système un certain nombre de fichiers, un répertoire wrapper et un répertoire .gradle (invisible sous unix)

ls -a
.  ..  build.gradle  gradle  .gradle  gradlew  gradlew.bat  settings.gradle

Alors commençons par la partie wrapper. Celle ci n’est pas indispensable mais Gradle préconise son utilisation afin de garantir l'homogénéité des builds du projet [13]. Les repertoires et fichiers concernés sont gradle, gradlew et gradlew.bat. En fait le wrapper permet contrairement à Maven,  le rechargement lors du build de la version proposée de gradle qui doit être utilisée pour construire le projet. Ainsi, pour tous les développeurs utilisant le wrapper, le build sera forcément identique (ou du moins reposera sur un environnement identique). Les fichiers gradlew permettent de lancer le wrapper et dans le repertoire nous trouverons un jar (réalisant le maintien de la version) et un fichier de propriétés pour le wrapper (contenant des données d’environnement) [14].

Les fichiers et répertoires participant a proprement parler au build sont build.gradle, settings.gradle et .gradle. Le repertoire .gradle est un repertoire interne a gradle propre au projet permettant à gradle de tagger les fichiers nouvellements modifiés et devant être incorporé au build incremental (c’est lui qui va rendre le processus de build plus efficace et plus rapide également). Normalement il ne devrait pas y avoir de besoin d’aller mettre son nez dedans; par contre, nous arrivons enfin sur les deux fichiers qui nous interesse le plus settings.gradle et build.gradle.

Le fichier settings.gradle est optionnel mais, il est recommandé de systématiquement l’utiliser. En effet celui-ci n’est pas utile pour un projet isolé mais sera indispensable dans le cas des multi-projets (equivalent aux modules maven). De plus il permet de définir un nom aux projets qui par défaut s’il n’existe pas est pris sur la base du nom du repertoire contenant le projet (ce qui n’est pas tiptop…)

settings.gradle

De façon simple voici ce qu’il peut contenir:

/*
// To declare projects as part of a multi-project build use the 'include' method
include 'shared'
include 'api'
include 'services:webservice'
*/
rootProject.name = 'monProjetGradle'

On aura compris que rootProject.name est le nom de notre projet…. A noter la primitive include afin de déclarer des sous projets à construire.

build.gradle

Intéressons nous au fichier de build lui-même: build.gradle. Ce fichier a pour vocation de décrire le contenu du build lui-même en s’appuyant sur la syntaxe Groovy. On imagine alors ce que l’on va y decrire sera considéré avant tout comme du comportement plutôt que de la configuration (a l’inverse de Maven) et c’est effectivement le cas à quelques exception près.

Prenons l’exemple d’un projet Java, il va falloir commencer par indiquer à Gradle que le build s’effectuera en Java. Pour cela, on va lui specifier l’utilisation d’un plugin [15]:

apply plugin: 'java'

Ensuite, à l'instar de maven on pourra donner une description et une version à l’artifact que nous allons créer. A la suite de quoi on va lui donner la version java pour la compilation:

description ="Projet de test pour gradle"
version = '0.1.0-SNAPSHOT'
sourceCompatibility = 1.8

Pour identifier les sources, il est possible de spécifier leur emplacement, ainsi que l’emplacement des tests.

sourceSets {
    main {
         java {
              srcDir 'src/main/java'
              }
         }
    test {
         java {
              srcDir 'src/test/java'
              }
         }
}

Encore une fois comme pour maven, il faut définir quelles vont être les dépendances nécessaires à la compilation et aux tests: Plus simplement que pour maven il suffit de déclarer le bloc dependencies comme suit:

dependencies {
    compile 'org.tc.osgi.bundle.utils:tc-osgi-bundle-utils-interfaces:0.1.0-SNAPSHOT'
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-all:1.9.5'
}

A noter que le nommage des artifacts peut suivre les règles d’ecriture prescrit par Maven et permettre d’utiliser des repository Maven.
C’est d’ailleur ce que l’on va ajouter à notre build, la possibilité d’aller rechercher des artifacts dans un artifactory maven. Ainsi, si vous utilisez un repository maven perso, il faudra le déclarer avec la primitive repositories comme suit:

repositories {
    //jcenter()
    maven {
        url "http://localhost:8081/artifactory/cots"
        credentials {
            username = "toto"
            password = "tata"
        }
   }
}

Voila, et enfin pour les plus aguerri, il est possible de modifier manuellement le Manifest du jar produit. Pour exemple, pour construire des Bundles OSGI (nous regarderons cela dans un autre article sur ce qu’est OSGI) il faudra non seulement rajouter le plugin osgi mais aussi triturer un peu le manifest:

apply plugin: 'osgi'
jar {
    manifest {
    attributes 'Bundle-SymbolicName' : project.name
attributes 'Bundle-Name' : project.name + '-' + version
attributes 'Bundle-Version' : version
attributes 'Bundle-Vendor' : 'TC'
        attributes 'Bundle-Activator': 'org.tc.osgi.bundle.utils.module.activator.UtilsActivator'
        attributes 'Bundle-Description': 'Un bundle pour contenir des services utilitaires'
    }
}

ou plus classiquement pour un jar java standard:

jar {
    manifest {
        attributes 'Main-Class': 'monProjet.Main'
    }
}

Voila, ceci termine la partie statique du build. Maintenant découvrons sa partie dynamique, les taches.

Les taches

Les tâches sont du code groovy permettant d’orchestrer le déroulement du build. Alors bien sur, il ne faudra pas écrire tout le code nécessaire à son exécution. En effet si il existe deux types de tâches, les premières et les plus employées sont celles par défauts déclarer dans les plugins [15] [16].

Ensuite bien sur, il est possible d’en créer soit même avec à terme la définition d’un plugin.. A noter que outre la simplicité évidente du langage, dans le monde maven, il ne serait pas possible d’ajouter du comportement au build sans faire obligatoirement un plugin-maven puisque l’on ne peut ajouter directement du comportement dans le pom (a moins d’utiliser un plugin de type script).

Par exemple voici une petite tache propre a notre build:

// définition d'une tache perso nommé hello
task matache {
    doLast {
        println 'Building ' + project.name
    }
}

Voila il suffira d’appeler la commande

gradle matache

Pour afficher dans la console de build, “Building monProjetGradle”

Voila on a fait le tour de Gradle sans forcement etre rentrer dans le détail mais probablement suffisamment pour pouvoir rapidement mettre en oeuvre un début de build. A mon sens gradle, au vue de sa flexibilité, ne peut que finir par remplacer Maven car il en détient tous les avantages et en complète les défauts (attention par contre a ce que flexibilité ne ressemble pas a chaos). Finalement, son seul tort est peut etre encore son manque de popularité et de visibilité auprès des grands groupes du fait de son “jeune” âge même si la communauté est très active et fournit du contenu riche et très varié.  Pour plus d’informations, n'hésitez pas à consulter [17] [18] qui m’ont été d’une grande aide pour découvrir gradle et écrire cet article.

References:

[1] https://jenkins.io/
[2] https://jfrog.com/artifactory/
[3] https://www.sonarqube.org/
[4] http://robotframework.org/
[5] https://maven.apache.org/
[6] http://un-est-tout-et-tout-est-un.blogspot.com/2017/12/maven-introduction.html
[7] https://gradle.org/releases/
[8] Leon Osterweil:  “software processes are software too”, Proceeding ICSE '87 Proceedings of the 9th international conference on Software Engineering, Pages 2-13
[9] https://dzone.com/articles/gradle-vs-maven?edition=306207&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=dd%202017-07-02
[10] https://devops.com/puzzle-gradle-maven/
[11] http://www.tutos-android.com/maitriser-gradle-partie-1
[12] http://www.gradle.org/downloads
[13] https://docs.gradle.org/current/userguide/gradle_wrapper.html
[14] https://docs.gradle.org/2.4/userguide/build_environment.html#sec:gradle_configuration_properties
[15] https://docs.gradle.org/2.4/userguide/standard_plugins.html
[16] https://plugins.gradle.org/
[17] https://www.petrikainulainen.net/getting-started-with-gradle/
[18] http://www.vogella.com/tutorials/Gradle/article.html

dimanche 7 janvier 2018

Maven, préparons des parents

Dans un précédent article [1], nous avions vu les principes généraux de Maven et son utilisation. Nous avions egalement vu le pattern parent/module/projet qui permet d’organiser la structure des projets.

Dans cet article, nous allons nous concentrer sur la partie haute de ce pattern en présentant une proposition de pom parents génériques ainsi que quelques déclinaisons de sous parents pour des sous classes d’applications (dans notre cas pour des application OSGI, des projets de plugins mavens et dans un avenir proche pour des application JEE)



Pour illustrer et expliquer ce pattern, je me permettrai de m'inspirer de mes propres projets de mes pom parents du dépôt Git Hub [2]

Nomenclature 

De façon générale, tous pom doit définir 4 points pour lesquels j’applique la nomenclature suivantes:
  • groupid = org.tc.${FONCTION}
  • artifactid = tc-${FONCTION}[-module]
  • version = [maj].[min].[ft]
  • packaging = pom
ou

  • $[FONCTION] est le rôle du pom courant 
  • [-module] la gestion du cas particulier du pom module
  • [maj] modification majeur du composant ( généralement fonctionnelle)
  • [min] modification mineur du composant ( généralement technique)
  • [ft] modification suite à un correctif 

Le groupId permet a maven de classer le pom, ainsi si l’on transpose cela a un répertoire, cela représente un chemin de répertoires dans lequel on trouvera tous les projets de ce type.
L'artifactId représente l’identifiant unique du pom dans la famille de pom classer sous le groupId.
La version permet de gérer les évolutions dans le temps et les dépendances des composants entre eux selon ces évolutions.

Le packaging permet à maven de connaitre la nature du composant qu’il aura à construire ainsi, un packaging pom est typiquement un pom parent ou module alors que jar, implique que l'exécution du pom produise une application java packager. (On aura aussi par exemple des packaging de type war, ejb, bundle ou ear… [3]

Optionnellement, les poms (et dans mon cas ils seront systématiquement définis) il est possible de donner une description au pom permettant de rentrer un peu plus dans le détail de son rôle et egalement de definir le name qui n’est que le nom simple du pom (“human readable”) tel qu’il sera affiché dans le reator du processus Maven (le resultat d’execution du build) il est donc important d’en choisir un lisible, simple et évident.

On choisira un name comme ci-dessous:
name = ${project.artifactId}-${project.version}

Pom parent


<groupId>org.tc.parent</groupId>
<artifactId>tc-parent</artifactId>
<name>${project.artifactId}-${project.version}</name>
<version>0.7.0-SNAPSHOT</version>
<packaging>pom</packaging>
<description>Pom parent des projets</description>

Le plus important dans le pattern parent/module/projet est, comme pour une hiérarchie de classe, l'élément le plus abstrait, celui de plus haut niveau c’est a dire le pom parent. Le pom parent a pour role de factoriser l’ensemble des informations concernant les composants participant à la construction de toutes les familles d’applications de nos projets.

De plus il participe également  à la définition d’information d’ordre plus générale comme l’identité du mainteneur du pom, les url permettant d'accéder au dépôt git ou au site web du projet. Pour ce genre d’informations je vous invite à consulter le pom en question, nous nous intéresserons aux premières composantes.

Donc pour proposer aux futur projet un environnement de production cohérent, le pom parent va définir le type d’encodage des sources:


<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>


Il peut aussi déclarer ou sera déployée la production produite par maven en définissant le distributionManagement qui poussera les jar et les pom dans par exemple un artifactory ou un nexus:


<distributionManagement>
    <snapshotRepository>
     <id>artifactory</id>
     <name>artifactory-snapshots</name>
     <!-- ${arti-url} defined in conf/settings.xml url d'artifactory-->
     <url>${arti-url}/artifactory/libs-snapshot-local</url>
    </snapshotRepository>
    <repository>
     <id>artifactory</id>
     <name>artifactory-releases</name>
     <!-- ${arti-url} defined in conf/settings.xml  url d'artifactory-->
     <url>${arti-url}/artifactory/libs-release-local</url>
    </repository>
    <site>
     <id>siteweb</id>
     <!-- defined in conf/settings.xml correspond au repertoire local ou sera déposé le contenu du build de la phase site-->
     <url>${local-siteweb-url}</url>
    </site>
</distributionManagement>


Dans le cas présent, on en a profité également pour définir ou sera déployé le site maven si la phase site est exécutée (variable ${local-siteweb-url}). A noter, que l’on utilise des variables directement dans le pom qui doivent avoir été défini dans le settings. Cette approche rend certe le pom non utilisable directement mais il procure l’avantage de permettre la pré-configuration des builds pour l’environnement dans lequel il va s'exécuter. Dans le settings pour pouvoir exécuter le build, il faudra affecter a ces variables les urls de artifactory et du site web.

On passe ensuite à la définition des outils de reporting a utiliser pour la génération du site web maven du projet.

Dans notre cas, nous employons


<reporting>
    <plugins>
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-plugin-plugin</artifactId>
      <version>3.3</version>
     </plugin>
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-changelog-plugin</artifactId>
      <version>2.2</version>
     </plugin>
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-checkstyle-plugin</artifactId>
      <version>2.10</version>
     </plugin>
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-docck-plugin</artifactId>
      <version>1.0</version>
     </plugin>
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jxr-plugin</artifactId>
      <version>2.3</version>
     </plugin>
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-pmd-plugin</artifactId>
      <version>3.0.1</version>
     </plugin>
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-project-info-reports-plugin</artifactId>
      <version>2.6</version>
     </plugin>
     <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-report-plugin</artifactId>
      <version>2.18.1</version>
     </plugin>
    </plugins>
</reporting>


Dont les rôles sont [4] :

  • maven-plugin-plugin : Permet de generer la description d’un plugin maven
  • maven-changelog-plugin : Permet de generer le rapport changelog
  • maven-checkstyle-plugin : Permet de generer le rapport d’analyse de checkstyle (bonne pratique d’ecriture du code)
  • maven-docck-plugin : Permet de vérifier la documentation
  • maven-jxr-plugin : fourni un rapport des references croisées des sources java
  • maven-pmd-plugin : fournir un rapport d’analyse de code du code java
  • maven-project-info-reports-plugin : Permet de consolider dans un rapport les informations complémentaire contenu dans les pom
  • maven-surefire-report-plugin : permet de fournir un rapport d'exécution des tests unitaires

Ensuite par défaut il est possible de déclarer que tous les projets utilisent les même outils de tests. Dans le cas de nos projets nous avons fait le choix d’utiliser JUnit et Mockito


<dependencies>
    <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.10</version>
     <scope>test</scope>
    </dependency>
    <dependency>
     <groupId>org.mockito</groupId>
     <artifactId>mockito-all</artifactId>
     <version>1.9.5</version>
     <scope>test</scope>
    </dependency>
</dependencies>


Nous avons traité tous les éléments annexes au build lui même (c’est a dire la documentations et les informations complémentaires générées lors de la construction du site maven) . Maintenant, il convient de définir la configuration des builds eux même et donc la configuration des composants participants à ces builds. Ces éléments sont à déclarer entre les balises <build></build>.

Premier point a ajouter aux builds est la déclaration des répertoires de ressources et de les spécifier filtering. De cette façon, il est possible de déclarer des fichiers de configuration incluant des variables qui seront remplacées et affectées au moment du build par la valeur déclarée dans le pom. Ainsi, en déclarant des profils avec des valeurs différentes, il est possible de réaliser des productions incorporant des configurations differentes. (Nous reviendrons sur ce point plus tard, ce ne sont pas les exemples d’utilisations qui manquerons)


<resources>
 <resource>
  <directory>src/main/resources</directory>
  <filtering>true</filtering>
 </resource>
</resources>


Ensuite, il faut ajouter différents plugins:

Le plugin permettant de spécifier le JDK à employer ainsi que sa version:


<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>3.1</version>
 <configuration>
  <verbose>true</verbose>
  <source>1.8</source>
  <target>1.8</target>
  <testSource>1.8</testSource>
  <testTarget>1.8</testTarget>
  <arg>-XprintRounds -XprintProcessorInfo -Xlint -J-verbose</arg>
  <encoding>UTF-8</encoding>
  <fork>true</fork>
  <!-- defined in conf/settings.xml -->
  <executable>${JAVA_1_8_HOME}\bin\javac</executable>
 </configuration>
</plugin>


Les options -XprintRounds -XprintProcessorInfo -Xlint -J-verbose ont pour but d’augmenter la verbosité pendant la compilation et de fournir plus d’informations sur l’utilisation du processeur d’annotation [5].

Comme pour les urls dont nous avons parlé, il faudra ajouter la variable ${JAVA_1_8_HOME} à votre settings permettant à maven de savoir ou se trouve votre installation de Java.

Pour produire la javadoc on ajoutera le plugin suivant avec le paramètre Xdoclint:none afin a rendre le parseur générant la documentation moins rigide [6], (sinon il se peut que vous ne puissiez plus générer votre java doc):


<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-javadoc-plugin</artifactId>
  <version>2.9</version>
  <executions>
    <execution>
      <id>attach-javadocs</id>
      <goals>
        <goal>jar</goal>
      </goals>
      <configuration>
        <additionalparam>-Xdoclint:none</additionalparam>
      </configuration>
    </execution>
  </executions>
</plugin>


Même combat avec le plugin maven site qui va aussi vous produire la javadoc en plus des reporting que nous avons vu précédemment:


<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-site-plugin</artifactId>
  <version>3.3</version>
  <configuration>
  <reportPlugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-javadoc-plugin</artifactId>
      <configuration>
        <additionalparam>-Xdoclint:none</additionalparam>
      </configuration>
    </plugin>
   </reportPlugins>
   </configuration>
</plugin>


Enfin si vous livrer vos sources sous la forme d’un jar séparé de vos binaires, il faudra ajouter le plugin maven-source-plugin:

Pom parents fils

Voila on a fait le tour des plugins du pom parent principal. De la même manière, on va pouvoir créer d’autres pom parent plus spécialisés héritant de ce pom [2]. Sans entrer dans le détail, le dépôt propose par exemple de façon plus ou moins abouti des pom parent pour la production de bundles OSGI ou des plugins maven ou des ébauches pour la production de projets JEE ou Android.

Pom Module

Une fois ces pom définit, il reste à orchestrer leur production. Pour cela on utilisera un pom module.


<groupId>org.tc.module</groupId>
<artifactId>tc-parent-module</artifactId>
<name>${project.artifactId}-${project.version}</name>
<version>0.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<description>Pom module de tc-project</description>


De façon très simple, un pom module ne définit que la liste des projets à construire.  Dans le cas de notre dépôt ce pom se constitue donc du pom parent principal et des pom fils au sein du balisage xml module:


<modules>
  <module>tc-parent</module>
  <module>tc-osgi-parent</module>
  <module>tc-jee-parent</module>
  <module>tc-maven-plugin-parent</module>
  <module>tc-android-parent</module>
</modules>


A noter que l’on retrouve souvent les pom parents et les pom modules sous la forme d’un seul et même pom. Si cette configuration est souvent la plus simple lorsque l’on a qu’un seul projet java a réaliser (même constitué par plusieurs sous projet), il n’est pas préconisé de procéder ainsi. En effet comme pour le pattern MVC ou l’on retrouve souvent le contrôleur et la vue implémenter dans le même composant, il est important pour éviter des rework de respecter la règles de la séparation des préoccupations. Ainsi, il sera plus simple de modifier la liste des éléments à construire sans avoir a modifier la version du pom parent et par voie de conséquence tous les pom en héritant.

Enfin dernier point à considérer selon les convenances, pour que le build puisse aboutir dans le cas d’utilisation de la phase deploy de maven, il est nécessaire de permettre au pom module d'être uploader dans un repository distant. Il reste alors a votre choix de soit declarer dans le pom un bloc distributionManagement comme nous l’avons fait pour le pom parent mais cela revient à dupliquer du code ou plus simplement, de déclarer le pom module comme fils du pom parent afin de bénéficier du distributionManagement hérité de celui ci.

Pour plus d’information sur la construction des pom je vous invite a consulter la documentation officielle [7]

References:

[1] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/maven-introduction.html
[2] https://github.com/collonville-tom/tc-parent
[3] https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Packaging
[4] http://matthieu-lux.developpez.com/tutoriels/java/maven/?page=site
[5] http://www.javatronic.fr/articles/2014/08/31/how_to_make_sure_javac_is_using_a_specific_annotation_processor.html
[6] http://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html
[7] https://maven.apache.org/guides/introduction/introduction-to-the-pom.html