Nous abordons maintenant l'écriture du client web mobile.
Le client web mobile permet de gérer les Arduinos à distance avec le navigateur d'un smartphone ou d'une tablette. Il présente à l'utilisateur les écrans suivants :
La vue d'accueil [1] propose les opérations possibles. La vue [pinWrite] [2] permet d'écrire une valeur sur une pin d'un Arduino.
La vue [pinRead] [1] permet de lire la valeur d'une pin d'un Arduino. La vue [blink] [2] permet de faire clignoter une led d'un Arduino :
La vue [commands] [1] permet d'envoyer une commande JSON à un Arduino. De chaque vue, on peut revenir à la vue d'accueil avec l'icône [2].
L'architecture du client web mobile est la suivante :
- la couche [DAO] communique avec le serveur REST. C'est un client REST implémenté par Spring-Android ;
- la couche [web] est une couche JSF2 (Java Server Faces) avec la bibliothèque Ajax PFM (Primefaces Mobile).
La couche [DAO] dialogue avec le serveur REST des Arduinos :
Le serveur REST des Arduinos est exécuté sur un serveur Tomcat. Pour l'application web mobile, nous utiliserons le serveur Glassfish. En mode développement on est amené à redéployer souvent les applications sur les serveurs. Or redéployer une application sur le serveur Tomcat relance Tomcat lui-même donc l'application REST des Arduinos. En mettant le client web mobile sur Glassfish on évite de relancer le serveur REST.
Pour que les serveurs Glassfish et Tomcat puissent fonctionner ensemble, il faut qu'ils travaillent sur des ports différents. Or par défaut, ils travaillent tous les deux sur le port 8080. On pourra changer le port du serveur Tomcat de la façon suivante :
- en [1], dans la fenêtre Servers double-cliquer sur le serveur Tomcat ;
- en [2], modifier son port HTTP.
Les projets Eclipse suivent l'architecture ci-dessus :
En [1] les deux projets Eclipse du client web mobile :
- [webmobile-restClient] : projet de la couche [DAO] ;
- [webmobile-pfm] : le projet web Primefaces Mobile
Dans l'architecture du client web mobile
La couche [DAO] dialogue avec le serveur REST des Arduinos :
La couche [DAO] est un client REST comme l'étaient les couches [métier] et [DAO] du projet Android (paragraphe , page et paragraphe , page ). On y trouve du code analogue.
XII-D-1. Le projet Eclipse▲
Le projet Eclipse de la couche [DAO] est le suivant :
- en [1], le projet [webmobile-restClient] est un projet Maven ;
- en [2], les dépendances Maven ;
- en [3], l'implémentation du client REST ;
XII-D-2. Les dépendances Maven▲
Le fichier [pom.xml] du projet est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
<
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
>
istia.st.domotique.webmobile<
/
groupId
>
<
artifactId
>
webmobile-restClient<
/
artifactId
>
<
version
>
1.0-SNAPSHOT<
/
version
>
<
packaging
>
jar<
/
packaging
>
<
name
>
webmobile-restClient<
/
name
>
<
url
>
http://maven.apache.org<
/
url
>
<
properties
>
<
project
.
build
.
sourceEncoding
>
UTF-8<
/
project
.
build
.
sourceEncoding
>
<
spring
.
version
>
3.1.1.RELEASE<
/
spring
.
version
>
<
jackson
.
mapper
.
version
>
1.5.6<
/
jackson
.
mapper
.
version
>
<
/
properties
>
<
repositories
>
<
repository
>
<
id
>
com.springsource.repository.bundles.release<
/
id
>
<
name
>
SpringSource Enterprise Bundle Repository - Release<
/
name
>
<
url
>
http://repository.springsource.com/maven/bundles/release<
/
url
>
<
/
repository
>
<
/
repositories
>
<
dependencies
>
<
dependency
>
<
groupId
>
org.springframework<
/
groupId
>
<
artifactId
>
spring-webmvc<
/
artifactId
>
<
version
>
${spring.version}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.codehaus.jackson<
/
groupId
>
<
artifactId
>
jackson-mapper-asl<
/
artifactId
>
<
version
>
${jackson.mapper.version}<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
istia.st.domotique.common<
/
groupId
>
<
artifactId
>
domotique-entities<
/
artifactId
>
<
version
>
1.0-SNAPSHOT<
/
version
>
<
/
dependency
>
<
/
dependencies
>
<
/
project
>
- lignes 5-7 : l'identité Maven du projet ;
- lignes 29-33 : Pour le client REST, on a besoin du framework SpringMVC ;
- lignes 34-38 : la dépendance sur la bibliothèque JSON Jackson ;
- lignes 39-43 : le client et le serveur REST échangent des entités définies dans le projet Maven [domotique-entities].
XII-D-3. L'implémentation du client REST▲
Le client REST offre à la couche [web], l'interface [IMetier] suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
package
client.rest.metier;
...
public
interface
IMetier {
public
Collection<
Arduino>
getArduinos
(
);
public
Reponse pinRead
(
String idCommande, String idArduino, int
pin, String mode);
public
Reponse pinWrite
(
String idCommande, String idArduino, int
pin, String mode,int
val);
public
Reponse faireClignoterLed
(
String idCommande, String idArduino, int
pin, int
millis, int
nbIter);
public
List<
String>
sendCommandesJson
(
String idArduino, List<
String>
commandes);
public
List<
Reponse>
sendCommandes
(
String idArduino, List<
Commande>
commandes);
}
C'est l'interface de la couche [métier] du serveur. C'est normal. Lorsqu'on met bout à bout le client et le serveur, on voit que la couche [web] du client dialogue avec la couche [métier] du serveur.
L'implémentation [Metier] de cette interface pourrait être la suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
package
client.rest.metier;
...
@
Service
public
class
Metier implements
IMetier {
@
Autowired
private
RestTemplate restTemplate;
private
Gson gson =
new
Gson
(
);
private
String urlServiceRest;
public
Metier
(
) {
}
public
Collection<
Arduino>
getArduinos
(
) {
...
}
public
Reponse pinRead
(
String idCommande, String idArduino, int
pin, String mode) {
...
}
public
Reponse pinWrite
(
String idCommande, String idArduino, int
pin, String mode, int
val) {
...
}
public
Reponse faireClignoterLed
(
String idCommande, String idArduino, int
pin, int
millis, int
nbIter) {
...
}
@
SuppressWarnings
(
"
unchecked
"
)
public
List<
String>
sendCommandesJson
(
String idArduino, List<
String>
commandes) {
...
}
public
List<
Reponse>
sendCommandes
(
String idArduino, List<
Commande>
commandes) {
...
}
public
String executeRestService
(
String method, String urlService, Object request, Map<
String, String>
paramètres) {
....
}
...
}
Vous connaissez cette implémentation. C'est celle que vous avez écrite pour le client Android. Revoyez les couches [métier] au paragraphe , page et [DAO] au paragraphe , page du projet Android. Il y a une différence : la bibliothèque JSON utilisée est Jackson et non Gson.
: écrire la classe [RestMetier].
XII-D-4. Les tests du client REST▲
- dans le dossier [support] du TP, vous trouverez un projet Eclipse [webmobile-console] [1] ;
- importez-le dans Eclipse. C'est un projet Maven [2] ;
- en [3], les dépendances Maven du projet. Celui-ci dépend du client REST [webmobile-restClient]. Les autres dépendances en découlent ;
- en [4], vous trouverez quatre applications Java testant certaines fonctionnalités du client REST ;
- les quatre applications utilisent Spring pour instancier le client REST [5].
Examinons le code de la classe [ListArduinos] qui affiche la liste des Arduinos connectés :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
package
arduino.restClient.console;
...
public
class
ListArduinos {
public
static
void
main
(
String[] args) {
IMetier metier =
(
IMetier) new
ClassPathXmlApplicationContext
(
"
spring-restClient.xml
"
).getBean
(
"
metier
"
);
System.out.println
(
"
Liste
des
Arduinos
connectés
"
);
Collection<
Arduino>
arduinos =
metier.getArduinos
(
);
if
(
arduinos.size
(
) =
=
0
) {
System.out.println
(
"
Aucun
Arduino
n'est
connecté
actuellement...
"
);
}
else
{
for
(
Arduino arduino : arduinos) {
System.out.println
(
arduino);
}
}
}
}
- ligne 7 : le client REST est instancié ;
- ligne 9 : on lui demande la liste des Arduinos connectés ;
Le fichier de configuration de Spring [] est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
<?
xml
version="1.0"
encoding="UTF-8"?
>
<
beans
xmlns
=
"
http://www.springframework.org/schema/beans
"
xmlns
:
xsi
=
"
http://www.w3.org/2001/XMLSchema-instance
"
xmlns
:
p
=
"
http://www.springframework.org/schema/p
"
xmlns
:
context
=
"
http://www.springframework.org/schema/context
"
xmlns
:
oxm
=
"
http://www.springframework.org/schema/oxm
"
xsi
:
schemaLocation
=
"
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/oxm
http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd
"
xmlns
:
util
=
"
http://www.springframework.org/schema/util
"
>
<
bean
id
=
"
metier
"
class
=
"
client.rest.metier.Metier
"
>
<
property
name
=
"
urlServiceRest
"
value
=
"
localhost:8081/server-rest
"
/
>
<
property
name
=
"
restTemplate
"
ref
=
"
restTemplate
"
/
>
<
/
bean
>
<
bean
id
=
"
restTemplate
"
class
=
"
org.springframework.web.client.RestTemplate
"
>
<
property
name
=
"
messageConverters
"
>
<
list
>
<
ref
bean
=
"
jacksonConverter
"
/
>
<
ref
bean
=
"
stringConverter
"
/
>
<
/
list
>
<
/
property
>
<
/
bean
>
<
bean
id
=
"
jacksonConverter
"
class
=
"
org.springframework.http.converter.json.MappingJacksonHttpMessageConverter
"
/
>
<
bean
id
=
"
stringConverter
"
class
=
"
org.springframework.http.converter.StringHttpMessageConverter
"
/
>
<
/
beans
>
- ligne 12 : définition du client REST ;
- ligne 13 : injection de l'URL du service REST distant. Vous serez peut-être amenés à modifier cette valeur ;
- ligne 14 : injection du client REST de Spring ;
- ligne 17 : le client REST de Spring. La classe appartient au paquetage [org.springframework] ;
- ligne 18 : on injecte dans cette classe une liste de convertisseurs de messages. Cette notion a été expliquée au paragraphe , page .
- ligne 25 : le convertisseur des messages JSON. On utilise ici la bibliothèque Jackson ;
Travail à faire : Passer les quatre tests du client REST.
A lire :
- le cours JSF (Java Server Faces) de [ref1] ;
XII-E-1. Le projet Eclipse▲
Le projet Eclipse de la couche [web] est le suivant :
- en [1], le projet est un projet Maven ;
- en [2], les dépendances Maven du projet. Celui-ci dépend uniquement du client REST [webmobile-restClient]. Les autres dépendances en découlent ;
- en [3], les classes du projet :
- le paquetage [arduino.jsf.beans] contient les trois beans JSF de l'application de portées respectives Application, Session et Request,
- le paquetage [com.corejsf.util] est un paquetage utilitaire pour gérer l'internationalisation des messages. Nous ne l'expliciterons pas ici. C'est fait dans le cours JSF de [ref1] ;
XII-E-2. Le projet Maven▲
Le fichier [pom.xml] est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
<
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
>
istia.st.domotique.webmobile<
/
groupId
>
<
artifactId
>
webmobile-pfm<
/
artifactId
>
<
version
>
1.0-SNAPSHOT<
/
version
>
<
packaging
>
war<
/
packaging
>
<
name
>
server-pfm<
/
name
>
<
properties
>
<
endorsed
.
dir
>
${project.build.directory}/endorsed<
/
endorsed
.
dir
>
<
project
.
build
.
sourceEncoding
>
UTF-8<
/
project
.
build
.
sourceEncoding
>
<
/
properties
>
<
dependencies
>
<
dependency
>
<
groupId
>
com.sun.faces<
/
groupId
>
<
artifactId
>
jsf-api<
/
artifactId
>
<
version
>
2.1.1-b04<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
com.sun.faces<
/
groupId
>
<
artifactId
>
jsf-impl<
/
artifactId
>
<
version
>
2.1.1-b04<
/
version
>
<
/
dependency
>
<
dependency
>
<
groupId
>
org.primefaces<
/
groupId
>
<
artifactId
>
mobile<
/
artifactId
>
<
version
>
0.9.3<
/
version
>
<
type
>
jar<
/
type
>
<
/
dependency
>
<
dependency
>
<
groupId
>
istia.st.domotique.webmobile<
/
groupId
>
<
artifactId
>
webmobile-restClient<
/
artifactId
>
<
version
>
1.0-SNAPSHOT<
/
version
>
<
/
dependency
>
<
/
dependencies
>
<
build
>
...
<
/
build
>
<
repositories
>
<
repository
>
<
id
>
prime-repo<
/
id
>
<
name
>
PrimeFaces Maven Repository<
/
name
>
<
url
>
http://repository.primefaces.org<
/
url
>
<
layout
>
default<
/
layout
>
<
/
repository
>
<
/
repositories
>
<
/
project
>
- lignes 5-7 : l'identité Maven du projet ;
- lignes 18-27 : les dépendances vis à vis des bibliothèques JSF ;
- lignes 28-33 : les dépendances vis à vis de la bibliothèque Primefaces Mobile ;
- lignes 34-38 : la dépendance sur le client REST ;
- lignes 46-51 : le dépôt MAven où peut être trouvée la bibliothèque Primefaces Mobile.
XII-E-3. La configuration de la couche [web]▲
- la configuration et les pages XHTML de la couche [web] sont dans le dossier [webapp] [1] ;
- le projet est configuré par les fichiers [web.xml] et [faces-config.xml] [2].
XII-E-4. Le fichier [web.xml]▲
Le fichier [web.xml] est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
<?
xml
version="1.0"
encoding="UTF-8"?
>
<
web-app
xmlns
:
xsi
=
"
http://www.w3.org/2001/XMLSchema-instance
"
xmlns
=
"
http://java.sun.com/xml/ns/javaee
"
xmlns
:
web
=
"
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
"
xsi
:
schemaLocation
=
"
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd
"
version
=
"
3.0
"
>
<
context-param
>
<
param-name
>
javax.faces.STATE_SAVING_METHOD<
/
param-name
>
<
param-value
>
client<
/
param-value
>
<
/
context-param
>
<
context-param
>
<
param-name
>
javax.faces.FACELETS_SKIP_COMMENTS<
/
param-name
>
<
param-value
>
true<
/
param-value
>
<
/
context-param
>
<
context-param
>
<
param-name
>
javax.faces.PROJECT_STAGE<
/
param-name
>
<
param-value
>
Development<
/
param-value
>
<
/
context-param
>
<
servlet
>
<
servlet-name
>
Faces Servlet<
/
servlet-name
>
<
servlet-class
>
javax.faces.webapp.FacesServlet<
/
servlet-class
>
<
load-on-startup
>
1<
/
load-on-startup
>
<
/
servlet
>
<
servlet-mapping
>
<
servlet-name
>
Faces Servlet<
/
servlet-name
>
<
url-pattern
>
/faces/*<
/
url-pattern
>
<
/
servlet-mapping
>
<
session-config
>
<
session-timeout
>
30<
/
session-timeout
>
<
/
session-config
>
<
welcome-file-list
>
<
welcome-file
>
faces/index.xhtml<
/
welcome-file
>
<
/
welcome-file-list
>
<
/
web-app
>
C'est le fichier standard de configuration d'une application web JSF. Il n'y a rien ici de spécifique à Primefaces Mobile.
XII-E-5. Le fichier de configuration [faces-config.xml]▲
Le fichier [faces-config.xml] configure les différents éléments d'une application JSF. Il est ici le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
<?
xml
version='1.0'
encoding='UTF-8'?
>
<
faces-config
version
=
"
2.0
"
xmlns
=
"
http://java.sun.com/xml/ns/javaee
"
xmlns
:
xsi
=
"
http://www.w3.org/2001/XMLSchema-instance
"
xsi
:
schemaLocation
=
"
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd
"
>
<
application
>
<
resource-bundle
>
<
base-name
>
messages
<
/
base-name
>
<
var
>
msg<
/
var
>
<
/
resource-bundle
>
<
message-bundle
>
messages<
/
message-bundle
>
<
default-render-kit-id
>
PRIMEFACES_MOBILE<
/
default-render-kit-id
>
<
/
application
>
<
managed-bean
>
<
managed-bean-name
>
applicationData<
/
managed-bean-name
>
<
managed-bean-class
>
arduino.jsf.beans.ApplicationData<
/
managed-bean-class
>
<
managed-bean-scope
>
application<
/
managed-bean-scope
>
<
/
managed-bean
>
<
managed-bean
>
<
managed-bean-name
>
sessionData<
/
managed-bean-name
>
<
managed-bean-class
>
arduino.jsf.beans.SessionData<
/
managed-bean-class
>
<
managed-bean-scope
>
session<
/
managed-bean-scope
>
<
/
managed-bean
>
<
managed-bean
>
<
managed-bean-name
>
requestData<
/
managed-bean-name
>
<
managed-bean-class
>
arduino.jsf.beans.RequestData<
/
managed-bean-class
>
<
managed-bean-scope
>
request<
/
managed-bean-scope
>
<
managed-property
>
<
property-name
>
applicationData<
/
property-name
>
<
value
>
#{applicationData}<
/
value
>
<
/
managed-property
>
<
/
managed-bean
>
<
/
faces-config
>
- lignes 12-18 : définissent le fichier des messages internationalisés. Il s'appelle [messages.properties] et se trouve dans le Classpath du projet [1] :
- ligne 19 : un kit de rendu des pages XHTML. Ici, c'est le kit de Primefaces Mobile qui est utilisé ;
- lignes 23-27 : le bean de portée [Application][2] ;
- lignes 29-33 : le bean de portée [Session] [2] ;
- lignes 35-43 : le bean de portée [Request] [2]. On notera qu'on y injecte la référence du bean de portée [Application] (lignes 39-42).
XII-E-6. Le bean de portée [Application]▲
Le bean de portée [Application] est instancié au démarrage de l'application JSF. Il reste en mémoire pendant la durée de vie de l'application d'où son nom. Il est accessible à toutes les requêtes de tous les utilisateurs. A cause de cette concurrence d'accès, il est généralement utilisé uniquement en lecture.
Le code de la classe [ApplicationData] est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
package
arduino.jsf.beans;
...
public
class
ApplicationData implements
IMetier, Serializable {
private
static
final
long
serialVersionUID =
1L
;
private
IMetier metier;
@
PostConstruct
public
void
init
(
) {
metier =
(
IMetier) new
ClassPathXmlApplicationContext
(
"
spring-restClient.xml
"
).getBean
(
"
metier
"
);
}
@
Override
public
Collection<
Arduino>
getArduinos
(
) {
return
metier.getArduinos
(
);
}
@
Override
public
Reponse pinRead
(
String idCommande, String idArduino, int
pin, String mode) {
return
metier.pinRead
(
idCommande, idArduino, pin, mode);
}
@
Override
public
Reponse pinWrite
(
String idCommande, String idArduino, int
pin, String mode, int
val) {
return
metier.pinWrite
(
idCommande, idArduino, pin, mode, val);
}
@
Override
public
Reponse faireClignoterLed
(
String idCommande, String idArduino, int
pin, int
millis, int
nbIter) {
return
metier.faireClignoterLed
(
idCommande, idArduino, pin, millis, nbIter);
}
@
Override
public
List<
String>
sendCommandesJson
(
String idArduino, List<
String>
commandes) {
return
metier.sendCommandesJson
(
idArduino, commandes);
}
@
Override
public
List<
Reponse>
sendCommandes
(
String idArduino, List<
Commande>
commandes) {
return
metier.sendCommandes
(
idArduino, commandes);
}
}
- ligne 5 : la classe [ApplicationData] implémente l'interface [IMetier] du client REST ;
- ligne 9 : une référence sur le client REST ;
- lignes 13-16 : la référence sur le client REST est obtenue avec Spring ;
- lignes 18-46 : implémentation de l'interface [IMetier]. Tout est délégué au client REST. Nous allons ainsi pouvoir cacher le client REST aux autres beans de l'application JSF. Ceux-ci s'adresseront au bean [ApplicationData] plutôt qu'au client REST.
XII-E-7. Le fichier de configuration de Spring▲
Le fichier [spring-restClient.xml] est le même que celui décrit au paragraphe , page .
XII-E-8. Le bean de portée Session▲
Le bean de portée [Session] est la mémoire d'un utilisateur. Toutes les requêtes de celui-ci peuvent écrire et lire de l'information dans cette mémoire. Ici, nous ne mémorisons qu'une information :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
package
arduino.jsf.beans;
public
class
SessionData {
String locale=
"
fr
"
;
...
}
- ligne 6 : l'unique information de la session : la langue utilisée pour les pages affichées par l'application web mobile, ici le français.
XII-E-9. La page principale du projet PFM (PrimeFaces Mobile)▲
La page [index.html] ci-dessus est l'unique page du projet PFM. Son code est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
<
f
:
view
xmlns
=
"
http://www.w3.org/1999/xhtml
"
xmlns
:
f
=
"
http://java.sun.com/jsf/core
"
xmlns
:
h
=
"
http://java.sun.com/jsf/html
"
xmlns
:
ui
=
"
http://java.sun.com/jsf/facelets
"
xmlns
:
p
=
"
http://primefaces.org/ui
"
xmlns
:
pm
=
"
http://primefaces.org/mobile
"
contentType
=
"
text/html
"
locale
=
"
#{sessionData.locale}
"
>
<
pm
:
page
title
=
"
#{msg['appli.titre']}
"
>
<
pm
:
view
id
=
"
home
"
>
<
ui
:
include
src
=
"
home.xhtml
"
/
>
<
/
pm
:
view
>
<
pm
:
view
id
=
"
commands
"
>
<
ui
:
include
src
=
"
commands.xhtml
"
/
>
<
/
pm
:
view
>
<
pm
:
view
id
=
"
blink
"
>
<
ui
:
include
src
=
"
blink.xhtml
"
/
>
<
/
pm
:
view
>
<
pm
:
view
id
=
"
pinWrite
"
>
<
ui
:
include
src
=
"
pinWrite.xhtml
"
/
>
<
/
pm
:
view
>
<
pm
:
view
id
=
"
pinRead
"
>
<
ui
:
include
src
=
"
pinRead.xhtml
"
/
>
<
/
pm
:
view
>
<
/
pm
:
page
>
<
/
f
:
view
>
- la page [index.html] est l'unique page affichée par l'application. Elle définit cinq vues. A un moment donné, une seule de ces cinq vues est visible. Au démarrage c'est la première qui est visible. On passe ensuite aux suivantes par navigation ;
- lignes 11-13 : la vue [home]. Elle présente quatre liens pour passer à l'une des quatre autres vues. Ces dernières ont toutes une icône permettant de revenir à la vue [home] ;
- lignes 14-16 : la vue [commands] permet d'envoyer une commande JSON aux Arduinos connectés ;
- lignes 17-19 : la vue [blink] permet de faire clignoter une led sur les Arduinos connectés ;
- lignes 20-22 : la vue [pinWrite] permet d'écrire une valeur binaire ou analogique sur l'une des pins des Arduinos connectés ;
- lignes 23-25 : la vue [pinRead] permet de lire la valeur binaire ou analogique de l'une des pins des Arduinos connectés.
XII-E-10. La vue [home]▲
Le code [home.xhtml] de cette vue est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
<?
xml
version='1.0'
encoding='UTF-8'
?
>
<!
DOCTYPE
html
PUBLIC
"
-//W3C//DTD
XHTML
1.0
Transitional//EN
"
"
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
"
>
<
html
xmlns
=
"
http://www.w3.org/1999/xhtml
"
xmlns
:
h
=
"
http://java.sun.com/jsf/html
"
xmlns
:
p
=
"
http://primefaces.org/ui
"
xmlns
:
f
=
"
http://java.sun.com/jsf/core
"
xmlns
:
pm
=
"
http://primefaces.org/mobile
"
xmlns
:
ui
=
"
http://java.sun.com/jsf/facelets
"
>
<
pm
:
header
title
=
"
#{msg['appli.titre']}
"
swatch
=
"
b
"
>
<
f
:
facet
name
=
"
left
"
>
<
p
:
button
icon
=
"
gear
"
value
=
"
"
href
=
"
#config
"
/
>
<
/
f
:
facet
>
<
/
pm
:
header
>
<
pm
:
content
>
<
h
:
form
id
=
"
frmHome
"
>
<
p
:
dataList
id
=
"
list1
"
type
=
"
inset
"
>
<
f
:
facet
name
=
"
header
"
>
#{msg['menu.options']}<
/
f
:
facet
>
<
h
:
outputLink
value
=
"
#pinRead
"
>
#{msg['menu.pinRead']}<
/
h
:
outputLink
>
<
h
:
outputLink
value
=
"
#pinWrite
"
>
#{msg['menu.pinWrite']}<
/
h
:
outputLink
>
<
h
:
outputLink
value
=
"
#blink
"
>
#{msg['menu.clignoter']}<
/
h
:
outputLink
>
<
h
:
outputLink
value
=
"
#commands
"
>
#{msg['menu.commande']}<
/
h
:
outputLink
>
<
/
p
:
dataList
>
<
/
h
:
form
>
<
/
pm
:
content
>
<
pm
:
footer
>
<
div
align
=
"
center
"
>
&
copy
;
Votre nom<
br
/
>
ISTIA, université d'Angers
<
/
div
>
<
/
pm
:
footer
>
<
/
html
>
- lignes 11-15 : définissent l'entête de la vue ;
- ligne 11 : le libellé [1] de l'entête ;
- ligne 13 : le bouton [2]. Un clic sur ce bouton fait apparaître la vue [config.xhtml] (attribut href). Ici, elle n'a pas été définie. Donc le bouton sera inactif. Vous pourrez le rendre actif en créant une vue [config.xhtml] permettant de choisir une autre langue que le français ;
- ligne 16 : le contenu de la vue. La balise est obligatoire ;
- ligne 17 : le formulaire. Il y en normalement un dans chaque vue ;
- ligne 18 : l'unique composant de ce formulaire, un [DataList] ;
- ligne 19 : le libellé [3] du DataList ;
- ligne 20 : un lien vers la vue [pinRead] (attribut value) ;
- ligne 21 : un lien vers la vue [pinWrite] (attribut value) ;
- ligne 22 : un lien vers la vue [blink] (attribut value) ;
- ligne 23 : un lien vers la vue [commands] (attribut value) ;
- ligne 29 : le bas de page de la vue affichée. Mettez votre nom par exemple.
XII-E-11. Le fichier des messages▲
La vue [home.xhtml] détaillée précédemment utilise des messages provenant du fichier [messages_fr.properties] :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
appli
.
titre
=Mobilit\u00e9 & Domotique
appli
.
footer
=
menu
.
options
=Options
menu
.
pinWrite
=| Ecrire une valeur sur une pin
menu
.
pinRead
=| Lire la valeur d'une pin
menu
.
clignoter
=| Faire clignoter une led
menu
.
commande
=| Envoyer une commande Json
menu
.
rafraichir
=Rafra\u00eechir la liste
cmd
.
executer
=Ex\u00e9cuter
cmd
.
rafraichir
=Rafra\u00eechir
.
.
.
Si vous internationalisez l'application en y ajoutant l'anglais, vous aurez à remplir le fichier [messages_en.properties] avec les mêmes clés que dans [messages_fr.properties] et des textes en anglais.
XII-E-12. La vue [blink.xhtml]▲
Nous allons examiner l'une des vues comme exemple à suivre. La vue [blink.xhtml] est la suivante :
Le code XHTML est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
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.
<?
xml
version='1.0'
encoding='UTF-8'
?
>
<!
DOCTYPE
html
PUBLIC
"
-//W3C//DTD
XHTML
1.0
Transitional//EN
"
"
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
"
>
<
html
xmlns
=
"
http://www.w3.org/1999/xhtml
"
xmlns
:
h
=
"
http://java.sun.com/jsf/html
"
xmlns
:
p
=
"
http://primefaces.org/ui
"
xmlns
:
f
=
"
http://java.sun.com/jsf/core
"
xmlns
:
pm
=
"
http://primefaces.org/mobile
"
xmlns
:
ui
=
"
http://java.sun.com/jsf/facelets
"
>
<
pm
:
header
title
=
"
#{msg['appli.titre']}
"
swatch
=
"
b
"
>
<
f
:
facet
name
=
"
left
"
>
<
p
:
button
icon
=
"
home
"
value
=
"
"
href
=
"
#home
"
/
>
<
/
f
:
facet
>
<
/
pm
:
header
>
<
pm
:
content
>
<
h
:
form
id
=
"
frmBlink
"
>
<
h2
>
#{msg['listeArduinos']}<
/
h2
>
<
h
:
selectManyCheckbox
id
=
"
arduinos
"
value
=
"
#{requestData.selectedArduinosIds}
"
>
<
f
:
selectItems
var
=
"
arduino
"
value
=
"
#{applicationData.arduinos}
"
itemLabel
=
"
#{arduino.ip}
"
itemValue
=
"
#{arduino.id}
"
/
>
<
/
h
:
selectManyCheckbox
>
<
br
/
>
<
p
:
commandButton
id
=
"
cmdRafraichir
"
value
=
"
#{msg['menu.rafraichir']}
"
immediate
=
"
true
"
update
=
"
:frmBlink:arduinos
"
/
>
<
h2
>
#{msg['commande']}<
/
h2
>
<
pm
:
field
>
<
h
:
outputLabel
for
=
"
pin
"
value
=
"
#{msg['pinNumber']}
"
/
>
<
h
:
selectOneMenu
id
=
"
pin
"
value
=
"
#{requestData.pin}
"
>
<
f
:
selectItems
var
=
"
pin
"
value
=
"
#{requestData.pins}
"
itemLabel
=
"
#{pin}
"
itemValue
=
"
#{pin}
"
/
>
<
/
h
:
selectOneMenu
>
<
/
pm
:
field
>
<
h
:
outputText
id
=
"
msgErreur1
"
value
=
"
#{msg[requestData.pinMessageKey]}
"
style
=
"
color:
red
"
/
>
<
pm
:
field
>
<
h
:
outputLabel
for
=
"
millis
"
value
=
"
#{msg['clignoter.dureeClignotement']}
"
/
>
<
h
:
inputText
id
=
"
millis
"
value
=
"
#{requestData.dureeClignotement}
"
required
=
"
true
"
requiredMessage
=
"
#{msg['clignoter.dureeClignotement.required']}
"
validatorMessage
=
"
#{msg['clignoter.dureeClignotement.error']}
"
>
<
f
:
validateLongRange
minimum
=
"
100
"
maximum
=
"
10000
"
/
>
<
/
h
:
inputText
>
<
/
pm
:
field
>
<
h
:
message
id
=
"
msgErreur2
"
for
=
"
millis
"
style
=
"
color:
red
"
/
>
<
pm
:
field
>
<
h
:
outputLabel
for
=
"
nbClignotements
"
value
=
"
#{msg['clignoter.nbClignotements']}
"
/
>
<
h
:
inputText
id
=
"
nbClignotements
"
value
=
"
#{requestData.nbClignotements}
"
required
=
"
true
"
requiredMessage
=
"
#{msg['clignoter.nbClignotements.required']}
"
validatorMessage
=
"
#{msg['clignoter.nbClignotements.error']}
"
>
<
f
:
validateLongRange
minimum
=
"
1
"
maximum
=
"
100
"
/
>
<
/
h
:
inputText
>
<
/
pm
:
field
>
<
h
:
message
id
=
"
msgErreur3
"
for
=
"
nbClignotements
"
style
=
"
color:
red
"
/
>
<
p
:
commandButton
id
=
"
clignoter
"
value
=
"
#{msg['cmd.executer']}
"
action
=
"
#{requestData.clignoter}
"
update
=
"
:frmBlink:msgErreur1
:frmBlink:msgErreur2
:frmBlink:msgErreur3
:frmBlink:reponses
:frmBlink:exceptions
"
/
>
<
p
:
outputPanel
id
=
"
reponses
"
>
<
ui
:
fragment
rendered
=
"
#{!
empty
requestData.réponses}
"
>
<
hr
/
>
<
h2
>
#{msg['réponsesArduinos']}<
/
h2
>
<
ul
>
<
ui
:
repeat
var
=
"
réponse
"
value
=
"
#{requestData.réponses}
"
>
<
li
>
#{réponse}<
/
li
>
<
/
ui
:
repeat
>
<
/
ul
>
<
/
ui
:
fragment
>
<
/
p
:
outputPanel
>
<
p
:
outputPanel
id
=
"
exceptions
"
>
<
ui
:
fragment
rendered
=
"
#{!
empty
requestData.exceptions}
"
>
<
hr
/
>
<
h2
>
#{msg['exceptions']}<
/
h2
>
<
ul
>
<
ui
:
repeat
var
=
"
exception
"
value
=
"
#{requestData.exceptions}
"
>
<
li
>
#{exception}<
/
li
>
<
/
ui
:
repeat
>
<
/
ul
>
<
/
ui
:
fragment
>
<
/
p
:
outputPanel
>
<
/
h
:
form
>
<
/
pm
:
content
>
<
pm
:
footer
>
<
div
align
=
"
center
"
>
&
copy
;
Votre nom<
br
/
>
ISTIA, université d'Angers
<
/
div
>
<
/
pm
:
footer
>
<
/
html
>
- lignes 10-14 : l'entête [1] de la vue ;
- ligne 12 : l'icône [A] qui permet de revenir sur la vue [home] (attribut href) ;
- ligne 15 : le contenu de la vue ;
- ligne 16 : le formulaire de la vue ;
- ligne 18 : le libellé [2] ;
- ligne 19-21 : la liste de cases à cocher [3] ;
- ligne 19 : la liste des éléments cochés sera mémorisée dans le champ [selectedArduinosIds] du bean de portée [request] RequestData :
Sélectionnez 1.
private
String[] selectedArduinosIds;
- ligne 20 : la liste est alimentée par la liste des Arduinos connectés. Celle-ci est trouvée dans le bean [ApplicationData] :
Sélectionnez 1.
2.
3.
4.
@
Override
public
Collection<
Arduino>
getArduinos
(
) {
return
metier.getArduinos
(
);
}
Le libellé de chaque case à cocher sera l'adresse IP de l'Arduino (attribut itemLabel). La valeur postée sera l'identifiant de l'Arduino (attribut itemValue) ;
- ligne 24 : le bouton [4] qui demande au client REST, la liste des Arduinos connectés. Elle le fait avec un appel Ajax, donc sans rechargement de page. L'attribut immediate=true permet de s'affranchir des tests de validité du formulaire. Ils ne seront pas exécutés. La liste des Arduinos de la ligne 19 sera régénérée (attribut update) ;
- ligne 25 : le libellé [5] ;
- ligne 27 : un champ PFM ;
- ligne 28 : le libellé [6]. Notez l'attribut for qui référence le composant d'id pin. PFM utilise cette information pour sa mise en page ;
- lignes 29-31 : la liste déroulante [7]. La valeur postée sera affectée (attribut value) au champ suivant de [RequestData] :
Sélectionnez
- ligne 30 : la liste déroulante est alimentée (attribut value) par le champ suivant de [RequestData] :
Sélectionnez 1.
private
String[] pins =
{
"
0
"
, "
1
"
, "
2
"
, "
3
"
, "
4
"
, "
5
"
, "
6
"
, "
7
"
, "
8
"
, "
9
"
, "
10
"
, "
11
"
, "
12
"
, "
13
"
}
;
Le libellé affiché (itemLabel) ainsi que la valeur postée (itemValue) seront le n° de pin.
- ligne 33 : un message d'erreur non représenté sur la copie d'écran. Ce message apparaît si le n° de pin posté est invalide ;
- lignes 35-42 : le champ PFM des composants 8 et 9 ;
- ligne 36 : le libellé [8] ;
-
ligne 37 : le champ de saisie [9]. La valeur postée sera mémorisée (attribut value) dans le champ suivant de [RequestData] :
On peut se demander si un type int n'aurait pas été préférable. A l'affichage, la valeur initiale de [dureeClignotement] est affichée dans le champ de saisie. Si le type est int, la valeur 0 est affichée. Avec le type String, rien n'est affiché. D'où le choix de ce dernier ;
Sélectionnez 1.
private
String dureeClignotement;
-
ligne 38 : le champ est obligatoire (attribut required) ;
-
ligne 40 : la durée du clignotement doit être entre 100 ms et 10 s ;
-
ligne 43 : affiche un message d'erreur si la saisie est incorrecte ;
-
lignes 45-53 : on a un code analogue pour la saisie du nombre de clignotements ;
- lignes 54 : le bouton qui va faire clignoter les leds des Arduinos sélectionnés ;
La méthode [RequestData].clignoter va être exécutée (attribut action) par un appel Ajax. Les zones identifiées par msgErreur1 (ligne 33), msgErreur2 (ligne 43), msgErreur3 (ligne 53), reponses (ligne 56), exceptions (ligne 69) seront mises à jour avec le résultat de l'appel Ajax (attribut update) ;
- lignes 56-67 : un bloc de la vue qui contient le fragment de la ligne 57. Celui-ci n'est affiché que si après l'appel Ajax, la liste des réponses des Arduinos est non vide ;
- ligne 60 : le libellé [1] ;
-
lignes 62-64 : ce code produit la liste [2] alimentée par le champ suivant de [RequestData] :
Sélectionnez 1.
private
List<
String>
réponses;
-
ligne 63 : la réponse d'un Arduino est affichée sous la forme [2] ;
-
lignes 69-80 : un bloc de la vue qui contient le fragment de la ligne 70. Celui-ci n'est affiché que si après l'appel Ajax, la liste des exceptions produite par l'appel Ajax est non vide ;
-
ligne 73 : le libellé [1] ;
-
lignes 74-78 : ce code produit la liste [2] alimentée par le champ suivant de [RequestData] :
Sélectionnez 1.
private
List<
String>
exceptions;
- ligne 76 : chaque exception est affichée sous la forme [2]. Ici le câble réseau des Arduinos avait été déconnecté de l'ordinateur hôte ;
La vue [blink.xhtml] utilise les messages suivants du fichier [messages_fr.properties] :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
appli
.
titre
=Mobilit\u00e9 & Domotique
appli
.
footer
=
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
cmd
.
clignoter
=Faire clignoter
clignoter
.
nbClignotements
=Nombre de clignotements
clignoter
.
dureeClignotement
=Dur\u00e9e du clignotement en millisecondes
clignoter
.
dureeClignotement
.
required
=Donn\u00e9e requise
clignoter
.
nbClignotements
.
required
=Donn\u00e9e requise
clignoter
.
dureeClignotement
.
error
=La dur\u00e9e doit \u00eatre comprise entre 100
et 10000
millisecondes
clignoter
.
nbClignotements
.
error
=Le nombre de clignotements doit \u00eatre compris entre 1
et 100
.
.
.
.
.
.
.
.
.
.
.
.
.
exceptions
=Il y a eu des exceptions
Pour terminer cet exemple, il ne nous reste plus qu'à décrire la méthode [RequestData].clignoter qui fait clignoter les leds.
XII-E-13. La méthode [RequestData].clignoter▲
Le code de la méthode est le suivant :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
public
String clignoter
(
) throws
IOException {
réponses =
new
ArrayList<
String>
(
);
exceptions =
new
ArrayList<
String>
(
);
for
(
String arduinoId : selectedArduinosIds) {
try
{
réponses.add
(
applicationData.faireClignoterLed
(
arduinoId, arduinoId, pin, Integer.parseInt
(
dureeClignotement),
Integer.parseInt
(
nbClignotements)).toString
(
));
}
catch
(
Exception ex) {
recordException
(
ex);
}
}
return
null
;
}
- pour comprendre ce code, il faut se souvenir que les champs [selectedArduinosIds, dureeClignotement, nbClignotements] ont reçu les valeurs saisies par l'utilisateur ;
- ligne 7 : on parcourt la liste des Arduinos sélectionnés par l'utilisateur ;
- ligne 10 : pour chacun d'eux, on demande au bean [ApplicationData] de faire clignoter la led. Les réponses JSON des Arduinos sont cumulés dans la liste de la ligne 4 ;
- ligne 14 : les exceptions sont cumulées dans la liste de la ligne 5 par la méthode privée [recordException] suivante :
Sélectionnez 1.
2.
3.
4.
5.
6.
7.
8.
9.
private
void
recordException
(
Exception ex) {
Throwable th =
ex;
while
(
th !
=
null
) {
exceptions.add
(
th.getMessage
(
));
th =
th.getCause
(
);
}
}
: en suivant le modèle qui vient d'être décrit pour la vue [blink], réalisez les vues [pinWrite], [pinRead] et [commands].