Wenn die „ADF Task Flows“ in JavaServer Faces einfließen, heißen sie „Faces Flows” – Grundlagen eines neuen Features von JSF 2.2

  • Erstellt von Ed Burns
  • iJUG, Java

Die Version 2.2 von JavaServer Faces (JSF) ist mit einer Fülle von neuen Features herausgekommen. Für Entwickler sind im Standard-Framework unter den siebzig neuen Funktionen und Verbesserungen drei besonders interessant: „HTML5 Friendly Markup“, „Resource Library Contracts“, „Faces Flows“. DOAG Online hat die „Faces Flows“ herausgepickt und Ed Burns darum gebeten, die Grundlagen dieser Funktionalität zu erläutern. 

Die Version 2.2 von JavaServer Faces (JSF) ist mit einer Fülle von neuen Features herausgekommen. Für Entwickler sind im Standard-Framework unter den siebzig neuen Funktionen und Verbesserungen drei besonders interessant: „HTML5 Friendly Markup“, „Resource Library Contracts“, „Faces Flows“. DOAG Online hat die „Faces Flows“ herausgepickt und Ed Burns darum gebeten, die Grundlagen dieser Funktionalität zu erläutern. 

Die erste Frage drängt sich bereits auf: Was kann man damit machen? Nun, Flows kann man zum Beispiel nutzen, um Teilprozesse einer Unternehmensapplikation – wie Formularstrecken oder Bezahlvorgänge – umzusetzen. 

Innovation kommt woanders her: „ADF Taskflows“, „Spring Web Flow“, „Apache MyFaces CODI“ und „Faces Flow“

Wie bei jedem Standard ist JSF bestrebt, die besten Ideen aus der aktuellen Praxis der Entwicklung aufzunehmen und sie sauber in den Rest der Spezifikation einfließen zu lassen – eine Bestätigung der Devise von Ron Goldman und Richard P.Gariel: „innovation happens elsewhere“. In unserem Fall wurde die Innovation auch von anderen Projekten vorangetrieben: „ADF Taskflows“, „Spring Web Flow“ und „Apache MyFaces CODI“ sind die Inspirationsquellen für die „Faces Flows“ gewesen.

Wer sich mit diesen Technologien nicht auskennt, wird von diesem Artikel problemlos neue Erkenntnisse gewinnen können. Insbesondere ADF-Anwendern dürfte eine überraschende Ähnlichkeit zwischen der Nutzung der „Faces Flows“ und „ADF Taskflows“ auffallen.

Subroutinen und Flows

Was wäre besser als eine Analogie, um die „Faces Flows“ zu verstehen? Wenn Ihnen das Konzept der Subroutine (Unterprogramm) in der prozeduralen Programmierung ein Begriff ist, dann sind Ihnen wahrscheinlich auch folgende Fakten geläufig: 

  • Eine Subroutine hat einen eindeutig definierten Einstiegspunkt, eine Liste von Parametern und einen Rückgabewert.
  • Subroutinen nutzen das Konzept des "scope". Das heißt, dass Informationen ausschließlich während des Aufrufs der Subroutine verfügbar gemacht werden können. Im Umkehrschluss sind solche Informationen nicht außerhalb des „scope“ der Subroutine verfügbar. So verbrauchen sie keine Ressourcen, nachdem die Subroutine zurückgegeben wurde.
  • Subroutinen können andere Subroutinen abrufen, bevor sie zurückgeliefert werden. Der Aufruf von Subroutinen wird dann in einem „Call Stack“ (Aufrufstapel) gehalten: Eine neue Subroutine bewirkt ein „Push“, das ein Objekt – in dem Fall eine Subroutine – auf den Stapel legt; ein Return verursacht ein „Pop“, welches das Objekt vom Stapel entfernt.

Sie ahnen es schon: Das alles gilt auch für die „Faces Flows“. Dabei ist anzumerken, dass Flows mehrere Werte zurückgeben können. Ein Flow ist eine Sammlung von verwandten JSF-Views und -Metadaten, welche die vorhergehenden Fakten in Idiomen erfasst, die JSF-Entwicklern vertraut sind.

Wie sieht der einfachste Flow aus? – Simplest Possible Flow

Wenn es ums Deklarieren von Dingen geht, dann gilt das Motto „Konvention vor Konfiguration“. Wir wolle ja die Entwicklungsgeschwindigkeit nicht unnötig bremsen. Das Design von Flows versucht besagtem Prinzip zu folgen. Der einfachste Weg, um einen Flow zu definieren, ist das Anlegen eines Verzeichnisses in der obersten Ebene des Web-App-Roots. In das Verzeichnis muss dann eine Nulllänge-XML-Datei platziert werden. Der Name der XML-Datei muss dem Namen des Flows entsprechen und mit der exakten Zeichenkette -flow.xml enden. 

