Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf

Développement d'un service web de type Rest OSGI multibundle sous Eclipse et déploiement dans Karaf

| Olivier ROZIER | Winjerome |

L'objectif de cet article est de montrer comment développer un service Web de type Rest basé sur JPA sous Eclipse et le déployer sous Karaf. Plus précisément, nous montrerons comment diviser un service web REST OSGI en différentes couches applicatives. Chaque couche sera caractérisée par un module Maven qui constituera un bundle sous Karaf. Sous Eclipse, notre projet sera donc composé de modules et de sous-modules Maven.

Nous montrerons plus concrètement que dans notre précédent tutoriel « Tutoriel pour développer et déployer un service web RESTful sous Eclipse et Karaf », qui constitue les bases du développement d'un service web REST pour Karaf, l'intérêt de OSGI puisque nous développerons un service web REST composé de plusieurs modules, liés entre eux par un couplage lâche. Ce service web REST accédera à une base de données par l'intermédiaire d'une source de données configurée dans Karaf.

Puis nous apprendrons, en fin de tutoriel, comment OSGI permet de gérer plusieurs versions différentes d'un même bundle.

Au cours de ce tutoriel, nous aborderons :

- la manière d'utiliser Eclipse pour découper notre service web REST en plusieurs bundles ;

- la programmation, sous Eclipse, de projets constitués de modules et de sous-modules Maven ;

- l'utilisation de l'archétype Maven « karaf-feature-archetype » qui nous permettra de faciliter le déploiement de notre service web dans Karaf en créant une archive Kar et une « feature » contenant tous les modules composant notre service Web de type Rest. Cette archive permettra également de créer des DataSourceFactory et d'installer les features nécessaires à l'exécution de notre service web ;

- les bases de l'implémentation de JPA par le framework hibernate ;

- l'utilisation de JAXB afin de transformer des données issues d'instances de classes en XML.

Nous vous invitons à lire le tutoriel « Tutoriel pour développer et déployer un service web RESTful sous Eclipse et Karaf » pour la configuration de Maven et pour acquérir les bases du développement d'un service web Rest OSGI pour Karaf.

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum : Commentez

- Titre : Tutoriel pour développer et déployer un service Web RESTFUL OSGI multibundle sous Eclipse et Karaf

- Auteurs : Olivier ROZIER, Winjerome

- Parution : 16 juin 2016

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Olivier ROZIER. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Voir également :

- Journalisation avec SLF4J

- Tutoriel pour développer et déployer un service web RESTful sous Eclipse et Karaf

Sommaire

- I. Liens utiles

- II. OSGI : Rappel

- III. Configuration logicielle

- IV. Mise en place des dépendances entre bundles

- V. Création des différents bundles

- V-A. Création du bundle Web

- V-B. Création du bundle de la couche service métier

- V-C. Création du bundle propre à l'ORM et au modèle

- V-D. Création d'une DataSource en tant que service OSGI

- V-E. Création du bundle Kar

- V-F. Les logs

- V-F-1. Les logs CXF

- V-F-2. Les logs « SLF4J »

- V-F-3. Les logs Hibernate

- VI. Compilation et Build

- VII. Installation et configuration de Karaf

- VIII. Déploiement de l'application dans Karaf

- VIII-A. Préparation

- VIII-B. Déploiement de l'archive Kar

- VIII-C. Déploiement à partir d'une feature

- VIII-D. Création d'une distribution personnalisée de Karaf

- IX. Mise à jour d'un bundle

- X. Code source de l'exemple

- X-A. Création des tables dans la base de données

- X-B. Code JAVA de l'exemple

- XI. Conclusion

- Index

I - Liens utiles

Nous avons profité de ce tutoriel pour aborder les bases de JPA et de JAXB. Aussi, vous verrez, dans les codes sources présentés, des commentaires explicatifs sur le fonctionnement de ces API.

Nous nous sommes appuyé sur le site http://www.javaworld.com/article/2077819/java-se/understanding-jpa-part-2-relationships-the-jpa-way.html pour développer le modèle de données utilisé par JPA dans la couche DAO.

La documentation de JPA est disponible à cette adresse : http://aries.apache.org/modules/jpaproject.html et une introduction à JPA et Hibernate est disponible à cette adresse : http://orm.bdpedia.fr/introjpa.html . À noter également le très bon site : http://blog.paumard.org/cours/jpa/chap03-entite-relation.html .

Concernant JAXB, nous recommandons le lien ci-dessous :

http://blog.paumard.org/cours/jaxb-rest/chap02-jaxb-annotations.html

Concernant la création de l'archive « kar » et l'utilisation des DataSources sous Karaf, nous conseillons le lien ci-dessous :

http://blog.ippon.fr/2015/12/02/tester-ses-commandes-osgi-avec-karaf-et-paxexam/

Les notions de la mise en place d'un service web constitué de plusieurs modules sont disponibles par ce lien : http://aredko.blogspot.fr/2014/08/osgi-gateway-into-micro-services.html

II - OSGI : Rappel

Une application OSGI est un ensemble de modules (bundles) OSGI. Un bundle OSGI est un fichier jar classique contenant des données supplémentaires dans un MANIFEST.

Ce fichier jar est placé sur un serveur OSGI. Dans notre cas, le serveur OSGI sera le serveur d'applications Karaf. Karaf peut être utilisé sur tout système d'exploitation disposant de Java.

Selon les spécifications OSGI, un bundle peut dépendre d'autres bundles. Par conséquent, cela signifie que, la plupart du temps, pour déployer une application OSGI, vous devez d'abord déployer les bundles requis par l'application. Par conséquent, vous devez d'abord trouver ces bundles et les installer. Ces bundles peuvent aussi requérir d'autres bundles pour satisfaire leurs propres dépendances. Les bundles, de par toutes ces dépendances, peuvent donc être compliqués à installer.

Un bundle OSGI est un fichier jar contenant des métadonnées supplémentaires dans le MANIFEST du jar. Dans OSGI, un bundle peut dépendre d'autres bundles. Ce qui signifie que pour déployer une application OSGI, vous devez d'abord déployer beaucoup d'autres bundles requis par l'application.

Afin d'éviter cette tâche fastidieuse, il est préférable d'utiliser une feature.

Une feature décrit une application par :

- un nom ;

- une version ;

- une description (optionnelle) ;

- un ensemble de bundles sur lesquels elle s'appuie ;

- un ensemble de configurations ou de fichiers de configurations (optionnnel) ;

- un ensemble d'autres features dont dépend le projet (optionnel).

Quand on installe une feature, Karaf installe toutes les ressources décrites dans la feature. Il résoudra et installera donc automatiquement les bundles, les configurations et les features dont dépend le projet et qui sont cités dans la feature.

Dans notre cas de figure, afin de faciliter la maintenance et l'évolution d'une application service Web de type Rest, nous diviserons le service Web de type Rest en trois couches (bundles) :

- un bundle Web pour la couche services REST ;

- un bundle pour la couche services métier ;

- un bundle pour la couche d'accès aux données et pour le modèle (DAO).

En procédant ainsi, on profite que OSGI introduit un couplage faible entre bundles pour introduire un couplage faible entre les couches que nous avons définies.

De par OSGI et par l'intermédiaire de Blueprint, on bénéficie de la supervision des bundles et donc de la supervision des couches de notre service Web de type Rest. On a donc un modèle SOA intra JVM !

Afin de faciliter l'installation de ces bundles et tenir compte des dépendances qui existent entre eux, nous créerons un module Maven permettant de packager l'ensemble du projet dans une archive « kar » et un autre module Maven permettant de créer une distribution personnalisée de Karaf, afin de faciliter le déploiement de notre service web REST et ainsi d'éviter de se soucier des dépendances lors de l'installation.

Voici le schéma architectural de notre application :

III - Configuration logicielle

Les logiciels utilisés dans ce tutoriel sont les suivants :

- Eclipse Mars ;

- Karaf 4.0.4 ;

- JDK 1.7.

Les API JAVA utilisées sont :

- JPA 2 ;

- JAXB.

Les frameworks utilisés :

- Hibernate ;

- Apache CXF.

IV - Mise en place des dépendances entre bundles

Dans ce paragraphe, nous allons spécifier les dépendances entre chacun des bundles (Web, Services et accès aux données). Pour ce faire, il est nécessaire de créer un nouveau projet Maven dans lequel nous préciserons que le packaging sera de type « pom ». C'est à l'aide de ce type de packaging que nous pouvons spécifier les dépendances entre les sous-projets qui représenteront, eux, les différentes couches citées plus haut.

1. Créez un nouveau projet par le menu « File » ? « New » ? « Project »

2. Dans la fenêtre qui s'affiche, choisissez « General » puis « Project »

3. Nommez le projet « customerRestFulHibernateWS » puis cliquez sur « Finish ».

4. Cliquez droit sur le projet « customerRestFulHibernateWS » puis choisissez « Configure » ? « Convert to Maven Project »

5. Renseignez la fenêtre qui apparaît de la manière suivante :
« Group Id » avec la valeur « com.exemple.customerRestFulHibernateWS »
« Artefact Id » avec la valeur « customerRestFulHibernateWS »
« Version » avec la valeur « 0.0.1-SNAPSHOT »
« Packaging » avec la valeur « pom »

Cliquez sur « Finish ».

Notre nouveau projet ne contient qu'un fichier « pom.xml ». Ce nouveau projet, par l'intermédiaire du fichier « pom.xml », englobera les sous-projets qui contiennent le code. Voici le contenu provisoire de ce fichier :

pom.xml

1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

2. <modelVersion>4.0.0</modelVersion>

3. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

4. <artifactId>customerRestFulHibernateWS</artifactId>

5. <version>0.0.1-SNAPSHOT</version>

6. <packaging>pom</packaging>

7. </project>

Ce fichier contiendra également toutes les dépendances du projet et leur version. Ainsi, un changement de version de dépendance dans le projet n'impacterait que ce fichier.

N'oubliez pas de compléter ce fichier avec les dépendances en même temps que vous complétez les fichiers « pom.xml » des autres modules avec leurs dépendances !

Pour information, à la fin de notre projet, le fichier « customerRestFulHibernateWS/pom.xml » contiendra ceci :

customerRestFulHibernateWS/pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

3. <modelVersion>4.0.0</modelVersion>

4. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

5. <artifactId>customerRestFulHibernateWS</artifactId>

6. <version>0.0.1-SNAPSHOT</version>

7. <packaging>pom</packaging>

8. <modules>

9. <module>module-jaxrs</module>

10. <module>module-services</module>

11. <module>module-data</module>

12. <module>module-kar</module>

13. </modules>

15. <properties>

16. <cxf.version>3.1.5</cxf.version>

17. <osgi.version>6.0.0</osgi.version>

18. <project.build.outputEncoding>UTF-8</project.build.outputEncoding>

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

20. </properties>

22. <!-- Introduction de la balise <dependencyManagement> qui permet de centraliser les informations de dependances.

23. Quand on a un ensemble de projets qui heritent d'un parent commun, on peut mettre toutes les informations de dependance dans le POM commun

24. et avoir des references plus simples vers les artefacts dans les POM enfants. Ceci permet de consolider et de centraliser la gestion des versions des dependances

25. sans ajouter des dependances qui sont heritees par tous les sous-modules. C'est tres interessant quand on a un ensemble de projets qui heritent

26. d'un meme parent-->

27. <dependencyManagement>

28. <dependencies>

30. <!-- Module utile au "module-jaxrs" -->

31. <dependency>

32. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

33. <artifactId>module-services</artifactId>

34. <version>${project.version}</version>

35. </dependency>

37. <!-- Module utile au "module-jaxrs" et au "module-services" -->

38. <dependency>

39. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

40. <artifactId>module-data</artifactId>

41. <version>${project.version}</version>

42. </dependency>

44. <!-- Dependance pour BundleContext -->

45. <dependency>

46. <groupId>org.osgi</groupId>

47. <artifactId>org.osgi.core</artifactId>

48. <version>${osgi.version}</version>

49. <scope>provided</scope>

50. </dependency>

52. <!-- Dependance necessaire pour le service web REST (GET, POST, PUT, Response, ...) -->

53. <dependency>

54. <groupId>org.apache.cxf</groupId>

55. <artifactId>cxf-rt-frontend-jaxrs</artifactId>

56. <version>${cxf.version}</version>

57. <scope>provided</scope>

58. </dependency>

60. <!-- Dependances necessaires pour la dependance org.apache.cxf/cxf-rt-frontend-jaxrs -->

61. <dependency>

62. <groupId>org.apache.cxf</groupId>

63. <artifactId>cxf-rt-frontend-jaxws</artifactId>

64. <version>${cxf.version}</version>

65. <scope>provided</scope>

66. </dependency>

67. <dependency>

68. <groupId>org.apache.cxf</groupId>

69. <artifactId>cxf-rt-transports-http</artifactId>

70. <version>${cxf.version}</version>

71. <scope>provided</scope>

72. </dependency>

74. <!-- Dependance necessaire pour org.slf4j.Logger -->

75. <dependency>

76. <groupId>org.apache.cxf</groupId>

77. <artifactId>cxf-rt-transports-http-jetty</artifactId>

78. <version>${cxf.version}</version>

79. <scope>provided</scope>

80. </dependency>

82. <!-- Dependance pour org.osgi.service.log.LogService -->

83. <dependency>

84. <groupId>org.osgi</groupId>

85. <artifactId>org.osgi.compendium</artifactId>

86. <version>5.0.0</version>

87. <scope>provided</scope>

88. </dependency>

90. <!-- JPA -->

91. <dependency>

92. <groupId>org.eclipse.persistence</groupId>

93. <artifactId>javax.persistence</artifactId>

94. <version>2.1.1</version>

95. <scope>provided</scope>

96. </dependency>

98. </dependencies>

99. </dependencyManagement>

102. <build>

103. <plugins>

104. <plugin>

105. <groupId>org.apache.maven.plugins</groupId>

106. <artifactId>maven-compiler-plugin</artifactId>

107. <version>3.3</version>

108. <configuration>

109. <compilerVersion>1.7</compilerVersion>

110. <source>1.7</source>

111. <target>1.7</target>

112. </configuration>

113. </plugin>

114. </plugins>

115. </build>

116. </project>

V - Création des différents bundles

V-A - Création du bundle Web

Le premier module que nous allons générer est le module Web qui gérera la couche « services REST ». Ce module dépendra donc du projet « customerRestFulHibernateWS.

Ce bundle correspond à la partie suivante du schéma de notre application :

1. Cliquez droit sur le projet « customerRestFulHibernateWS » ? « New » ? « Project ». Dans la fenêtre qui apparaît, saisissez « maven » dans le filtre, puis choisissez « Maven module ».

Cliquez sur le bouton « Next ».

2. Dans la fenêtre qui apparaît, dans le but de pouvoir choisir un archétype comme nous l'avons fait dans le tutoriel « Développement d'un service Web de type Rest sous Eclipse et déploiement dans Karaf » et donc afin de diminuer notre charge de travail, ne cochez surtout pas la case «  Create a simple project (skip archetype selection) ».
Saisissez le nom de notre module Web. Par exemple : « module-jaxrs ».

Cliquez sur le bouton « Next ».

3. Dans le champ « Filter » de la fenêtre qui apparaît, saisissez « blueprint » puis, dans la liste qui apparaît, choisissez la ligne correspondant à « karaf-blueprint-archetype » puis cliquez sur le bouton « Next ».

4. Renseignez la fenêtre qui apparaît de cette manière :
Group Id : com.exemple.customerRestFulHibernateWS
Artefact Id : module-jaxrs
Version : 0.0.1-SNAPSHOT
Dans la section « Properties available from archetype », attribuez la valeur « com.exemple.customerRestFulHibernateWS.jaxrs » puis cliquez sur le bouton « Finish ».

5. Dans l'explorateur de projets d'Eclipse, nous pouvons voir que notre projet « customerRestFulHibernateWS » a été complété :

Nous constatons désormais deux fichiers « pom.xml » : l'un, que nous avons évoqué lors de la création de notre projet global et l'autre qui est propre au sous-module que nous venons de créer.

Au passage, nous voyons que le fichier « pom.xml » relatif au projet global s'est enrichi de quelques lignes :

La balise « modules » a été ajoutée automatiquement afin de signaler que le premier module attaché à notre projet est le module « module-jaxrs ». Ce module fait donc partie des dépendances du projet.

Comme sur la capture d'écran ci-dessus, nous désignerons désormais ce fichier par « customerRestFulHibernateWS/pom.xml ».

Mais ce n'est pas tout : un nouveau projet nommé « module-jaxrs » a également été créé et il contient tout ce que nous venons de voir dans le projet « customerRestFulHibernateWS ». Notre projet est donc bien relié à notre sous module « module-jaxrs ».

6. Allez dans le projet « module-jaxrs » et, dans le package « com.exemple.customerRestFulHibernateWS.jaxrs », supprimez les fichiers « MyService.java » et « MyServiceImpl.java » par un clic droit sur chacun d'eux puis en sélectionnant « Delete ».
Désormais, le package est vide :

7. Cliquez droit sur ce même répertoire puis sélectionnez « New » ? « Other » et choisissez « Class ». Créez une classe nommée « CustomerRestService ».

8. Complétez le fichier « CustomerRestService.java » de la manière suivante :

module-jaxrs/CustomerRestService.java

1. package com.exemple.customerRestFulHibernateWS.jaxrs;

3. import javax.ws.rs.core.Response;

4. import javax.ws.rs.Consumes;

5. import javax.ws.rs.DELETE;

6. import javax.ws.rs.GET;

7. import javax.ws.rs.POST;

8. import javax.ws.rs.PUT;

9. import javax.ws.rs.Path;

10. import javax.ws.rs.PathParam;

11. import javax.ws.rs.Produces;

13. import org.slf4j.Logger;

14. import org.slf4j.LoggerFactory;

16. import java.util.List;

18. import com.exemple.customerRestFulHibernateWS.services.CustomerService;

19. import com.exemple.customerRestFulHibernateWS.data.model.Customer;

20. import com.exemple.customerRestFulHibernateWS.data.model.Order;

21. import com.exemple.customerRestFulHibernateWS.data.model.Product;

24. /*La classe CustomerRestService est l'implementation d'un service. Elle permet de faire l'interfacage entre le client et la couche metier.

25. *La couche metier sera accessible via l'interface CustomerService du package com.exemple.karaf.services. Pour cette raison, il est necessaire de declarer

26. *une instance de cette interface en tant que membre de la classe CustomerRestService*/

27. public class CustomerRestService

