Qu'est ce qu'on se fait ch ... !

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

Write once , run everywhere ... sauf sur google app engine

Après la mise à jour des différents composants ( GAE #1.3.5 , primefaces 2.1RC1, ...) me voila reparti à re-développer une appli sur GAE. Après quelques galères tests unitaires, j'ai pu me rendre compte des nombreuses limitations à JPA / GAE.

Les requêtes

D'abord, seul JPA V1 est implémenté. Ça a l'air con comme ca, mais par exemple on ne peut pas utiliser les CriteriaQuery alors que l'API JDO fournie par Google fournit les Filter.Après , me direz vous, c'est pas l'extase, ca ne fait pas exactement la même chose, on peut coder directement les critères en JPQL. Mais voila dès que vous voulez faire des recherches un peu larges avec par exemple un formulaire de recherche à critères multiples, vous pouvez obtenir l'erreur suivante :

testcase: testFindLike(info.touret.mycellar.test.BottleTest):        Caused an ERROR
Problem with query <SELECT A FROM info.touret.winecellar.pojo.Bottle A WHERE A.name like 'BOUTEILLE%'  or A.vintage like '2001%'>: Or filters cannot be applied to multiple properties (found both name and vintage).
org.datanucleus.store.appengine.query.DatastoreQuery$UnsupportedDatastoreFeatureException: Problem with query <SELECT A FROM info.touret.winecellar.pojo.Bottle A WHERE A.name like 'BOUTEILLE%'  or A.vintage like '2001%'>: Or filters cannot be applied to multiple properties (found both name and vintage).
        at org.datanucleus.store.appengine.query.DatastoreQuery.addLeftPrimaryOrExpression(DatastoreQuery.java:1131)
        at org.datanucleus.store.appengine.query.DatastoreQuery.addLeftPrimaryExpression(DatastoreQuery.java:1105)
        at org.datanucleus.store.appengine.query.DatastoreQuery.addPrefix(DatastoreQuery.java:931)
        at org.datanucleus.store.appengine.query.DatastoreQuery.handleMatchesOperation(DatastoreQuery.java:891)
        at org.datanucleus.store.appengine.query.DatastoreQuery.addExpression(DatastoreQuery.java:864)
        at org.datanucleus.store.appengine.query.DatastoreQuery.addExpression(DatastoreQuery.java:835)

Un peu bête ...

Les jointures et fetching

Sur ce sujet, je me suis arraché les cheveux pas mal de temps. Exemple : ne Jointure OneToMany ne me ramenait pas du tout les entités en question lors d'un select. Que faire ?? Après quelques recherches sur la toile, je me suis rendu à l'évidence, que GAE ne gérait que les jointures via les clés primaires. Oubliez les belles jointures bi directionnelles et uni directionnelles JPA . L'insertion , la modification ne peut s'effectuer que par les clés primaires

Exemple avec une relation onetoone

@OneToOne(cascade = {CascadeType.REFRESH})
    private Key producer;

Mais il est possible de "hacker" la matrice en rajoutant un autre attribut à notre classe qui aurait la configuration suivante :

@OneToOne(cascade = {CascadeType.REFRESH})
    @Column(name = "producer", insertable = false, updatable = false)
    private Producer producerAlias;

Donc pour les insertions, suppressions, nous sommes obligés de passer par l'instance de la classe Key, par contre, une sélection passerait par l'alias. Cette manipulation permet de gérer la jointure ( avec fetch !) directement au niveau de la requête JPQL.

Exemple :

Query query = em.createQuery("select from Bottle b join b.producerAlias");

C'est un peu biaisé, mais bon ca simplifie la vie au niveau des requêtes.

Après, ce n'est que mon avis, je me suis trouvé pas mal obligé de dé-normaliser mes relations entre entités. Par exemple, je me suis mis dans l'idée de faire un nuage de tags. Bien au lieu de créer un pojo tag qui serai persisté directement dans big table, j'ai préféré créer un attribut tagline pour mon entité maître. Ca m'a pris moins de temps à créer. après va falloir que j'optimise les requêtes par une gestion de cache par exemple.

Conclusion :

Quand on développe sur GAE, il faut à mon avis bien penser aux contraintes de cette plateforme, surtout sur la persistance des données. Le développeur JAVAEE habitué à hibernate/jpa peut vite pédaler dans la choucroute au début. A mon avis ( et pas que ) JDO est à préférer. l'API semble connaître moins de limites.

Pas mal de plaintes on été faite à ce sujet. Je viens de voir un article sur une recherche full text. A voir ...

Et non je n'ai pas changé

Après quelques jours semaines mois passés à faire autre chose ... mais faut bien vivreeeee.... moi voila revenu sur mon projet de cellier numérique et regardez l'erreur maven que j'obtiens :

Copying webapp resources[D:\java\src\my-cellar\src\main\webapp]
------------------------------------------------------------------------
[ERROR]FATAL ERROR
------------------------------------------------------------------------
Negative time
------------------------------------------------------------------------
Trace
java.lang.IllegalArgumentException: Negative time
        at java.io.File.setLastModified(File.java:1258)
        at org.apache.maven.plugin.war.packaging.AbstractWarPackagingTask.copyFile(AbstractWarPackagingTask.java:295)
        at org.apache.maven.plugin.war.packaging.AbstractWarPackagingTask$1.registered(AbstractWarPackagingTask.java:150)

Si c'est pas un signe pour que je parte en week end....

Pourquoi je suis passé sur Netbeans

Voila quelques raisons qui m'ont fait choisir netbeans comme mon ide de prédilection :

La gestion des plugins est beaucoup plus intégré

Pas besoin d'aller sur un milliard de sites pour récupérer un plugin. Tout est centralisé!

Le support de Maven est natif

Et oui, par rapport à eclipse, le support des projets maven est natif. Quand vous ouvrez un projet maven, netbeans prend en compte automatiquement la configuration des fichiers pom.xml et substitue maven aux builds ant livrés par défaut.

Une seule gestion du classpath

C'est la ou eclipse pêche.... Dans un vrai projet, il y a plusieurs niveaux de classpath à gérer : le classpath pris en compte dans eclipse et celui de maven. Vu que la majorité des problèmes JAVA est lié à la configuration et la gestion du classpath, ca m'économise pas mal de temps.

C'est roots

Oui, eclipse /wtp est peut être trop intégré pour moi, j'aime assez être proche de la configuration cible ( build maven) et exclure de mes résolutions de pb l'IDE.

Ce qui manque encore

  • Pas mal de fonctionnalités d'édition et refactoring où eclipse est encore loin devant. Peut être qu' intellijidea est meilleurs mais bon netbeans est libre et gratuit.
  • Un support facelets digne de ce nom
  • Un support seam :D
  • Une bonne complétion dans les annotations JPA et JSF
  • Un support de tous les frameworks JSF dans l'editeur graphique

Le plugin maven jaxws en action

Voila résumé en quelques lignes les différentes manipulations que j'ai effectué sur mon projet maven pour intégrer des services web:

D'abord j'ai crée un module à part entière qui centralise les sources générés à partir des fichiers WSDL. Dans ce projet de type jar

	<!-- Paramètres généraux -->
    <modelVersion>4.0.0</modelVersion>
    <artifactId>wsclient</artifactId>
    <name>wsclient</name>
    <description />
    <packaging>jar</packaging>
    <version>2.1.2-SNAPSHOT</version>
j'ai attaché à la phase generate-sources la génération des STUBS à partir des fichiers WSDL.

Génération des couches d'appel

La seule configuration "exotique" que j'ai apporté est la configuration par package . Pour faire bref, un package = 1 service web. Logiquement, on n'a pas trop à faire ca car les objets doivent être factorisés. Mais bon vu la techno préhistorique que j'ai en face, je n'ai pas trop le choix

Pour que le plugin jaxws s'exécute bien, il faut faire la configuration suivante :

 <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxws-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>01</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>wsimport</goal>
                        </goals>
                        <configuration>
                            <verbose>true</verbose>
                            <packageName>fr.monpackage.ws.1</packageName>
                            <sourceDestDir>${basedir}/src/main/java</sourceDestDir>
                            <staleFile>${project.build.directory}/jaxws/stale/.stale1Flag</staleFile>
                            <wsdlFiles>
                                <wsdlFile>01.wsdl</wsdlFile>
                            </wsdlFiles>
                        </configuration>
                    </execution>
                    <execution>
                        <id>02</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>wsimport</goal>
                        </goals>
                        <configuration>
                            <verbose>true</verbose>
                            <packageName>fr.monpackage.ws.2</packageName>
                            <staleFile>${project.build.directory}/jaxws/stale/.stale2Flag</staleFile>
                            <sourceDestDir>${basedir}/src/main/java</sourceDestDir>
                            <wsdlFiles>
                                <wsdlFile>02.wsdl</wsdlFile>
                            </wsdlFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Remarque : la personnalisation du fichier stale est obligatoire

Nettoyage des fichiers générés

Pour automatiser la suppression des fichiers générés au moment de l'appel du plugin clean ,j'ai apporté la configuration suivante :

 <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>${basedir}/src/main/java</directory>
                            <includes>
                                <include>**/**</include>
                            </includes>
                        </fileset>
                        <fileset>
                            <directory>${basedir}/src/wsdl</directory>
                            <includes>
                                <include>*.wsdl</include>
                            </includes>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>

Inclusion dans l'EAR

A l'instar des autres JARS, j'ai inclus l'artifact wsclient dans mon ear en tant que MODULE JAR

                        <jarModule>
                           <groupId>monpackage</groupId>
                           <artifactId>wsclient</artifactId>
                           <bundleFileName>wsclient.jar</bundleFileName>
                           <includeInApplicationXml>true</includeInApplicationXml>
                       </jarModule>

...

                   </modules>

Vu que je suis sous JBOSS, je n'ai pas à m'occuper des inclusions de CLASSPATH dans les fichiers MANIFEST :)

Réinitialiser la bonne version d'un projet dans MAVEN

Il arrive - je vous demande un maximum d'imagination :-) - que vous ayez échoué la première exécution de la commande mvn release:prepare et que même après avoir lancé un mvn release:clean, les fichiers pom.xml soient vérolés et n'indiquent pas la bonne version du projet exemple :


<version>2.0.1</version>

et non


<version>2.0.1-SNAPSHOT</version>

Pour rétablir, soit vous le faites à la main dans tous les fichiers. Dans le cas d'un projet JAVA EE, ca peut s'avérer fastidieux ou le faire avec PERL :-) Voici la ligne de commande

 $ find . -name "pom.xml" -exec perl -p -i.old -e "s/2.0.1/2.0.1-SNAPSHOT/g" {}\;

En espérant que ca aidera quelques développeurs malchanceux dans leur versions du vendredi soir !

Deux phénomènes très rares en moins d'une semaine !!

Maven a sorti une nouvelle version 2.10 et Debian Lenny est sorti !

Mulder et Scully, débarquez au plus vite, c'est incroyable!! :-D

Maven vs Kodo vs Cobertura

Dans la série, j'essaye d' intégrer tout et n'importe quoi ensemble.... J'ai un projet java ee sous BEA Weblogic. Ce dernier est JAVA EE 5. Il y a tout ce qu'il existe de plus chatoyant : ejb3, jsf, jpa. Pour ce dernier, le provider par défaut est kodo (bon la c'est moins drôle). Bien évidemment, j'ai plus ou moins réussi à maveniser le tout. Pour la réalisation du site, voici le problème:

Cobertura instrumente via un agent les classes après compilation. Kodo essaye à l'exécution d'instrumenter ces mêmes classes. ---> fatal error.

Voila la manipulation que j'ai réalisé avec l'aide du modérateur de la section java de developpez.com

Exclusion des pojo à l'instrumentation de cobertura

Comme d'habitude, il faut savoir lire la documentation ....


<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>cobertura-maven-plugin</artifactId>
        <configuration>
          <instrumentation>
            <ignores>
              <ignore>com.example.boringcode.*</ignore>
            </ignores>
            <excludes>
              <exclude>com/example/couchejpa/**/*.class</exclude>
              <exclude>com/example/**/*Test.class</exclude>
            </excludes>
          </instrumentation>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>clean</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Passage de l'agent openjpa/kodo a surefire

 <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <forkMode>pertest</forkMode>
          <argLine>-javaagent:D:/kodo-4.1.2/lib/openjpa.jar</argLine>
        </configuration>
      </plugin>

Après vous pourrez vérifier la couverture de tests dans les couches session ( EJB3 SESSION STATELESS et STATEFUL )

Intégrer kodo avec maven surefire

Le développement JPA sous BEA Weblogic impose l'utilisation de BEA KODO comme moteur de persistence.

Si vous voulez faire des tests unitaires sur la réalisation de vos accès bases ( bref le contenu de vos session beans + le CRUD ), vous aurez sans doute à appliquer l' " enhancement" sur vos classes. Cette technique permet à la compilation ou à l'exécution avec un agent java de rendre vos pojo persistants avec kodo ( pour plus de renseignements allez voir du coté de BCEL). Cette technique est hérité de la norme morte née JDO.

lancement de kodo en standalone

Nous aurons donc à l'exécution, le paramètre suivant :

-javaagent:chemin_du_fichier_openjpa.jar

par exemple :

-javaagent:/usr/local/java/lib/kodo-4.1.2/openjpa.jar

Formidable no :-D ?

Avec Maven surefire

Pour lancer les tests unitaires sous maven avec le plugin surefire, nous aurons donc besoin de renseigner cet agent java à l'éxecution du plugin surefire

- <plugin>
  <groupId>org.apache.maven.plugins</groupId> 
  <artifactId>maven-surefire-plugin</artifactId> 
- <configuration>
  <forkMode>pertest</forkMode> 
  <argLine>-javaagent:/usr/local/java/lib/kodo-4.1.2/openjpa.jar</argLine> 
  </configuration>
  </plugin>

Automatiser la création d'un projet avec les archetypes MAVEN

Au démarrage d'un projet, il y a toujours cette tâche fastidieuse : initialiser le projet avec tous les composants choisis par l'architecte, la configuration initiale et le tout prêt à développer et à déployer sur le serveur cible.

Maven propose une solution: les archetypes. Le plugin archetype fournit un ensemble de scripts Velocity permettant de créer rapidement un squelette de projet. Il existe déjà quelques archetypes de projet On pourra y retrouver des exemples de projets J2EE, des portlets. Malheureusement, si on veut faire des projets un peu complexes ou représentant un réel développement en entreprise ( ou les deux :-D ), on a besoin de mettre les mains dans le cambouis.

Création de l'archetype

Définition d'un projet exemple.

Sous eclipse ou netbeans , vim pour les puristes , j'ai crée un projet JAVA EE qui me parait cohérent et évolutif. Je le paramètre pour qu'il ait une configuration mavenisée et hop j'ai mon projet démo

├───ears
│   └───ear
│       ├───.settings
│       └───src
│           └───main
│               └───application
│                   ├───APP-INF
│                   │   ├───classes
│                   │   └───lib
│                   └───META-INF
├───ejbs
│   └───ejb
│       ├───.settings
│       ├───build
│       │   └───classes
│       │       └───META-INF
│       └───src
│           ├───main
│           │   ├───java
│           │   └───resources
│           │       └───META-INF
│           └───test
│               └───resources
│                   └───META-INF
└───guis
    └───front-gui
        ├───.apt_src
        ├───.settings
        ├───build
        │   ├───classes
        │   └───weboutput
        │       └───WEB-INF
        └───src
            └───main
                ├───java
                ├───resources
                │   └───resources
                └───webapp
                    ├───META-INF
                    └───WEB-INF
                        ├───classes
                        ├───config
                        └───lib

Les répertoires .settings contiennent les configuration WTP ou BEA WORKSHOP. Il peut être utile de les mettre dans le template afin d'automatiser la configuration initiale.

Initialisation

Comme l'indique le maven book, j'ai crée le squelette de mon archetype avec .... un archetype :-)

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes \
                     -DarchetypeArtifactId=maven-archetype-archetype \
                     -DarchetypeVersion=1.0 \
                     -DgroupId=com.mycompany \
                     -DartifactId=my-archetype

