Introduction I - Prise en main Eclipse, projet WTP avec JSF

Transcription

Introduction I - Prise en main Eclipse, projet WTP avec JSF
Guide pour le développement rapide
d'une apllication pour mobiles
JSF (Trinidad) – Spring
Version 1.0
Auteur: Yves Deschamps
[email protected]
Introduction
Comment réaliser rapidement une application pour mobiles avec les outils de
développement Java les plus usuels dans la communauté Esup ? Voici une proposition,
expliquée pas à pas et sans doute à compléter.
On s'attache ici à la couche « présentation », on suppose qu'une autre application délivre
les données « métier » que l'on pourra récupérer via un Web Service.
En suivant ces explications, il est relativement aisé d'aboutir à une maquette qui puisse
dérouler quelque écrans.
Les difficultés surgiront lorsqu'il faudra adapter le résultat, plutôt prévu pour Iphone à des
mobiles différents avec des systèmes d'exploitation et des navigateurs variés.
I - Prise en main Eclipse, projet WTP avec JSF
Prérequis: utiliser Eclipse Ganymede Version: 3.4.0 et disposer d'un serveur d'application
Tomcat 6.
… et disposer des librairies JSF nécessaires (au moins Apache MyFaces et Trinidad):
Créer un nouveau projet JSF en utilisant la perspective JEE d'Eclipse et créer un nouveau
projet dynamique Web:
...puis poursuivre « Next » jusqu'ici, vérifier les éléments indiqués et terminer:
II - Où placer les sources, classes compilées, ressources et
fichiers de propriétés
Vous pouvez utiliser les options par défaut, c'est même recommandé pour pouvoir
distribuer l'application en utilisant la possibilité de créer un fichier WAR depuis un projet
Eclipse WTP:
•
•
•
•
•
les sources java et les fichiers de propriétés, les fichiers MessageResouces sont
dans le répertoire src.
les classes compilées, les fichiers de propriétés, les fichiers MessageResouces sont
déposés dans monProjet/build à chaque « build » du projet (qu'on peut laisser en
mode « automatique » d'ailleurs).
la Javadoc peut être générée automatiquement par la commande « Export /
Javadoc ». Par défaut elle se place dans un répertoire « doc » où vous pouvez aussi
déposer votre manuel ou un fichier « readme.txt » pour les utilisateurs.
Les fichiers de configuration JSF (faces-config.xml), Trinidad (trinidad-config.xml et
trinidad-skins.xml), Spring (au minimum applicationContext.xml) et d'éventuels
librairies de tag (castools.tld pour utiliser une page jsp de login CAS) se trouvent
dans le répertoire WebContent/WEB-INF, tout comme le fichier web.xml qui prend
en compte les particularités de ce type de projet (Trinidad, Spring)1.
Les librairies spécifiques au projet doivent être présentes dans le répertoire
WebContent/WEB-ING/lib. On vérifiera qu'elles sont bien « exportées » à l'aide de
1 Voir les exemples plus loin.
•
•
la propriété Build Path du projet Eclipse.
Les pages jsp sont placées directement dans le répertoire WebContent du projet.
Les skins utilisés par Trinidad pour les mobiles sont à placer dans le répertoire
WebContent avec un répertoire par skin et un répertoire spécifique pour les
images2.
Ce qui donne l'arboresence suivante:
Les fichiers MessageResouces peuvent être facilement éditables en utilisant le plugin
Eclipse RBE (Resource Bundle Editor). On mettra en place une classe de service Java pour
pouvoir manipuler dans l'application ces ressources3.
Le fichier faces-config.xml peut être édité via l'interface intégrée d'Eclipse, Faces Config
Editor qui possède un mode visuel pour la partie Navigation Rule. Il peut être aussi
pratique pour définir les Converters et Validators JSF de l'application.
2 Voir dans les exemples, un bout de code css.
3 BundleService.java, voir les exemples plus loin.
Si vous utilisez Spring pour configurer l'application, il n'est pas recommandé d'utiliser les
possibilités offertes par la partie « ManagedBean » de JSF (dans le même fichier facesconfig.xml). On utilisera alors à minima le fichier applicationContext.xml4.
4 Qui lui-même peut faire référence à d'autres fichiers de configuration Spring si nécessaire. Voir un exemple simple
plus loin.
L'avantage de la configuration avec Spring, c'est de pouvoir utiliser des fichiers de
propriétés pour injecter les bonnes données dans les beans via un Spring bean nommé
"propertyConfigurer"5. Ces fichiers sont placés dans le répertoire src.
Une attention particulière est à apporter à l'implémentation des différents skins utilisés par
Trinidad. On peut consulter la documentation Trinidad et celle d'Oracle à ce sujet.
Néanmoins, nous avons été amenés à corriger l'appel aux images dans les css6.
III - distribution de l'application
On peut créer un fichier WAR avec Eclipse en chosissant « Export / WAR File » depuis le
projet (cliquez sur « Export source files » pour distribuer en même temps les sources).
Néanmoins, si vous désirez ne pas propager votre fichier config.properties, il vous faudra
le ranger provisoirement ailleurs que dans monProjet/src avant de procéder à cet Export.
L'utilisateur final aura à sa charge la réalisation manuelle du fichier config.properties
depuis defaults.properties (à expliciter dans la documentation jointe à l'application).
La documentation (javadoc, manuel ou fichier readme.txt) doit être distribuée séparément
car elle n'est pas reprise dans le fichier WAR. Ceci peut être réalisé avec Eclipse en
choisissant « Export / Jar File » depuis de répertoire « doc ».
IV - Exemples
•
web.xml, ce fichier est présenté ici dans sa totalité, le listener pour Spring est
indispensable pour que la configuration Spring soit prise en compte. Le servletmapping ressources est indispensable pour la mise en action des ressources inclues
dans les composants trinidad. Le paramètre session-timeout est important de
même que la manière de gérer par une page jsp l'erreur 500 consécutive à une
perte de connexion (voir plus loin).
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/webapp_2_5.xsd">
<display-name>esup-annuaire-mobile</display-name>
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
<param-value>resources.application</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<!-Trinidad also supports an optimized strategy for caching some view
state at an application level, which significantly improves
scalability. However, it makes it harder to develop (updates to pages
will not be noticed until the server is restarted), and in some rare
cases cannot be used for some pages (see Trinidad documentation for
more information)
-->
5 Voir l'exemple applicationContext.xml.
6 Voir « code.css » dans les exemples.
<context-param>
<param-name>org.apache.myfaces.trinidad.USE_APPLICATION_VIEW_CACHE</param-name>
<param-value>false</param-value>
</context-param>
<!-If this parameter is enabled, Trinidad will automatically check the
modification date of your JSPs, and discard saved state when they
change; this makes development easier, but adds overhead that should
be avoided when your application is deployed
-->
<context-param>
<param-name>org.apache.myfaces.trinidad.CHECK_FILE_MODIFICATION</param-name>
<param-value>true</param-value>
</context-param>
<!-Enables Change Persistence at a session scope. By default, Change
Persistence is entirely disabled. The ChangeManager is an API, which
can persist component modifications (like, is a showDetail or tree
expanded or collapsed). For providing a custom Change Persistence
implementation inherit from the Trinidad API's ChangeManager class. As
the value you have to use the fullqualified class name.
-->
<context-param>
<param-name>org.apache.myfaces.trinidad.CHANGE_PERSISTENCE</param-name>
<param-value>session</param-value>
</context-param>
<filter>
<filter-name>trinidad</filter-name>
<filter-class>org.apache.myfaces.trinidad.webapp.TrinidadFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>trinidad</filter-name>
<servlet-name>faces</servlet-name>
</filter-mapping>
<!-- for Spring -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Faces Servlet -->
<servlet>
<servlet-name>faces</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
<!-- resource loader servlet -->
<servlet>
<servlet-name>resources</servlet-name>
<servlet-class>org.apache.myfaces.trinidad.webapp.ResourceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>resources</servlet-name>
<url-pattern>/adf/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<context-param>
<param-name>org.apache.myfaces.ERROR_HANDLING</param-name>
<param-value>false</param-value>
</context-param>
<error-page>
<error-code>500</error-code>
<location>/errordisplay.jsp</location>
</error-page>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
</web-app>
•
faces-config.xml, ce fichier est ici présenté en partie pour la partie Validators,
langues et Spring.
<!-- For Validators -->
<validator>
<description>A validator for search a name</description>
<display-name>Name Validator</display-name>
<validator-id>nameValidator</validator-id>
<validator-class>
org.esupportail.annuaire.mobile.web.validators.NameValidator
</validator-class>
</validator>
<application>
<default-render-kit-id>
org.apache.myfaces.trinidad.core
</default-render-kit-id>
<locale-config>
<default-locale>fr</default-locale>
<supported-locale>en</supported-locale>
</locale-config>
<message-bundle>MessageResources</message-bundle>
<!-- For Spring -->
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver
</el-resolver>
</application>
•
trinidad-config.xml, ce fichier est le point d'entrée pour utiliser le mécanisme ,de
détection des types de mobiles (voir plus loin le Spring bean agentUtil et la classe
java associée) et permet de gérer l'accessibilité et le mode debug des composants
trinidad.
<?xml version="1.0"?>
<trinidad-config xmlns="http://myfaces.apache.org/trinidad/config">
<!-- Enable debug output -->
<debug-output>false</debug-output>
<accessibility-mode>default</accessibility-mode>
<skin-family>#{agentUtil.phoneFamily}</skin-family>
</trinidad-config>
•
trinidad-skins.xml, ce fichier décrit les skins associés aux différents types de
mobiles.
<?xml version="1.0" encoding="UTF-8"?>
<skins xmlns="http://myfaces.apache.org/trinidad/skin">
<skin>
</skin>
<id>blackberry</id>
<family>blackberryFamily</family>
<render-kit-id>org.apache.myfaces.trinidad.pda
</render-kit-id>
<style-sheet-name>blackberry/blackberry.css</style-sheet-name>
<skin>
<id>windowsPhoneMobile</id>
<family>windowsPhoneFamily</family>
<render-kit-id>org.apache.myfaces.trinidad.desktop
</render-kit-id>
<style-sheet-name>windowsMobile/windowsMobile.css</style-sheet-name>
</skin>
<skin>
<id>windowsPdaMobile</id>
<family>windowsPdaFamily</family>
<render-kit-id>org.apache.myfaces.trinidad.pda
</render-kit-id>
<style-sheet-name>windowsMobile/windowsMobile.css</style-sheet-name>
</skin>
<skin>
<id>iphone</id>
<family>iphoneFamily</family>
<render-kit-id>org.apache.myfaces.trinidad.desktop
</render-kit-id>
<style-sheet-name>iPhone/iPhone.css</style-sheet-name>
</skin>
</skins>
•
applicationContext.xml, c'est le fichier de base de la configuration Spring. Dans
cet exemple, un Web Service de type Xfire est configuré. La configuration via des
fichiers de propriétés est mise en avant.
<?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:tx="http://www.springframework.org/schema/tx"
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">
<context:annotation-config />
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/defaults.properties</value>
<value>classpath:/config.properties</value>
</list>
</property>
</bean>
<bean id="authInformation"
class="org.esupportail.annuaire.mobile.authentication.CASService">
<property name="authenticateURL" value="${cas.url}/login" />
<property name="validateURL" value="${cas.url}/validate" />
<property name="logoutURL" value="${cas.url}/logout" />
</bean>
<bean id="remoteInformation" lazy-init="true"
class="org.codehaus.xfire.spring.remoting.XFireClientFactoryBean">
<property name="serviceClass"
value="org.esupportail.annuaire.services.remote.SearchService" />
<property name="wsdlDocumentUrl" value="${ws.documentUrl}" />
</bean>
<bean id="commonController"
class="org.esupportail.annuaire.mobile.web.controllers.CommonController">
<property name="locale" value="fr" />
<property name="casLogoutUrl" value="${cas.url}/logout?service=" />
<property name="facultyWebPageUrl" value="${misc.facultyWebPageUrl}" />
<property name="googleKey" value="${misc.googleMapsKey}" />
</bean>
<bean id="agentUtil" class="org.esupportail.annuaire.mobile.utils.AgentUtil">
<property name="skins">
<map>
<entry key="iPhone" value="iphoneFamily" />
<entry key="Pre" value="iphoneFamily" />
<entry key="Symbian" value="symbianFamily" />
<entry key="BlackBerry9000" value="blackberryFamily" />
<entry key="Windows Phone" value="windowsPhoneFamily" />
<entry key="Windows CE" value="windowsPdaFamily" />
</map>
</property>
</bean>
</beans>
•
defaults.properties
########################################################################
# Authentication
#
cas.url=https://cas.domain.edu
########################################################################
# WEB SERVICES
#
ws.documentUrl=
########################################################################
# MISC
#
misc.facultyWebPageUrl=http://www.domain.edu
misc.googleMapsKey=
•
AgentUtil.java
package org.esupportail.annuaire.mobile.utils;
import java.util.Iterator;
import java.util.Map;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Scope;
/**
* @author Yves Deschamps (Université de Lille 1) - 2010
*
*/
@Configurable
@Scope("request")
public class AgentUtil {
private String phoneFamily;
private Map<String, String> skins;
/**
* @return the skin from user-agent detect.
*/
public String getPhoneFamily() {
FacesContext fc = FacesContext.getCurrentInstance();
HttpServletRequest req = (HttpServletRequest) fc.getExternalContext()
.getRequest();
String agent = req.getHeader("User-Agent");
}
for (Iterator<String> i = skins.keySet().iterator() ; i.hasNext() ; ){
String key = i.next();
if (agent != null && agent.indexOf(key) > -1) {
phoneFamily = skins.get(key);
return phoneFamily;
}
}
phoneFamily = "minimalFamily";
return phoneFamily;
/**
* @return the skins
*/
public Map<String, String> getSkins() {
}
return skins;
/**
* @param skins the skins to set
*/
public void setSkins(Map<String, String> skins) {
this.skins = skins;
}
}
•
BundleService.java
package org.esupportail.annuaire.mobile.bundle;
import javax.faces.context.FacesContext;
import org.esupportail.annuaire.mobile.utils.BundleUtil;
/**
* @author Yves Deschamps (Université de Lille 1) - 2010
*
*/
public class BundleService {
/**
* @param key
* @return a String in current locale.
*/
public static String getString(String key) {
FacesContext context = FacesContext.getCurrentInstance();
String text = BundleUtil.getMessageResourceString(context
.getApplication().getMessageBundle(), key, null, context
.getViewRoot().getLocale());
return text;
}
}
•
BundleUtil.java
package org.esupportail.annuaire.mobile.utils;
import
import
import
import
java.text.MessageFormat;
java.util.Locale;
java.util.MissingResourceException;
java.util.ResourceBundle;
/**
* @author Yves Deschamps (Université de Lille 1) - 2010
*
*/
public class BundleUtil {
protected static ClassLoader getCurrentClassLoader(Object defaultObject) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = defaultObject.getClass().getClassLoader();
}
return loader;
}
/**
* @param bundleName
* @param key
* @param params
* @param locale
* @return a String for a key and for a locale.
*/
public static String getMessageResourceString(String bundleName,
String key, Object params[], Locale locale) {
String text = null;
ResourceBundle bundle = ResourceBundle.getBundle(bundleName, locale,
getCurrentClassLoader(params));
try {
}
text = bundle.getString(key);
} catch (MissingResourceException e) {
text = "?? key " + key + " not found ??";
}
if (params != null) {
MessageFormat mf = new MessageFormat(text, locale);
text = mf.format(params, new StringBuffer(), null).toString();
}
return text;
}
•
login.jsp
<%@ taglib uri="/WEB-INF/castools.tld" prefix="cas"%>
<cas:auth id="netid" scope="session" />
<jsp:forward page="/home.jsf" />
•
castools.tld
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>castools</shortname>
<uri>http://www.yale.edu/its/tp/castools</uri>
<info>"CAS tools" taglib</info>
<tag>
<name>auth</name>
<tagclass>org.esupportail.annuaire.mobile.authentication.CasTag</tagclass>
<teiclass>org.esupportail.annuaire.mobile.authentication.CasTEI</teiclass>
<bodycontent>empty</bodycontent>
<attribute>
<name>id</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
<tag>
<name>logout</name>
<tagclass>org.esupportail.annuaire.mobile.authentication.LogoutTag</tagclass>
<bodycontent>empty</bodycontent>
<attribute>
<name>id</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
</taglib>
•
errordisplay.jsp
<%@page pageEncoding="UTF-8"%>
<%@ page
import="java.util.List,java.io.PrintWriter,org.apache.myfaces.shared_tomahawk.util.ExceptionUtils"
isErrorPage="true" %>
<html>
<head>
<meta HTTP-EQUIV="Content-Type" CONTENT="text/html;charset=UTF-8" />
<title>Annuaire Lille 1 - Erreur</title>
</head>
<body>
<style>
body {
font-family : arial, verdana, Geneva, Arial, Helvetica, sans-serif;
font-size : 1.1em;
}
.errorHeader {
font-size: 1.6em;
background-color: #6392C6;
color: white;
font-weight: bold;
padding: 3px;
margin-bottom: 10px;
}
.errorFooter {
font-size: 0.8em;
background-color: #6392C6;
color: white;
font-style: italic;
padding: 3px;
margin-top: 5px;
}
.errorMessage {
color: red;
font-weight: bold;
}
.errorExceptions {
}
.errorExceptionStack {
margin-top: 5px;
padding: 3px;
border-style: solid;
border-width: 1px;
border-color: #9F9F9F;
background-color: #E0E0E0;
}
.errorExceptionCause {
font-size: 1.1em;
padding: 3px;
border-style: solid;
border-width: 1px;
border-color: #9F9F9F;
background-color: #E0E0E0;
}
.errorException {
font-size: 1.0em;
}
</style>
<div class="errorHeader">Annuaire Lille 1 - Une erreur est survenue</div>
<%
if (exception != null)
{
List exceptions = ExceptionUtils.getExceptions(exception);
Throwable throwable = (Throwable) exceptions.get(exceptions.size()-1);
String exceptionMessage = ExceptionUtils.getExceptionMessage(exceptions);
if (exceptionMessage.indexOf("No saved view state") > -1) {
exceptionMessage="Temps de connexion dépassé...";
}
%>Message: <span class="errorMessage"><%=exceptionMessage %></span><%
PrintWriter pw = new PrintWriter(out);
}
else
{
%>Unknown error<%
}
%>
<br/>
<a href="<%= request.getContextPath() %>">Veuillez cliquer ici...</a>
<div class="errorFooter">Annuaire Lille 1 - Rapport d'exception</div>
</body>
</html>
•
code css, notez le chemin pour accéder aux images partagées par tous les skins.
.AFRequiredIcon:alias {
content:url(/images/zoom.png);
width: 16px;
height: 16px;
}
V - Liens utiles
http://www.eclipse.org/webtools/jsf/dev_resource/JSFTutorialRC3/JSFTools_tutorial.html
http://myfaces.apache.org/trinidad/devguide/index.html
http://www.oracle.com/technology/tech/wireless/adf_mobile.html
http://www.genuitec.com/mobile/

Documents pareils