28. {

29. private static final Logger LOG = LoggerFactory.getLogger(CustomerService.class);

31. private CustomerService customerService;

33. public CustomerService getCustomerService()

34. {

35. return customerService;

36. }

38. public void setCustomerService(CustomerService customerService)

39. {

40. this.customerService = customerService;

41. }

44. /**

45. * Cette methode recupere un client a partir de son idCustomer.

46. * Elle est mappee a la requete HTTP GET : "http://localhost:8181/cxf/olivier/customer/{customerId}". La valeur

47. * de {customerId} sera passee en parametre a la methode par utilisation de l'annotation @PathParam.

48. * <p/>

49. * La methode retournera un objet de la classe Customer par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.

50. * <p/>

51. * Exemple d'appel : http://localhost:8181/cxf/olivier/customer/2

52. */

53. @GET

54. @Path("/customer/{id}/")

55. @Produces("application/xml")

56. public Customer getCustomer(@PathParam("id") long customerId)

57. {

58. LOG.info(getClass().getSimpleName() + "[getCustomer({})]", customerId);

60. return customerService.getCustomerById(customerId);

61. }

64. /**

65. * Cette methode met a jour un client a partir d'informations transmises en parametre dans un objet JSON.

66. * Elle est mappee a la requete HTTP PUT http://localhost:8181/cxf/olivier/customer/update.

67. * On peut ainsi envoyer la representation XML d'un objet Customer.

68. * La representation XML sera obtenue par transformation d'un Customer par JAXB.

69. * <p/>

70. * Cette methode met a jour un objet Customer dans notre map locale puis utilise la classe Response pour construire

71. * une reponse HP appropriee : soit OK si la mise a jour a ete effectuee avec succes (Traduction du statut HTTP 200/OK)

72. * ou NON MODIFIE si la mise a jour de l'objet Customer a echoue (Traduction du statut HTTP 304/Not Modified).

73. * <p/>

74. * Exemple d'appel : http://localhost:8181/cxf/olivier/customer/update

75. * avec pour parametre JSON : {"Customer":{"customerId":2, "nom":"Croche","prenom": "Sarah"}}

76. */

77. @PUT

78. @Path("/customer/update")

79. @Consumes({"application/xml", "application/json" })

80. public Response updateCustomer(Customer customer)

81. {

82. LOG.info(getClass().getSimpleName() + "[updateCustomer({})]", customer.getIdCustomer());

84. final Customer c = customerService.getCustomerById(customer.getIdCustomer());

85. Response r;

86. if(c != null)

87. {

88. c.setNom(customer.getNom());

89. customerService.updateCustomer(c);

90. r = Response.ok().build();

91. }

92. else

93. {

94. r = Response.notModified().build();

95. }

97. return r;

98. }

102. /**

103. * Cette methode ajoute une commande a un client dont l'identifiant est fourni dans l'URL. La commande est fournie en parametre dans un objet JSON.

104. * Cette methode est mappee a une requete HTTP PUT de la forme http://localhost:8181/cxf/olivier/customer/{customerId}/order.

105. * On peut ainsi envoyer la representation XML d'un objet Customer.

106. * La representation XML sera obtenue par transformation d'un Customer par JAXB.

107. * <p/>

108. * Cette methode ajoute une commande à un Customer dans notre map locale puis utilise la classe Response pour construire

109. * une reponse HP appropriee : soit OK si la mise a jour a ete effectuee avec succes (Traduction du statut HTTP 200/OK)

110. * ou NON MODIFIE si la mise a jour de l'objet Customer a echoue (Traduction du statut HTTP 304/Not Modified).

111. * <p/>

112. * Exemple d'appel : http://localhost:8181/cxf/olivier/customer/1/order

113. * avec pour parametre JSON : {"Order":{"description":Nouvelle commande, "products": [{"idProduct":1,"description":"Clavier"},{"idProduct":2,"description":"Souris"}]}}

114. */

115. @PUT

116. @Path("/customer/{id}/order")

117. @Consumes({"application/xml", "application/json" })

118. public Response addOrderToCustomer(@PathParam("id") long customerId, Order order)

119. {

120. LOG.info(getClass().getSimpleName() + "[addOrderToCustomer({}, {})]", customerId, order.toString());

122. final Customer c = customerService.getCustomerById(customerId);

123. Response r;

124. if(c != null)

125. {

126. if(customerService.addOrderToCustomer(c, order) != null)

127. {

128. r = Response.ok().build();

129. }

130. else

131. {

132. r = Response.notModified().build();

133. }

134. }

135. else

136. {

137. r = Response.notModified().build();

138. }

139. return r;

140. }

144. /**

145. * Cette methode insere un client fourni en parametre dans un objet JSON.

146. * Elle est mappe a la requete HTTP POST http://localhost:8181/cxf/olivier/customer/add

147. * <p/>

148. * Apres que cette methode a ajoute le client dans la map local, elle utilisera la classe Response pour construire la reponse HTTP

149. * en retournant a la fois l'objet Customer insere et le statut HTTP 200/OK. Ceci permet de recuperer l'ID du nouvel object Customer.

150. *<p/>

151. * Exemple d'appel : http://localhost:8181/cxf/olivier/customer/add

152. * avec pour parametre JSON : {"Customer":{"nom":"Niplancheharepasser","prenom": "Jennifer"}}

153. */

154. @POST

155. @Path("/customer/add")

156. @Consumes({"application/xml", "application/json" })

157. public Response addCustomer(Customer customer)

158. {

159. LOG.info(getClass().getSimpleName() + "[addCustomer({})]", customer.toString());

161. customerService.addCustomer(customer);

163. return Response.ok().type("application/xml").entity(customer).build();

164. }

168. /**

169. * Cette methode permet de supprimer un client dont le nom est fourni en parametre.

170. * Elle est mappee a une requete HTTP DELETE du type : "http://localhost:8181/cxf/olivier/customer/{customerId}".

171. * La valeur pour {id} sera passee en tant que parametre en utilisant l'annotation @PathParam.

172. * <p/>

173. * Cette methode utilise la classe Response pour creer une reponse HTTP : soit le statut HTTP 200/OK si l'objet Customer

174. * a ete correctement supprime de la map local, soit le statut HTTP 304/Not Modified si la suppression a echoue.

175. * <p/>

176. * Exemple d'appel : http://localhost:8181/cxf/olivier/customer/5

177. */

178. @DELETE

179. @Path("/customer/{id}/")

180. public Response deleteCustomer(@PathParam("id") long customerId)

181. {

182. LOG.info(getClass().getSimpleName() + "[deleteCustomer({})]", customerId);

184. final Customer customer = customerService.getCustomerById(customerId);

186. Response r = null;

187. if (customer != null)

188. {

189. customerService.removeCustomer(customerId);

190. r = Response.ok().build();

191. }

192. else

193. {

194. r = Response.notModified().build();

195. }

197. return r;

198. }

201. /**

202. * Cette methode permet de recuperer les commandes d'un client dont l'identifiant est fourni dans la requete.

203. * Elle est mappee a une requete HTTP GET du type : "http://localhost:8181/cxf/olivier/customer/{customerId}/orders".

204. * La valeur pour {id} sera passee en tant que parametre en utilisant l'annotation @PathParam.

205. * <p/>

206. * La methode retournera une liste d'objet de la classe Order par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.

207. * <p/>

208. * Exemple d'appel : http://localhost:8181/cxf/olivier/customer/1/orders

209. */

210. @GET

211. @Path("/customer/{customerId}/orders/")

212. @Produces("application/json")

213. public List<Order> getOrder(@PathParam("customerId") long customerId)

214. {

215. LOG.info(getClass().getSimpleName() + "[getOrder({})]", customerId);

217. return customerService.getCustomerOrders(customerId);

218. }

222. /**

223. * Cette methode permet de recuperer une commande dont l'identifiant est fourni en parametre de meme que l'identifiant du client.

224. * Elle est mappee a une requete HTTP GET du type : "http://localhost:8181/cxf/olivier/customers/{customerId}/order/{orderId}".

225. * <p/>

226. * La methode retournera un objet de la classe Order par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.

227. * <p/>

228. * Exemple d'appel : http://localhost:8181/cxf/olivier/customers/1/order/1

229. */

230. @GET

231. @Path("/customer/{customerId}/order/{orderId}")

232. @Produces("application/xml")

233. public Order getOrder(@PathParam("customerId") long customerId, @PathParam("orderId") long orderId)

234. {

235. LOG.info(getClass().getSimpleName() + "[getOrder({}, {})]", customerId, orderId);

237. return customerService.getCustomerOrder(customerId, orderId);

238. }

241. /**

242. * Cette methode permet de recuperer un produit donne dans une commande donnee d'un client donne.

243. * Les identifiants du produit, de la commande et du client sont fournis en parametres.

244. * Cette methode est mappee a une requete HTTP GET du type : "http://localhost:8181/cxf/olivier/customer/{customerId}/order/{orderId}/product/{productId}".

245. * <p/>

246. * La methode retournera un objet de la classe Product par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.

247. * <p/>

248. * Exemple d'appel : http://localhost:8181/cxf/olivier/customer/1/order/1/product/3

249. */

250. @GET

251. @Path("/customer/{customerId}/order/{orderId}/product/{productId}")

252. @Produces("application/xml")

253. public Product getProduct(@PathParam("customerId") long customerId, @PathParam("orderId") long orderId, @PathParam("productId") long productId)

254. {

255. LOG.info(getClass().getSimpleName() + "[getProduct({}, {}, {})]", customerId, orderId, productId);

257. return customerService.getProduct(customerId, orderId, productId);

258. }

261. /**

262. * Cette methode permet de recuperer tous les produits d'une commande donnee d'un client donne.

263. * Les identifiants du client et de la commande seront fournis dans l'URL.

264. * Cette methode est mappee a une requete HTTP GET du type : "http://localhost:8181/cxf/olivier/customer/{customerId}/order/{orderId}/products".

265. * <p/>

266. * La methode retournera un objet de la classe Product par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.

267. * <p/>

268. * Exemple d'appel : http://localhost:8181/cxf/olivier/customer/1/order/1/products

269. */

270. @GET

271. @Path("/customer/{customerId}/order/{orderId}/products")

272. @Produces("application/xml")

273. public List<Product> getProducts(@PathParam("customerId") long customerId, @PathParam("orderId") long orderId)

274. {

275. LOG.info(getClass().getSimpleName() + "[getProducts({}, {})]", customerId, orderId);

277. return customerService.getProducts(customerId, orderId);

278. }

279. }

Notez la présence des classes « Customer », « Order » et « Product » que nous aborderons dans le paragraphe « V.C Création du bundle propre à l'ORM et au modèleCréation du bundle propre à l'ORM et au modèlepropre à l'ORM et au modèle et au modèleau modèleu modèle ».

Dans le code de la classe CustomerRestService, nous avons introduit une instance de l'interface « CustomerService ». Il s'agit d'une interface que nous n'avons pas encore créée. Et pour cause, elle fera partie du module « module-services » que nous développerons dans le paragraphe « V.B Création du bundle de la couche service MétierMétier ».

De par cette manière de procéder, nous utilisons directement les fonctionnalités de Blueprint qui s'appuie sur les spécifications OSGI.

En effet, en configurant le fichier « module-jaxrs/my-service.xml », nous indiquerons à Blueprint où récupérer le module (bundle) nécessaire à la bonne exécution de notre service web REST « CustomerRestService ».

On voit très nettement, dès lors, l'intérêt du framework Blueprint : il permet de séparer les différentes couches composant la globalité de notre service web. Cette modularité permettra de développer ou de modifier des modules indépendamment les uns des autres et même de les redéployer sans affecter les autres.

9. Le contenu du fichier « module-jaxrs/my-service.xml » situé dans le répertoire « src/main/resources/OSGI-INF/blueprint » spécifiera donc la dépendance du bean « com.exemple.customerRestFulHibernateWS.jaxrs.CustomerRestService » avec l'interface « CustomerService ». Le contenu de ce fichier est le suivant :

module-jaxrs/my-service.xml

1. <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"

2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3. xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"

4. xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"

5. xmlns:cxf="http://cxf.apache.org/blueprint/core"

6. xsi:schemaLocation="

7. http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd

8. http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd

9. http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd

10. http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd">

12. <!-- Configuration des logs CXF afin de faire apparaître les requetes recues et les reponses fournies, dans le journal de Karaf -->

13. <cxf:bus id="bus">

14. <cxf:features>

15. <cxf:logging/>

16. </cxf:features>

17. </cxf:bus>

19. <!-- Configuration du endpoint JAX-RS dans le container OSGI-->

20. <jaxrs:server address="/olivier" id="olivier">

21. <jaxrs:serviceBeans>

22. <ref component-id="customerRestService"/>

23. </jaxrs:serviceBeans>

24. </jaxrs:server>

27. <!-- Declaration du bean customerRestService implemente par le POJO com.exemple.karaf.jaxrs.CustomerRestService-->

28. <!-- Ce bean possede une propriete a injecter. L'injection de cette propriete (ici une instance de la classe CustomerService) est faite

29. immediatement apres la creation du bean  -->

30. <bean id="customerRestService" class="com.exemple.customerRestFulHibernateWS.jaxrs.CustomerRestService">

31. <property name="customerService" ref="customerService"/>

32. </bean>

34. <!-- Definition des dependances -->

35. <reference id="customerService" interface="com.exemple.customerRestFulHibernateWS.services.CustomerService"/>

36. </blueprint>

Vous pouvez voir que nous avons d'ores et déjà introduit l'interface « com.exemple.customerRestFulHibernateWS.services.CustomerService » que nous créerons dans le module « module-services » plus loin dans ce tutoriel.

10. Complétez le fichier « pom.xml » de la manière suivante afin d'y renseigner les packages nécessaires au bon fonctionnement du module :

module-jaxrs/pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

3. <modelVersion>4.0.0</modelVersion>

5. <parent>

6. <artifactId>customerRestFulHibernateWS</artifactId>

7. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

8. <version>0.0.1-SNAPSHOT</version>

9. </parent>

13. <artifactId>module-jaxrs</artifactId>

14. <packaging>bundle</packaging>

15. <name>module-jaxrs Blueprint Bundle</name>

16. <description>module-jaxrs OSGi blueprint bundle project.</description>

18. <properties>

19. <cxf.version>2.7.10</cxf.version>

20. <osgi.version>6.0.0</osgi.version>

21. <project.build.outputEncoding>UTF-8</project.build.outputEncoding>

22. </properties>

24. <dependencies>

25. <dependency>

26. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

27. <artifactId>module-services</artifactId>

28. </dependency>

30. <dependency>

31. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

32. <artifactId>module-data</artifactId>

33. </dependency>

35. <dependency>

36. <groupId>org.osgi</groupId>

37. <artifactId>org.osgi.core</artifactId>

38. <scope>provided</scope>

39. </dependency>

41. <!-- Dependance necessaire pour le service web REST (GET, POST, PUT, Response, ...) -->

42. <dependency>

43. <groupId>org.apache.cxf</groupId>

44. <artifactId>cxf-rt-frontend-jaxrs</artifactId>

45. <scope>provided</scope>

46. </dependency>

48. <!-- Dependance necessaire pour org.slf4j.Logger -->

49. <dependency>

50. <groupId>org.apache.cxf</groupId>

51. <artifactId>cxf-rt-transports-http-jetty</artifactId>

52. <scope>provided</scope>

53. </dependency>

54. </dependencies>

56. <build>

57. <plugins>

58. <plugin>

59. <groupId>org.apache.felix</groupId>

60. <artifactId>maven-bundle-plugin</artifactId>

61. <version>3.0.0</version>

62. <extensions>true</extensions>

63. <configuration>

64. <instructions>

65. <!-- Le fichier MANIFEST.MF sera rempli avec les informations ci-dessous. Pour plus d'informations : http://wiki.osgi.org/wiki/Category:Manifest_Header -->

67. <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName><!-- Nom qui identifie le bundle de maniere unique -->

68. <Bundle-Version>${project.version}</Bundle-Version><!-- Decrit la version du bundle et permet l'activation simultanee de plusieurs versions d'un bundle dans la meme instance de structure -->

69. <Export-Package>com.exemple.customerRestFulHibernateWS.jaxrs*;version=${project.version}</Export-Package><!-- Declare les packages qui sont visibles hors du bundle. Si un package n'est pas declare dans cet en-tete, il n'est visible que dans le bundle -->

70. <Import-Package>*</Import-Package>

71. </instructions>

72. </configuration>

73. </plugin>

74. </plugins>

75. </build>

76. </project>

V-B - Création du bundle de la couche service métier

Nous avons vu, dans le paragraphe ci-dessus, que la classe « CustomerRestService » a besoin, entre autres, de l'interface « CustomerService » pour fonctionner. Cette interface se situera dans le module « module-services » que nous allons créer tout de suite.

Ce bundle correspond à la partie suivante du schéma de notre application :

1. Cliquez droit sur le projet « customerRestFulHibernateWS » ? « New » ? « Project ». Dans la fenêtre qui apparaît, saisissez « maven » dans le filtre puis choisissez « Maven module ».

Cliquez sur le bouton « Next ».

2. Dans la fenêtre qui apparaît, ne cochez toujours pas la case «  Create a simple project (skip archetype selection) ».
Saisissez le nom de notre module Web. Par exemple : « module-services ».

Cliquez sur le bouton « Next ».

3. Dans le champ « Filter » de la fenêtre qui apparaît, saisissez « blueprint » puis, dans la liste qui apparaît, choisissez la ligne correspondant à « karaf-blueprint-archetype » puis cliquez sur le bouton « Next ».

4. Renseignez la fenêtre qui apparaît de cette manière :
Group Id : com.exemple.customerRestFulHibernateWS
Artefact Id : module-services
Version : 0.0.1-SNAPSHOT
Dans la section « Properties available from archetype », attribuez la valeur « com.exemple.customerRestFulHibernateWS.services » puis cliquez sur le bouton « Finish ».

5. Dans l'explorateur de projets d'Eclipse, nous pouvons voir que notre projet « customerRestFulHibernateWS » a été complété :

Le fichier « pom.xml » a été modifié pour y inclure le nouveau module (« module-services »):

On peut également voir qu'un nouveau projet nommé « module-services » a été créé :

6. Supprimez les fichiers « MyService.java » et « MyServiceImpl.java » qui ont été créés par défaut en effectuant un clic droit sur chacun d'entre eux et en choisissant « Delete ».

7. Cliquez droit sur le package « com.exemple.customerRestFulHibernateWS.services » puis choisissez « New » ? « Interface » afin de créer l'interface « CustomerService » que nous avons rencontrée dans la classe « CustomerRestService » du package « com.exemple.customerRestFulHibernateWS.jaxrs » dans le module « Web ».

Cliquez sur « Finish ».

8. Le fichier « CustomerService.java » sera rempli de la manière suivante :

module-services/CustomerService.java

1. package com.exemple.customerRestFulHibernateWS.services;

3. import java.util.List;

5. import com.exemple.customerRestFulHibernateWS.data.model.Customer;

6. import com.exemple.customerRestFulHibernateWS.data.model.Order;

7. import com.exemple.customerRestFulHibernateWS.data.model.Product;