Création du squelette

Dans le répertoire /src/main/resources/archetype-resource, je met les sources de mon projet démo. J'ai inséré dans les différents fichiers les paramètres nécessaires à la création du squelette:

Dans les fichiers pom.xml :

  • GroupId
  • ArtifactId
  • version

Voici un exemple de fichier pom.xml avec les paramètres velocity :

<project>
	<modelVersion>4.0.0</modelVersion>
	<groupId>${groupId}</groupId>
	<version>${version}</version>
	<artifactId>${artifactId}-parent</artifactId>
	<packaging>pom</packaging>
	<name>${artifactId} project</name>
	<description>Parent Project for ${groupId} / ${artifactId} </description>
	  ....

Je crois que le système est extensible. Vous pouvez rajouter des propriétés à l'execution qui seront automatiquement résolues à la création du squelette.

Paramétrage et définition des meta données

Dans le fichier pom.xml racine ( celui de l'archetype ), mettre les références de l'archetype

Exemple :

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>mon.groupe</groupId>
  <artifactId>mon-archetype</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>maven-plugin</packaging>
</project>

Dans le fichier /src/main/resources/META-INF/maven/archetype.xml indiquer les fichiers à prendre en compte dans le squelette

Exemple :

<archetype>
    <id>mon-archetype</id>
    <resources>
		<!-- Niveau Parent -->
        <resource>pom.xml</resource>
		<!-- Niveau EAR -->
        <resource>ears/pom.xml</resource>
		<resource>ears/ear/pom.xml</resource>
		<resources>ears/ear/.project</resources>
		<resources>ears/ear/.settings/org.eclipse.wst.common.component</resources>
		<resources>ears/ear/.settings/org.eclipse.wst.common.project.facet.core.xml </resources>
		<!-- Niveau WAR -->
		<resource>guis/pom.xml</resource>
		<resource>guis/front-gui/pom.xml</resource>
                  [...]
		<!-- Niveau EJB -->
		<resource>ejbs/pom.xml</resource>
		<resource>ejbs/ejb/pom.xml</resource>
                 [...]
    </resources>
    <sources>
    </sources>
	<allowPartial>true</allowPartial>
</archetype>

Installation et déploiement

A la racine de l'archetype, entrez la commande suivante dans un prompt:

$mvn install 
$mvn deploy

Création du squelette

Dans un répertoire , lancer la commande

$ mvn  archetype:create -DarchetypeGroupId=mon.groupe -DarchetypeArtifactId=
mon-archetype -DarchetypeVersion=1.0-SNAPSHOT -DgroupId=test.package -Dartifac
tId=testprojet -Dversion=12345

Et la, oh miracle de la technologie moderne, on a un beau projet tout beau :-)