Das Modul UX-Bridge kommt dem Trend von dynamischen Websites nach.
Immer dort, wo eine Vorgenerierung von Inhalten nicht möglich ist, muss dynamisch auf die CMS-Inhalte zugegriffen werden.
Dynamisch bedeutet in diesem Fall, dass sich der Inhalt für jeden Website-User und zu jedem Zeitpunkt ändern kann.
Die UX-Bridge bietet eine Infrastruktur für die Anforderung einer dynamischen Content-Delivery-Plattform.
Somit ergänzt das Modul den hybriden Architekturansatz um eine Standardkomponente für dynamische Content-Auslieferung.
Weitere Informationen finden Sie im UX-Bridge Whitepaper.
Diese Dokumentation soll die Entwicklung mit der UX-Bridge-Infrastruktur unterstützen und einen Einblick in die Konzeption geben.
Es beschreibt im zweiten Kapitel allgemein die Schritte, die beim Einsatz der UX-Bridge zu berücksichtigen sind. Das Kapitel deckt dabei die Themen Installation, Überlegungen zum Datenaustauschformat, Einrichtung in FirstSpirit, Routing sowie Adapter- und Webapplikationseinbindung ab.
Anhand von zwei Tutorials wird die Verwendung der UX-Bridge erläutert. Hier wird an konkreten Beispielen die Implementierung in FirstSpirit, die Erstellung eines Adapters sowie der Webanwendung Schritt für Schritt exemplarisch durchgeführt.
Bestehen in einem Projekt Anforderungen, die mit Hilfe der UX-Bridge umgesetzt werden sollen, so stellen sich vor der Implementierung einige Fragen, die im Rahmen der Konzeption bedacht und ggf. beantwortet werden sollten.
Bei der Konzeption können zwei Fälle unterschieden werden:
Wird ein FirstSpirit-Projekt vollständig neu entwickelt, so liegt der Fokus der Entwicklung auf der optimalen Umsetzung der fachlichen Anforderungen.
Gleichzeitig soll das Konzept hinreichend flexibel sein, um auch zukünftige Anforderungen leicht umsetzen und integrieren zu können.
Soll die UX-Bridge andererseits in ein bereits bestehendes Projekt integriert werden, so stellt sich primär die Frage, wie sie am besten in die bestehenden Konzepte und Mechanismen eingebunden werden kann.
Im Folgenden werden einige Punkte betrachtet, die in vielen Projekten relevant sein werden.
In vielen Projekten erfolgt die Aktualisierung der Webseite durch zyklische Generierungs- und Deploymentaufträge. Diese Aufträge führen einen Komplett- bzw. einen Teilabgleich durch. Unter Umständen können diese Aufträge auch manuell ausgeführt werden. Soll in diesem Szenario die UX-Bridge verwendet werden, so reicht es oft aus, die bestehenden Aufträge um die UX-Bridge-spezifischen Aufgaben zu erweitern. Details dazu finden Sie im Kapitel Auftrag anlegen und konfigurieren.
Innerhalb der Generierung wird für jede Seitenreferenz, die innerhalb der UX-Bridge-Generierungsaufgabe erzeugt wird, eine Nachricht an den UX-Bus gesendet.
Innerhalb des Projekts sollte also darauf geachtet werden, dass innerhalb dieser Aufgabe nur die notwendigen Seitenreferenzen erzeugt werden.
Sollen in einem Projekt z.B. nur die News (gepflegt über eine Datenquelle) über die UX-Bridge ausgeliefert werden, so sollten in der UX-Bridge-Generierungsaufgabe auch nur die notwendigen Contentprojektionsseiten generiert werden.
Um diese Einschränkung vorzunehmen, kann zum Beispiel eine Teilgenerierung verwendet werden.
Alternativ dazu kann auch eine Vollgenerierung genutzt werden.
Allerdings sollte dann uxbSkipMessage
eingesetzt werden, um die Generierung von Seitenreferenzen im UX-Bridge Ausgabekanal abzubrechen, die keine Nachrichten erzeugen sollen (siehe
→ → → → → →
).
In anderen Projekten wird ein Großteil der Änderungen über Arbeitsabläufe freigegeben, die anschließend eine Generierung und ein Deployment der beteiligten Objekte vornehmen. Hierbei werden zumeist die geänderten Objekte per Skript ermittelt, die dann als Startknoten einer Teilgenerierung definiert werden. Das Löschen von Objekten erfolgt durch den Einsatz von Löscharbeitsabläufen. Die UX-Bridge lässt sich in solche Szenarien ebenfalls problemlos integrieren (siehe Kapitel Workflow-Kopplung).
Spielt die Zeit, in der Objekte auf der Webseite verfügbar sein müssen, eine große Rolle, so ist die Verwendung des arbeitsablauforientierten Ansatzes oft die bessere Wahl.
Je weniger Objekte im Zuge einer Generierung erzeugt werden müssen, desto schneller sind die Objekte auf der Webseite verfügbar.
Mit FirstSpirit 5 steht mit der DeltaGenerierung (siehe →
bzw. →
) ein vereinfachter Mechanismus für solche Szenarien zur Verfügung.
Unter FirstSpirit 4 kann dies bereits durch die Nutzung der Revisions-API erreicht werden.
Insgesamt gliedert sich die UX-Bridge somit in das bestehende oder in ein neu aufzusetzendes Generierungs- und Deployment-Konzept ein und bringt dafür keine eigenständige (getrennte) Lösung mit.
Soll die UX-Bridge in ein bestehendes FirstSpirit-Projekt integriert werden, so ist das Datenmodell oft bereits vorgegeben. Bei stark strukturierten Inhalten durch das Datenbankschema, bei schwach strukturierten Inhalten durch Formulare der Seiten- und Absatzvorlagen. Hier sollte geprüft werden, ob die zu erstellende Webapplikation, die später auf die Daten der UX-Bridge zugreifen soll, mit diesem Datenmodell arbeiten kann oder ob ein anderes Datenmodell sinnvoller ist. Dies könnte der Fall sein, wenn die Webapplikation ein deutlich einfacheres bzw. deutlich komplexeres Datenmodell benötigt.
Im letzteren Fall sind die FirstSpirit-Daten also nur ein Teil des Datenmodells der Webapplikation. Im erstgenannten Fall ist für viele Webapplikationen auch eine Teilmenge des in FirstSpirit hinterlegten Datenmodells ausreichend. Zusätzlich ist zu überlegen, ob aus Performancegründen das Datenmodell der Webapplikation denormalisiert wird.
Wurde die Entscheidung getroffen, abweichende Datenmodelle zu verwenden, so ist die Frage zu klären, in welchem Schritt die Transformation von einem Datenmodell in das andere Datenmodell vorgenommen werden soll. Die Antwort ist projektspezifisch, so dass hier nur die möglichen Varianten beschrieben werden.
Eine Bewertung muss im Projektkontext erfolgen:
Folgende Überlegungen können bei der Bewertung helfen:
Wie im vorherigen Kapitel erwähnt, wird für jede Seitenreferenz innerhalb der Generierung eine Nachricht erzeugt und an den UX-Bus gesendet. Ein Teil des UX-Busses ist eine Routing-Komponente, die die Nachricht entgegen nimmt und an einen anderen sogenannten Endpunkt weiterleitet bzw. routet. Details zur Standardkonfiguration finden Sie im Kapitel Routing.
Diese Konfiguration kann für die projektspezifischen Anforderungen angepasst werden.
Hierbei steht mit der Apache Camel Spring XML
-Syntax eine einfache Domain-Specific Language
(DSL) zur Verfügung, mit der alle gängigen Enterprise Integration Patterns (siehe http://camel.apache.org/enterprise-integration-patterns
) umgesetzt werden können.
Mit diesem mächtigen Integrationsframework kann der UX-Bus als Informationsdrehscheibe für den Webauftritt und alle beteiligten Systeme verwendet werden.
Hier einige Beispiele, die durch ein Routing auf dem UX-Bus umgesetzt werden können:
In der Standard-Konfiguration der UX-Bridge ist bereits ein Routing konfiguriert, welches für Standardszenarien ausreicht. Nur für komplexere Szenarien (siehe Kapitel Datenmodell und Adapter) sind projektspezifische Anpassungen notwendig.
In diesem Kapitel werden die Schritte beschrieben, die notwendig sind, um die UX-Bridge in einem Projekt einzusetzen. Der Quick Walkthrough ist für den erfahrenen FirstSpirit-Vorlagenentwickler und Webapplikationsentwickler gedacht. Eine genauere Schritt-für-Schritt-Anleitung bieten die Tutorials.
Ausgangspunkt für die Entwicklung im UX-Bridge-Kontext ist die Installation der Komponenten.
Dazu sollte zunächst das mitgelieferte Modul in den Server-Einstellungen
des FirstSpirit-Servers installiert werden.
Dadurch wird der UX-Bridge-Service gestartet und die UX-Bridge-Komponenten sind in den Projekten des FirstSpirit-Servers verfügbar.
Neben dem FirstSpirit-Modul ist auch die Installation des UX-Busses notwendig. Zur lokalen Entwicklung ist die Installation im Standalone-Betrieb empfohlen.
Weitere Informationen erhalten Sie in der UX-Bridge Installationsanleitung.
Die Architektur der UX-Bridge erlaubt es, die Entwicklung von Lösungen auf Basis der UX-Bridge auf zwei Rollen zu verteilen:
Werden die Rollen durch unterschiedliche Personen wahrgenommen, so sollte während der Konzeption ein gemeinsames Datenaustauschformat definiert werden. Hiermit ist das Datenformat gemeint, was durch die Vorlagen erzeugt und über den UX-Bus als Nachricht an den Adapter verschickt wird.
Das Datenaustauschformat bildet die Schnittstelle zwischen den Komponenten und damit auch zwischen den beiden Rollen. Aus Sicht des Vorlagenentwicklers handelt es sich hierbei um das Endprodukt seiner Arbeit. Für den (Web-)Applikationsentwickler stellt es den Input für den Adapter dar. Dabei wird seitens der UX-Bridge nur ein äußerer Container vorgegeben. Der Rest kann frei definiert werden (siehe auch Kapitel Ausgabekanal anlegen und befüllen).
Ein geschickt gewähltes Datenaustauschformat kann den Implementierungsaufwand deutlich reduzieren.
Folgende Fragestellungen können bei der Wahl helfen:
Um die UX-Bridge zu nutzen, sollte zunächst unter →
ein neuer Vorlagensatz (UXB) angelegt werden.
Als Präsentationskanal sollte XML
als Konvertierungsregel Unicode to XML
und als Zieldateierweiterung xml
konfiguriert werden.
Die Konvertierungsregel Unicode to XML dient dazu, Sonder- und Steuerzeichen in korrespondierende XML-Entitäten umzuwandeln, die sonst in XML als Teil der Markupsprache interpretiert werden bzw. ungültige Zeichen darstellen würden.
Um Nachrichten an den UX-Bus zu schicken, sind in der entsprechenden Vorlage, die die Nachrichten erzeugen soll, die Felder, die im Datenaustauschformat definiert wurden, in Form von XML auszugeben.
Vorgegeben ist lediglich folgende Struktur:
<uxb_entity uuid = String destinations = String language = String command = String objectType = String> <uxb_content> […] definiertes Datenaustauschformat […] </uxb_content> </uxb_entity>
Property | Beschreibung | Beispiel | Pflichtfeld |
Uuid | Eindeutiger Bezeichner des Objektes z.B. fs_id | 1234 | Ja |
destinations | Ziel(e) der Nachricht | postgres, mongodb | Ja |
language | Sprache der Nachricht | DE | Nein |
command | Auszuführendes Kommando vom Adapter (z.B. anlegen/löschen) | Add | Nein |
objectType | Objektyp der vom Adapter ausgewertet wird (z.B. News, Products) | News | Nein |
Die Attribute language
, command
und objectType
sind optional, haben sich aber bei den von e-Spirit implementierten Adaptern als hilfreich erwiesen.
Um die Daten von FirstSpirit in Nachrichten umzuwandeln, die vom UX-Bus weiterverarbeitet werden können, muss zwingend ein Auftrag angelegt oder ein bestehender Auftrag so erweitert werden, dass er XML generiert und dieses an den UXB-Service weiterreicht, der daraus eine Nachricht erzeugt und diese an den UX-Bus versendet. Der Auftrag kann später über einen Workflow gestartet werden. In diesem Kapitel sollen nun zwei Aufträge erklärt werden, die wahrscheinlich in jedem Projekt, welches die UX-Bridge nutzt, benötigt werden.
Teilgenerierung
Um neue Inhalte schnell auf der Webseite zu publizieren, bietet es sich an, einen Auftrag anzulegen bzw. so zu erweitern, dass er die folgenden beschriebenen Schritte durchführt und hierbei nur die neuen Inhalte generiert und veröffentlicht.
Ein typischer Generierungsauftrag, der die UX-Bridge nutzt, gliedert sich in mehrere Aktionen, wie nachfolgend beschrieben:
Zunächst sollten in einer Generierungsaktion die komplett statischen Seiten, die zur Anzeige auf der Webseite notwendig sind (z.B. News-Übersichtsseiten), generiert werden. Diese Generierungsaktion findet sich auch in den bisher üblichen Deployment-Aufträgen und muss dann nicht angepasst werden.
Im nächsten Schritt sollten dann die im vorherigen Schritt generierten Inhalte wie gewohnt auf den Webserver übertragen werden (im Beispiel über rsync). Auch in diesem Schritt sind in der Regel keine Anpassungen notwendig.
Um die UX-Bridge einzusetzen, ist danach eine neue, weitere Aktion hinzuzufügen, die mittels Skriptaufruf die Generierung für die UX-Bridge aktiviert:
UX-Bridge Generate.
#! executable-class com.espirit.moddev.uxbridge.inline.UxbInlineUtil
In der darauf folgenden Generierungsaktion sollten dann genau die Seite generiert werden (z.B. News-Detailseite), die das XML erzeugt, das an den UXB-Service weitergereicht werden soll. Wichtig zu beachten ist, dass in den erweiterten Eigenschaften auch der UXB-Vorlagensatz aktiviert wurde.
Die Delta-Generierung in FirstSpirit 5 passt die Generierungsaktion automatisch so an, dass nicht mehr alle Seiten, sondern nur noch die gewünschte Seite generiert wird. Dazu kann z.B. in einem Workflowskript, das die Generierung anstößt, die zu generierende Seite an den Auftrag übergeben werden.
Die letzte Aktion UX-Bridge Statistics Report
ist optional und ermöglicht es, die Durchlaufzeiten für die Nachrichten im UX-Bus bis zur Veröffentlichung auf der Webseite zu messen.
INFO 22.08.2012 09:59:54.631 (de.espirit.firstspirit.server.scheduler.ScheduleManagerImpl): starting task 'UX-Bridge Statistics Report' - schedule entry 'UX-Bridge-Test (News)' (id=5142) INFO 22.08.2012 10:00:04.645 (com.espirit.moddev.uxbridge.service.UxbServiceImpl): Time for #uxb/pressreleasesdetails/UXB/EN/256 (postgres): 242ms INFO 22.08.2012 10:00:04.645 (com.espirit.moddev.uxbridge.service.UxbServiceImpl): Time for #uxb/pressreleasesdetails/UXB/DE/256 (postgres): 224ms INFO 22.08.2012 10:00:04.645 (com.espirit.moddev.uxbridge.service.UxbServiceImpl): 2/2 deployed successfully (overall: 233ms, postgres: 233ms). INFO 22.08.2012 10:00:04.646 (de.espirit.firstspirit.server.scheduler.ScheduleManagerImpl): finished task 'UX-Bridge Statistics Report' - schedule entry 'UX-Bridge-Test (News)' (id=5142)
Dazu ist folgender Skriptaufruf notwendig:
UX-Bridge Statistics Report.
#! executable-class com.espirit.moddev.uxbridge.inline.UxbResultHandler
Der Service wartet maximal den in der Servicekonfiguration der UX-Bridge definierten Zeitraum, bis die Antworten der Adapter ausgewertet werden. Kommt in diesem Zeitfenster keine Antwort, so gilt die Nachricht als fehlerhaft zugestellt. Da die Antwortzeiten je nach Nachricht und System variieren können, ist dieser Wert konfigurierbar.
Alternativ zu diesem maximalen Zeitraum für den gesamten Auftrag, kann ein Heartbeat konfiguriert werden.
Hierfür wird ein maximaler Zeitraum konfiguriert, der zwischen zwei Heartbeat-Nachrichten vergehen darf.
Es ist zu beachten, dass alle verwendeten Adapter einen Heartbeat senden müssen.
Empfängt der ResultHandler zu lange keinen Heartbeat, wird der Auftrag als fehlerhaft gewertet und eine Timeout-Nachricht angezeigt.
Das Versenden von Heartbeat-Nachrichten muss auf Adapterseite implementiert werden.
Eine Beispielimplementierung befindet unter https://github.com/e-Spirit/uxbridge-samples/blob/master/newsWidget/adapter/hibernate/src/main/java/com/espirit/moddev/examples/uxbridge/newswidget/jpa/ArticleHandler.java
Zu beachten ist, dass jede Generierungsaktion, die zwischen UX-Bridge Activate Generation
und UX-Bridge Statistics Report
aufgerufen wird, eine UX-Bridge-Generierung durchführt.
Soll im selben Auftrag eine weitere Generierung ins Dateisystem erfolgen, ohne dass zuvor das Skript UX-Bridge Statistics Report
aufgerufen wurde, so muss vor dieser Generierung ein Skript zur Deaktivierung der UX-Bridge-Generierung eingefügt werden:
UX-Bridge Deactivate Generation.
#! executable-class com.espirit.moddev.uxbridge.inline.UxbDeactivate
Komplettabgleich
Es ist sinnvoll einen weiteren Auftrag anzulegen, der einen Komplettabgleich durchführt, um den Datenbestand im Content-Repository auf dem aktuellsten Stand zu halten. Hierfür müssen die in FirstSpirit gelöschten Daten ebenfalls auch im externen Repository gelöscht werden.
Es empfiehlt sich folgende Vorgehensweise:
lastmodified
gespeichert wird.
cleanup
-Methode mit einem Timestamp als Parameter aufruft, der die neueste Projektrevision des Auftrags beschreibt.
Die cleanup
-Methode löscht dann alle Daten, die eine ältere Zeit, als die übergebene, im Feld lastmodified
gespeichert haben.
Das sind die Daten, die im FirstSpirit-Projekt bereits gelöscht wurden und somit im Schritt 2 keinen neuen Timestamp in lastmodified
gespeichert haben.
cleanup-Skript.
import com.espirit.moddev.uxbridge.api.v1.service.UxbService; uxbService = context.getConnection().getService(UxbService.class); uxbService.removeUxbEntriesByTime(context.getStartTime().getTime(), "news", "postgres,mongodb");
Historische Daten
Um historische Daten mittels der UX-Bridge zu generieren, muss eine neue Skript-Aktion vor der Aktion UX-Bridge - Activate Generation
hinzugefügt werden z.B.
Skript.
import java.util.Date; Date d = new Date(114, 5, 24); context.setStartTime(d);
Das gesetzte Datum wird dann in der folgenden Aktion (UX-Bridge - Activate Generation
) berücksichtigt.
Auftrag Start / Ende im Adapter erkennen
Um im Adapter auf den Start bzw. das Ende eines Auftrags unter Umständen gezielt reagieren zu können, kann die UX-Bridge zu Beginn und am Ende der Generierung jeweils automatisch eine separate Nachricht versenden (siehe auch UX-Bridge Installationsanleitung).
Die Startnachricht besitzt dabei folgendes Format:
<uxb_entity projectName="PROJEKT_NAME" status="start" schedulerId="AUFTRAGS_ID" createTime="ZEITPUNKT_DER_NACHRICHT" projectId="PROJEKT_ID" startTime="STARTZEITPUNKT_DES_AUFTRAGS" />
Bei einer Vollgenerierung wird noch das Attribut command=“startMaintenanceMode“
hinzugefügt.
Die Endnachricht besitzt folgendes Format:
<uxb_entity projectName="PROJEKT_NAME" status="end" schedulerId="AUFTRAGS_ID" createTime="ZEITPUNKT_DER_NACHRICHT" projectId="PROJEKT_ID" startTime="STARTZEITPUNKT_DES_AUFTRAGS" />
Bei einer Vollgenerierung wird noch das Attribut command=“stopMaintenanceMode“
hinzugefügt.
Sollte bei einer Vollgenerierung die maximale Anzahl an erlaubten Fehlern bzw. Timeouts im Adapter überschritten werden, hat das Attribut command
den Wert keepMaintenanceModeUp
.
Root-Attribute erweitern
Die Wurzelknoten der über die UX-Bridge verschickten Nachrichten besitzen standardmäßig bereits einige Attribute, wie beispielsweise den Projektnamen oder die ID des Auftrags.
Diese lassen sich durch beliebige, weitere Attribute erweitern.
Zur Angabe dieser Root-Attribute muss dem Generierungsauftrag als erste Aktion das Skript Configure UXB Message Header
hinzugefügt werden, welches den folgenden Inhalt besitzt:
Skript - Configure UXB Message Header.
import java.util.HashMap; attributeMap = new HashMap(); attributeMap.put("customAttribute1", "customAttributeValue1"); attributeMap.put("customAttribute2", "customAttributeValue2"); context.setProperty("uxbMessageRootAttributes", attributeMap);
Das Skript erzeugt eine HashMap, in der die gewünschten Attribute in Form von Key/Value-Paaren hinzugefügt werden können. Die HashMap wird anschließend an den Kontext des Auftrags übergeben, damit der UXBService während der Erzeugung der Nachrichten auf sie zugreifen kann.
Sollen bei der Nachrichtengenerierung Seiten übersprungen werden, so ist dies durch das setzen der Seitenvariable uxbSkipMessage
möglich.
uxbSkipMessage.
$CMS_SET(#global.pageContext["uxbSkipMessage"], true)$
Die Benutzung von stopGenerate
(siehe
→ → → → → →
)
wird nicht unterstützt und führt in diesem Fall zu ungültigem XML und dadurch zu Fehlermeldungen im Log.
Die UX-Bridge kann zu Beginn der Generierung eine Nachricht mit erweiterten Projektinformationen verschicken (siehe UX-Bridge Installationsanleitung). Zu diesen Informationen zählen zurzeit die definierten Projektsprachen und Auflösungen.
Beispiel.
<uxb_entity projectName="UXB" objectType="projectInfo" schedulerId="92460" createTime="1371037891875" projectId="88785" startTime="1375346932923"> <project key="UXB"> <id>88785</id> <name>UXB</name> <languages> <language key="DE"> <name>Deutsch</name> <abbreviation>DE</abbreviation> <isMasterLanguage>true</isMasterLanguage> </language> </languages> <resolutions> <resolution key="ORIGINAL"> <name>Originalauflösung</name> <uid>ORIGINAL</uid> <height>0</height> <width>0</width> <isOriginal>true</isOriginal> </resolution> </resolutions> </project> </uxb_entity>
Die Publikation von Inhalten über die UX-Bridge kann sowohl direkt über Skripte und Aufträge als auch indirekt über Workflows gestartet werden.
Release Workflow
Um Inhalte zu publizieren, muss ein bestehender Workflow lediglich um ein Workflow-Skript erweitert werden, das einen Auftrag startet, der neben Generierung und Deployment auch die XML-Nachrichten erzeugt und an den UXB-Service weiterreicht (siehe Kapitel Teilgenerierung).
Delete Workflow
Um Inhalte zu löschen, muss ein bestehender Löschworkflow um ein Workflow-Skript erweitert werden, das eine XML-Nachricht erzeugt, die an den UXB-Service weitergereicht wird.
Der Aufruf des UXB-Service im Skript lautet wie folgt, wobei msg
(String) der XML-Nachricht entspricht:
Aufruf des UXB-Service.
UxbService uxbService = context.getConnection().getService(UxbService.class); uxbService.removeUxbEntry(msg);
Die XML-Nachricht folgt dabei folgendem Muster:
<uxb_entity uuid=STRING language=STRING destinations=STRING objectType=STRING command=STRING />
Property | Beschreibung | Beispiel | Pflichtfeld |
Uuid | Eindeutiger Bezeichner der Objektes (z.B. fs_id) | 12345 | Ja |
Destinations | Ziel(e) der Nachricht (kommasepariert) | postgres | Ja |
Command | Auszuführendes Kommando vom Adapter | delete | Ja |
Language | Sprache der Nachricht | DE | Nein |
objectType | Objekttyp, der vom Adapter ausgewertet wird | news | Nein |
Adapter dienen dazu, die Daten aus den JMS-Nachrichten auszulesen und in die ausgewählten Repositories zu schreiben. Im Tutorial werden zwei Adapter beispielhaft als Webanwendungen realisiert, aber auch andere Implementierungen (z.B. standalone Java) sind möglich.
FirstSpirit erwartet eine Antwort in Form eines XML-Dokuments vom Adapter, nachdem eine Nachricht in ein Repository geschrieben wurde. Die Antwort wird sowohl bei einer erfolgreichen als auch bei einer fehlhaften Verarbeitung erwartet. Das Antwort-XML-Dokument ist wie folgt aufgebaut:
<uxb_entity command=STRING createTime= STRING destinations=STRING finishTime=STRING language=STRING path=STRING schedulerId=STRING startTime=STRING status=STRING uuid=STRING ><uxb_error>STRING </uxb_error></uxb_entity>
Property | Beschreibung | Beispiel | Pflichtfeld |
destinations | Das Ziel-Repository, in das das Objekt geschrieben wurde bzw. geschrieben werden sollte. | postgres | Ja |
startTime | Timestamp des Starts der Aktion (Wird von FirstSpirit bei der Aktion ins XML-Dokument eingehangen.) | 1314567899516 | Ja |
finishTime | Timestamp der Fertigstellung des Kommandos | 1314567899516 | Ja |
path | FirstSpirit interner Pfad (Wird von FirstSpirit bei der Aktion ins XML-Dokument eingehangen.) | the/Path/to/ | Ja |
status | Status der Aktion. | OK | Ja |
uuid | eindeutiger Bezeichner der Objektes z.B. fs_id | 123456 | Ja |
schedulerId | eindeutige ID des Auftrages (Wird von FirstSpirit bei der Aktion ins XML-Dokument eingehangen.) | 123456 | Ja |
command | ausgeführtes Kommando vom Adapter | delete | Nein |
language | Sprache der Nachricht | DE | Nein |
createTime | Timestamp der Erstellung der Aktion (Wird von FirstSpirit bei der Aktion ins XML-Dokument eingehangen.) | 1314567899516 | Nein |
uxb_error | das Containerelement für die im Fehlerfall vorhandene Fehlermeldung | com.mongodb. MongoException | Nein |
Durch die offene Architektur der UX-Bridge und den Umstand, dass die Art und Anzahl der Repositories nicht vorgegeben werden, sind die Technologie und das Framework, um die WebApplikation zu entwickeln, frei wählbar. Es bietet sich an, die Auswahl der Technologie und des Frameworks sowohl an den Anwendungsfall als auch an das vorhandene KnowHow bzw. die Unternehmensstandards anzupassen.
Die UX-Bridge nutzt Apache Camel
zum Routen von Nachrichten.
Als Transport- und Nachrichtenprotokoll kommt der Java Message Service
(JMS) zum Einsatz.
Die beiden beteiligten Komponenten (FirstSpirit-Server und Adapter) fungieren dabei jeweils sowohl in der Rolle eines Producers, der Nachrichten erzeugt und in einem Endpunkt zur Verfügung stellt, als auch in der Rolle eines Consumers, der Nachrichten von einem Endpunkt abholt und weiterverarbeitet.
Der UX-Bus übernimmt in diesem Szenario lediglich das Routing der Nachrichten zwischen den beteiligten Endpunkten.
Die Konfiguration des UXB-Service ist über →
erreichbar.
Aus dem ausgeklappten Modulbaum muss der UXB-Service
selektiert werden und über den Button öffnet sich die Konfiguration des Services (Spring DSL), der die Endpunkte und eine Route enthält.
Die Konfiguration muss in der Regel nicht angepasst werden, es sei denn die Namen der im UX-Bus konfigurierten Endpunkte werden geändert.
Dann müssen diese auch in der Konfiguration in FirstSpirit angepasst werden.
Die Adapter-Statistics-Response-Route
mit dem UxbServiceStatisticsResponseHandler
-Bean sind zwingend zu benutzen, wenn das UX-Bridge-eigene Monitoring benutzt werden soll (siehe UX-Bridge Installationsanleitung).
Innerhalb der Spring DSL
ist ein Camel Context
beschrieben, der die Routen und Endpunkte enthält:
Camel-Context.
<camelContext xmlns="http://camel.apache.org/schema/spring" id="camelContext" trace="false"> <package>com.espirit.moddev.uxbridge.service</package> <template id="producerTemplate"/> <endpoint id="FS-OUT" uri="activemq:topic:FS_OUT"></endpoint> <onException> <exception>java.lang.Exception</exception> <handled> <constant>true</constant> </handled> <process ref="uxbExceptionProcessor"/> </onException> <route id="Adapter-Statistics-Response-Route"> <from uri="jms:topic:FS_IN"/> <convertBodyTo type="com.espirit.moddev.uxbridge.api.v1.service.UXBEntity"/> <bean ref="UxbServiceStatisticsResponseHandler" method="print"/> </route> </camelContext> <bean id="UxbServiceStatisticsResponseHandler" class="com.espirit.moddev.uxbridge.service.UxbServiceStatisticsResponseHandler"> <constructor-arg ref="camelContext"/> </bean> <bean id="uxbExceptionProcessor" class="com.espirit.moddev.uxbridge.inline.UxbExceptionProcessor"/>
Im Beispiel gibt es einen Endpunkt mit der Id FS_OUT
, der als Endpunkt für Nachrichten dient, die vom Service geschickt werden.
Daneben ist die Adapter-Statistics-Response-Route
definiert, die vom Endpunkt jms:topic:FS_IN
Nachrichten konsumiert.
Die Nachrichten werden vom UXB-Service wieder in ein Objekt (UXBEntity) konvertiert und es wird danach der UxbServiceStatisticsResponseHandler
auf den Objekten angewandt, so dass diese z.B. zu Timingzwecken wieder ausgewertet werden können.
Die Spring DSL
im UX-Bus enthält einen Camel Context
mit vier Endpunkten, die zwei Routen bilden.
Die erste Route geht vom Endpunkt von FirstSpirit zum Endpunkt des Adapters und die zweite vom Endpunkt des Adapters in entgegengesetzter Richtung zum Endpunkt des FirstSpirit-Service.
Camel-Context.
<camelContext trace="false" xmlns="http://camel.apache.org/schema/spring"> <route id="uxbridge-router"> <from uri="activemq:topic:FS_OUT"/> <filter> <xpath>//uxb_entity[contains(@objecttype, 'products')]</xpath> <to uri="activemq:topic:VirtualTopic.BUS_OUT_mongo"/> </filter> <filter> <xpath>//uxb_entity[contains(@objecttype, 'news')]</xpath> <to uri="activemq:topic:VirtualTopic.BUS_OUT_postgres"/> </filter> </route> <route id="uxbridge-router-response"> <from uri="activemq:topic:BUS_IN"/> <to uri="activemq:topic:FS_IN"/> </route> </camelContext>
In der Standardkonfiguration kommen virtuelle Endpunkte zum Einsatz (siehe http://activemq.apache.org/virtual-destinations.html
).
Der Vorteil von virtuellen Endpunkten ist, dass für weitere Adapter keine Modifikation am Routing vorgenommen werden muss.
Die virtuellen Endpunkte folgen dem Namensschema VirtualTopic.%Ziel-Endpunkt%
.
Durch die Virtualisierung können Nachrichten nicht wie bei einer Queue nur von einem Adapter gelesen werden, sondern alle entsprechenden Adapter erhalten die Nachricht.
Die erste Route schickt Nachrichten vom Endpunkt activemq:topic:FS_OUT
in Richtung der Adapter zu dessen Endpunkt activemq:topic:VirtualTopic.BUS_OUT
.
Über XPath wird hier beispielhaft noch eine Unterscheidung für mehrere Adapter vorgenommen.
@objecttype
bezieht sich hier auf den Header der JMS-Nachricht (siehe Kapitel Ausgabekanal anlegen und befüllen).
Nachrichten, die von den Adaptern aus an den Endpunkt activemq:topic:BUS_IN
in Richtung FirstSpirit-Service geschickt werden, werden an den Endpunkt activemq:topic:FS_IN
weitergeleitet.
In dieser Konfiguration sind in der Regel nur Anpassung in der Route zum Adapter vorzunehmen, wenn sich der Name des Endpunkts ändern sollte oder besondere Routingmechanismen, wie z.B. eine Fallunterscheidung, für mehrere Adapter wie im Beispiel vorzunehmen ist.
Der Adapter kann im Gegensatz zur Umsetzung in FirstSpirit und im UX-Bus frei implementiert werden. Einzige Voraussetzung ist, dass der Adapter JMS-Nachrichten von einem Endpunkt entgegennehmen und in einem weiteren Endpunkt erzeugen kann. Zwei Beispiele von mit Camel umgesetzten Adaptern finden Sie in den Tutorials.
In den folgenden Tutorials werden zwei Beispiele Schritt für Schritt erläutert. Diese Beispiele können in eigene Projekte übernommen oder als Anregungen für eigene Implementierungen verwendet werden.
Als Grundlage für die Beispiele diente jeweils ein frisch aufgesetztes Mithras Energy-Projekt, welches als Beispielprojekt jeder FirstSpirit-Installation beiliegt.
Aktuelle Versionen des Quellcodes für die Beispiele finden sie unter: https://github.com/e-Spirit/uxbridge-samples
Für diese Beispiele ist grundlegendes Wissen folgender Technologien von Nutzen:
Außerdem ist es erforderlich, dass der UX-Bus in Betrieb und erreichbar ist. Informationen für den Betrieb des UX-Busses finden sie in der UX-Bridge Installationsanleitung.
Bei den Anwendungen ist es erforderlich, die Datenbankkonfiguration an die lokalen Gegebenheiten anzupassen. Informationen, wo die jeweilige Konfigurationen zu finden sind, entnehmen Sie bitte dem jeweiligen Kapitel.
In diesem Beispiel wird ein einfaches Widget erstellt, welches die neuesten Artikel anzeigt. Die Anzeige wird per JavaScript automatisch aktualisiert, sobald neue Artikel in dem Live-Repository hinzugefügt werden.
FirstSpirit ist das führende System, d.h. die Seiten werden statisch generiert und auf dem FirstSpirit-Server abgelegt. Das dynamische Widget wird zur Laufzeit per JavaScript in die Seite gebaut.
In der Beispiel-Anwendung wird das Widget auf der Startseite in der rechten Spalte eingebunden.
Den Quellcode für dieses Beispiel finden Sie im Github-Repository unter newsWidget:
https://github.com/e-Spirit/uxbridge-samples/tree/master/newsWidget/
Die Webanwendung liefert nur das JavaScript als jQuery-Plugin und einen Service mit JSONP-Unterstützung zur Aktualisierung der Daten. Das Grundgerüst des Widgets wird in FirstSpirit verwaltet.
Die Webanwendung wurde mit dem Web-Framework Grails in Version 2.4.4 erstellt.
Konfiguration
Alle Konfigurationsdateien liegen in dem für Grails-Anwendungen typischen Ordner → →
.
Wichtig sind an dieser Stelle die Dateien DataSource.groovy
und Config.groovy
.
DataSource.groovy
Hier werden für die verschiedenen Environments (test, development und production) die Datenbankverbindungen konfiguriert.
Config.groovy
Hier wird neben den URLs für die unterschiedlichen Environments auch die Verbindung zu MongoDB konfiguriert.
UrlMappings.groovy
Hier wurden 2 Mappings hinzugefügt:
/rest/v1/articles
verweist auf die Action list
des ArticleRestControllers
.
/rest/v1/article/$id
verweist auf die Action show
des ArticleRestControllers
.
Domainklasse
In dieser Anwendung gibt es eine einzelne Domainklasse: com.espirit.moddev.examples.uxbridge.widget.Article
Grails verwendet, wie der Adapter auch, das Persistence Framework Hibernate. Daher ist es nötig, darauf zu achten, dieselben Namen für die Attribute, Tabellen und Indizes zu verwenden, die auch schon im Adapter verwendet wurden.
Rest-Controller
Im Package com.espirit.moddev.examples.uxbridge.widget
findet sich der ArticleRestController
.
Über diesen Controller lädt das Widget die Liste der Artikel.
Der ArticleRestController
bietet die zwei Methoden list
und show
an.
Methode: list
Diese Methode liefert eine bestimmte Anzahl von Artikeln im JSONP-Format zurück.
Methode: show
Diese Methode liefert einen Artikel anhand der FirstSpirit-ID und der Sprache im JSONP-Format. Wird für den übergebenen Parameter kein Artikel gefunden, liefert die Methode einen 404 Fehlercode.
Service
Der ArticleService
liegt im Package com.espirit.moddev.examples.uxbridge.widget
.
Für dieses Beispiel wurden die beiden Methoden getLatestArticles
und ellipsis
implementiert.
Der ArticleService
wird im ArticleRestController
verwendet
getLatestArticles
Die Methode liefert die aktuellsten Artikel aus dem Live-Repository
ellipsis
Diese Methode wird verwendet um den Text für das Widget auf eine bestimmte Anzahl von Zeichen zu kürzen.
SQL und NoSQL
Im Gegensatz zu den Adaptern ist eine Anpassung des Quellcodes der Web-Anwendung in der Regel nicht nötig. Dank der Verwendung des Grails-Frameworks können die Domainklassen sowohl in einer Relationalen als auch einer NoSQL-Datenbank gespeichert werden.
Starten der Beispiel-Anwendung
Gestartet wird die Anwendung über die Kommandozeile:
grails run-app
Zum Starten der Anwendung mit dem MongoDB Live-Repository muss das entsprechende Environment angegeben werden:
grails mongo run-app
Das News Widget wird in diesem Tutorial in das Standard-Projekt Mithras Energy eingebaut. Deswegen importieren Sie dieses zuerst und nehmen Sie alle folgenden Änderungen in diesem Projekt vor.
Das komplett fertiggestellte Beispielprojekt wird unter dem Namen uxbridge_tutorial_newsWidget.tar.gz
mit ausgeliefert und kann genutzt werden, um den Templatecode und die Einstellungen zu betrachten.
Projekt-Konfiguration
Im ersten Schritt sollte in der Projekt-Konfiguration
ein neuer Vorlagensatz
für die UX-Bridge angelegt werden, der wie folgt zu konfigurieren ist:
Um Nachrichten an den UX-Bus zu schicken, sind im entsprechenden Template, das die Nachrichten erzeugen soll, die Felder, die im Datenmodell definiert wurden, in Form von XML auszugeben (siehe auch Kapitel Ausgabekanal anlegen und befüllen).
Projekteinstellungen
In den Projekteinstellungen wird die URL zur Web-Applikation definiert, aus der später die passenden Artikel dynamisch nachgeladen und in dem Widget angezeigt werden. Dafür wird das Template für die Projekteinstellungen um ein Feld erweitert.
<CMS_GROUP> <LANGINFOS> <LANGINFO lang="*" label="UX-Bridge"/> </LANGINFOS> <CMS_INPUT_TEXT name="ps_baseURL_UXB" hFill="yes" singleLine="no" useLanguages="no"> <LANGINFOS> <LANGINFO lang="*" label="Base URL UXB Widget"/> <LANGINFO lang="DE" label="Basis URL UXB Widget"/> </LANGINFOS> </CMS_INPUT_TEXT> </CMS_GROUP>
Sobald dies erfolgt ist, können die URLs für die UX-Bridge gepflegt werden.
Die Base URL ist die der Grails Anwendung widgetExample
, also z.B.: http://localhost:8080/widgetExample
In den globalen Inhalten muss eine neue globale Seite, basierend auf der Vorlage multilanguagelabel
, mit dem eindeutigen Namen latestarticles
angelegt werden
(Deutsch: Neueste Artikel, Englisch: Latest articles).
Seitenvorlage
Die Seitenvorlagen, die die UX-Bridge nutzen sollen, werden im Beispiel um eine Eingabekomponente erweitert. Diese Eingabekomponente bewirkt, dass die Marginalspalte vergrößert wird, so dass dem einzubauenden Widget mehr Platz zur Verfügung steht. Der Grund dafür ist, dass die Marginalspalte in ihrer Standardbreite zu klein ist, um das News Widget in einer angemessenen Auflösung darzustellen.
<CMS_GROUP> <LANGINFOS> <LANGINFO lang="*" label="UX Bridge Features"/> <LANGINFO lang="DE" label="UX Bridge Funktionen"/> </LANGINFOS> <CMS_INPUT_TOGGLE name="pt_enableUxBridgeLayout" type="radio" hFill="yes" preset="copy" singleLine="no" useLanguages="no"> <LANGINFOS> <LANGINFO lang="*" label="Enable UX Bridge layout for this page"/> <LANGINFO lang="DE" label="UX Bridge Layout für diese Seite aktivieren"/> </LANGINFOS> <OFF> <LANGINFO lang="*" label="No"/> <LANGINFO lang="DE" label="Nein"/> </OFF> <ON> <LANGINFO lang="*" label="Yes"/> <LANGINFO lang="DE" label="Ja"/> </ON> </CMS_INPUT_TOGGLE> </CMS_GROUP>
Diese Änderungen dienen des Weiteren dazu, die UX-Bridge nur auf den Seiten zu aktivieren, die diese auch einbinden.
Neben der Erweiterung des Eingabeformulars muss auch der Code in der Vorlage angepasst werden.
Zu Beginn der Seitenvorlage muss der Body-Bereich in eine Variable ausgelagert werden, damit die Variablen, die im Body gesetzt werden, schon zu Beginn der Seitenvorlage zur Verfügung stehen.
Beispiel.
$CMS_SET(set_pt_bodyright)$ $CMS_VALUE(#global.page.body("Content right"))$ $CMS_END_SET$ $CMS_SET(set_pt_bodyright, set_pt_bodyright.toString)$
Die Ausgabe an der ursprünglichen Stelle des Bodies lautet dann z.B.:
$CMS_VALUE(set_pt_bodyright)$
Der Header der Seitenvorlage muss noch um folgenden Aufruf erweitert werden, die die Seitenvariablen der UX-Bridge initialisiert:
$CMS_SET set_pt_insertIntoHead,““)$
Der HTML-Header muss danach um folgenden Aufruf erweitert werden, um die Java-Skripte zu importieren:
$CMS_VALUE(set_pt_insertIntoHead)$
Absatzvorlage
Das News Widget wird über eine Absatzvorlage in die Marginalspalte der gewünschten Seite eingebunden.
Dazu wird zunächst eine neue Absatzvorlage mit dem Namen uxb_widget
wie folgt angelegt.
Absatzvorlage uxb_widget.
$CMS_IF(pt_enableUxBridgeLayout)$ $CMS_SET(set_st_insertIntoHead)$ $CMS_RENDER(template:"uxbridge_widget_head", set_news_count:st_entries)$ $CMS_END_SET$ <div class="clearfix teasermodule uxbWidgetContainer"> <div class="uxbWidgetHeader"> <span>$CMS_VALUE(#global.gca("latestarticles"))$</span> </div> <div id="uxbWidgetContent"></div> </div> $CMS_SET(#global.pageContext["set_pt_insertIntoHead"], set_st_insertIntoHead.toString)$ $CMS_END_IF$
Die Klassen clearfix
und teasermodule
verwenden CSS-Eigenschaften aus dem Mithras Energy-Projekt und sind dafür ausgelegt, im Bereich Content right
der Standardseitenvorlage oder der Homepage eingesetzt zu werden.
Über die im Formular angelegte Eingabekomponente CMS_INPUT_NUMBER
kann definiert werden, wie viele News-Einträge in dem Widget angezeigt werden sollen.
Eingabekomponente CMS_INPUT_NUMBER.
<CMS_INPUT_NUMBER name="st_entries" allowEmpty="no" hFill="yes" max="20.0" min="1.0" preset="copy" singleLine="no" useLanguages="yes"> <LANGINFOS> <LANGINFO lang="*" label="Number of entries"/> <LANGINFO lang="DE" label="Anzahl der Einträge"/> </LANGINFOS> </CMS_INPUT_NUMBER>
Formatvorlage
Im Beispiel wird eine neue Formatvorlage (uxbridge_widget_head
) benutzt, die den benötigten JavaScript- und CSS-Code enthält.
Die Parameter, mit denen das jQuery-Plugin uxb_widget
initialisiert wird, sind konfigurierbar:
<script type="text/javascript" src="$CMS_VALUE(ps_baseURL_UXB)$/static/bundle-ui_head.js"/> <link rel="stylesheet" href="$CMS_VALUE(ps_baseURL_UXB)$/static/bundle-ui_head.css"/> <script> $(document).ready(function () { $("#uxbWidgetContent").uxb_widget({ lang:'DE', url:"$CMS_VALUE(ps_baseURL_UXB)$/rest/v1/articles", speed:2000, fadeFrom: "#F7D358", fadeTo: "white", count: $CMS_VALUE(set_news_count)$ }); }); </script>
Seite anlegen
Um die UX-Bridge zu nutzen, wird ein neuer Absatz vom Typ uxb_widget
in eine beliebige Seite eingebaut.
Gegebenenfalls muss dazu vorher der Absatz in der Seitenvorlage für die Marginalspalte noch erlaubt werden.
Tabelle und Tabellenvorlage (XML)
Basierend auf der im Schema bereits definierten Tabelle Press_Releases
sollte dann eine Tabellenvorlage angelegt werden, die das XML erzeugt, das an den UXB-Service weitergereicht wird:
XML, das an den UXB-Service weitergereicht wird.
<uxb_entity uuid = String destinations = String language = String command = String objectType = String> <uxb_content> <fs_id/> <language/> <url/> <date/> <headline/> <subheadline/> <teaser/> <content/> </uxb_content> </uxb_entity>
Die Kind-Elemente im uxb_content
-Tag sind gleichzeitig die Inhaltsfelder, die vom Adapter in das angeschlossene Content-Repository geschrieben werden und daher auch bei der Erstellung der Datenstruktur berücksichtigt werden sollten.
Dieses Codebeispiel stellt dabei nur die Struktur dieser Tabellenvorlage dar.
Den kompletten Code finden Sie im Beispielprojekt in der Tabellenvorlage mit dem Referenznamen Products.press_releases
.
Deployment
Im Beispielprojekt ist es möglich, die Generierung der JMS-Nachrichten und damit auch die Einträge in den angebundenen Content-Repositories automatisch über einen Workflow direkt aus der Datenquelle zu starten. Ebenso ist es möglich, über einen weiteren Workflow Objekte in FirstSpirit und in den angebundenen Content-Repositories direkt aus den Datenquellen heraus zu löschen. Die Workflows nutzen dazu neben Skripten auch Tabellenabfragen und Aufträge, die zunächst zu konfigurieren sind.
1. Tabellenabfragen anlegen
Es müssen Tabellenabfragen für das Erzeugen eines Datensatzes und allen Datensätzen der News-Tabelle angelegt werden.
Die Abfrage für einen Datensatz muss dabei noch eine Einschränkung auf die fs_id
-Spalte erhalten mit dem neu anzulegenden Parameter Id
.
Den kompletten Code finden Sie im Beispielprojekt in der Tabellenabfrage mit dem Referenznamen Products.pressdetailfilter
.
2. Auftrag anlegen
Es muss ein neuer Auftrag angelegt werden, der neben der Generierung der JMS-Nachrichten für den UXB-Service auch die Generierung und das Deployment der Übersichtsseiten übernimmt. Dazu ist dem Auftrag zunächst eine Generierungsaktion hinzuzufügen, die die Übersichtsseiten erzeugt. Das Delta-Deployment erweitert diese Aktion zur Laufzeit um die Detailseite des aktuell zu generierenden Datensatzes. Danach muss eine Skriptaktion erfolgen, die die UX-Bridge aktiviert:
Aktivierung der UX-Bridge.
#! executable-class com.espirit.moddev.uxbridge.inline.UxbInlineUtil
In der danach anzulegenden Generierung sollte dann eine Teilgenerierung erfolgen. Seite und Datensatz werden später durch das Delta-Deployment automatisch eingetragen, so dass nur die gewünschte JMS-Nachricht erzeugt wird. Danach kann das Deployment der Webseiten wie gewohnt erfolgen.
Soll die Durchlaufzeit für die Nachrichten im UX-Bus bis zur Veröffentlichung auf der Webseite gemessen werden, so muss als letztes noch die Aktion UX-Bridge Statistics Report
hinzugefügt werden, die folgendes Skript enthält:.
UX-Bridge Statistics Report.
#! executable-class com.espirit.moddev.uxbridge.inline.UxbResultHandler
Der Service wartet maximal den in der Servicekonfiguration der UX-Bridge definierten Zeitraum, bis die Antworten der Adapter ausgewertet werden. Kommt in diesem Zeitfenster keine Antwort, so gilt die Nachricht als fehlerhaft zugestellt. Da die Antwortzeiten je nach Nachricht und System variieren können, ist dieser Wert konfigurierbar.
Alternativ zu diesem maximalen Zeitraum für den gesamten Auftrag, kann ein Heartbeat konfiguriert werden. Hierfür wird ein maximaler Zeitraum konfiguriert, der zwischen zwei Heartbeat-Nachrichten vergehen darf. Empfängt der ResultHandler zu lange keinen Heartbeat, wird der Auftrag als fehlerhaft gewertet und eine Timeout-Nachricht angezeigt. Das Versenden von Heartbeat-Nachrichten muss auf Adapterseite implementiert werden.
3. Workflow-Skripte importieren
Im nächsten Schritt müssen die benötigten Workflow-Skripte importiert werden:
Die Init-Skripte initialisieren in diesem Fall Variablen und schreiben diese in die Session, so dass die Methoden des UXB-Moduls, die in den anderen Skripten aufgerufen werden, auf diese zugreifen können.
Konfiguriert werden müssen in uxb_content_release_init
folgende Parameter:
Parameter | Beispielwert | Beschreibung |
detail_page | pressreleasesdetails | Seitenreferenz der Seite, die die JMS-Nachrichten enthält |
query_uid | Products.pressdetailsfilter | Tabellenabfrage, die alle Datensätze erzeugt |
single_query_uid | Products.pressdetailfilter | Tabellenabfrage, die als Parameter die Id des Datensatzes erhält, der erzeugt werden soll |
query_param | Id | Parametername der Tabellenabfrage |
schedule_name | UX-Bridge | Name des Auftrags, der die JMS-Nachrichten erzeugen soll |
scheduler_uxb_generate | UX-Bridge Generate | Name der Generierungsaktion der JMS Nachrichten |
scheduler_generate | Generate | Name der Generierungsaktion für die HTML-Seiten |
Das Skript uxb_content_release_script
startet danach den zuvor konfigurierten Auftrag und führt die definierte Transition aus.
Konfiguriert werden müssen in uxb_content_delete_init
folgende Parameter:
Parameter | Beispielwert | Beschreibung |
Destinations | postgres | Name der Content Repositories, aus denen der Datensatz gelöscht werden soll |
transition_name | release | Name der Transition im Workflow, die nach dem |
object_type | news | Typ des Objekts, der gelöscht werden soll |
Innerhalb des nachfolgenden Skripts uxb_content_delete_script
wird der selektierte Datensatz in FirstSpirit gelöscht und eine Nachricht über den UXB-Service und den UX-Bus an das angeschlossene Content Repository geschickt, die dort die Löschaktion auslöst.
4. Workflows importieren
Um dem Redakteur eine einfache Möglichkeit zu bieten, die zuvor definierten Skripte auf einem Datensatz auszuführen, werden die beiden Workflows uxb_content_release
und uxb_content_delete
aus dem Demoprojekt verwendet.
Die Workflows führen dabei die gewünschten Operationen (Freigeben, Löschen) sowohl in FirstSpirit als auch über die UX-Bridge im konfigurierten Content-Repository durch.
5. Komplettabgleich
Im FirstSpirit-Beispielprojekt wurde der Komplettabgleich in dem Auftrag UX-Bridge Full Deployment
umgesetzt.
Dieses Beispiel enthält zwei Adapter: Einen für eine relationale Datenbank (PostgreSQL) und einen für eine NoSql-Datenbank (MongoDB).
Unter https://github.com/e-Spirit/uxbridge-samples/tree/master/newsWidget/adapter/
befindet sich neben den Projekten für die beiden Adapter (hibernate, mongodb) auch ein drittes Projekt, das Java-Klassen enthält, die in beiden Adaptern verwendet werden.
Zum Verarbeiten des Austauschformats wird JAXB verwendet.
Die entsprechenden Klassen befinden sich im Projekt https://github.com/e-Spirit/uxbridge-samples/tree/master/newsWidget/adapter/base
im Package com.espirit.moddev.uxbridge.entity
.
JAXB ermöglicht das einfache Arbeiten mit Java-Objekten, ohne sich um das Parsen des XML Gedanken machen zu müssen. Ähnlich wie bei JPA wird hier mit Annotationen gearbeitet.
@XmlRootElement(name = "uxb_entity") @XmlAccessorType(XmlAccessType.FIELD) public class UXBEntity { @XmlAttribute private String uuid; @XmlAttribute private String language; @XmlAttribute private String destinations; @XmlElement(type = UXBContent.class) private UXBContent uxb_content; @XmlAttribute private String command; @XmlAttribute private long createTime; @XmlAttribute private long finishTime; @XmlAttribute private boolean isHeartbeat; [...] }
DateType: XmlAdapter für das Datumsformat
Datumsangaben werden im FirstSpirit-Ausgabekanal nach dem Format yyyy-MM-dd’T’HH:mm:ssZ
formatiert.
Damit dieses Format in den JAXB-Klassen eingelesen werden kann, wurde die Klasse DateAdapter
implementiert.
Diese Klasse befindet sich im Package com.espirit.moddev.examples.uxbridge.widget.entity.type
.
@XmlElement() @XmlJavaTypeAdapter(value = DateAdapter.class, type = Date.class) private Date date;
UXBEntity und UXBContent
Die beiden Klassen implementieren den von der UX-Bridge vorgegebenen Teil (UXBEntity) und den projektspezifischen Teil (UXBContent) des Austauschformats.
UXBHeartbeat
Diese Klasse dient als Vorlage für eine Heartbeat-Nachricht. Sie basiert auf der UXBEntity-Klasse und definiert keine eigenen Format-Elemente. Stattdessen wird die isHeartbeat
-Eigenschaft der UXBEntity-Klasse auf true
gesetzt, um eine Nachricht als Heartbeat-Nachricht zu identifizieren.
Der Adapter für relationale Datenbanken wurde mit Hilfe von Hibernate umgesetzt. In diesem Beispiel wird zwar PostgreSQL verwendet, der Adapter sollte aber ohne größeren Aufwand auch mit anderen, von Hibernate unterstützten Datenbanken funktionieren.
Domainklasse: Article
Die Domain Klasse Article
befindet sich im Projekt widgetExample/adapter/base
im Package com.espirit.moddev.examples.uxbridge.widget
.
Die Klasse verfügt über eine generierte ID:
@Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id;
Das Attribut id
wird verwendet, damit die Domainklasse kompatibel zur Grails-Implementierung in der Web-Anwendung ist.
Um komplexe Datenbankstrukturen zu verhindern, wurde in diesem Beispiel pro Sprache ein Objekt erzeugt.
D.h. die FirstSpirit-ID ist in diesem Kontext nicht mehr eindeutig. Der Zugriff auf die Daten erfolgt daher über die FirstSpirit-ID (aid
) und die Sprache (language
).
Sinnvoll wäre an dieser Stelle der Einsatz eines zusammengesetzten Primärschlüssels. In diesem Beispiel wurde aber auf Grund der Komplexität bewusst auf diese Möglichkeit verzichtet.
Zu beachten ist, dass sich nach einem Löschen und erneutem Hinzufügen eines Artikels in das Live-Repository die ID ändert. Daher ist es erforderlich, für den Zugriff immer die FirstSpirit-ID und die Sprache zu verwenden.
ArticleHandler
Der Zugriff auf die Datenbank erfolgt im ArticleHandler (Package com.espirit.moddev.examples.uxbridge.widget.jpa
).
Er kümmert sich um das Entgegennehmen und Bearbeiten der Daten.
In dem Beispiel wurde im Handler für jedes der unterstützten Kommandos eine eigene Methode implementiert.
Die Konfiguration für diesen Adapter befindet sich in der Datei →
.
In dieser Spring-Xml Datei werden neben der Datenbank auch das JMS, der ArticleHandler und die Camel-Routen konfiguriert.
CamelContext
In diesem Context wird konfiguriert, welche Nachrichten für diesen Adapter interessant sind und von ihm verarbeitet werden.
Camel-Context.
<camelContext id="camelContext" trace="false" xmlns="http://camel.apache.org/schema/spring"> <package>com.espirit.moddev.examples.uxbridge.newswidget.entity</package> <onException> <exception>java.lang.Exception</exception> <handled> <constant>true</constant> </handled> <to uri="adapterReturn:jms:topic:BUS_IN?destination=mongodb&bodyValue=bodyTemp " /> </onException> <route id="uxbridge-commands"> <from uri="jms:topic:BUS_OUT" /> <filter> <xpath>//uxb_entity[contains(@destinations, 'mongodb')]</xpath> <filter> <xpath>//uxb_entity[@objectType = 'news']</xpath> <camel:setHeader headerName="bodyTemp"> <simple>${body}</simple> </camel:setHeader> <filter> <xpath>//uxb_entity[@command = 'add']</xpath> <convertBodyTo type="com.espirit.moddev.examples.uxbridge.newswidget.entity.UXBEntity" /> <bean ref="articleHandler" method="add" /> </filter> <filter> <xpath>//uxb_entity[@command = 'delete']</xpath> <convertBodyTo type="com.espirit.moddev.examples.uxbridge.newswidget.entity.UXBEntity" /> <bean ref="articleHandler" method="delete" /> </filter> <filter> <xpath>//uxb_entity[@command = 'cleanup']</xpath> <convertBodyTo type="com.espirit.moddev.examples.uxbridge.newswidget.entity.UXBEntity" /> <bean ref="articleHandler" method="cleanup" /> </filter> <to uri="adapterReturn:jms:topic:BUS_IN?destination=mongodb" /> </filter> </filter> </route> <route> <from uri="jms:topic:BUS_IN" /> <to uri="stream:out" /> </route> </camelContext>
Eine detaillierte Erläuterung zur Erstellung der Rückmeldung befindet sich im Kapitel Verwendung der Camel Komponente zur Antwortgenerierung.
Empfang von Nachrichten
Über den From-Tag (<from uri="activemq:Consumer.newsWidgetHibernate.VirtualTopic.BUS_OUT"/>
) wird die URI konfiguriert, über die Nachrichten eingelesen werden.
An dieser Stelle kommt ein virtueller Endpunkt zum Einsatz (siehe http://activemq.apache.org/virtual-destinations.html
).
Der Vorteil von virtuellen Endpunkten ist, dass für weitere Adapter keine Modifikation am Routing vorgenommen werden muss.
Neue virtuelle Endpunkte müssen lediglich dem Namensschema Consumer.%beliebiger Adaptername%.VirtualTopic.%Quell-Endpunkt%
folgen.
Durch die Virtualisierung können Nachrichten nicht, wie bei einer Queue, nur von einem Adapter gelesen werden, sondern alle entsprechenden Adapter erhalten die Nachricht.
Soll also zum Beispiel auch der neue Adapter myAdapter
Nachrichten konsumieren, die an den Endpunkt FS_OUT ausgeliefert werden, so könnte ein möglicher Endpunkt wie folgt aussehen:
Beispiel für einen Endpunkt.
activemq:Consumer.myAdapter.VirtualTopic.BUS_OUT
Filtern für das Live-Repository
Mittels des XPath-Ausdrucks (//uxb_entity[contains(@destinations, 'postgres')]
) werden die Nachrichten gefiltert, die in dieses Live-Repository geschrieben werden sollen.
Filtern des Objekt-Types
Mit dem Ausdruck //uxb_entity[@objectType = 'news']
werden die Nachrichten auf Objekte des Typs news
beschränkt.
Hinzufügen und Löschen von Artikeln
Mit diesen Ausdrücken wird nach dem entsprechenden Kommando gefiltert.
Bei //uxb_entity[@command = 'add']
handelt es sich um ein Hinzufügen zu dem Repository und bei //uxb_entity[@command = 'delete']
um Löschen aus dem Repository.
Vor dem eigentlichen Methoden-Aufruf wird das Austauschformat mittels JAXB und der Camel-Anweisung <convertBodyTo type="com.espirit.moddev.examples.uxbridge.widget.entity.UXBEntity"/>
umgewandelt.
Der Aufruf der entsprechenden Methode im ArticleHandler
erfolgt dann mit einem Objekt vom Typ UXBEntity
.
ArticleHandler
Der ArticleHandler ist der Teil des Adapters, der die Kommandos verarbeitet und die Artikel in das Repository schreibt oder sie daraus löscht.
Der ArticleHandler benötigt zum einen die EntityManagerFactory
für den Zugriff auf die Datenbank, zum anderen den CamelContext
und den Namen der Routen, auf denen Nachrichten zurück an FirstSpirit gesendet werden können.
Für das NoSQL-Live-Repository wurde ausschließlich der MongoDB-Datenbanktreiber verwendet. Auf den Einsatz eines Persistence Frameworks wurde bewusst verzichtet, da in dem Adapter die DB-Struktur der Web-Anwendung nachgebildet werden musste.
Domainklasse Article
Der MongoDB-Adapter verwendet dieselbe Domainklasse, die auch von dem Hibernate-Adapter verwendet wird. Die JPA-Annotationen werden dabei nicht berücksichtigt.
Id Generierung
In der Web-Anwendung wird Grails GORM für den Datenbankzugriff verwendet.
Damit der Adapter die identische Datenbankstruktur verwendet, musste eine Helfer-Methode generateIdentifier
eingeführt werden.
In dieser Methode werden über eine extra Collection (siehe http://www.mongodb.org/display/DOCS/Collections
) IDs verwaltet.
ArticleHandler
Das Vorgehen im ArticleHandler unterscheidet sich nicht von dem Vorgehen des Hibernate-ArticleHandlers (siehe Kapitel ArticleHandler).
Konfiguration
Die Konfiguration unterscheidet sich nur geringfügig von der Konfiguration des Hibernate-Adapters.
Der Destination-Filter filtert Nachrichten für die Destination mongodb
.
Die Parameter für die Verbindung zur Datenbank werden direkt an den ArticleHandler übergeben.
Mit dem folgendem Aufruf kann die API in das lokale Maven-Repository geladen werden:
mvn install:install-file -Dfile=<path-to-file> -DgroupId= com.espirit.moddev.uxbridge -DartifactId= uxbridge-camel-component -Dversion=<version> -Dpackaging=jar
Eine Beispielumsetzung kann wie folgt aussehen:
mvn install:install-file -Dfile=D:\ uxbridge-camel-component-1.2.4.1133.jar -DgroupId=com.espirit.moddev.uxbridge -DartifactId=uxbridge-camel-component -Dversion=1.2.4.1133 -Dpackaging=jar
Die Beispiel-Adapter können über die Kommandozeile gebaut werden:
mvn package
Die daraus resultierende War-Datei kann auf einen beliebigen ServletContainer (Tomcat, Jetty usw.) deployed werden.
Alternativ können die Adapter auch über die Kommandozeile gestartet werden:
mvn tomcat7:run
Um den Port des dadurch gestarteten Tomcats anzupassen, muss die Datei pom.xml
im Verzeichnis des jeweiligen Adapters angepasst werden.
In dem Beispielprojekt sind Unit- und Integrations-Tests enthalten.
Für die Tests werden eine In-Memory-Datenbank und jMockMongo (https://github.com/thiloplanz/jmockmongo
) verwendet.
Damit die Tests für den MongoDB-Adapter gestartet werden können, muss das jMockMongo-Jar in das lokale Repository importiert werden oder folgendes Maven-Repository verwendet werden:
<repositories> <repository> <id>thiloplanz-snapshot</id> <url>http://repository-thiloplanz.forge.cloudbees.com/snapshot/</url> </repository> </repositories>
Die dependency
muss dann wie folgt aussehen:
<dependency> <groupId>jmockmongo</groupId> <artifactId>jmockmongo</artifactId> <version>0.0.1-SNAPSHOT</version> <scope>test</scope> </dependency>
Die Integrations-Tests können mit folgendem Aufruf gestartet werden:
mvn verify -Pintegration-test
Wie im vorangegangenen Beispiel wird auch in diesem Beispiel ein einfaches Widget erstellt, welches die neuesten Artikel anzeigt. Der Unterschied liegt in der Umsetzung des Adapters. Dieser wurde ohne Programmierung und nur durch den Einsatz von Camel realisiert.
Es kann auf die Benutzung des ArticleHandlers verzichtet werden. Die Funktionen des ArticleHandlers werden dabei durch eine Konfiguration des CamelContextes ersetzt.
Die meisten Punkte sind identisch zu dem vorherigen Beispiel. Deshalb werden im folgendem nur die Änderungen beschrieben, die notwendig sind, um das Beispiel ohne Programmierung umzusetzen.
In Teilen ist der CamelContext mit dem im vorherigen Beispiel Erläutertem identisch. Im folgendem wird trotzdem der gesamte Context erläutert.
Camel-Context.
<camelContext id="camelContext" trace="false" xmlns="http://camel.apache.org/schema/spring"> <onException> <exception>java.io.IOException</exception> <handled> <constant>true</constant> </handled> <to uri="adapterReturn:jms:topic:BUS_IN?destination=mongodb&bodyValue=bodyTemp" /> </onException> <route id="uxbridge-commands"> <from uri="jms:topic:BUS_OUT" /> <filter> <xpath>//uxb_entity[contains(@destinations, 'mongodb')]</xpath> <filter> <xpath>//uxb_entity[@objectType = 'news']</xpath> <camel:setHeader headerName="bodyTemp"> <simple>${body}</simple> </camel:setHeader> <filter> <xpath>//uxb_entity[@command = 'add']</xpath> <camel:split stopOnException="true"> <camel:xpath>/uxb_entity/uxb_content/text()</camel:xpath> <camel:convertBodyTo type="java.lang.String" /> <camel:setBody> <language language="groovy"> <![CDATA[request.getBody().substring(request.getBody().indexOf("<![CDATA[")+9,request.getBody().lastIndexOf("]]]]><![CDATA[>"))]]> </language> </camel:setBody> <camel:convertBodyTo type="com.mongodb.DBObject" /> <to uri="mongodb:myDb?database=newsWidget& collection=article&operation=save" /> </camel:split> </filter> <filter> <xpath>//uxb_entity[@command = 'delete']</xpath> <camel:split stopOnException="true"> <camel:xpath>/uxb_entity</camel:xpath> <camel:convertBodyTo type="java.lang.String" /> <camel:setBody> <camel:groovy>'{aid:'+request.getBody().substring(request.getBody().indexOf('uuid=')+6,request.getBody().indexOf('"',request.getBody().indexOf('uuid=')+6))+', "language":"'+request.getBody().substring(request.getBody().indexOf('language=')+10,request.getBody().indexOf('"',request.getBody().indexOf('language=')+10))+'"}'</camel:groovy> </camel:setBody> <camel:convertBodyTo type="com.mongodb.DBObject" /> <to uri="mongodb:myDb?database=newsWidget& collection=article&operation=remove" /> </camel:split> </filter> <filter> <xpath>//uxb_entity[@command = 'cleanup']</xpath> <camel:split stopOnException="true"> <camel:xpath>/uxb_entity</camel:xpath> <camel:convertBodyTo type="java.lang.String" /> <camel:setBody> <camel:groovy>'{"lastmodified":{$lt:'+request.getBody().substring(request.getBody().indexOf('createTime=')+12,request.getBody().indexOf('"',request.getBody().indexOf('createTime=')+12))+'}}'</camel:groovy> </camel:setBody> <camel:convertBodyTo type="com.mongodb.DBObject" /> <to uri="mongodb:myDb?database=newsWidget& collection=article&operation=remove" /> </camel:split> </filter> <to uri="adapterReturn:jms:topic:BUS_IN?destination=mongodb" /> </filter> </filter> </route> </camelContext>
Der CamelContext beginnt mit einem Exception Handling. Dazu wird innerhalb des onException-Tags definiert, welche Exceptions und wie diese verarbeitet werden. Im vorliegenden Fall ist lediglich eine java.io.Exception angegeben. Es sind jedoch auch mehrere Exceptions gleichzeitig möglich.
Ist der handled-Tag auf true
gesetzt, wird die Exception behandelt.
In diesem Fall wird die Exception nicht weiter geworfen und es kommt nicht zu einem Abbruch des gesamten Vorgangs.
Dies entspricht einem try-catch-Block um alle Routen.
Sollen Exceptions in bestimmten Bereichen gesondert behandelt werden, ist ein expliziter try-catch-Block für diese Bereiche möglich.
Abschließend wird angegeben, was im Fall einer Exception getan werden soll. In diesem Fall wird eine Nachricht an BUS_IN gesendet. Der genaue Aufbau wird im Kapitel Verwendung der Camel Komponente zur Antwortgenerierung beschrieben.
Innerhalb der Route wird mittels filter
und xpath
das übergebene XML analysiert und die entsprechenden Aufrufe werden getätigt.
Um mit einer Mongo-Datenbank kommunizieren zu können, muss ein DBObject erzeugt werden.
Die Erzeugung erfolgt mit <camel:convertBodyTo type="com.mongodb.DBObject" />
.
Als Übergabeparameter wird ein JSON-Objekt als String erwartet.
Hierzu wird innerhalb des XML-Dokuments ein JSON-Objekt übergeben.
Mit text()
wird der Inhalt eines XML-Tags, in diesem Fall das JSON-Objekt, ausgelesen.
Sind innerhalb des JSON-Objekts Daten enthalten, die durch den XML-Parser bereits interpretiert werden, muss das JSON durch einen CDATA-Bereich eingeschlossen werden, um die ungewünschte Interpretation zu verhindern.
Dieser Bereich muss vor der Erstellung des DBObjects entfernt werden.
Das erfolgt mit:
<language language="groovy"><![CDATA[request.getBody().substring(request.getBody().indexOf("<![CDATA[")+9,request.getBody().lastIndexOf("]]]]><![CDATA[>"))]]></language>
Um das DBObject an die Mongo-Datenbank zu übermitteln, wird <to uri="mongodb:myDb?database=newsWidget&collection=article&operation=save"/>
aufgerufen.
Als Parameter werden die Datenbank, die Collection und die Operation mit übergeben.
Im delete
- und cleanup
-Bereich werden die JSON-Objekte mittels groovy
erstellt. Die benötigten Informationen (uuid
, language
, createTime
) werden aus dem uxb_entity
-Tag des XML-Dokuments geparst und an die entsprechende Stelle im JSON-Objekt gesetzt.
Somit ist es nicht notwendig, ein JSON-Objekt innerhalb des XML-Dokuments zu übergeben.
Um das News-Widget-Szenario ohne Programmierung verwenden zu können, ist noch eine kleinere Anpassung innerhalb FirstSpirits notwendig. Wie bereits im vorhergehenden Kapitel beschrieben, ist es notwendig, die Informationen im JSON-Format verschachtelt in einem XML-Dokument zu übergeben.
Content hinzufügen
Zum Hinzufügen von Content muss der UXB-Kanal des Datenbank-Schemas Products.press_release
angepasst werden. Der UXB-Kanal muss wie folgt aussehen:
<?xml version="1.0" encoding="UTF-8" ?>$CMS_SET(_id)$$CMS_VALUE(#row.id)$$CMS_VALUE(#global.language.hashCode())$$CMS_END_SET$ <uxb_entity uuid="$CMS_VALUE(#row.id)$" language="$CMS_VALUE(#global.language)$" destinations="postgres,mongodb" command="add" objectType="news"> <uxb_content> <![CDATA[{ "_id":$CMS_VALUE(_id)$, "aid":$CMS_VALUE(#row.id)$, "language":"$CMS_VALUE(#global.language)$", "url":"$CMS_REF(#global.node, contentId:#row.getId(),abs:1, templateSet:"html")$", $CMS_IF(#global.preview)$"lastmodified":$CMS_VALUE(#global.now.getTimeInMillis())$,$CMS_ELSE$"lastmodified":$CMS_VALUE(#global.getScheduleContext().getStartTime().getTimeInMillis())$,$CMS_END_IF$ $CMS_IF(!cs_date.isEmpty)$ "date":{"$date":"$CMS_VALUE(cs_date.format("yyyy-MM-dd'T'HH:mm:ss'Z'"))$"},$CMS_END_IF$ $CMS_IF(!cs_headline.isEmpty)$ "title":"$CMS_VALUE(cs_headline.convert2)$",$CMS_END_IF$ $CMS_IF(!cs_subheadline.isEmpty)$ "subHeadline":"$CMS_VALUE(cs_subheadline.convert2)$",$CMS_END_IF$ $CMS_IF(!cs_teaser.isEmpty)$ "teaser":"$CMS_VALUE(cs_teaser.convert2)$",$CMS_END_IF$ $CMS_IF(!cs_content.isEmpty)$ "content":"$CMS_FOR(section, cs_content)$$CMS_SET(tmp)$$CMS_VALUE(section)$$CMS_END_SET$$CMS_SET(tmp,tmp.toString)$$CMS_VALUE(tmp.convert2)$$CMS_END_FOR$"$CMS_END_IF$ }]]> </uxb_content> </uxb_entity>
Wie bereits beschrieben, wird ein XML-Dokument erzeugt, in dem ein JSON-Objekt eingebettet ist.
In diesem Beispiel wird eine Übersicht von Pressemitteilungen erzeugt, die über eine Drill-Down-Funktion nach Kategorien gefiltert werden können.
Die Web-Anwendung ist dabei das führende System. D.h. die Drill-Down-Funktion und die Übersichtsseite werden dynamisch erstellt. Die Detailseiten und die übrigen Seiten werden statisch generiert. Header und Footer werden auf der Übersichtsseite als HTML-Fragmente eingebunden. Diese Fragmente werden ebenfalls von FirstSpirit generiert.
Die News-Artikel, Kategorien und Meta-Kategorien werden mithilfe der UX-Bridge in ein Content-Repository geschrieben, auf welches die WebApp Zugriff hat. Diese Implementierung ist für das Beispiel einfach gehalten und nicht performance-optimiert, da bei jedem Update einer News sowohl auf die Kategorie als auch auf die Meta-Kategorie zugegriffen und diese ggf. aktualisiert wird. In einem realen Adapter würde man dies natürlich optimieren und Kategorien und Meta-Kategorien nur einmal auslesen und nur bei Änderungen an den Kategorien ein Update vornehmen. Alle Kategorien und Meta-Kategorien werden in der WebApp in einem Drill-Down-Menü angezeigt, wo die Kategorien, deren News angezeigt werden sollen, mithilfe von Checkboxen markiert werden können. Bei jedem An- und Abwählen einer Checkbox wird eine AJAX-Anfrage gesendet. Das zurückgelieferte HTML wird in den Bereich der Newsliste auf der Seite eingebaut. Es wurde eine Pagination realisiert, um die Übersichtlichkeit zu gewährleisten. Diese nutzt ebenfalls AJAX, denn die Anzahl der aufzulistenden News variiert mit den gewählten Kategorien.
Die Beispielanwendung newsExample
besteht aus den Komponenten Adapter (Hibernate), der Web-Anwendung (Grails) und dem FirstSpirit-Beispielprojekt.
Die Web-Anwendung wurde mit dem Web-Framework Grails in Version 2.4.4 erstellt.
Konfiguration
Alle Konfigurationsdateien liegen in dem für Grails-Anwendungen typischen Ordner <newsExample>/grails-app/conf
.
Wichtig sind an dieser Stelle die Dateien DataSource.groovy
und Config.groovy
.
DataSource.groovy
Hier werden für die verschiedenen Environments (test
, development
und production
) die Datenbankverbindungen konfiguriert.
Config.groovy
Hier werden die URLs für die aus FirstSpirit generierte Navigation definiert.
Domainklassen
Erstellen Sie drei Domainklassen mit den Namen News
, Category
und MetaCategory
.
Grails verwendet, wie der Adapter auch, das Persistence-Framework Hibernate.
Daher ist es notwendig, darauf zu achten, dieselben Namen für die Attribute, Tabellen und Indizes zu verwenden, die auch schon im Adapter verwendet wurden.
Rest-Controller
Erstellen Sie den passenden Controller zu der Domainklasse News
und implementieren Sie die Methode list
.
Über diese Methode lädt die Web-Anwendung die Liste der Artikel.
Methode: list
Diese Methode wird genutzt um die gleichnamige gsp
zu rendern.
Methode: listNews
Diese Methode rendert das gsp-Template newsListing
für eine bestimmte Liste von News, die vom FilterService geholt werden.
Methode: drilldown
Diese Methode rendert das gleichnamige Template, welches das Drill-Down-Menü und das dafür notwendige JavaScript bereitstellt.
Das Drill-Down-Menü wird beim Seitenaufruf direkt in die Seite hineingerendert. Das JavaScript darin nutzt jQuery und verwaltet das an- und abhaken der Checkboxen für die einzelnen Kategorien und Metakategorien.
Bei jedem Klick einer Checkbox wird eine AJAX-Anfrage mit den aktuell ausgewählten Kategorien an die Methode listNews
des Controllers gesendet.
Das zurückgelieferte HTML wird dann in das dafür vorgesehene div auf der Newsübersichtsseite eingefügt.
Damit die Liste der News übersichtlich bleibt, wird mit einer Pagination gearbeitet, welche ebenfalls dynamisch über AJAX-Abfragen die richtigen Seiten mit den richtigen Artikeln nachlädt.
Services
Dieser Service stellt Methoden zum Holen von News nach ihren Kategorien zur Verfügung.
Die Methode filter
liefert eine Map mit folgenden Schlüsseln zurück:
noCategory
zurückgeliefert, der vom Controller genutzt wird, um eine Nachricht hierüber anzuzeigen.
Mithilfe des Parameters categories
, können alle Kategorien angegeben werden, die angezeigt werden sollen.
Man übergibt diesem einen String mit dem Format cat_1cat_2_cat_4
um beispielsweise die Kategorien mit den IDs 1, 2 und 4 anzuzeigen.
Wenn der String all
enthält, werden alle Kategorien zurückgegeben.
Die Parameter max
und offset
werden benötigt, um Pagination nutzen zu können.
Die Methode filterForCategory
liefert alle News einer gegebenen Kategorie zurück.
Sie wird von der filter-Methode für jede einzelne Kategorie aufgerufen.
Dieser Service stellt eine Methode zum Rendern von HTML zur Verfügung.
renderHtml
Man übergibt der Methode die URL. Es wird ein http-Request ausgeführt, der den HTML-Schnipsel holt. Der korrekt formatierte HTML-Schnipsel wird anschließend zurückgegeben.
RenderTagLib
Diese TagLib stellt drei Tags bereit, um den Header, den Footer und die linke Navigationsspalte zu rendern.
Diese Tags werden in der main.gsp
genutzt.
Starten der Beispielanwendung
Gestartet wird die Anwendung über die Kommandozeile:
grails run-app
Übersichtsseite als Grails-App
Sobald die Anwendung erfolgreich gestartet wurde, kann die News-Übersichtsseite unter folgender URL aufgerufen werden:
http://localhost:8080/newsDrilldown/
Die Links zu den News-Artikeln auf der dynamischen Übersichtsseite verweisen auf die statisch generierten News-Detailseiten. So kann eine hohe Dynamik der Webseite erreicht werden, ohne Performance einbüßen zu müssen.
Das News-Szenario wird in diesem Tutorial in das Standard Projekt Mithras Energy eingebaut.
Deswegen importieren Sie dieses zuerst und nehmen Sie alle folgenden Änderungen in diesem Projekt vor.
Das komplett fertiggestellte FirstSpirit-Beispielprojekt wird unter dem Namen uxbridge_tutorial_newsDrilldown.tar.gz
mit ausgeliefert und kann genutzt werden um den Templatecode und die Einstellungen zu betrachten.
Konfiguration
Im ersten Schritt sollte eine neue Konvertierungs-Regel in den Server-Eigenschaften
angelegt werden.
Die entsprechende Regel sollte zuvor als Textdatei angelegt werden.
Außerdem sollte in der Projekt-Konfiguration
ein neuer Vorlagensatz für die UX-Bridge angelegt werden, der wie folgt zu konfigurieren ist:
Um Nachrichten an den UX-Bus zu schicken, sind im entsprechenden Template, das die Nachrichten erzeugen soll, die Felder, die im Datenmodell definiert wurden, in Form von XML auszugeben (siehe Kapitel Ausgabekanal anlegen und befüllen).
Seiten anlegen
Erstellen Sie zunächst vier neue Absatzvorlagen mit den Namen navigation_header
, navigation_footer
, navigation_left
und navigation_css
und füllen Sie den HTML-Ausgabe Kanal mit den benötigten HTML- und CSS-Fragmenten der Mithras Energy-Navigation.
Diese werden nachher separat ausgegeben und in die Web-App eingebaut.
In dem Beispielprojekt finden Sie diese in dem Ordner Header / Footer
in den Absatzvorlagen.
Legen Sie dann eine neue Seitenvorlage an und fügen Sie Ihre zuvor erstellten Absatzvorlagen zu den erlaubten Inhaltsbereichen im Register Eigenschaften
hinzu.
Schlussendlich editieren Sie Ihren HTML-Ausgabekanal wie folgt:
$CMS_VALUE(#global.page.body("content"))$
Achten Sie darauf, dass Sie in Ihrer Seitenvorlage kein HTML-Grundgerüst verwenden! |
Legen Sie nun auf Basis der zuvor erstellen Seitenvorlage drei neue Seiten in Ihrer Inhaltsverwaltung
an und fügen Sie folgende Absatzvorlagen hinzu:
Tabelle und Tabellenvorlage (XML)
Im Schema muss zunächst die Datenstruktur für die News, die Kategorien und die Metakategorien definiert werden, sofern nicht schon vorhanden.
In der Tabelle Press_Releases
wird der allgemeine Inhalt der Pressemitteilungen definiert.
Dazu gehören unter anderem eine Überschrift, der Text und das Datum.
Über eine n:m-Beziehung wird auf die Tabelle Press_Category
referenziert, in der sich der Name einer Kategorie speichern lässt.
Über eine weitere m:n-Beziehung können einer Kategorie mehrere Metakategorien hinzugefügt werden.
Basierend auf der News-Tabelle sollte nach folgendem Schema dann eine Tabellenvorlage angelegt werden, die das XML erzeugt, das an den UXB-Service weitergereicht wird:
<uxb_entity uuid = String destinations = String language = String command = String objectType = String> <uxb_content> <fs_id/> <language/> <url/> <date/> <headline/> <subheadline/> <teaser/> <content/> <metaCategories> <metaCategory> <fs_id/> <name/> <categories> <category> <fs_id/> <name/> </category> </categories> </metaCategory> </metaCategories> </uxb_content> </uxb_entity>
Die Kind-Elemente im uxb_content
-Tag sind gleichzeitig die Inhaltsfelder, die vom Adapter in das angeschlossene Content-Repository geschrieben und daher auch bei der Erstellung der Datenstruktur berücksichtigt werden sollen.
Legen Sie neben der News-Tabelle auch zwei Tabellenvorlagen für die Kategorie und Metakategorie an und befüllen Sie diese anschließend in Ihren Datenquellen.
Im Beispielprojekt finden Sie diese im Schema Products
mit den Referenznamen Products.press_category
und Products.press_metacategory
.
Deployment
Im Beispielprojekt ist es möglich, die Generierung der JMS-Nachrichten und damit auch der Einträge in den angebundenen Content-Repositories automatisch über einen Workflow direkt aus der Datenquelle zu starten. Ebenso ist es möglich, über einen weiteren Workflow Objekte in FirstSpirit und in den angebundenen Content-Repositories direkt aus den Datenquellen heraus zu löschen. Die Workflows nutzen dazu neben Skripten auch Tabellenabfragen und Aufträge, die zunächst zu konfigurieren sind.
1. Tabellenabfragen anlegen
Es müssen Tabellenabfragen für die Erzeugung von einem Datensatz und allen Datensätzen der News-Tabelle angelegt werden.
Die Abfrage für einen Datensatz muss dabei noch eine Einschränkung auf die fs_id
Spalte mit dem neu anzulegenden Parameter Id
erhalten.
2. Auftrag anlegen
Es muss ein neuer Auftrag angelegt werden, der neben der Generierung der JMS-Nachrichten für den UXB-Service auch die Generierung und das Deployment der Übersichtsseiten übernimmt. Dazu ist dem Auftrag zunächst eine Generierungsaktion hinzuzufügen, die die Übersichtsseiten erzeugt. Das Delta-Deployment erweitert diese Aktion zur Laufzeit um die Detailseite des aktuell zu generierenden Datensatzes. Danach muss eine Skriptaktion erfolgen, die die UX-Bridge aktiviert:
#! executable-class com.espirit.moddev.uxbridge.inline.UxbInlineUtil
In der danach anzulegenden Generierung sollte eine Teilgenerierung erfolgen. Seite und Datensatz werden später durch das Delta-Deployment automatisch eingetragen, so dass nur die gewünschte JMS-Nachricht erzeugt wird. Danach kann das Deployment der Webseiten wie gewohnt erfolgen.
Soll die Durchlaufzeit für die Nachrichten im UX-Bus bis zur Veröffentlichung auf der Webseite gemessen werden, so muss als letztes noch die Aktion UX-Bridge Statistics Report
hinzugefügt werden, die folgendes Skript enthält:
#! executable-class com.espirit.moddev.uxbridge.inline.UxbResultHandler
Der Service wartet maximal den in der Servicekonfiguration der UX-Bridge definierten Zeitraum, bis die Antworten der Adapter ausgewertet werden. Kommt in diesem Zeitfenster keine Antwort, so gilt die Nachricht als fehlerhaft zugestellt. Da die Antwortzeiten je nach Nachricht und System variieren können, ist dieser Wert konfigurierbar.
Alternativ zu diesem maximalen Zeitraum für den gesamten Auftrag, kann ein Heartbeat konfiguriert werden. Hierfür wird ein maximaler Zeitraum konfiguriert, der zwischen zwei Heartbeat-Nachrichten vergehen darf. Empfängt der ResultHandler zu lange keinen Heartbeat, wird der Auftrag als fehlerhaft gewertet und eine Timeout-Nachricht angezeigt. Das Versenden von Heartbeat-Nachrichten muss auf Adapterseite implementiert werden.
3. Workflow-Skripte importieren
Im nächsten Schritt müssen die benötigten Workflow-Skripte importiert werden:
Die Init-Skripte initialisieren in diesem Fall Variablen und schreiben diese in die Session, so dass die Methoden des UXB-Moduls, die in den anderen Skripten aufgerufen werden, auf diese zugreifen können.
In uxb_news_example_release_init
müssen folgende Parameter konfiguriert werden:
Parameter | Beispielwert | Beschreibung |
detail_page | pressreleasesdetails | Seitenreferenz der Seite, die die JMS-Nachrichten erzeugt |
query_uid | Products.pressdetailsfilter | Tabellenabfrage, die alle Datensätze erzeugt |
single_query_uid | Products.pressdetailfilter | Tabellenabfrage, die als Parameter die Id des Datensatzes erhält, der erzeugt werden soll |
query_param | Id | Parametername der Tabellenabfrage |
schedule_name | UX-Bridge | Name des Auftrags, der die JMS-Nachrichten erzeugen soll |
scheduler_uxb_generate | UX-Bridge Generate | Name der Generierungsaktion der JMS-Nachrichten |
scheduler_generate | Generate | Name der Generierungsaktion für die HTML-Seiten |
transition_name | Release | Name der Transition im Workflow, die nach dem |
Das Skript uxb_news_example_release_script
startet danach den zuvor konfigurierten Auftrag und führt die definierte Transition aus.
In uxb_news_example_delete_init
müssen folgende Parameter konfiguriert werden:
Parameter | Beispielwert | Beschreibung |
destinations | postgres | Name der Content Repositories, aus denen der Datensatz gelöscht werden soll |
transition_name | release | Name der Transition im Workflow, die nach dem |
object_type | news | Typ des Objekts, der gelöscht werden soll |
Innerhalb des nachfolgenden Skripts uxb_news_example_delete_script
wird der selektierte Datensatz in FirstSpirit gelöscht und eine Nachricht über den UXB-Service und den UX-Bus an das angeschlossene ContentRepository geschickt, um dort die Löschaktion auszulösen.
4. Workflows importieren
Die Workflows uxb_news_example_release
und uxb_news_example_delete
rufen die zuvor konfigurierten Skripte auf.
5. Komplettabgleich
In dem FirstSpirit-Beispielprojekt wurde der Komplettabgleich in dem Auftrag UX-Bridge Full Deployment
umgesetzt.
Der Adapter ist die Komponente, die die Daten vom UX-Bus einliest und in das entsprechende Live-Repository schreibt.
Für die Verarbeitung des im Ausgabekanal definierten XML wird in diesem Beispiel JAXB verwendet.
Die entsprechenden Klassen befinden sich im Package com.espirit.moddev.examples.uxbridge.newsdrilldown.entity
.
Wie JPA wird bei JAXB mit Annotations gearbeitet, um die XML-Tags an ein Java-Objekt zu binden.
@XmlRootElement(name = “uxb_entity”) @XmlAccessorType(XmlAccessType.FIELD) Public class UXBEntity { @XmlAttribute private String uuid; @XmlAttribute private String language; @XmlAttribute private String destinations; @XmlElement(type = UXBContent.class) private UXBContent uxb_content; @XmlAttribute private String command; @XmlAttribute private String createTime; @XmlAttribute private String finishTime; [...] }
DateType: XmlAdapter für das Datumsformat
Datumsangaben werden im FirstSpirit-Ausgabekanal nach dem Format yyyy-MM-dd’T’HH:mm:ssZ
formatiert.
Damit dieses Format in den JAXB-Klassen eingelesen werden kann, wurde die Klasse DateAdapter
implementiert.
Diese Klasse befindet sich im Package com.espirit.moddev.examples.uxbridge.newsdrilldown.entity.type
.
@XmlElement() @XmlJavaTypeAdapter(value = DateAdapter.class, type = Date.class) Private Date date
UXBEntity, UXBContent, UXBMetaCategory und UXBCategory
Diese Klassen repräsentieren das im Ausgabekanal definierte Austauschformat.
UXBEntity
Diese Klasse entspricht dem von der UX-Bridge vorgegebenen Grundgerüst des Austauschformats.
UXBContent, UXBMetaCategory und UXBCategory
Die projektspezifischen JAXB-Klassen zum Verarbeiten des Austauschformats. Hier sind die eigentlichen Informationen der Objekte enthalten, die über die UX-Bridge verteilt werden.
In diesem Beispiel sind dies also die Pressemitteilungen mit den entsprechenden Meta-Kategorien und Kategorien.
Die Domainklassen befinden sich im Package com.espirit.moddev.uxbridge
.
Um Mehrsprachigkeit abzubilden, enthält jedes Objekt eine Sprache und jede Sprache wird als eigenständiges Objekt im Repository gespeichert.
Dieses Vorgehen hat zur Folge, dass die FirstSpirit-ID (UUID) nicht mehr eindeutig ist. Daher wurden Standard Hibernate/JPA-Mechanismen verwendet, um eine eindeutige ID zu erzeugen.
@Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id;
Diese ID im Live-Repository wird sich nach dem Löschen aus dem Repository und erneutem Hinzufügen ändern. Sollte Ihr Anwendungsfall ein anderes Verhalten benötigen, bietet sich der Einsatz eines zusammengesetzten Primärschlüssels, aus FirstSpirit-ID und Sprache an.
News, NewsCategory, NewsMetaCategory
Der Aufbau der Klassen entspricht in etwa dem in FirstSpirit definierten Datenbank-Schema. Dieses Vorgehen ist nicht zwingend nötig, soll an dieser Stelle aber für das Verständnis hilfreich sein.
Der NewsHandler
im Package com.espirit.moddev.exsamples.uxbridge.news.jpa
ist die Klasse, welche die Daten entgegen nimmt und bearbeitet.
In dem Beispiel wurde im Handler für jedes der unterstützten Kommandos eine eigene Methode implementiert.
1. Command add
Speichern oder Aktualisieren einer Pressemitteilung im Live-Repository.
In diesem Fall ist darauf zu achten, dass die Meta-Kategorien und Kategorien im Austauschformat innerhalb der Pressemittelung übermittelt werden. Im Repository werden Kategorien und Meta-Kategorien aber getrennt gespeichert.
Für dieses Beispiel bedeutet dies, dass bei diesem Kommando neben der Pressemitteilung auch die enthaltenen Kategorien und Meta-Kategorien aktualisiert bzw. neu angelegt werden müssen.
2. Command delete
Löschen einer Pressemitteilung im Live-Repository und der dazugehörigen und im Auftragsscript definierten Detailseite (siehe Kapitel Auftrag anlegen und konfigurieren) auf dem Webserver.
Damit die Methode die richtige Seite auf dem Webserver finden kann, muss in der applicationContext.xml
im Newshandler-Bean der Parameter webpath
auf den Pfad zum Verzeichnis des Webservers (z.B. /home/tomcat/webapps
) gesetzt werden.
<constructor-arg name="webpath" value="/home/tomcat/webapps"/>
Zu beachten ist, dass die Implementierung in diesem Beispiel nicht dafür sorgt, dass Meta-Kategorien oder Kategorien gelöscht werden, wenn es keine Pressemitteilung in einer dieser Kategorien gibt.
3. Command cleanup
Löschen aller Pressemitteilungen, die älter sind, als das gegebene Datum.
In der Spring-XML-Datei WEB-INF/applicationContext.xml
werden die Komponenten konfiguriert.
D.h. es werden die Datenbankverbindung, ConnectionPooling, JMS und das Routing definiert.
Das Routing wird im XML-Bereich <camelContext id="camelContext" …>
definiert.
<camelContext id="camelContext" trace="false" xmlns="http://camel.apache.org/schema/spring"> <package>com.espirit.moddev.examples.uxbridge.newsdrilldown.entity</package> <onException> <exception>java.io.IOException</exception> <handled> <constant>true</constant> </handled> <to uri="adapterReturn:jms:topic:BUS_IN?destination=postgres&bodyValue=bodyTemp" /> </onException> <route id="uxbridge-commands"> <from uri="activemq:Consumer.newsDrillDown-Hibernate.VirtualTopic.BUS_OUT" /> <filter> <xpath>//uxb_entity[contains(@destinations, 'postgres')]</xpath> <filter> <xpath>//uxb_entity[@objectType = 'news_article']</xpath> <camel:setHeader headerName="bodyTemp"> <simple>${body}</simple> </camel:setHeader> <filter> <xpath>//uxb_entity[@command = 'add']</xpath> <convertBodyTo type="com.espirit.moddev.examples.uxbridge.newsdrilldown.entity.UXBEntity" /> <bean ref="newsHandler" method="add" /> </filter> <filter> <xpath>//uxb_entity[@command = 'delete']</xpath> <convertBodyTo type="com.espirit.moddev.examples.uxbridge.newsdrilldown.entity.UXBEntity" /> <bean ref="newsHandler" method="delete" /> </filter> <filter> <xpath>//uxb_entity[@command = 'cleanup']</xpath> <convertBodyTo type="com.espirit.moddev.examples.uxbridge.newsdrilldown.entity.UXBEntity" /> <bean ref="newsHandler" method="cleanup" /> </filter> <to uri="adapterReturn:jms:topic:BUS_IN?destination=postgres" /> </filter> </filter> </route> </camelContext>
Weiterführende Informationen und Möglichkeiten finden Sie unter http://camel.apache.org/spring.html
.
Eine detaillierte Erläuterung zur Erstellung der Rückmeldung befindet sich im Kapitel Verwendung der Camel Komponente zur Antwortgenerierung.
Route uxbridge-commands
Es können beliebig viele Routen definiert werden, wobei die im Adapter definierten Routen nicht mit den Routen des UX-Bus verwechselt werden oder deren Aufgabe übernehmen sollten. Die Routen im Adapter sollten nur die für diesen Adapter wichtigen Routen beinhalten.
In dieser Beispielanwendung wurde eine Route definiert:
<route id="uxbridge-commands">
Nachrichtenquelle
Mit dem From
-Tag wird dem Integration-Framework (Apache Camel
) angegeben, von wo Daten eingelesen werden.
In diesem Beispiel sieht das From
-Tag folgendermaßen aus
<from uri="activemq:Consumer.newsDrillDown-Hibernate.VirtualTopic.BUS_OUT" />
Die Daten oder auch Nachrichten werden über das JMS-Topic BUS_OUT
eingelesen.
An dieser Stelle kommt ein virtueller Endpunkt zum Einsatz (siehe http://activemq.apache.org/virtual-destinations.html
).
Der Vorteil von virtuellen Endpunkten ist, dass für weitere Adapter keine Modifikation am Routing vorgenommen werden muss.
Neue virtuelle Endpunkte müssen lediglich dem Namensschema Consumer.%beliebiger Adaptername%.VirtualTopic.%Quell-Endpunkt%
folgen.
Durch die Virtualisierung können Nachrichten nicht wie bei einer Queue nur von einem Adapter gelesen werden, sondern alle entsprechenden Adapter erhalten die Nachricht.
Soll also zum Beispiel auch der neue Adapter myAdapter
Nachrichten konsumieren, die an den Endpunkt FS_OUT
ausgeliefert werden, so könnte ein möglicher Endpunkt wie folgt aussehen:
activemq:Consumer.myAdapter.VirtualTopic.BUS_OUT
Filter
Durch das Filtern von Nachrichten erfasst der NewsHandler
keine für ihn unwichtigen Nachrichten, also zum Beispiel Nachrichten für ein anderes Repository oder eines anderen Objekt-Typs.
Zum Filtern der Nachrichten werden in diesem Beispiel einfach XPath-Ausdrücke verwendet.
Bei diesem funktional eingeschränkten Beispiel sind nicht alle Filter-Optionen nötig, wurden aber trotzdem integriert, um Anhaltspunkte für eigene Ideen zu geben.
1. Filter Destination
//uxb_entity[contains(@destinations, 'postgres')]
An dieser Stelle werden Nachrichten gefiltert, die in der PostgreSQL-Datenbank landen sollen. Alle anderen Nachrichten, auf die dieser Ausdruck nicht passt, werden nicht weiter behandelt. Da Nachrichten gleichzeitig in verschiedene Live-Repositories geschrieben werden können, wird an dieser Stelle mit contains gearbeitet.
2. Filter Object-Type
//uxb_entity[@objectType = 'news_article']
Der NewsHandler
kann nur Objekte vom Typ news_article
bearbeiten.
Auch hier gilt, dass die Nachrichten, für die dieser Ausdruck nicht zutreffend ist, nicht weiter behandelt werden.
In der Regel enthalten Nachrichten immer nur ein Objekt eines Typs, daher wird hier mit "=" gearbeitet.
3. Filter Command
<xpath>//uxb_entity[@command = 'add']</xpath>
Im letzten Schritt werden die Nachrichten nach den Kommandos gefiltert.
4. JAXB Konvertierung
<convertBodyTo type="com.espirit.moddev.examples.uxbridge.news.entity.UXBEntity" />
Das XML der Nachricht wird mittels JAXB in eine Java-Klasse konvertiert.
5. Methoden-Aufruf
<bean ref="newsHandler" method="add" />
Zu guter Letzt wird am Ende der Filterkette die entsprechende Methode im NewsHandler
aufgerufen.
Mit dem folgendem Aufruf kann die API in das lokale Maven-Repository geladen werden:
mvn install:install-file -Dfile=<path-to-file> -DgroupId=<group-id> -DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=<packaging>
Eine Beispielumsetzung kann wie folgt aussehen:
mvn install:install-file -Dfile= D:\uxbridge-module-api-1.2.4.1133.jar -DgroupId=com.espirit.moddev.uxbridge -DartifactId=uxbridge-module-api -Dversion=1.2.4.1133 -Dpackaging=jar
Die Beispiel-Adapter können über die Kommandozeile gebaut werden:
mvn package
Die daraus resultierende War-Datei kann auf einen beliebigen ServletContainer (Tomcat, Jetty usw.) deployed werden.
Alternativ können die Adapter auch über die Kommandozeile gestartet werden:
mvn tomcat7:run
Um den Port des dadurch gestarteten Tomcat anzupassen, muss die Datei pom.xml
im Verzeichnis des jeweiligen Adapters angepasst werden.
In dem Beispielprojekt sind Unit- und Integrations-Tests enthalten.
Für die Tests werden eine In-Memory-Datenbank und jMockMongo (siehe https://github.com/thiloplanz/jmockmongo
) verwendet.
Damit die Tests für den MongoDB-Adapter gestartet werden können, muss das jMockMongo-Jar in das lokale Repository importiert werden.
Die Integrations-Tests können mit folgendem Aufruf gestartet werden:
mvn verify -Pintegration-test
Zur Verwendung des UXB-Service in einem Modul oder in einem Skript muss das API-Jar in der entsprechenden Version in den Classpath aufgenommen werden.
Der Zugriff auf den UXBService erfolgt dann über den Aufruf:
UxbService uxbService = context.getConnection().getService(UxbService.class);
Eine Beispiel-Implementierung für Delete- und Release-Executables finden sie im →
.
Um das Demo-Projekt zu erstellen, wird Apache-Maven benötigt.
Außerdem ist es erforderlich, dass das fs-access.jar
als Artefakt im lokalen Maven-Repository installiert ist.
Sind diese Vorrausetzungen erfüllt, kann das Projekt mit mvn clean package
gebaut werden.
In diesem Schritt wird im target
-Verzeichnis des Projekts ein FSM erstellt, das über die FirstSpirit-Server Adminkonsole installiert werden kann.
Danach können die enthaltenen Beispiel-Executables verwendet werden.
Die beiden Beispiel-Implementierungen können in den beiden vorangegangenen Tutorials verwendet werden. Dafür muss wie folgt vorgegangen werden:
Skript „Datensatz löschen“ (uxb_content_delete_script)
Das Skript wurde als Executable implementiert, daher wird an dieser Stelle nur noch das Executable aufgerufen.
#! executable-class com.espirit.moddev.uxbridge.samples.workflow.DeleteEntityExecutable
Skript „Datensatz veröffentlichen“ (uxb_content_release_script)
Dieses Skript wird ebenfalls durch das entsprechende Executable ersetzt:
#! executable-class com.espirit.moddev.uxbridge.samples.workflow.ReleaseAndDeployEntityExecutable
CamelContext Rückgabe
FirstSpirit erwartet nach einer Interaktion mit einem Adapter über die UX-Bridge eine Rückmeldung in Form eines XML-Dokuments.
Hierfür steht eine Camel-Component
zur Verfügung, die dieses XML-Dokument erstellt.
Um Zugriff auf die Komponente im CamelContext zu haben, muss sie wie folgt eingebunden werden:
<bean id="adapterReturn" class="org.uxbridge.camel.component.AdapterReturnComponent"></bean>
Um die Funktion zu verwenden, muss sie bei der Weitergabe der Daten an BUS_IN
mit aufgerufen werden:
<to uri="adapterReturn:jms:topic:BUS_IN" />
Dies geschieht sowohl bei der erfolgreichen Übermittlung der Daten an die Datenbank als auch im Falle einer Exception. Die Funktion erstellt jeweils die passende Antwort.
Mittels dieser Komponente ist es möglich, die Antwort, die FirstSpirit von einem Adapter erwartet, zu generieren.
Dies gilt sowohl für eine reguläre Antwort als auch für eine Antwort im Fehlerfall.
Voraussetzung für den Einsatz dieser Komponente ist, dass der Adapter mit Apache Camel
umgesetzt wird (siehe http://camel.apache.org/
).
Einbinden der Komponente
Um einen Zugriff auf die Camel-Komponente zu bekommen, muss diese eingebunden werden.
Dies geschieht über die mit ausgelieferte Datei uxbridge-camel-component-<version>.jar
.
Diese muss in den Java Class Path des Projekts eingebunden werden.
(Bei Eclipse: Rechts Klick auf das → → →
)
Um die Komponente innerhalb eines Adapters nutzen zu können, muss die Komponente als bean eingebunden werden. Der Aufruf hierzu sieht wie folgt aus: <bean id="adapterReturn" class="org.uxbridge.camel.component.AdapterReturnComponent"></bean> Die ID kann frei gewählt werden. Eine Änderung der ID erfordert jedoch eine entsprechende Anpassung des nachfolgend dargestellten Aufbaus der URL. |
Aufbau der URL
Der Aufbau der URL beginnt mit dem Aufruf der Komponente.
Dies geschieht über die ID, die beim Einbinden der Komponente angegeben wird.
Danach folgt der Aufruf der Ziels. Am Ende wird noch der Parameter destination
an die URL angehängt.
Der gesamte Aufbau kann dann wie folgt aussehen:
<to uri="adapterReturn:jms:topic:BUS_IN?destination=mongodb" />
Beim Aufruf innerhalb des Exception-Handlings ist ein zweiter Parameter notwendig, um welchen der Aufbau der URL ergänzt werden muss:
<to uri="adapterReturn:jms:topic:BUS_IN?destination=mongodb&bodyValue=bodyTemp" />
Parameter
Es werden zwei Parameter an die Komponente übergeben.
Der erste Parameter ist die destination
, von der die Antwort erzeugt wird.
Dieser Parameter wird direkt mittels eines Fragezeichens an die URL angehängt.
Da innerhalb des Aufrufs des Adapters mehrere destinations
übergeben werden können, dient dieser Parameter der Unterscheidung des Ziels, das zu dieser Antwort geführt hat.
Der zweite Parameter wird nur im Fall einer Exception benötigt.
Da im Fall einer Exception der aktuelle Status der Nachricht an den Exception Handler
übergeben wird, kann es vorkommen, dass der Inhalt der Nachricht nicht mehr vollständig und beispielsweise das root-Element des XML-Dokuments nicht mehr vorhanden ist.
Da für die Verarbeitung allerdings das gesamt XML-Dokument benötigt wird, ist es notwendig, dieses vor der Verarbeitung im Header der Nachricht zwischen zu speichern.
Das Zwischenspeichern erfolgt mit:
<setHeader headerName="bodyTemp"><simple>${body}</simple></setHeader>
Der headerName
ist dabei frei wählbar, muss der Komponente aber mitgeteilt werden.
Dies geschieht über den zweiten Parameter bodyValue
.
Durch eine Erweiterung ist es möglich, über den UXB-Service eigene Nachrichten, die ein frei definierbares Format besitzen, an den UX-Bus zu schicken.
Dazu muss das Interface des UxbMessageGenerators
implementiert werden.
Das Interface stellt bereits den Kontext des Auftrags sowie einige Methoden, die Informationen zum generierten Element liefern, zur Verfügung.
Über den Kontext ist ein Zugriff auf das gesamte Projekt und dessen Elemente möglich.
Um einen eigenen UxbMessageGenerator
zu implementieren, muss das API-Jar der UX-Bridge in der entsprechenden Version in den Classpath aufgenommen werden (siehe Kapitel Verwendung der UXB-Service-API).
Zusätzlich besteht eine Abhängigkeit zum fs-access.jar
.
Dieses wird mit jedem FirstSpirit-Server installiert und muss ebenfalls im Classpath verfügbar gemacht werden.
Die neue Klasse implementiert zunächst das UxbMessageGenerator-Interface
:
public class DemoMessage implements UxbMessageGenerator
Innerhalb der Klasse müssen darüber hinaus einige Methoden implementiert werden, die vom UXB-Service genutzt werden, um Informationen zu übergeben, und die nachfolgend kurz beschrieben werden:
supportsMedia()
Gibt an, ob dieser MessageGenerator Nachrichtengenerierung für Medien unterstützt.
setStartTime(long startTime)
Die startTime
ist der Zeitpunkt, an dem die Nachricht erzeugt wurde.
setSchedulerId(long schedulerId
Die schedulerId
ist die Id des gestarteten Auftrags.
setProjectId(long projectId)
Die Id
des Projekts, das generiert wird.
setGenerationContext(GenerationContext generationContext)
Im generationContext
befindet sich der gesamte Generierungskontext des Auftrags.
Über diesen ist ein Zugriff auf das gesamte Projekt und dessen Elemente sowie auf das aktuell generierte Element möglich.
generate()
Die generate
-Methode wird vom UXB-Service zur Nachrichtenerzeugung aufgerufen und muss die generierte Nachricht vom Typ Document
zurückliefern.
generateMedia(Media media, Language language, Resolution resolution)
Die generateMedia
-Methode wird vom UXB-Service zur Nachrichtenerzeugung für Medien aufgerufen wenn supportsMedia()
true liefert, und muss die generierte Nachricht vom Typ Document
zurückliefern.
Innerhalb des Auftrags, der die UXB-Nachrichten generiert, muss vor der Script-Aktion Activate Generation
(siehe Kapitel Teilgenerierung) eine weitere Script-Aktion ausgeführt werden.
Innerhalb des Scripts muss die Kontextvariable MessageGenerator
mit dem vollqualifizierten Klassennamen der Klasse belegt werden, der die Nachrichtengenerierung übernehmen soll.
context.setProperty("MessageGenerator","com.package.DemoMessage");
Die Klasse muss dabei innerhalb des FirstSpirit-Servers verfügbar gemacht werden, z.B. über ein Modul. Um ein korrektes Classloading zu gewährleisten, kann die Klasse als Public-Komponente mit modul-lokalen Resourcen konfiguriert werden.
Aufruf im Cluster-Betrieb Da im Cluster-Betrieb auf den Slave-Systemen keine Scripte gestartet werden, erfolgt hier die Übergabe des Klassennamens der Klasse, die die Nachrichtengenerierung übernehmen soll, nicht über die Script-Aktion, sondern im Template der Projekteinstellungsseite. Dieses muss um folgende Zeile erweitert werden: $CMS_SET(#global.pageContext["MessageGenerator"], "com.espirit.moddev.portal.UxbPortalMessage")$ Darüber hinaus ist darauf zu achten, das in der Generierungsaktion im Auftrag keine |
[convert] 0x00="" 0x01="" 0x02="" 0x03="" 0x04="" 0x05="" 0x06="" 0x07="" 0x08="" 0x09="" 0x0A="" 0x0B="" 0x0C="" 0x0D="" 0x0E="" 0x0F="" 0x10="" 0x11="" 0x12="" 0x13="" 0x14="" 0x15="" 0x17="" 0x18="" 0x19="" 0x1A="" 0x1B="" 0x1C="" 0x1D="" 0x1E="" 0x1F="" 0x3c="<" 0x3e=">" 0x22=""" 0x27="'" 0x26="&" [quote]