9. public interface CustomerService

10. {

11. Customer getCustomerById(long customerId);

12. Customer addCustomer(Customer customer);

13. Customer updateCustomer(Customer customer);

14. Customer addOrderToCustomer(Customer customer, Order order);

15. void removeCustomer(long customerId);

16. Order getCustomerOrder(long customerId, long orderId);

17. List<Order> getCustomerOrders(long customerId);

18. Product getProduct(long customerId, long orderId, long productId);

19. List<Product> getProducts(long customerId, long orderId);

20. }

9. Cliquez droit sur le projet « module-services » puis choisissez « New » ? « Folder ». Déroulez ensuite l'arborescence des fichiers jusqu'au répertoire « src/main/java/com/exemple/customerRestFulHibernateWS/services » puis créez-y un répertoire « impl » comme indiqué ci-dessous :

Cliquez sur « Finish ». Le package « com.exemple.customerRestFulHibernateWS.services.impl » est désormais créé. Il contiendra l'implémentation de l'interface com.exemple.customerRestFulHibernateWS.services.CustomerService ».

10. Cliquez droit sur le package nouvellement créé puis choisissez « New » ? « Class ». Le nom à renseigner pour cette nouvelle classe sera « CustomerServiceImpl » comme le présente la capture d'écran ci-dessous :

11. Le code à intégrer dans le fichier « CustomerServiceImpl.java » est le suivant :

module-services/CustomerServiceImpl.java

1. package com.exemple.customerRestFulHibernateWS.services.impl;

3. import org.slf4j.Logger;

4. import org.slf4j.LoggerFactory;

6. import java.util.List;

7. import java.util.ArrayList;

9. import com.exemple.customerRestFulHibernateWS.data.model.Customer;

10. import com.exemple.customerRestFulHibernateWS.data.model.Order;

11. import com.exemple.customerRestFulHibernateWS.data.model.Product;

12. import com.exemple.customerRestFulHibernateWS.data.CustomerDao;

13. import com.exemple.customerRestFulHibernateWS.services.CustomerService;

15. public class CustomerServiceImpl implements CustomerService

16. {

17. private CustomerDao customerDao;

18. private static final Logger LOG = LoggerFactory.getLogger(CustomerDao.class);

20. public Customer getCustomerById(long customerId)

21. {

22. LOG.info(getClass().getSimpleName() + "[getCustomerById({})]", customerId);

23. return customerDao.findCustomer(customerId);

24. }

27. public Customer addCustomer(Customer customer)

28. {

29. LOG.info(getClass().getSimpleName() + "[addCustomer({})]", customer.toString());

30. return customerDao.saveCustomer(customer);

31. }

34. public Customer updateCustomer(Customer customer)

35. {

36. LOG.info(getClass().getSimpleName() + "[updateCustomer({})]", customer.toString());

37. return customerDao.saveExistingCustomer(customer);

38. }

41. public Customer addOrderToCustomer(Customer customer, Order order)

42. {

43. LOG.info(getClass().getSimpleName() + "[addOrderToCustomer({}, {})]", customer.toString(), order.toString());

45. /*Si on souhaite inserer un nouveau produit dans la table product quand on insere une commande (c'est un exemple!!!)

46. * on utilise les trois lignes ci-dessous et on modifie la relation

47. * @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)

48. * en

49. * @ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER) dans la classe Order.

50. * De cette maniere, grace à CascadeType.MERGE qui est inclus dans CascadeType.ALL, on inserera dans la table "order",

51. * dans la table "order_detail" et dans la table "product"

52. order.setCustomer(customer);

53. customer.addOrder(order);

54. return customerDao.saveExistingCustomer(customer);

55. */

57. /*

58. * Comme on ne souhaite pas inserer de nouveaux produits dans la table "product" quand on insere une commande,

59. * il nous faut recuperer les entite "Product" correspondant aux donnees fournies en parametres.

60. * Afin de ne pas inserer de nouveau produit dans la table product, on recupere les entites de product existantes

61. */

62. List<Product> listProducts = new ArrayList<Product>();

64. for(Product product: order.getProducts())

65. {

66. Product p = customerDao.findProduct(product.getIdProduct());

67. if(p != null)

68. {

69. listProducts.add(p);

70. }

71. else

72. {

73. //Le produit n'existe pas dans la base de donnees

74. return null;

75. }

76. }

77. order.setProducts(listProducts);

79. order.setCustomer(customer);

80. customer.addOrder(order);

81. return customerDao.saveExistingCustomer(customer);

82. }

84. public void removeCustomer(long customerId)

85. {

86. LOG.info(getClass().getSimpleName() + "[removeCustomer({})]", customerId);

88. customerDao.deleteCustomer(customerId);

89. }

92. public Order getCustomerOrder(long customerId, long orderId)

93. {

94. LOG.info(getClass().getSimpleName() + "[getCustomerOrder({}, {})]", customerId, orderId);

96. Customer customer = customerDao.findCustomer(customerId);

98. for(Order order: customer.getOrders())

99. {

100. if(order.getIdOrder() == orderId)

101. {

102. return order;

103. }

104. }

105. return null;

106. }

109. public List<Order> getCustomerOrders(long customerId)

110. {

111. LOG.info(getClass().getSimpleName() + "[getCustomerOrders({})]", customerId);

113. List<Order> commandes = new ArrayList<Order>();

115. Customer customer = customerDao.findCustomer(customerId);

116. /*JPA retourne l'ensemble des enregistrements qui sont lies entre eux dans les differentes tables. On a quelque chose de ce genre :

117. *     idCustomer, nom, prenom, customer_id, idOrder, idOrder, customer_id, description, order_id, product_id, product_id, description

118. 1;"Rozier";"Olivier";1;1;1;1;"Cmd 1";1;3;3;"Ecran"

119. 1;"Rozier";"Olivier";1;5;5;1;"Cmd 5";5;2;2;"Souris"

120. 1;"Rozier";"Olivier";1;6;6;1;"Cmd 6";6;1;1;"Clavier"

121. 1;"Rozier";"Olivier";1;1;1;1;"Cmd 1";1;1;1;"Clavier"

122. 1;"Rozier";"Olivier";1;1;1;1;"Cmd 1";1;2;2;"Souris"

123. Quand on demande a JPA les commandes liees au client, il voit 5 commandes mais il s'agit en fait de cinq lignes de commandes.

124. Il est donc necessaire de recuperer une seule fois chaque commande afin que JAXB puisse creer un fichier XML sans noeud duplique.

125. */

126. for(Order order: customer.getOrders())

127. {

128. //Recuperation des orders differents

129. if(!commandes.contains(order))

130. {

131. commandes.add(order);

132. }

133. }

135. return commandes;

136. }

139. public Product getProduct(long customerId, long orderId, long productId)

140. {

141. LOG.info(getClass().getSimpleName() + "[getProduct({}, {}, {})]", customerId, orderId, productId);

143. Order order = this.getCustomerOrder(customerId, orderId);

145. for(Product product: order.getProducts())

146. {

147. if(product.getIdProduct() == productId)

148. {

149. return product;

150. }

151. }

153. return null;

154. }

157. public List<Product> getProducts(long customerId, long orderId)

158. {

159. Order order = this.getCustomerOrder(customerId, orderId);

160. return order.getProducts();

161. }

164. public void setCustomerDao( final CustomerDao customerDao)

165. {

166. this.customerDao = customerDao;

167. }

168. }

12. Le contenu du fichier « my-service.xml » situé dans le répertoire « src/main/resources/OSGI-INF/blueprint » devra être le suivant :

module-services/my-service.xml

1. <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"

2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3. xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"

4. xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"

5. xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"

6. xmlns:cxf="http://cxf.apache.org/blueprint/core"

7. xsi:schemaLocation="

8. http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd

9. http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd

10. http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd

11. http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd">

13. <!-- Definition de l'enregistrement du service dans le registre de services OSGI. On renseigne le bean qui fournit le service via l'attribut ref -->

14. <service ref="customerService" interface="com.exemple.customerRestFulHibernateWS.services.CustomerService">

15. <service-properties>

16. <entry key="service.exported.interfaces" value="*" />

17. </service-properties>

18. </service>

20. <!-- Les runtimes d'applications OSGI traitent les services distants differemment de ceux en local a cause de la semantique d'invocation par defaut qui est differente

21. (passage par reference local versus passage par valeur distant). Pour eviter qu'une application appelle accidentellement un service exporte qui est uniquement designe

22. par un appel par "passage de valeur", il faut cacher le lookup local.

23. La solution est d'exporter le meme bean deux fois : une fois pour les appels distants et une autre pour les appels locaux.

24. En d'autres termes, on ajoute un autre element avec la meme configuration mais sans la propriete "service.exported.interfaces"-->

25. <service ref="customerService" interface="com.exemple.customerRestFulHibernateWS.services.CustomerService" />

28. <!-- Declaration du bean customerService implemente par le POJO com.example.services.impl.CustomerServiceImpl-->

29. <!-- Ce bean possede des proprietes a injecter. L'injection de ces proprietes (ici une instance de la classe CustomerDao et une de la classe LogService) est faite

30. immediatement apres la creation du bean  -->

31. <bean id="customerService" class="com.exemple.customerRestFulHibernateWS.services.impl.CustomerServiceImpl">

32. <property name="customerDao" ref="customerDao" />

33. </bean>

35. <!-- Definition des dependances -->

36. <reference id="customerDao" interface="com.exemple.customerRestFulHibernateWS.data.CustomerDao" />

37. </blueprint>

13. Complétez le contenu du fichier « pom.xml » associé au projet « module-data » de la manière suivante :

module-services/pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

3. <modelVersion>4.0.0</modelVersion>

5. <parent>

6. <artifactId>customerRestFulHibernateWS</artifactId>

7. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

8. <version>0.0.1-SNAPSHOT</version>

9. </parent>

11. <artifactId>module-services</artifactId>

12. <packaging>bundle</packaging>

13. <name>module-services Blueprint Bundle</name>

14. <description>module-services OSGi blueprint bundle project.</description>

16. <properties>

17. <cxf.version>2.7.10</cxf.version>

18. <osgi.version>6.0.0</osgi.version>

19. <project.build.outputEncoding>UTF-8</project.build.outputEncoding>

20. </properties>

22. <dependencies>

23. <dependency>

24. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

25. <artifactId>module-data</artifactId>

26. <version>0.0.1-SNAPSHOT</version>

27. <type>bundle</type>

28. </dependency>

30. <dependency>

31. <groupId>org.osgi</groupId>

32. <artifactId>org.osgi.core</artifactId>

33. </dependency>

35. <!-- Dependance necessaire pour org.slf4j.Logger -->

36. <dependency>

37. <groupId>org.apache.cxf</groupId>

38. <artifactId>cxf-rt-transports-http-jetty</artifactId>

39. <scope>provided</scope>

40. </dependency>

41. </dependencies>

43. <build>

44. <plugins>

45. <plugin>

46. <groupId>org.apache.felix</groupId>

47. <artifactId>maven-bundle-plugin</artifactId>

48. <version>3.0.0</version>

49. <extensions>true</extensions>

50. <configuration>

51. <instructions>

52. <!-- Le fichier MANIFEST.MF sera rempli avec les information ci-dessous. Pour plus d'informations : http://wiki.osgi.org/wiki/Category:Manifest_Header -->

54. <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName> <!-- Nom qui identifie le bundle de maniere unique -->

55. <Bundle-Version>${project.version}</Bundle-Version><!-- Decrit la version du bundle et permet l'activation simultanee de plusieurs versions d'un bundle dans la meme instance de structure -->

56. <Export-Package>com.exemple.customerRestFulHibernateWS.services*;version=${project.version}</Export-Package><!-- Declare les packages qui sont visibles hors du bundle. Si un package n'est pas declare dans cet en-tete, il n'est visible que dans le bundle -->

57. <Import-Package>*</Import-Package>

58. </instructions>

59. </configuration>

60. </plugin>

61. </plugins>

62. </build>

63. </project>

V-C - Création du bundle propre à l'ORM et au modèle

Travailler avec JDBC est assez fastidieux, notamment dans le cas d'application SCRUD (Search, Create, Read, Update, Delete), comme celle-ci, dont le développement peut être facilité par un Mapping Objet Relationnel (ORM). Pour cette raison, nous utiliserons JPA 2 afin d'assurer la persistance des données.

JPA est une spécification intégrée à JEE qui permet de standardiser la couche d'association entre une base relationnelle et une application JAVA construite sur des objets.

JPA fait tendre les frameworks d'accès aux données existant, tel hibernate, à devenir des implantations particulières de cette norme.

JPA définit une interface dans le package « javax.persistence.* ».

La définition des associations avec la base de données s'appuie essentiellement sur les annotations Java, ce qui évite de produire de longs fichiers de configuration XML qui doivent être maintenus en parallèle aux fichiers de code (par exemple le fichier « cfg.xml » utilisé jusqu'à présent par Hibernate).

De cette façon, on obtient une manière assez légère de définir une sorte de base de données objet virtuelle qui est matérialisée au cours de l'exécution d'une application par des requêtes SQL produites par le framework sous-jacent.

Dans ce bundle propre à l'ORM et au modèle, nous utiliserons Hibernate comme framework de persistance, tout en respectant JPA autant que possible (notamment par utilisation de ses annotations) afin de faciliter le changement de framework si besoin.

Peut-être vous êtes-vous rendu-compte de l'existence des classes « Customer », « Order » et « Product » dans les classes et interfaces que nous avons vues dans les modules « module-jaxrs » et « module-services ».

Ces classes font partie du modèle de données. Elles seront donc contenues dans le bundle « module-data » au même titre que les classes et interfaces de la couche ORM (CustomerDao et CustomerDaoImpl).

De cette manière, il sera possible de faire évoluer la couche d'accès aux données indépendamment du reste de l'application.

Ce bundle correspond à la partie suivante du schéma de notre application :

La conception de ce module se résume aux étapes suivantes :

1. cliquez droit sur le projet « customerRestFulHibernateWS » ? « New » ? « Project ». Dans la fenêtre qui apparaît, saisissez « maven » dans le filtre puis choisissez « Maven module ».

Cliquez sur le bouton « Next » ;

2. dans la fenêtre qui apparaît, ne cochez toujours pas la case «  Create a simple project (skip archetype selection) ».
Saisissez le nom de notre module Web. Par exemple : « module-data ».

Cliquez sur le bouton « Next » ;

3. dans le champ « Filter » de la fenêtre qui apparaît, saisissez « blueprint » puis, dans la liste qui apparaît, choisissez la ligne correspondant à « karaf-blueprint-archetype » puis cliquez sur le bouton « Next ».

4. renseignez la fenêtre qui apparaît de cette manière :
Group Id : com.exemple.customerRestFulHibernateWS
Artefact Id : module-data
Version : 0.0.1-SNAPSHOT
Dans la section « Properties available from archetype », attribuez la valeur « com.exemple.customerRestFulHibernateWS.data » puis cliquez sur le bouton « Finish ».

5. dans l'explorateur de projets d'Eclipse, nous pouvons voir que notre projet « customerRestFulHibernateWS » a été complété :

Le fichier « pom.xml » a été modifié pour y inclure le nouveau module (« module-data »):

On peut également voir qu'un nouveau projet nommé « module-data » a été créé :

6. supprimez les fichiers « MyService.java » et « MyServiceImpl.java » qui ont été créés par défaut en effectuant un clic droit sur chacun d'entre eux et en choisissant « Delete » ;

7. dans le package « com.exemple.customerRestFulHibernateWS.data », créez une interface nommée « CustomerDao » en cliquant droit sur le package et en sélectionnant « New » ? « Interface ».

8. complétez le fichier « CustomerDao.java » de la manière suivante :

Module-data/CustomerDao.java

1. package com.exemple.customerRestFulHibernateWS.data;

3. import com.exemple.customerRestFulHibernateWS.data.model.Customer;

4. import com.exemple.customerRestFulHibernateWS.data.model.Order;

5. import com.exemple.customerRestFulHibernateWS.data.model.Product;

7. public interface CustomerDao

8. {

9. Customer findCustomer(final long customerId);

10. Customer saveExistingCustomer(final Customer customer);

11. Customer saveCustomer(final Customer customer);

12. void deleteCustomer(final long customerId);

13. Order findOrder(final long orderId);

14. Product findProduct(final long productId);

15. }

9. cliquez droit sur le projet « module-data » puis choisissez « New » ? « Folder ». Déroulez ensuite l'arborescence des fichiers jusqu'au répertoire « data » puis créez-y un répertoire « model » comme indiqué ci-dessous :

Cliquez sur « Finish ». Le package « com.exemple.customerRestFulHibernateWS.data.model » est alors créé. Ce package va contenir les beans « Customer », « Order » et « Product ».

10. Cliquez droit sur le package « com.exemple.customerRestFulHibernateWS.data.model » puis choisissez « New » ? « Class » afin de créer la classe « Customer ». Réitérez l'opération pour créer les classes « Order » et « Product » .
Complétez les fichiers « Customer.java », « Order.java » et « Product.java » de la manière suivante :

module-data/Customer.java

1. package com.exemple.customerRestFulHibernateWS.data.model;

3. //Packages propres a JAXB

4. import javax.xml.bind.annotation.XmlRootElement;

5. import javax.xml.bind.annotation.XmlTransient;

6. import javax.xml.bind.annotation.XmlType;

8. import java.util.List;

10. //Packages propres a JPA

11. import javax.persistence.Entity;

12. import javax.persistence.GeneratedValue;

13. import javax.persistence.GenerationType;

14. import javax.persistence.Id;

15. import javax.persistence.OneToMany;

16. import javax.persistence.FetchType;

17. import javax.persistence.Table;

18. import javax.persistence.CascadeType;

19. import javax.persistence.Column;

22. /**

23. * La classe Customer est un objet JAVA contenant des methodes get et set

24. * <p/>

25. * En ajoutant l'annotation @XmlRootElement, nous offrons la possibilite a JAXB de transformer cet objet en document XML et inversement.

26. * <p/>

27. * La representation XML d'un customer ressemblera a ceci :

28. * <Customer>

29. * <idCustomer>123</idCustomer>

30. * <prenom>Olivier</prenom>

31. * <nom>Rozier</nom>

32. * </Customer>

33. */

34. @XmlRootElement(name = "Customer")

35. @XmlType(propOrder={"idCustomer", "prenom", "nom"}) //Fixe l'ordre des champs dans le fichier XML

36. @Entity

37. @Table(name="customer",schema="ctm")

38. public class Customer

