I. Liens utiles▲
Nous avons profité de ce tutoriel pour aborder les bases de JPAJava Persistence API et de JAXBJava Architecture for XML Binding. Aussi, vous verrez, dans les codes sources présentés, des commentaires explicatifs sur le fonctionnement de ces APIApplication Programming Interface.
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 JPAJava Persistence API dans la couche DAOData Access Object.
La documentation de JPAJava Persistence API est disponible à cette adresse : http://aries.apache.org/modules/jpaproject.html et une introduction à JPAJava Persistence API 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 JAXBJava Architecture for XML Binding, 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 OSGIOpen Services Gateway Initiative est un ensemble de modules (bundles) OSGIOpen Services Gateway Initiative. Un bundle OSGIOpen Services Gateway Initiative 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 OSGIOpen Services Gateway Initiative, un bundle peut dépendre d'autres bundles. Par conséquent, cela signifie que, la plupart du temps, pour déployer une application OSGIOpen Services Gateway Initiative, 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 RESTRepresentational State Transfer ;
- un bundle pour la couche services métier ;
- un bundle pour la couche d'accès aux données et pour le modèle (DAOData Access Object).
En procédant ainsi, on profite que OSGIOpen Services Gateway Initiative introduit un couplage faible entre bundles pour introduire un couplage faible entre les couches que nous avons définies.
De par OSGIOpen Services Gateway Initiative 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 SOAService-Oriented Architecture 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 RESTRepresentational State Transfer 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 APIApplication Programming Interface JAVA utilisées sont :
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.
-
Créez un nouveau projet par le menu « File » ? « New » ? « Project »
-
Dans la fenêtre qui s'affiche, choisissez « General » puis « Project »
-
Nommez le projet « customerRestFulHibernateWS » puis cliquez sur « Finish ».
-
Cliquez droit sur le projet « customerRestFulHibernateWS » puis choisissez « Configure » ? « Convert to Maven Project »
- 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 :
2.
3.
4.
5.
6.
7.
<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"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
customerRestFulHibernateWS</artifactId>
<version>
0.0.1-SNAPSHOT</version>
<packaging>
pom</packaging>
</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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
<?xml version="1.0" encoding="UTF-8"?>
<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"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
customerRestFulHibernateWS</artifactId>
<version>
0.0.1-SNAPSHOT</version>
<packaging>
pom</packaging>
<modules>
<module>
module-jaxrs</module>
<module>
module-services</module>
<module>
module-data</module>
<module>
module-kar</module>
</modules>
<properties>
<cxf.version>
3.1.5</cxf.version>
<osgi.version>
6.0.0</osgi.version>
<project.build.outputEncoding>
UTF-8</project.build.outputEncoding>
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
</properties>
<!-- Introduction de la balise <dependencyManagement> qui permet de centraliser les informations de dependances.
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
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
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
d'un meme parent-->
<dependencyManagement>
<dependencies>
<!-- Module utile au "module-jaxrs" -->
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-services</artifactId>
<version>
${project.version}</version>
</dependency>
<!-- Module utile au "module-jaxrs" et au "module-services" -->
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-data</artifactId>
<version>
${project.version}</version>
</dependency>
<!-- Dependance pour BundleContext -->
<dependency>
<groupId>
org.osgi</groupId>
<artifactId>
org.osgi.core</artifactId>
<version>
${osgi.version}</version>
<scope>
provided</scope>
</dependency>
<!-- Dependance necessaire pour le service web REST (GET, POST, PUT, Response, ...) -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-frontend-jaxrs</artifactId>
<version>
${cxf.version}</version>
<scope>
provided</scope>
</dependency>
<!-- Dependances necessaires pour la dependance org.apache.cxf/cxf-rt-frontend-jaxrs -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-frontend-jaxws</artifactId>
<version>
${cxf.version}</version>
<scope>
provided</scope>
</dependency>
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-transports-http</artifactId>
<version>
${cxf.version}</version>
<scope>
provided</scope>
</dependency>
<!-- Dependance necessaire pour org.slf4j.Logger -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-transports-http-jetty</artifactId>
<version>
${cxf.version}</version>
<scope>
provided</scope>
</dependency>
<!-- Dependance pour org.osgi.service.log.LogService -->
<dependency>
<groupId>
org.osgi</groupId>
<artifactId>
org.osgi.compendium</artifactId>
<version>
5.0.0</version>
<scope>
provided</scope>
</dependency>
<!-- JPA -->
<dependency>
<groupId>
org.eclipse.persistence</groupId>
<artifactId>
javax.persistence</artifactId>
<version>
2.1.1</version>
<scope>
provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
3.3</version>
<configuration>
<compilerVersion>
1.7</compilerVersion>
<source>
1.7</source>
<target>
1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</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 RESTRepresentational State Transfer ». Ce module dépendra donc du projet « customerRestFulHibernateWS.
Ce bundle correspond à la partie suivante du schéma de notre application :
-
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 ».
-
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 ».
-
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 ».
-
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 ». -
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 ».
-
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 : -
Cliquez droit sur ce même répertoire puis sélectionnez « New » ? « Other » et choisissez « Class ». Créez une classe nommée « CustomerRestService ».
-
Complétez le fichier « CustomerRestService.java » de la manière suivante :
module-jaxrs/CustomerRestService.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.package
com.exemple.customerRestFulHibernateWS.jaxrs;import
javax.ws.rs.core.Response;import
javax.ws.rs.Consumes;import
javax.ws.rs.DELETE;import
javax.ws.rs.GET;import
javax.ws.rs.POST;import
javax.ws.rs.PUT;import
javax.ws.rs.Path;import
javax.ws.rs.PathParam;import
javax.ws.rs.Produces;import
org.slf4j.Logger;import
org.slf4j.LoggerFactory;import
java.util.List;import
com.exemple.customerRestFulHibernateWS.services.CustomerService;import
com.exemple.customerRestFulHibernateWS.data.model.Customer;import
com.exemple.customerRestFulHibernateWS.data.model.Order;import
com.exemple.customerRestFulHibernateWS.data.model.Product;/*La classe CustomerRestService est l'implementation d'un service. Elle permet de faire l'interfacage entre le client et la couche metier.
*La couche metier sera accessible via l'interface CustomerService du package com.exemple.karaf.services. Pour cette raison, il est necessaire de declarer
*une instance de cette interface en tant que membre de la classe CustomerRestService*/
public
class
CustomerRestService{
private
static
final
Logger LOG=
LoggerFactory.getLogger
(
CustomerService.class
);private
CustomerService customerService;public
CustomerServicegetCustomerService
(
){
return
customerService;}
public
void
setCustomerService
(
CustomerService customerService){
this
.customerService=
customerService;}
/**
* Cette methode recupere un client a partir de son idCustomer.
* Elle est mappee a la requete HTTP GET : "http://localhost:8181/cxf/olivier/customer/{customerId}". La valeur
* de {customerId} sera passee en parametre a la methode par utilisation de l'annotation @PathParam.
*
<
p/
>
* La methode retournera un objet de la classe Customer par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customer/2
*/
@GET
@Path
(
"/customer/{id}/"
)@Produces
(
"application/xml"
)public
CustomergetCustomer
(
@PathParam
(
"id"
)long
customerId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getCustomer({})]"
, customerId);return
customerService.getCustomerById
(
customerId);}
/**
* Cette methode met a jour un client a partir d'informations transmises en parametre dans un objet JSON.
* Elle est mappee a la requete HTTP PUT http://localhost:8181/cxf/olivier/customer/update.
* On peut ainsi envoyer la representation XML d'un objet Customer.
* La representation XML sera obtenue par transformation d'un Customer par JAXB.
*
<
p/
>
* Cette methode met a jour un objet Customer dans notre map locale puis utilise la classe Response pour construire
* une reponse HP appropriee : soit OK si la mise a jour a ete effectuee avec succes (Traduction du statut HTTP 200/OK)
* ou NON MODIFIE si la mise a jour de l'objet Customer a echoue (Traduction du statut HTTP 304/Not Modified).
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customer/update
* avec pour parametre JSON : {"Customer":{"customerId":2, "nom":"Croche","prenom": "Sarah"}}
*/
@PUT
@Path
(
"/customer/update"
)@Consumes
({
"application/xml"
,"application/json"
}
)public
ResponseupdateCustomer
(
Customer customer){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[updateCustomer({})]"
, customer.getIdCustomer
(
));final
Customer c=
customerService.getCustomerById
(
customer.getIdCustomer
(
)); Response r;if
(
c!=
null
){
c.setNom
(
customer.getNom
(
)); customerService.updateCustomer
(
c); r=
Response.ok
(
).build
(
);}
else
{
r=
Response.notModified
(
).build
(
);}
return
r;}
/**
* 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.
* Cette methode est mappee a une requete HTTP PUT de la forme http://localhost:8181/cxf/olivier/customer/{customerId}/order.
* On peut ainsi envoyer la representation XML d'un objet Customer.
* La representation XML sera obtenue par transformation d'un Customer par JAXB.
*
<
p/
>
* Cette methode ajoute une commande à un Customer dans notre map locale puis utilise la classe Response pour construire
* une reponse HP appropriee : soit OK si la mise a jour a ete effectuee avec succes (Traduction du statut HTTP 200/OK)
* ou NON MODIFIE si la mise a jour de l'objet Customer a echoue (Traduction du statut HTTP 304/Not Modified).
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customer/1/order
* avec pour parametre JSON : {"Order":{"description":Nouvelle commande, "products": [{"idProduct":1,"description":"Clavier"},{"idProduct":2,"description":"Souris"}]}}
*/
@PUT
@Path
(
"/customer/{id}/order"
)@Consumes
({
"application/xml"
,"application/json"
}
)public
ResponseaddOrderToCustomer
(
@PathParam
(
"id"
)long
customerId, Order order){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[addOrderToCustomer({}, {})]"
, customerId, order.toString
(
));final
Customer c=
customerService.getCustomerById
(
customerId); Response r;if
(
c!=
null
){
if
(
customerService.addOrderToCustomer
(
c, order)!=
null
){
r=
Response.ok
(
).build
(
);}
else
{
r=
Response.notModified
(
).build
(
);}
}
else
{
r=
Response.notModified
(
).build
(
);}
return
r;}
/**
* Cette methode insere un client fourni en parametre dans un objet JSON.
* Elle est mappe a la requete HTTP POST http://localhost:8181/cxf/olivier/customer/add
*
<
p/
>
* Apres que cette methode a ajoute le client dans la map local, elle utilisera la classe Response pour construire la reponse HTTP
* 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.
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customer/add
* avec pour parametre JSON : {"Customer":{"nom":"Niplancheharepasser","prenom": "Jennifer"}}
*/
@POST
@Path
(
"/customer/add"
)@Consumes
({
"application/xml"
,"application/json"
}
)public
ResponseaddCustomer
(
Customer customer){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[addCustomer({})]"
, customer.toString
(
)); customerService.addCustomer
(
customer);return
Response.ok
(
).type
(
"application/xml"
).entity
(
customer).build
(
);}
/**
* Cette methode permet de supprimer un client dont le nom est fourni en parametre.
* Elle est mappee a une requete HTTP DELETE du type : "http://localhost:8181/cxf/olivier/customer/{customerId}".
* La valeur pour {id} sera passee en tant que parametre en utilisant l'annotation @PathParam.
*
<
p/
>
* Cette methode utilise la classe Response pour creer une reponse HTTP : soit le statut HTTP 200/OK si l'objet Customer
* a ete correctement supprime de la map local, soit le statut HTTP 304/Not Modified si la suppression a echoue.
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customer/5
*/
@DELETE
@Path
(
"/customer/{id}/"
)public
ResponsedeleteCustomer
(
@PathParam
(
"id"
)long
customerId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[deleteCustomer({})]"
, customerId);final
Customer customer=
customerService.getCustomerById
(
customerId); Response r=
null
;if
(
customer!=
null
){
customerService.removeCustomer
(
customerId); r=
Response.ok
(
).build
(
);}
else
{
r=
Response.notModified
(
).build
(
);}
return
r;}
/**
* Cette methode permet de recuperer les commandes d'un client dont l'identifiant est fourni dans la requete.
* Elle est mappee a une requete HTTP GET du type : "http://localhost:8181/cxf/olivier/customer/{customerId}/orders".
* La valeur pour {id} sera passee en tant que parametre en utilisant l'annotation @PathParam.
*
<
p/
>
* 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.
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customer/1/orders
*/
@GET
@Path
(
"/customer/{customerId}/orders/"
)@Produces
(
"application/json"
)public
List<
Order>
getOrder
(
@PathParam
(
"customerId"
)long
customerId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getOrder({})]"
, customerId);return
customerService.getCustomerOrders
(
customerId);}
/**
* Cette methode permet de recuperer une commande dont l'identifiant est fourni en parametre de meme que l'identifiant du client.
* Elle est mappee a une requete HTTP GET du type : "http://localhost:8181/cxf/olivier/customers/{customerId}/order/{orderId}".
*
<
p/
>
* La methode retournera un objet de la classe Order par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customers/1/order/1
*/
@GET
@Path
(
"/customer/{customerId}/order/{orderId}"
)@Produces
(
"application/xml"
)public
OrdergetOrder
(
@PathParam
(
"customerId"
)long
customerId,@PathParam
(
"orderId"
)long
orderId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getOrder({}, {})]"
, customerId, orderId);return
customerService.getCustomerOrder
(
customerId, orderId);}
/**
* Cette methode permet de recuperer un produit donne dans une commande donnee d'un client donne.
* Les identifiants du produit, de la commande et du client sont fournis en parametres.
* Cette methode est mappee a une requete HTTP GET du type : "http://localhost:8181/cxf/olivier/customer/{customerId}/order/{orderId}/product/{productId}".
*
<
p/
>
* La methode retournera un objet de la classe Product par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customer/1/order/1/product/3
*/
@GET
@Path
(
"/customer/{customerId}/order/{orderId}/product/{productId}"
)@Produces
(
"application/xml"
)public
ProductgetProduct
(
@PathParam
(
"customerId"
)long
customerId,@PathParam
(
"orderId"
)long
orderId,@PathParam
(
"productId"
)long
productId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getProduct({}, {}, {})]"
, customerId, orderId, productId);return
customerService.getProduct
(
customerId, orderId, productId);}
/**
* Cette methode permet de recuperer tous les produits d'une commande donnee d'un client donne.
* Les identifiants du client et de la commande seront fournis dans l'URL.
* Cette methode est mappee a une requete HTTP GET du type : "http://localhost:8181/cxf/olivier/customer/{customerId}/order/{orderId}/products".
*
<
p/
>
* La methode retournera un objet de la classe Product par creation d'une reponse HTTP. Cet objet sera transforme en XML par JAXB.
*
<
p/
>
* Exemple d'appel : http://localhost:8181/cxf/olivier/customer/1/order/1/products
*/
@GET
@Path
(
"/customer/{customerId}/order/{orderId}/products"
)@Produces
(
"application/xml"
)public
List<
Product>
getProducts
(
@PathParam
(
"customerId"
)long
customerId,@PathParam
(
"orderId"
)long
orderId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getProducts({}, {})]"
, customerId, orderId);return
customerService.getProducts
(
customerId, orderId);}
}
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 OSGIOpen Services Gateway Initiative.
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 RESTRepresentational State Transfer « 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.
-
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.xmlSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.<blueprint
xmlns
=
"http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
jaxws
=
"http://cxf.apache.org/blueprint/jaxws"
xmlns
:
jaxrs
=
"http://cxf.apache.org/blueprint/jaxrs"
xmlns
:
cxf
=
"http://cxf.apache.org/blueprint/core"
xsi
:
schemaLocation
=
"
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd
http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd"
>
<!-- Configuration des logs CXF afin de faire apparaître les requetes recues et les reponses fournies, dans le journal de Karaf -->
<
cxf
:
bus
id
=
"bus"
>
<
cxf
:
features>
<
cxf
:
logging/>
</
cxf
:
features>
</
cxf
:
bus>
<!-- Configuration du endpoint JAX-RS dans le container OSGI-->
<
jaxrs
:
server
address
=
"/olivier"
id
=
"olivier"
>
<
jaxrs
:
serviceBeans>
<ref
component-id
=
"customerRestService"
/>
</
jaxrs
:
serviceBeans>
</
jaxrs
:
server>
<!-- Declaration du bean customerRestService implemente par le POJO com.exemple.karaf.jaxrs.CustomerRestService-->
<!-- Ce bean possede une propriete a injecter. L'injection de cette propriete (ici une instance de la classe CustomerService) est faite
immediatement apres la creation du bean -->
<bean
id
=
"customerRestService"
class
=
"com.exemple.customerRestFulHibernateWS.jaxrs.CustomerRestService"
>
<property
name
=
"customerService"
ref
=
"customerService"
/>
</bean>
<!-- Definition des dependances -->
<reference
id
=
"customerService"
interface
=
"com.exemple.customerRestFulHibernateWS.services.CustomerService"
/>
</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.
- Complétez le fichier « pom.xml » de la manière suivante afin d'y renseigner les packages nécessaires au bon fonctionnement du module :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
<?xml version="1.0" encoding="UTF-8"?>
<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"
>
<modelVersion>
4.0.0</modelVersion>
<parent>
<artifactId>
customerRestFulHibernateWS</artifactId>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<version>
0.0.1-SNAPSHOT</version>
</parent>
<artifactId>
module-jaxrs</artifactId>
<packaging>
bundle</packaging>
<name>
module-jaxrs Blueprint Bundle</name>
<description>
module-jaxrs OSGi blueprint bundle project.</description>
<properties>
<cxf.version>
2.7.10</cxf.version>
<osgi.version>
6.0.0</osgi.version>
<project.build.outputEncoding>
UTF-8</project.build.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-services</artifactId>
</dependency>
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-data</artifactId>
</dependency>
<dependency>
<groupId>
org.osgi</groupId>
<artifactId>
org.osgi.core</artifactId>
<scope>
provided</scope>
</dependency>
<!-- Dependance necessaire pour le service web REST (GET, POST, PUT, Response, ...) -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-frontend-jaxrs</artifactId>
<scope>
provided</scope>
</dependency>
<!-- Dependance necessaire pour org.slf4j.Logger -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-transports-http-jetty</artifactId>
<scope>
provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>
org.apache.felix</groupId>
<artifactId>
maven-bundle-plugin</artifactId>
<version>
3.0.0</version>
<extensions>
true</extensions>
<configuration>
<instructions>
<!-- Le fichier MANIFEST.MF sera rempli avec les informations ci-dessous. Pour plus d'informations : http://wiki.osgi.org/wiki/Category:Manifest_Header -->
<Bundle-SymbolicName>
${project.artifactId}</Bundle-SymbolicName>
<!-- Nom qui identifie le bundle de maniere unique -->
<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 -->
<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 -->
<Import-Package>
*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</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 :
-
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 ».
-
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 ».
-
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 ».
-
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 ». -
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éé :
-
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 ».
-
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 ».
-
Le fichier « CustomerService.java » sera rempli de la manière suivante :
module-services/CustomerService.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.package
com.exemple.customerRestFulHibernateWS.services;import
java.util.List;import
com.exemple.customerRestFulHibernateWS.data.model.Customer;import
com.exemple.customerRestFulHibernateWS.data.model.Order;import
com.exemple.customerRestFulHibernateWS.data.model.Product;public
interface
CustomerService{
CustomergetCustomerById
(
long
customerId); CustomeraddCustomer
(
Customer customer); CustomerupdateCustomer
(
Customer customer); CustomeraddOrderToCustomer
(
Customer customer, Order order);void
removeCustomer
(
long
customerId); OrdergetCustomerOrder
(
long
customerId,long
orderId); List<
Order>
getCustomerOrders
(
long
customerId); ProductgetProduct
(
long
customerId,long
orderId,long
productId); List<
Product>
getProducts
(
long
customerId,long
orderId);}
-
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 ».
-
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 :
-
Le code à intégrer dans le fichier « CustomerServiceImpl.java » est le suivant :
module-services/CustomerServiceImpl.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.package
com.exemple.customerRestFulHibernateWS.services.impl;import
org.slf4j.Logger;import
org.slf4j.LoggerFactory;import
java.util.List;import
java.util.ArrayList;import
com.exemple.customerRestFulHibernateWS.data.model.Customer;import
com.exemple.customerRestFulHibernateWS.data.model.Order;import
com.exemple.customerRestFulHibernateWS.data.model.Product;import
com.exemple.customerRestFulHibernateWS.data.CustomerDao;import
com.exemple.customerRestFulHibernateWS.services.CustomerService;public
class
CustomerServiceImplimplements
CustomerService{
private
CustomerDao customerDao;private
static
final
Logger LOG=
LoggerFactory.getLogger
(
CustomerDao.class
);public
CustomergetCustomerById
(
long
customerId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getCustomerById({})]"
, customerId);return
customerDao.findCustomer
(
customerId);}
public
CustomeraddCustomer
(
Customer customer){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[addCustomer({})]"
, customer.toString
(
));return
customerDao.saveCustomer
(
customer);}
public
CustomerupdateCustomer
(
Customer customer){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[updateCustomer({})]"
, customer.toString
(
));return
customerDao.saveExistingCustomer
(
customer);}
public
CustomeraddOrderToCustomer
(
Customer customer, Order order){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[addOrderToCustomer({}, {})]"
, customer.toString
(
), order.toString
(
));/*Si on souhaite inserer un nouveau produit dans la table product quand on insere une commande (c'est un exemple!!!)
* on utilise les trois lignes ci-dessous et on modifie la relation
* @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
* en
* @ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER) dans la classe Order.
* De cette maniere, grace à CascadeType.MERGE qui est inclus dans CascadeType.ALL, on inserera dans la table "order",
* dans la table "order_detail" et dans la table "product"
order.setCustomer(customer);
customer.addOrder(order);
return customerDao.saveExistingCustomer(customer);
*/
/*
* Comme on ne souhaite pas inserer de nouveaux produits dans la table "product" quand on insere une commande,
* il nous faut recuperer les entite "Product" correspondant aux donnees fournies en parametres.
* Afin de ne pas inserer de nouveau produit dans la table product, on recupere les entites de product existantes
*/
List<
Product>
listProducts=
new
ArrayList<
Product>(
);for
(
Product product: order.getProducts
(
)){
Product p=
customerDao.findProduct
(
product.getIdProduct
(
));if
(
p!=
null
){
listProducts.add
(
p);}
else
{
//Le produit n'existe pas dans la base de donnees
return
null
;}
}
order.setProducts
(
listProducts); order.setCustomer
(
customer); customer.addOrder
(
order);return
customerDao.saveExistingCustomer
(
customer);}
public
void
removeCustomer
(
long
customerId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[removeCustomer({})]"
, customerId); customerDao.deleteCustomer
(
customerId);}
public
OrdergetCustomerOrder
(
long
customerId,long
orderId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getCustomerOrder({}, {})]"
, customerId, orderId); Customer customer=
customerDao.findCustomer
(
customerId);for
(
Order order: customer.getOrders
(
)){
if
(
order.getIdOrder
(
)==
orderId){
return
order;}
}
return
null
;}
public
List<
Order>
getCustomerOrders
(
long
customerId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getCustomerOrders({})]"
, customerId); List<
Order>
commandes=
new
ArrayList<
Order>(
); Customer customer=
customerDao.findCustomer
(
customerId);/*JPA retourne l'ensemble des enregistrements qui sont lies entre eux dans les differentes tables. On a quelque chose de ce genre :
* idCustomer, nom, prenom, customer_id, idOrder, idOrder, customer_id, description, order_id, product_id, product_id, description
1;"Rozier";"Olivier";1;1;1;1;"Cmd 1";1;3;3;"Ecran"
1;"Rozier";"Olivier";1;5;5;1;"Cmd 5";5;2;2;"Souris"
1;"Rozier";"Olivier";1;6;6;1;"Cmd 6";6;1;1;"Clavier"
1;"Rozier";"Olivier";1;1;1;1;"Cmd 1";1;1;1;"Clavier"
1;"Rozier";"Olivier";1;1;1;1;"Cmd 1";1;2;2;"Souris"
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.
Il est donc necessaire de recuperer une seule fois chaque commande afin que JAXB puisse creer un fichier XML sans noeud duplique.
*/
for
(
Order order: customer.getOrders
(
)){
//Recuperation des orders differents
if
(!
commandes.contains
(
order)){
commandes.add
(
order);}
}
return
commandes;}
public
ProductgetProduct
(
long
customerId,long
orderId,long
productId){
LOG.info
(
getClass
(
).getSimpleName
(
)+
"[getProduct({}, {}, {})]"
, customerId, orderId, productId); Order order=
this
.getCustomerOrder
(
customerId, orderId);for
(
Product product: order.getProducts
(
)){
if
(
product.getIdProduct
(
)==
productId){
return
product;}
}
return
null
;}
public
List<
Product>
getProducts
(
long
customerId,long
orderId){
Order order=
this
.getCustomerOrder
(
customerId, orderId);return
order.getProducts
(
);}
public
void
setCustomerDao
(
final
CustomerDao customerDao){
this
.customerDao=
customerDao;}
}
-
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.xmlSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.<blueprint
xmlns
=
"http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
cm
=
"http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
xmlns
:
jaxws
=
"http://cxf.apache.org/blueprint/jaxws"
xmlns
:
jaxrs
=
"http://cxf.apache.org/blueprint/jaxrs"
xmlns
:
cxf
=
"http://cxf.apache.org/blueprint/core"
xsi
:
schemaLocation
=
"
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd
http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd"
>
<!-- Definition de l'enregistrement du service dans le registre de services OSGI. On renseigne le bean qui fournit le service via l'attribut ref -->
<service
ref
=
"customerService"
interface
=
"com.exemple.customerRestFulHibernateWS.services.CustomerService"
>
<service-properties>
<entry
key
=
"service.exported.interfaces"
value
=
"*"
/>
</service-properties>
</service>
<!-- 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
(passage par reference local versus passage par valeur distant). Pour eviter qu'une application appelle accidentellement un service exporte qui est uniquement designe
par un appel par "passage de valeur", il faut cacher le lookup local.
La solution est d'exporter le meme bean deux fois : une fois pour les appels distants et une autre pour les appels locaux.
En d'autres termes, on ajoute un autre element avec la meme configuration mais sans la propriete "service.exported.interfaces"-->
<service
ref
=
"customerService"
interface
=
"com.exemple.customerRestFulHibernateWS.services.CustomerService"
/>
<!-- Declaration du bean customerService implemente par le POJO com.example.services.impl.CustomerServiceImpl-->
<!-- 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
immediatement apres la creation du bean -->
<bean
id
=
"customerService"
class
=
"com.exemple.customerRestFulHibernateWS.services.impl.CustomerServiceImpl"
>
<property
name
=
"customerDao"
ref
=
"customerDao"
/>
</bean>
<!-- Definition des dependances -->
<reference
id
=
"customerDao"
interface
=
"com.exemple.customerRestFulHibernateWS.data.CustomerDao"
/>
</blueprint>
- Complétez le contenu du fichier « pom.xml » associé au projet « module-data » de la manière suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
<?xml version="1.0" encoding="UTF-8"?>
<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"
>
<modelVersion>
4.0.0</modelVersion>
<parent>
<artifactId>
customerRestFulHibernateWS</artifactId>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<version>
0.0.1-SNAPSHOT</version>
</parent>
<artifactId>
module-services</artifactId>
<packaging>
bundle</packaging>
<name>
module-services Blueprint Bundle</name>
<description>
module-services OSGi blueprint bundle project.</description>
<properties>
<cxf.version>
2.7.10</cxf.version>
<osgi.version>
6.0.0</osgi.version>
<project.build.outputEncoding>
UTF-8</project.build.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-data</artifactId>
<version>
0.0.1-SNAPSHOT</version>
<type>
bundle</type>
</dependency>
<dependency>
<groupId>
org.osgi</groupId>
<artifactId>
org.osgi.core</artifactId>
</dependency>
<!-- Dependance necessaire pour org.slf4j.Logger -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-transports-http-jetty</artifactId>
<scope>
provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>
org.apache.felix</groupId>
<artifactId>
maven-bundle-plugin</artifactId>
<version>
3.0.0</version>
<extensions>
true</extensions>
<configuration>
<instructions>
<!-- Le fichier MANIFEST.MF sera rempli avec les information ci-dessous. Pour plus d'informations : http://wiki.osgi.org/wiki/Category:Manifest_Header -->
<Bundle-SymbolicName>
${project.artifactId}</Bundle-SymbolicName>
<!-- Nom qui identifie le bundle de maniere unique -->
<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 -->
<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 -->
<Import-Package>
*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</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 (ORMObject Relational Mapping). Pour cette raison, nous utiliserons JPAJava Persistence API 2 afin d'assurer la persistance des données.
JPAJava Persistence API 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.
JPAJava Persistence API fait tendre les frameworks d'accès aux données existant, tel hibernate, à devenir des implantations particulières de cette norme.
JPAJava Persistence API 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'ORMObject Relational Mapping et au modèle, nous utiliserons Hibernate comme framework de persistance, tout en respectant JPAJava Persistence API 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 ORMObject Relational Mapping (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 :
-
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 » ;
-
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 » ;
-
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 ».
-
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 ». -
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éé :
-
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 » ;
-
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 ».
-
complétez le fichier « CustomerDao.java » de la manière suivante :
Module-data/CustomerDao.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.package
com.exemple.customerRestFulHibernateWS.data;import
com.exemple.customerRestFulHibernateWS.data.model.Customer;import
com.exemple.customerRestFulHibernateWS.data.model.Order;import
com.exemple.customerRestFulHibernateWS.data.model.Product;public
interface
CustomerDao{
CustomerfindCustomer
(
final
long
customerId); CustomersaveExistingCustomer
(
final
Customer customer); CustomersaveCustomer
(
final
Customer customer);void
deleteCustomer
(
final
long
customerId); OrderfindOrder
(
final
long
orderId); ProductfindProduct
(
final
long
productId);}
-
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 ».
-
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.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.package
com.exemple.customerRestFulHibernateWS.data.model;//Packages propres a JAXB
import
javax.xml.bind.annotation.XmlRootElement;import
javax.xml.bind.annotation.XmlTransient;import
javax.xml.bind.annotation.XmlType;import
java.util.List;//Packages propres a JPA
import
javax.persistence.Entity;import
javax.persistence.GeneratedValue;import
javax.persistence.GenerationType;import
javax.persistence.Id;import
javax.persistence.OneToMany;import
javax.persistence.FetchType;import
javax.persistence.Table;import
javax.persistence.CascadeType;import
javax.persistence.Column;/**
* La classe Customer est un objet JAVA contenant des methodes get et set
*
<
p/
>
* En ajoutant l'annotation @XmlRootElement, nous offrons la possibilite a JAXB de transformer cet objet en document XML et inversement.
*
<
p/
>
* La representation XML d'un customer ressemblera a ceci :
*
<
Customer
>
*
<
idCustomer
>
123
<
/idCustomer
>
*
<
prenom
>
Olivier
<
/prenom
>
*
<
nom
>
Rozier
<
/nom
>
*
<
/Customer
>
*/
@XmlRootElement
(
name=
"Customer"
)@XmlType
(
propOrder={
"idCustomer"
,"prenom"
,"nom"
}
)//Fixe l'ordre des champs dans le fichier XML
@Entity
@Table
(
name=
"customer"
,schema=
"ctm"
)public
class
Customer{
@Id
//specifie la cle primaire
@Column
(
name=
"idCustomer"
, nullable=
false
)@GeneratedValue
(
strategy=
GenerationType.IDENTITY)private
long
idCustomer;@Column
(
name=
"prenom"
, length=
50
)private
String prenom;@Column
(
name=
"nom"
, length=
50
)private
String nom;//FetchType.LAZY indique a la JPA runtime qu'on souhaite reporter le chargement des champs au moment de l'acces
//Ceci est completement transparent : les donnees sont chargees depuis la base de donnees dans les objets discretement lorsqu'on tente de lire
//les champs pour la premiere fois.
//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
//que tous les champs sont renseignes.
@OneToMany
(
cascade=
CascadeType.ALL, fetch=
FetchType.EAGER, mappedBy=
"customer"
)private
List<
Order>
orders;public
long
getIdCustomer
(
){
return
idCustomer;}
public
void
setIdCustomer
(
long
idCustomer){
this
.idCustomer=
idCustomer;}
public
StringgetNom
(
){
return
nom;}
public
void
setNom
(
String nom){
this
.nom=
nom;}
public
StringgetPrenom
(
){
return
prenom;}
public
void
setPrenom
(
String prenom){
this
.prenom=
prenom;}
@XmlTransient
//Permet d'eviter de faire une boucle infinie a la generation du XML
public
List<
Order>
getOrders
(
){
return
orders;}
public
void
setOrders
(
List<
Order>
orders){
this
.orders=
orders;}
public
void
addOrder
(
Order order){
this
.orders.add
(
order);}
@Override
public
StringtoString
(
){
return
getClass
(
).getSimpleName
(
)+
"[idCustomer="
+
idCustomer+
", nom="
+
nom+
", prenom="
+
prenom+
"]"
;}
}
module-data/Order.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.package
com.exemple.customerRestFulHibernateWS.data.model;import
java.util.List;//Packages propres a JAXB
import
javax.xml.bind.annotation.XmlRootElement;import
javax.xml.bind.annotation.XmlAccessorType;import
javax.xml.bind.annotation.XmlAccessType;import
javax.xml.bind.annotation.XmlTransient;//Packages propres a JPA
import
javax.persistence.Entity;import
javax.persistence.GeneratedValue;import
javax.persistence.GenerationType;import
javax.persistence.Id;import
javax.persistence.Table;import
javax.persistence.Column;import
javax.persistence.JoinColumn;import
javax.persistence.JoinTable;import
javax.persistence.FetchType;import
javax.persistence.ManyToOne;import
javax.persistence.ManyToMany;import
javax.persistence.CascadeType;/**
* La classe OrderBean est un objet JAVA contenant des methodes get et set et est aussi utilisee pour les commandes retournees par
* la classe CustomerServiceImpl.
*
<
p/
>
* En ajoutant l'annotation @XmlRootElement, nous offrons la possibilite a JAXB de transformer cet objet en document XML et inversement.
*
<
p/
>
* La representation XML pour un OrderBean est la suivante :
*
<
Order
>
*
<
idOrder
>
223
<
/idOrder
>
*
<
description
>
Order 223
<
/description
>
*
<
/Order
>
*/
@XmlRootElement
(
name=
"Order"
)//@XmlAccessorType(XmlAccessType.FIELD) est obligatoire car le binding par defaut de JAXB est XmlAccessType.PUBLIC_MEMBER.
//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"
//et la methode "getProducts()". En utilisant @XmlAccessorType(XmlAccessType.FIELD), le binding ne se fait qu'une fois.
@XmlAccessorType
(
XmlAccessType.FIELD)@Entity
@Table
(
name=
"order"
, schema=
"ctm"
)public
class
Order{
@Id
@Column
(
name=
"idOrder"
, nullable=
false
)@GeneratedValue
(
strategy=
GenerationType.IDENTITY)private
long
idOrder;private
String description;//Chaque commande doit avoir un client associe donc optional=false
@ManyToOne
(
optional=
false
)@JoinColumn
(
name=
"customer_id"
, referencedColumnName=
"idcustomer"
, nullable=
false
)private
Customer customer;//On utilise une table nommee "order_detail" pour modeliser l'association entre la table "order" et la table "product".
//L'annotation @JoinTable est utilisee pour specifier une table de la base de donnees qui associe "order_id" avec "product_id".
//L'entite qui specifie le @JoinTable est le proprietaire de la relation. Dans notre cas, l'entite Order est le proprietaire de la relation
//avec l'entite Product.
@ManyToMany
(
cascade=
{
CascadeType.PERSIST, CascadeType.REFRESH}
, fetch=
FetchType.EAGER)@JoinTable
(
name=
"order_detail"
, joinColumns=
@JoinColumn
(
name=
"order_id"
, referencedColumnName=
"idOrder"
), inverseJoinColumns=
@JoinColumn
(
name=
"product_id"
, referencedColumnName=
"idProduct"
), schema=
"ctm"
)private
List<
Product>
products;public
CustomergetCustomer
(
){
return
customer;}
public
void
setCustomer
(
Customer customer){
this
.customer=
customer;}
public
long
getIdOrder
(
){
return
idOrder;}
public
void
setIdOrder
(
long
idOrder){
this
.idOrder=
idOrder;}
public
StringgetDescription
(
){
return
description;}
public
void
setDescription
(
String d){
this
.description=
d;}
@XmlTransient
//Permet d'eviter de faire une boucle infinie a la generation du XML
public
List<
Product>
getProducts
(
){
return
products;}
public
void
setProducts
(
List<
Product>
products){
this
.products=
products;}
@Override
public
StringtoString
(
){
return
getClass
(
).getSimpleName
(
)+
"[idOrder="
+
idOrder+
", desription="
+
description+
"]"
;}
}
La relation « ManyToMany » définie par l'annotation JPAJava Persistence API « @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.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.@Entity
@Table
(
name=
"order"
, schema=
"ctm"
)public
class
Order{
...@OneToMany
(
mappedBy=
"order"
)private
List<
OrderDetail>
products; ...}
module-data/Product.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.@Entity
@Table
(
name=
"product"
, schema=
"ctm"
)public
class
Product{
...@OneToMany
(
mappedBy=
"product"
)private
List<
OrderDetail>
orders; ...}
//Ajout d'une commande (a mettre dans la couche Service Metier)
public
void
addOrder
(
Order order,int
quantity){
OrderDetail od=
new
OrderDetail
(
); od.setOrder
(
order); od.setProduct
(
this
); od.setOrderId
(
order.getIdOrder
(
)); od.setProductId
(
this
.getIdProduct
(
)); od.setQuantity
(
quantity) ;this
.orders.add
(
od); order.getProducts
(
).add
(
od); ...}
module-data/OrderDetail.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.@Entity
@Table
(
name=
"order_detail"
, schema=
"ctm"
)@IdClass
(
OrderDetailId.class
)public
class
OrderDetail{
@Id
@Column
(
name=
"order_id"
)private
long
orderId;@Id
@Column
(
name=
"product_id"
)private
long
productId;@column
(
name=
"quantity"
)private
int
quantity;@ManyToOne
@JoinColumn
(
name=
"order_id"
, updatable=
false
, insertable=
false
, referencedColumnName=
"idOrder"
)private
Order order;@JoinColumn
(
name=
"product_id"
, updatable=
false
, insertable=
false
, referencedColumnName=
"idProduct"
)private
Product product; ...//completez avec les setter et les getter propres aux proprietes orderId, productId, quantity, order et product
...}
module-data/orderDetailIdSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.@Entity
public
class
OrderDetailIdimplements
Serializable{
private
static
final
long
serialVersionUID=
315964054054
L;private
long
orderId;private
long
productId;public
OrderDetailId
(
){}
public
OrderDetailId
(
long
orderId,long
productId){
this
.orderId=
orderId;this
.productId=
productId;}
//Il n'y a pas de methode set car la cle primaire ne doit pas etre changee
public
long
getOrderId
(
){
return
orderId;}
public
long
getProductId
(
){
return
productId;}
//On doit surcharger les methodes equals() et hashcode() car les objets cle primaire ont besoin d'etre identifies de maniere unique.
//On peut les utiliser dans des collections en utilisant le hashage.
@Override
public
int
hashCode
(
){
return
(
int
)(
orderId+
productId);}
@Override
public
boolean
equals
(
Object object){
if
(
objectinstanceof
OrderDetailId){
OrderDetail otherId=
(
OrderDetail) object;return
(
otherId.orderId==
this
.orderId)&&
(
otherId.productId==
this
.productId);}
return
false
;}
@Override
public
StringtoString
(
){
return
getClass
(
).getSimpleName
(
)+
"[seId="
+
seId+
", applicationId="
+
applicationId+
", typeFluxId="
+
typeFluxId+
", etatFluxId="
+
etatFluxId+
"]"
;}
}
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.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.package
com.exemple.customerRestFulHibernateWS.data.model;import
java.util.List;//Packages propres a JAXB
import
javax.xml.bind.annotation.XmlRootElement;import
javax.xml.bind.annotation.XmlTransient;import
javax.xml.bind.annotation.XmlType;//Packages propres a JPA
import
javax.persistence.Entity;import
javax.persistence.GeneratedValue;import
javax.persistence.GenerationType;import
javax.persistence.Column;import
javax.persistence.Id;import
javax.persistence.Table;import
javax.persistence.ManyToMany;import
javax.persistence.FetchType;/**
* La classe Product est un objet JAVA contenant des methodes get et set
*
<
p/
>
* En ajoutant l'annotation @XmlRootElement, nous offrons la possibilite a JAXB de transformer cet objet en document XML et inversement.
*
<
p/
>
* La representation XML d'un Product ressemblera a ceci :
*
*
<
Product
>
*
<
idProduct
>
10010
<
/idProduct
>
*
<
description
>
produit 1
<
/description
>
*
<
/Product
>
*/
@XmlRootElement
(
name=
"Product"
)@XmlType
(
propOrder={
"idProduct"
,"description"
}
)//Fixe l'ordre des champs dans le fichier XML
@Entity
@Table
(
name=
"product"
,schema=
"ctm"
)public
class
Product{
@Id
@Column
(
name=
"idProduct"
, nullable=
false
)@GeneratedValue
(
strategy=
GenerationType.IDENTITY)private
Long idProduct;@Column
(
name=
"description"
, length=
200
)private
String description;@ManyToMany
(
mappedBy=
"products"
,fetch=
FetchType.EAGER)private
List<
Order>
orderList;public
long
getIdProduct
(
){
return
idProduct;}
public
void
setIdProduct
(
long
idProduct){
this
.idProduct=
idProduct;}
public
StringgetDescription
(
){
return
description;}
public
void
setDescription
(
String d){
this
.description=
d;}
@XmlTransient
//Permet d'eviter de faire une boucle infinie a la generation du XML
public
List<
Order>
getOrderList
(
){
return
orderList;}
public
void
setOrderList
(
List<
Order>
orderList){
this
.orderList=
orderList;}
}
-
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 » ;
-
cliquez droit sur le package « com.exemple.customerRestFulHibernateWS.data.impl » puis choisissez « New » ? « Class » afin de créer la classe « CustomerDaoImpl ».
-
complétez le fichier « CustomerDaoImpl.java » de la manière suivante :
Module-data/CustomerDaoImpl.javaSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.package
com.exemple.customerRestFulHibernateWS.data.impl;import
javax.persistence.EntityManager;import
javax.persistence.PersistenceContext;import
com.exemple.customerRestFulHibernateWS.data.CustomerDao;import
com.exemple.customerRestFulHibernateWS.data.model.Customer;import
com.exemple.customerRestFulHibernateWS.data.model.Order;import
com.exemple.customerRestFulHibernateWS.data.model.Product;import
org.osgi.service.log.LogService;public
class
CustomerDaoImplimplements
CustomerDao{
//@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">)
@PersistenceContext
(
unitName=
"customerDb"
)private
EntityManager entityManager;private
LogService logService;public
void
setEntityManager
(
final
EntityManager entityManager ){
this
.entityManager=
entityManager;}
@Override
public
CustomerfindCustomer
(
final
long
customerId){
logService.log
(
LogService.LOG_INFO,getClass
(
).getSimpleName
(
)+
"[findCustomer("
+
customerId+
")]"
);return
entityManager.find
(
Customer.class
, customerId);}
@Override
public
CustomersaveCustomer
(
final
Customer customer){
//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
logService.log
(
LogService.LOG_INFO,getClass
(
).getSimpleName
(
)+
"[saveCustomer("
+
customer.toString
(
)+
")]"
); entityManager.persist
(
customer);return
customer;}
@Override
public
CustomersaveExistingCustomer
(
final
Customer customer){
//On attache l'entite a l'Entity Manager courant.
logService.log
(
LogService.LOG_INFO,getClass
(
).getSimpleName
(
)+
"[saveExistingCustomer("
+
customer.toString
(
)+
")]"
); entityManager.merge
(
customer);return
customer;}
@Override
public
void
deleteCustomer
(
final
long
customerId){
//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
logService.log
(
LogService.LOG_INFO,getClass
(
).getSimpleName
(
)+
"[deleteCustomer("
+
customerId+
")]"
); entityManager.remove
(
findCustomer
(
customerId));}
@Override
public
OrderfindOrder
(
final
long
orderId){
logService.log
(
LogService.LOG_INFO,getClass
(
).getSimpleName
(
)+
"[findOrder("
+
orderId+
")]"
);return
entityManager.find
(
Order.class
, orderId);}
@Override
public
ProductfindProduct
(
final
long
productId){
logService.log
(
LogService.LOG_INFO,getClass
(
).getSimpleName
(
)+
"[findProduct("
+
productId+
")]"
);return
entityManager.find
(
Product.class
, productId);}
public
void
setLogService
(
final
LogService logService ){
this
.logService=
logService;}
}
-
complétez le contenu du fichier « pom.xml » associé au projet « module-data » de la manière suivante :
module-data/pom.xmlSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.<?xml version="1.0" encoding="UTF-8"?>
<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"
>
<modelVersion>
4.0.0</modelVersion>
<parent>
<artifactId>
customerRestFulHibernateWS</artifactId>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<version>
0.0.1-SNAPSHOT</version>
</parent>
<artifactId>
module-data</artifactId>
<packaging>
bundle</packaging>
<name>
module-data Blueprint Bundle</name>
<description>
module-data OSGi blueprint bundle project.</description>
<properties>
<project.build.outputEncoding>
UTF-8</project.build.outputEncoding>
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Dependance pour BundleContext -->
<dependency>
<groupId>
org.osgi</groupId>
<artifactId>
org.osgi.core</artifactId>
<scope>
provided</scope>
</dependency>
<!-- JPA -->
<dependency>
<groupId>
org.eclipse.persistence</groupId>
<artifactId>
javax.persistence</artifactId>
<scope>
provided</scope>
</dependency>
<!-- Dependance pour org.osgi.service.log.LogService -->
<dependency>
<groupId>
org.osgi</groupId>
<artifactId>
org.osgi.compendium</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>
org.apache.felix</groupId>
<artifactId>
maven-bundle-plugin</artifactId>
<version>
3.0.0</version>
<extensions>
true</extensions>
<configuration>
<instructions>
<JPA-PersistenceUnits>
customerDb</JPA-PersistenceUnits>
<!-- Le fichier MANIFEST.MF sera rempli avec les informations ci-dessous. Pour plus d'informations : http://wiki.osgi.org/wiki/Category:Manifest_Header -->
<!-- La balise <Meta-Persistence> pointe sur le fichier "persistence.xml" afin que JPA cree une EntityManagerFactory
pour ce bundle-->
<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 -->
<Bundle-SymbolicName>
${project.artifactId}</Bundle-SymbolicName>
<!-- Nom qui identifie le bundle de maniere unique -->
<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 -->
<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 -->
<Import-Package>
<!-- Necessaire pour le proxy de Javassist sous peine d'avoir le message "java.lang.RuntimeException: by java.lang.NoClassDefFoundError: org/hibernate/proxy/HibernateProxy" -->
org.hibernate.proxy,javassist.util.proxy,*</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 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 OSGIOpen Services Gateway Initiative 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 ».
- Dans le fichier « module-data/pom.xml », remarquez les lignes « <JPA-PersistenceUnits>customerDb</JPA-PersistenceUnits> » et « <Meta-Persistence>META-INF/persistence.xml</Meta-Persistence> ».
-
complétez le contenu du fichier « src/main/resources/OSGI/blueprint/my-service.xml de la manière suivante :
module-data/my-service.xmlSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.<?xml version="1.0" encoding="UTF-8"?>
<blueprint
xmlns
=
"http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns
:
jpa
=
"http://aries.apache.org/xmlns/jpa/v2.0.0"
xmlns
:
tx
=
"http://aries.apache.org/xmlns/transactions/v1.2.0"
default-activation
=
"lazy"
>
<!-- On active JPA -->
<
jpa
:
enable />
<!-- Definition de l'enregistrement du service dans le registre de services OSGI. On renseigne le bean qui fournit le service via l'attribut ref -->
<service
ref
=
"customerDao"
interface
=
"com.exemple.customerRestFulHibernateWS.data.CustomerDao"
>
<service-properties>
<entry
key
=
"service.exported.interfaces"
value
=
"*"
/>
</service-properties>
</service>
<!-- 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
(passage par reference local versus passage par valeur distant). Pour eviter qu'une application appelle accidentellement un service exporte qui est uniquement designe
par un appel par "passage de valeur", il faut cacher le lookup local.
La solution est d'exporter le meme bean deux fois : une fois pour les appels distants et une autre pour les appels locaux.
En d'autres termes, on ajoute un autre element avec la meme configuration mais sans la propriete "service.exported.interfaces"-->
<service
ref
=
"customerDao"
interface
=
"com.exemple.customerRestFulHibernateWS.data.CustomerDao"
/>
<!-- Publication du bean en tant que service OSGI-->
<!-- Ce bean possede des proprietes a injecter. L'injection de ces proprietes (ici la propriete entityManager) est faite
immediatement apres la creation du bean -->
<bean
id
=
"customerDao"
class
=
"com.exemple.customerRestFulHibernateWS.data.impl.CustomerDaoImpl"
>
<property
name
=
"logService"
ref
=
"logService"
/>
<!-- Etablissement des transactions pour toutes les methodes DAO. L'application n'a pas besoin de creer l'entity manager que nous avons declare.
La OSGI runtime creera cet entity manager et l'injectera partout ou il est requis. la balise "<tx:transaction>" permet de demander au runtime
d'envelopper les methodes dans une transaction.-->
<
tx
:
transaction
method
=
"*"
value
=
"Required"
/>
</bean>
<!-- Definition des dependances -->
<reference
id
=
"logService"
interface
=
"org.osgi.service.log.LogService"
/>
</blueprint>
-
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 » ;
-
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 à JPAJava Persistence API et Hibernate ;
-
complétez le fichier « persistence.xml » de la manière suivante :
module-data/persistence.xmlSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.<?xml version="1.0" encoding="UTF-8"?>
<persistence
xmlns
=
"http://xmlns.jcp.org/xml/ns/persistence"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version
=
"2.1"
>
<!-- ******************************************************************* -->
<!-- JTA (Java Transaction API). Elle fournit des interfaces Java standards entre un gestionnaire de transaction et les differentes parties
impliquees dans un systeme de transactions distribuees : le gestionnaire de ressources, le serveur d'application et les applications transactionnelles.
JTA est un protocole de commit a deux phases :
- 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
- 2e phase : chaque partie valide les changements des donnees. Cette phase est obligatoire, des lors que les parties se sont engagees.
Ce protocole de commit a deux phases fonctionne plutot bien sur les transactions courtes, mais est totalement inefficace en cas de transaction lente
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
qu'apres la deuxieme phase. -->
<!-- ******************************************************************* -->
<persistence-unit
name
=
"customerDb"
transaction-type
=
"JTA"
>
<provider>
org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- On precise l'ensemble des classes qui sont gerees par l'entityManager dans l'application -->
<class>
com.exemple.customerRestFulHibernateWS.data.model.Customer</class>
<class>
com.exemple.customerRestFulHibernateWS.data.model.Order</class>
<class>
com.exemple.customerRestFulHibernateWS.data.model.Product</class>
<!-- On exclut les classes qui ne sont pas listees ci-dessus -->
<exclude-unlisted-classes>
true</exclude-unlisted-classes>
<!-- On specifie le nom global JNDI de la datasource qui sera utilisee par le container.
Ce nom est la valeur de la propriete "osgi.jndi.service.name" fournie par la commande Karaf "service:list DataSource"-->
<jta-data-source>
osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=customerDb)</jta-data-source>
<properties>
<!-- La propriete hibernate.dialect definit le dialecte SQL de notre base de donnees.Hibernate s'appuiera sur ce dialect
pour optimiser l'execution des requetes en utilisant les proprietes specifiques a la base de donnees.
Dans notre cas, on utilisera le dialect de PostgreSQL-->
<property
name
=
"hibernate.dialect"
value
=
"org.hibernate.dialect.PostgreSQLDialect"
/>
<!-- La propriete hibernate.hbm2ddl.auto=none specifie que le schema de la table n'est pas automatiquement cree quand l'application est deployee.
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.
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.
En production, un utilisateur n'a que tres rarement le privilege de creer ou supprimer des tables.-->
<property
name
=
"hibernate.hbm2ddl.auto"
value
=
"none"
/>
<!-- On log les requetes executees par hibernate pour faciliter le debogage -->
<property
name
=
"hibernate.show_sql"
value
=
"true"
/>
<!-- On met des commentaires dans toutes les requetes SQL generees pour faciliter la comprehension des requetes-->
<property
name
=
"use_sql_comments"
value
=
"true"
/>
</properties>
</persistence-unit>
</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 !
- enfin, complétez le contenu du fichier « customerRestFulHibernateWS/pom.xml » de la manière suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
<?xml version="1.0" encoding="UTF-8"?>
<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"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
customerRestFulHibernateWS</artifactId>
<version>
0.0.1-SNAPSHOT</version>
<packaging>
pom</packaging>
<modules>
<module>
module-jaxrs</module>
<module>
module-services</module>
<module>
module-data</module>
<module>
module-kar</module>
</modules>
<properties>
<cxf.version>
3.1.5</cxf.version>
<osgi.version>
6.0.0</osgi.version>
<project.build.outputEncoding>
UTF-8</project.build.outputEncoding>
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
</properties>
<!-- Introduction de la balise <dependencyManagement> qui permet de centraliser les informations de dependances.
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
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
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
d'un meme parent-->
<dependencyManagement>
<dependencies>
<!-- Module utile au "module-jaxrs" -->
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-services</artifactId>
<version>
${project.version}</version>
</dependency>
<!-- Module utile au "module-jaxrs" et au "module-services" -->
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-data</artifactId>
<version>
${project.version}</version>
</dependency>
<!-- Dependance pour BundleContext-->
<dependency>
<groupId>
org.osgi</groupId>
<artifactId>
org.osgi.core</artifactId>
<version>
${osgi.version}</version>
<scope>
provided</scope>
</dependency>
<!-- Dependance necessaire pour le service web REST (GET, POST, PUT, Response, ...) -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-frontend-jaxrs</artifactId>
<version>
${cxf.version}</version>
<scope>
provided</scope>
</dependency>
<!-- Dependances necessaires pour la dependance org.apache.cxf/cxf-rt-frontend-jaxrs -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-frontend-jaxws</artifactId>
<version>
${cxf.version}</version>
<scope>
provided</scope>
</dependency>
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-transports-http</artifactId>
<version>
${cxf.version}</version>
<scope>
provided</scope>
</dependency>
<!-- Dependance necessaire pour org.slf4j.Logger -->
<dependency>
<groupId>
org.apache.cxf</groupId>
<artifactId>
cxf-rt-transports-http-jetty</artifactId>
<version>
${cxf.version}</version>
<scope>
provided</scope>
</dependency>
<!-- Dependance pour org.osgi.service.log.LogService -->
<dependency>
<groupId>
org.osgi</groupId>
<artifactId>
org.osgi.compendium</artifactId>
<version>
5.0.0</version>
<scope>
provided</scope>
</dependency>
<!-- JPA -->
<dependency>
<groupId>
org.eclipse.persistence</groupId>
<artifactId>
javax.persistence</artifactId>
<version>
2.1.1</version>
<scope>
provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
3.3</version>
<configuration>
<compilerVersion>
1.7</compilerVersion>
<source>
1.7</source>
<target>
1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
V-D. Création d'une DataSource en tant que service OSGI▲
Comme OSGIOpen Services Gateway Initiative 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 OSGIOpen Services Gateway Initiative 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 OSGIOpen Services Gateway Initiative 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 OSGIOpen Services Gateway Initiative 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 OSGIOpen Services Gateway Initiative par extension ou adaptation des pilotes selon les spécifications OSGIOpen Services Gateway Initiative.
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 :
-
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 OSGIOpen Services Gateway Initiative.
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 ;
-
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
-
dès lors, la création de notre DataSource peut être réalisée de deux manières différentes :
-
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 ;
-
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.cfgSélectionnez1.
2.
3.
4.
5.
6.
7.osgi.jdbc.driver.name=PostgreSQL JDBC Driver serverName=XXX.XXX.XXX.XXX databaseName=test portNumber=5003 user=pgtest password=mot_de_passe 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.
-
-
notre DataSource est désormais créée. Nous pouvons le vérifier en tapant la commande suivante :
service:list DataSource
- 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 :
-
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 » ;
-
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 » ;
-
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 ».
-
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 ». -
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.
-
complétez le fichier « feature.xml » de la manière suivante :
module-kar/feature.xmlSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.<?xml version="1.0" encoding="UTF-8"?>
<features
name
=
"${project.artifactId}-${project.version}"
xmlns
=
"http://karaf.apache.org/xmlns/features/v1.3.0"
>
<!-- Ajout de features au repository -->
<repository>
mvn:org.apache.cxf.karaf/apache-cxf/3.1.5/xml/features</repository>
<repository>
mvn:org.ops4j.pax.jdbc/pax-jdbc-features/0.7.0/xml/features</repository>
<repository>
mvn:org.hibernate/hibernate-osgi/5.1.0.Final/xml/karaf</repository>
<feature
name
=
'${project.artifactId}'
description
=
'${project.name}'
version
=
'${project.version}'
>
<details>
${project.description}</details>
<!-- Installation de la feature cxf -->
<feature
version
=
"3.1.5"
>
cxf</feature>
<!-- Installation de pax-jdbc pour creer les DataSourceFactory -->
<!-- La creation des DataSources se fait par depot d'un fichier org.ops4j.datasource-*.cfg
(ou "*" doit etre remplace par le nom de la DataSource) dans le repertoire "/etc" de Karaf -->
<feature>
pax-jdbc</feature>
<feature>
pax-jdbc-config</feature>
<feature>
pax-jdbc-pool-dbcp2</feature>
<!-- Installation de hibernate-osgi 5.1.0 -->
<feature>
hibernate-orm</feature>
<!-- Installation de la feature jdbc embarquee dans Karaf pour interroger les DataSources depuis l'invite de commande Karaf -->
<feature>
jdbc</feature>
<feature
version
=
"4.3.6.Final"
>
hibernate</feature>
<feature
version
=
"2.3.0"
>
jpa</feature>
<feature
version
=
"1.3.0"
>
transaction</feature>
<feature
version
=
"4.0.4"
>
jndi</feature>
<!-- Installation du driver PostgreSQL en tant que bundle -->
<bundle>
mvn:org.postgresql/postgresql/9.4-1203-jdbc41</bundle>
<!-- On place, dans l'archive, les dependances Maven apparaissant dans le fichier customerRestFullHibernateWS/pom.xml -->
<bundle>
mvn:org.osgi/org.osgi.core/6.0.0</bundle>
<bundle>
mvn:commons-logging/commons-logging/1.2</bundle>
<bundle>
mvn:commons-io/commons-io/2.4</bundle>
<!-- On renseigne les bundles que nous avons developpes et qui seront utiles au service web REST
le chemin de ces bundles est de la forme "mvn:groupId/artifactId/version" ou :
- groupeId est la valeur de la balise "/project/parent/groupId" du fichier "pom.xml" de chaque module
- artefactId est la valeur de la balise "/project/artefactId" du fichier "pom.xml" de chaque module
- version est la valeur de la balise "/project/parent/version" du fichier "pom.xml" de chaque module -->
<bundle>
mvn:com.exemple.customerRestFulHibernateWS/module-data/0.0.1-SNAPSHOT</bundle>
<bundle>
mvn:com.exemple.customerRestFulHibernateWS/module-services/0.0.1-SNAPSHOT</bundle>
<bundle>
mvn:com.exemple.customerRestFulHibernateWS/module-jaxrs/0.0.1-SNAPSHOT</bundle>
</feature>
</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 ;
-
complétez le fichier « pom.xml » de la manière suivante (notez la valeur « kar » dans la balise « packaging ») :
module-kar/pom.xmlSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.<?xml version="1.0" encoding="UTF-8"?>
<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"
>
<modelVersion>
4.0.0</modelVersion>
<parent>
<artifactId>
customerRestFulHibernateWS</artifactId>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<version>
0.0.1-SNAPSHOT</version>
</parent>
<artifactId>
module-kar</artifactId>
<packaging>
kar</packaging>
<name>
module-kar-feature</name>
<description>
module-kar details</description>
<!-- On fait appel a la bibliotheque "Aether" qui travaille avec les specifications des repository locaux et distants,
les workspaces de developpement, les artefacts de resolution et de transport -->
<dependencies>
<dependency>
<groupId>
org.eclipse.aether</groupId>
<artifactId>
aether-util</artifactId>
<version>
0.9.0.M2</version>
<scope>
compile</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>
org.apache.karaf.tooling</groupId>
<artifactId>
karaf-maven-plugin</artifactId>
<version>
4.0.3</version>
<extensions>
true</extensions>
</plugin>
<!--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-->
<plugin>
<groupId>
org.eclipse.m2e</groupId>
<artifactId>
lifecycle-mapping</artifactId>
<version>
1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.karaf.tooling</groupId>
<artifactId>
karaf-maven-plugin</artifactId>
<versionRange>
[4.0.3,)</versionRange>
<goals>
<goal>
features-generate-descriptor</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>
org.apache.karaf.tooling</groupId>
<artifactId>
karaf-maven-plugin</artifactId>
<configuration>
<startLevel>
50</startLevel>
<aggregateFeatures>
true</aggregateFeatures>
<!--<resolver>(obr)</resolver>-->
<checkDependencyChange>
true</checkDependencyChange>
<failOnDependencyChange>
false</failOnDependencyChange>
<logDependencyChanges>
true</logDependencyChanges>
<overwriteChangedDependencies>
true</overwriteChangedDependencies>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 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 » :
2.
3.
4.
5.
6.
7.
...
<
cxf
:
bus
id
=
"bus"
>
<
cxf
:
features>
<
cxf
:
logging/>
</
cxf
:
features>
</
cxf
:
bus>
...
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
----------------------------
ID: 1
Address: http://localhost:8181/cxf/olivier/customer/1/order
Encoding: UTF-8
Http-Method: PUT
Content-Type: application/json
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]}
Payload: {"Order":{"description":Nouvelle commande, "products": [{"idProduct":1,"description":"Clavier"},{"idProduct":2,"description":"Souris"}]}}
--------------------------------------
…
---------------------------
ID: 1
Response-Code: 200
Content-Type: text/html
Headers: {Date=[Thu, 24 Mar 2016 13:53:52 GMT], Content-Length=[0]}
--------------------------------------
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 OSGIOpen Services Gateway Initiative) déclaré dans la classe « CustomerDaoImpl » :
2.
3.
4.
5.
6.
7.
8.
9.
import
org.osgi.service.log.LogService;
public
class
CustomerDaoImpl implements
CustomerDao
{
//@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">)
@PersistenceContext
(
unitName =
"customerDb"
)
private
EntityManager entityManager;
private
LogService logService;
...
La récupération de ce logger se fait dans les classes « CustomerServiceImpl » et « CustomerRestService » de la manière suivante :
2.
3.
4.
5.
6.
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
...
private
static
final
Logger LOG =
LoggerFactory.getLogger
(
CustomerService.class
);
...
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) :
2.
3.
4.
5.
6.
7.
8.
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])]
2016-03-24 14:53:50,469 | INFO | tp1880379883-641 | CustomerDao | 6612 - module-services - 0.0.1.SNAPSHOT | CustomerServiceImpl[getCustomerById(1)]
2016-03-24 14:53:50,693 | INFO | tp1880379883-641 | module-data | 6610 - module-data - 0.0.1.SNAPSHOT | CustomerDaoImpl[findCustomer(1)]
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])]
2016-03-24 14:53:50,999 | INFO | tp1880379883-641 | module-data | 6610 - module-data - 0.0.1.SNAPSHOT | CustomerDaoImpl[findProduct(1)]
2016-03-24 14:53:51,630 | INFO | tp1880379883-641 | module-data | 6610 - module-data - 0.0.1.SNAPSHOT | CustomerDaoImpl[findProduct(2)]
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])]
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
<properties>
<!-- La propriete hibernate.dialect definit le dialect SQL de notre base de donnees.Hibernate s'appuiera sur ce dialecte
pour optimiser l'execution des requetes en utilisant les proprietes specifiques a la base de donnees.
Dans notre cas, on utilisera le dialecte de PostgreSql-->
<property
name
=
"hibernate.dialect"
value
=
"org.hibernate.dialect.PostgreSQLDialect"
/>
<!-- La propriete hibernate.hbm2ddl.auto=none specifie que le schema de la table n'est pas automatiquement cree quand l'application est deployee.
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.
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.
En production, un utilisateur n'a que tres rarement le privilege de creer ou supprimer des tables.-->
<property
name
=
"hibernate.hbm2ddl.auto"
value
=
"none"
/>
<!-- On log les requetes executees par hibernate pour faciliter le debogage -->
<property
name
=
"hibernate.show_sql"
value
=
"true"
/>
<!-- On met des commentaires dans toutes les requetes SQL generees pour faciliter la comprehension des requetes-->
<property
name
=
"use_sql_comments"
value
=
"true"
/>
</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 :
<property
name
=
"hibernate.show_sql"
value
=
"true"
/>
VI. Compilation et Build▲
-
Cliquez droit sur le projet « customerRestFulHibernateWS » ? Run as ? Maven Build
-
Dans le champ « Goals », écrivez « clean install », puis appuyez sur le bouton « Run »
- 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 :
- téléchargez et installez Karaf Container 4.0.4 sur le site http://karaf.apache.org/archives.html#container-404 ;
- exécutez le fichier « karaf.bat » qui se situe dans le répertoire « bin » ;
- installez éventuellement la console d'administration via la commande :
feature:install webconsole ; - à 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 BuildCompilation 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
org.ops4j.pax.url.mvn.repositories= \
http://repo1.maven.org/maven2@id=central, \
http://repository.springsource.com/maven/bundles/release@id=spring.ebr.release, \
http://repository.springsource.com/maven/bundles/external@id=spring.ebr.external, \
http://zodiac.springsource.com/maven/bundles/release@id=gemini, \
http://repository.apache.org/content/groups/snapshots-group@id=apache@snapshots@noreleases, \
https://oss.sonatype.org/content/repositories/snapshots@id=sonatype.snapshots.deploy@snapshots@noreleases, \
https://oss.sonatype.org/content/repositories/ops4j-snapshots@id=ops4j.sonatype.snapshots.deploy@snapshots@noreleases, \
http://repository.springsource.com/maven/bundles/external@id=spring-ebr-repository@snapshots@noreleases, \
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 :
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 BuildCompilation 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 BuildCompilation 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 :
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 :
- cliquez droit sur le projet « customerRestFulHibernateWS » ? « New » ? « Project ». Dans la fenêtre qui apparaît, saisissez « maven » dans le filtre puis choisissez « Maven module » ;
-
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 » ;
-
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 :
-
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 » ;
-
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 » ;
-
de la même manière, dans le répertoire « main », créez le répertoires « resources » ;
-
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 ;
-
commencez par remplacer le contenu du fichier « pom.xml » par ceci :
module-karaf-custom-distribution/pom.xmlSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.<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"
>
<modelVersion>
4.0.0</modelVersion>
<parent>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
customerRestFulHibernateWS</artifactId>
<version>
1.0.0</version>
</parent>
<artifactId>
module-karaf-custom-distribution</artifactId>
<packaging>
karaf-assembly</packaging>
<version>
1.0.0</version>
<name>
Distribution de Karaf personnalisee</name>
<properties>
<karaf.version>
4.0.4</karaf.version>
</properties>
<dependencies>
<!-- La valeur de la balise <scope> est "compile" donc toutes les features sont installees dans le fichier startup.properties
et le repository des features n'est pas ajoute dans le fichier etc/org.apache.karaf.features.cfg.
Le fichier etc/startup.properties est utilise pour definir les services noyaux et leur ordre de demarrage dans Karaf-->
<dependency>
<groupId>
org.apache.karaf.features</groupId>
<artifactId>
framework</artifactId>
<version>
${karaf.version}</version>
<type>
kar</type>
<scope>
compile</scope>
</dependency>
<!-- La valeur de la balise <scope> est "runtime" donc le repository des features est liste dans le fichier etc/org.apache.karaf.features.cfg
et les features seront installees dans le repertoire system.
Cette dependance importe les features standards de Karaf decrites a l'adresse
https://repo1.maven.org/maven2/org/apache/karaf/features/standard/3.0.4/standard-3.0.4-features.xml
Ces features sont installees dans la configuration de karaf-maven-plugin -->
<!-- Les features jdbc et hibernate ne sont pas incluses dans Karaf standard, elles sont dans Karaf enterprise donc on recupere Karaf enterprise
qui inclut aussi Karaf standard -->
<dependency>
<groupId>
org.apache.karaf.features</groupId>
<artifactId>
enterprise</artifactId>
<version>
${karaf.version}</version>
<classifier>
features</classifier>
<type>
xml</type>
<scope>
runtime</scope>
</dependency>
<!-- Installation du driver PostgreSql -->
<dependency>
<groupId>
org.postgresql</groupId>
<artifactId>
postgresql</artifactId>
<version>
9.4-1203-jdbc41</version>
<scope>
runtime</scope>
</dependency>
<!-- Installation de la feature propre a notre projet. Cette feature a ete generee par le module ""module-kar" -->
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-kar</artifactId>
<version>
${project.version}</version>
<classifier>
features</classifier>
<type>
xml</type>
<scope>
runtime</scope>
</dependency>
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-data</artifactId>
<version>
${project.version}</version>
<scope>
runtime</scope>
</dependency>
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-services</artifactId>
<version>
${project.version}</version>
<scope>
runtime</scope>
</dependency>
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-jaxrs</artifactId>
<version>
${project.version}</version>
<scope>
runtime</scope>
</dependency>
</dependencies>
<build>
<!-- Inclusion des ressources dans la distribution -->
<resources>
<!-- Le fichier contenant la configuration de notre dataSource sera localise dans le repertoire "src/main/resources"
afin d'etre copie dans le repertoire "etc" de Karaf -->
<resource>
<directory>
src/main/resources</directory>
<filtering>
false</filtering>
<includes>
<include>
**/*</include>
</includes>
</resource>
<!-- Le repertoire "src/main/filtered-resources" contient les fichiers qui incluent des variables denotees par "${...}".
Ces variables peuvent venir des proprietes du systeme, des proprietes du projet...
Le fichier branding.properties contient de telles variables. Il sera copie, comme les fichiers qui se trouvent dans le repertoire
"src/main/resources" dans le repertoire "etc" de Karaf.
Voir le lien : http://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html -->
<resource>
<directory>
src/main/filtered-resources</directory>
<filtering>
true</filtering>
<includes>
<include>
**/*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>
org.apache.karaf.tooling</groupId>
<artifactId>
karaf-maven-plugin</artifactId>
<version>
${karaf.version}</version>
<extensions>
true</extensions>
<configuration>
<!-- La balise <bootFeatures> permet de demander a Karaf lors de son demarrage de demarrer les features.
Le nom des features est obtenu dans le fichier features.xml dans la section <dependencies>.
-->
<bootFeatures>
<!-- Features liees a Karaf standard -->
<feature>
wrap</feature>
<feature>
aries-blueprint</feature>
<feature>
shell</feature>
<feature>
shell-compat</feature>
<feature>
feature</feature>
<feature>
jaas</feature>
<feature>
ssh</feature>
<feature>
management</feature>
<feature>
bundle</feature>
<feature>
config</feature>
<feature>
deployer</feature>
<feature>
diagnostic</feature>
<feature>
feature</feature>
<feature>
instance</feature>
<feature>
kar</feature>
<feature>
log</feature>
<feature>
package</feature>
<feature>
service</feature>
<feature>
system</feature>
<feature>
webconsole</feature>
<!-- Features liees a Karaf entreprise et necessaires a notre projet -->
<feature>
jdbc</feature>
<!-- <feature>hibernate</feature>-->
<feature>
jpa</feature>
<feature>
transaction</feature>
<feature>
jndi</feature>
<!-- 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"-->
<feature>
pax-jdbc</feature>
<feature>
pax-jdbc-config</feature>
<feature>
pax-jdbc-pool-dbcp2</feature>
<feature>
cxf</feature>
<feature>
hibernate-orm</feature>
<!-- Installation la feature de notre projet -->
<feature>
module-kar</feature>
</bootFeatures>
<!-- <installedFeatures>
installedFeatures installe les features dans le repertoire ${KARAF_HOME}/system directory mais l'utilisateur devra l'installer manuellement via la command feature:install
<feature>pax-jdbc</feature>
<feature>pax-jdbc-config</feature>
<feature>pax-jdbc-pool-dbcp2</feature>
</installedFeatures>-->
</configuration>
</plugin>
</plugins>
</build>
</project>
-
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.propertiesSélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.welcome=\ \u001B[36m _____ _____ _____ _____ _______ _____ _____ _____\u001B[0m\r\n\ \u001B[36m /\\ \\ /\\ \\ /\\ \\ /\\ \\ /::\\ \\ /\\ \\ /\\ \\ /\\ \\\u001B[0m\r\n\ \u001B[36m /::\\ \\ /::\\____\\ /::\\ \\ /::\\ \\ /::::\\ \\ /::\\____\\ /::\\ \\ /::\\ \\\u001B[0m\r\n\ \u001B[36m /::::\\ \\ /:::/ / /::::\\ \\ \\:::\\ \\ /::::::\\ \\ /::::| | /::::\\ \\ /::::\\ \\\u001B[0m\r\n\ \u001B[36m /::::::\\ \\ /:::/ / /::::::\\ \\ \\:::\\ \\ /::::::::\\ \\ /:::::| | /::::::\\ \\ /::::::\\ \\\u001B[0m\r\n\ \u001B[36m /:::/\\:::\\ \\ /:::/ / /:::/\\:::\\ \\ \\:::\\ \\ /:::/~~\\:::\\ \\ /::::::| | /:::/\\:::\\ \\ /:::/\\:::\\ \\\u001B[0m\r\n\ \u001B[36m /:::/ \\:::\\ \\ /:::/ / /:::/__\\:::\\ \\ \\:::\\ \\ /:::/ \\:::\\ \\ /:::/|::| | /:::/__\\:::\\ \\ /:::/__\\:::\\ \\\u001B[0m\r\n\ \u001B[36m /:::/ \\:::\\ \\ /:::/ / \\:::\\ \\:::\\ \\ /::::\\ \\ /:::/ / \\:::\\ \\ /:::/ |::| | /::::\\ \\:::\\ \\ /::::\\ \\:::\\ \\\u001B[0m\r\n\ \u001B[36m /:::/ / \\:::\\ \\ /:::/ / _____ ___\\:::\\ \\:::\\ \\ /::::::\\ \\ /:::/____/ \\:::\\____\\ /:::/ |::|___|______ /::::::\\ \\:::\\ \\ /::::::\\ \\:::\\ \\\u001B[0m\r\n\ \u001B[36m /:::/ / \\:::\\ \\ /:::/____/ /\\ \\ /\\ \\:::\\ \\:::\\ \\ /:::/\\:::\\ \\ |:::| | |:::| | /:::/ |::::::::\\ \\ /:::/\\:::\\ \\:::\\ \\ /:::/\\:::\\ :::\\____\\\u001B[0m\r\n\ \u001B[36m/:::/____/ \\:::\\____\\|:::| / /::\\____\\/::\\ \\:::\\ \\:::\\____\\ /:::/ \\:::\\____\\|:::|____| |:::| |/:::/ |:::::::::\\____\\/:::/__\\:::\\ \\:::\\____\\/:::/ \\:::\\ \\:::| |\u001B[0m\r\n\ \u001B[36m\\:::\\ \\ \\::/ /|:::|____\\ /:::/ /\\:::\\ \\:::\\ \\::/ / /:::/ \\::/ / \\:::\\ \\ /:::/ / \\::/ / ~~~~~/:::/ /\\:::\\ \\:::\\ \\::/ /\\::/ |::::\\ /:::|____|\u001B[0m\r\n\ \u001B[36m \\:::\\ \\ \\/____/ \\:::\\ \\ /:::/ / \\:::\\ \\:::\\ \\/____/ /:::/ / \\/____/ \\:::\\ \\ /:::/ / \\/____/ /:::/ / \\:::\\ \\:::\\ \\/____/ \\/____|:::::\\/:::/ /\u001B[0m\r\n\ \u001B[36m \\:::\\ \\ \\:::\\ \\ /:::/ / \\:::\\ \\:::\\ \\ /:::/ / \\:::\\ /:::/ / /:::/ / \\:::\\ \\:::\\ \\ |:::::::::/ /\u001B[0m\r\n\ \u001B[36m \\:::\\ \\ \\:::\\ /:::/ / \\:::\\ \\:::\\____\\ /:::/ / \\:::\\__/:::/ / /:::/ / \\:::\\ \\:::\\____\\ |::|\\::::/ /\u001B[0m\r\n\ \u001B[36m \\:::\\ \\ \\:::\\__/:::/ / \\:::\\ /:::/ / \\::/ / \\::::::::/ / /:::/ / \\:::\\ \\::/ / |::| \\::/____/\u001B[0m\r\n\ \u001B[36m \\:::\\ \\ \\::::::::/ / \\:::\\/:::/ / \\/____/ \\::::::/ / /:::/ / \\:::\\ \\/____/ |::| ~|\u001B[0m\r\n\ \u001B[36m \\:::\\ \\ \\::::::/ / \\::::::/ / \\::::/ / /:::/ / \\:::\\ \\ |::| |\u001B[0m\r\n\ \u001B[36m \\:::\\____\\ \\::::/ / \\::::/ / \\::/____/ /:::/ / \\:::\\____\\ \\::| |\u001B[0m\r\n\ \u001B[36m \\::/ / \\::/____/ \\::/ / ~~ \\::/ / \\::/ / \\:| |\u001B[0m\r\n\ \u001B[36m \\/____/ ~~ \\/____/ \\/____/ \\/____/ \\|___|\u001B[0m\r\n\ \u001B[33m (version ${project.version} 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 :
-
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 » ;
-
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) ;
-
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 ;
- 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-services</artifactId>
<type>
bundle</type>
</dependency>
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-data</artifactId>
<type>
bundle</type>
</dependency>
Remplacé par :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-services</artifactId>
<version>
[1.0.0,2.0)</version>
<type>
bundle</type>
</dependency>
<dependency>
<groupId>
com.exemple.customerRestFulHibernateWS</groupId>
<artifactId>
module-data</artifactId>
<version>
[1.0.0,2.0)</version>
<type>
bundle</type>
</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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
CREATE
TABLE
ctm.product
(
idProduct serial
NOT
NULL
,
description character
varying
(
200
)
NOT
NULL
,
CONSTRAINT
pk_product PRIMARY
KEY
(
idProduct)
)
;
CREATE
TABLE
ctm.customer
(
idCustomer serial
NOT
NULL
,
prenom character
varying
(
50
)
NOT
NULL
,
nom character
varying
(
50
)
NOT
NULL
,
CONSTRAINT
pk_customer PRIMARY
KEY
(
idCustomer)
)
;
CREATE
TABLE
ctm.order
(
idOrder serial
NOT
NULL
,
description character
varying
(
50
)
NOT
NULL
,
customer_id integer
,
CONSTRAINT
pk_order PRIMARY
KEY
(
idOrder)
,
CONSTRAINT
fk_customer FOREIGN
KEY
(
customer_id)
REFERENCES
ctm.customer (
idCustomer)
)
;
CREATE
TABLE
ctm.order_detail
(
id serial
NOT
NULL
,
order_id integer
NOT
NULL
,
product_id integer
NOT
NULL
,
quantite integer
NOT
NULL
,
CONSTRAINT
pk_order_detail PRIMARY
KEY
(
id)
,
CONSTRAINT
fk_order FOREIGN
KEY
(
order_id)
REFERENCES
ctm.order
(
idOrder)
,
CONSTRAINT
fk_product FOREIGN
KEY
(
product_id)
REFERENCES
ctm.product (
idProduct)
)
;
INSERT
INTO
ctm.customer(
prenom, nom)
VALUES
(
'Olivier'
, 'Rozier'
)
;
INSERT
INTO
ctm.customer(
prenom, nom)
VALUES
(
'Sarah'
, 'Pelle'
)
;
INSERT
INTO
ctm.customer(
prenom, nom)
VALUES
(
'Amélie'
, 'Passa'
)
;
INSERT
INTO
ctm.customer(
prenom, nom)
VALUES
(
'Pierre'
, 'Afeu'
)
;
INSERT
INTO
ctm.product(
description)
VALUES
(
'Clavier'
)
;
INSERT
INTO
ctm.product(
description)
VALUES
(
'Souris'
)
;
INSERT
INTO
ctm.product(
description)
VALUES
(
'Ecran'
)
;
INSERT
INTO
ctm.product(
description)
VALUES
(
'Joystick'
)
;
INSERT
INTO
ctm.product(
description)
VALUES
(
'Clé USB'
)
;
INSERT
INTO
ctm.order
(
description, customer_id)
VALUES
(
'Cmd 1'
,1
)
;
INSERT
INTO
ctm.order
(
description, customer_id)
VALUES
(
'Cmd 2'
,2
)
;
INSERT
INTO
ctm.order
(
description, customer_id)
VALUES
(
'Cmd 3'
,3
)
;
INSERT
INTO
ctm.order
(
description, customer_id)
VALUES
(
'Cmd 4'
,4
)
;
INSERT
INTO
ctm.order
(
description, customer_id)
VALUES
(
'Cmd 5'
,1
)
;
INSERT
INTO
ctm.order
(
description, customer_id)
VALUES
(
'Cmd 6'
,1
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
1
,1
,2
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
1
,2
,2
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
1
,3
,2
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
2
,3
,1
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
2
,4
,1
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
3
,4
,1
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
4
,4
,1
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
4
,5
,1
)
;
INSERT
INTO
ctm.order_detail(
order_id, product_id, quantite)
VALUES
(
5
,2
,1
)
;
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
- Récupérez l'archive à partir du lien ci-dessus.
-
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 :
-
Copiez le répertoire « customerRestFulHibernateWS » dans votre Workspace Eclipse.
-
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 ».
-
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 ».
- 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) |