Groupe 1C (normes)

Date de création : 1 Décembre 2003
Dernière modification : 1 Décembre 2003
Diffusion : internet

Canal Web Service CAS et Session

Introduction

Ce document a pour but d'expliquer comment créer un canal UPortal (utilisant interface IChanel ) qui fait appel à un Web Service. Lequel Web Service est compatible CAS ce qui implique que le canal utilise les capacités de UPortal à être un proxy CAS. De plus, le Web Services maintient une session avec le canal afin notamment de ne pas rejouer, à chaque appel, la validation PT (proxy Ticket) CAS.

Ce document est architecturé de la façon suivante :

Remarques :

Développement d'un canal dans uPortal

Introduction

Parmi les types de canaux proposés dans uPortal, celui qui offre le plus de possibilités (interaction avec les objets internes, utilisation du moteur de rendu XML, etc.) est le type Custom. C'est aussi le type de canal le plus compliqué à réaliser.

Cette section s'attache à décrire les grands principes de création d'un tel canal. Elle est très largement inspirée de la documentation de référence uPortal [1].

Figure 8 - Exemple de canal

Où mettre les sources

Les fichiers java doivent être placés dans le répertoire source de uPortal sous la forme <nom_package>/CnomCanal.java. Le C en début du nom du canal est requis (ex : source/org/esupportail/uportal/channels/testws/CTestWS.java).

Les fichiers utilisés pour le rendu XML doivent être placés dans le répertoire webpages/stylesheets/<nom_package> de uPortal (ex : webpages/stylesheets/esupportail/uportal/channels/testws/normal.xsl).

L'utilisation de "ant deploy" permet de compiler la classe et de la positionner dans le contexte du serveur d'applications avec les fichiers de rendu XML liés.

La classe java

Il est nécessaire d'importer certaines classes uPortal :

   import org.jasig.portal.IChannel;
import org.jasig.portal.ChannelStaticData;
import org.jasig.portal.ChannelRuntimeData;
import org.jasig.portal.ChannelRuntimeProperties;
import org.jasig.portal.PortalEvent;
import org.jasig.portal.PortalException;
import org.jasig.portal.utils.XSLT;
import org.xml.sax.ContentHandler;
import java.util.Date;

La classe java doit "implémenter" IChannel.

Le constructeur de la classe est appelé une fois par session utilisateur uPortal dans la mesure où le canal est affiché au moins une fois.

Les méthodes de l'interface Ichannel

public ChannelRuntimeProperties getRuntimeProperties()

Permet au canal de signaler au portail s'il doit s'afficher ou pas en renvoyant un objet ChannelRuntimeProperties. A la création de cet objet la propriété pilotant l'affichage est vrai par défaut.

public void receiveEvent(PortalEvent ev)

Le portail informe le canal des actions de l'utilisateur en appelant cette méthode. Pour cela le portail passe un objet PortalEvent dont l'appel à la méthode getEventNumber permet au canal de savoir si l'utilisateur a cliqué sur l'un des boutons suivants : "about", "detach", "edit", "help" ou si l'utilisateur supprime le canal ou si l'utilisateur ferme sa session uPortal.

Par exemple, si l'on gère une page "about", c'est là que l'on va détecter que l'utilisateur demande son affichage. On mémorise alors l'information pour l'utiliser au moment de l'affichage par renderXML.

  if (ev.getEventNumber() == PortalEvent.ABOUT_BUTTON_EVENT) {
mode = MODE_INFO;
}

public void setStaticData(ChannelStaticData sd)

ChannelStaticData permet de retrouver des informations sur la configuration du canal ou l'utilisateur courant.

Cette méthode est appelée aussitôt après le constructeur et peut servir, comme ce dernier, à initialiser des variables.

public void setRuntimeData(ChannelRuntimeData rd)

ChannelRuntimeData permet de retrouver des informations d'exécution comme les paramètres d'un lien cliqué dans le canal.

Cette méthode est appelée juste avant l'affichage.

Dans notre exemple, on s'en sert pour rechercher la date et l'heure courante quand l'utilisateur clique sur un lien "Mettre à jour" avec un href de type "?appel=true" :

   this.runtimeData = rd;