39. {

40. @Id //specifie la cle primaire

41. @Column(name = "idCustomer", nullable = false)

42. @GeneratedValue(strategy = GenerationType.IDENTITY)

43. private long idCustomer;

45. @Column(name = "prenom", length = 50)

46. private String prenom;

48. @Column(name = "nom", length = 50)

49. private String nom;

51. //FetchType.LAZY indique a la JPA runtime qu'on souhaite reporter le chargement des champs au moment de l'acces

52. //Ceci est completement transparent : les donnees sont chargees depuis la base de donnees dans les objets discretement lorsqu'on tente de lire

53. //les champs pour la premiere fois.

54. //FetchType.EAGER indique que quel que soit le moment ou on recupere une entite depuis une requete ou depuis l'entity manager, on a la garantie

55. //que tous les champs sont renseignes.

57. @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="customer")

58. private List<Order> orders;

60. public long getIdCustomer()

61. {

62. return idCustomer;

63. }

65. public void setIdCustomer(long idCustomer)

66. {

67. this.idCustomer = idCustomer;

68. }

70. public String getNom()

71. {

72. return nom;

73. }

75. public void setNom(String nom)

76. {

77. this.nom = nom;

78. }

80. public String getPrenom()

81. {

82. return prenom;

83. }

85. public void setPrenom(String prenom)

86. {

87. this.prenom = prenom;

88. }

90. @XmlTransient //Permet d'eviter de faire une boucle infinie a la generation du XML

91. public List<Order> getOrders()

92. {

93. return orders;

94. }

96. public void setOrders(List<Order> orders)

97. {

98. this.orders = orders;

99. }

101. public void addOrder(Order order)

102. {

103. this.orders.add(order);

104. }

106. @Override

107. public String toString()

108. {

109. return getClass().getSimpleName() + "[idCustomer=" + idCustomer + ", nom=" + nom + ", prenom=" + prenom + "]";

110. }

111. }

module-data/Order.java

1. package com.exemple.customerRestFulHibernateWS.data.model;

3. import java.util.List;

5. //Packages propres a JAXB

6. import javax.xml.bind.annotation.XmlRootElement;

7. import javax.xml.bind.annotation.XmlAccessorType;

8. import javax.xml.bind.annotation.XmlAccessType;

9. import javax.xml.bind.annotation.XmlTransient;

10. //Packages propres a JPA

11. import javax.persistence.Entity;

12. import javax.persistence.GeneratedValue;

13. import javax.persistence.GenerationType;

14. import javax.persistence.Id;

15. import javax.persistence.Table;

16. import javax.persistence.Column;

17. import javax.persistence.JoinColumn;

18. import javax.persistence.JoinTable;

19. import javax.persistence.FetchType;

20. import javax.persistence.ManyToOne;

21. import javax.persistence.ManyToMany;

22. import javax.persistence.CascadeType;

25. /**

26. * La classe OrderBean est un objet JAVA contenant des methodes get et set et est aussi utilisee pour les commandes retournees par

27. * la classe CustomerServiceImpl.

28. * <p/>

29. * En ajoutant l'annotation @XmlRootElement, nous offrons la possibilite a JAXB de transformer cet objet en document XML et inversement.

30. * <p/>

31. * La representation XML pour un OrderBean est la suivante :

32. * <Order>

33. * <idOrder>223</idOrder>

34. * <description>Order 223</description>

35. * </Order>

36. */

38. @XmlRootElement(name = "Order")

39. //@XmlAccessorType(XmlAccessType.FIELD) est obligatoire car le binding par defaut de JAXB est XmlAccessType.PUBLIC_MEMBER.

40. //Donc, dans ce cas, JAXB tentera de binder tous les champs publics et les champs et proprietes annotees. Il tenterait donc de binder le champ "products"

41. //et la methode "getProducts()". En utilisant @XmlAccessorType(XmlAccessType.FIELD), le binding ne se fait qu'une fois.

42. @XmlAccessorType(XmlAccessType.FIELD)

43. @Entity

44. @Table(name="order", schema="ctm")

45. public class Order

46. {

47. @Id

48. @Column(name = "idOrder", nullable = false)

49. @GeneratedValue(strategy = GenerationType.IDENTITY)

50. private long idOrder;

52. private String description;

55. //Chaque commande doit avoir un client associe donc optional=false

56. @ManyToOne(optional=false)

57. @JoinColumn(name="customer_id", referencedColumnName="idcustomer", nullable=false)

58. private Customer customer;

60. //On utilise une table nommee "order_detail" pour modeliser l'association entre la table "order" et la table "product".

61. //L'annotation @JoinTable est utilisee pour specifier une table de la base de donnees qui associe "order_id" avec "product_id".

62. //L'entite qui specifie le @JoinTable est le proprietaire de la relation. Dans notre cas, l'entite Order est le proprietaire de la relation

63. //avec l'entite Product.

64. @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)

65. @JoinTable(name="order_detail",

66. joinColumns=@JoinColumn(name="order_id", referencedColumnName="idOrder"),

67. inverseJoinColumns=@JoinColumn(name="product_id", referencedColumnName="idProduct"),

68. schema="ctm"

69. )

70. private List<Product> products;

73. public Customer getCustomer() {

74. return customer;

75. }

77. public void setCustomer(Customer customer)

78. {

79. this.customer = customer;

80. }

82. public long getIdOrder()

83. {

84. return idOrder;

85. }

87. public void setIdOrder(long idOrder)

88. {

89. this.idOrder = idOrder;

90. }

92. public String getDescription()

93. {

94. return description;

95. }

97. public void setDescription(String d)

98. {

99. this.description = d;

100. }

102. @XmlTransient //Permet d'eviter de faire une boucle infinie a la generation du XML

103. public List<Product> getProducts()

104. {

105. return products;

106. }

108. public void setProducts(List<Product> products)

109. {

110. this.products = products;

111. }

113. @Override

114. public String toString()

115. {

116. return getClass().getSimpleName() + "[idOrder=" + idOrder + ", desription=" + description + "]";

117. }

118. }

La relation « ManyToMany » définie par l'annotation JPA « @ManyToMany » que nous voyons dans la classe « Order » ci-dessus nécessite, comme toute relation « ManyToMany », une table associative annotée « @JoinTable ». Dans notre cas, cette table associative porte le nom « order_detail » dans la base de données.

Le cas de figure présenté ci-dessus constitue un cas simple dans lequel la table associative n'a pas de champs supplémentaire : elle n'est constituée que des identifiants des deux autres tables.

Un cas de figure fréquent est le cas où deux classes ont une relation « ManyToMany » et que la table associative possède des champs supplémentaires. Dans notre cas, ceci se serait produit si la table « order_detail » possédait une colonne « quantity » en plus des colonnes « order_id » et « product_id ».

Dans ce cas, la meilleure solution est de créer une nouvelle classe qui modélise la table associative. On abandonne, dès lors, l'annotation « @JoinTable ». Voici ce que cela donnerait avec notre exemple :

module-data/Order.java

1. @Entity

2. @Table(name="order", schema="ctm")

3. public class Order

4. {

5. ...

6. @OneToMany(mappedBy="order")

7. private List<OrderDetail> products;

8. ...

9. }

module-data/Product.java

1. @Entity

2. @Table(name="product", schema="ctm")

3. public class Product

4. {

5. ...

6. @OneToMany(mappedBy="product")

7. private List<OrderDetail> orders;

8. ...

9. }

10. //Ajout d'une commande (a mettre dans la couche Service Metier)

11. public void addOrder(Order order, int quantity)

12. {

13. OrderDetail od = new OrderDetail();

14. od.setOrder(order);

15. od.setProduct(this);

16. od.setOrderId(order.getIdOrder());

17. od.setProductId(this.getIdProduct());

18. od.setQuantity(quantity) ;

19. this.orders.add(od);

20. order.getProducts().add(od);

21. ...

22. }

module-data/OrderDetail.java

1. @Entity

2. @Table(name="order_detail", schema="ctm")

3. @IdClass(OrderDetailId.class)

4. public class OrderDetail

5. {

6. @Id

7. @Column(name = "order_id")

8. private long orderId;

10. @Id

11. @Column(name = "product_id")

12. private long productId;

14. @column(name= "quantity")

15. private int quantity;

17. @ManyToOne

18. @JoinColumn(name="order_id", updatable = false, insertable = false, referencedColumnName = "idOrder")

19. private Order order;

21. @JoinColumn(name="product_id", updatable = false, insertable = false, referencedColumnName = "idProduct")

22. private Product product;

23. ...

24. //completez avec les setter et les getter propres aux proprietes orderId, productId, quantity, order et product

25. ...

27. }

module-data/orderDetailId

1. @Entity

2. public class OrderDetailId implements Serializable

3. {

4. private static final long serialVersionUID = 315964054054L;

6. private long orderId;

8. private long productId;

9. public OrderDetailId() {}

11. public OrderDetailId(long orderId, long productId)

12. {

13. this.orderId = orderId;

14. this.productId = productId;

15. }

17. //Il n'y a pas de methode set car la cle primaire ne doit pas etre changee

19. public long getOrderId()

20. {

21. return orderId;

22. }

24. public long getProductId()

25. {

26. return productId;

27. }

28. //On doit surcharger les methodes equals() et hashcode() car les objets cle primaire ont besoin d'etre identifies de maniere unique.

29. //On peut les utiliser dans des collections en utilisant le hashage.

30. @Override

31. public int hashCode()

32. {

33. return (int)(orderId + productId);

34. }

36. @Override

37. public boolean equals(Object object)

38. {

39. if (object instanceof OrderDetailId)

40. {

41. OrderDetail otherId = (OrderDetail) object;

42. return (otherId.orderId == this.orderId) && (otherId.productId == this.productId);

43. }

44. return false;

45. }

47. @Override

48. public String toString()

49. {

50. return getClass().getSimpleName() + "[seId=" + seId + ", applicationId=" + applicationId + ", typeFluxId=" + typeFluxId + ", etatFluxId=" + etatFluxId + "]";

51. }

53. }

La solution ci-dessus peut également s'appliquer dans le cas où la clé primaire de la table associative est constituée de plus de deux clés étrangères.

module-data/Product.java

1. package com.exemple.customerRestFulHibernateWS.data.model;

3. import java.util.List;

5. //Packages propres a JAXB

6. import javax.xml.bind.annotation.XmlRootElement;

7. import javax.xml.bind.annotation.XmlTransient;

8. import javax.xml.bind.annotation.XmlType;

10. //Packages propres a JPA

11. import javax.persistence.Entity;

12. import javax.persistence.GeneratedValue;

13. import javax.persistence.GenerationType;

14. import javax.persistence.Column;

15. import javax.persistence.Id;

16. import javax.persistence.Table;

17. import javax.persistence.ManyToMany;

18. import javax.persistence.FetchType;

20. /**

21. * La classe Product est un objet JAVA contenant des methodes get et set

22. * <p/>

23. * En ajoutant l'annotation @XmlRootElement, nous offrons la possibilite a JAXB de transformer cet objet en document XML et inversement.

24. * <p/>

25. * La representation XML d'un Product ressemblera a ceci :

26. *

27. * <Product>

28. * <idProduct>10010</idProduct>

29. * <description>produit 1</description>

30. * </Product>

31. */

33. @XmlRootElement(name = "Product")

34. @XmlType(propOrder={"idProduct", "description"}) //Fixe l'ordre des champs dans le fichier XML

35. @Entity

36. @Table(name="product",schema="ctm")

37. public class Product

38. {

39. @Id

40. @Column(name = "idProduct", nullable = false)

41. @GeneratedValue(strategy = GenerationType.IDENTITY)

42. private Long idProduct;

45. @Column(name = "description", length = 200)

46. private String description;

49. @ManyToMany(mappedBy="products",fetch=FetchType.EAGER)

50. private List<Order> orderList;

53. public long getIdProduct()

54. {

55. return idProduct;

56. }

58. public void setIdProduct(long idProduct)

59. {

60. this.idProduct = idProduct;

61. }

63. public String getDescription()

64. {

65. return description;

66. }

68. public void setDescription(String d)

69. {

70. this.description = d;

71. }

73. @XmlTransient //Permet d'eviter de faire une boucle infinie a la generation du XML

74. public List<Order> getOrderList()

75. {

76. return orderList;

77. }

79. public void setOrderList(List<Order> orderList)

80. {

81. this.orderList = orderList;

82. }

83. }

11. cliquez droit sur le projet « module-data » puis choisissez « New » ? « Folder ». Déroulez ensuite l'arborescence des fichiers jusqu'au répertoire « data » puis créez-y un répertoire « impl » comme indiqué ci-dessous :

Cliquez sur « Finish ». Le package « com.exemple.customerRestFulHibernateWS.data.impl » est alors créé. Ce package va contenir l'implémentation de l'interface « CustomerDao » ;

12. cliquez droit sur le package «  com.exemple.customerRestFulHibernateWS.data.impl » puis choisissez « New » ? « Class » afin de créer la classe « CustomerDaoImpl ».

13. complétez le fichier « CustomerDaoImpl.java » de la manière suivante :

Module-data/CustomerDaoImpl.java

1. package com.exemple.customerRestFulHibernateWS.data.impl;

3. import javax.persistence.EntityManager;

4. import javax.persistence.PersistenceContext;

6. import com.exemple.customerRestFulHibernateWS.data.CustomerDao;

7. import com.exemple.customerRestFulHibernateWS.data.model.Customer;

8. import com.exemple.customerRestFulHibernateWS.data.model.Order;

9. import com.exemple.customerRestFulHibernateWS.data.model.Product;

11. import org.osgi.service.log.LogService;

13. public class CustomerDaoImpl implements CustomerDao

14. {

15. //@PersistenceContext permet de recuperer l'entityManager. Le unitName est celui fourni dans le fichier persistence.xml (dans la balise <persistence-unit name="customerDb" transaction-type="JTA">)

16. @PersistenceContext(unitName = "customerDb")

17. private EntityManager entityManager;

18. private LogService logService;

21. public void setEntityManager( final EntityManager entityManager )

22. {

23. this.entityManager = entityManager;

24. }

26. @Override

27. public Customer findCustomer(final long customerId)

28. {

29. logService.log(LogService.LOG_INFO, getClass().getSimpleName() + "[findCustomer(" + customerId + ")]");

31. return entityManager.find(Customer.class, customerId);

32. }

35. @Override

36. public Customer saveCustomer(final Customer customer)

37. {

38. //On rend l'entite persistante. C'est-a-dire qu'on l'ecrit en base sur le prochain commit de la transaction dans laquelle on se trouve

39. logService.log(LogService.LOG_INFO, getClass().getSimpleName() + "[saveCustomer(" + customer.toString() + ")]");

41. entityManager.persist(customer);

42. return customer;

43. }

45. @Override

46. public Customer saveExistingCustomer(final Customer customer)

47. {

48. //On attache l'entite a l'Entity Manager courant.

49. logService.log(LogService.LOG_INFO, getClass().getSimpleName() + "[saveExistingCustomer(" + customer.toString() + ")]");

51. entityManager.merge(customer);

52. return customer;

53. }

55. @Override

56. public void deleteCustomer(final long customerId)

57. {

58. //On rend l'entite non persistante. Une entite rendue non persistante sera effacee de la base sur le prochain commit de la transaction dans laquelle on se trouve

59. logService.log(LogService.LOG_INFO, getClass().getSimpleName() + "[deleteCustomer(" + customerId + ")]");

61. entityManager.remove(findCustomer(customerId));

62. }

64. @Override

65. public Order findOrder(final long orderId)

66. {

67. logService.log(LogService.LOG_INFO, getClass().getSimpleName() + "[findOrder(" + orderId + ")]");

69. return entityManager.find(Order.class, orderId);

70. }

72. @Override

73. public Product findProduct(final long productId)

74. {

75. logService.log(LogService.LOG_INFO, getClass().getSimpleName() + "[findProduct(" + productId + ")]");

77. return entityManager.find(Product.class, productId);

78. }

80. public void setLogService( final LogService logService )

81. {

82. this.logService = logService;

83. }

85. }

14. complétez le contenu du fichier « pom.xml » associé au projet « module-data » de la manière suivante :

module-data/pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

3. <modelVersion>4.0.0</modelVersion>

4. <parent>

5. <artifactId>customerRestFulHibernateWS</artifactId>

6. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

7. <version>0.0.1-SNAPSHOT</version>

8. </parent>

10. <artifactId>module-data</artifactId>

11. <packaging>bundle</packaging>

12. <name>module-data Blueprint Bundle</name>

13. <description>module-data OSGi blueprint bundle project.</description>

15. <properties>

16. <project.build.outputEncoding>UTF-8</project.build.outputEncoding>

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

18. </properties>

20. <dependencies>

22. <!-- Dependance pour BundleContext -->

23. <dependency>

24. <groupId>org.osgi</groupId>

25. <artifactId>org.osgi.core</artifactId>

26. <scope>provided</scope>

27. </dependency>

29. <!-- JPA -->

30. <dependency>

31. <groupId>org.eclipse.persistence</groupId>

32. <artifactId>javax.persistence</artifactId>

33. <scope>provided</scope>

34. </dependency>

36. <!-- Dependance pour org.osgi.service.log.LogService -->

37. <dependency>

38. <groupId>org.osgi</groupId>

39. <artifactId>org.osgi.compendium</artifactId>

40. </dependency>

42. </dependencies>

45. <build>

46. <plugins>

47. <plugin>

48. <groupId>org.apache.felix</groupId>

49. <artifactId>maven-bundle-plugin</artifactId>

50. <version>3.0.0</version>

51. <extensions>true</extensions>

52. <configuration>

53. <instructions>

54. <JPA-PersistenceUnits>customerDb</JPA-PersistenceUnits>

55. <!-- Le fichier MANIFEST.MF sera rempli avec les informations ci-dessous. Pour plus d'informations : http://wiki.osgi.org/wiki/Category:Manifest_Header -->

57. <!-- La balise <Meta-Persistence> pointe sur le fichier "persistence.xml" afin que JPA cree une EntityManagerFactory

58. pour ce bundle-->

59. <Meta-Persistence>META-INF/persistence.xml</Meta-Persistence> <!-- Valable car c'est un bundle de persistance. Repertorie tous les emplacements des fichiers persistence.xml dans le bundle de persistance -->

60. <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName><!-- Nom qui identifie le bundle de maniere unique -->

61. <Bundle-Version>${project.version}</Bundle-Version><!-- Decrit la version du bundle et permet l'activation simultanee de plusieurs versions d'un bundle dans la meme instance de structure -->

62. <Export-Package>com.exemple.customerRestFulHibernateWS.data*;version=${project.version}</Export-Package><!-- Declare les packages qui sont visibles hors du bundle. Si un package n'est pas declare dans cet en-tete, il n'est visible que dans le bundle -->

63. <Import-Package>

64. <!-- Necessaire pour le proxy de Javassist sous peine d'avoir le message "java.lang.RuntimeException: by java.lang.NoClassDefFoundError: org/hibernate/proxy/HibernateProxy" -->

65. org.hibernate.proxy,javassist.util.proxy,*

66. </Import-Package>

67. </instructions>

68. </configuration>

69. </plugin>

70. </plugins>

71. </build>

72. </project>

