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/