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