1. Dans le fichier « module-data/pom.xml », remarquez les lignes « <JPA-PersistenceUnits>customerDb</JPA-PersistenceUnits> » et « <Meta-Persistence>META-INF/persistence.xml</Meta-Persistence> ».
La balise « JPA-PersistenceUnits » constitue l'entête OSGI indiquant l'identifiant de la persistence unit. Il s'agit du nom de la DataSource.
Ce nom apparaît dans la balise « jta-data-source » du fichier « module-data/persistence.xml ».

15. complétez le contenu du fichier « src/main/resources/OSGI/blueprint/my-service.xml de la manière suivante :

module-data/my-service.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"

3. xmlns:jpa="http://aries.apache.org/xmlns/jpa/v2.0.0"

4. xmlns:tx="http://aries.apache.org/xmlns/transactions/v1.2.0"

5. default-activation="lazy">

7. <!-- On active JPA -->

8. <jpa:enable />

10. <!-- Definition de l'enregistrement du service dans le registre de services OSGI. On renseigne le bean qui fournit le service via l'attribut ref -->

11. <service ref="customerDao" interface="com.exemple.customerRestFulHibernateWS.data.CustomerDao">

12. <service-properties>

13. <entry key="service.exported.interfaces" value="*" />

14. </service-properties>

15. </service>

17. <!-- Les runtimes d'applications OSGI traitent les services distants differemment de ceux en local a cause de la semantique d'invocation par defaut qui est differente

18. (passage par reference local versus passage par valeur distant). Pour eviter qu'une application appelle accidentellement un service exporte qui est uniquement designe

19. par un appel par "passage de valeur", il faut cacher le lookup local.

20. La solution est d'exporter le meme bean deux fois : une fois pour les appels distants et une autre pour les appels locaux.

21. En d'autres termes, on ajoute un autre element avec la meme configuration mais sans la propriete "service.exported.interfaces"-->

22. <service ref="customerDao" interface="com.exemple.customerRestFulHibernateWS.data.CustomerDao"/>

24. <!-- Publication du bean en tant que service OSGI-->

25. <!-- Ce bean possede des proprietes a injecter. L'injection de ces proprietes (ici la propriete entityManager) est faite

26. immediatement apres la creation du bean  -->

27. <bean id="customerDao" class="com.exemple.customerRestFulHibernateWS.data.impl.CustomerDaoImpl">

28. <property name="logService" ref="logService" />

30. <!-- Etablissement des transactions pour toutes les methodes DAO. L'application n'a pas besoin de creer l'entity manager que nous avons declare.

31. La OSGI runtime creera cet entity manager et l'injectera partout ou il est requis. la balise "<tx:transaction>" permet de demander au runtime

32. d'envelopper les methodes dans une transaction.-->

33. <tx:transaction method="*" value="Required"/>

34. </bean>

36. <!-- Definition des dependances -->

37. <reference id="logService" interface="org.osgi.service.log.LogService" />

39. </blueprint>

16. créez le répertoire « META-INF » via un clic droit sur le projet « module-data » ? « New » ? « Folder » puis en descendant dans l'arborescence tel que le montre la capture d'écran ci-dessous :

Cliquez sur « Finish » ;

17. faites un clic droit sur ce nouveau répertoire puis choisissez « New » ? « File » et nommez le fichier que vous êtes en train de créer « persistence.xml ».

Cliquez sur le bouton « Finish ».

Ce fichier « persistence.xml » contiendra la configuration nécessaire à JPA et Hibernate ;

18. complétez le fichier « persistence.xml » de la manière suivante :

module-data/persistence.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"

3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence

5. http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"

6. version="2.1">

8. <!-- ******************************************************************* -->

9. <!-- JTA (Java Transaction API). Elle fournit des interfaces Java standards entre un gestionnaire de transaction et les differentes parties

10. impliquees dans un systeme de transactions distribuees : le gestionnaire de ressources, le serveur d'application et les applications transactionnelles.

11. JTA est un protocole de commit a deux phases :

12. - 1re phase : chaque partie prenant part a la transaction distribuee s'engage à verrouiller les donnees concernees et a valider ces donnees une fois la transaction terminee

13. - 2e phase : chaque partie valide les changements des donnees. Cette phase est obligatoire, des lors que les parties se sont engagees.

15. Ce protocole de commit a deux phases fonctionne plutot bien sur les transactions courtes, mais est totalement inefficace en cas de transaction lente

16. ou le risque d'une deconnexion ou bien d'un crash entre les deux phases est eleve, car les verrous restent poses apres la premiere phase et ne sont liberes

17. qu'apres la deuxieme phase.                                              -->

18. <!-- ******************************************************************* -->

20. <persistence-unit name="customerDb" transaction-type="JTA">

21. <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

23. <!-- On precise l'ensemble des classes qui sont gerees par l'entityManager dans l'application -->

24. <class>com.exemple.customerRestFulHibernateWS.data.model.Customer</class>

25. <class>com.exemple.customerRestFulHibernateWS.data.model.Order</class>

26. <class>com.exemple.customerRestFulHibernateWS.data.model.Product</class>

27. <!-- On exclut les classes qui ne sont pas listees ci-dessus -->

28. <exclude-unlisted-classes>true</exclude-unlisted-classes>

29. <!-- On specifie le nom global JNDI de la datasource qui sera utilisee par le container.

30. Ce nom est la valeur de la propriete "osgi.jndi.service.name" fournie par la commande Karaf "service:list DataSource"-->

31. <jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=customerDb)</jta-data-source>

34. <properties>

36. <!-- La propriete hibernate.dialect definit le dialecte SQL de notre base de donnees.Hibernate s'appuiera sur ce dialect

37. pour optimiser l'execution des requetes en utilisant les proprietes specifiques a la base de donnees.

38. Dans notre cas, on utilisera le dialect de PostgreSQL-->

39. <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>

41. <!-- La propriete  hibernate.hbm2ddl.auto=none specifie que le schema de la table n'est pas automatiquement cree quand l'application est deployee.

42. Si la propriete a la valeur "create-drop", les tables de la base de donnees sont creees au deploiement de l'application et supprimees lors de l'undeployed ou de l'arret du serveur.

43. Si la propriete a la valeur "update", le schema de la base de donnees est mis a jour ou cree, mais son contenu n'est pas supprime.

44. En production, un utilisateur n'a que tres rarement le privilege de creer ou supprimer des tables.-->

45. <property name="hibernate.hbm2ddl.auto" value="none"/>

47. <!-- On log les requetes executees par hibernate pour faciliter le debogage -->

48. <property name="hibernate.show_sql" value="true"/>

50. <!-- On met des commentaires dans toutes les requetes SQL generees pour faciliter la comprehension des requetes-->

51. <property name="use_sql_comments" value="true"/>

53. </properties>

54. </persistence-unit>

55. </persistence>

On remarque la propriété <property name="hibernate.show_sql" value="true"/>. Cette propriété permet d'afficher les logs d'hibernate dans la console de Karaf. Ceci est utile dans le cas d'un débogage. Il convient d'attribuer la valeur false à l'attribut « value » en production car Hibernate est très bavard !

19. enfin, complétez le contenu du fichier « customerRestFulHibernateWS/pom.xml » de la manière suivante :

customerRestFulHibernateWS/pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

3. <modelVersion>4.0.0</modelVersion>

4. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

5. <artifactId>customerRestFulHibernateWS</artifactId>

6. <version>0.0.1-SNAPSHOT</version>

7. <packaging>pom</packaging>

8. <modules>

9. <module>module-jaxrs</module>

10. <module>module-services</module>

11. <module>module-data</module>

12. <module>module-kar</module>

13. </modules>

15. <properties>

16. <cxf.version>3.1.5</cxf.version>

17. <osgi.version>6.0.0</osgi.version>

18. <project.build.outputEncoding>UTF-8</project.build.outputEncoding>

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

20. </properties>

22. <!-- Introduction de la balise <dependencyManagement> qui permet de centraliser les informations de dependances.

23. Quand on a un ensemble de projets qui heritent d'un parent commun, on peut mettre toutes les informations de dependance dans le POM commun

24. et avoir des references plus simples vers les artefacts dans les POM enfants. Ceci permet de consolider et de centraliser la gestion des versions des dependances

25. sans ajouter des dependances qui sont heritees par tous les sous-modules. C'est tres interessant quand on a un ensemble de projets qui heritent

26. d'un meme parent-->

27. <dependencyManagement>

28. <dependencies>

30. <!-- Module utile au "module-jaxrs" -->

31. <dependency>

32. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

33. <artifactId>module-services</artifactId>

34. <version>${project.version}</version>

35. </dependency>

37. <!-- Module utile au "module-jaxrs" et au "module-services" -->

38. <dependency>

39. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

40. <artifactId>module-data</artifactId>

41. <version>${project.version}</version>

42. </dependency>

44. <!-- Dependance pour BundleContext-->

45. <dependency>

46. <groupId>org.osgi</groupId>

47. <artifactId>org.osgi.core</artifactId>

48. <version>${osgi.version}</version>

49. <scope>provided</scope>

50. </dependency>

52. <!-- Dependance necessaire pour le service web REST (GET, POST, PUT, Response, ...) -->

53. <dependency>

54. <groupId>org.apache.cxf</groupId>

55. <artifactId>cxf-rt-frontend-jaxrs</artifactId>

56. <version>${cxf.version}</version>

57. <scope>provided</scope>

58. </dependency>

60. <!-- Dependances necessaires pour la dependance org.apache.cxf/cxf-rt-frontend-jaxrs -->

61. <dependency>

62. <groupId>org.apache.cxf</groupId>

63. <artifactId>cxf-rt-frontend-jaxws</artifactId>

64. <version>${cxf.version}</version>

65. <scope>provided</scope>

66. </dependency>

67. <dependency>

68. <groupId>org.apache.cxf</groupId>

69. <artifactId>cxf-rt-transports-http</artifactId>

70. <version>${cxf.version}</version>

71. <scope>provided</scope>

72. </dependency>

74. <!-- Dependance necessaire pour org.slf4j.Logger -->

75. <dependency>

76. <groupId>org.apache.cxf</groupId>

77. <artifactId>cxf-rt-transports-http-jetty</artifactId>

78. <version>${cxf.version}</version>

79. <scope>provided</scope>

80. </dependency>

82. <!-- Dependance pour org.osgi.service.log.LogService -->

83. <dependency>

84. <groupId>org.osgi</groupId>

85. <artifactId>org.osgi.compendium</artifactId>

86. <version>5.0.0</version>

87. <scope>provided</scope>

88. </dependency>

90. <!-- JPA -->

91. <dependency>

92. <groupId>org.eclipse.persistence</groupId>

93. <artifactId>javax.persistence</artifactId>

94. <version>2.1.1</version>

95. <scope>provided</scope>

96. </dependency>

98. </dependencies>

99. </dependencyManagement>

102. <build>

103. <plugins>

104. <plugin>

105. <groupId>org.apache.maven.plugins</groupId>

106. <artifactId>maven-compiler-plugin</artifactId>

107. <version>3.3</version>

108. <configuration>

109. <compilerVersion>1.7</compilerVersion>

110. <source>1.7</source>

111. <target>1.7</target>

112. </configuration>

113. </plugin>

114. </plugins>

115. </build>

116. </project>

V-D - Création d'une DataSource en tant que service OSGI

Comme OSGI décrit avant tout une architecture modulaire, il est logique de pousser la modularité jusqu'à l'accès à la base de données. Pour cela, nous pouvons utiliser les avantages de JEE et plus spécialement de la configuration de source de données par l'utilisation d'objets JNDI référencés depuis une application.

Dans ce paragraphe, nous allons montrer la mise en place manuelle d'une DataSource en tant que service OSGI dans Karaf afin de comprendre le rôle de chacune des features utilisées. C'est cette DataSource (nommée « customerDb ») que nous avons utilisée dans le paragraphe précédent.

Les commandes apparaissant dans ce paragraphe ne sont pas à exécuter dans le cas où vous prévoyez de déployer automatiquement une DataSourceFactory et une DataSource lors du déploiement de votre service web.

En effet, nous verrons ensuite, dans le paragraphe « V.E Création du bundle Kar » comment ce même service OSGI peut être déployé automatiquement en même temps que notre service web REST.

En ce qui concerne Karaf, la mise en place d'un accès à une base de données en tant que service OSGI est très simple. Elle consiste d'abord à installer un driver de base de données, à créer une DataSourceFactory et d'y générer une DataSource.

La plupart des pilotes de bases de données constituent des bundles et sont disponibles dans le repository Maven.

Dans le cas de notre application, nous utiliserons la base de données PostgreSQL.

Les pilotes de bases de données PostgreSQL fournissent désormais une DataSourceFactory.

Afin d'installer une DataSourceFactory et d'y configurer des sources de données, la version 4 de Karaf fait appel à « Pax JDBC ». Il s'agit d'un projet de la communauté OPS4J qui simplifie l'usage des pilotes JDBC dans les applications OSGI par extension ou adaptation des pilotes selon les spécifications OSGI.

Voir la page : https://ops4j1.jira.com/wiki/display/PAXJDBC/2012/06/17/Introducing+Pax+JDBC

Karaf 4.0.4 intègre la version 0.7.0 du projet « pax-jdbc ». On peut le voir via la commande :

feature:list | grep pax-jdbc

Nous verrons, plus loin, la différence essentielle entre la version 7 et les versions suivantes.

Si toutefois, vous souhaitez utiliser une nouvelle version de « pax-jdbc » publiée sous Maven depuis la rédaction de cet article, veuillez exécuter la commande suivante pour la récupérer, avant d'aller plus loin dans cet article :

feature:repo-add mvn:org.ops4j.pax.jdbc/pax-jdbc-features/X.X.X/xml/features

en remplaçant « X.X.X » par le numéro de la version.

Vous pouvez également choisir de récupérer la version en cours de développement de « pax-jdbc » (version « SNAPSHOT »). Ainsi, selon la manière dont est configuré votre Maven, il vous suffit de la récupérer via la commande suivante avant d'aller plus loin :

feature:repo-add pax-jdbc

L'installation d'une DataSourceFactory associée à PostgreSQL se fait de la manière suivante :

1. quelle que soit la version de « pax-jdbc », exécutez les commandes suivantes dans l'ordre afin d'installer les features nécessaires à l'installation du driver PostgreSQL :

feature:install pax-jdbc

feature:install pax-jdbc-config

feature:install pax-jdbc-pool-dbcp2

feature:install jdbc

La feature « pax-jdbc » fournit une extension générique qui enregistre un service DataSourceFactory de la part d'un driver JDBC 4.0 possédant un manifest OSGI.

La feature « pax-jdbc-config » crée et publie une DataSource et une XADataSource en se basant sur un fichier de configuration et en s'appuyant sur le service DataSourceFactory existant. Dans Karaf, le fichier de configuration sera nommé de la façon suivante : « etc/org.ops4j.datasource_*.cfg ». Nous reviendrons sur ce fichier plus loin dans ce paragraphe.

La feature «  pax-jdbc-pool-dbcp2 » permet de gérer le pooling et le support JTA via DBCP2.

La feature « jdbc » permet saisir des commandes jdbc dans la console de Karaf ;

2. comme nous l'avons dit plus haut, la plupart des pilotes de bases de données sont déjà fournis sous forme de bundles valides et disponibles dans le repository Maven. Cependant, « pax-jdbc » fournit, pour plusieurs bases de données, des features karaf afin d'installer la version courante de leur pilote.
Malheureusement, le pilote PostgreSQL embarqué dans la version 0.7.0 de « pax-jdbc » ne constitue pas la dernière version et par conséquent, son installation sous Karaf ne génère pas automatiquement de DataSourceFactory.

Pour nous faciliter la tâche, nous allons pourtant installer ce dernier pilote. Nous allons donc distinguer le cas où vous êtes arrivé à ce point à l'aide de la version « 0.7.0 » de « pax-jdbc » et le cas où vous avez installé une version supérieure.

Installation du dernier driver PostgreSQL en utilisant « pax-jdbc 0.7.0 »

Il vous suffit d'installer le dernier driver via la commande suivante :

bundle:install -s mvn:org.postgresql/postgresql/9.4-1203-jdbc41

Installation du dernier driver PostgreSQL en utilisant une version ultérieure de « pax-jdbc 0.7.0 »

Dans les versions ultérieures à la version « 0.7.0 » de « pax-jdbc », l'adaptateur fourni par « pax-jdbc » intègre le dernier pilote PostgreSQL qui génère automatiquement des dataSourceFactory. Ainsi, l'installation du pilote dans Karaf se résume à l'exécution de la commande suivante :

feature:install pax-jdbc-postgresql

service:list DataSourceFactory

3. dès lors, la création de notre DataSource peut être réalisée de deux manières différentes :

1. soit en exécutant la commande suivante :
jdbc:ds-create -dn 'PostgreSQL JDBC Driver' -dbName test -url jdbc:postgresql://XXX.XXX.XXX.XXX:5003/test?currentSchema=test -u pgtest -p mot_de_passe customerDb
-dn : valeur de la propriété « org.osgi.driver.name » de la DataSourceFactory.  D'après la capture d'écran ci-dessus, il s'agit de « PostgreSQL JDBC Driver »,
-dbName : Nom de la base de données,
-url : URL d'accès à la base de données,
-u : Nom de l'utilisateur de la base de données,
-p : Mot de passe de l'utilisateur de la base de données.

La valeur « customerDb » constitue le nom de la datasource. Elle est reprise dans le fichier « module-data/persistence.xml » et plus précisément dans la balise « persistence/persistence-unit/jta-data-source ».

Elle figure également dans la balise « JPA-PersistenceUnits » du fichier « module-data/pom.xml ».

Cette commande, en plus de générer une DataSource, crée également un fichier dans le répertoire « data\cache\bundle7\data\config\org\ops4j\datasource\ ». Ce fichier, dans le cadre de notre démonstration, est nommé « 42506f7f-ff39-4026-a6ea-a2ee11c20ea8.config ».

Ce nom s'appuie sur le PID du service gérant la DataSource. Ce fichier contient toutes les informations saisies dans la commande.

Néanmoins, en cas de changement des paramètres de connexion à la base de données, il n'est pas très aisé de trouver un tel fichier pour en modifier le contenu ;

2. soit en créant, dans le répertoire « etc » de Karaf, un fichier, dont le nom est de la forme « org.ops4j.datasource_*.cfg » où « * » devra être remplacé par le nom de la DataSource.
Ce fichier sera utilisé par le bundle « pax-jdbc-config » (que nous avons installé dans ce but au point 1) de ce paragraphe) pour créer la DataSource.
Le contenu de ce fichier sera de la forme suivante :

etc/org.ops4j.datasource-customerDb.cfg

1. osgi.jdbc.driver.name=PostgreSQL JDBC Driver

2. serverName=XXX.XXX.XXX.XXX