Das ist tatsächlich eine leere Flow-Definition. Sie fragen sich, wie Nicht-leere-Flow-Definitionen aussehen? Nicht so ungeduldig! Wir kommen noch darauf zu sprechen.

Facelet-Views als Teil eines Flows

Alle Facelet -Views, die in dieses Verzeichnis abgelegt werden, werden als Teil des Flows betrachtet. Dabei muss jeder Flow einen designierten "Start Node" (Startknoten) und einen oder mehrere "End Node(s)" (Endknoten) haben. Nun zurück zu unserer kleinen, leeren Flow-Definition: Die Facelet-View, die als Start Node fungiert, trägt den selben Namen wie die xml-Datei, die den Flow definiert. Der End Node wird ebenso nach dem Namen des Flows benannt – gefolgt von der Zeichenkette -return.

Flows müssen dem Navigationssystem ein Ergebnis zurückgeben, wenn sie zurückkommen. Ist die Flow-Definition leer, dann ist das Ergebnis ist "/" + flowId + "-return". Wie bereits angedeutet, nutzt Faces Flows das Prinzip „convention over configuration“, um den Anwendern einen einfachen Start mit der Funktionalität zu ermöglichen.

 

LängeDateiname
001_simplest/01_simplest-flow.xml
52601_simplest/01_simplest.xhtml
53101_simplest/a.xhtml 
514 01_simplest-return.xhtml
349index.xhtml

 Beispiel 1: So könnte das Sample unseres einfachsten Flows aussehen.

Flow betreten und verlassen

In unserem Beispiel haben wir einen Button auf index.xhtml, der die Aktion 01_simplest aufruft. So springt die Navigation zum Startknoten 01_simplest.xhtml. Befindet man sich auf 01_simplest.xhtml, so ermöglicht ein Button die Navigation zur zweiten Seite a.xhtml. In unser Beispiel haben wir einen Button auf a.xhtml, dessen Aktion 01_simplest-return ist. Man bemerke die Datei 01_simplest-return.xhtml, sozusagen die „kleine Zwillingsschwester“ von index.xhtml, die das Verlassen unseres Flows ermöglicht. Das ist die Datei, die annavigiert  wird, wenn eine beliebige Befehlkomponente auf einer beliebigen Seite innerhalb des Flows die Aktion 01_simplest-return auflistet.

Der Source-Code kann im Übrigen hier heruntergeladen werden.

 

Hinweis
Es ist auch möglicht, Flows so zu deklarieren, dass sie in eine JAR-Datei komprimiert werden können. Dieser Ansatz liefert für JSF-Anwendungen einen zusätzlichen Maß an Modularität. Eine nähere Erläuterung würde allerdings den Rahmen dieses Artikels sprengen.

Navigation von, bis und innerhalb des Flows

Um Faces Flow zu verstehen, ist es unerlässlich, mit der JSF-Navigation vertraut zu sein. Der Grund dafür ist leicht verständlich: Das Feature ist – was das Betreten, Navigieren und Verlassen des Flows anbelangt – komplett von der JSF-Navigation abhängig. 

Implizite Navigation: Wie funktioniert die JSF-Navigation?

Die Navigation in JSF kann mit dem Durchqueren eines gerichteten Graphen verglichen werden. Damit dies reibungslos funktioniert, definieren JSF-Applikationen Regeln, sogenannte „Navigation Rules“. Diese legen die erlaubten Traversierungen innerhalb des Graphen fest. Mit JSF 2.0 wurde eine implizierte Navigation eingeführt, die das Deklarieren von Navigationsregeln überflüssig macht. Die Navigieren ist innerhalb der Applikation zwischen allen Seiten möglich. Faces Flows zieht einen großen Vorteil aus dieser implizierten Navigation, denn auch da gelten die selben Regeln. Allerdings gibt es eine kleine Einschränkung:

Befindet man sich einmal in einem Flow, so ist die implizierte Navigation nur zwischen den Knoten jenes Flows möglich. Die Face-Flow-Navigation ist einfach eine Erweiterung des JSF-2.0-Navigationssystems, das um spezifische Erlaubnisse für das neue Konzept des Flows erweitert wurde. 

Innerhalb der Flows navigieren

Wie bereits beschrieben, ist ein Flow eine geschlossene Einheit, die einen einzigen Einstiegspunkt beinhaltet (Start Node). Er besteht darüber hinaus aus definierten Eingabeparametern und Rückgabewerten. Einen Flow modelliert man als einen Graphen von Knoten.

Die View-Knoten

Views sind die normalen „JSF VDL“-Ansichten, die von Anfang an in der Spezifikation beschrieben wurden. Sie geben vor, welche Page beziehungsweise welches Facelet angezeigt werden soll.

