Unterstützung bei der Entwicklung

Entwicklung von AJAX-Anwendungen mit JSF und JSR 168

Erwan Paccard und Christophe Jolif

AJAX ist einer der wichtigsten Entwickler-Trends und entwickelt sich immer mehr zum Standard in der Community. Die Anwendung verbessert nicht nur das Nutzererlebnis und die Akzeptanz von Webanwendungen, sondern steigert vor allem die Produktivität der Entwickler. Doch neben den Vorteilen bringt die Implementierung von AJAX noch immer viele Herausforderungen mit sich, die bewältigt werden müssen. So mangelt es an passenden Tools für die Erstellung der Applikationen. Insbesondere bei großen AJAX-Anwendungen erschwert die asynchrone Kommunikation das Design, das Debuggen und die Wartung. JavaServer Faces (JSF) und JSR 168-Portale sind zwei Technologien, die die Entwicklung von AJAX-Applikationen unterstützen können.

Als Java-basiertes Web-Application-Framework vereinfacht JSF die UI-Entwicklung für Java EE-Applikationen. Mit JSF können Entwickler unabhängig von ihrem Erfahrungsschatz Webanwendungen einfach erstellen, indem sie wiederverwendbare UI-Komponenten kombinieren, diese mit einer Datenquelle verbinden und Client-generierte Events mit dem Steuerungssystem verlinken.

Portale liefern dem Web-Entwickler Dienste wie die Verwaltung von Benutzer-spezifischen Einstellungen, Single-Sign-On für alle Web-Anwendungen und die Aggregation von Inhalten verschiedener Anwendungen. Die Java Portlet-Spezifikation JSR 168 definiert die Verbindung zwischen Portlet Container und Portlets. Portlet-Entwickler erhalten damit ein passendes Programmiermodell.

Zwar verfügt die neueste JSF-Spezifikation 1.2 über ein abstraktes API zur Erstellung von Portlet-Systemen, um JSF in einer JSR 168-Portlet-Umgebung anwenden zu können, benötigen Entwickler jedoch eine JSF-Portlet-Bridge-Referenz-Implementierung oder eine andere Bridge passend zur JSF-Referenz-Implementierung.

Integration von JSF in JSR 168-Portale

JSF-Komponenten können in fünf Schritten in ein JSR 168-Portal integriert werden:

1. Zuerst wird die Portlet-Bridge jsf-portlet.jar in das WEB-INF/lib-Directory der JSF-Applikation kopiert.

2. Im zweiten Schritt wird ein neuer Portlet Deployment Descriptor (portlet.xml) in das WEB-INF-Directory der Anwendung eingefügt.

Der nachfolgende Code zeigt einen typischen portlet.xml-Deployment Descriptor, in dem der INIT_VIEW-Parameter ersetzt wurde, um auf die entsprechende JSP-Page zu verweisen.

JSF PortletjsfPortlet JSF Portletcom.sun.faces.portlet.FacesPortletPortlet init view pagecom.sun.faces.portlet.INIT_VIEW/index.jsptext/htmlVIEWJSF PortletjsfPortlet

3. <html>, <head>, <body> oder andere verbotene Tags müssen nach Vorgabe der JSR 168-Spezifikationen auf den JSF/JSP-Seiten des erstellten Portlets vermieden werden. Das erzeugte HTML wird später in die HTML-Seite des Portals integriert.

4. Damit jedes einzelne Portlet eine eigene, individuelle ID erhält, dürfen JSF-Tags nur innerhalb von <p:portletPage>-Tags vorkommen, die selber wiederum in <f:view>-Tags liegen. Der <p:portletPage>-Tag-Renderer sorgt dafür, dass alle Teilkomponenten in den verschiedenen Portlets der ganzen Portal-Seite eindeutige IDs bekommen.

Beispiel:

5. Abschließend wird das resultierende Portlet-WAR-File wie in der ausgewählten Portalumgebung spezifiziert eingesetzt.

  • Diese fünf Schritte stellen jedoch nur die vorgegebene Implementierung von JSF-Komponenten mit AJAX-Verhalten dar. Im Folgenden soll aufgezeigt werden, wie anspruchsvollere Anwendungen entwickelt werden können. Die folgende Erklärung basiert auf dem Auto-Suggest-Eingabefeld und zeigt, wie AJAX-Abläufe innerhalb des Portlets verfügbar werden.
  • Im Beispiel-Code hat die autosuggest-Komponente ein value-Attribut für die Ausgabe und ein suggestMethod-Attribut. Es verweist auf eine vom Server gesteuerte Bean, die alle vorgeschlagenen Ergänzungen für das übermittelte Keyword zusammenstellt.