if (runtimeData.getParameter("appel") != null) { //appel au service Web
try {
SimpleDateFormat tmp = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
retWS tmp.format(new Date());
}
catch (Exception e) {
System.out.println(e);
}
…/…

public void renderXML(ContentHandler out) throws PortalException

Cette méthode génère le XML qui sera traité par uPortal pour l'affichage.

Dans notre exemple où l'on gère deux affichages (un "normal" donnant la date et l'heure et un autre "info" quand on clique sur le bouton "about" de uPortal) :

   xml = new StringBuffer(); 
if (mode == MODE_NORMAL) {
xml.append("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
xml.append("<ret>");
xml.append(retWS);
xml.append("</ret>");
stylesheet = "normal";
}
if (mode == MODE_INFO) {
xml.append("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
xml.append("<ret>");
xml.append("Canal d'appel d'un Web Service");
xml.append("</ret>");
stylesheet = "info";
}

La méthode initialise ensuite le rendu XML avec xslt.setXSL (Cf. paragraphe suivant). La fonction xslt.setStylesheetParameter permet de passer des paramètres à la feuille de style. Un de ces paramètres très important est l'URL du canal à l'intérieur de la page uPortal que l'on obtient grâce à ChannelRuntimeData.getBaseActionURL. Dans notre exemple, nous en avons besoin pour pouvoir insérer un lien "Mettre à jour" valide :

   XSLT xslt = new XSLT(this);
xslt.setXML(xml.toString());
xslt.setXSL("CTestWS.ssl", stylesheet, runtimeData.getBrowserInfo()); xslt.setStylesheetParameter("baseActionURL", runtimeData.getBaseActionURL());
xslt.setTarget(out);
xslt.transform();

La gestion des feuilles de style XML

uPortal gère, grâce à xslt.setXSL, les feuilles de styles xslt différentes en fonction de trois paramètres principaux :

Dans notre exemple on a défini un fichier CTestWS.ssl :

   <?xml-stylesheet
href="normal.xsl"
title="normal"
type="text/xsl"
media="netscape"
default="true"?>
<?xml-stylesheet
href="normal_explorer.xsl"
title="normal"
type="text/xsl"
media="explorer"?>
<?xml-stylesheet
href="info.xsl"
title="info"
type="text/xsl"
default="true"?>

Il ne reste plus qu'à écrire chaque feuille de style.

Exemple de normal_explorer.xsl :

   <?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="no" />
<xsl:param name="baseActionURL"></xsl:param>
<xsl:template match="/">
Bonjour, Nous sommes le : <xsl:value-of select="ret" />
<p><a href="{$baseActionURL}?appel=true">Mettre à jour</a></p>
Vous utilisez Internet Explorer !!!!
</xsl:template>
</xsl:stylesheet>

Développement d'un Web Service avec AXIS

Introduction

Axis, en plus d'être une bibliothèque utilisable pour invoquer un Web Services côté client est aussi un environnement d'exécution de Web services.

Principe de développement d'un Web Service avec Axis

C'est une chose assez simple. Elle se résume généralement à l'écriture d'une simple classe java. Le plus compliqué consiste à déposer cette classe dans l'environnement d'exécution de Axis.

Attention, quand je parle de l'environnement d'exécution de Axis, il ne faut pas voir Axis comme un nouveau serveur d'applications au même titre que Tomcat par exemple. Axis s'installe sur un serveur d'applications (Tomcat par exemple) et offre ensuite un espace d'exécution, des classes qu'on lui confie, sous forme de Web Services (gestion des WSDL, SOAP, etc.).

Développement d'un Web Service simple

Le paragraphe 2 utilise un Web Service très simple dont voici le code source :

import java.util.Date;
import java.text.SimpleDateFormat;
public class WSTest {
public String hm() {
SimpleDateFormat tmp = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
return tmp.format(new Date());
}
}

Ce Web Service est déployé dans Axis simplement en :

C'est tout !

On peut ensuite invoquer le Web Service, comme mentionner au paragraphe 2, grâce à une ULR de type http://addr_srv_appli:port/axis/WSTest.jws.

L'appel de http://addr_srv_appli:port/axis/WSTest.jws dans un navigateur génère cet affichage :

Un clique sur "Click to see the WSDL" affiche le WSDL du Web Service.

Par contre, un Web Service, déployé sous la forme d'un fichier jws est limité. Dans notre cas, par exemple, il ne peut pas supporter les sessions. On est obligé d'aller plus loin.

Développement d'un Web Service complexe

En fait, ce n'est pas beaucoup plus complexe. En effet, pour gérer les sessions, il suffit de déployer la classe java manuellement dans AXIS à l'aide d'un fichier WSDD [2].

De plus, la classe java, est elle-même un peu plus complexe à écrire car elle gère l'identification CAS.

Voici le code source de la classe java modifiée

package org.esupportail;
import java.util.Date;
import java.text.SimpleDateFormat;
import edu.yale.its.tp.cas.client.ProxyTicketValidator;
public class WSTest2 {
private int numberOfCalls;
private String user = "????";
public WSTest2() {
numberOfCalls = 0;
}
public String login(String PT) {
try {
System.setProperty( "java.protocol.handler.pkgs",
"com.sun.net.ssl.internal.www.protocol");
ProxyTicketValidator pv = new ProxyTicketValidator();
pv.setCasValidateUrl("https://lscri.univ-rennes1.fr:8443/cas/proxyValidate");
pv.setService("http://localhost:8080/axis/services/WSTest2");
pv.setServiceTicket(PT);
pv.validate();
System.out.println(pv.getResponse());
System.out.println();
if (pv.isAuthenticationSuccesful()) {
user = pv.getUser();
System.out.println("user: " + pv.getUser());
System.out.println("proxies:\n " + pv.getProxyList());
} else {
System.out.println("error code: " + pv.getErrorCode());
System.out.println("error message: " + pv.getErrorMessage()); } } catch (Exception e) { System.out.println("error : " + e); } return "(user : "+user+")"; } public String hm() { numberOfCalls++; SimpleDateFormat tmp = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); String tmp2 = "; (Session : "+ numberOfCalls+"); (user : "+user+")"; return tmp.format(new Date()) + tmp2; } }

Explications :

Voici le WSDD utilisé pour déployer ce Web Service dans AXIS :

<deployment xmlns="http://xml.apache.org/axis/wsdd/"
  xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
  <service name="WSTest2" provider="java:RPC">
    <parameter name="scope" value="session"/>
    <parameter name="className" value="org.esupportail.WSTest2"/>
    <parameter name="allowedMethods" value="*"/>
  </service>
</deployment>

Explications :

Le fichier WSDD est pris en compte par AXIS grâce à l'utilisation de l'utilitaire AXIS AdminClient. Exemple d'un fichier ant appelant cet utilitaire :

<target name="maj wsdd">
  <echo message="Appel de AdminClient"/>
  <property name="wsdd" value=" "/>
  <java fork="true" dir="${basedir}" classname="org.apache.axis.client.AdminClient">
    <classpath>
      <pathelement path="${axis.lib}/axis.jar"/>
      <pathelement path="${axis.lib}/commons-discovery.jar"/>
      <pathelement path="${axis.lib}/commons-logging.jar"/>
      <pathelement path="${axis.lib}/jaxrpc.jar"/>
      <pathelement path="${axis.lib}/saaj.jar"/>
      <pathelement path="${axis.lib}/log4j-1.2.8.jar"/>
      <pathelement path="${axis.lib}/xml-apis.jar"/>
      <pathelement path="${axis.lib}/xercesImpl.jar"/>
    </classpath>
    <arg value="${wsdd}"/>
  </java>
</target>

Proxy CAS et session entre un canal et un Web Service

Introduction

Nous devons maintenant modifier le canal présenté au paragraphe 2 afin d'atteindre les objectifs suivants :

Utilisation d'un Web Service

Génération de code à partir du WSDL

La solution la plus élégante pour écrire un client de service Web avec Axis consiste à générer du code avec l'utilitaire WSDL2Java livré avec Axis.

On peut consulter le WSDL décrivant un service Web déployé dans un conteneur Axis, simplement en référençant l'URL de ce service Web et en y ajoutant "?wsdl". Typiquement dans notre cas : http://localhost:8080/axis/services/WSTest2?wsdl

Il est à noter que le développeur n'a pas à écrire le WSDL, il est automatiquement généré par le conteneur Axis.

On utilise alors tout simplement WSDL2Java en lui passant en paramètre l'url du WSDL. Cet utilitaire va générer un ensemble de classes java regroupées dans un package "localhost". Si l'on avait référencé un WSDL d'un service Web sur un site ayant une adresse de type site.domaine.com on aurait obtenu un package de nom "com.domaine.site".

WSDL2Java génère le code dans un répertoire temporaire. Il convient de déplacer ce code dans le classpath courant.

Appel du service Web

La première chose à faire consiste à importer le package généré par l'utilitaire WSDL2Java en ajoutant au début du code du canal :

   import localhost.*;

On déclare ensuite des objets de type WSTest2Service et WSTest2 :

  private WSTest2Service service;
  private WSTest2 ws;

On initialise ces deux objets dans le constructeur :

  service = new WSTest2ServiceLocator();
  ws = service.getWSTest2();

On modifie ensuite le code de la méthode setRuntimeData pour faire appel au service Web :

  public void setRuntimeData(ChannelRuntimeData rd) {
    this.runtimeData = rd;
    if (runtimeData.getParameter("appel") != null) {
      //appel au service Web
      try {
        retWS = ws.hm();
      }
      catch (Exception e) {
        System.out.println(e);
      }
    } 
  }
Comme on peut le constater l'appel au service Web est des plus simple - ws.hm(). Le code généré par WSDL2Java masque complètement la complexité liée aux services Web.

Obtention du PT

L'obtention d'un PT dans un canal est aussi une chose simple grâce à l'utilisation de la bibliothèque ESUP org.esup.utils.CAS et sa méthode getPT() (Cf. Librairie CAS Pour uPortal).

Typiquement, on ne doit chercher à obtenir le PT qu'une fois. La méthode setStaticData() de l'interface IChanel est donc une bonne candidate pour cela. C'est aussi là que l'on va faire appel à la méthode login() de notre Web service pour initialiser notre session.

Voici le code de la méthode setStaticData() :

public void setStaticData(ChannelStaticData sd) {
  this.staticData = sd;
  try {
    PT = CAS.get_pt(sd, "http://localhost:8080/axis/services/WSTest2");
    ((WSTest2ServiceLocator)service).setMaintainSession(true);
    retWS = ws.login(PT); 
  } 
  catch (Exception e) {
    e.printStackTrace();
  }
}

Maintenir la session

La encore, c'est assez simple, vous l'avez peut-être même déjà remarqué dans le code de la méthode setStaticData(). Il suffit de "dire" à l'objet service de maintenir la session avec un code du type :

((WSTest2ServiceLocator)service).setMaintainSession(true);

On doit retrouver ce code de maintien de session, en plus de la méthode setStaticData() qui fait appel à la méthode login() de notre Web Service, dans la méthode setRuntimeData() qui fait appel à la méthode hm() de notre Web Service. Ce qui donne :

public void setRuntimeData(ChannelRuntimeData rd) {
  // process the form submissions
  this.runtimeData = rd;
  if (runtimeData.getParameter("appel") != null) {
    //appel au service Web
    try {
      ((WSTest2ServiceLocator)service).setMaintainSession(true);
      retWS = ws.hm(); 
    }
    catch (Exception e) {
      System.out.println(e);
    }
  } 
}

Au final, avec tous les textes de trace ajoutés dans le canal et le Web Service voici ce que donne l'utilisation de canal :

Références et notes

[1] Owen Gunden, Writing a channel for uPortal 2.0.1, http://www.stwing.upenn.edu/~ogunden/uportal_2-0-1/channel_2-0-1.html : Juin 2002

[2] Format XML propre à AXIS pour décrire un Web Service avant de le déployer