Ein weiterer Unterschied zwischen dem JSF- und dem Flow-Navigationssystem betrifft die Knoten in dem Navigationsgraphen. Vor der Einführung der Flows waren die JSF-Views – sowohl Facelet als auch JavaServer Pages (JSP) – die einzigen Knotenarten in dem Navigationsgraphen. Mit den Flows kommen nun sämliche Arten von Knotentypen zum Einsatz, die hier aufgeführt werden:

Switch-Knoten

Switch-Knoten stellen eine Liste von EL-Ausdrücken dar. Befindet man sich in dem Flow, so werden alle Ausdrücke nach und nach ausgewertet, bis einer der Ausdrücke den Wert true zurückgibt. Dann wird dieser Ausdruck genutzt, um die ID des nächsten Knotens zu definieren und eine weitere Navigation zu ermöglichen. Liefert keine der Ausdrücke den Wert true zurück, dann wird die Standardnavigation mit der spezifierten Standard-ID genutzt. 

Return-Knoten

Dieser Knotentypus wird genutzt, um den aktuellen Flow zu beenden und in den aufrufenden Flow zurückzuspringen.

Methodenaufruf (Method Call)

Dies ermöglicht den Aufruf beliebiger Applikationslogik zu jedem Zeitpunkt in der Ausführung des Flows. Auch kann der Flow über einen spezifierten Wert einen Navigationsfall auslösen, der erreicht werden soll, nachdem die Methode aufgerufen wurde. 

Flow Call

Flow Call ermöglicht es einem Flow, eine andere Flow aufzurufen. Der erste aufgerufende Flow bleibt aktiv und wird nicht verlassen, bevor die Steuerung vom zweiten aufgerufenen Flow zurückkommt. 

Eigenschaften von Knoten

Abgesehen von dem View-Knoten haben alle Arten von Knoten folgende Eigenschaft: Ihr alleiniger Zweck ist es, die Steuerung an einen anderen Knoten zu übergeben, sobald die Steuerung in ihrer Obhut liegt. Demnach pausiert die „Navigation State Machine“, wann auch immer die Steuerung einem View Node übergeben wird. In jedem Flow muss exakt ein Knoten als Start Node markiert werden. Dabei kann es sich um jeden der hier vorgestellten Typen handeln. 

Einen Flow betreten

Beim Betreten eines Flows müssen zwei Fälle berücksichtigt werden:

  • Das Betreten eines Flows von außerhalb
  • Das Betreten eines Flows von einem anderen Flow aus

Das Aufrufen eines Flows von außerhalb tritt ein, wenn der Wert einer beliebigen Navigationsregel einem Flow-Namen gleicht. Sobald wir uns innerhalb eines Flows befinden, gibt es nur zwei Wege, wieder herauszukommen. Entweder muss man den Flow verlassen oder einen anderen Flow aufrufen.

Es ist nicht erlaubt, aus einem Flow in den nächsten einfach herauszunavigieren. Man muss ausdrücklich einen „Flow Call“-Knoten durchlaufen. Dies hat auch den Vorteil, dass die Parameter eines Flows dem weiteren Flow übergeben werden können.

Einen Flow verlassen

Das Verlassen eines Flows erfolgt über einen Return-Knoten. Dadurch wird der gegenwärtige Flow vom Stapel entfernt (Pop) und die Steuerung wird mit einem spezifischen Rückgabewert dem Caller übergeben.

Einen Flow deklarieren

Es gibt zwei Standardwege, um Flows zu deklarieren. Entweder kann eine XML-Syntax genutzt werden, die von der „XML Schema Definition“ (XSD) der Datei faces-config.xml definiert wird. Sonst besteht auch die Möglichkeit, eine builder-API zu nutzen.

JSF-Implementierungen müssen diese beiden Techniken der Flow-Definition ermöglichen. Die FlowHandler-API ermöglicht einiges mehr: Ähnlich wie die ViewHandler-API, der 2005 die Tür für die Erfindung vom Facelets View Declaration Language öffnete,  ermöglicht die FlowHandler-API die Verwendung anderer deklarativen Sprachen.

Die zwei Standard-Möglichkeiten, Flows zu deklarieren, sollten in den meisten Fällen ausreichend sein.

Flow mit XML deklarieren

Erinnern Sie sich an unser Beispiel „the Simpliest Possible Flow“? Es entsprach der Konvention <flowName>-flow.xml. In unserem Beispiel war die Flow-Definition leer. Normalerweise ist es nicht der Fall.

Die vollständige Spezifikation der Flow-Definition kann in den Files  XML schema definition for faces-config.xml gefunden werden. Jedoch zeigen wir an Ort und Stelle, wie die verschiedenen Elemente zusammenspielen. Das Element <flow-definition>  muss ein Unterordner von Root <faces-config>  sein.