Beispiel JSP-Page:

Beispiel Server-gesteuerte Bean:

public class MyServerBean {
  public String[] getSuggestion(String prefix) {
    // bestimmt und liefert die Antwort return new String[] {};
  }
}

p>Zusätzlich zum HTML <input>-Tag rendert die JSF-Komponente JavaScript-Code, um asynchrone Anfragen an den Server durchzuführen. Die Server-Antwort wird danach im Browser des Clients angezeigt.

AJAXPhaseListener-basierte AJAX-Erstellung

Die AJAX-Anfrage an den Server wendet einen zusätzlichen Parameter an: &autosuggest=’value’. Dieser Request wird von einer JSF 1.2 PhaseListener-Instanz bearbeitet. Diese wird im Server-JSF-Lifecycle von einem faces-config.xml erfasst. Die JSF 1.2 PhaseListener-Instanz schickt die Vorschläge als JSON- oder XML-Daten zurück. Dieselbe AJAX-Anfrage beinhaltet auch den Verweis auf die vom <foo:autoSuggest>-Tag referenzierte Methode.

Ruft ein Anwender zum Beispiel einen Vorschlag für das Keyword „start” ab, wird der folgende PhaseListener via XmlHttpRequest aufgerufen:

public class AutoSuggestRequestHandler implements PhaseListener {
  private class Class[] PARAMS = new Class[] { String.class };