3. databaseName=test

4. portNumber=5003

5. user=pgtest

6. password=mot_de_passe

7. dataSourceName=customerDb

Ce fichier déclenchera automatiquement la création de la DataSource par le bundle pax-jdbc-config. Ce bundle s'appuiera sur la propriété « osgi.jdbc.driver.name » pour sélectionner la DataSourceFactory appropriée que nous avons créée précédemment.

Prenez soin de remplacer « XXX.XXX.XXX.XXX » par l'adresse IP de votre serveur de base de données.
Le numéro du port pourrait également être à changer, tout comme le nom d'utilisateur et le mot de passe et éventuellement le nom de la DataSource.

4. notre DataSource est désormais créée. Nous pouvons le vérifier en tapant la commande suivante :

service:list DataSource

5. désormais, du fait que nous avons installé la feature « jdbc », il est même possible d'exécuter des commandes sur la base de données telles :
jdbc:tables customerDb qui permet de lister toutes les tables, index, séquences et vues disponibles depuis la DataSource customerDb.
ou
jdbc:query customerDb select * from customer qui permet d'exécuter une requête.
D'autres commandes sont disponibles ici :
https://karaf.apache.org/manual/latest/users-guide/jdbc.html

V-E - Création du bundle Kar

Le module « module-kar » permettra de générer une archive kar qui contiendra l'ensemble des bundles que nous avons développés ci-dessus et qui nous permettra d'installer les features nécessaires à la bonne exécution de notre service Web de type Rest.

Il contiendra principalement un fichier « feature.xml » qui permettra de spécifier les bundles à installer au démarrage de l'application et de packager le service web REST avec les bundles que nous avons développés.

C'est par ce fichier « feature.xml » que nous installerons « pax-JDBC » vu dans le paragraphe précédent et donc que nous créerons nos DataSourceFactory.

Pour réaliser l'archive KAR, nous allons procéder comme suit :

1. cliquez droit sur le projet « customerRestFulHibernateWS » ? « New » ? « Project ». Dans la fenêtre qui apparaît, saisissez « maven » dans le filtre puis choisissez « Maven module ».

Cliquez sur le bouton « Next » ;

2. dans la fenêtre qui apparaît, ne cochez toujours pas la case «  Create a simple project (skip archetype selection) ».
Saisissez le nom de notre module Web. Par exemple : « module-kar ».

Cliquez sur le bouton « Next » ;

3. dans le champ « Filter » de la fenêtre qui apparaît, saisissez « feature » puis, dans la liste qui apparaît, choisissez la ligne correspondant à « karaf-feature-archetype » puis cliquez sur le bouton « Next ».

4. renseignez la fenêtre qui apparaît de cette manière :
Group Id : com.exemple.customerRestFulHibernateWS
Artefact Id : module-kar
Version : 0.0.1-SNAPSHOT
Dans la section « Properties available from archetype », attribuez la valeur « com.exemple.customerRestFulHibernateWS.kar » puis cliquez sur le bouton « Finish ».

5. comme précédemment, le module apparaît dans notre projet principal et un nouveau projet « module-kar » est apparu dans l'explorateur de projet.

6. complétez le fichier « feature.xml » de la manière suivante :

module-kar/feature.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <features name="${project.artifactId}-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0">

4. <!-- Ajout de features au repository -->

5. <repository>mvn:org.apache.cxf.karaf/apache-cxf/3.1.5/xml/features</repository>

6. <repository>mvn:org.ops4j.pax.jdbc/pax-jdbc-features/0.7.0/xml/features</repository>

7. <repository>mvn:org.hibernate/hibernate-osgi/5.1.0.Final/xml/karaf</repository>

9. <feature name='${project.artifactId}' description='${project.name}' version='${project.version}'>

10. <details>${project.description}</details>

12. <!-- Installation de la feature cxf -->

13. <feature version="3.1.5">cxf</feature>

15. <!-- Installation de pax-jdbc pour creer les DataSourceFactory -->

16. <!-- La creation des DataSources se fait par depot d'un fichier org.ops4j.datasource-*.cfg

17. (ou "*" doit etre remplace par le nom de la DataSource) dans le repertoire "/etc" de Karaf -->

18. <feature>pax-jdbc</feature>

19. <feature>pax-jdbc-config</feature>

20. <feature>pax-jdbc-pool-dbcp2</feature>

22. <!-- Installation de hibernate-osgi 5.1.0  -->

23. <feature>hibernate-orm</feature>

25. <!-- Installation de la feature jdbc embarquee dans Karaf pour interroger les DataSources depuis l'invite de commande Karaf -->

26. <feature>jdbc</feature>

28. <feature version="4.3.6.Final">hibernate</feature>

29. <feature version="2.3.0">jpa</feature>

30. <feature version="1.3.0">transaction</feature>

31. <feature version="4.0.4">jndi</feature>

33. <!-- Installation du driver PostgreSQL en tant que bundle -->

34. <bundle>mvn:org.postgresql/postgresql/9.4-1203-jdbc41</bundle>

36. <!-- On place, dans l'archive, les dependances Maven apparaissant dans le fichier customerRestFullHibernateWS/pom.xml -->

37. <bundle>mvn:org.osgi/org.osgi.core/6.0.0</bundle>

38. <bundle>mvn:commons-logging/commons-logging/1.2</bundle>

39. <bundle>mvn:commons-io/commons-io/2.4</bundle>

42. <!-- On renseigne les bundles que nous avons developpes et qui seront utiles au service web REST

43. le chemin de ces bundles est de la forme "mvn:groupId/artifactId/version" ou :

44. - groupeId est la valeur de la balise "/project/parent/groupId" du fichier "pom.xml" de chaque module

45. - artefactId est la valeur de la balise "/project/artefactId" du fichier "pom.xml" de chaque module

46. - version est la valeur de la balise "/project/parent/version" du fichier "pom.xml" de chaque module -->

47. <bundle>mvn:com.exemple.customerRestFulHibernateWS/module-data/0.0.1-SNAPSHOT</bundle>

48. <bundle>mvn:com.exemple.customerRestFulHibernateWS/module-services/0.0.1-SNAPSHOT</bundle>

49. <bundle>mvn:com.exemple.customerRestFulHibernateWS/module-jaxrs/0.0.1-SNAPSHOT</bundle>

50. </feature>

52. </features>

Pour revenir à ce que nous disions au paragraphe « V.D Création d'une DataSource en tant que service OSGI », il est possible de créer, de manière automatique, des DataSourceFactory lors du déploiement de notre service web. C'est ce que nous avons fait dans le fichier « feature.xml » ci-dessus.

On y voit, de la ligne 18 à la ligne 20, l'installation automatique des features concernant « pax-jdbc ». Ces features sont celles qui ont été importées de Maven via la ligne 6. Comme nous récupérons la version 0.7.0 de « pax-jdbc », il nous est nécessaire d'installer le pilote PostgreSQL. C'est ce que nous faisons à la ligne 31.

Dans le cas où nous aurions récupéré une version de « pax-jdbc » supérieure, la ligne 31 ne nous aurait pas été utile puisque le driver PostgreSQL installé dans « pax-jdbc » inclurait des DataSourceFactory. Il aurait alors suffi d'ajouter la ligne « <feature>pax-jdbc-postgresql</feature> » à la ligne 21 ;

7. complétez le fichier « pom.xml » de la manière suivante (notez la valeur « kar » dans la balise « packaging ») :

module-kar/pom.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

3. <modelVersion>4.0.0</modelVersion>

5. <parent>

6. <artifactId>customerRestFulHibernateWS</artifactId>

7. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

8. <version>0.0.1-SNAPSHOT</version>

9. </parent>

11. <artifactId>module-kar</artifactId>

12. <packaging>kar</packaging>

14. <name>module-kar-feature</name>

15. <description>module-kar details</description>

17. <!-- On fait appel a la bibliotheque "Aether" qui travaille avec les specifications des repository locaux et distants,

18. les workspaces de developpement, les artefacts de resolution et de transport  -->

19. <dependencies>

20. <dependency>

21. <groupId>org.eclipse.aether</groupId>

22. <artifactId>aether-util</artifactId>

23. <version>0.9.0.M2</version>

24. <scope>compile</scope>

25. </dependency>

26. </dependencies>

28. <build>

29. <pluginManagement>

30. <plugins>

31. <plugin>

32. <groupId>org.apache.karaf.tooling</groupId>

33. <artifactId>karaf-maven-plugin</artifactId>

34. <version>4.0.3</version>

35. <extensions>true</extensions>

36. </plugin>

37. <!--Cette configuration de plugin est utilisee seulement pour stocker les parametres d'Eclipse m2e. Ceci n'a pas d'influence sur le build Maven en lui-meme-->

38. <plugin>

39. <groupId>org.eclipse.m2e</groupId>

40. <artifactId>lifecycle-mapping</artifactId>

41. <version>1.0.0</version>

42. <configuration>

43. <lifecycleMappingMetadata>

44. <pluginExecutions>

45. <pluginExecution>

46. <pluginExecutionFilter>

47. <groupId>

48. org.apache.karaf.tooling

49. </groupId>

50. <artifactId>

51. karaf-maven-plugin

52. </artifactId>

53. <versionRange>

54. [4.0.3,)

55. </versionRange>

56. <goals>

57. <goal>

58. features-generate-descriptor

59. </goal>

60. </goals>

61. </pluginExecutionFilter>

62. <action>

63. <ignore></ignore>

64. </action>

65. </pluginExecution>

66. </pluginExecutions>

67. </lifecycleMappingMetadata>

68. </configuration>

69. </plugin>

70. </plugins>

71. </pluginManagement>

72. <plugins>

73. <plugin>

74. <groupId>org.apache.karaf.tooling</groupId>

75. <artifactId>karaf-maven-plugin</artifactId>

76. <configuration>

77. <startLevel>50</startLevel>

78. <aggregateFeatures>true</aggregateFeatures>

79. <!--<resolver>(obr)</resolver>-->

80. <checkDependencyChange>true</checkDependencyChange>

81. <failOnDependencyChange>false</failOnDependencyChange>

82. <logDependencyChanges>true</logDependencyChanges>

83. <overwriteChangedDependencies>true</overwriteChangedDependencies>

84. </configuration>

85. </plugin>

86. </plugins>

87. </build>

88. </project>

8. créez le fichier « org.ops4j.datasource-customerDb.cfg », dont le contenu est décrit dans le paragraphe précédent, dans le répertoire « etc » de Karaf afin qu'il soit pris en compte par « pax-JDBC » après l'installation automatique des features « pax-jdbc », « pax-jdbc-config » et « pax-jdbc-pool-dbcp2 ».

Dans le nom du fichier «  org.ops4j.datasource-customerDb.cfg », la partie « customerDb » doit obligatoirement être remplacée par le nom de votre DataSource.

V-F - Les logs

Il existe trois types de logs dans notre service Web de type Rest :

- les logs CXF ;

- les logs SLF4J ;

- les logs Hibernate.

V-F-1 - Les logs CXF

Ils se configurent dans le fichier « module-jaxrs/my-service.xml ». par l'ajout de la balise « cxf:bus » :

module-jaxrs/my-service.xml

1. ...

2. <cxf:bus id="bus">

3. <cxf:features>

4. <cxf:logging/>

5. </cxf:features>

6. </cxf:bus>

7. ...

Ces lignes ont pour effet de faire apparaître les requêtes reçues par le service web, et les réponses qui y ont été apportées, dans le fichier « /data/log/karaf.log ».

Ces logs ont le format suivant :

Log CXF

1. ----------------------------

2. ID: 1

3. Address: http://localhost:8181/cxf/olivier/customer/1/order

4. Encoding: UTF-8

5. Http-Method: PUT

6. Content-Type: application/json

7. Headers: {Accept=[text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8], accept-encoding=[gzip, deflate], Accept-Language=[null], Authorization=[Basic a2FyYWY6a2FyYWY=], connection=[keep-alive], Content-Length=[138], content-type=[application/json], Host=[localhost:8181], User-Agent=[Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0]}

8. Payload: {"Order":{"description":Nouvelle commande,     "products": [{"idProduct":1,"description":"Clavier"},{"idProduct":2,"description":"Souris"}]}}

9. --------------------------------------

11. …

13. ---------------------------

14. ID: 1

15. Response-Code: 200

16. Content-Type: text/html

17. Headers: {Date=[Thu, 24 Mar 2016 13:53:52 GMT], Content-Length=[0]}

18. --------------------------------------

V-F-2 - Les logs « SLF4J »

Simple Logging Facade for Java (SLF4J) est une simple façade qu'on peut apposer sur les frameworks de log tels « java.util.logging », « logback », « log4j » ou, dans notre cas « Apache Felix Log ». Cette façade rend donc abstraite la couche de journalisation.

La mise en place de « SLF4J » est assez simple. Il faut d'abord récupérer un logger que nous avons initialisé. Dans notre cas, Il s'agit d'utiliser le logger de « Apache Felix Log » (c'est un service de log pour les plates-formes OSGI) déclaré dans la classe « CustomerDaoImpl » :

module-data/CustomerDaoImpl.java

1. import org.osgi.service.log.LogService;

3. public class CustomerDaoImpl implements CustomerDao

4. {

5. //@PersistenceContext permet de recuperer l'entityManager. Le unitName est celui fourni dans le fichier persistence.xml (dans la balise <persistence-unit name="customerDb" transaction-type="JTA">)

6. @PersistenceContext(unitName = "customerDb")

7. private EntityManager entityManager;

8. private LogService logService;

9. ...

La récupération de ce logger se fait dans les classes « CustomerServiceImpl » et « CustomerRestService » de la manière suivante :

module-services/CustomerServiceImpl - module-jaxrs/CustomerRestService

1. import org.slf4j.Logger;

2. import org.slf4j.LoggerFactory;

3. ...

4. private static final Logger LOG = LoggerFactory.getLogger(CustomerService.class);

5. ...

6. LOG.info(getClass().getSimpleName() + "[updateCustomer({})]", customer.getIdCustomer());

Ces logs ont le format suivant (en fonction des appels aux méthodes de l'objet LOG comme celui ci-dessus) :

Logs SLF4J

1. 2016-03-24 14:53:50,468 | INFO  | tp1880379883-641 | CustomerService                  | 6611 - module-jaxrs - 0.0.1.SNAPSHOT | CustomerRestService[addOrderToCustomer(1, Order[idOrder=0, desription=Nouvelle commande])]

2. 2016-03-24 14:53:50,469 | INFO  | tp1880379883-641 | CustomerDao                      | 6612 - module-services - 0.0.1.SNAPSHOT | CustomerServiceImpl[getCustomerById(1)]

3. 2016-03-24 14:53:50,693 | INFO  | tp1880379883-641 | module-data                      | 6610 - module-data - 0.0.1.SNAPSHOT | CustomerDaoImpl[findCustomer(1)]

4. 2016-03-24 14:53:50,999 | INFO  | tp1880379883-641 | CustomerDao                      | 6612 - module-services - 0.0.1.SNAPSHOT | CustomerServiceImpl[addOrderToCustomer(Customer[idCustomer=1, nom=Rozier, prenom=Olivier], Order[idOrder=0, desription=Nouvelle commande])]

5. 2016-03-24 14:53:50,999 | INFO  | tp1880379883-641 | module-data                      | 6610 - module-data - 0.0.1.SNAPSHOT | CustomerDaoImpl[findProduct(1)]

6. 2016-03-24 14:53:51,630 | INFO  | tp1880379883-641 | module-data                      | 6610 - module-data - 0.0.1.SNAPSHOT | CustomerDaoImpl[findProduct(2)]

7. 2016-03-24 14:53:52,249 | INFO  | tp1880379883-641 | module-data                      | 6610 - module-data - 0.0.1.SNAPSHOT | CustomerDaoImpl[saveExistingCustomer(Customer[idCustomer=1, nom=Rozier, prenom=Olivier])]

8. 2016-03-24 14:53:52,983 | INFO  | tp1880379883-641 | LoggingOutInterceptor            | 6461 - org.apache.cxf.cxf-core - 3.1.5 | Outbound Message

Tout comme les logs CXF, ils sont visibles dans le fichier « /data/log/karaf.log ».

V-F-3 - Les logs Hibernate

Les logs Hibernate sont configurables dans le fichier « module-data/persistence.xml ». Les messages s'affichent alors dans la console Karaf.

module-data/persistence.xml

1. <properties>

3. <!-- La propriete hibernate.dialect definit le dialect SQL de notre base de donnees.Hibernate s'appuiera sur ce dialecte

4. pour optimiser l'execution des requetes en utilisant les proprietes specifiques a la base de donnees.

5. Dans notre cas, on utilisera le dialecte de PostgreSql-->

6. <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>

8. <!-- La propriete  hibernate.hbm2ddl.auto=none specifie que le schema de la table n'est pas automatiquement cree quand l'application est deployee.

9. Si la propriete a la valeur "create-drop", les tables de la base de donnees sont creees au deploiement de l'application et supprimees lors de l'undeployed ou de l'arret du serveur.

10. Si la propriete a la valeur "update", le schema de la base de donnees est mis a jour ou cree, mais son contenu n'est pas supprime.

11. En production, un utilisateur n'a que tres rarement le privilege de creer ou supprimer des tables.-->

12. <property name="hibernate.hbm2ddl.auto" value="none"/>

14. <!-- On log les requetes executees par hibernate pour faciliter le debogage -->

15. <property name="hibernate.show_sql" value="true"/>

17. <!-- On met des commentaires dans toutes les requetes SQL generees pour faciliter la comprehension des requetes-->

18. <property name="use_sql_comments" value="true"/>

20. </properties>

Ces logs ont le format suivant :

Ces logs étant assez bavards, il convient de les activer seulement dans le cas de débogage.

La désactivation de ces logs se fait en attribuant la valeur « false » à l'attribut « value » dans la balise :

module-data/persistence.xml

1. <property name="hibernate.show_sql" value="true"/>

VI - Compilation et Build

1. Cliquez droit sur le projet « customerRestFulHibernateWS » ? Run as ? Maven Build

2. Dans le champ « Goals », écrivez « clean install », puis appuyez sur le bouton « Run »

3. Le message suivant apparaît alors :

On peut y lire dans quel répertoire récupérer l'archive « kar ». Il s'agit du répertoire « target » de notre « module-kar » qui se situe dans le workspace du projet.

VII - Installation et configuration de Karaf

L'installation et la configuration relève de la même procédure que vu précédemment dans le tutoriel « Développement d'un RESTFUL Webservice sous Eclipse et déploiement dans KARAF » (point 1) à 8) du paragraphe VIII).

La seule exception est que nous n'installons plus CXF manuellement puisque, comme vous l'avez vu dans le paragraphe précédent, son installation est automatisée lors du déploiement de l'archive « Kar ».

Dans un souci pratique, voici un rappel de cette procédure  :