Wenn eine Flow-Definition in einem Ordner deklariert wird, der die <flowName>-flow.xml-Konvention nutzt, dann darf es in dieser Datei nur eine einzige Flow-Definition geben. Anders sieht es aus, wenn man stattdessen die `faces-config.xml'-Datei nutzt. In dem Fall kann es beliebige Flow-Definitionen geben. An folgendem Beispiel sehen Sie, wie eine solche Flow-Definition aussehen kann:

<faces-config version="2.2" xmlns=
"http://xmlns.jcp.org/xml/ns/javaee"   xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"   
xsi:schemaLocation="
        http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" >

<flow-definition id="flow-b">

            <flow-return id="taskFlowReturn1">
                <from-outcome>#{flow_b_Bean.returnValue}</from-outcome>
            </flow-return>

            <inbound-parameter>
                <name>param1FromFlowA</name>
                <value>#{flowScope.param1Value}</value>
            </inbound-parameter>
            <inbound-parameter>
                <name>param2FromFlowA</name>
                <value>#{flowScope.param2Value}</value>
            </inbound-parameter>

            <flow-call id="callA">
                <flow-reference>
                    <flow-document-id>unique</flow-document-id>
                    <flow-id>flow-a</flow-id>
                </flow-reference>
                <outbound-parameter>
                    <name>param1FromFlowB</name>
                    <value>param1Value</value>
                </outbound-parameter>
                <outbound-parameter>
                    <name>param2FromFlowB</name>
                    <value>param2Value</value>
                </outbound-parameter>
            </flow-call>

        </flow-definition>

</faces-config>

Beispiel 2: Flow mit XML deklarieren - so könnte eine Flow-Definition aussehen.

Flows mit Java deklarieren

Weil viele XML als ein Relikt aus alten Zeiten wahrnehmen, bietet JSF 2.2 die Möglichkeit, einen Flow mit einer Java-language-Builder-API zu deklarieren. Dieses Feature baut auf den Funktionalitäten auf, die „Contexts and Dependency Injection“ (CDI) mitliefert. Es handelt sich um eine andere JavaEE 7-Spezifikation.

Um ein Java-Objekt als Flow-Definition zu deklarieren, stellen Sie sicher, dass die Klasse Serializable implementiert ist und fügen Sie die Annotationen @Produces @FlowDefinition zu einer Methode hinzu, welche die Klasse javax.faces.flow.Flow zurückgibt. Die Methode muss sich eines FlowBuilder-Parameters bedienen, der selbst mit @FlowBuilderParameter annotiert wird. Wird eine FlowBuilder-Instanz an eine solche Methode übergeben, kann diese zum Deklarieren eines Flows manipuliert werden. Am Ende der Methode müssen Sie das Ergebnis des Aufrufs getFlow() zur FlowBuilder-Instanz zurückgeben. Im folgenden Beispiel (Beispiel 3) wird flow-a vom vorigen XML definierten Flow aufgerufen:

import java.io.Serializable;
import javax.enterprise.inject.Produces;
import javax.faces.flow.Flow;
import javax.faces.flow.builder.FlowBuilder;
import javax.faces.flow.builder.FlowDefinition;
import javax.faces.flow.builder.FlowBuilderParameter;


public class FlowA implements Serializable {

    private static final long serialVersionUID = -7623501087369765218L;

    public FlowA() {
    }

    @Produces @FlowDefinition
    public Flow buildMyFlow(@FlowBuilderParameter FlowBuilder flowBuilder) {
       String flowId = "flow-a";
       flowBuilder.id("unique", flowId);
       flowBuilder.returnNode("taskFlowReturn1").
         fromOutcome("#{flow_a_Bean.returnValue}");
       flowBuilder.inboundParameter("param1FromFlowB", "#{flowScope.param1Value}");
       flowBuilder.inboundParameter("param2FromFlowB", "#{flowScope.param2Value}");
       flowBuilder.flowCallNode("callB").flowReference("", "flow-b").
         outboundParameter("param1FromFlowA", "param1Value").
         outboundParameter("param2FromFlowA", "param2Value");

       return flowBuilder.getFlow();
    }
}

 Beispiel 3: Flows mit Java deklarieren - flow-a wird vom vorigen XML definierten Flow aufgerufen.

Natürlich ist dieser Artikel nur eine kurze Einführung in die Faces Flows, die in JSF 2.2 eingeführt worden sind. Zu den weiteren Neuerungen in JSF 2.2 gehören HTML5 Friendly Markup und Resource Library Contracts. Weitere Informationen zu JSF 2.2 finden Sie in der Java EE 7 Tutorial. Die vollständige Liste der technischen Daten finden Sie hier.