  public void afterPhase(PhaseEvent event) {
    FacesContext ctx = event.getFacesContext();
    HttpServletRequest request = (HttpServletResponse)
      ctx.getExternalContext().getRequest();
    Object autosuggest = request.getParameter("autosuggest""); 
    if (autosuggest != null) {
        try {
          // get the method the user has specified for auto-suggestion
          // the reference to the method is available in the "method"
          // parameter
          MethodExpression mx =
            ctx.getApplication().getExpressionFactory().
               createMethodExpression(ctx.getELContext(),
                                      request.getParameter("method"),
                                      String.class, PARAMS);
          // get the auto suggestion result from the method 
          Object[] args = new Object[] { autosuggest };
          Object[] result = mx.invoke(ctx.getELContext(), args);
          // send the suggestions to the client using JSON utilities
          JSONUtil.write(response.getHttpResponse().getOutputStream(), 
                         result);
        } catch (Exception e) {
          // log error
        } finally {
          // cut the lifecycle to avoid rendering components
          ctx.responseComplete();
        }
    }
  }

Dieser Code konvertiert die Parameter in Servlet-spezifische Objekte. Hier im Portlet-Kontext muss stattdessen der Zugriff auf die Parameter folgendermaßen geschrieben werden:

ctx.getExternalContext().getParameterMap().get("autosuggest")

Abb. 1: AJAX-Erstellung mit AJAXPhaseListener
AJAXServlet-basierter Ansatz für die AJAX-Erstellung

Die JavaScript-Komponente des Browsers empfängt nicht nur die PhaseListener-Response als JSON-Daten, sondern den kompletten Datensatz mit allen weiteren Portlets. Dies ist deutlich schwerer zu interpretieren.

Damit diese Schwierigkeiten vermieden werden, ist das AJAX Auto-suggest Request-Management nicht in einem PhaseListener, sondern stattdessen in einem externen Servlet gehostet. So wird die Anfrage nicht von der Portlet-Bridge abgefangen und die Antworten des Servers werden wie vorgesehen als partielle JSON-Daten versendet. Hier ein Beispiel-Servlet, durch den der PhaseListener im Portalkontext ersetzt werden kann:

public class AutoSuggestServlet extends HttpServlet {
  protected void doGet(HttpServletRequest req,          
                       HttpServletResponse resp)
    throws ServletException, java.io.IOException {
    FacesContext ctx =
 ServletFacesUtil.createFacesContext(req, resp);    
    Map params = ctx.getExternalContext().getParameterMap();
    Object autosuggest = params.get("autosuggest");
    if (autosuggest != null) {
        try {                   
          MethodExpression mx =
            ctx.getApplication().getExpressionFactory().
               createMethodExpression(ctx.getELContext(),
                                      params.get("method"),
                                      String.class, PARAMS);          
          Object[] args = new Object[] { autosuggest };
          Object[] result = mx.invoke(ctx.getELContext(), args);         
          JSONUtil.write(resp.getOutputStream(), 
                         result);
        } catch (Exception e) {
          // log error
        } finally {
          // release our temporary FacesContext
          ctx.release();
        }
    }    
  }
}

Der Code ist sehr ähnlich. Doch der FacesContext wird in diesem Fall nicht vom JSF-API, sondern vom ServletFacesUtil.createFacesContext() aufgerufen:

  • Es wird nicht FacesContext.getCurrentInstance() benutzt, weil das in diesem Fall null ergeben würde. Denn wir befinden uns nicht im FacesServlet-Kontext.
  • FacesContextFactory wird nicht direkt genutzt, weil es aufgrund der Portlet-Bridge Portlet-Objekte wie PortletContext oder Portletsession und nicht Servlet-Objekte wie ServletContext oder HttpSession erwartet. Zur Erinnerung: Der PhaseListener wird mithilfe eines Servlets ersetzt. Bevor FacesContextFactory angesprochen wird, müssen Portlet-Objekte aufgerufen oder erstellt werden.

Diese auf ServletFacesUtil.createFacesContext() basierende Herangehensweise funktioniert, wenn der JSF-Komponenten-Renderer context– und session-Objekte unter Benutzung des APPLICATION_SCOPE abspeichert. Diese Objekte können anschließend vom Servlet abgerufen werden. request– und response-Objekte können unter Einbindung der korrespondierenden Servlet-Objekte gebildet werden. 

Abb. 2: AJAX-Erstellung mit AJAXServlet
Ausblick

Dieser Beitrag sollte einen Einblick in die Möglichkeiten geben, die JSR 168-Portale und JSF für die Entwicklung von AJAX-Applikationen bieten. Da die JSR 168-Spezifikationen und Portlet-Bridges jedoch nicht speziell für AJAX entwickelt wurden, gestaltet sich ihre Einbindung nicht vollkommen problemlos. Jedoch werden neue JSRs so konzipiert (Portlet 2.0 Specification, Portlet Bridge Specification for JSF), dass sie den bestehenden Anforderungen von AJAX-JSF-Komponenten in Portalen genügen können.

Die Integration von AJAX-Komponenten in Server-seitige Frameworks wie JSF wird vor allem durch die OpenAJAX Alliance vorangetrieben, die eine spezielle Server-Integration-Taskforce ins Leben gerufen hat.

Auch ILOG Visualisierungskomponenten wie solche aus ILOG JViews tragen zur weiteren Integration bei. Sie bildeten die Grundlage für die in diesem Beitrag verwendeten Beispiele.

In den vergangenen neun Jahren war Erwan Paccard in fünf führenden Softwarehäusern sowohl im Produktmanagement als auch im Marketing tätig. Mit der Übernahme von JLOOX durch ILOG im Jahr 2004 übernahm Erwan Paccard den Posten des Senior Product Managers für ILOGs Visualisierungs-Produktlinie. Er arbeitet unmittelbar mit den Sales- und Product-Development-Teams zusammen und vertritt ILOG im OpenAJAX Alliance Marketing Committee. Er hat am französischen l’Institut National de Recherche en Informatique et en Automatique (www.inria.fr) Software Engineering studiert und anschließend seinen Masterabschluss in Hightech-Marketing an der EM Lyon (www.em-lyon.com) gemacht.

Christophe Jolif ist Lead Software Architect für die Entwicklung von Web-basierten Komponenten für die ILOG JViews Visualisierungsprodukte. Christophe Jolif blickt auf mehr als zehn Jahre Erfahrung mit Java™ zurück und arbeitet bereits seit 1999 mit den verschiedensten Web-Technologien. Er vertritt ILOG in diversen W3C-Arbeitsgruppen. Zudem ist er seit kurzem Mitglied der OpenAJAX Alliance Server-Taskforce.

Geschrieben von
Erwan Paccard und Christophe Jolif
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.