1. téléchargez et installez Karaf Container 4.0.4 sur le site http://karaf.apache.org/archives.html#container-404 ;

2. exécutez le fichier « karaf.bat » qui se situe dans le répertoire « bin » ;

3. installez éventuellement la console d'administration via la commande :
feature:install webconsole ;

4. à l'issue de cette installation, on peut accéder à la console d'administration de karaf via l'URL : http://localhost:8181/system/console/
Par défaut, l'utilisateur est karaf et le mot de passe est karaf.

VIII - Déploiement de l'application dans Karaf

Le déploiement de notre application dans Karaf peut se dérouler de trois manières différentes :

- en déployant l'archive « kar » que nous avons créée au paragraphe « VI Compilation et Build » ;

- ou en installant nos bundles à partir d'une feature ;

- ou en créant une distribution personnalisée de Karaf.

Les archives « kar » sont particulièrement utiles pour les déploiements hors-ligne. Elles regroupent nos bundles ainsi que toutes les features nécessaires à leur fonctionnement et permettent de les installer dans le repository Maven de Karaf.

Les features sont caractérisées par un fichier « feature.xml » qui liste l'URL des bundles et des features nécessaires à leur fonctionnement. On peut voir un tel fichier dans le paragraphe « V-F 6) ». Ainsi, une feature doit être ajoutée au repository de Karaf avant d'être installée.

Vous l'aurez compris, on dispose déjà de ce fichier « feature.xml » en utilisant l'archétype « karaf-feature-archetype » qui nous a permis de créer notre archive « kar ».

Nous verrons la mise en œuvre de ces deux solutions dans ce paragraphe.

VIII-A - Préparation

Afin de faciliter le déploiement de notre application puis, nous le verrons plus loin, la mise à jour des bundles, nous allons ajouter notre repository Maven (celui inclus dans Eclipse dans notre cas) à la configuration de Karaf afin que Karaf puisse récupérer le fruit de notre travail après la compilation de notre projet.

En effet, Eclipse renseigne notre repository locale avec les fichiers « jar » et autre « kar » à l'issue de chaque build.

Pour cela, ouvrez le fichier « apache-karaf-4.0.4/etc/org.ops4j.pax.url.mvn.cfg », cherchez la ligne « org.ops4j.pax.url.mvn.repositories » puis ajoutez le chemin de votre repository local.

La modification doit ressembler à ceci :

org.ops4j,pax.url.mvn.cfg

1. org.ops4j.pax.url.mvn.repositories= \

2. http://repo1.maven.org/maven2@id=central, \

3. http://repository.springsource.com/maven/bundles/release@id=spring.ebr.release, \

4. http://repository.springsource.com/maven/bundles/external@id=spring.ebr.external, \

5. http://zodiac.springsource.com/maven/bundles/release@id=gemini, \

6. http://repository.apache.org/content/groups/snapshots-group@id=apache@snapshots@noreleases, \

7. https://oss.sonatype.org/content/repositories/snapshots@id=sonatype.snapshots.deploy@snapshots@noreleases, \

8. https://oss.sonatype.org/content/repositories/ops4j-snapshots@id=ops4j.sonatype.snapshots.deploy@snapshots@noreleases, \

9. http://repository.springsource.com/maven/bundles/external@id=spring-ebr-repository@snapshots@noreleases, \

10. file://C:/Users/orozier/.m2/repository@id=1@snapshots

Les informations ajoutées et qui sont propres à notre repository Maven dans l'extrait du fichier ci-dessus sont :

1. , \

2. file://C:/Users/orozier/.m2/repository@id=1@snapshots

où « id » est l'identifiant qui est spécifié dans la balise « <id> » du fichier « .m2/settings.xml » renseigné dans la fenêtre « Windows » -> « Preferences » -> « Maven » -> « User Settings » d'Eclipse.

«  @snapshots » spécifie que le repository peut contenir des snapshots (sinon, pax url ne regardera que les release).

VIII-B - Déploiement de l'archive Kar

Le déploiement de l'archive Kar est très simple. Il consiste à récupérer l'archive Kar générée dans le paragraphe « VI Compilation et Build » puis de la déposer dans le répertoire « deploy » de Karaf. Ce dépôt peut se faire à chaud.

À noter que dans le cas du déploiement de l'archive Kar, il n'y a pas besoin de modifier le fichier « apache-karaf-4.0.4/etc/org.ops4j.pax.url.mvn.cfg » comme indiqué ci-dessus puisque Karaf n'a pas besoin d'explorer notre repository local.

Afin de s'assurer du déploiement correct de notre application, il suffit d'exécuter la commande « bundle:list » dans la console de Karaf et de constater que nos trois bundles sont présents :

Nous constatons, sur la capture d'écran ci-dessus que nos bundles (d'identifiant 122, 123 et 124) ont correctement été déployés.

Si vous vous déplacez dans le repository de Karaf et plus précisément dans le répertoire « ${user.home}/.m2/repository/org », vous constaterez que toutes les features que nous avons listées dans le fichier « module-kar/feature.xml » sont bien présentes.

Par conséquent, Karaf les a bien extraites de notre archive « kar » pour les placer dans son repository.

VIII-C - Déploiement à partir d'une feature

Afin de déployer notre projet à partir d'un fichier « feature.xml », il est nécessaire de modifier le fichier « apache-karaf-4.0.4/etc/org.ops4j.pax.url.mvn.cfg » conformément à ce que nous avons écrit dans le paragraphe « VIII.A Préparation ».

En effet, le fichier « feature.xml » que nous allons utiliser pour déployer notre application existe déjà puisqu'il a été généré dans le paragraphe « VI. Compilation et Build ».

À l'issue de la phase de Build, Eclipse a renseigné le repository local avec :

- les fichiers « jar » propres à chacun de nos modules et qui deviendront des bundles sous Karaf ;

- le fichier « kar » regroupant ces « jar » ;

- le fichier « module-kar-0.0.1-SNAPSHOT-features.xml » qui n'est autre que notre fameux fichier « feature.xml » qui va servir au déploiement de notre service web REST. Ce fichier a été généré par le « module-kar ».

Il est donc nécessaire que Karaf puisse accéder à notre repository local afin de récupérer ce fichier « feature.xml ».

Ce fichier « feature.xml » ne contient que des URL référençant, dans notre repository local, nos bundles et les features nécessaires à leur fonctionnement.

Afin d'installer tout ce petit monde, Karaf a besoin que nous ajoutions notre « feature.xml » à son repository simplement en exécutant la commande :

Ajout de la feature au repository Karaf

1. feature:repo-add mvn:com.exemple.customerRestFulHibernateWS/module-kar/0.0.1-SNAPSHOT/xml/features

Comme notre version est un SNAPSHOT, il est très important de ne pas oublier, dans la ligne « ,\
file://C:/Users/orozier/.m2/repository@id=1@snapshots » du fichier « apache-karaf-4.0.4/etc/org.ops4j.pax.url.mvn.cfg » la partie « @snapshots » sous peine que pax url ne détecte pas notre fichier « feature.xml » puisqu'il ne chercherait, dans ce cas-là, que les release.

Maintenant que nous avons ajouté notre feature au repository de Karaf, nous pouvons vérifier sa présence par la commande :

« feature:list | grep module-kar »

Il ne reste plus qu'à l'installer en utilisant la commande « feature:install module-kar ».

En exécutant la commande « bundle:list », on peut voir que nos trois bundles (identifiant 122, 123 et 124) sont correctement déployés :

VIII-D - Création d'une distribution personnalisée de Karaf

La création d'une distribution de Karaf personnalisée est très pratique dans le cas où le serveur de production ne doit pas accéder au Web et/ou vous voulez faciliter l'installation de votre projet en production à l'équipe d'exploitation.

L'équipe d'exploitation en charge de l'installation de votre projet n'aura :

- ni à configurer les DataSource Factory ;

- ni à configurer les sources de données ;

- ni à installer Hibernate ;

- ni à installer pax-jdbc ;

- ni à installer les pilotes de la base de données ;

- ni à installer cxf ;

- ni a déployer les bundles ou l'archive « kar ».

Entendez également par tous ces avantages que l'équipe d'exploitation n'aura pas à se soucier des versions des différentes features. Ce qui est un avantage non négligeable pour l'équipe de développement également puisqu'elle contrôle la version des features intervenant dans le projet de bout en bout.

Nous allons procéder, dans ce paragraphe, à la création d'une telle distribution. N'ayez crainte, c'est aussi rapide que pratique puisque nous allons nous appuyer sur tout ce qui a été fait précédemment.

La création d'une distribution personnalisée de Karaf est complémentaire à tout ce que nous avons fait précédemment. C'est-à-dire qu'à l'issue du Build de notre projet, vous pourrez récupérer :

- les bundles (fichiers jar propres à chaque module développé) ;

- l'archive « kar » ;

- La distribution personnalisée de Karaf.

Ainsi, vous pourrez choisir de livrer votre projet de la manière qui vous convient le plus.

Voici la marche à suivre pour accomplir un tel prodige :

1. cliquez droit sur le projet « customerRestFulHibernateWS » ? « New » ? « Project ». Dans la fenêtre qui apparaît, saisissez « maven » dans le filtre puis choisissez « Maven module » ;

2. dans la fenêtre qui apparaît, cochez la case « Create a simple project (skip archetype selection) » et saisissez le nom du nouveau module. Par exemple : « module-karaf-custom-distribution » :

Cliquez sur « Next » ;

3. dans la fenêtre qui apparaît, choisissez la valeur « pom » dans la section « packaging » puis cliquez sur « Finish » :

Un nouveau module est apparu dans l'explorateur de projet d'Eclipse :

4. renommez le répertoire « site » en « main » via un clic droit sur ce répertoire puis, dans le menu qui apparaît, choisissez « Rename » :

Cliquez sur « OK » ;

5. faites un clic droit sur le répertoire « main » puis, dans le menu contextuel qui apparaît, choisissez « New » puis « Folder ». Dans la fenêtre qui apparaît, dans la section « Folder name », saisissez « filtered-resources » puis cliquez sur « Finish » ;

6. de la même manière, dans le répertoire « main », créez le répertoires « resources » ;

7. dans chacun des répertoires « filtered-resources » et « resources », créez un répertoire « etc » comme le présente la capture d'écran ci-dessous :

Le répertoire « filtered-resources » contiendra tous les fichiers de ressources comportant des valeurs concernant des propriétés du système ou du projet (par exemple : « ${project.version} » ou « ${karaf.version} ») et pour lesquelles ces valeurs doivent être remplacées.

Le répertoire « resources » contiendra les fichiers qui devront être localisés dans le répertoire « /etc » de Karaf, mais qui ne possèdent pas de variables. On y déposera notre fichier de configuration de DataSource (nommé « org.ops4j.datasource-customerDb.cfg »).

Le fichier « pom.xml » permettra à Maven de télécharger la version « enterprise » de Karaf, y installer le driver PostgreSql, y installer les modules que nous avons développés, y installer les ressources déposées dans les répertoires « filtered-resources » et « resources » ci-dessus ;

8. commencez par remplacer le contenu du fichier « pom.xml » par ceci :

module-karaf-custom-distribution/pom.xml

1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

2. <modelVersion>4.0.0</modelVersion>

3. <parent>

4. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

5. <artifactId>customerRestFulHibernateWS</artifactId>

6. <version>1.0.0</version>

7. </parent>

9. <artifactId>module-karaf-custom-distribution</artifactId>

10. <packaging>karaf-assembly</packaging>

12. <version>1.0.0</version>

13. <name>Distribution de Karaf personnalisee</name>

15. <properties>

16. <karaf.version>4.0.4</karaf.version>

17. </properties>

19. <dependencies>

21. <!-- La valeur de la balise <scope> est "compile" donc toutes les features sont installees dans le fichier startup.properties

22. et le repository des features n'est pas ajoute dans le fichier etc/org.apache.karaf.features.cfg.

23. Le fichier etc/startup.properties est utilise pour definir les services noyaux et leur ordre de demarrage dans Karaf-->

24. <dependency>

25. <groupId>org.apache.karaf.features</groupId>

26. <artifactId>framework</artifactId>

27. <version>${karaf.version}</version>

28. <type>kar</type>

29. <scope>compile</scope>

30. </dependency>

32. <!-- La valeur de la balise <scope> est "runtime" donc le repository des features est liste dans le fichier etc/org.apache.karaf.features.cfg

33. et les features seront installees dans le repertoire system.

34. Cette dependance importe les features standards de Karaf decrites a l'adresse

35. https://repo1.maven.org/maven2/org/apache/karaf/features/standard/3.0.4/standard-3.0.4-features.xml

36. Ces features sont installees dans la configuration de karaf-maven-plugin -->

38. <!-- Les features jdbc et hibernate ne sont pas incluses dans Karaf standard, elles sont dans Karaf enterprise donc on recupere Karaf enterprise

39. qui inclut aussi Karaf standard -->

40. <dependency>

41. <groupId>org.apache.karaf.features</groupId>

42. <artifactId>enterprise</artifactId>

43. <version>${karaf.version}</version>

44. <classifier>features</classifier>

45. <type>xml</type>

46. <scope>runtime</scope>

47. </dependency>

49. <!-- Installation du driver PostgreSql -->

50. <dependency>

51. <groupId>org.postgresql</groupId>

52. <artifactId>postgresql</artifactId>

53. <version>9.4-1203-jdbc41</version>

54. <scope>runtime</scope>

55. </dependency>

57. <!-- Installation de la feature propre a notre projet. Cette feature a ete generee par le module ""module-kar" -->

58. <dependency>

59. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

60. <artifactId>module-kar</artifactId>

61. <version>${project.version}</version>

62. <classifier>features</classifier>

63. <type>xml</type>

64. <scope>runtime</scope>

65. </dependency>

67. <dependency>

68. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

69. <artifactId>module-data</artifactId>

70. <version>${project.version}</version>

71. <scope>runtime</scope>

72. </dependency>

74. <dependency>

75. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

76. <artifactId>module-services</artifactId>

77. <version>${project.version}</version>

78. <scope>runtime</scope>

79. </dependency>

81. <dependency>

82. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

83. <artifactId>module-jaxrs</artifactId>

84. <version>${project.version}</version>

85. <scope>runtime</scope>

86. </dependency>

88. </dependencies>

90. <build>

91. <!-- Inclusion des ressources dans la distribution -->

92. <resources>

93. <!-- Le fichier contenant la configuration de notre dataSource sera localise dans le repertoire "src/main/resources"

94. afin d'etre copie dans le repertoire "etc" de Karaf -->

95. <resource>

96. <directory>src/main/resources</directory>

97. <filtering>false</filtering>

98. <includes>

99. <include>**/*</include>

100. </includes>

101. </resource>

103. <!-- Le repertoire "src/main/filtered-resources" contient les fichiers qui incluent des variables denotees par "${...}".

104. Ces variables peuvent venir des proprietes du systeme, des proprietes du projet...

105. Le fichier branding.properties contient de telles variables. Il sera copie, comme les fichiers qui se trouvent dans le repertoire

106. "src/main/resources" dans le repertoire "etc" de Karaf.

107. Voir le lien : http://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html -->

108. <resource>

109. <directory>src/main/filtered-resources</directory>

110. <filtering>true</filtering>

111. <includes>

112. <include>**/*</include>

113. </includes>

114. </resource>

115. </resources>

117. <plugins>

118. <plugin>

119. <groupId>org.apache.karaf.tooling</groupId>

120. <artifactId>karaf-maven-plugin</artifactId>

121. <version>${karaf.version}</version>

122. <extensions>true</extensions>

123. <configuration>

124. <!-- La balise <bootFeatures> permet de demander a Karaf lors de son demarrage de demarrer les features.

125. Le nom des features est obtenu dans le fichier features.xml dans la section <dependencies>.

126. -->

127. <bootFeatures>

128. <!-- Features liees a Karaf standard -->

129. <feature>wrap</feature>

130. <feature>aries-blueprint</feature>

131. <feature>shell</feature>

132. <feature>shell-compat</feature>

133. <feature>feature</feature>

134. <feature>jaas</feature>

135. <feature>ssh</feature>

136. <feature>management</feature>

137. <feature>bundle</feature>

138. <feature>config</feature>

139. <feature>deployer</feature>

140. <feature>diagnostic</feature>

141. <feature>feature</feature>

142. <feature>instance</feature>

143. <feature>kar</feature>

144. <feature>log</feature>

145. <feature>package</feature>

146. <feature>service</feature>

147. <feature>system</feature>

148. <feature>webconsole</feature>

150. <!-- Features liees a Karaf entreprise et necessaires a notre projet -->

151. <feature>jdbc</feature>

152. <!-- <feature>hibernate</feature>-->

153. <feature>jpa</feature>

154. <feature>transaction</feature>

155. <feature>jndi</feature>

157. <!-- Demarrage des features propres a notre projet. "pax-jdbc", "apache-cxf" et "hibernate-osgi 5.1.0" sont recuperes depuis Maven grace au fichier feature.xml du module "module-kar"-->

158. <feature>pax-jdbc</feature>

159. <feature>pax-jdbc-config</feature>

160. <feature>pax-jdbc-pool-dbcp2</feature>

161. <feature>cxf</feature>

162. <feature>hibernate-orm</feature>

164. <!-- Installation la feature de notre projet -->

165. <feature>module-kar</feature>

166. </bootFeatures>

168. <!-- <installedFeatures>

169. installedFeatures installe les features dans le repertoire ${KARAF_HOME}/system directory mais l'utilisateur devra l'installer manuellement via la command feature:install

170. <feature>pax-jdbc</feature>

171. <feature>pax-jdbc-config</feature>

172. <feature>pax-jdbc-pool-dbcp2</feature>

173. </installedFeatures>-->

175. </configuration>

176. </plugin>

177. </plugins>

178. </build>

180. </project>

9. il est possible de personnaliser la page d'accueil de la console de Karaf. Pour cela, créez le fichier « branding.properties » dans le répertoire « src/main/filtered-resources/etc ». Ce fichier contient votre œuvre d'art, généralement un ASCII Art. Nous le plaçons dans le répertoire « filtered-resources », car nous avons intégré des variables qui doivent être remplacées comme le présente son contenu ci-dessous :

module-karaf-custom-distribution/branding.properties

1. welcome=\

