Montag, 16. Dezember 2013

AppFuse - extend with eclipse

Nachdem in den letzten beiden Posts gezeigt wurde, was alles in AppFuse steckt und womit das realisiert wurde, geht es in diesem Post um die Erweiterbarkeit von AppFuse. Dazu soll ein neuer Menüpunkt zum Verwalten von Projekten geschaffen werden. Die Erweiterung wird mit Hilfe von Eclipse umgesetzt, weshalb zuerst ein entsprechender Import erfolgen muss.

Der Import von AppFuse in Eclipse ist ziemlich straightforward, wenn man bereits Eclipse installiert und das Maven-Plugin m2e hinzugefügt hat. Zusätzlich empfohlen wird das WebToolPlatform-Plugin oder kurz nur WTP-Plugin. In Eclipse muss das Menü "File > Import" aufgerufen werden. Es erscheint ein Fenster in dem man sich bis zu dem Punkt "Maven > Existing Maven Projects" duchhangelt.



Mit dem Button "Next" geht es weiter zur Auswahl des zu importierenden Projektes. Hier kann das root-Verzeichnis des Multi-Mode-Projektes ausgewählt werden. Der Import erkennt automatisch die darunter liegen Projekte der Einzelmodule.



Mit einem erneuten Klick auf den Button "Next" erscheint eine Mapping-Tabelle in der Maven-Goals zu Eclipse-Aktionen zugeordnet werden können. Dabei fehlen 3 Zuordnungen mit dem Verweis auf "Resolve later".



Also schauen wir mal, ob wir diese Zuordnungen wirklich benötigen und wenn ja auch später zuordnen können. Mit dem Klick auf den Button "Finish" wird der Import gestartet. Das dauert jetzt eine ganze Weile, da Eclipse noch weitere JARs aus dem Internet holt. Anschließend wollte Eclipse neu gestartet werden.

Es gibt auch die alternative Möglichkeit, die .project-Files von Eclipse durch das Eclipse-Plugin von Maven generieren zu lassen:

#> mvn install eclipse:eclipse


Als ich diesen Befehl aus dem root-Ordner des Projektes aufgerufen habe, kam folgende Fehlermeldung:

[WARNING] 1 errors occurred while performing .
[ERROR] Error #1: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure


Maven wollte alle Unit- und Integrationstest ausführen und hat wieder einmal MySQL nicht gefunden. Also das Profil ergänzt und auf ein Neues:

#> mvn -Ph2 install eclipse:eclipse


Jetzt kam der Fehler, dass in meinem $TOMCAT_HOME keine WebApp "webapps/manager" existiert. Das stimmte auch, weil ich für andere Tests gerade einen alten Tomcat-5.5 verwendet hatte. Aber wozu braucht Maven einen Tomcat, wenn ich doch eigentlich mit Jetty starten will? Darum kümmere ich mich später. Zuerst habe ich mal das $TOMCAT_HOME auf meinen letzten Tomcat7 umgehängt und kontrolliert, dass der eine manager-WebApp hat.

Und noch mal das Ganze. Diese Mal mit folgendem Fehler:

Caused by: org.apache.maven.plugin.MojoExecutionException: There were test failures : 2/26


Also letzter Versuch - ohne Tests:

#> mvn -Ph2 -DskipTests=true install eclipse:eclipse


Das ging bedeutend schneller und scheint von Erfolg gekrönt zu sein. In den Verzeichnissen "core" und "web" befinden sich jeweils die Dateien ".project" und ".classpath" sowie das Verzeichnis ".settings". Im root-Ordner des Projektes fehlen diese allerdings. Dann schaue ich mir das mal in Eclipse an. Dazu muss in Eclipse auch das Menü "File > Import" aufgerufen werden. Im danach erscheinenden Fenster muss aber jetzt der Punkt "General > Existing Projects into Workspace" ausgewählt werden.



Das muss jeweils für das Web- und das Core-Modul durchgeführt werden.



Im Ergebnis haben wir in dieser Variante statt 3 nur 2 Eclipse-Projekte erstellt. Ob das Projekt aus dem root-Ordner nützlich wäre oder nicht, vermag ich nicht zu sagen. Jedenfalls werden in dieser Variante die "pom.xml"-Dateien nicht von Eclipse angemeckert. Das Ausführen der Tests aus Eclipse funktioniert auch, sofern man über rechte Maustaste auf die pom.xml aus dem Kontxmenü "Run As > Run Configurations ..." oder alternativ "Run As > Maven build ..." auswählt und im Eingabefeld "Profil" dann "h2" einträgt:



