Cyclos - Eine Architekturanalyse

Transcrição

Cyclos - Eine Architekturanalyse
Cyclos - Eine Architekturanalyse
1 Grundlegende Struktur
Cyclos ist eine auf dem Struts-Framework basierende Webapplikation. Struts selbst folgt
extensiv dem MVC-Paradigma (genauer: der Model2-Architektur) und somit ist auch die
Cyclos-Architektur an dieses Paradigma gebunden.
2 Konfiguration
Die Datei web.xml im Verzeichnis /WEB-INF des web-Projekts ist die zentrale Konfigurationsdatei des Struts-Frameworks. Sie enthält alle Links zu den entsprechenden
struts-config-Dateien, grundlegende Konfigurationen, Links zu den verwendeten Servlets
und Spring-Dateien, sowie den Pfad der Willkommen-Seite (Startseite) von Cyclos.
3 Requestabarbeitung
Requests werden von der Struts Controller-Komponente empfangen, im Fall Cyclos ist
diese das vorgeschaltet Action-Servlet. Nun identifiziert Struts das Action-Mapping, welches für die Abarbeitung des Requests zuständig ist.
Cyclos verteilt das Action-Mapping auf mehrere kleinere Dateien, die alle den Namen
struts-config name.xml tragen und sich im Ordner WEB-INF/struts-config/ des webProjekts befinden. Der Platzhalter name beschreibt hierbei eine aus Entwicklersicht logische Zusammenfassung von Actions, die für eine bestimmte Funktionalität innerhalb der
Cyclos-Plattform zuständig sind. Diese Trennung ist jedoch von Struts nicht vorgeschrieben, sie dient nur der besseren Übersicht.
Im Folgenden betrachten wir beispielhaft den Workflow, der zum Editieren einer Währung
abgearbeitet wird:
• der Controller (das Action-Servlet) erhält vom Client den GET-Request do/admin/editCurrency
• die web.xml Konfigurationsdatei wird nach den Pfaden der einzelnen struts-configs
durchsucht
• die struts-configs werden nach der entsprechenden Action durchsucht
• die zuständige Action wird ausgeführt
Jede Action wird innerhalb des Mappings in einem separaten action-Tag angelegt, dessen
- teilweise optionalen - Parameter durch die Struts-API vorgeschrieben sind.
<a c t i o n
p a t h=” / admin / e d i t C u r r e n c y ”
t y p e=” n l . s t r o h a l m . c y c l o s . c o n t r o l s . a c c o u n t s . c u r r e n c i e s . E d i t C u r r e n c y A c t i o n ”
n a m e=” e d i t C u r r e n c y F o r m ”
s c o p e=” r e q u e s t ”
i n p u t=” admin / e d i t C u r r e n c y ”>
<s e t −p r o p e r t y p r o p e r t y=” s e c u r e ” v a l u e=” t r u e ” />
< f o r w a r d n a m e=” s u c c e s s ” p a t h=” / do / admin / e d i t C u r r e n c y ” r e d i r e c t=” t r u e ”></ f o r w a r d >
</ a c t i o n >
1
Das obige Beispiel veranschaulicht die Definition der Action editCurrency, welche in der
Datei struts-config currencies.xml eingetragen ist. Sie enthält als Parameter alle wichtigen
Informationen für die Abarbeitung des Requests:
• path - aufrufender Request (hierüber wird die Action dem Request zugeordnet)
• type - Name und Pfad der zur Bearbeitung benötigten Java-Klasse
• name (optional) - Name des Form-Beans, falls mit dieser Aktion einer verknüpft ist
• scope (optional) - identifiziert, in welchem Bereich (Scope) auf den Form-Bean zugegriffen werden soll
• input - URI des Forms, der aufgerufen wird, falls es während der Abarbeitung zu
einem Validierungsfehler kommt
Alle verarbeitenden Klassen im Cyclos liegen im controler-Verzeichnis. Jede dieser ControlerKlassen muss primär von BaseAction abgeleitet sein. BaseAction implementiert die Methode execute(), die von Struts automatisch aufgerufen wird, um die FormBeans auszulesen und die entsprechenden Aktionen auszuführen. Der Funktion werden 4 Argumente
übergeben.
• ActionMapping - das <action/>-Element, welches die auszuführende Aktionsklasse
beschreibt
• ActionForm - das FormBean-Objekt, mit dem Formulardaten ausgelesen werden
können
• HttpServletRequest - die zu verarbeitende HTTP-Anfrage
• HttpServletResponse - das Response-Objekt, welches an den Client zurückgegeben
wird
@Override
public f i n a l
ActionForward
execute ( final
final
final
final
ActionMapping actionMapping ,
ActionForm actionForm ,
HttpServletRequest request ,
H t t p S e r v l e t R e s p o n s e r e s p o n s e ) throws E x c e p t i o n {
...
// C r e a t e an a c t i o n
final ActionContext
context
c o n t e x t = new A c t i o n C o n t e x t ( a c t i o n M a p p i n g , a c t i o n F o r m , r e q u e s t ,
response , user , f e t c h S e r v i c e ) ;
r e q u e s t . s e t A t t r i b u t e ( ” formAction ” , a c t i o n M a p p i n g . g e t P a t h ( ) ) ;
...
// P r o c e s s t h e a c t i o n
final ActionForward forward = executeAction ( context ) ;
return f o r w a r d ;
...
}
Bevor die eigentliche Aktion bearbeitet wird, muss vorher noch geprüft werden, ob der
Benutzer korrekt angemeldet ist und die Session wird entsprechend geladen.
Der ActionContext kapselt alle wichtigen Daten in einem Objekt. Desweiteren wird dem
Request der Pfad des ActionMapping-Objektes angefügt, damit das ActionServlet später
weiß, welche Aktion weiter abgearbeitet werden soll. Schließlich wird die Methode executeAction() aufgerufen. Diese erzeugt ein ActionForward -Objekt, welches an das aufgerufene
ActionServlet übergeben wird.
Im Fall der Aktion alle Währungen anzeigen“ würde diese Methode in der Klasse
”
ListCurrenciesAction folgenden Aufbau haben:
2
@Override
protected A c t i o n F o r w a r d e x e c u t e A c t i o n ( A c t i o n C o n t e x t c o n t e x t ) throws E x c e p t i o n {
HttpServletRequest request = context . getRequest () ;
L i s t <C u r r e n c y > c u r r e n c i e s = c u r r e n c y S e r v i c e . l i s t A l l ( ) ;
request . setAttribute (” currencies ” , currencies ) ;
request . setAttribute (” editable ” ,
g e t P e r m i s s i o n S e r v i c e ( ) . c h e c k P e r m i s s i o n ( ” s y s t e m C u r r e n c i e s ” , ” manage ” ) ) ;
return c o n t e x t . g e t I n p u t F o r w a r d ( ) ;
}
In Cyclos ist es so geregelt, dass nun auf eine oder mehrere Service-Klassen zugegriffen wird. Im obigen Beispiel wird auf die Klasse currencyService zugegriffen, in der eine
Methode liegt, die konkret zum Auflisten aller Währungen dient: listAll().
public L i s t <C u r r e n c y > l i s t A l l ( ) {
i f ( c a c h e d C u r r e n c i e s == n u l l ) {
cachedCurrencies = currencyDao . listAll () ;
}
return c a c h e d C u r r e n c i e s ;
}
Die Service-Klassen können im Cyclos nicht direkt auf die Datenbank zugreifen. Ein Zugriff auf die Datenbank ist nur über ein DAO-Objekt (Data Access Object) möglich. Daher
findet in der Service-Methode ein Aufruf der entsprechenden DAO-Klasse statt. Wie im
nächsten Beispiel ersichtlich kann die entsprechende DAO-Klasse dann die entsprechende
Datenbank-Anfrage durchführen und das Ergebnis zurückliefern.
public L i s t <C u r r e n c y > l i s t A l l ( ) {
return l i s t ( ” from C u r r e n c y c o r d e r by c . name” , n u l l ) ;
}
Die Konfiguration und Zusammenarbeit zwischen den Entity, DAO und Service-Klassen
wird in Cyclos allerdings mittels Spring und auf Datenbankebene durch Heibernate realisiert.
Im Allgemeinen ruft der Controller die Modelschicht auf, in Cyclos sind das die ServiceKlassen, um Daten zu verarbeiten und in die Datenbank zu schreiben bzw. aus der Datenbank zu lesen. Das Ergebnis wird an den Controller zurück gegeben. Im Falle der listAll()Funktion werden alle Währungen aus der Datenbank dem Namen nach geordnet in der
Liste currencies gespeichert. Sind diese Daten wichtig für direkt nachfolgende Aktionen
bzw. für das Erstellen der Webseite, müssen die Daten dem Request hinzugefügt werden.
Dies geschieht mit Hilfe der Methode setAttribut(). Im obigen Beispiel wird die Währungsliste und ein boolscher Wert dem Request angefügt, um sie später in der Java-Server-Page
wieder auslesen zukönnen.
Schließlich wird ein ActionForward -Objekt erzeugt. Dieses Objekt ist eine JavaBean
und wird vom Controller verwendet, um festzulegen, welche Seite als Nächstes angezeigt
werden soll. context.getInputForward() teilt Struts mit, dass die JSP-Datei geladen werden
soll, welche im input-Tag des ActionMapping-Objekts angegeben ist.
In Cyclos repräsentiert das input-Attribut allerdings nie direkt den Pfad einer JSP-Seite,
sondern referenziert den Namen einer Tiles-Definition. Um diese Definition heraus zu finden, verfährt Struts nach dem selben Prinzip wie für die actions. In der struts-config.xml
sind alle tiles-defs-Dateien aufgelistet. Alle tiles-defs name.xml Dateien befinden sich im
Verzeichnis WEB-INF/tiles-defs/. Diese werden der Reihe nach geparst, bis der Wert des
name-Attributes der definition mit dem Wert des input-Attributes der action übereinstimmt. Für den input=“admin/ listCurrencies“befindet sich die zu suchende Definition
in der tiles-defs currencies.xml Datei.
3
< d e f i n i t i o n n a m e=” admin / l i s t C u r r e n c i e s ” extends=” . adminLayout ”>
<p u t n a m e=” body ” v a l u e=” / p a g e s / a c c o u n t s / c u r r e n c i e s / l i s t C u r r e n c i e s . j s p ”/>
</ d e f i n i t i o n >
• name - gibt an, welcher Bereich der Seite neu geladen werden soll
• value - der Pfad der JSP-Datei. In Cyclos befinden sich alle dieser Dateien im pagesOrdner
Innerhalb dieser JSP-Dateien kann man nun auf die Attribute des Request zugreifen, die
im Controller estellt wurden. Dies geschieht mittels ihrem Attributnamen. In der listCurrencies.jsp wird eine Übersicht der möglichen Zahlungsmittel in Form eine Tabelle erzeugt.
Über eine Schleife werden iterativ alle Währungen durchlaufen und jeweils eine neue Zeile der Tabelle hinzugefügt. Der Wert editable gibt an, ob dem Benutzer die Möglichkeit
zusteht, Währungen zu bearbeiten.
<c : f o r E a c h v a r=” c u r r e n c y ” i t e m s=” $ { c u r r e n c i e s } ”>
...
<t d a l i g n=” l e f t ”>$ { c u r r e n c y . n a m e }</t d>
<t d a l i g n=” l e f t ”>$ { c u r r e n c y . s y m b o l }</t d>
...
</c : f o r E a c h >
Schließlich wird diese generierte HTML-Seite als Response an den Client zurück geschickt.
3.1 Formulare
Am Beispiel für das Editieren von Währungen soll nun der Einsatz und die Wirkungsweise
von Formularen erläutert werden. Die Rechte zum Bearbeiten von Währungen liegen beim
Administrator. Der Aufbau der Menüliste wird durch die menuAdmin.jsp-Datei beschrieben, welche sich im Verzeichnis /web/pages/general/layout/ befindet.
<c y c l o s : m e n u k e y=”menu . admin . a c c o u n t s ”>
<c y c l o s : m e n u u r l=” / do / admin / l i s t C u r r e n c i e s ” k e y=”menu . admin . a c c o u n t s . c u r r e n c i e s ” m o d u l e=
” s y s t e m C u r r e n c i e s ” o p e r a t i o n=” v i e w ” />
...
</ c y c l o s : m e n u >
Der Ausschnitt beschreibt den Menüpunkt Accounts. Er besteht aus mehreren Untermenüs. Klickt man auf diesen Link, so wird ein Request mit der angegebenen URL an
den Server geschickt und es wird die bereits beschriebene Aktion ausgeführt. Alle Module,
die in Cyclos zur Verwendung kommen, müssen in der Datei Permission.java im Ordner
/src/nl/strohalm/cyclos/setup aufgelistet werden. Sie werden während des Setup statisch
erzeugt. Das Attribut key referenziert die Überschrift des Menüs. Der resultierende Wert
ist kontextabhängig von der eingestellten Sprache.
• Spracheinstellung - Die Sprache wird in der cyclos.properties-Datei eingestellt. Als
Voreinstellung ist die englische Sprache angegeben cyclos.embedded.locale = en US“
”
• Wertzuweisung - für jede Sprache muss es eine Datei geben, in der jedem key
einen Text zugewiesen wird. Diese Dateien befinden sich im Verzeichnis /web/WEBINF/classes
In der ApplicationResources en US.properties aus dem Verzeichnis /web/WEB-INF/classes
findet man folgenden Eintrag:
4
...
m e n u . a d m i n . a c c o u n t s . c u r r e n c i e s=M a n a g e
...
Currencies
Als Ergebnis der Action /admin/listCurrencies wird eine Übersicht aller Währungen
erstellt. Ist man als Administrator angemeldet, so hat man zusätzlich die Möglichkeit, die
einzelnen Zahlungsmittel zu bearbeiten bzw. zu löschen.
...
<c y c l o s : s c r i p t s r c=” / p a g e s / a c c o u n t s / c u r r e n c i e s / l i s t C u r r e n c i e s . j s ” />
...
<c : c h o o s e ><c : w h e n t e s t=” $ { e d i t a b l e } ”>
<i m g c u r r e n c y I d=” $ { c u r r e n c y . i d } ” s r c=”<c : u r l v a l u e=” / p a g e s / i m a g e s / e d i t . g i f ” />” c l a s s=”
e d i t d e t a i l s ” />
<i m g c u r r e n c y I d=” $ { c u r r e n c y . i d } ” s r c=”<c : u r l v a l u e=” / p a g e s / i m a g e s / d e l e t e . g i f ” />” c l a s s=”
remove ” />
</c : w h e n ><c : o t h e r w i s e >
<i m g c u r r e n c y I d=” $ { c u r r e n c y . i d } ” s r c=”<c : u r l v a l u e=” / p a g e s / i m a g e s / v i e w . g i f ” />” c l a s s=”
v i e w d e t a i l s ” />
</c : o t h e r w i s e ></c : c h o o s e >
...
Zusätzlich zum generierten HTML-Code wird dem Response eine JavaScript-Datei übergeben. Dieses Script definiert die Events, welche beim Klicken auf die Bilder ausgelöst
werden sollen.
...
’ img . d e t a i l s ’ : f u n c t i o n ( i m g ) {
setPointer ( img ) ;
img . onclick = function () {
s e l f . l o c a t i o n = p a t h P r e f i x + ” / e d i t C u r r e n c y ? c u r r e n c y I d=” + i m g . g e t A t t r i b u t e ( ”
currencyId ”) ;
}
},
...
Die JS-Funktion self.location zwingt dem Browser zum Neuladen der angegeben Seite, in
diesem Falle do/editCurrency?currencyId =ID. Es wird ein Request an den Server geschickt
und das Servlet bearbeitet folgende Action:
<f o r m −b e a n s >
<f o r m −b e a n n a m e=” e d i t C u r r e n c y F o r m ” t y p e=” n l . s t r o h a l m . c y c l o s . c o n t r o l s . a c c o u n t s . c u r r e n c i e s .
EditCurrencyForm ” />
</ f o r m −b e a n s >
<a c t i o n
p a t h=” / admin / e d i t C u r r e n c y ”
t y p e=” n l . s t r o h a l m . c y c l o s . c o n t r o l s . a c c o u n t s . c u r r e n c i e s . E d i t C u r r e n c y A c t i o n ”
n a m e=” e d i t C u r r e n c y F o r m ”
s c o p e=” r e q u e s t ”
i n p u t=” admin / e d i t C u r r e n c y ”>
<s e t −p r o p e r t y p r o p e r t y=” s e c u r e ” v a l u e=” t r u e ” />
< f o r w a r d n a m e=” s u c c e s s ” p a t h=” / do / admin / e d i t C u r r e n c y ” r e d i r e c t=” t r u e ”></ f o r w a r d >
</ a c t i o n >
Das Attribut name bestimmt die Form-Bean, die als FormAction-Objekt der executeMethode der EditCurrencyAction.jsp übergeben wird.
Alle Controller-Klassen, die Form-Beans verwenden, werden nicht direkt von BaseAction abgeleiten, sondern von der Klasse BaseFormAction. Diese Klasse überschreibt die
Methode executeAction().
...
@Override
protected f i n a l A c t i o n F o r w a r d e x e c u t e A c t i o n ( f i n a l A c t i o n C o n t e x t
if ( isFormPreparation ( context ) ) {
return h a n d l e D i s p l a y ( c o n t e x t ) ;
} else i f ( i s F o r m V a l i d a t i o n ( context ) ) {
return h a n d l e V a l i d a t i o n ( c o n t e x t ) ;
} else i f ( i s F o r m S u b m i s s i o n ( context ) ) {
return h a n d l e S u b m i t ( c o n t e x t ) ;
} else {
return c o n t e x t . s e n d E r r o r ( ” e r r o r s . i n v a l i d r e q u e s t ” ) ;
}
}
protected A c t i o n F o r w a r d h a n d l e D i s p l a y ( f i n a l A c t i o n C o n t e x t
prepareForm ( context ) ;
return c o n t e x t . g e t A c t i o n M a p p i n g ( ) . g e t I n p u t F o r w a r d ( ) ;
}
...
5
c o n t e x t ) throws E x c e p t i o n {
c o n t e x t ) throws E x c e p t i o n {
Diese Methode definiert eine Fallunterscheidung der Request-Anfrage.
• bei einer GET-Anfrage wird die Funktion handleDisplay() ausgeführt
• eine POST-Anfrage bewirkt den Aufruf der Funktion handleSubmit()
Die Funktion self.location() löst eine GET-Anfrage aus. Die Funktion prepareForm()
wird durch die Klasse EditCurrencyAction implementiert. handleSubmit() wird durch derer überschrieben.
prepareForm() erstellt die Formular-Bean und lädt die Währung aus der Datenbank
mittels der vom Browser übergebenen Variable currencyId. Falls die ID keinen Wert hat,
wird eine neue Währung erstellt. Anschließend werden die Daten wieder dem RequestObjekt angefügt.
@Override
protected void p r e p a r e F o r m ( f i n a l A c t i o n C o n t e x t c o n t e x t ) throws E x c e p t i o n {
final HttpServletRequest request = context . getRequest () ;
final EditCurrencyForm form = context . getForm () ;
f i n a l long i d = f o r m . g e t C u r r e n c y I d ( ) ;
f i n a l boolean i s I n s e r t = i d <= 0 L ;
...
Currency currency ;
if ( isInsert ) {
c u r r e n c y = new C u r r e n c y ( ) ;
e d i t a b l e = true ;
} else {
c u r r e n c y = c u r r e n c y S e r v i c e . load ( id ) ;
}
...
request . s e t A t t r i b u t e ( ” currency ” , currency ) ;
...
}
Der Aufruf return context.getActionMapping().getInputForward()“ bewirkt, dass die
”
Kontrolle an das ActionServlet zurückgegeben wird und die unter input angegebene JSPDatei wird geladen. Zusätzlich wird der Datei noch das ActionMapping-Objekt übergeben.
Die editCurrency.jsp generiert eine Eingabemaske, über die der Benutzer Eigenschaften
der Währung bearbeiten kann. Von Bedeutung ist folgender Tag:
<s s l : f o r m
m e t h o d=” p o s t ” a c t i o n=” $ { f o r m A c t i o n } ”>
Er teilt dem Browser mit, dass das Request an den Server nach dem submit per POSTAnfrage geschickt werden soll. Und die eben beschriebene Action /admin/editCurrency
wird erneut bearbeitet. Vor dem Abschicken des Formulars wird es clientseitig validiert.
Diesmal wird die Methode handleSubmit() ausgewertet.
@Override
protected A c t i o n F o r w a r d h a n d l e S u b m i t ( f i n a l A c t i o n C o n t e x t c o n t e x t ) throws E x c e p t i o n {
final EditCurrencyForm form = context . getForm () ;
Currency currency = getDataBinder () . readFromString ( form . getCurrency () ) ;
...
currency = currencyService . save ( currency ) ;
...
return A c t i o n H e l p e r . r e d i r e c t W i t h P a r a m ( c o n t e x t . g e t R e q u e s t ( ) , c o n t e x t . g e t S u c c e s s F o r w a r d ( ) ,
” currencyId ” , currency . getId () ) ;
}
Die Funktion getDataBinder().readFromString() wandelt Plaintext in ein Datenobjekt
um, welches von Java interpretiert werden kann. Anschließend wird das Currency-Objekt
in der Datenbank abgespeichert. ActionHelper.redirectWithParam() bewirkt den Aufruf
einer weiteren Action. Es wird innerhalb des ActionMapping-Objekts derjenige forward Tag ausgewählt, dessen name-Attribut=“success“ ist. path gibt die nächste Aktion an, die
aufgerufen werden soll, ohne vorher eine JSP-Datei auszuführen. In diesem Fall wird die
selbe Aktion erneut aufgerufen als GET-Anfrage. Dabei werden dem Request das Attribut
currencyId hinzugefügt. Am Ende wird entsprechend wieder die Formularmaske der eben
bearbeiteten Währung angezeigt.
6
<f o r w a r d
n a m e=” s u c c e s s ” p a t h=” / do / admin / e d i t C u r r e n c y ” r e d i r e c t=” t r u e ”></ f o r w a r d >
4 Zusammenfassung
• Cyclos folgt dem MVC-Prinzp
das Model befindet sich im services-Ordner
der Controller befindet sich im controls-Verzeichnis
der View ist im Ordner pages im Webverzeichnis lokalisiert
• die web.xml ist die zentrale Konfigurationsdatei
in ihr werden alle Konfigurationsdateien für Spring aufgelistet
sie konfiguriert das Action Servlet
• Cylcos verwendet das Tiles-Plugin, um die Webseite in logische Bereiche zu untergliedern
alle tiles-defs-Dateien befinden sich im tiles-defs-Odner im Webverzeichnis
sie bestimmen, welche JSP-Dateien ausgeführt werden sollen
• die auszuführenden Actions werden in den struts-config-Dateien beschrieben
sie übergeben den Request an den Controller
sie rufen die JSP -Datei auf, welche als Response an den Client zurückgeschickt
wird
• der Controller stellt die Verbindung zwischen dem Action Servlet und der Modelschicht dar
nur der Controller kennt die aufgerufene Action
alle controls-Klassen müssen direkt/indirekt von der Klasse BaseAction abgeleitet sein
Struts ruft automatisch die Methode execute() auf
sollen zusätzlich Form-Beans eingesetzt werden, muss die Klasse von BaseFormAction erben
bei Formularen wird beim Request zwischen GET und POST unterschieden
der Controller lässt die Daten vom Model verarbeiten und speichert sie im Request-Objekt ab
soll die Kontrolle an das Action Servlet zurückgegeben werden, muss ein ActionForward-Objekt erzeugt werden
• in Abhängigkeit vom Rückgabewert der Controllers hat das Servlet zwei Möglichkeiten
es wird ein Response-Objekt erstellt und an den Browser geschickt
der Request wird zur weiteren Bearbeitung an eine andere Action geleiten
7