2. \u001B[36m          _____                    _____                    _____                _____                   _______                   _____                    _____                    _____\u001B[0m\r\n\

3. \u001B[36m         /\\    \\                  /\\    \\                  /\\    \\              /\\    \\                 /::\\    \\                 /\\    \\                  /\\    \\                  /\\    \\\u001B[0m\r\n\

4. \u001B[36m        /::\\    \\                /::\\____\\                /::\\    \\            /::\\    \\               /::::\\    \\               /::\\____\\                /::\\    \\                /::\\    \\\u001B[0m\r\n\

5. \u001B[36m       /::::\\    \\              /:::/    /               /::::\\    \\           \\:::\\    \\             /::::::\\    \\             /::::|   |               /::::\\    \\              /::::\\    \\\u001B[0m\r\n\

6. \u001B[36m      /::::::\\    \\            /:::/    /               /::::::\\    \\           \\:::\\    \\           /::::::::\\    \\           /:::::|   |              /::::::\\    \\            /::::::\\    \\\u001B[0m\r\n\

7. \u001B[36m     /:::/\\:::\\    \\          /:::/    /               /:::/\\:::\\    \\           \\:::\\    \\         /:::/~~\\:::\\    \\         /::::::|   |             /:::/\\:::\\    \\          /:::/\\:::\\    \\\u001B[0m\r\n\

8. \u001B[36m    /:::/  \\:::\\    \\        /:::/    /               /:::/__\\:::\\    \\           \\:::\\    \\       /:::/    \\:::\\    \\       /:::/|::|   |            /:::/__\\:::\\    \\        /:::/__\\:::\\    \\\u001B[0m\r\n\

9. \u001B[36m   /:::/    \\:::\\    \\      /:::/    /                \\:::\\   \\:::\\    \\          /::::\\    \\     /:::/    / \\:::\\    \\     /:::/ |::|   |           /::::\\   \\:::\\    \\      /::::\\   \\:::\\    \\\u001B[0m\r\n\

10. \u001B[36m  /:::/    / \\:::\\    \\    /:::/    /      _____    ___\\:::\\   \\:::\\    \\        /::::::\\    \\   /:::/____/   \\:::\\____\\   /:::/  |::|___|______    /::::::\\   \\:::\\    \\    /::::::\\   \\:::\\    \\\u001B[0m\r\n\

11. \u001B[36m /:::/    /   \\:::\\    \\  /:::/____/      /\\    \\  /\\   \\:::\\   \\:::\\    \\      /:::/\\:::\\    \\ |:::|    |     |:::|    | /:::/   |::::::::\\    \\  /:::/\\:::\\   \\:::\\    \\  /:::/\\:::\\   :::\\____\\\u001B[0m\r\n\

12. \u001B[36m/:::/____/     \\:::\\____\\|:::|    /      /::\\____\\/::\\   \\:::\\   \\:::\\____\\    /:::/  \\:::\\____\\|:::|____|     |:::|    |/:::/    |:::::::::\\____\\/:::/__\\:::\\   \\:::\\____\\/:::/  \\:::\\   \\:::|    |\u001B[0m\r\n\

13. \u001B[36m\\:::\\    \\      \\::/    /|:::|____\\     /:::/    /\\:::\\   \\:::\\   \\::/    /   /:::/    \\::/    / \\:::\\    \\   /:::/    / \\::/    / ~~~~~/:::/    /\\:::\\   \\:::\\   \\::/    /\\::/   |::::\\  /:::|____|\u001B[0m\r\n\

14. \u001B[36m \\:::\\    \\      \\/____/  \\:::\\    \\   /:::/    /  \\:::\\   \\:::\\   \\/____/   /:::/    / \\/____/   \\:::\\    \\ /:::/    /   \\/____/      /:::/    /  \\:::\\   \\:::\\   \\/____/  \\/____|:::::\\/:::/    /\u001B[0m\r\n\

15. \u001B[36m  \\:::\\    \\               \\:::\\    \\ /:::/    /    \\:::\\   \\:::\\    \\      /:::/    /             \\:::\\    /:::/    /                /:::/    /    \\:::\\   \\:::\\    \\            |:::::::::/    /\u001B[0m\r\n\

16. \u001B[36m   \\:::\\    \\               \\:::\\    /:::/    /      \\:::\\   \\:::\\____\\    /:::/    /               \\:::\\__/:::/    /                /:::/    /      \\:::\\   \\:::\\____\\           |::|\\::::/    /\u001B[0m\r\n\

17. \u001B[36m    \\:::\\    \\               \\:::\\__/:::/    /        \\:::\\  /:::/    /    \\::/    /                 \\::::::::/    /                /:::/    /        \\:::\\   \\::/    /           |::| \\::/____/\u001B[0m\r\n\

18. \u001B[36m     \\:::\\    \\               \\::::::::/    /          \\:::\\/:::/    /      \\/____/                   \\::::::/    /                /:::/    /          \\:::\\   \\/____/            |::|  ~|\u001B[0m\r\n\

19. \u001B[36m      \\:::\\    \\               \\::::::/    /            \\::::::/    /                                  \\::::/    /                /:::/    /            \\:::\\    \\                |::|   |\u001B[0m\r\n\

20. \u001B[36m       \\:::\\____\\               \\::::/    /              \\::::/    /                                    \\::/____/                /:::/    /              \\:::\\____\\               \\::|   |\u001B[0m\r\n\

21. \u001B[36m        \\::/    /                \\::/____/                \\::/    /                                      ~~                      \\::/    /                \\::/    /                \\:|   |\u001B[0m\r\n\

22. \u001B[36m         \\/____/                  ~~                       \\/____/                                                                \\/____/                  \\/____/                  \\|___|\u001B[0m\r\n\

23. \u001B[33m                                                        (version ${project.version}

24. basee sur Karaf ${karaf.version})\u001B[0m                \u001B[0m\r\n\

Vous pouvez générer un ASCII art sur le site : http://patorjk.com/software/taag/ et utiliser la page Web suivante pour choisir des couleurs : https://gist.github.com/dainkaplan/4651352 .

Concernant le ASCII Art, pensez à dématérialiser le caractère « \ ».

Le rendu correspondant au fichier « branding.properties » ci-dessus est celui-ci :

10. déposez, dans le répertoire « src/main/resources/etc », le fichier « org.ops4j.datasource-customerDb.cfg » créé dans le paragraphe « V-D. Création d'une DataSource en tant que service OSGI » ;

11. générez votre distribution personnalisée en cliquant droit sur le projet « customerRestFulHibernateWS » puis « Run as » puis « Maven Build » puis saisissez « clean install » (comme vous le faites jusqu'à présent) ;

12. récupérez votre distribution de Karaf dans le répertoire « \customerRestFulHibernateWS\module-karaf-custom-distribution\target ». Vous pouvez voir que votre distribution est disponible aux formats « tar.gz » et « zip » :

Vous n'avez plus qu'à la placer dans le répertoire de votre choix puis à la décompresser avant de l'utiliser comme vous le feriez avec une version classique de Karaf ;

13. l'exécution de la commande « feature:list » montre que toutes les features que nous avons évoquées dans le fichier «  module-karaf-custom-distribution/pom.xml » sont présentes et démarrées.
De plus, la commande « service:list DataSource » nous montre que la source de données a bien été installée.

IX - Mise à jour d'un bundle

C'est le grand avantage de OSGI : plutôt que d'installer une nouvelle version de tous les modules formant le service web REST, on peut choisir de mettre à jour un bundle en particulier en installant une nouvelle version de ce bundle. Dès lors, plusieurs versions d'un même bundle peuvent cohabiter dans Karaf. Cette fonctionnalité peut se révéler très utile dans le cas où un bundle est utilisé par plusieurs applications.

Afin de mettre en œuvre cette solution, il convient d'abord de supprimer du fichier « customerRestFulHibernateWS/pom.xml » les lignes « <version>${project.version}</version> » qui ont été mises dans chaque balise « dependency » correspondant à l'un des modules que nous avons développés.

Dès lors, il est nécessaire de spécifier la version des modules dépendant dans le fichier « pom.xml » du module enfant.

Par exemple, si on souhaite que le module « module-jaxrs » appelle une certaine version des modules « module-data » et « module service », il sera nécessaire de modifier les parties correspondantes dans le fichier « module-jaxrs/pom.xml » de la manière suivante :

module-jaxrs/pom.xml

1. <dependency>

2. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

3. <artifactId>module-services</artifactId>

4. <type>bundle</type>

5. </dependency>

7. <dependency>

8. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

9. <artifactId>module-data</artifactId>

10. <type>bundle</type>

11. </dependency>

Remplacé par :

module-jaxrs/pom.xml

1. <dependency>

2. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

3. <artifactId>module-services</artifactId>

4. <version>[1.0.0,2.0)</version>

5. <type>bundle</type>

6. </dependency>

8. <dependency>

9. <groupId>com.exemple.customerRestFulHibernateWS</groupId>

10. <artifactId>module-data</artifactId>

11. <version>[1.0.0,2.0)</version>

12. <type>bundle</type>

13. </dependency>

ATTENTION : dans notre cas, si vous souhaitez utiliser la même version du « module-data » dans le module « module-services », pensez bien à modifier de la même manière le contenu du fichier « module-services/pom.xml » qui fait également appel au module « module-data ».

Comme on peut le voir ci-dessus, la prise en compte des versions que les modules doivent utiliser s'effectue en spécifiant un intervalle de version dans la balise « dependency » du fichier « pom.xml » propre au module appelant.

Si une nouvelle version de l'artefact « module-data » est déployée, il n'y a pas besoin de recompiler et de redéployer le bundle « module-jaxrs » ou le module « module-services ». Cette nouvelle version sera celle qui sera privilégiée par le bundle.

Le crochet « [ » dans l'extrait du fichier « pom.xml » ci-dessus spécifie au bundle « module-jaxrs » qu'il ne peut faire appel au bundle « module-data » qu'à partir de la version « 1.0.0 » incluse de ce module.
La parenthèse « ) » spécifie au bundle « module-jaxrs » qu'il ne peut faire appel au bundle « module-data » que jusqu'à la version « 2.0 » exclue.

Ainsi, d'autres bundles peuvent faire appel à d'autres versions du bundle « module-data ». Par conséquent, il est indispensable de bien réfléchir, lors de la création de l'application, aux plages de version qui seront utilisées par les bundles d'une même application dans le futur.

Exemple

Simulons un changement de version pour le bundle « module-data ». On passe d'une version « 1.0.0 » à une version « 1.0.1 ».

Pensez à bien modifier le fichier « apache-karaf-4.0.4/etc/org.ops4j.pax.url.mvn.cfg » afin d'y faire figurer votre repository local conformément à ce qui est écrit dans le paragraphe « VIII.A Préparation ». En effet, nous demanderons à Karaf d'installer la nouvelle version du bundle depuis le repository local.

Cette manière de faire permet à Karaf de stocker le bundle dans le
répertoire data. Dès lors, après un redémarrage de Karaf, on disposera de la dernière version du bundle que nous
avions avant le redémarrage.

Ce qui ne serait pas le cas si on mettait la nouvelle version de notre fichier jar dans le répertoire deploy car, à chacun de ses redémarrages, Karaf reprendrait la version déployée par l'archive « kar » ou la feature et on serait obligé de faire un bundle:update sur le bundle que l'on souhaite mettre à jour pour que Karaf tienne compte de la nouvelle version.

Il est alors nécessaire d'ajouter la ligne « <version>1.0.1</version> » (« 1.0.1 » est un exemple) après la ligne « <name>module-data Blueprint Bundle</name> » dans le fichier « module-data/pom.xml ». Mais, dans tous les cas, nous ne pouvons pas mettre de version supérieure ou égale à « 2.0.0 » d'après ce qui est renseigné dans les fichiers « module-jaxrs/pom.xml » et « module-services/pom.xml ».

Il suffit ensuite :

- de recompiler le module « module-data » de la même manière qu'on compile le projet (clic droit sur le projet « module-data » puis « Run as » puis « Maven build » puis saisir « Clean install »). Dès lors, le fichier jar se situe dans le repository Maven intégré à Eclipse ;

- d'exécuter, depuis la console Karaf, la commande « bundle:install mvn:com.exemple.customerRestFulHibernateWS/module-data/1.0.1 ».
Karaf va alors récupérer le bundle que nous venons de créer dans le repository Maven local pour le stocker dans le répertoire « data ». La ligne « Bundle ID: id_du_nouveau_bundle » apparaît alors.
La commande « bundle:list » permet de voir que notre nouvelle version du bundle est bien installée (id = 223), mais pas démarrée ;

- de démarrer le bundle via la commande « bundle:start id_du_nouveau_bundle » afin de le rendre actif ;

- de mettre à jour le bundle d'origine. Dans notre cas, le bundle d'origine porte l'identifiant 122 comme on peut le voir sur la capture d'écran ci-dessus.
La commande de mise à jour de notre bundle est :
« bundle:update 122 » (identifiant du bundle d'origine)

X - Code source de l'exemple

X-A - Création des tables dans la base de données

Dans la base de données PostgreSQL, il est nécessaire de créer un schéma nommé « ctm » avant d'exécuter le script SQL ci-dessous permettant de créer et de remplir les tables nécessaires à l'exécution de l'exemple décrit tout au long de ce tutoriel :

Création des tables de la base

1. CREATE TABLE ctm.product

2. (

3. idProduct serial NOT NULL,

4. description character varying(200) NOT NULL,

6. CONSTRAINT pk_product PRIMARY KEY (idProduct)

7. );

10. CREATE TABLE ctm.customer

11. (

12. idCustomer serial NOT NULL,

13. prenom character varying(50) NOT NULL,

14. nom character varying(50) NOT NULL,

16. CONSTRAINT pk_customer PRIMARY KEY (idCustomer)

17. );

19. CREATE TABLE ctm.order

20. (

21. idOrder serial NOT NULL,

22. description character varying(50) NOT NULL,

23. customer_id integer,

25. CONSTRAINT pk_order PRIMARY KEY (idOrder),

26. CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES ctm.customer (idCustomer)

27. );

30. CREATE TABLE ctm.order_detail

31. (

32. id serial NOT NULL,

33. order_id integer NOT NULL,

34. product_id integer NOT NULL,

35. quantite integer NOT NULL,

37. CONSTRAINT pk_order_detail PRIMARY KEY (id),

38. CONSTRAINT fk_order FOREIGN KEY (order_id) REFERENCES ctm.order (idOrder),

39. CONSTRAINT fk_product FOREIGN KEY (product_id) REFERENCES ctm.product (idProduct)

40. );

43. INSERT INTO ctm.customer(prenom, nom) VALUES ('Olivier', 'Rozier');

44. INSERT INTO ctm.customer(prenom, nom) VALUES ('Sarah', 'Pelle');

45. INSERT INTO ctm.customer(prenom, nom) VALUES ('Amélie', 'Passa');

46. INSERT INTO ctm.customer(prenom, nom) VALUES ('Pierre', 'Afeu');

48. INSERT INTO ctm.product(description) VALUES('Clavier');

49. INSERT INTO ctm.product(description) VALUES('Souris');

50. INSERT INTO ctm.product(description) VALUES('Ecran');

51. INSERT INTO ctm.product(description) VALUES('Joystick');

52. INSERT INTO ctm.product(description) VALUES('Clé USB');

54. INSERT INTO ctm.order(description, customer_id) VALUES('Cmd 1',1);

55. INSERT INTO ctm.order(description, customer_id) VALUES('Cmd 2',2);

56. INSERT INTO ctm.order(description, customer_id) VALUES('Cmd 3',3);

57. INSERT INTO ctm.order(description, customer_id) VALUES('Cmd 4',4);

58. INSERT INTO ctm.order(description, customer_id) VALUES('Cmd 5',1);

59. INSERT INTO ctm.order(description, customer_id) VALUES('Cmd 6',1);

61. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (1,1,2);

62. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (1,2,2);

63. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (1,3,2);

64. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (2,3,1);

65. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (2,4,1);

66. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (3,4,1);

67. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (4,4,1);

68. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (4,5,1);

69. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (5,2,1);

70. INSERT INTO ctm.order_detail(order_id, product_id, quantite) VALUES (6,1,1);

X-B - Code JAVA de l'exemple

Le code source de l'exemple dont il est question dans ce tutoriel est disponible à l'adresse suivante : olivier-rozier.developpez.com/tutoriels/rest/restful-karaf-multibundle/fichiers/customerRestFulHibernateWS.zip

1. Récupérez l'archive à partir du lien ci-dessus.

2. Extraire les répertoires et les fichiers de telle manière à avoir « customerRestFullHibernateWS » comme répertoire racine. L'architecture des fichiers doit être celle-ci :

3. Copiez le répertoire « customerRestFulHibernateWS » dans votre Workspace Eclipse.

4. Pour chaque Module Maven que nous avons évoqué dans ce tutoriel (« module-data », « module-jaxrs », « module-kar », « module-services ») Dans le menu d'Eclipse, choisissez « File » ? « Import » ? « General » ? « Existing Projects into Workspace » :

Cliquez sur « Next ».

5. Dans « Select root directory », sélectionnez le chemin du répertoire « customerRestFulHibernateWS » que vous avez placé dans votre Workspace et choisissez un des modules Maven (répétez cette opération pour tous les autres modules) :

Cliquez sur « Finish ».

6. Après avoir importé tous les modules, n'oubliez pas d'importer le projet qui englobe tous les modules Maven :

Cliquez sur Finish.

XI - Conclusion

Après avoir développé une application comme celle sur laquelle nous nous sommes appuyés tout au long de ce tutoriel, nous pouvons déduire de OSGI et du développement en modules les bénéfices suivants :

- Possibilité de gérer des dépendances explicites et/ou versionnées
Les modules déclarent les dépendances dont ils ont besoin et éventuellement une plage de versions de ces dépendances.

- Atomicité
Les modules ne sont pas « packagés » avec toutes leurs dépendances.

- Facilité de changer de version
Les modules peuvent être développés et versionnés indépendamment.

- Facilité de redéployer un module en particulier
Un module peut être redéployé sans impact pour ceux qui le sont déjà.

Il nous reste à voir la manière d'effectuer les tests unitaires sur une telle application. C'est le sujet de notre tutoriel Tutoriel pour mettre en place et exécuter les tests unitaires sur un projet multibundle déployé sous Karaf et développé sous Eclipse qui s'appuie sur l'exemple que nous venons d'étudier.

À titre d'information, vous pouvez également consulter notre tutoriel concernant le développement d'un service web SOAP sous Eclipse et Karaf et son export en service OSGI grâce à Blueprint . Vous remarquerez beaucoup de similitudes avec le développement d'un service web REST et notamment que la principale différence réside dans l'usage de la bibliothèque « jax-ws » en lieu et place de « jax-rs ».

Je tiens à remercier Mickaël Baron pour la relecture technique de cet article, avec l'aimable collaboration de Milkoseck pour les remarques liées aux fonctionnalités du tutoriel et à la présence de caractères parasites ainsi que Claude Leloup pour la relecture orthographique et la rapidité dont il fait preuve pour corriger les tutoriels plus ou moins longs. Sans oublier Winjerome pour la seconde relecture orthographique.

Index

- A -

DAO (1), (2)

JPA (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12), (13), (14)

- R -

API (1), (2)

- J -

- O -

REST (1), (2), (3), (4), (5), (6)

- D -

JAXB (1), (2), (3), (4)

OSGI (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12), (13), (14), (15), (16), (17), (18), (19), (20)