In beiden Ausführungsvarianten erstellt Eclipse eine eigene Konfiguration. Aber irgendwie gefiel mir der Import über Variante 1 besser und ich verwende diesen Import. Dann habe ich die Chance herauszufinden, ob das fehlende 3. Projekt einen Nutzen hat. Zumindest komme ich so einfach an die übergeordnete "pom.xml" heran, dessen Module die "pom.xml" aus den Verzeichnissen "core" und "web" sind. Alle Properties sind ausschließlich in der übergeordneten Maven-Projektdatei definiert.

Bevor ich jetzt die eigentliche Erweiterung starte will ich sehen, wie JUnit-Tests ausgeführt werden können. Erster Ansatz ist das direkte Ausführen einer Testklasse in Eclipse. Ich wähle die Klasse "RoleDaoTest" klicke mit der rechten Maustaste drauf und starte aus dem Kontextmenü "Run As > JUnit Test".



Das war nicht von Erfolg gekrönt. Der Test sucht wieder eine MySQL-Verbindung, obwohl AppFuse bei Tests mit DBunit arbeitet. Bleibt wohl nur die Möglichkeit, die Tests über Maven auszuführen. Aber wie sehe ich dann die Testergebnisse? Abhilfe schafft das verwendete Surefire-Plugin. Das erzeugt als Ergebnis XML-Dateien im Verzeichnis "target/surefire-reports". Ein Doppelklick auf eine dieser XML-Dateien öffnet die bekannte JUnit-View von Eclipse und zeigt alle Testergebnisse. Mit einem Klick auf einen Methodennamen springt man mit dem Cursor an die betreffende Stelle im Editor.



Aber ich will ja nicht immer alle Testfälle ausführen. Das Surefire-Manual verrät, um einen einzelnen Testfall oder eine andere Unterauswahl von Tests auszuführen, muss mann das Property "-Dtest=RoleDaoTest" in die Ausführungskonfiguration von Eclipse hinzufügen.



Leider muss man Eclipse-typisch anschließend den Verzeichnisbaum mit F5 aktualisieren, da Eclipse sonst die Änderungen durch Maven nicht mitbekommt. Zusätzlich sollte man vor dem Test-Goal noch das Clean-Goal ausführen, damit man nur die wirklich interessierenden Ergebnisse bekommt.

Jetzt wissend, wie Tests ausgeführt werden können starte ich mit der Testklasse für den DB-Zugriff der Projektverwaltung. Das Tutorial verspricht, dass für einfache CRUD-Operationen gar kein eigenes DAO geschrieben werden muss. Statt dessen wird das direkt vom GenericDao erledigt. Nicht desto trotz möchte ich das testen. Dazu schaffe ich mir ein Spring-Bean "projectDao" in dem die Datei "src/main/resources/applicationContext.xml" entsprechend erweitert wird:



Dabei fallen zwei Dinge auf: Hibernate wird nicht wie im vorigen Post beschrieben als JPA-Provider sondern direkt als OR-Mapper verwendet und zweitens muss eine Modellklasse als Konstruktorparameter existieren. Diese soll natürlich eine Entität sein, damit sie persistiert werden kann. (Die getter und setter spare ich der Übersichtlichkeit wegen aus.)



Jetzt sollte ich ein ProjectDao zur Verfügung haben und müsste es testen können. Dieser Testlauf schlägt mit der Meldung "org.hibernate.MappingException: Unknown entity: org.test.model.Project" fehl. D.h. in der Hibernate-Konfiguration "core/src/main/resources/hibernate.cfg.xml" muss die neue Tabelle explizit angegeben werden. Ein automatischer PackageScan ist nicht eingerichtet.



Mit dieser Erweiterung funktioniert der Test. Jetzt wird ein Manager benötigt, um die Projektverwaltung mit der Web-Oberfläche zu verbinden. Hier verspricht AppFuse ebenfalls die Möglichkeit, für CRUD-Funktionen einen GenericManager zu verwenden. Also muss der Manager ebenso in der Datei "applicationContext.xml" hinzugefügt werden.



Für diesen ProjectManager wird jetzt ein JUnit-Test erstellt.



Die Ausführung klappt auf Anhieb. Jetzt kommt die Oberfläche dran. Dazu muss ich ins Web-Modul wechseln. Und ich sollte mir endlich Gedanken machen, was die Projektverwaltung überhaupt leisten soll: * Alle hinterlegten Projekte müssen aufgelistet werden können. * Neue Projekte müssen angelegt werden können. * Vorhandene Projekte müssen aktualisiert und gelöscht werden können. * Abgrenzung: Aufgrund der wenigen Projektdaten wird auf eine Detailansicht verzichtet.

Also benötige ich erst einmal eine Listenansicht der vorhanden Projekte. Auch hier wird zuerst die Testklasse erstellt. AppFuse stellt dazu wieder eine Basisklasse "BasePageTestCase.java" bereit, die sich um die JSF- und Mock-Umgebung kümmert.



Diese Testklasse kompiliert natürlich noch nicht, da die Klasse "ProjectAction" noch fehlt. Also wird auch diese inkl. der mitgelieferten Basisklasse angelegt.



Erstaunlicherweise schlägt die Testausführung "mvn -Ph2 -Dtest=ProjectActionTest clean test" bzw. mittels Eclipse fehl. Folgender Failure Trace erscheint:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.test.webapp.action.ProjectActionTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.test.webapp.action.ProjectAction org.test.webapp.action.ProjectActionTest.projectAction; nested exception is java.lang.IllegalStateException: No Scope registered for scope 'request'
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287)
 at 
    ...


Nach einiger Suche im Netz bin ich auf folgende Lösung gestoßen. In der Spring-Konfiguration "web/src/main/resources/applicationContext-resources.xml" müssen folgende Zeilen ergänzt werden:



Und jetzt funktioniert der Test. Die initialen Datensätze werden in AppFuse automatisch mittels DbUnit geladen. Es musste nur die Datei "web/src/test/resources/sample-data.xml" erweitert werden:



Damit können die anderen Anforderungen umgesetzt werden: Neu anlegen, bearbeiten und löschen. Natürlich zuerst der Test:



Und danach die Implementierung.



Der generische Manager bot zum Holen der Projekte "nur" die Methoden "getAll()" und "search()" an. Da ich alles Holen etwas überdimensioniert fand, probierte ich die Suchmethode. Leider führte das zu dem Fehler "java.lang.IllegalArgumentException: HSEARCH000109: org.test.model.Project is not an indexed type". Also musste entweder "Project" für die Suche indiziert oder ein anderer Weg gefunden werden. Ich entschied mich für letzteres und erweiterte den generischen Manager um die Methode "List getByName(String name)". Deren Implementierung setzt voraus, dass es in der Modellklasse eine NamedQuery namens "findByName" gibt:



Diese Methode konnte jetzt in der ProjectAction verwendet werden.



Und jetzt funktionierten auch die Tests. Damit geht es weiter zur GUI. Zuerst muss eine XHTML-Datei für die Auflistung aller Projekte erstellt werden. In dem JSF-Tutorial von AppFuse wird das RessourceBundle über das Tag "f:loadBundle" geholt. Da ich das aber nicht auf jeder Seite neu machen möchte und es laut IRIAN zu Problemen bei partiellem Page-Rendering mit Ajax kommen kann, erweitere ich erst einmal die faces-config.xml zusätzlich zum bereits konfigurierten "message-bundle". Der Unterschied beider Bundles wird auf Stackoverflow gut erläutert.



Im Tutorial wird innerhalb der list-XHTML eine explizite Navigation für den Add-Button verwendet, d.h. die aufzurufende Action wird erst in der faces-config.xml als "navigation-rule" definiert. Ich finde das ein wenig umständlich und mache das implizit im Tag "h:commandButton".



Damit die Liste jetzt auch über das Menü aufgerufen werden kann, sind zwei Schritte notwendig. In der Datei "web/src/main/webapp/WEB-INF/menu-config.xml" muss die Zeile "<Menu name="ProjectMenu" title="menu.viewProjects" page="/projectList"/>" eingefügt werden. Damit wird die Verbindung zur oben erstellten XHTML-Datei geschaffen.



Als Zweites muss in der Datei "web/src/main/webapp/common/menu.jsp" der Menüeintrag "" ergänzt werden. Über die Position dieses Eintrag bestimmt sich auch die Position im Menü.



Will man auch noch auf der Startseite einen Link zu den Projekten haben, dann auch die "mainMenu.xhtml" um einen CommandLink erweitert werden.



Mit dem Kommando "mvn -Ph2 jetty:run" wird AppFuse gestartet. Nach dem Login werden soweohl der Link auf der Startseite sowie der neue Menüeintrag präsentiert. Mit einem Klick auf das Menü wird das Testprojekt angezeigt, dass im Core-Projekt mittels DBunit erstellt wurde. Sollen andere Daten angezeigt werden, so kann man die Daten "web/src/test/resources/sample-data.xml" erweitern. Diese Daten werden aber nur angezogen, wenn das Prod-Profil ausgewählt wird. Der Startaufruf sieht dann so aus "mvn -Ph2,prod jetty:run".

Jetzt fehlt noch die Bearbeitenmaske und die Sicherheit, d.h. wer darf die Projekte sehen und pflegen. Da dieser Post schon etwas länglicher geworden ist, hebe ich mir das für den kommenden Post auf. Hinzu kommt eine Untersuchung der Sicherheit einer AppFuse-Anwendung unter Berücksichtigung der "OWASP Top 10 2013 vulnerabilities".

Keine Kommentare:

Kommentar veröffentlichen