Maven 3.5 + maven.apache.org + Maven-Multimodulprojekte + andere TechDocs + Apache Maven ist (ähnlich wie Ant und Gradle) ein leistungsfähiges Werkzeug, um viele in der Softwareentwicklung immer wieder anfallende Prozeduren zu automatisieren und zu vereinfachen. Es wird manchmal als "Build Management System" bezeichnet und ist Teil vom "Software Configuration Management (SCM)". Während Ant eher kommandoorientiert arbeitet, ist Maven eher strategisch orientiert, realisiert mehr Abstraktionen, wird deklarativer gesteuert, berücksichtigt Abhängigkeiten besser und ist besonders für aufwändigere Multimodulprojekte geeignet. Im Folgenden wird nur auf Maven 3.5 eingegangen. Informationen zu Vorgängerversionen finden Sie unter Maven 3.3.9, Maven 2.2.1 und Maven 1. Inhalt 1. Vergleich mit Ant 2. Einige wichtige Begriffe zu Maven 3. Installation von Maven 4. Maven-Hello-World-Projekt 5. Ausführbare Jar-Datei 6. Ausführbare Jar-Datei inklusive Abhängigkeiten mit dem Assembly Plugin 7. Maven-Webapp-Projekt mit Jetty 8. Maven-Webapp mit Properties im Manifest 9. Maven-Properties-Projekt mit Ressourcen-Filterung 10. Erweiterung um Maven-Profile 11. Generierung von HTML, PDF, RTF etc. mit DocBook und dem Docbkx Maven Plugin 12. Java-Codegenerierung aus einem XSD-Schema mit JAXB 13. REST-Webservice mit JAX-RS und SOAP-Webservice mit JAX-WS 14. FTP-Client und embedded FTP-Server im JUnit-Test 15. Einfaches Multimodulprojekt 16. Multimodulprojekt mit Plugin-Management und Dependency-Management 17. Multimodulprojekt mit Corporate POM 18. Site Report um Project Reports zur Sourcecodeanalyse erweitern Vorbemerkungen zu Site Project Reports, Site Project Reports mit Maven 3.5 19. Sourcecodeanalyse mit SonarQube 20. Erstellung von Javadoc- und Source-Archiven 21. Signaturen erzeugen 22. Java-Programme (und andere Programme) mit dem exec-maven-plugin ausführen 23. Guice und AOP 24. Eigenes Maven-Plugin (Mojo) Mojo mit Parameter, Mojo-PluginContext, Mojo-JUnit-Test 25. Test-Jar 26. JUnit-Tests mit JUnit 5 27. Parallelisierte Testausführung mit TestNG 28. JMockit 29. Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit 30. Automatisierter Integrationstest mit Jetty und JWebUnit 31. Automatisierter Integrationstest mit Selenium 32. Automatisierter Integrationstest mit Fit 33. Automatisierter HTML-Akzeptanztest mit Jetty und Fit 34. Automatisierter Integrationstest mit Cargo für WebLogic, JBoss, WildFly und Tomcat 35. Integrationstests mit dem Maven Failsafe Plugin 36. Webapp mit Wicket 37. Vert.x-HelloWorld 38. OSGi-Bundle mit dem Maven-Bundle-Plugin 39. Suche mit Lucene 40. Java-EE-Anwendungen (Servlet, JSP, JSF, JPA, EJB3) 41. Maven mit Docker 42. Maven-Repository Zugang zu Internet-Repositories über Firmen-Proxy, Team-Repository über freigegebenes Verzeichnis, Repository-Manager, Installation und Konfiguration des Repository-Managers Archiva, Distribution-Deployment mit dem Repository-Manager Archiva, Repository-Manager Nexus 43. Continuous Integration mit Jenkins / Hudson und Maven 3 Vergleich mit Ant Sowohl Ant als auch Maven sind leistungsfähige Werkzeuge, um viele in der Softwareentwicklung immer wieder anfallende Prozeduren zu automatisieren und zu vereinfachen. Beide haben ihre bevorzugten Einsatzbereiche. Vorteile von Ant: Ant ist leichter einzurichten und zu erlernen Für kleine einfache Projekte kann Ant einfacher zu handhaben sein Meistens reicht eine einzige Steuerdatei ('build.xml'), mit der über verschiedene 'Targets' unterschiedliche Ergebnisse erzeugt werden können Für Ant gibt es in vielen Entwicklungsumgebungen (z.B. Eclipse) gute Unterstützung Vorteile von Maven: Fördert Standardisierungen, 'Convention over Configuration' und die Realisierung von 'Best Practices' Fördert Wiederverwendung, einheitliche Verzeichnisstrukturen und einheitliche Organisation der Abhängigkeiten Vereinfacht das Handling bei vielen Abhängigkeiten und benötigten Zusatz-Artefakten Vereinfacht die Verwaltung von Multimodulprojekten Fördert durch die Definition der Goals in Plug-ins die Arbeitsteilung zwischen Konfigurationsmanagement und Softwareentwicklung Erzeugt nicht nur Javadoc, sondern auch weitere hilfreiche Dokumentationen Bietet Unterstützung und Anbindung für weitere Anwendungen (Fehlerverfolgung, Reporting-Systeme, Integrationssysteme) Einige wichtige Begriffe zu Maven Maven Maven ist ein Tool für das Projektmanagement von Softwareentwicklungsprojekten. Dabei werden Patterns und Best Practices eingesetzt, um die Produktivität und Wiederverwendbarkeit zu erhöhen. Im Fokus stehen Builds, Dokumentation, Reporting, Abhängigkeiten, SCM (Software Configuration Management), Releases und Distribution. Siehe hierzu auch: What is Maven? Doku zu Maven Doku zu Maven finden Sie zum Beispiel unter: http://maven.apache.org/guides http://maven.apache.org/guides/getting-started http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html http://maven.apache.org/plugins maven-definitive-guide_de.pdf Buch: Martin Spiller, Maven 2 Buch: Kai Uwe Bachmann, Maven 2 Build Lifecycle Maven beinhaltet als wichtiges Konzept genau definierte Build Lifecycles. Die drei Standard-Lifecycle sind: clean, default und site. Build Lifecycles bestehen aus Lifecycle-Phasen. Lifecycle-Phasen Build Lifecycles sind unterteilt in Phasen. Wichtige Phasen sind beispielsweise: clean, compile, test, package, integration-test, verify, install, deploy und site. Die Phasen werden in einer bestimmten Reihenfolge durchlaufen. Wird im Kommandozeilenfenster zum Beispiel das Kommando "mvn package" eingegeben, dann werden alle vorhergehenden Phasen (z.B. u.a. compile und test) und die angegebene Phase (hier package) ausgeführt, aber nicht die nachfolgenden Phasen. In den Lifecycle-Phasen werden jeweils bestimmte Plugin-Goals ausgeführt. Maven-Plugin Maven-Plugins sind Bibliotheken, die thematisch zusammengehörende Goals implementieren. Wichtige Plugins sind zum Beispiel: archetype, compiler, surefire, jar, war, install, deploy, site und dependency. Eine Liste vieler weiterer Maven-Plugins finden Sie hier. Hinweise zur Plugin-Konfiguration finden Sie hier. Goal Goals sind von Maven-Plugins angebotene ausführbare Kommandos. Goals können bestimmten Lifecycle-Phasen zugeordnet werden und werden dann automatisch zum richtigen Zeitpunkt aufgerufen (z.B. "compiler:compile"). Viele Goals können auch direkt über die Kommandozeile aufgerufen werden. Dabei wird getrennt durch einen Doppelpunkt (":") der Plugin-Name und der Goal-Name angegeben, zum Beispiel so: "mvn archetype:generate". Maven-Goals sind vergleichbar mit Ant-Tasks. Archetype und Standard Directory Layout Ein Maven-Archetype ist ein Projekt-Template. Über das archetype-Plugin können Standard-Directory-Layouts und Projektvorlagen für verschiedene Projekttypen erstellt werden (z.B. quickstart, webapp und mojo). Dabei wird auch eine vorläufige pom.xml angelegt. Artefakt Mit Artefakt sind im Zusammenhang mit Maven Arbeitsergebnisse gemeint. MavenProjekte haben in der Regel ein Hauptergebnis-Artefakt, oft eine jar-, war- oder earDatei. Aber auch andere Ergebnisse wie Projektdokumentationen können als Artefakt bezeichnet werden. POM und pom.xml POM bedeutet "Project Object Model" und wird repräsentiert durch eine XML-Datei, üblicherweise die pom.xml, die als zentrale Projektbeschreibungs- und -steuerungsdatei Meta-Daten zum Projekt enthält, unterteilt in die fünf Bereiche Koordinaten, Projektbeziehungen, Projektinformationen, Projekteinstellungen und Projektumgebung. Wichtige Einträge sind zum Beispiel: groupId, artifactId, version, packaging, build, dependencies, profiles und properties. Die möglichen Elemente sind hier aufgeführt. Weitere Informationen finden Sie hier und hier. Koordinaten Mit "Koordinaten" werden die fünf ein Artefakt identifizierenden Informationsbestandteile bezeichnet. Oft sind vor allem die drei wichtigsten gemeint: groupId, artifactId und version. Zu den Koordinaten gehören aber auch der optionale classifier und der durch das packaging definierte type, da auch hierüber verschiedene Artefakte differenziert werden. groupId Die groupId ist eine Gruppierungsbezeichnung (ähnlich den Java-Package-Namen). Normalerweise wird der umgekehrte Domainname der Firma verwendet, eventuell ergänzt um den Abteilungs- und/oder Projektnamen, zum Beispiel "de.meinefirma.meinprojekt". Im Maven-Repository repräsentieren die durch Punkte (".") getrennte Bestandteile Unterverzeichnisse, ähnlich wie bei Package-Namen in Sourcecodeverzeichnissen. artifactId Die artifactId ist der Name des Hauptergebnis-Artefakts dieses Projekts. Der vollständige Dateiname des Hauptergebnis-Artefakts wird meistens gebildet aus: "<artifactId>-<version>.<type>", also zum Beispiel "junit-3.8.1.jar" oder "MeineApp-1.0-SNAPSHOT.jar". Falls ein classifier definiert ist, wird auch dieser noch dem Dateinamen hinzugefügt (hinter der Versionsbezeichnung), zum Beispiel so: "testng-5.11-jdk15.jar" und "MeineEjb-1.0-client.jar". version Die version des Projekts, typischerweise im Format: "<Major>.<Minor>.<Bugfix><Qualifier>-<Buildnumber>" (wobei die vorderen Teile bei Versionsvergleichen numerisch gewertet werden (1.10 ist aktueller als 1.9) und die hinteren Teile optional sind). Lautet der Qualifier "SNAPSHOT", bedeutet dies, dass sich das Projekt noch in Entwicklung befindet und die Versionsnummer nicht bei jedem Build hochgezählt wird (was bei Versionsvergleichen eine Rolle spielt). Es können auch Versionsbereiche angegeben werden, beispielsweise: 4.7 bedeutet: Version 4.7, [3.8.1,) bedeutet: alle Versionen ab mindestens 3.8.1, [3.8.1,4.7) bedeutet: alle Versionen ab 3.8.1 bis ausschließlich 4.7, [3.8.1,4.7] bedeutet: alle Versionen ab 3.8.1 bis einschließlich 4.7 classifier Einige Ergebnis-Artefakte können in unterschiedlichen Ausführungen vorliegen, die im Dateinamen durch den classifier unterschieden werden. Zum Beispiel das Assembly Plugin erstellt ein zusätzliches durch einen classifier unterschiedenes ErgebnisArtefakt (z.B. MeineApp-1.0-jar-with-dependencies.jar). TestNG gibt es für verschiedene Java-Versionen (z.B. testng-5.11-jdk15.jar). Oder beim Build eines EJB-Moduls wird eine MeineEjb-1.0.jar für das Deployment zum App-Server und per generateClient eine MeineEjb-1.0-client.jar für die Client-Applikation erstellt. packaging Das packaging definiert den Typ des Hauptergebnis-Artefakts, zum Beispiel jar, war, ear, pom oder maven-plugin. profile Ein profile definiert Voreinstellungen. Über Umgebungsbedingungen, Kommandozeilenoptionen oder andere Parameter können Profile aktiviert werden. Siehe auch die Beispiele Erweiterung um Maven-Profile und Integrationstest mit Cargo sowie Introduction to Build Profiles. property Ein property definiert ein Name/Wert-Paar. Siehe auch das Beispiel Maven-PropertiesProjekt. dependency Abhängigkeiten werden als dependency eingetragen (transitive Abhängigkeiten brauchen nicht eingetragen zu werden). Siehe auch Dependency Mechanism und das Beispiel zum Dependency-Management. Die eingetragenen Artefakte werden beim Build im Repository benötigt. Über das scope-Attribut wird gesteuert, für welche Buildprozessphase die Abhängigkeit benötigt wird. scope Bei dependency-Einträgen kann über das scope-Attribut eine Art Sichtbarkeit definiert werden, nämlich in welchen Phasen des Buildprozesses die Abhängigkeit benötigt wird. Nur in den spezifizierten Phasen werden die eingetragenen Module dem CLASSPATH hinzugefügt. Die wichtigsten Ausprägungen sind: compile (Compilierphase), provided (ähnlich wie compile, aber Artefakt wird zur Laufzeit von Laufzeitumgebung (z.B. JEEContainer) bereitgestellt), runtime (Laufzeit) und test (Tests). Repository Es wird unterschieden zwischen dem lokalen Maven-Repository und RemoteRepositories. Das lokale Maven-Repository dient hauptsächlich als Cache-Zwischenspeicher für Remote-Repositories und als Austauschverzeichnis für lokal installierte Artefakte. Von Remote-Repositories werden benötigte und noch nicht im lokalen Repository vorhandene Artefakte geladen. Zusätzlich zum Standard-Repository (meistens http://central.maven.org/maven2) können auch zusätzliche Repositories definiert werden. Siehe auch Introduction to Repositories und die Liste einiger häufig verwendeter Artefakte. Site Mit Site ist eine Art Website gemeint, also eine Zusammenstellung von Webseiten, die als Projektdokumentation dient. Siehe auch das Beispiel Site Report um Project Reports zur Sourcecodeanalyse erweitern. Mojo In Java programmierte Maven-Plugins bestehen aus Mojos. Ein Mojo ("Maven (plain) old Java Object") ist eine Java-Klasse die das Interface org.apache.maven.plugin.Mojo implementiert (oder org.apache.maven.plugin.AbstractMojo erweitert) und damit ein Plugin-Goal realisiert. Siehe auch das Beispiel Eigenes Maven-Plugin sowie guide-java-plugin-development, maven-plugin-api und mojo-api-specification. Eindeutigkeit Maven stellt einen großen Fortschritt für die Eindeutigkeit und Reproduzierbarkeit von Build-Ergebnissen dar. Aber auch Maven hat Lücken: Wenn in der pom.xml für Plugins keine Versionsnummer angegeben wird, können bei verschiedenen Builds (anderer Zeitpunkt oder anderer Rechner) unterschiedliche Ergebisse entstehen, je nachdem, welche Plugin-Version sich entweder zufällig im jeweiligen lokalen Repository befindet oder in der jeweiligen Maven-Super-POM vordefiniert ist (bedenken Sie, dass in der Maven-Super-POM nur die wichtigsten Plugins vordefiniert sind). mvn-Kommandos Die Aufruf-Syntax der im Kommandozeilenfenster aufrufbaren mvn-Kommandos lautet: "mvn [options] [<goal(s)>] [<phase(s)>]". Es können also übergeben werden (sowohl einzeln als auch kombiniert): Mit einem Minuszeichen ("-") beginnende Optionen (z.B. -D, -P, -B, -X, -e, siehe "mvn -h") Einzelne Goals (bestehend aus dem Plugin-Namen und dem Goal-Namen, getrennt durch einen Doppelpunkt (":"), z.B. archetype:generate und jetty:run) Lifecycle-Phasen (z.B. clean, compile, test, package, integration-test, verify, install, deploy und site) Einige wichtige oder häufig benutzte Maven-Kommandos sind: Kommando Bedeutung mvn -v Anzeige der Version von Maven mvn -h Hilfe zu den Kommandozeilenoptionen von Maven mvn help:help Hilfe zur aktuellen Build-Umgebung mvn help:describe -Dplugin=... Hilfe zu bestimmten Plugins (und mit -Ddetail auch zu Goals) mvn help:effective-settings Anzeige der aktuellen projektübergreifenden SettingsEinstellungen mvn help:effective-pom Anzeige der aktuell resultierenden projektbezogenen POM mvn help:active-profiles Anzeige der aktiven Profile (aber ohne geerbte Profile) mvn help:all-profiles Anzeige aller Profile (aber ohne geerbte Profile) mvn help:evaluate -Dexpression=... Auflösung von Maven-Ausdrücken, z.B. in Batchdatei: for /f "delims=" %%a in ('mvn help:evaluate Dexpression^=settings.localRepository ^| findstr /V [') do set _mvnrepo=%%a Auch Server-Props können abgefragt werden, z.B.: mvn help:evaluate Dexpression=settings.servers[0].username mvn dependency:tree -Dverbose Übersicht zum Abhängigkeitsbaum; weitere Goals: analyze, build-classpath, resolveplugins, go-offline, copy-dependencies mvn dependency:sources Zusätzlich zu den Libs und POMs auch die Sourcen downloaden, falls vorhanden mvn dependency:resolve -Dclassifier=javadoc Zusätzlich zu den Libs und POMs auch die JavadocDateien downloaden mvn clean Löschen aller erzeugten Artefakte und des targetVerzeichnisses mvn compile Kompilierung mvn test Ausführen der Komponententests (z.B. JUnit-Tests) mvn package Build und Erzeugung der Ergebnis-Artefakte mvn verify Ausführen der Integrationstests (nicht "mvn integrationtest", damit auch die "post-integration-test"-Phase ausgeführt wird) mvn install Kopieren des Ergebnis-Artefakts ins lokale MavenRepository mvn deploy Kopieren des Ergebnis-Artefakts ins Remote Repository mvn site Erzeugen der Projektdokumentation mvn archetype:generate Projektstruktur erzeugen mvn test -Dtest=MeinTest Nur eine einzige Testklasse ausführen mvn test -Dtest=MeinAbcTest,MeinXyzTest Mehrere Testklassen in definierter Reihenfolge ausführen mvn package -DskipTests Test-Klassen werden kompiliert, aber nicht ausgeführt mvn package -Dmaven.test.skip=true Test-Klassen werden nicht kompiliert und nicht ausgeführt mvn jetty:run Startet den Jetty-Server und führt das Webapp-Projekt aus Installation von Maven Maven-Basisinstallation unter Linux Siehe: Installation von Maven unter Ubuntu-Linux. Maven-Basisinstallation unter Windows 1. Installieren Sie ein aktuelles Java SE JDK. 2. Downloaden Sie Maven 3.5 von http://maven.apache.org (z.B. apache-maven-3.5.0bin.zip). 3. Entzippen Sie die Maven-.zip-Datei in ein beliebiges Verzeichnis, zum Beispiel nach D:\Tools, und umbenennen Sie das resultierende Verzeichnis, zum Beispiel nach D:\Tools\Maven3. 4. Setzen Sie folgende Umgebungsvariablen (Environment-Variablen) (passen Sie die Pfadangaben an Ihre Java- und Maven-Verzeichnisse an) (unter Windows 7 und Vista: 'WindowsTaste + PauseTaste' | 'Erweiterte Systemeinstellungen' | Reiter 'Erweitert' | 'Umgebungsvariablen...'; unter Windows XP: 'WindowsTaste + PauseTaste' | 'Erweitert' | 'Umgebungsvariablen'; unter Linux: siehe linux.htm#Umgebungsvariablen): Benutzervariablen setzen: JAVA_HOME C:\Program Files\Java\jdk1.8 M2_HOME D:\Tools\Maven3 MAVEN_OPTS -Xms256m -Xmx512m PATH %JAVA_HOME%\bin;%M2_HOME%\bin 5. Bitte beachten Sie, dass Sie nicht die PATH-Systemvariable um die "%...%\bin"-Pfade erweitern, sondern eine PATH-Benutzervariable anlegen (sonst funktioniert die "%...%"Syntax nicht). 6. Jetzt müssen folgende Kommandos ein erfolgreiches Ergebnis liefern. Öffnen Sie (in Windows mit 'Windows-Taste' + 'R' und 'cmd') ein neues Kommandozeilenfenster (damit die Änderung der Umgebungsvariablen wirksam wird) und geben Sie ein: set set JAVA_HOME set M2_HOME path java -version javac -version javac -help mvn -v mvn -h 7. Weitere Installationshinweise finden Sie hier. Konfiguration 1. Fehlende Bibliotheken und Plug-ins lädt Maven automatisch zum Beispiel von http://central.maven.org/maven2. Beachten Sie auch die Hinweise zu Sun JARs. 2. Wenn Sie die Voreinstellung nicht ändern, wird als lokales Maven-RepositoryVerzeichnis je nach Betriebssystem zum Beispiel C:\Users\%USERNAME%\.m2\repository, C:\Dokumente und Einstellungen\%USERNAME%\.m2\repository, <userhome>/.m2/repository oder ~/.m2/repository verwendet. 3. Für alle Projekte geltende Voreinstellungen (z.B. Zugangsdaten zu CVS- oder Subversion-Servern) werden in settings.xml-Dateien definiert: %M2_HOME%\conf\settings.xml für systemweite Einstellungen, %USERPROFILE%\.m2\settings.xml für benutzerspezifische Einstellungen. Projektabhängige Einstellungen befinden sich in den jeweiligen pom.xml-Dateien. 4. Falls Sie spezielle Konfigurationen benötigen, wie zum Beispiel HTTP-Proxy-Server, Repository-Manager oder Server-Authentifizierung, lesen Sie bitte Maven-Repository, Configuring Maven, Settings Reference, Technical Settings Descriptor und Password Encryption. 5. Falls Sie eine Fehlermeldung ähnlich zu folgender erhalten: Failed to invoke Maven build. Error configuring command-line. Reason: Maven executable not found at: ...\bin\mvn.bat Dann prüfen Sie, ob sich in Ihrem Maven-bin-Verzeichnis eine mvn.bat-Datei befindet. Falls nicht, führen Sie aus: cd /D %M2_HOME%\bin dir mvn.* if not exist mvn.bat copy mvn.cmd mvn.bat Maven in Firmen 1. In Firmen sind in der systemweiten settings.xml meistens zumindest Einstellungen zum localRepository und eines mirrors erforderlich, beispielsweise so: 2. 3. <settings> 4. <localRepository>D:\Tools\Maven3-Repo</localRepository> 5. <mirrors> 6. <mirror> 7. <id>MeineMirrorId</id> 8. <mirrorOf>central</mirrorOf> 9. <url>http://firma.repository.server:port/repo/path</url> 10. </mirror> 11. </mirrors> 12. </settings> Das localRepository-Verzeichnis sollte in Firmen nicht im <user-home>-Verzeichnis liegen, weil in Firmen häufig das <user-home>-Verzeichnis bei jedem PCHerunterfahren gesichert und beim Starten wiederhergestellt wird, was unnötig BackupSpeicherplatz kosten und diese Vorgänge verlangsamen würde. Ein mirror-Server muss oft definiert werden, damit Artefakte nicht vom Internet, sondern von einem firmeninternen Server geladen werden, weil in Firmen eventuell restriktivere Sicherheitsvorschriften gelten oder weil vielleicht nur bestimmte Libs und Versionen zur Verwendung freigegeben sind. Sehen Sie sich die eingestellten Settings an über: mvn help:effective-settings Weiteres zur settings.xml und zum Umgang mit Firmen-Proxies und RepositoryManager finden Sie weiter unten unter Maven-Repository. Maven mit Eclipse mit M2E (empfohlen) 1. Wenn Sie Eclipse verwenden, sollten Sie normalerweise nicht das maven-eclipseplugin verwenden, sondern stattdessen Maven-Projekte mit dem M2Eclipse-Plugin importieren. Siehe hierzu: Das M2Eclipse-Plugin (m2e) zur Zusammenarbeit von Eclipse mit Maven. 2. Um in Eclipse ein Projektmodul aus CVS, Subversion oder Mercurial (hg) zu laden (oder upzudaten), verfahren Sie wie beschrieben unter CVS, Subversion bzw. Mercurial (hg). 3. Falls Sie Maven-Multimodulprojekte in Eclipse bearbeiten wollen, sehen Sie sich an: maven-multiproj.htm#Maven-Multimodulprojekt-in-Eclipse und Using maven-eclipseplugin in multi-module projects with WTP. Maven mit Eclipse ohne M2E (nicht empfohlen) 1. Wenn Sie unbedingt das maven-eclipse-plugin verwenden wollen, obwohl es nicht empfohlen ist: Geben Sie das lokale Maven-Repository-Verzeichnis über die Eclipse-Variable "M2_REPO" bekannt. Dies geht am einfachsten über das Maven-Goal eclipse:configure-workspace über folgenden Kommandozeilenbefehl (bitte EclipseWorkspace-Pfad "D:\MeinWorkspace" anpassen): mvn -Declipse.workspace=D:\MeinWorkspace eclipse:configure-workspace Alternativ können Sie in Eclipse über 'Window' | 'Preferences' | '[+] Java' | '[+] Build Path' | 'Classpath Variables' | 'New...' eingeben (bitte Pfad anpassen): Eclipse Classpath Variables: M2_REPO C:\Users\%USERNAME%\.m2\repository Bzw. je nach Konfiguration: M2_REPO D:\Tools\Maven3-Repo Anschließend müssen Sie Eclipse neu starten. 2. Bei neuen Maven-Projekten oder nach Updates auf bestehenden Maven-Projekten müssen Sie als Erstes die Eclipse-Projektdateien erstellen bzw. aktualisieren. Falls Sie nicht das M2Eclipse-Plugin verwenden, erfolgt das über "mvn eclipse:..."Kommandos: mvn eclipse:eclipse Falls Sie nachträglich Konfigurationsänderungen durchführen, kann folgendes Kommando "aufräumen": mvn eclipse:clean eclipse:eclipse 3. Falls das Projekt noch nicht in Eclipse existiert: Importieren Sie es über: 'File' | 'Import...' | 'Existing Projects into Workspace' | 'Select root directory'. 4. Wichtig: Nach dem "mvn eclipse:eclipse"-Kommando und auch nach jeder anderen Änderung, die Sie außerhalb von Eclipse vornehmen, müssen Sie in Eclipse im 'Package Explorer' die Projektmodule markieren und mit 'F5' einen 'Refresh' ausführen. Maven mit Java 9 und Jigsaw 1. Falls Sie mit Java 9 und Jigsaw arbeiten wollen, beachten Sie folgende Hinweise: Für Jigsaw-Module wird manchmal eine vom üblichen Maven-Standard abweichende Verzeichnisstruktur empfohlen, siehe hierzu: Verzeichnisstruktur für Java-9-JigsawModule. Falls Sie mit Maven JUnit-Tests ausführen wollen: Im Juli 2017 war dies mit dem Maven Compiler Plugin in der Version 3.6.1 auf Grund eines Fehlers nicht möglich, siehe hierzu: Jigsaw-Dep-Demo mit Maven und Maven Compiler Plugin: MCOMPILER-294: test-compile broken due to -Xmodule removal in jdk-ea167. Maven-Hello-World-Projekt 1. Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace), erzeugen Sie mit dem Mavenarchetype-Plugin eine einfache Standard-Directory-Struktur und sehen Sie sich diese an (das mvn-Kommando in einer Zeile): cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnHelloApp cd MvnHelloApp tree /F Sie erhalten: [\MeinWorkspace\MvnHelloApp] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- App.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml 2. Um den Build durchzuführen und das entstandene Programm auszuführen, geben Sie im Kommandozeilenfenster ein: cd \MeinWorkspace\MvnHelloApp mvn package java -cp target/MvnHelloApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App Sie erhalten: Hello World! 3. Sehen Sie nach, was im target-Verzeichnis entstanden ist und was in der resultierenden Jar-Datei enthalten ist: cd \MeinWorkspace\MvnHelloApp tree /F jar tvf target/MvnHelloApp-1.0-SNAPSHOT.jar 4. Fügen Sie in App.java etwas Sinnvolles ein und testen Sie dies in AppTest.java. 5. Folgeaufrufe von "mvn package" gehen wesentlich schneller, weil jetzt die benötigten Maven-Plugins im lokalen Maven-Repository vorhanden sind. Ausführbare Jar-Datei Im letzten Beispiel wurde direkt die kompilierte Klasse App.class ausgeführt. Im folgenden Beispiel wird das letzte Beispiel so erweitert, dass die kompilierten Klassen mit dem Maven JAR Plugin zu einem ausführbaren Jar-Archiv zusammengefasst werden, inklusive einer automatisch generierten MANIFEST.MF mit geignetem Main-Class-Eintrag. Falls Sie zusätzlich Libs einbinden wollen, sehen Sie sich die Beispiele zum maven-assemblyplugin und zum maven-shade-plugin an. 1. Ersetzen Sie im MvnHelloApp-Projektverzeichnis den Inhalt der pom.xml durch: 2. 3. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5. <modelVersion>4.0.0</modelVersion> 6. <groupId>de.meinefirma.meinprojekt</groupId> 7. <artifactId>MvnHelloApp</artifactId> 8. <version>1.0-SNAPSHOT</version> 9. <packaging>jar</packaging> 10. <name>MvnHelloApp</name> 11. <build> 12. <plugins> 13. <plugin> 14. <groupId>org.apache.maven.plugins</groupId> 15. <artifactId>maven-jar-plugin</artifactId> 16. <version>3.0.2</version> 17. <configuration> 18. <archive> 19. <manifest> 20. <mainClass>de.meinefirma.meinprojekt.App</mainClass> 21. <addClasspath>true</addClasspath> 22. </manifest> 23. </archive> 24. </configuration> 25. </plugin> 26. </plugins> 27. </build> 28. <dependencies> 29. <dependency> 30. <groupId>junit</groupId> 31. <artifactId>junit</artifactId> 32. <version>4.12</version> 33. <scope>test</scope> 34. </dependency> 35. </dependencies> 36. </project> 37. Führen Sie die Jar-Datei im Kommandozeilenfenster aus: cd \MeinWorkspace\MvnHelloApp mvn package java -jar target/MvnHelloApp-1.0-SNAPSHOT.jar Sie erhalten wieder: Hello World! 38. Sie können auch weiterhin die App.class direkt ausführen (wie im obigen Beispiel): java -cp target/MvnHelloApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App 39. Sehen Sie sich die generierte MANIFEST.MF mit dem Main-Class-Eintrag an: cd \MeinWorkspace\MvnHelloApp jar xvf target/MvnHelloApp-1.0-SNAPSHOT.jar META-INF/MANIFEST.MF type META-INF\MANIFEST.MF Sie enthält die Zeile: Main-Class: de.meinefirma.meinprojekt.App 40. Übrigens kann das maven-jar-plugin noch mehr, beispielsweise können Sie mit dem test-jar-Goal Testartefakte auch in anderen Modulen nutzen, siehe Test-Jar. Sie können auch mehrere verschiedene Artifakte mit einem Aufruf erzeugen, siehe RESTClient mit REST-JUnit-Test. 41. Falls Sie die Attribute in der MANIFEST.MF zur Laufzeit auslesen wollen, ersetzen Sie den Inhalt der App.java durch: 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. package de.meinefirma.meinprojekt; import java.io.File; import java.util.jar.*; public class App { public static void main( String[] args ) throws Exception { System.out.println( "\nManifest-Attribute:\n" + getJarManifestAttributes( App.class ).entrySet() ); 53. } 54. 55. public static Attributes getJarManifestAttributes( Class<?> clazz ) throws Exception 56. { 57. JarFile jarFile = new JarFile( new File( clazz.getProtectionDomain().getCodeSource().getLocation().toURI() ) ); 58. Manifest manifest = jarFile.getManifest(); 59. jarFile.close(); 60. return manifest.getMainAttributes(); 61. } 62. } Und führen Sie aus: mvn package java -jar target/MvnHelloApp-1.0-SNAPSHOT.jar Ausführbare Jar-Datei inklusive Abhängigkeiten mit dem Assembly Plugin Im folgenden Beispiel besteht die Anwendung aus einer Klasse, die eine zusätzliche .jarBibliothek benötigt. Die kompilierten Klassen, die benötigten .jar-Libs und eine automatisch generierte MANIFEST.MF werden mit dem Maven Assembly Plugin in ein ausführbares "Fat-Jar" gepackt. Alternativ zum maven-assembly-plugin wird mittlerweile häufig das maven-shade-plugin bevorzugt. Siehe hierzu das Beispiel Vert.x-HelloWorld. 1. Starten Sie ein neues Projekt: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnJarMitLibs cd MvnJarMitLibs 2. Ersetzen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis den Inhalt von App.java durch: 3. 4. package de.meinefirma.meinprojekt; 5. 6. import org.apache.log4j.Logger; 7. 8. public class App 9. { 10. private static final Logger LOGGER = Logger.getRootLogger(); 11. 12. public static void main( String[] args ) 13. { 14. LOGGER.info( "---- Hallo Logger! ----" ); 15. } 16. } Die Anwendung verwendet den Log4j-Logger. Falls Sie Log4j nicht kennen: Sehen Sie sich java-log4j.htm an. 17. Ersetzen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis den Inhalt von AppTest.java durch: 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. package de.meinefirma.meinprojekt; import junit.framework.TestCase; public class AppTest extends TestCase { public void testApp() { App.main( null ); } } Erzeugen Sie im src\main-Verzeichnis das Unterverzeichnis resources folgende Log4j-Konfigurationsdatei: log4j.properties und darin 31. 32. log4j.rootLogger=DEBUG, MeinConsoleAppender 33. log4j.appender.MeinConsoleAppender=org.apache.log4j.ConsoleAppender 34. log4j.appender.MeinConsoleAppender.layout=org.apache.log4j.PatternLayo ut 35. log4j.appender.MeinConsoleAppender.layout.ConversionPattern=%d{ISO8601 } %-5p [%t] %c: %m%n 36. Die Abhängigkeit zur log4j-1.2.17.jar muss in die pom.xml eingetragen werden. Ersetzen Sie im MvnJarMitLibs-Projektverzeichnis den Inhalt der pom.xml durch: 37. 38. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 39. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 40. <modelVersion>4.0.0</modelVersion> 41. <groupId>de.meinefirma.meinprojekt</groupId> 42. <artifactId>MvnJarMitLibs</artifactId> 43. <version>1.0-SNAPSHOT</version> 44. <packaging>jar</packaging> 45. <name>MvnJarMitLibs</name> 46. <dependencies> 47. <dependency> 48. <groupId>log4j</groupId> 49. <artifactId>log4j</artifactId> 50. <version>1.2.17</version> 51. </dependency> 52. <dependency> 53. <groupId>junit</groupId> 54. <artifactId>junit</artifactId> 55. <version>4.12</version> 56. <scope>test</scope> 57. </dependency> 58. </dependencies> 59. </project> 60. Sehen Sie sich mit tree /F die Verzeichnisstruktur an: 61. 62. [\MeinWorkspace\MvnJarMitLibs] 63. |- [src] 64. | |- [main] 65. | | |- [java] 66. | | | '- [de] 67. | | | '- [meinefirma] 68. | | | '- [meinprojekt] 69. | | | '- App.java 70. | | '- [resources] 71. | | '- log4j.properties 72. | '- [test] 73. | '- [java] 74. | '- [de] 75. | '- [meinefirma] 76. | '- [meinprojekt] 77. | '- AppTest.java 78. '- pom.xml 79. Führen Sie den JUnit-Test aus: mvn test Sie erhalten: 2015-01-01 11:12:13,456 INFO [main] root: ---- Hallo Logger! ---- Im Test ist die log4j-1.2.17.jar im Classpath und der Log4j-Logger funktioniert. 80. Führen Sie den Build-Prozess aus, sehen Sie sich an, was in der erstellten Jar-Datei enthalten ist und versuchen Sie die Anwendung zu starten: mvn package jar tvf target/MvnJarMitLibs-1.0-SNAPSHOT.jar java -cp target/MvnJarMitLibs-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App Weil in der erstellten Jar-Datei die log4j-1.2.17.jar-Lib fehlt, erhalten Sie die Exception: Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger 81. Um eine ausführbare Jar-Datei zu erhalten, sind zwei Erweiterungen notwendig: Libs und andere Abhängigkeiten müssen in die Jar-Datei integriert werden. Eine META-INF/MANIFEST.MF muss hinzugefügt werden inklusive eines MainClass-Eintrags. Dies leistet das maven-assembly-plugin. Ersetzen Sie im MvnJarMitLibs-Projektverzeichnis den Inhalt der pom.xml durch: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnJarMitLibs</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MvnJarMitLibs</name> <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.0.0</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>de.meinefirma.meinprojekt.App</mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project> <descriptorRef>jar-with-dependencies</descriptorRef> ist ein vordefinierter "prefabricated assembly descriptor" zur Integration von Abhängigkeiten, wie zum Beispiel .jar-Libs. Weitere Pre-defined Descriptor Files sind: bin, src und project. Alternativ können Sie auch einen selbst erstellten "custom assembly descriptor" verwenden, wie unter Configuration and Usage beschrieben ist. <mainClass>de.meinefirma.meinprojekt.App</mainClass> definiert die auszuführende Klasse, welche die main()-Methode enthält. 82. Bauen Sie die "jar-with-dependencies" und führen Sie sie aus: mvn clean package dir target\MvnJarMitLibs*.jar jar tvf target\MvnJarMitLibs-1.0-SNAPSHOT-jar-with-dependencies.jar --> Die org/apache/log4j/...-Klassen sind enthalten. java -jar target\MvnJarMitLibs-1.0-SNAPSHOT-jar-with-dependencies.jar --> Die Jar-Datei kann ausgeführt werden und Sie erhalten das korrekte Ergebnis: 2015-01-01 11:12:13,456 INFO [main] root: ---- Hallo Logger! ---- 83. Sehen Sie sich die Hilfstexte zu den Goals des Assembly-Plugins an: mvn help:describe -Dplugin=org.apache.maven.plugins:maven-assemblyplugin Maven-Webapp-Projekt mit Jetty 1. Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace) und erzeugen Sie eine WebappProjektstruktur (das mvn-Kommando in einer Zeile): cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DarchetypeArtifactId=maven-archetype-webapp DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnJettyWebApp cd MvnJettyWebApp tree /F Sie erhalten: [\MeinWorkspace\MvnJettyWebApp] |- [src] | '- [main] | |- [resources] | '- [webapp] | |- [WEB-INF] | | '- web.xml | '- index.jsp '- pom.xml 2. Ersetzen Sie den Inhalt der pom.xml durch: 3. 4. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnJettyWebApp</artifactId> 9. <version>1.0-SNAPSHOT</version> 10. <packaging>war</packaging> 11. <name>MvnJettyWebApp Maven Webapp</name> 12. <url>http://www.meinefirma.de</url> 13. <build> 14. <finalName>${project.artifactId}</finalName> 15. <plugins> 16. <plugin> 17. <groupId>org.eclipse.jetty</groupId> 18. <artifactId>jetty-maven-plugin</artifactId> 19. <version>9.3.10.v20160621</version> 20. <configuration> 21. <webAppConfig> 22. <contextPath>/${project.artifactId}</contextPath> 23. </webAppConfig> 24. </configuration> 25. </plugin> 26. </plugins> 27. </build> 28. </project> 29. Ausführung des Projekts: cd \MeinWorkspace\MvnJettyWebApp mvn jetty:run Warten Sie, bis "Started Jetty Server" erscheint, und rufen Sie dann im Webbrowser auf: http://localhost:8080/MvnJettyWebApp Sie erhalten eine Webseite mit: Hello World! 30. Beenden Sie Jetty mit "Strg + C". 31. Sehen Sie sich die Hilfstexte zu dem genannten und anderen Goals des Jetty-Plugins an: mvn help:describe -Dplugin=org.eclipse.jetty:jetty-maven-plugin Sehen Sie sich Konfigurationsoptionen des Jetty-Plugins an: http://eclipse.org/jetty/documentation/current/jetty-maven-plugin.html. Maven-Webapp mit Properties im Manifest Dieses Beispiel demonstriert Folgendes: Das automatische Hinzufügen einer MANIFEST.MF-Datei zu einer Webanwendung (WARDatei). Das Hinzufügen zusätzlicher Attribute zu dieser MANIFEST.MF-Datei. Das Auslesen der Attribute der MANIFEST.MF-Datei innerhalb der Webanwendung (z.B. in einer JSP). Führen Sie folgende Schritte durch: 1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace) und erzeugen Sie eine Webapp-Projektstruktur: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DarchetypeArtifactId=maven-archetype-webapp DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnWebManifest cd MvnWebManifest tree /F Sie erhalten: [\MeinWorkspace\MvnWebManifest] |- [src] | '- [main] | |- [resources] | '- [webapp] | |- [WEB-INF] | | '- web.xml | '- index.jsp '- pom.xml 2. Ersetzen Sie den Inhalt der pom.xml durch: 3. 4. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnWebManifest</artifactId> 9. <version>1.0-SNAPSHOT</version> 10. <packaging>war</packaging> 11. <name>MvnWebManifest</name> 12. <url>http://www.meinefirma.de</url> 13. <properties> 14. <maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format> 15. </properties> 16. <build> 17. <finalName>${project.artifactId}</finalName> 18. <plugins> 19. <plugin> 20. <artifactId>maven-war-plugin</artifactId> 21. <version>2.6</version> 22. <configuration> 23. <!-- <packagingExcludes>, um folgende Warnung mit der 2.1.1Version zu vermeiden: 24. [WARNING] Warning: selected war files include a WEBINF/web.xml which will be ignored 25. (webxml attribute is missing from war task, or ignoreWebxml attribute is specified as 'true') --> 26. <packagingExcludes>WEB-INF/web.xml</packagingExcludes> 27. <archive> 28. 29. 30. <manifest> <!-- Default-Properties: --> <addDefaultImplementationEntries>false</addDefaultImplementationEntries > 31. </manifest> 32. <manifestEntries> 33. <!-- Maven-Properties: --> 34. <Modulname>${project.name}</Modulname> 35. <Modulversion>${project.version}</Modulversion> 36. <Maven-Build-Timestamp>${maven.build.timestamp}</MavenBuild-Timestamp> 37. <!-- Andere Properties, z.B. per buildnumber-mavenplugin, von Jenkins/Hudson oder per Kommandozeile gesetzt: --> 38. <VCS-Buildnumber>${buildNumber}</VCS-Buildnumber> 39. <hg-ChangeSet-ID>${changeSet}, ${changeSetDate}</hgChangeSet-ID> 40. <Modulbuild>${BUILD_TAG}, ${BUILD_ID}</Modulbuild> 41. <MeinSpezProp>${MeinSpezProp}</MeinSpezProp> 42. </manifestEntries> 43. </archive> 44. </configuration> 45. </plugin> 46. <plugin> 47. <groupId>org.eclipse.jetty</groupId> 48. <artifactId>jetty-maven-plugin</artifactId> 49. <version>9.4.5.v20170502</version> 50. <configuration> 51. <webAppConfig> 52. <contextPath>/${project.artifactId}</contextPath> 53. </webAppConfig> 54. </configuration> 55. </plugin> 56. </plugins> 57. </build> 58. </project> 59. Ersetzen Sie im src\main\webapp-Verzeichnis den Inhalt der index.jsp durch: 60. 61. <%@ page import="java.io.*" %> 62. <%@ page import="java.util.*" %> 63. <%@ page import="java.util.jar.*" %> 64. 65. <html> 66. <body> 67. <h2>Hello World!</h2> 68. <% 69. String pth = getServletConfig().getServletContext().getRealPath( "/" ); 70. if( !pth.endsWith( "/" ) && !pth.endsWith( "\\" ) ) pth += "/"; 71. File mf = new File( pth + "META-INF/MANIFEST.MF" ); 72. if( mf.exists() ) { 73. out.println( "<h4>Attribute in der META-INF/MANIFEST.MF:</h4>" ); 74. Manifest manifest = new Manifest( new FileInputStream( mf ) ); 75. for( Map.Entry<Object,Object> attr : manifest.getMainAttributes().entrySet() ) { 76. 77. } 78. } 79. %> 80. </body> 81. </html> out.println( attr + "<br>" ); 82. Ausführung des Projekts: cd \MeinWorkspace\MvnWebManifest mvn clean war:manifest war:war -DMeinSpezProp=AbcXyz type src\main\webapp\META-INF\MANIFEST.MF mvn jetty:run Warten Sie, bis "Started Jetty Server" erscheint, und rufen Sie dann im Webbrowser auf: http://localhost:8080/MvnWebManifest 83. Sie erhalten eine Webseite mit der Auflistung der Attribute in der MANIFEST.MF inklusive des per Kommandozeile angegebenen Attributs MeinSpezProp=AbcXyz. Das ${buildNumber}-Property kann durch das buildnumber-maven-plugin mit dem create-Goal gesetzt werden (falls Ihr Versionskontrollsystem unterstützt wird und korrekt im SCM-Tag eingetragen ist). Falls Sie Mercurial (hg) als Versionskontrollsystem verwenden, können mit dem buildnumber-maven-plugin-hgchangeset-Goal die ${changeSet}- und ${changeSetDate}"-Properties gesetzt werden. Die BUILD_...-Properties können z.B. durch Jenkins/Hudson gesetzt werden. Falls einige Properties beim oben gezeigten Aufruf nicht gesetzt werden (z.B. weil sie erst in der validate-Phase gesetzt werden): Führen Sie folgende Kommandos aus, um sie anzuzeigen: cd \MeinWorkspace\MvnWebManifest call mvn clean install cd target jar xf MvnWebManifest.war type META-INF\MANIFEST.MF 84. Beenden Sie Jetty mit "Strg + C". 85. Sie können die resultierende WAR-Datei target\MvnWebManifest.war auch in beliebige andere Web Application Server (z.B. Tomcat) deployen. Maven-Properties-Projekt mit Ressourcen-Filterung Dieses Beispiel zeigt Zweierlei: Es gibt verschiedene Möglichkeiten, um Property-Werte zu setzen. Mit Property-Werten können Ressourcen gefiltert werden. Filterung bedeutet in diesem Zusammenhang, dass "${...}"-Platzhalterausdrücke durch Werte von Properties ersetzt werden können. In diesem Programmierbeispiel werden die Property-Werte lediglich in der Java-Anwendung ausgelesen und angezeigt. Mit Property-Werten könnte aber auch der Build-Prozess gesteuert werden (z.B. über Profile). Alternativ könnten auch mit dem maven-replacer-plugin Ausdrücke ersetzt werden. 1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace) und erzeugen Sie eine neue Projektstruktur: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnPropApp cd MvnPropApp md src\main\filters md src\main\resources\filtered md src\test\resources 2. Ersetzen Sie in src\main\java\de\meinefirma\meinprojekt den Inhalt von App.java durch: 3. 4. package de.meinefirma.meinprojekt; 5. 6. import java.io.IOException; 7. import java.util.Properties; 8. 9. public class App 10. { 11. public static void main( String[] args ) 12. { 13. System.out.println( (new App()).run() ); 14. } 15. 16. public String run() 17. { 18. 19. 20. 21. Properties props = new Properties(); try { // Properties einlesen: props.load( getClass().getResourceAsStream( "/filtered/MeineProps.properties" ) ); 22. } catch( IOException ex ) { 23. throw new RuntimeException( ex.getCause() ); 24. } 25. // Properties zu String wandeln: 26. String s = props.toString(); 27. if( s.length() > 2 ) { s = s.substring( 1, s.length() - 1 ); } 28. return s.replace( ", ", "\n" ); 29. } 30. } 31. Ersetzen Sie in src\test\java\de\meinefirma\meinprojekt den Inhalt von AppTest.java durch: 32. 33. package de.meinefirma.meinprojekt; 34. 35. import java.io.IOException; 36. import java.util.Properties; 37. import junit.framework.TestCase; 38. 39. public class AppTest extends TestCase 40. { 41. public void testApp() 42. { 43. Properties propsApp = new Properties(), propsTst = new Properties(); 44. try { 45. propsApp.load( getClass().getResourceAsStream( "/filtered/MeineProps.properties" ) ); 46. propsTst.load( getClass().getResourceAsStream( "/MeineTestProps.properties" ) ); 47. } catch( IOException ex ) { 48. throw new RuntimeException( ex.getCause() ); 49. } 50. assertEquals( "MeinPomProp vor Merge", "PropAusPom", propsApp.get( "MeinPomProp" ) ); 51. propsApp.putAll( propsTst ); 52. assertEquals( "MeinPomProp nach Merge", "PropAusTestProps", propsApp.get( "MeinPomProp" ) ); 53. } 54. } Bitte beachten Sie, dass Sie im Test sowohl auf die Ressourcen unter src\main\resources als auch unter src\test\resources zugreifen können. Wenn Sie wie im Beispiel gezeigt einen Merge durchführen, können Sie Properties für den Testfall überschreiben. 55. Ersetzen Sie den Inhalt der pom.xml durch: 56. 57. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 58. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 59. <modelVersion>4.0.0</modelVersion> 60. <groupId>de.meinefirma.meinprojekt</groupId> 61. <artifactId>MvnPropApp</artifactId> 62. <version>1.0-SNAPSHOT</version> 63. <packaging>jar</packaging> 64. <name>MvnPropApp</name> 65. <url>http://www.meinefirma.de</url> 66. <build> 67. <filters> 68. <filter>src/main/filters/filter.properties</filter> 69. </filters> 70. <resources> 71. <resource> 72. <filtering>true</filtering> 73. <directory>src/main/resources</directory> 74. <includes> 75. <include>**/filtered/*</include> 76. </includes> 77. </resource> 78. <resource> 79. <filtering>false</filtering> 80. <directory>src/main/resources</directory> 81. <excludes> 82. <exclude>**/filtered/*</exclude> 83. </excludes> 84. </resource> 85. </resources> 86. </build> 87. <dependencies> 88. <dependency> 89. <groupId>junit</groupId> 90. <artifactId>junit</artifactId> 91. <version>4.12</version> 92. <scope>test</scope> 93. </dependency> 94. </dependencies> 95. <properties> 96. <MeinProp>PropAusPom</MeinProp> 97. <MeinPomProp>PropAusPom</MeinPomProp> 98. </properties> 99. </project> Beachten Sie bitte den <build>- und den <properties>-Block: Im <properties>-Block werden zwei Property-Werte definiert. Im <build>-Block wird eine zusätzlich zu berücksichtigende Filter-Datei definiert (<filter>src/main/filters/filter.properties) und es wird Filterung aktiviert (<filtering>true), aber nicht für alle Ressourcen, sondern nur für die im filteredUnterverzeichnis. Wählen Sie für Filterung besser nicht das gesamte RessourcenVerzeichnis: Vielleicht wollen Sie später Dateien hinzufügen, die nicht gefiltert werden dürfen (z.B. Bilddateien). Erzeugen Sie unterhalb von src\main ein Unterverzeichnis mit dem Namen filters. Erzeugen Sie in diesem Unterverzeichnis die Datei filter.properties mit folgendem Inhalt: 100. 101. 102. MeinProp=PropAusFilter 103. MeinFilterProp=PropAusFilter 104. Erzeugen Sie unterhalb von src\main ein Unterverzeichnis mit dem Namen resources und darunter eines mit dem Namen filtered. Erzeugen Sie in letzterem die Datei MeineProps.properties mit folgendem Inhalt: 105. 106. MeinPropsProp=PropAusMeineProps 107. MeinCmdProp=${MeinCmdProp} 108. MeinPomProp=${MeinPomProp} 109. MeinFilterProp=${MeinFilterProp} 110. MeinProp=${MeinProp} 111. pom.name=${pom.name} 112. pom.version=${pom.version} 113. pom.build.finalName=${pom.build.finalName} 114. pom.url=${pom.url} 115. settings.localRepository=${settings.localRepository} 116. basedir=${basedir} 117. os.arch=${os.arch} 118. os.name=${os.name} 119. java.version=${java.version} 120. user.home=${user.home} 121. Erzeugen Sie unterhalb von src\test ein Unterverzeichnis mit dem Namen resources und erzeugen Sie darin die Datei MeineTestProps.properties mit folgendem Inhalt: 122. 123. MeinPomProp=PropAusTestProps 124. Lassen Sie sich die Verzeichnisstruktur anzeigen: cd \MeinWorkspace\MvnPropApp tree /F --> [\MeinWorkspace\MvnPropApp] |- [src] | |- [main] | | |- [filters] | | | '- filter.properties | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | '- App.java | | '- [resources] | | '- [filtered] | | '- MeineProps.properties | '- [test] | |- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- AppTest.java | '- [resources] | '- MeineTestProps.properties '- pom.xml 125. Um den Build durchzuführen und das entstandene Programm inklusive eines Kommandozeilenparameters ("-D...") auszuführen, geben Sie im Kommandozeilenfenster ein: cd \MeinWorkspace\MvnPropApp mvn clean package "-DMeinCmdProp=PropVonCmd" java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App Sie erhalten eine Ausgabe ähnlich zu: MeinPropsProp=PropAusMeineProps MeinCmdProp=PropVonCmd MeinPomProp=PropAusPom MeinFilterProp=PropAusFilter MeinProp=PropAusPom pom.name=MvnPropApp pom.version=1.0-SNAPSHOT pom.build.finalName=MvnPropApp-1.0-SNAPSHOT pom.url=http://www.meinefirma.de settings.localRepository=C:\Users\User\.m2\repository basedir=\MeinWorkspace\MvnPropApp os.arch=amd64 os.name=Windows 10 java.version=1.8 user.home=C:\Users\User Die Property-Werte stammen aus unterschiedlichen Quellen: MeinPropsProp wurde in der MeineProps.properties-Propertiesdatei definiert. MeinCmdProp wurde über den Kommandozeilenparameter DMeinCmdProp=PropVonCmd definiert. MeinPomProp wurde in der pom.xml als zusätzliche Property definiert. Die pom...-Properties sind in der pom.xml definierte Standardeinträge. MeinFilterProp wurde in der filter.properties definiert. MeinProp wird sowohl in der pom.xml als auch in der filter.properties gesetzt: Wie man sieht, setzt sich die pom.xml durch. Wenn Sie diese Property zusätzlich in der Kommandozeile setzen würden (mvn clean package "- DMeinCmdProp=PropVonCmd" "-DMeinProp=PropVonCmd"), würde sich diese durchsetzen. 126. settings.localRepository hätte in einer der beiden settings.xml definiert sein können (im Beispiel war sie nicht definiert und es wird der Default-Wert angezeigt). basedir wird von Maven gesetzt. os.arch, os.name, java.version und user.home sind Java-Systemproperties. Properties können für die Test-Phase überschrieben werden. Um nur die JUnit-Tests auszuführen, geben Sie im Kommandozeilenfenster ein: cd \MeinWorkspace\MvnPropApp mvn clean test Falls es Probleme beim Test gibt, sehen Sie sich die Dateien im target\surefire-reportsVerzeichnis an: start target\surefire-reports\de.meinefirma.meinprojekt.AppTest.txt start target\surefire-reports\TESTde.meinefirma.meinprojekt.AppTest.xml Die XML-Datei enthält auch relevante Environment-Variablen und Konfigurationen (z.B. <property name="localRepository" value="C:\Users\User\.m2\repository"/>). 127. Sehen Sie nach, was in der resultierenden Jar-Datei enthalten ist: cd \MeinWorkspace\MvnPropApp mvn package "-DMeinCmdProp=PropVonCmd" jar tvf target/MvnPropApp-1.0-SNAPSHOT.jar --> de/meinefirma/meinprojekt/App.class filtered/MeineProps.properties META-INF/MANIFEST.MF META-INF/maven/de.meinefirma.meinprojekt/MvnPropApp/pom.properties META-INF/maven/de.meinefirma.meinprojekt/MvnPropApp/pom.xml Bitte beachten Sie: Die filter.properties-Datei wurde zwar beim Filtern berücksichtigt (${MeinFilterProp} wurde umgewandelt in PropAusFilter), aber sie wird nicht dem Ergebnis-Artefakt hinzugefügt. Erweiterung um Maven-Profile Ein profile definiert Voreinstellungen. Über Umgebungsbedingungen, Kommandozeilenoptionen oder andere Parameter können Profile aktiviert werden. Eine Einführung finden Sie unter Introduction to Build Profiles. Ein anderes Profile verwendendes Beispiel finden Sie unter Integrationstest mit Cargo. Außer in der pom.xml können Profile auch in einer settings.xml definiert werden. Unter Maven 2.2.1 war zusätzlich auch die Definition von Profilen in einer profiles.xml-Datei möglich, was ab Maven 3.0 nicht mehr möglich ist (siehe Maven Release Notes und Maven 3.x Compatibility Notes). In der settings.xml definierte Profile beeinflussen hauptsächlich: Properties können definiert oder umdefiniert werden. Repositories können zusätzlich definiert werden. Am meisten Einflussmöglichkeiten gibt es, wenn das Profil in der pom.xml definiert wird. Dann sind beispielsweise zusätzlich beeinflussbar: Plugin-Goals können zusätzlich ausgeführt werden. Module können unterschiedlich gewählt werden. Dependencies können ergänzt werden. Dependency-Management und Distribution-Management können beeinflusst werden. Die Auswahl und Aktivierung (activation) eines (oder mehrerer) Profile kann über verschiedene Auslöser erfolgen: Explizit durch direkte Auswahl eines oder mehrerer Profile über die -PKommandozeilenoption (z.B. -P MeinProfil1,MeinProfil2), oder auch der Ausschluss eines Profiles (z.B. -P !MeinProfil3) Kommandozeilen-Property (z.B. -DMeinProp=MeinWert) Umgebungsvariable System-Properties (z.B. Betriebssystem, Java-Version, ...) Existenz eines Properties, ein bestimmter Wert eines Properties oder die Negation, also das Property hat nicht einen bestimmten Wert (<property><name>...<value>...) Existenz oder Fehlen einer Datei (<file><exists>... bzw. <file><missing>...) Normalerweise werden die verschiedenen Profile eines Projekts in nur einer der möglichen Dateien gespeichert, fast immer in der pom.xml. Von den vielen Möglichkeiten kann das folgende Beispiel nur wenig demonstrieren: Die ProfileAktivierung erfolgt über -P-Kommandos oder alternativ über Properties, die per Umgebungsvariable oder Kommandozeilenparameter gesetzt werden. Und als Effekt werden lediglich die eingestellten Properties ausgegeben. Damit die steuernde Property auch per Umgebungsvariable gesetzt werden kann, muss sie mit einem vorangestellten "env." beginnen und ansonsten in Großbuchstaben geschrieben sein (im Beispiel lautet sie: "env.UMGEBUNG"). Als Umgebungsvariable wird sie ohne das "env." gesetzt (siehe Beispiel unten). 1. Sie können dieses und die meisten der folgenden Programmierbeispiele wahlweise downloaden oder, wie im Folgenden beschrieben wird, schrittweise ausführen. 2. Voraussetzung ist, dass Sie das obige Beispiel Maven-Properties-Projekt (im Folgenden "MvnPropApp" genannt) erfolgreich durchgeführt haben. 3. Ergänzen Sie im MvnPropApp-Projektverzeichnis in der Datei pom.xml zwischen </properties> und </project> Folgendes: 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. <profiles> <profile> <id>Entw</id> <activation> <property> <name>env.UMGEBUNG</name> <value>Entwicklung</value> </property> </activation> <properties> <appsrv.url>meine.Url.zum.lokalen.Appserver</appsrv.url> <appsrv.usr>mein lokaler AppServer-User</appsrv.usr> <appsrv.pwd>geheim</appsrv.pwd> <db.url>meine.Url.zur.lokalen.Datenbank</db.url> <db.usr>mein lokaler DB-User</db.usr> <db.pwd>supergeheim</db.pwd> </properties> </profile> <profile> <id>IntTest</id> <activation> <property> <name>env.UMGEBUNG</name> <value>Integrationstest</value> </property> </activation> <properties> <appsrv.url>Url.zum.IntTest.Appserver</appsrv.url> <appsrv.usr>IntTest-AppServer-User</appsrv.usr> <appsrv.pwd>geheim</appsrv.pwd> <db.url>Url.zur.IntTest.Datenbank</db.url> <db.usr>IntTest-DB-User</db.usr> <db.pwd>supergeheim</db.pwd> </properties> </profile> </profiles> 41. Ergänzen Sie in der Properties-Datei src\main\resources\filtered\MeineProps.properties 42. 43. env.UMGEBUNG=${env.UMGEBUNG} 44. appsrv.url=${appsrv.url} 45. appsrv.usr=${appsrv.usr} Folgendes: 46. 47. 48. 49. appsrv.pwd=${appsrv.pwd} db.url=${db.url} db.usr=${db.usr} db.pwd=${db.pwd} 50. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in das MvnPropAppProjektverzeichnis und führen Sie folgende Kommandos aus: cd \MeinWorkspace\MvnPropApp mvn clean package java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort --> Die appsrv...- und db...-Properties sind nicht gesetzt. set UMGEBUNG=Entwicklung mvn clean package java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort --> Die appsrv...- und db...-Properties sind für "Entwicklung" gesetzt. mvn clean package "-Denv.UMGEBUNG=Integrationstest" java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort --> Die appsrv...- und db...-Properties sind für "Integrationstest" gesetzt (die set-Umgebungsvariable wird durch den Kommandozeilenparameter überschrieben). Statt des Kommandozeilenparameters können Sie auch die Umgebungsvariable setzen: set UMGEBUNG=Integrationstest mvn clean package java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort Sie erhalten eine Ausgabe wie oben gezeigt, aber je nach gesetztem env.UMGEBUNGProperty-Wert ergänzt um beispielsweise: appsrv.pwd=geheim appsrv.url=Url.zum.IntTest.Appserver appsrv.usr=IntTest-AppServer-User db.pwd=supergeheim db.url=Url.zur.IntTest.Datenbank db.usr=IntTest-DB-User env.UMGEBUNG=Integrationstest 51. Alternativ können Sie die verschiedenen Profile auch über die -P-Option aktivieren: set UMGEBUNG= mvn package java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort --> Die appsrv...- und db...-Properties sind nicht gesetzt. mvn -P IntTest package java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort --> Die appsrv...- und db...-Properties sind für "Integrationstest" gesetzt. 52. Falls Sie viele Profile haben und die Übersicht verlieren, können Sie sich mit dem Maven Help Plugin die aktivierten anzeigen lassen: Wenn Sie mvn help:active-profiles ohne Profilangabe aufrufen, wird kein Profil aufgelistet. Aber sowohl mvn help:active-profiles "-Denv.UMGEBUNG=Integrationstest" als auch mvn help:active-profiles -P IntTest führen zu: The following profiles are active: - IntTest (source: ...) 53. Das Maven Help Plugin bietet noch weitere hilfreiche Kommandos, zum Beispiel: mvn help:effective-pom "-Denv.UMGEBUNG=Integrationstest" Generierung von HTML, PDF, RTF etc. mit DocBook und dem Docbkx Maven Plugin 1. Sehen Sie sich die Doku zu DocBook und Docbkx an: http://www.docbook.org/tdg5/en/html/docbook.html http://docs.codehaus.org/display/MAVENUSER/Docbkx+Maven+Plugin 2. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace) und erzeugen Sie eine neue Projektstruktur: cd \MeinWorkspace md MvnDocbkx\src\docbkx cd MvnDocbkx 3. Erzeugen Sie im MvnDocbkx-Projektverzeichnis die Projektkonfiguration: pom.xml 4. 5. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 6. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 7. <modelVersion>4.0.0</modelVersion> 8. <groupId>de.meinefirma.meinprojekt</groupId> 9. <artifactId>MvnDocbkx</artifactId> 10. <version>1.0</version> 11. <packaging>pom</packaging> 12. <name>MvnDocbkx</name> 13. <build> 14. <plugins> 15. <plugin> 16. <groupId>com.agilejava.docbkx</groupId> 17. <artifactId>docbkx-maven-plugin</artifactId> 18. <version>2.0.14</version> 19. <executions> 20. <execution> 21. <goals> 22. <goal>generate-html</goal> 23. <goal>generate-webhelp</goal> 24. <goal>generate-rtf</goal> 25. <goal>generate-pdf</goal> 26. </goals> 27. <phase>package</phase> 28. </execution> 29. </executions> 30. <configuration> 31. <includes>book.xml</includes> 32. </configuration> 33. </plugin> 34. </plugins> 35. </build> 36. </project> 37. Erzeugen Sie im src\docbkx-Verzeichnis die Dokumentbeschreibung: book.xml 38. 39. <book xmlns="http://docbook.org/ns/docbook" 40. xmlns:xlink="http://www.w3.org/1999/xlink" 41. version="5.0"> 42. <info> 43. <author> 44. <personname><firstname>Torsten</firstname><surname>Horn</surname></pers onname> 45. </author> 46. <copyright><year>2012</year><holder>Torsten Horn</holder></copyright> 47. </info> 48. <title>Mein DocBook-Dokument</title> 49. <article> 50. <title>Mein erstes Kapitel</title> 51. <section> 52. <title>Mein erster Abschnitt</title> 53. <para>Mein erster Absatz vom ersten Abschnitt.</para> 54. <para>Mein zweiter Absatz vom ersten Abschnitt.</para> 55. </section> 56. <section> 57. <title>Mein zweiter Abschnitt</title> 58. <para>Mein erster Absatz vom zweiten Abschnitt.</para> 59. <para>Mein zweiter Absatz vom zweiten Abschnitt, mit Formatierungen, z.B.: 60. <quote>quote</quote>, <emphasis>emphasis</emphasis>, <replaceable>replaceable</replaceable>, 61. <literal>literal</literal>, <userinput>userinput</userinput>.</para> 62. </section> 63. </article> 64. <article> 65. <title>Mein zweites Kapitel</title> 66. <section> 67. <title>Mein neuer Abschnitt im zweiten Kapitel</title> 68. <para>Mein erster Absatz vom neuen Abschnitt im zweiten Kapitel.</para> 69. <para>Mein zweiter Absatz vom neuen Abschnitt im zweiten Kapitel mit einer Tabelle und mit HTML-Links:</para> 70. <table> 71. <title>Doku-Tabelle</title> 72. <tgroup cols="2"> 73. <thead><row><entry>Thema</entry><entry>DokuLink</entry></row></thead> 74. <tbody> 75. <row><entry>DocBook</entry><entry><link xlink:href="http://www.docbook.org/tdg5/en/html/docbook.html">http://ww w.docbook.org/tdg5/en/html/docbook.html</link></entry></row> 76. <row><entry>Docbkx </entry><entry><link xlink:href="http://docs.codehaus.org/display/MAVENUSER/Docbkx+Maven+Plu gin">http://docs.codehaus.org/display/MAVENUSER/Docbkx+Maven+Plugin</li nk></entry></row> 77. </tbody> 78. </tgroup> 79. </table> 80. </section> 81. </article> 82. </book> 83. Führen Sie im MvnDocbkx-Projektverzeichnis aus: cd \MeinWorkspace\MvnDocbkx mvn clean package start target/docbkx/html/book.html start target/docbkx/webhelp/book/content/index.html start target/docbkx/pdf/book.pdf start target/docbkx/rtf/book.rtf 84. Das Layout der Dokumente ist in diesem einfachen Beispiel sehr rudimentär, aber Sie finden in der oben genannten Doku viele Hinweise zur Verbesserung. 85. Falls es Ihnen um PDF-Generierung geht, sehen Sie sich auch an: PDF mit Apache PDFBox PDF mit XSL-FO und Apache FOP Java-Codegenerierung aus einem XSD-Schema mit JAXB 1. Das folgende Beispiel demonstriert Verschiedenes: Aus einer XML-Schema-Definitions-Datei (XSD-Datei) wird Java-Code generiert. Falls Sie sich nicht mit XML-Schema-Definitionen auskennen: Sehen Sie sich die Einführungen unter java-xsd.htm und http://de.wikipedia.org/wiki/XML_Schema an sowie die W3C XML Schema Specification entweder im englischen Original oder ins deutsche übersetzt unter Einführung, Strukturen und Datentypen. Als Codegenerator wird JAXB 2 verwendet. Siehe hierzu: Generierung von JavaCode aus einem XSD-Schema, JSR 222 und JAXB 2.x. Die Einbindung in Maven erfolgt über das maven-jaxb2-plugin. Da JAXB 2 mindestens Java 5 voraussetzt, wird außerdem gezeigt, wie dies dem maven-compiler-plugin mitgeteilt wird (über <configuration>...1.7...). 2. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace) und erzeugen Sie eine neue Projektstruktur: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnJaxbApp cd MvnJaxbApp md src\main\resources tree /F 3. Ersetzen Sie den Inhalt der pom.xml durch: 4. 5. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 6. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 7. <modelVersion>4.0.0</modelVersion> 8. <groupId>de.meinefirma.meinprojekt</groupId> 9. <artifactId>MvnJaxbApp</artifactId> 10. <version>1.0-SNAPSHOT</version> 11. <packaging>jar</packaging> 12. <name>MvnJaxbApp</name> 13. <build> 14. <plugins> 15. <plugin> 16. <groupId>org.jvnet.jaxb2.maven2</groupId> 17. <artifactId>maven-jaxb2-plugin</artifactId> 18. <version>0.13.2</version> 19. <executions> 20. <execution> 21. <goals> 22. <goal>generate</goal> 23. </goals> 24. <configuration> 25. <generatePackage>de.meinefirma.meinprojekt.generated</generatePackage> 26. </configuration> 27. </execution> 28. </executions> 29. </plugin> 30. <plugin> 31. <groupId>org.apache.maven.plugins</groupId> 32. <artifactId>maven-compiler-plugin</artifactId> 33. <version>3.6.1</version> 34. <configuration> 35. <source>1.7</source> 36. <target>1.7</target> 37. </configuration> 38. </plugin> 39. </plugins> 40. </build> 41. <dependencies> 42. <dependency> 43. <groupId>com.sun.xml.bind</groupId> 44. <artifactId>jaxb-impl</artifactId> 45. <version>2.2.11</version> 46. </dependency> 47. <dependency> 48. <groupId>junit</groupId> 49. <artifactId>junit</artifactId> 50. <version>4.12</version> 51. <scope>test</scope> 52. </dependency> 53. </dependencies> 54. </project> 55. Ersetzen Sie den Inhalt von src\test\java\de\meinefirma\meinprojekt\AppTest.java 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. durch: package de.meinefirma.meinprojekt; import import import import import de.meinefirma.meinprojekt.generated.Buch; de.meinefirma.meinprojekt.generated.Buecherliste; java.math.BigDecimal; org.junit.Test; static org.junit.Assert.assertEquals; public class AppTest { @Test public void testApp() { Buch bu = new Buch(); bu.setTitel( "Mein Java-Buch" ); bu.setJahr( 2009 ); bu.setPreis( new BigDecimal( "47.11" ) ); bu.setKommentar( "Super!" ); Buecherliste bl = new Buecherliste(); bl.setKategorie( "Java-Bücher" ); bl.getListe().add( bu ); assertEquals( "Buecherliste", 1, bl.getListe().size() ); } } 82. Erzeugen Sie unterhalb von src\main ein Unterverzeichnis mit dem Namen resources und erzeugen Sie darin die XSD-Datei buecherliste.xsd mit folgendem Inhalt: 83. 84. <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 85. 86. <xsd:complexType name="Buecherliste"> 87. <xsd:sequence> 88. <xsd:element name="kategorie" type="xsd:string" /> 89. <xsd:element name="liste" type="Buch" maxOccurs="unbounded" /> 90. </xsd:sequence> 91. </xsd:complexType> 92. 93. <xsd:complexType name="Buch"> 94. <xsd:sequence> 95. <xsd:element name="titel" type="xsd:string" /> 96. <xsd:element name="jahr" type="xsd:int" /> 97. <xsd:element name="preis" type="xsd:decimal" /> 98. <xsd:element name="kommentar" type="xsd:string" /> 99. </xsd:sequence> 100. <xsd:attribute name="id" type="xsd:long" /> 101. </xsd:complexType> 102. 103. </xsd:schema> 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. Sehen Sie sich mit tree /F die Verzeichnisstruktur an: [\MeinWorkspace\MvnJaxbApp] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | '- App.java | | '- [resources] | | '- buecherliste.xsd | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml Bauen Sie das Projekt und sehen Sie sich das Ergebnis im target-Verzeichnis an: cd \MeinWorkspace\MvnJaxbApp mvn package tree /F Sie erhalten: [\MeinWorkspace\MvnJaxbApp] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | '- App.java | | '- [resources] | | '- buecherliste.xsd | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java |- [target] | |- ... | |- [generated-sources] | | '- [xjc] | | |- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | '- [generated] | | | |- Buch.java | | | | | | | '- ... '- pom.xml | | '- ... |- Buecherliste.java '- ObjectFactory.java Aus der XSD-Schema-Definition buecherliste.xsd im src\main\resourcesVerzeichnis wurden drei Java-Klassen im target\generatedsources\xjc\de\meinefirma\meinprojekt\generated-Verzeichnis generiert: Buch.java, Buecherliste.java und ObjectFactory.java. Sehen Sie sich diese drei Klassen an und beachten Sie, wie in der Buecherliste die Liste deklariert ist (über maxOccurs="unbounded"). Wie Sie diese Klassen ganz normal in Ihren Programmen verwenden können, zeigt der JUnit-Test in AppTest.java. REST-Webservice mit JAX-RS und SOAP-Webservice mit JAX-WS REST-Webservice mit JAX-RS Beispiele für RESTful-Webservices mit JAX-RS finden Sie unter jee-rest.htm#JaxRsMitMaven. SOAP-Webservice mit JAX-WS Das folgende Beispiel zeigt die Implementierung eines SOAP-Webservices mit JAX-WS inklusive Server und Client, allerdings nur in einer Quick-and-Dirty-Minimalversion. Weiterführende Hinweise finden Sie unter jee-jax-ws.htm. Wenn Sie mehr Features von JAXWS nutzen wollen, sollten Sie sich das JAX-WS Maven-Plugin ansehen. Wenn Sie die Webservice-Kommunikation beobachten wollen, empfiehlt sich soapUI. 1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace) und erzeugen Sie eine neue Projektstruktur: cd \MeinWorkspace md MvnJaxWs\src\main\java\de\meinefirma\meinprojekt cd MvnJaxWs 2. Erstellen Sie im MvnJaxWs-Projektverzeichnis die Projektkonfiguratiosdatei: pom.xml 3. 4. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnJaxWs</artifactId> 9. <version>1.0-SNAPSHOT</version> 10. <packaging>jar</packaging> 11. <name>MvnJaxWs</name> 12. <build> 13. <plugins> 14. <plugin> 15. <groupId>org.apache.maven.plugins</groupId> 16. <artifactId>maven-compiler-plugin</artifactId> 17. <version>3.6.1</version> 18. <configuration> 19. <source>1.7</source> 20. <target>1.7</target> 21. </configuration> 22. </plugin> 23. </plugins> 24. </build> 25. <dependencies> 26. <dependency> 27. <groupId>javax.xml.ws</groupId> 28. <artifactId>jaxws-api</artifactId> 29. <version>2.2.11</version> 30. </dependency> 31. </dependencies> 32. </project> Seit Java SE 6 können Sie den <dependency>...jaxws-api...-Abschnitt auch weglassen. 33. Legen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt die folgenden vier Java-Dateien an: HalloWelt.java package de.meinefirma.meinprojekt; import javax.jws.*; @WebService public interface HalloWelt { public String hallo( @WebParam( name = "wer" ) String wer ); } HalloWeltImpl.java package de.meinefirma.meinprojekt; import javax.jws.WebService; @WebService( endpointInterface="de.meinefirma.meinprojekt.HalloWelt" ) public class HalloWeltImpl implements HalloWelt { @Override public String hallo( String wer ) { return "Hallo " + wer; } } TestWsServer.java package de.meinefirma.meinprojekt; import javax.xml.ws.Endpoint; public class TestWsServer { public static void main( final String[] args ) { String url = ( args.length > 0 ) ? args[0] : "http://localhost:8080/test"; Endpoint.publish( url, new HalloWeltImpl() ); } } TestWsClient.java package de.meinefirma.meinprojekt; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.ws.Service; public class TestWsClient { public static void main( final String[] args ) throws Throwable { String url = ( args.length > 0 ) ? args[0] : "http://localhost:8080/test"; Service service = Service.create( new URL( url + "?wsdl" ), new QName( "http://meinprojekt.meinefirma.de/", "HalloWeltImplService" ) ); HalloWelt halloWelt = service.getPort( HalloWelt.class ); System.out.println( "\n" + halloWelt.hallo( args.length > 1 ? args[1] : "" ) ); } } 34. Die simplifizierte Projektstruktur sieht so aus: cd \MeinWorkspace\MvnJaxWs tree /F [\MeinWorkspace\MvnJaxWs] |- [src] | '- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | |- HalloWelt.java | |- HalloWeltImpl.java | |- TestWsClient.java | '- TestWsServer.java '- pom.xml 35. Öffnen Sie ein Kommandozeilenfenster, bauen Sie das Projekt und starten Sie den Server mit dem Webservice: cd \MeinWorkspace\MvnJaxWs mvn clean package java -cp target/MvnJaxWs-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.TestWsServer http://localhost:8080/test 36. Öffnen Sie ein zweites Kommandozeilenfenster und starten Sie den Webservice-Client: cd \MeinWorkspace\MvnJaxWs java -cp target/MvnJaxWs-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.TestWsClient http://localhost:8080/test ich Sie erhalten: Hallo ich 37. Sehen Sie sich die generierte WSDL-Datei über http://localhost:8080/test?wsdl und die generierte Schema-XSD-Datei über http://localhost:8080/test?xsd=1 an. Falls Ihr Webbrowser nichts anzeigt, verwenden Sie curl: curl http://localhost:8080/test?wsdl curl http://localhost:8080/test?xsd=1 38. Sie können auch andere URLs beim TestWsServer- und TestWsClient-Aufruf übergeben, beispielsweise http://localhost:4711/xyz. 39. Beenden Sie den Webservice-Server mit "Strg + C". 40. Sehen Sie sich die Webservice-Kommunikation mit soapUI an (siehe soapUIScreenshot). FTP-Client und embedded FTP-Server im JUnit-Test Mehrere Beispiele zu FTP-Projekten mit Maven finden Sie in java-ftp.htm. Einfaches Multimodulprojekt Größere Projekte bestehen aus vielen Teilprojekten. Um alle Teilprojekte gemeinsam kompilieren, bauen und verwalten zu können, werden sie zu Multimodulprojekten zusammengefasst (auch Multiprojekt genannt). Dies wird im Folgenden demonstriert. In diesem Dokument werden eher einfache Aspekte von Maven-Multimodulprojekten besprochen. Weitergehende Betrachtungen zum Zusammenspiel mit Eclipse und DVCS (z.B. Mercurial) finden Sie unter: maven-multiproj.htm. 1. Voraussetzung ist, dass Sie die beiden obigen Beispiele Maven-Webapp-Projekt (MvnJettyWebApp) und Maven-Properties-Projekt (MvnPropApp, egal ob mit oder ohne Erweiterung um Maven-Profile) erfolgreich durchgeführt haben. (Alternativ können Sie auch die genannten Projekte oder das Multimodulprojekt downloaden.) 2. Kopieren Sie die beiden Projekte MvnPropApp und MvnJettyWebApp in neue "...-Mp1"Verzeichnisse und erstellen Sie für das neue Multimodulprojekt das neue Verzeichnis MvnMultiProj1: cd \MeinWorkspace xcopy MvnPropApp MvnPropApp-Mp1\ /S xcopy MvnJettyWebApp MvnJettyWebApp-Mp1\ /S md MvnMultiProj1 3. Wechseln Sie in das MvnPropApp-Mp1-Verzeichnis und überprüfen Sie, dass das Projekt weiterhin funktioniert: cd \MeinWorkspace\MvnPropApp-Mp1 mvn clean package "-DMeinCmdProp=PropVonCmd" java -cp target/MvnPropApp-1.0-SNAPSHOT.jar de.meinefirma.meinprojekt.App | sort Wechseln Sie in das MvnJettyWebApp-Mp1-Verzeichnis und überprüfen Sie, dass auch dieses Projekt weiterhin funktioniert: cd \MeinWorkspace\MvnJettyWebApp-Mp1 mvn package jetty:run Sehen Sie sich die Webseite an: start http://localhost:8080/MvnJettyWebApp Beenden Sie Jetty mit "Strg + C". 4. Bislang sind die beiden Projekte MvnPropApp-Mp1 und MvnJettyWebApp-Mp1 unabhängig voneinander. Multimodulprojekte machen insbesondere bei voneinander abhängigen Projekten Sinn. Deshalb ändern wir MvnJettyWebApp-Mp1 so, dass es MvnPropApp-Mp1 benötigt. Ersetzen Sie im Verzeichnis MvnJettyWebApp-Mp1\src\main\webapp den Inhalt der Datei index.jsp durch: <%@ page import="java.text.SimpleDateFormat" %> <%@ page import="java.util.Date" %> <html> <head><title>MvnMultiProj-JSP</title></head> <body> <h2>MvnMultiProj-JSP</h2> <%= request.getRemoteHost() %><br> <%= (new SimpleDateFormat("yyyy-MM-dd, HH:mm:ss")).format(new Date()) + " h" %><br><br> <%= (new de.meinefirma.meinprojekt.App()).run().replace( "\n", "<br>\n" ) %> </body> </html> 5. Die Abhängigkeit zu MvnPropApp-Mp1 müssen Sie in der pom.xml von MvnJettyWebApp-Mp1 eintragen. Ergänzen Sie im MvnJettyWebApp-Mp1-Verzeichnis die pom.xml um folgenden <dependencies>-Block: 6. 7. 8. 9. 10. 11. 12. 13. 14. <dependencies> <dependency> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnPropApp</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> Wenn Sie jetzt MvnJettyWebApp-Mp1 ausführen, erhalten Sie eine cd \MeinWorkspace\MvnJettyWebApp-Mp1 mvn package jetty:run Fehlermeldung: --> [ERROR] Failed to execute goal on project MvnJettyWebApp: Could not resolve dependencies for project de.meinefirma.meinprojekt:MvnJettyWebApp:war:1.0-SNAPSHOT: Could not find artifact de.meinefirma.meinprojekt:MvnPropApp:jar:1.0SNAPSHOT (Falls Sie die Fehlermeldung nicht erhalten, haben Sie wahrscheinlich das Kommando "mvn install" schon mal getestet. Bitte löschen Sie in diesem Fall den Inhalt des Verzeichnisses ${localRepository}\de\meinefirma\meinprojekt.) 15. Wir könnten jetzt im MvnPropApp-Mp1-Projekt das Kommando "mvn install" ausführen. Dann würde das MvnPropApp-.jar-Artefakt in das lokale Maven-Repository kopiert und es würde vom MvnJettyWebApp-Mp1-Projekt gefunden werden. Diese Schritte müssten aber bei Änderungen jeweils wiederholt werden, was spätestens dann nicht mehr sinnvoll durchführbar und verwaltbar ist, wenn Sie nicht nur zwei, sondern viel mehr Teilprojekte haben. Deshalb erstellen wir ein übergeordnetes Multimodulprojekt. Erstellen Sie im MvnMultiProj1-Verzeichnis eine neue pom.xml, in der alle Teilprojekte eingetragen werden (im Beispiel MvnPropApp-Mp1 und MvnJettyWebApp-Mp1): 16. 17. <project> 18. <modelVersion>4.0.0</modelVersion> 19. <groupId>de.meinefirma.meinprojekt</groupId> 20. <artifactId>MvnMultiProj1</artifactId> 21. <version>1.0</version> 22. <packaging>pom</packaging> 23. <name>MvnMultiProj1</name> 24. <modules> 25. <module>../MvnJettyWebApp-Mp1</module> 26. <module>../MvnPropApp-Mp1</module> 27. </modules> 28. </project> 29. Die drei Projekte mit ihren jeweiligen pom.xml sind jetzt so 30. 31. [\MeinWorkspace] 32. |- [MvnJettyWebApp-Mp1] 33. | |- [src] 34. | | '- ... 35. | '- pom.xml 36. |- [MvnMultiProj1] 37. | '- pom.xml 38. '- [MvnPropApp-Mp1] 39. |- [src] 40. | '- ... 41. '- pom.xml angeordnet: 42. Jetzt können Sie mit folgenden Kommandos alle Teilprojekte (egal wie viele) kompilieren, alle Artefakte erzeugen und die Anwendung starten: cd \MeinWorkspace\MvnMultiProj1 mvn install cd \MeinWorkspace\MvnJettyWebApp-Mp1 mvn jetty:run Die Webseite des MvnJettyWebApp-Mp1-Projekts zeigt das Ergebnis des MvnPropAppMp1-Projekts an: start http://localhost:8080/MvnJettyWebApp 43. Falls Unterprojekte (wie MvnPropApp-Mp1 und MvnJettyWebApp-Mp1) vom übergeordneten Basisprojekt (MvnMultiProj1) Einstellungen erben sollen, können Sie das übergeordnete Basisprojekt als <parent> in den pom.xml-Dateien der Unterprojekte eintragen: 44. 45. 46. 47. 48. 49. 50. <parent> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnMultiProj1</artifactId> <version>1.0</version> <relativePath>../MvnMultiProj1/pom.xml</relativePath> </parent> Dies wird im folgenden Beispiel demonstriert. Multimodulprojekt mit Plugin-Management und Dependency-Management Bei Unterprojekten sollten gemeinsame Einstellungen (z.B. Java-Versionen und Versionen von Libs) nicht in jeder pom.xml jedes einzelnen Unterprojekts vorgenommen werden, sondern zentral erfolgen. Dies hat zwei Vorteile: - Die pom.xml der einzelnen Unterprojekte sind kürzer und fehlerfreier. - Änderungen können an einer Stelle für alle Unterprojekte durchgeführt werden. Bitte beachten Sie, dass durch die im Folgenden vorgestellten <pluginManagement>- und <dependencyManagement>-Blöcke noch keine Abhängigkeiten erzeugt werden, sondern vorerst lediglich Deklarationen und Konfigurationen erfolgen (z.B. zur Versionsnummer, zum Scope und <configuration>-Einstellungen). Ein noch etwas ausgefeilteres Multimodulprojektbeispiel finden Sie unter Java EE mit EJB3. 1. Voraussetzung für das folgende Beispiel ist, dass Sie die Beispiele JAX-WS (MvnJaxWs) und Einfaches Multimodulprojekt (MvnMultiProj1) erfolgreich durchgeführt haben. (Alternativ können Sie die Projekte auch downloaden.) 2. Kopieren Sie die drei Unterprojekte in neue Verzeichnisse und erstellen Sie für das neue Multimodulprojekt das neue Verzeichnis MvnMultiProj2: cd \MeinWorkspace xcopy MvnJaxWs MvnJaxWs-Mp2\ /S xcopy MvnJettyWebApp-Mp1 MvnJettyWebApp-Mp2\ /S xcopy MvnPropApp-Mp1 MvnPropApp-Mp2\ /S md MvnMultiProj2 cd MvnMultiProj2 3. Erzeugen Sie im MvnMultiProj2-Verzeichnis folgende pom.xml: 4. 5. <project> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnMultiProj2</artifactId> 9. <version>1.0</version> 10. <packaging>pom</packaging> 11. <name>MvnMultiProj2</name> 12. <modules> 13. <module>../MvnJaxWs-Mp2</module> 14. <module>../MvnJettyWebApp-Mp2</module> 15. <module>../MvnPropApp-Mp2</module> 16. </modules> 17. </project> 18. Das neue Projekt MvnMultiProj2 und die drei Unterprojekte 19. 20. [\MeinWorkspace] 21. |- [MvnJaxWs-Mp2] 22. | |- [src] 23. | | '- ... 24. | '- pom.xml 25. |- [MvnJettyWebApp-Mp2] 26. | |- [src] 27. | | '- ... 28. | '- pom.xml 29. |- [MvnMultiProj2] 30. | '- pom.xml 31. '- [MvnPropApp-Mp2] 32. |- [src] 33. | '- ... 34. '- pom.xml sind jetzt so angeordnet: 35. Bauen Sie alle vier Projekte: cd \MeinWorkspace\MvnMultiProj2 mvn clean install In der Reactor Summary wird zu allen vier Projekten SUCCESS gemeldet. 36. Entfernen Sie im MvnJaxWs-Mp2-Unterprojekt in der pom.xml den "<build>...</build>"-Block und rufen Sie im MvnMultiProj2-Verzeichnis erneut "mvn clean install" auf: Ab Maven 3.0 erhalten Sie keine Fehlermeldung, aber wenn Sie Maven 2.2.1 verwenden würden, würden Sie erwartungsgemäß erhalten: 37. [ERROR] BUILD FAILURE Compilation failure ...\MvnJaxWs-Mp2\...\HalloWeltImpl.java: annotations are not supported in -source 1.3 Entfernen Sie im MvnPropApp-Mp2-Unterprojekt in der pom.xml die beiden Zeilen "<version>4.12</version>" und "<scope>test</scope>" und rufen Sie im MvnMultiProj2-Verzeichnis erneut "mvn clean install" auf: Diesmal erhalten Sie wie erwartet die Fehlermeldung: 38. [ERROR] The project de.meinefirma.meinprojekt:MvnPropApp:1.0-SNAPSHOT has 1 error [ERROR] 'dependencies.dependency.version' for junit:junit:jar is missing. Ersetzen Sie im MvnMultiProj2-Verzeichnis die pom.xml durch: 39. 40. <project> 41. <modelVersion>4.0.0</modelVersion> 42. <groupId>de.meinefirma.meinprojekt</groupId> 43. <artifactId>MvnMultiProj2</artifactId> 44. <version>1.0</version> 45. <packaging>pom</packaging> 46. <name>MvnMultiProj2</name> 47. <modules> 48. <module>../MvnJaxWs-Mp2</module> 49. <module>../MvnJettyWebApp-Mp2</module> 50. <module>../MvnPropApp-Mp2</module> 51. </modules> 52. <build> 53. <pluginManagement> 54. <plugins> 55. <plugin> 56. <groupId>org.apache.maven.plugins</groupId> 57. <artifactId>maven-compiler-plugin</artifactId> 58. <version>3.6.1</version> 59. <configuration> 60. <source>1.7</source> 61. <target>1.7</target> 62. </configuration> 63. </plugin> 64. </plugins> 65. </pluginManagement> 66. </build> 67. <dependencyManagement> 68. <dependencies> 69. <dependency> 70. <groupId>junit</groupId> 71. <artifactId>junit</artifactId> 72. <version>4.12</version> 73. <scope>test</scope> 74. </dependency> 75. </dependencies> 76. </dependencyManagement> 77. </project> 78. Fügen Sie im MvnJaxWs-Mp2-Unterprojekt in der pom.xml folgendenmaßen einen "<parent>...</parent>"-Block hinzu (bei der Gelegenheit können Sie auch den <dependency>...jaxws-api...-Abschnitt entfernen): 79. 80. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 81. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 82. <modelVersion>4.0.0</modelVersion> 83. <parent> 84. <groupId>de.meinefirma.meinprojekt</groupId> 85. <artifactId>MvnMultiProj2</artifactId> 86. <version>1.0</version> 87. <relativePath>../MvnMultiProj2/pom.xml</relativePath> 88. </parent> 89. <groupId>de.meinefirma.meinprojekt</groupId> 90. <artifactId>MvnJaxWs</artifactId> 91. <version>1.0-SNAPSHOT</version> 92. <packaging>jar</packaging> 93. <name>MvnJaxWs</name> 94. </project> 95. Fügen Sie im MvnPropApp-Mp2-Unterprojekt ebenso in der pom.xml zwischen <modelVersion> und <groupId> den "<parent>...</parent>"-Block hinzu: 96. 97. <parent> 98. <groupId>de.meinefirma.meinprojekt</groupId> 99. <artifactId>MvnMultiProj2</artifactId> 100. <version>1.0</version> 101. <relativePath>../MvnMultiProj2/pom.xml</relativePath> 102. </parent> 103. Bauen Sie wieder alle vier Projekte: cd \MeinWorkspace\MvnMultiProj2 mvn clean install In der Reactor Summary wird wieder zu allen vier Projekten SUCCESS gemeldet. Multimodulprojekt mit Corporate POM Im letzten Beispiel wurde gezeigt, wie Sie bei vielen Unterprojekten gemeinsame Einstellungen (z.B. Java-Versionen und Versionen von Libs) zentral in einem Parent-Multimodulprojekt verwalten. Falls Sie mehrere Projekte (oder Multimodulprojekte) haben, sollten Sie noch einen Schritt weiter gehen, und auch die gemeinsamen Einstellungen der Projekte zentral verwalten, nämlich in einer "Corporate POM" (oder "Firmen-POM", "Projekt-POM", "Master POM", "Top POM", ...). Bitte verwechseln Sie die hier gemeinte selbsterstellte übergeordnete Corporate POM nicht mit der von Maven immer als Grundlage verwendeten Super POM pom-4.0.0.xml (die Sie in der Maven-Installation in der lib/maven-model-builder-3.5.0.jar finden). 1. Voraussetzung ist, dass Sie das Beispiel Multimodulprojekt mit Plugin-Management und Dependency-Management (MvnMultiProj2) erfolgreich durchgeführt haben. (Alternativ können Sie auch das Multimodulprojekt downloaden.) 2. Kopieren Sie die drei Unterprojekte in neue Verzeichnisse, erstellen Sie ein neues Multimodulprojekt in dem neuen Verzeichnis MvnMultiProj3 und legen Sie das neue Corporate-POM-Projekt MvnCorpPom an: cd \MeinWorkspace xcopy MvnJaxWs-Mp2 MvnJaxWs-Mp3\ /S xcopy MvnJettyWebApp-Mp2 MvnJettyWebApp-Mp3\ /S xcopy MvnPropApp-Mp2 MvnPropApp-Mp3\ /S md MvnMultiProj3 md MvnCorpPom 3. Ersetzen Sie sowohl in der MvnJaxWs-Mp3\pom.xml als auch in der MvnPropAppMp3\pom.xml jeweils an zwei Stellen: MvnMultiProj2 durch MvnMultiProj3. 4. Erzeugen Sie im MvnMultiProj3-Verzeichnis folgende pom.xml: 5. 6. <project> 7. <modelVersion>4.0.0</modelVersion> 8. <parent> 9. <groupId>de.meinefirma.meinprojekt</groupId> 10. <artifactId>MvnCorpPom</artifactId> 11. <version>1.0</version> 12. <relativePath>../MvnCorpPom/pom.xml</relativePath> 13. </parent> 14. <groupId>de.meinefirma.meinprojekt</groupId> 15. <artifactId>MvnMultiProj3</artifactId> 16. <version>1.0</version> 17. <packaging>pom</packaging> 18. <name>MvnMultiProj3</name> 19. <modules> 20. <module>../MvnJaxWs-Mp3</module> 21. <module>../MvnJettyWebApp-Mp3</module> 22. <module>../MvnPropApp-Mp3</module> 23. </modules> 24. </project> 25. Erzeugen Sie im MvnCorpPom-Verzeichnis folgende pom.xml: 26. 27. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 28. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 29. <modelVersion>4.0.0</modelVersion> 30. <groupId>de.meinefirma.meinprojekt</groupId> 31. <artifactId>MvnCorpPom</artifactId> 32. <version>1.0</version> 33. <packaging>pom</packaging> 34. <name>MvnCorpPom</name> 35. <properties> 36. <project.build.sourceEncoding>ISO-88591</project.build.sourceEncoding> 37. <project.reporting.outputEncoding>UTF8</project.reporting.outputEncoding> 38. </properties> 39. <build> 40. <pluginManagement> 41. <plugins> 42. <plugin> 43. <groupId>org.apache.maven.plugins</groupId> 44. <artifactId>maven-compiler-plugin</artifactId> 45. <version>3.6.1</version> 46. <configuration> 47. <source>1.7</source> 48. <target>1.7</target> 49. </configuration> 50. </plugin> 51. </plugins> 52. </pluginManagement> 53. </build> 54. <dependencyManagement> 55. <dependencies> 56. <dependency> 57. <groupId>junit</groupId> 58. <artifactId>junit</artifactId> 59. <version>4.12</version> 60. <scope>test</scope> 61. </dependency> 62. </dependencies> 63. </dependencyManagement> 64. </project> 65. Bauen Sie die Corporate POM und installieren Sie sie im lokalen Maven-Repository: cd \MeinWorkspace\MvnCorpPom mvn install 66. Wechseln Sie in das Multimodulprojekt und bauen Sie es inklusive aller Unterprojekte: cd \MeinWorkspace\MvnMultiProj3 mvn clean install In der Reactor Summary wird wieder zu allen vier Projekten SUCCESS gemeldet. 67. Testen Sie das Webprojekt: cd \MeinWorkspace\MvnJettyWebApp-Mp3 mvn jetty:run Rufen Sie die Webseite auf: start http://localhost:8080/MvnJettyWebApp 68. In diesem Beispiel erben die Untermodule vom übergeordneten Multimodulprojekt MvnMultiProj3, welches wiederum von der Corporate POM aus MvnCorpPom erbt. Alternativ ist es oft sinnvoll, wenn die Untermodule stattdessen direkt die Corporate POM aus MvnCorpPom einbinden. Dann ist es einfacher, mehrere verschiedene Multimodulprojekte für verschiedene Zwecke einzurichten, wie etwa für Integrationstests auf dem Entwicklungsrechner, Systemtests in einer Testumgebung, Continuous Integration, Release-Auslieferungen etc. (falls es zu aufwändig ist, dies über MavenProfile oder TestNG-Gruppen zu steuern). 69. Die Corporate POM können Sie erweitern und in beliebige weitere andere Projekte einbinden. In Firmen mit mehreren Projekten ist üblich, eine zentrale Corporate POM für firmenweite Voreinstellungen vorzusehen und zusätzlich pro Projekt eine Projekt-MasterPOM für Projektspezifisches zu erstellen. Die Projektmodule binden als Parent die Projekt-Master-POM ein, und diese erbt von der Corporate POM. 70. Das oben gezeigte Beispiel verwendet das so genannte "Flat Project Layout": Die MavenModule sind in der Verzeichnisstruktur parallel angeordnet. Alternativ könnten Sie die Maven-Projekthierarchie auch in einer hierarchischen Verzeichnisstruktur abbilden, also die Untermodule in Unterverzeichnisse zum Parent-Multimodulprojekt speichern. Zu den Vor- und Nachteilen sehen Sie sich den Vergleich des "Flat Project Layout" zum "Hierarchical Project Layout" an. 71. Falls die groupId und/oder die version der Untermodule identisch zum übergeordneten Parent-Modul (z.B. Projekt-Master-POM) sein soll, sollten Sie die Angaben nicht redundant wiederholen, sondern über <groupId>${project.groupId}</groupId> bzw. <version>${project.version}</version> einbinden. Im project-Kopf der abhängigen Module lassen Sie die Angaben einfach weg, dann werden sie automatisch geerbt (andernfalls würde Maven 3 melden: "[WARNING] 'groupId' contains an expression but should be a constant"). 72. Falls die version von Untermodulen nicht identisch zum übergeordneten Parent-Modul sein soll und es Abhängigkeiten zwischen den Untermodulen gibt, dann sollten Sie vermeiden, die Versionsnummer des von anderen Untermodulen verwendeten Untermoduls redundant direkt in den Dependencies einzutragen. Verwalten Sie auch die Versionsnummern der Untermodule in der Parent-POM (z.B. Projekt-Master-POM). Allerdings müssen Sie sicherstellen, dass alle Module stets die aktuellste Parent-POM verwenden und nicht irgendeine zufällig in einem (z.B. lokalen) Repository vorhandene. Besonders in der Startphase eines Projekts, in der es viele Änderungen gibt, kann es praktischer sein, die Projekt-Master-POM nicht im Maven-Repository zur Verfügung zu stellen (also für die Projekt-Master-POM weder mvn install noch mvn deploy auszuführen). Dann muss zusätzlich zum in Bearbeitung befindlichem Modul stets auch das Projekt-Master-POM-Modul aus dem Versionskontrollsystem (Git, Mercurial, Subversion, CVS, ...) ausgecheckt sein und beide Module können regelmäßig abgeglichen und aktualisiert werden. Damit dies funktioniert, muss beim Flat Project Layout der relativePath korrekt gesetzt sein (wie obiges Beispiel zeigt). 73. Bitte beachten Sie, dass es leider zwei kaum zu vermeidende Probleme gibt: o Bei allen Untermodulen muss die Versionsnummer des Parent-Moduls redundant eingetragen werden. Wenn sich die Versionsnummer des Parent-Moduls ändert, müssen alle abhängigen Module angepasst werden. o Strenggenommen gibt es häufig zyklische Abhängigkeiten zwischen den POMs: Untermodule müssen immer ihr Parent-Modul kennen (egal ob Projekt-MasterPOM oder Multimodulprojekt-POM). Umgekehrt müssen Multimodulprojekte immer ihre Untermodule kennen, und, falls Versionsnummern von Untermodulen in der Projekt-Master-POM verwaltet werden, hat auch diese Kenntnisse über die Untermodule. Während programmiertechnische Abhängigkeiten (z.B. Java-Abhängigkeiten) zwischen Modulen niemals zyklisch sein sollten, stellen diese genannten POMAbhängigkeitszyklen meistens kein Problem dar. 74. Planen Sie genau, wie ein Release für eine Auslieferung erstellt werden soll. Oft werden für jedes einzelne Modul eines Projekts folgende Schritte benötigt: 1. Abgleich mit dem Versionskontrollsystem (Git, Mercurial, Subversion, CVS, ...). 2. Prüfen, ob noch SNAPSHOT-Abhängigkeiten existieren. 3. Die Versionsbezeichnung in der POM wie für das Release gewünscht setzen (ohne SNAPSHOT) und die POM ins Versionskontrollsystem einchecken. 4. In allen abhängigen Modulen die neue Parent-Versionsbezeichnung eintragen und die Module ins Versionskontrollsystem einchecken. 5. Wenn in allen Modulen alle SNAPSHOT-Versionen ersetzt sind, setzen eines Release-Tags im Versionskontrollsystem für alle Module. 6. Build und Test sowie install ins lokale Maven-Repository. 7. Eventuell Deployment ins Remote-Maven-Repository (siehe hierzu auch Distribution-Deployment). 8. Dokumentationen, ER-Diagramme etc. aktualisieren. 9. Die Ergebnis-Artefakte in ein Auslieferverzeichnis kopieren. Dazu gehören nicht nur JAR-, WAR- und EAR-Dateien, sondern auch Datenbankänderungsskripte, Konfigurations-Properties und Übergabeprotokolle. 10. Die Versionsbezeichnung in den POMs auf die nächste Entwickler-SNAPSHOTVersion setzen und POMs einchecken. Bei diesen Schritten kann das Maven Release Plugin Unterstützung leisten. Voraussetzung ist eine korrekte SCM-Konfiguration. Bitte beachten Sie, dass hier wie auch beim Maven-SCM-Plugin mit "SCM" nicht das übergreifende "Software Configuration Management" gemeint ist, sondern lediglich "Source Code Management", also das Versionskontrollsystem (VCS). Falls bei komplizierteren Projekten das Maven Release Plugin nicht ausreicht: Sehen Sie sich das Versions Maven Plugin an. Sehen Sie sich auch folgende Erläuterungen an: Versionierung und Release-Build mit dem "Maven Release Plugin", dem "Versions Maven Plugin" und dem "Maven SCM Plugin". Site Report um Project Reports zur Sourcecodeanalyse erweitern Vorbemerkungen zu Site Project Reports Es gibt viele lohnenswerte Maven-Plugins, die automatisch Informationen zu Ihrem Projekt aufbereiten. Im Folgenden wird die Einbindung einiger solcher Maven-Plugins beispielhaft gezeigt. Bei den bisherigen Mini-Demos machen Project Reports kaum Sinn, aber vielleicht haben Sie ein größeres Projekt mit mehr Sourcedateien. Es werden diverse Metriken zur Sourcecodequalität ermittelt, zum Beispiel zu: Testabdeckung, potenzielle Fehler, Abhängigkeitsprobleme und strukturelle Probleme. Bitte beachten Sie: Einige Plugins entfalten ihre volle Stärke erst, wenn sie genauer konfiguriert werden. Nicht alle Metriken sind bei allen Projekten gleichermaßen sinnvoll oder vergleichbar. Sie können keinesfalls anhand solcher Metriken Softwareentwickler beurteilen, weil Sie das Gegenteil bewirken würden: Viele Metriken lassen sich leicht durch Tricks "verbessern", was allerdings zu Verschlechterungen des Sourcecodes führt. Trotz dieser Einschränkungen sind viele der Ergebnisse für den Softwareentwickler eine wichtige Hilfe zur Sourcecodeanalyse und Qualitätsverbesserung. Sie können beispielsweise folgende Ergebnisse erhalten: Project Information: Auflistung der Dependencies und "Dependency Tree" maven-project-inforeports-plugin Checkstyle Coding-Standards maven-checkstyle-plugin Cobertura Test Coverage Testabdeckung ermitteln (Cobertura nur bis Java 7) cobertura-maven-plugin CPD Report Code-Dubletten finden (Copy/Paste Detector) maven-pmd-plugin Dependency Analysis Auflistung der Dependencies und unused/undeclaredmaven-dependency-plugin Überprüfung FindBugs Report Potenzielle Fehler im Sourcecode per BytecodeAnalyse finden Dependencies Project Reports: findbugs-maven-plugin JavaDocs Javadoc-Dokumentation maven-javadoc-plugin PMD Report Potenzielle Probleme im Sourcecode finden maven-pmd-plugin Maven JXR HTML-basierende Verlinkung des Java-Sourcecodes maven-jxr-plugin Surefire Report Testergebnisse maven-surefire-reportplugin Tag List Bestimmte im Sourcecode enthaltene Tags auflisten (z.B. TODO, FIXME) taglist-maven-plugin Checkstyle, PMD und FindBugs scheinen sich auf den ersten Blick zu ähneln. Alle drei suchen nach bekannten Fehlermustern (Bug Patterns). Da sie aber verschieden funktionieren, finden sie unterschiedliche Fehler. Zum Beispiel analysieren Checkstyle und PMD den Sourcecode, während FindBugs den erzeugten Bytecode untersucht und so NullPointer, nicht geschlossene Datenströme und fehlerhafte Synchronisierungen aufspürt. Tragen Sie Ihr korrektes sourceEncoding ein, also das Encoding, mit dem Sie Ihre Sourcedateien speichern (unter Linux meistens UTF-8, unter Windows meistens Cp1252, unter Eclipse konfigurierbar), sowie Ihr gewünschtes reporting.outputEncoding (z.B. UTF-8). Binden Sie den folgenden <properties>-Block in die pom.xml ein: <properties> <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF8</project.reporting.outputEncoding> </properties> Site Project Reports mit Maven 3.5 1. Zu Site Reports gibt es seit Maven 3.0 erhebliche Änderungen, siehe hierzu: Maven Release Notes, Maven 3.x Compatibility Notes und Using maven-site-plugin with Maven 3. 2. Binden Sie die folgenden <properties>- und <reporting>-Blöcke in einem beliebigen Projekt in die pom.xml ein: 3. 4. ... 5. <properties> 6. <project.build.sourceEncoding>ISO-88591</project.build.sourceEncoding> 7. <project.reporting.outputEncoding>UTF8</project.reporting.outputEncoding> 8. </properties> 9. ... 10. <reporting> 11. <outputDirectory>${project.build.directory}/site</outputDirectory> 12. <plugins> 13. <plugin> 14. <groupId>org.apache.maven.plugins</groupId> 15. <artifactId>maven-site-plugin</artifactId> 16. <version>3.5.1</version> 17. </plugin> 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>2.9</version> </plugin> <!-- Falls Versionskontrollsystem konfiguriert ist: <plugin> <artifactId>maven-changelog-plugin</artifactId> </plugin> --> <plugin> <artifactId>maven-checkstyle-plugin</artifactId> <version>2.17</version> <configuration> <configLocation>google_checks.xml</configLocation> <includeTestSourceDirectory>true</includeTestSourceDirectory> <excludes>**/generated/*.java</excludes> </configuration> </plugin> <plugin> <artifactId>maven-dependency-plugin</artifactId> <version>2.10</version> <configuration> <repositoryUrl>--</repositoryUrl> <artifactItems /> </configuration> </plugin> <plugin> <artifactId>maven-javadoc-plugin</artifactId> <version>2.10.4</version> </plugin> <plugin> <artifactId>maven-jxr-plugin</artifactId> <version>2.5</version> </plugin> <plugin> <artifactId>maven-pmd-plugin</artifactId> <version>3.6</version> <configuration> <targetJdk>1.7</targetJdk> <format>xml</format> <linkXref>true</linkXref> <sourceEncoding>ISO-8859-1</sourceEncoding> <minimumTokens>100</minimumTokens> <excludes> <exclude>**/generated/*.java</exclude> </excludes> </configuration> </plugin> <plugin> <artifactId>maven-surefire-report-plugin</artifactId> <version>2.20</version> <configuration> <!-- Bei grossen Projekten auf false setzen: --> <showSuccess>true</showSuccess> </configuration> 74. 75. 76. </plugin> <plugin> <!-- Cobertura 2.1.1 und das cobertura-maven-plugin 2.7 funktionieren nur bis Java 7: --> 77. <groupId>org.codehaus.mojo</groupId> 78. <artifactId>cobertura-maven-plugin</artifactId> 79. <version>2.7</version> 80. </plugin> 81. <plugin> 82. <groupId>org.codehaus.mojo</groupId> 83. <artifactId>findbugs-maven-plugin</artifactId> 84. <version>3.0.3</version> 85. </plugin> 86. <plugin> 87. <groupId>org.codehaus.mojo</groupId> 88. <artifactId>taglist-maven-plugin</artifactId> 89. <version>2.4</version> 90. <configuration> 91. <tags> 92. <tag>fixme</tag> 93. <tag>FixMe</tag> 94. <tag>FIXME</tag> 95. <tag>@todo</tag> 96. <tag>todo</tag> 97. <tag>TODO</tag> 98. <tag>xxx</tag> 99. <tag>@deprecated</tag> 100. </tags> 101. </configuration> 102. </plugin> 103. <!-- Falls SonarQube verwendet wird: 104. <plugin> 105. <groupId>org.codehaus.sonar-plugins</groupId> 106. <artifactId>maven-report</artifactId> 107. <version>0.1</version> 108. </plugin> 109. --> 110. </plugins> 111. </reporting> 112. ... 113. Rufen Sie Folgendes auf und sehen Sie sich die Ergebnisseiten an: mvn site start target\site\index.html start target\site\project-info.html 114. Falls Sie beispielsweise so dass obige JAXB-Beispiel MvnJaxbApp untersuchen, erhalten Sie: Sourcecodeanalyse mit SonarQube (früher Sonar) Funktion von SonarQube SonarQube erleichtert Sourcecodeanalyse. SonarQube liefert ähnliche Ergebnisse, wie die oben unter "Site Report um Project Reports zur Sourcecodeanalyse erweitern" gezeigten Plug-ins. Die wichtigsten Unterschiede sind: Vorteile: Die Ergebnisse sind übersichtlicher aufbereitet, komfortabler auszuwerten und inklusive einer Historisierung. Außerdem braucht das zu untersuchende Projekt nicht modifiziert zu werden (anders als beim obigen Site Report, der nur mit zusätzlichen Einträgen in der POM vollständige Ergebnisse liefert). Nachteil: Es muss ein zusätzlicher SonarQube-Server installiert und gestartet werden. Idealerweise wird SonarQube in ein "Continuous Integration"-System eingebunden (z.B. Jenkins, Hudson oder TeamCity), siehe hierzu auch: Continuous Integration mit Jenkins / Hudson und Maven 3. SonarQube ermittelt diverse Metriken zur Sourcecodequalität, zum Beispiel zu: typische Programmierfehler, potenzielle Fehler, Testabdeckung, Abhängigkeitsprobleme und strukturelle Probleme. Auch hier gilt wieder: Einige Plugins entfalten ihre volle Stärke erst, wenn sie genauer konfiguriert werden. Nicht alle Metriken sind bei allen Projekten gleichermaßen sinnvoll oder vergleichbar. Sie können keinesfalls anhand solcher Metriken Software oder Softwareentwickler beurteilen, weil Sie das Gegenteil bewirken würden: Viele Metriken lassen sich leicht durch Tricks "verbessern", was allerdings zu Verschlechterungen des Sourcecodes führt. SonarQube ist für die Kontrolle durch das Management ungeeignet, aber eine wichtige Hilfe für den Softwareentwickler zur Sourcecodeanalyse und Qualitätsverbesserung. Dokus Siehe: Documentation, Requirements, Installing the Server, Running SonarQube as a Service on Windows, ... on Linux, Analyzing with SonarQube Scanner for Maven Basis-Installation und Vorbereitung 1. Downloaden Sie SonarQube (z.B. sonarqube-6.3.zip) von http://www.sonarqube.org/downloads. Entzippen Sie SonarQube, z.B. unter Windows nach \Tools oder unter Linux nach /opt. 2. Neuere SonarQube-Versionen setzen mindestens Java 8 voraus. Überprüfen Sie dies mit: java -version 3. Prüfen Sie, ob die SonarQube-Default-Portnummer 9000 noch frei ist: netstat -an | find ":9000" Falls der Port nicht frei ist: Tragen Sie in \Tools\SonarQube\conf\sonar.properties bei "sonar.web.port" eine andere Portnummer ein (und entfernen Sie das vorangestellte Auskommentierungszeichen '#'). 4. Prüfen Sie, ob die für die SonarQube-Datenbank benötigte Portnummer noch frei ist. Falls Sie die defaultmäßige integrierte Datenbank verwenden wollen: Je nach SonarQube-Version ist dies eine Derby Database (mit Port 1527) oder eine H2 Database (mit Port 9092). Prüfen Sie zum Beispiel so: netstat -a netstat -an | find ":1527" netstat -an | find ":9092" 5. Falls Sie eine Oracle-Datenbank verwenden wollen, überprüfen Sie Folgendes: Die DB muss für das Character-Encodung UTF-8 konfiguriert sein, optimalerweise so: NLS_CHARACTERSET = AL32UTF8. Überprüfen Sie dies mit dem SQL-Kommando "Select * from nls_database_parameters;". Zumindest bei früheren SonarQube-Versionen durfte die Sortierung nicht für GERMAN konfiguriert sein, sondern so: NLS_SORT = BINARY. Überprüfen Sie dies mit demselben SQL-Kommando. Laut Requirements soll ein Oracle-11.2.x-Treiber verwendet werden, und kein Oracle12.x-Treiber ("Oracle 12.x drivers are not supported"). Downloaden und kopieren Sie den Oracle-11.2.x-JDBC-Treiber (z.B. ojdbc6.jar in Version 11.2.0.4.0) nach: \Tools\SonarQube\extensions\jdbc-driver\oracle. Tragen Sie in \Tools\SonarQube\conf\sonar.properties die DB-ConnectionParameter ein: sonar.jdbc.username=... sonar.jdbc.password=... sonar.jdbc.url=jdbc:oracle:thin:@localhost:1521:XE sonar.jdbc.driverClassName=oracle.jdbc.driver.OracleDriver sonar.jdbc.validationQuery=select 1 from dual SonarQube-Server starten 1. Rufen Sie auf (passen Sie den Pfad an): Unter Windows: start "SonarQube-Server" \Tools\SonarQube\bin\windows-x8664\StartSonar.bat bzw. unter Linux: /opt/sonarqube/bin/linux-x86-64/sonar.sh start Warten Sie, bis der SonarQube-Server fertig gestartet ist, was schon mal mehrere Minuten dauern kann. Sehen Sie sich die Logdateien an: type \Tools\SonarQube\logs\*.log bzw. cat /opt/sonarqube/logs/*.log Rufen Sie auf: start http://localhost:9000 2. Falls Sie eine Fehlermeldung erhalten ähnlich zu: Encountered an error running main: java.io.IOException: Unable to create directory \Tools\SonarQube\temp Dann rufen Sie StartSonar.bat mit Admin-Rechten auf (über rechter Maustaste). 3. Um SonarQube als automatisch beim PC-Start startenden Service einzurichten, sehen Sie sich an: Running SonarQube as a Service on Windows, Running SonarQube as a Service on Linux, zusätzliche Hinweise. Besondere Vorgaben 1. Dies ist normalerweise unnötig, aber falls Sie besondere Einstellungen benötigen, können diese folgendermaßen in dem zu analysierenden Projekt in die pom.xml (bzw. falls vorhanden besser in eine Master-POM) eingetragen werden: 2. 3. 4. 5. ... <properties> <!-- Wird nur benoetigt, wenn eine abweichende SonarQube-URL (z.B. Remote) eingetragen werden muss: --> 6. <sonar.host.url>http://localhost:9000</sonar.host.url> 7. ... 8. </properties> 9. <build> 10. <plugins> 11. <!-- Wird nur benoetigt, falls eine bestimmte Versionsnummer vorgegeben werden muss: --> 12. <plugin> 13. <groupId>org.sonarsource.scanner.maven</groupId> 14. <artifactId>sonar-maven-plugin</artifactId> 15. <version>3.2</version> 16. </plugin> 17. ... 18. </plugins> 19. </build> 20. ... Aufruf und Analyse für ein einzelnes Maven-Projekt 1. Rufen Sie auf (passen Sie den Pfad an): cd \MeinWorkspace\<MeinProjekt> mvn clean install sonar:sonar Warten Sie, bis der Analyseprozess fertig durchgelaufen ist, was schon mal mehrere Minuten dauern kann. Rufen Sie anschließend auf: start http://localhost:9000 2. Falls beim sonar:sonar-Aufruf falsche Versionen gestartet werden, oder die SonarQube-Server-URL hinzugefügt werden soll, können Sie statt des abgekürzten Kommandos folgendes vollständige Kommando verwenden (vorher anpassen): mvn clean install org.sonarsource.scanner.maven:sonar-mavenplugin:3.2:sonar -Dsonar.host.url=http://localhost:9000 3. Falls Sie die Java Code Coverage der JUnit-Tests (= Testabdeckung) erfassen wollen und SonarQube keine Ergebnisse meldet, verwenden Sie folgendermaßen das JaCoCo-Plugin: cd \MeinWorkspace\<MeinProjekt> mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install Dmaven.test.failure.ignore=true mvn sonar:sonar start http://localhost:9000 Falls Sie das maven-surefire-plugin oder das maven-failsafe-plugin verwenden, beachten Sie die Hinweise zum JaCoCo-Maven Plug-in und zum prepare-agent-Goal. Falls Sie auf Probleme stoßen, sehen Sie sich an: Probleme mit dem JaCoCo-MavenPlugin. Aufruf und Analyse für viele Maven-Projekte 1. Falls Sie mehrere Maven-Projekte in parallelen Unterverzeichnissen zu einem gemeinsamen Projekteverzeichnis haben, können Sie mit einem einzigen Aufruf alle Maven-Projekte o mit SonarQube analysieren und o zusätzlich Site-Reports erstellen. Für die SonarQube-Analyse brauchen Sie in den einzelnen Projekten nichts vorzubereiten. Die in der Windows-Batchdatei erstellten Site-Reports dagegen machen erst dann wirklich Sinn, wenn Sie geeignete Vorgaben in den jeweiligen POMs (bzw. falls vorhanden in einer gemeinsamen Master-POM) erstellt haben, etwa so wie oben unter Site Project Reports gezeigt. 2. Windows: Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Unterverzeichnis für die Windows-Batchdatei: cd \MeinWorkspace md SonarQubeUndSiteReport cd SonarQubeUndSiteReport Erzeugen Sie im SonarQubeUndSiteReport-Verzeichnis die Batchdatei: runSonarQubeUndSiteReport-all.bat @title run-SonarQubeUndSiteReport-all.bat cls @echo. @echo -----------------------------------------------------------------------@echo run-SonarQubeUndSiteReport-all.bat @echo -----------------------------------------------------------------------@echo. set _SONAR_START_BAT=\Tools\SonarQube\bin\windows-x86-64\StartSonar.bat set _SITE_REPORTS_DIR=\MeineSiteReports set _IGNORED_DIRS=MvnAppSrvFailsaveIntTest MvnAppSrvIntTest MvnMultiProj1 MvnMultiProj2 MvnMultiProj3 MvnSign SonarQubeUndSiteReport @if not exist "%_SONAR_START_BAT%" goto _Fehler @if not exist "%_SITE_REPORTS_DIR%" md "%_SITE_REPORTS_DIR%" @if not exist "%_SITE_REPORTS_DIR%" goto _Fehler netstat -an | find "0.0.0.0:9000" @if not errorlevel 1 goto _Sonar_Server_fertig start "SonarQube" "%_SONAR_START_BAT%" :_Warte_Sonar_Server @echo Warten auf den SonarQube-Server ... @ping -n 5 127.0.0.1>nul @netstat -an | find "0.0.0.0:9000" @if errorlevel 1 goto _Warte_Sonar_Server :_Sonar_Server_fertig start http://localhost:9000 pushd . cd .. @echo off FOR /F %%i in ('dir /B /A:D') DO ( echo. call :checkignored %%i if not errorlevel 1 ( if exist %%i\pom.xml ( echo. echo -----------------------------------------------------------------------echo Projektverzeichnis: %%i echo -----------------------------------------------------------------------echo on cd %%i echo. echo ---- SonarQube fuer %%i: echo. call mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install -Dmaven.test.failure.ignore=true call mvn sonar:sonar if errorlevel 1 goto _Fehler @echo on echo. echo ---- Site-Reports fuer %%i: echo. if exist "%_SITE_REPORTS_DIR%\%%i\site" rd /S /Q "%_SITE_REPORTS_DIR%\%%i\site" call mvn clean site if errorlevel 1 goto _Fehler echo on xcopy target\site "%_SITE_REPORTS_DIR%\%%i\site\" /S if exist %_SITE_REPORTS_DIR%\%%i\site\project-info.html ( start %_SITE_REPORTS_DIR%\%%i\site\project-info.html ) @echo. cd .. echo off ) ) ) @echo start http://localhost:9000 @goto _fertig :checkignored FOR /D %%j in (%_IGNORED_DIRS%) DO IF "%1"=="%%j" EXIT /B 1 exit /B 0 :_Fehler @echo. @echo -----------------------------------------------------------------------@echo !!! Fehler !!! @echo -----------------------------------------------------------------------@echo pause :_fertig @echo on @echo. popd @echo. 3. Linux: Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Unterverzeichnis für das Linux-Shell-Skript: cd ~/MeinWorkspace mkdir SonarQube cd SonarQube Erzeugen Sie im SonarQube-Verzeichnis das Linux-Shell-Skript: run-SonarQubeall.sh #!/bin/bash echo echo -----------------------------------------------------------------------echo run-SonarQube-all.sh echo -----------------------------------------------------------------------echo _IGNORED_DIRS=" MvnAppSrvFailsaveIntTest MvnAppSrvIntTest MvnMultiProj1 MvnMultiProj2 MvnMultiProj3 MvnSign SonarUndSiteReport " if ! netstat -ant | grep -q ':9000' then /opt/sonarqube/bin/linux-x86-64/sonar.sh start & fi while ! netstat -ant | grep -q ':9000' do echo Warten auf den SonarQube-Server ... ; sleep 5 done echo cd .. echo "Aktuelles Verzeichnis: "$(pwd) echo ls -l echo for verzeichnis in * do echo -e "\n------------------------------------------------------" if [[ $_IGNORED_DIRS == *${verzeichnis}* ]] then echo "---- "${verzeichnis}" ist zu ignorieren ----" elif [ ! -d "${verzeichnis}" ] then echo "---- "${verzeichnis}" ist kein Verzeichnis ----" elif [ ! -f "${verzeichnis}/pom.xml" ] then echo "---- "${verzeichnis}" enthaelt keine pom.xml ----" else echo "---- "${verzeichnis}":" cd "${verzeichnis}" mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install Dmaven.test.failure.ignore=true mvn sonar:sonar || exit 1 cd .. fi done echo -e "\n------------------------------------------------------\n" ( speaker-test -t sine -f 1000 > /dev/null )& pid=$! ; sleep 0.1s ; kill -9 $pid echo -e "\n++++ OK ++++\n" 4. Je nach SonarQube-Version können Sie beispielsweise erhalten: Konfiguration und SonarQube-Weboberfläche (nur bis SonarQube 5.6.x und teilweise 6.0, ab 6.1 gibt es massive Änderungen, z.B. gibt es kein Dashboard mehr) 1. Konfigurieren Sie das Aussehen der Dashboard-Startseite, indem Sie sich über "Log in" mit "admin/admin" einloggen und "Dashboards" | "Home" | "Configure widgets" anklicken. Meistens empfiehlt es sich, das "Measure Filter as List"-Widget auf die gesamte Breite zu vergrößern, indem Sie rechts oben nicht das zweigeteilte Rechteck, sondern stattdessen das oberste ungeteilte Rechteck aktivieren. Außerdem sollten Sie dem "Measure Filter as List"-Widget noch eine Spalte für die Testabdeckung hinzufügen über: Im "Measure Filter as List"-Widget klicken auf "Projects" | "Change Columns" | in der Drop-Down-Liste unter Tests "Coverage" wählen | "Add Column" | die neue Spalte eins nach links verschieben | "Close" | "Save" | zurück mit "Dashboards". Insgesamt könnten beispielsweise folgende weiteren Spalten sinnvoll sein: NAME, VERSION, LOC, SQALE RATING, TECHNICAL DEBT, COVERAGE, BLOCKER ISSUES, CRITICAL ISSUES, ISSUES, LAST ANALYSIS. Außerdem kann eine Ansicht "Measure Filter as Treemap" sinnvoll sein, beispielsweise mit "Size: Lines of Code" und "Color: Technical Dept Ratio". 2. Sehen Sie sich die Konfiguration an und führen Sie Anpassungen durch: Loggen Sie sich mit "admin/admin" ein, klicken Sie auf "Settings" | "Quality Profiles", und wählen Sie das geeignetste Ausgangsprofil, falls vorhanden am besten "Sonar way with FindBugs". Kopieren Sie das gewählte Ausgangsprofil mit "Copy", vergeben Sie einen neuen Namen (ohne Sonderzeichen und ohne Leerzeichen) und setzen Sie die Kopie mit "Set as default" als Standard. Klicken Sie auf den neuen Profilnamen und klicken Sie auf die "Coding rule"-Namen, um die Regeln zu konfigurieren. Kandidaten für eine etwas weniger strenge Bewertung könnten sein: "Avoid commented-out lines of code", "Dodgy - instanceof will always return true", "Dodgy - int division result cast to double or float", "Dodgy - Redundant nullcheck of value known to be non-null", "Dodgy Write to static field from instance method", "Duplicated blocks", "Execution of the Garbage Collector should be triggered only by the JVM", "Left curly braces should be located at the end of lines of code", "Malicious code vulnerability - May expose internal representation by incorporating reference to mutable object", "Malicious code vulnerability - May expose internal representation by returning reference to mutable object", "Methods should not be too complex", "Performance - Inefficient use of keySet iterator instead of entrySet iterator", "Right curly braces should be located at the beginning of lines of code", "Statements should be on separate lines", "Switch cases should end with an unconditional break statement", "System.exit(...) and Runtime.getRuntime().exit(...) should not be called", "The Object.finalize() method should never be overridden" 3. Falls es das kombinierte Profil "Sonar way with FindBugs" nicht gibt, aber stattdessen die zwei Profile "Sonar way" und "FindBugs": Folgendermaßen können Sie Ihre Projektmodule mit beiden Profilen analysieren: call mvn clean install sonar:sonar -Dsonar.profile="MeinFindBugsProfil" -Dsonar.branch=FindBugs call mvn clean install sonar:sonar -Dsonar.profile="MeinSonarWayProfil" -Dsonar.branch=SonarWay 4. Falls Sie mehr als 20 Projekte haben und das SonarQube-Dashboard mehr als nur 20 Projekte pro Seite anzeigen soll, erhöhen Sie die Page Size unter "Configure widgets" im "Measure Filter as List"-Widget: "Edit" | "Page Size". 5. Falls Sie bestimmte Packages oder Klassen aus der Analyse ausklammern wollen, zum Beispiel automatisch generierte Klassen (etwa per JAXB), dann geben Sie diese über die Property "sonar.exclusions" an. Dabei können mehrere durch Komma getrennte Pfade angegeben werden, beispielsweise: "meinpackage1/generated/*.java, meinpackage2/**/generated/*.java". 6. Um Speicherplatz zu sparen, sollten Sie die Aufbewahrungszeiten konfigurieren über: "Log in" | "Settings" | "Configuration" | "General Settings" | "Database Cleaner". 7. Unter "Settings" | "Configuration" | "Backup" können Sie die Konfiguration über den "Backup"-Button als XML-Datei speichern, entweder als Backup oder um sie auf einen anderen PC zu übertragen. Das Einlesen erfolgt auf derselben Seite über "Restore". 8. Erläuterungen zu den verwendeten Metriken finden Sie unter: http://docs.sonarqube.org/display/SONAR/Metric+definitions 9. Sehen Sie sich die Doku zu SonarQube an: http://docs.sonarqube.org/display/SONAR/Documentation 10. Insbesondere in Continuous-Integration-Systemen (z.B. mit Jenkins/Hudson) sollten Sie den SonarQube-Server beim Booten automatisch als Windows-Dienst starten. Sehen Sie sich hierzu an: http://docs.sonarqube.org/display/SONAR/Running+SonarQube+as+a+Service+on+Win dows. Beachten Sie, dass einige SonarQube-Batchdateien (z.B. "SonarQube\bin\windowsx86-64\InstallNTService.bat") nur in einem Kommandozeilenfenster mit Administratorrechten ausgeführt werden können: "Start" | "Alle Programme" | "Zubehör" | "Eingabeaufforderung" mit rechter Maustaste und "Als Administrator ausführen". Beachten Sie, dass Sie beim installierten Windows-Dienst eventuell WindowsAnmeldeinformationen eintragen müssen: "Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste" | Doppelklick auf "SonarQube" | "Anmelden". 11. Beachten Sie, dass für systematischen und wiederholten Einsatz empfohlen wird, statt der defaultmäßigen integrierten Datenbank eine externe Datenbank zu verwenden. Diesen und viele weitere Hinweise finden Sie unter: http://docs.sonarqube.org/display/SONAR/Setup+and+Upgrade 12. Falls Sie JSP oder JSF verwenden, empfiehlt es sich, zusätzlich das SonarQube Web Plugin zu installieren: Downloaden Sie die Plugin-Jar-Datei (z.B. sonar-web-plugin-2.3.jar) von http://docs.sonarqube.org/display/SONAR/Web+Plugin, kopieren Sie sie in den SonarQube-Plugin-Ordner (z.B. \Tools\SonarQube\extensions\plugins) und starten Sie SonarQube neu. Der Aufruf kann zum Beispiel folgendermaßen erfolgen: cd \MeinWorkspace\<MeinWebProjekt> mvn clean sonar:sonar -Dsonar.language=web Dsonar.dynamicAnalysis=false Dsonar.web.sourceDirectory=src/main/webapp -Dsonar.branch=Web start http://localhost:9000 Probleme 1. Falls Sie die Ermittlung des RCI (Rules Compliance Index) vermissen: Den gab es nur in früheren SonarQube-Versionen. In den aktuellen Versionen gibt es stattdessen die "Technische Schuld" ("Technical Debt"). Falls Sie auch in aktuellen SonarQube-Versionen den RCI ermittelt haben wollen, gibt es verschiedene Optionen, beispielsweise: a) Sie können das sonar-rci-plugin von Robert Willems of Brilman installieren. Dann finden Sie das RCI-Ergebnis unter der jeweiligen Projekte-Startseite unter dem Tabulator-Menüpunkt "Measures". b) Alternativ können Sie die Violations per SonarQube-REST-Schnittstelle (Web API) abfragen und den RCI selbst berechnen. Dies leistet für SonarQube in der Version 6.3 das Programm "SonarQube63Rci". Es liefert eine Liste der RCIs zu allen in SonarQube berücksichtigten Projekten sowohl als HTML-Datei, sowie auch als CSV-Datei, welche in OpenOffice Calc und Microsoft Excel geladen werden kann. Sie finden "SonarQube63Rci" im Download der Maven-Projekte. 2. Falls Sie mit SonarQube serverseitige Probleme haben, sehen Sie sich die Logdateien an: type \Tools\SonarQube\logs\*.log 3. Falls Sie mit Ihrem "Quality Profile" Probleme haben, z.B. die Fehlermeldung [ERROR] Quality profile not found : 'Sonar way with Findbugs' on language 'java' dann stellen Sie sicher, dass es den Profilnamen gibt, der Profilname keine Sonderzeichen und keine Leerzeichen enthält, und dass es nicht zweierlei SonarQube-Installationen auf demselben PC gibt. 4. Falls Sie folgende Fehlermeldung erhalten: [ERROR] Failed to execute goal org.sonarsource.scanner.maven:sonarmaven-plugin:3.2:sonar (default-cli) on project [...]: SCM provider was set to "..." but no SCM provider found for this key. Supported SCM providers are git,svn oder [ERROR] Failed to execute goal org.codehaus.mojo:sonar-mavenplugin:2.5:sonar (default-cli) on project [...]: SCM provider was set to "[...]" but no provider found for this key. Supported providers are git,svn Dann können Sie den SCM-Sensor deaktivieren: Als "admin" anmelden, und dann je nach Version: entweder: "Administration" | "SCM" | "Disable the SCM Sensor": "True" | "Save", oder: "Settings" | "General Settings" | "SCM" | "Disable the SCM Sensor" --> "True". 5. Falls Sie eine Oracle-Datenbank verwenden wollen und folgende Fehlermeldung erhalten: org.sonar.api.utils.MessageException: Oracle NLS_CHARACTERSET does not support UTF8 Dann muss der Characterset in der Oracle-Datenbank auf AL32UTF8 umgestellt werden. Überprüfen Sie dies mit dem SQL-Kommando "Select * from nls_database_parameters;" und sehen Sie sich an: Installing the Database, Ändern des Oracle-Datenbank-Character-Encodings. 6. Falls Sie folgende Fehlermeldung erhalten: Der Dienst [...] konnte nicht gestartet werden [...] Der Prozess wurde unerwartet beendet Suchen Sie in der \Tools\SonarQube\logs\sonar.log nach genaueren Fehlermeldungen. Überprüfen Sie, ob Sie beim installierten Windows-Dienst korrekte WindowsAnmeldeinformationen eingetragen haben. Eventuell müssen Sie in der \Tools\SonarQube\conf\wrapper.conf die Zeile wrapper.java.command=java ändern zu: wrapper.java.command=C:\Program Files\Java\jdk1.7\bin\java.exe 7. Falls Sie unter 64-bit-Windows installieren und eine ältere Versionen von SonarQube verwenden: Ältere Versionen vom SonarQube-Wrapper funktionieren eventuell nur mit einem 32-bit-JDK. Dieses muss installiert (z.B. nach C:\Program Files (x86)\Java\jdk1.7-32bit) und bei SonarQube eingetragen werden. Hierzu in der \Tools\SonarQube\conf\wrapper.conf die Zeile wrapper.java.command=java ändern zu: wrapper.java.command=C:\Program Files (x86)\Java\jdk1.7-32bit\bin\java 8. JaCoCo: Falls Sie Probleme mit der Erfassung und Anzeige der Java Code Coverage der JUnitTests mit dem JaCoCo-Plug-in haben: Prüfen Sie, ob im target-Verzeichnis die Datei jacoco.exec erzeugt wurde. Beobachten Sie, ob im Kommandozeilenfenster angezeigt wird: ... [INFO] Sensor JaCoCoSensor [java] [INFO] Analysing .../target/jacoco.exec [INFO] Sensor JaCoCoSensor [java] (done) | time=12ms ... Falls Sie das maven-surefire-plugin oder das maven-failsafe-plugin verwenden, beachten Sie die Hinweise zum JaCoCo-Maven Plug-in und zum prepare-agent-Goal. Falls irgendwo (z.B. bei der Konfiguration vom maven-surefire-plugin oder mavenfailsafe-plugin) in einem Plug-in die Property argLine gesetzt wird, funktioniert die JaCoCo-Parameterweitergabe nicht, dadurch wird die Datei jacoco.exec nicht erzeugt, und Sie erhalten keine Code-Coverage-Ergebnisse. Um dies zu vermeiden, gibt es verschiedene Optionen: a) Sie können in den relevanten Plug-ins die argLine-Anweisungen überall (auch in Parent-POMs) entfernen, und die gewünschten Properties global im properties-Block definieren: <properties> <argLine>...</argLine> </properties> b) Alternativ können Sie in den Plug-ins den von JaCoCo benötigten argLine-Zusatz als festen Wert der argLine-Property hinzufügen, indem Sie statt <argLine>...</argLine> schreiben: <argLine>... javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/0.7.9 /org.jacoco.agent-0.7.9runtime.jar=destfile=target/jacoco.exec</argLine> Allerdings müssen Sie bei dieser Variante eventuell bei einem Versions-Upgrade den benötigten Ausdruck erneut ermitteln (mit mvn -X ...) und anpassen. c) Falls Sie keine Änderung in den POMs machen können, können Sie alternativ folgenden Trick anwenden: Falls in den POMs beim maven-surefire-plugin eine <argLine>...</argLine> definiert ist, können Sie den Kommandozeilenparameter maven.surefire.debug missbrauchen, um im Testfall die argLine per Kommandozeile zu erweitern (siehe hierzu surefire:test, debugForkedProcess): Unter Linux könnte dies beispielsweise so aussehen (die zwei Kommandos in jeweils einer Zeile): _MVN_LOCAL_REPO=$( mvn help:effective-settings | grep localRepository | sed "s|<localRepository>||" | sed "s|</localRepository>||" | tr -d '[:space:]' ) mvn clean install -Dmaven.surefire.debug="javaagent:$_MVN_LOCAL_REPO/org/jacoco/org.jacoco.agent/0.7.9/org.jacoco .agent-0.7.9-runtime.jar=destfile=target/jacoco.exec" org.jacoco:jacoco-maven-plugin:prepare-agent org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar Dsonar.host.url=http://localhost:9000 -Dmaven.test.failure.ignore=true Kontrollieren Sie das Ergebnis, indem Sie Maven mit -X im Debug-Modus starten, und sich beim Start der Tests die Ausgabe der "Forking command line" ansehen. Falls Sie das SonarQube-Kommando durch Jenkins ausführen lassen, und falls das lokale Maven-Repository jeweils im Workspace-Verzeichnis angelegt wird, können Sie das jeweilige Repo-Verzeichnis so ähnlich ermitteln wie: "_MVN_LOCAL_REPO=$WORKSPACE/.repository" (vermeiden Sie dabei Leerzeichen in den Pfaden). d) Alternativ können Sie vermeiden, dass der vorherige argLine-Wert überschrieben wird, wenn Sie in den Plug-ins statt <argLine>...</argLine> schreiben: <argLine>... ${argLine}</argLine> Falls das JaCoCo-Maven-Plug-in nicht für jedes Goal aktiv ist, kann dies allerdings zu einer der folgenden Fehlermeldungen führen: Fehler: Hauptklasse ${argLine} konnte nicht gefunden oder geladen werden Error: Could not find or load main class ${argLine} In diesem Fall kann die Lösung sein, dass Sie über eine Kommandozeilenvariable verschiedene Profile in der pom.xml aktivieren: ... <profiles> <profile> <id>ohne-JaCoCo</id> <activation> <activeByDefault>true</activeByDefault> <property> <name>!JaCoCo</name> </property> </activation> <properties> <jacoco.argLine></jacoco.argLine> </properties> </profile> <profile> <id>SonarQube-mit-JaCoCo</id> <activation> <property> <name>JaCoCo</name> </property> </activation> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.7.9</version> <executions> <execution> <id>jacoco-initialize</id> <goals> <goal>prepare-agent</goal> </goals> <configuration> <propertyName>jacoco.argLine</propertyName> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.12</version> <configuration> <argLine>-Duser.language=de -Duser.region=DE -Xmx512m Dfile.encoding=UTF-8 ${jacoco.argLine}</argLine> </configuration> </plugin> </plugins> </build> ... So wird bei normalen Kommandos wie z.B. "mvn clean install" die Property jacoco.argLine als Leerstring initialisiert, um die Fehlermeldung zu vermeiden. Für SonarQube-Analysen inklusive JaCoCo-Plug-in führen Sie aus: mvn clean install sonar:sonar -DJaCoCo Dann entfällt die Vorinitialisierung der Property jacoco.argLine und das JaCoCo-Plugin wird aktiviert. d) Weitere Ideen finden Sie unter: JaCoCo JVM args and Surefire JVM args together in Maven. 9. Cobertura: Falls Sie die Code Coverage by Unit Tests (Testabdeckung) nicht mit JaCoCo sondern stattdessen mit Cobertura ermitteln wollen, verfahren Sie wie folgt. Beachten Sie, dass Cobertura 2.1.1 und das cobertura-maven-plugin 2.7 nur bis Java 7 funktionieren. Während für JaCoCo in SonarQube kein Plug-in benötigt wird, muss für Cobertura das sonar-cobertura-plugin in einer passenden Version in SonarQube installiert werden. Beispielsweise für SonarQube 6.3 wird sonar-cobertura-plugin-1.9.jar benötigt. Downloaden Sie die passende Version von: https://github.com/galexandre/sonarcobertura/releases/. Kopieren Sie die heruntergeladene sonar-cobertura-plugin-Jar-Datei (z.B. sonarcobertura-plugin-1.9.jar) in das extensions/plugins-Verzeichnis der SonarQubeInstallation (siehe hierzu https://docs.sonarqube.org/display/SONAR/Installing+a+Plugin). Starten Sie SonarQube neu (z.B. unter Linux per "bin/linux-x86-64/sonar.sh restart"). Jetzt können Sie SonarQube-Reports inklusive Cobertura-Testabdeckungsergebnisse erzeugen: cd \MeinWorkspace\<MeinProjekt> mvn clean install cobertura:cobertura -Dcobertura.report.format=xml --> im target/site/cobertura-Verzeichnis entsteht die Datei coverage.xml. mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar Dsonar.host.url=http://localhost:9000 --> u.a. wird gemeldet: ... [INFO] Sensor CoberturaSensor [cobertura] [INFO] parsing .../target/site/cobertura/coverage.xml [INFO] Sensor CoberturaSensor [cobertura] (done) | time=5ms ... start http://localhost:9000 --> SonarQube zeigt die Testabdeckung an. Wenn Sie wollen, können Sie die beiden Maven-Kommandos auch in einer einzigen Zeile ausführen: mvn clean install cobertura:cobertura org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar Dcobertura.report.format=xml -Dsonar.host.url=http://localhost:9000 Falls Sie folgende Fehlermeldung erhalten: Unable to register extension org.sonar.plugins.cobertura.CoberturaSensor from plugin 'cobertura': Lorg/sonar/api/scan/filesystem/ModuleFileSystem;: org.sonar.api.scan.filesystem.ModuleFileSystem Dann passt die Version vom sonar-cobertura-plugin nicht zur SonarQube-Version, siehe hierzu: https://github.com/galexandre/sonar-cobertura/releases/. Für SonarQube 6.3 funktioniert sonar-cobertura-plugin-1.9.jar. Erstellung von Javadoc- und Source-Archiven Das folgende Beispiel zeigt, wie Sie mit dem "maven install"-Kommando nicht nur .jarArtefakte, sondern auch Javadoc und gezippten Sourcecode zu beliebigen Maven-Projekten erzeugen. Dazu werden das Maven Javadoc Plugin und das Maven Source Plugin verwendet. Falls Sie kein vorhandenes Maven-Projekt verwenden wollen, legen Sie folgendermaßen ein neues an: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnSourceUndJavadoc cd MvnSourceUndJavadoc Ersetzen Sie den Inhalt der pom.xml durch (bzw. fügen Sie bei anderen Projekten die <plugin>Blöcke ein): <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnSourceUndJavadoc</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MvnSourceUndJavadoc</name> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.10.4</version> <executions> <execution> <id>attach-javadoc</id> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>3.0.1</version> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project> Führen Sie aus: cd \MeinWorkspace\MvnSourceUndJavadoc mvn clean install Die Ergebnisse werden im lokalen Maven-Repository generiert. Setzen Sie folgende Environmentvariable entsprechend Ihres Repo-Pfades, um die Ergebnisse zu untersuchen, beispielsweise: set MVN_REPO=%USERPROFILE%\.m2\repository oder set MVN_REPO=D:\Tools\Maven3-Repo Sehen Sie sich das Ergebnis in Ihrem lokalen Maven-Repository an: dir %MVN_REPO%\de\meinefirma\meinprojekt\MvnSourceUndJavadoc\1.0-SNAPSHOT Zusätzlich zu den üblichen .jar- und .pom-Dateien erhalten Sie eine ...-javadoc.jar und eine ...-sources.jar. Sehen Sie sich die Javadoc an (die beim Standardbeispiel natürlich nur sehr kurz ausfällt): md \MeinWorkspace\MvnSourceUndJavadoc\javadoc cd \MeinWorkspace\MvnSourceUndJavadoc\javadoc jar xvf %MVN_REPO%\de\meinefirma\meinprojekt\MvnSourceUndJavadoc\1.0SNAPSHOT\MvnSourceUndJavadoc-1.0-SNAPSHOT-javadoc.jar start index.html Listen Sie den Inhalt der ...-sources.jar auf: jar tvf %MVN_REPO%\de\meinefirma\meinprojekt\MvnSourceUndJavadoc\1.0SNAPSHOT\MvnSourceUndJavadoc-1.0-SNAPSHOT-sources.jar Signaturen erzeugen Falls Sie Artefakte übers Internet weitergeben wollen (oder z.B. in Maven Central verfügbar machen wollen), empfiehlt es sich, Signaturen hinzuzufügen. Das folgende Beispiel erzeugt Signaturen mit dem PGP Maven plugin. Voraussetzung für das folgende Beispiel ist eine beliebige Private-Key-Schlüsseldatei und die Passphrase hierzu. Falls Sie dies noch nicht haben und falls Sie GnuPG / Gpg4win installiert haben, können Sie Schlüssel folgendermaßen erzeugen (Erläuterungen hierzu finden Sie unter java-crypto.htm#GnuPG): gpg2 --gen-key 1 2048 0 j Mein Name geheime GPG-Passphrase 42 [email protected] GPG-Test f Meine gpg2 -ao GPG-Test-pubkey.asc --export "GPG-Test" gpg2 -o GPG-Test-seckey.gpg --export-secret-keys "GPG-Test" Hierbei entsteht die Private-Key-Schlüsseldatei GPG-Test-seckey.gpg und der Public-Key GPG-Test-pubkey.asc. Weiterhin benötigen Sie für das folgende Beispiel ein beliebiges Maven-Projekt. Falls Sie kein vorhandenes verwenden wollen, legen Sie folgendermaßen ein neues an: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnSign cd MvnSign Ersetzen Sie den Inhalt der pom.xml durch (bzw. fügen Sie bei anderen Projekten den <plugin>Block ein): <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnSign</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MvnSign</name> <build> <plugins> <plugin> <groupId>org.kohsuke</groupId> <artifactId>pgp-maven-plugin</artifactId> <version>1.1</version> <executions> <execution> <goals> <goal>sign</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project> Führen Sie versuchsweise aus: cd \MeinWorkspace\MvnSign mvn clean install Da Sie keine Schlüsseldatei angegeben haben, erhalten Sie die Fehlermeldung: [ERROR] Failed to execute goal org.kohsuke:pgp-maven-plugin:1.1:sign (default) on project MvnSign: No PGP secret key is configured. Either do so in POM, or via -Dpgp.secretkey, or the PGP_SECRETKEY environment variable Geben Sie die Private-Key-Schlüsseldatei und die Passphrase über eine der genannten Methoden an, z.B. so (passen Sie die Ausdrücke an Ihre Schlüsseldatei an): mvn clean install -Dpgp.secretkey=keyfile:GPG-Test-seckey.gpg Dpgp.passphrase="literal:Meine geheime GPG-Passphrase 42" Um sich das Ergebnis ansehen zu können, setzen Sie zuerst Ihren Pfad zu Ihrem lokalen MavenRepository, beispielsweise: set MVN_REPO=%USERPROFILE%\.m2\repository oder set MVN_REPO=D:\Tools\Maven3-Repo Sehen Sie sich das Ergebnis an: dir %MVN_REPO%\de\meinefirma\meinprojekt\MvnSign\1.0-SNAPSHOT Zusätzlich zu den üblichen .jar- und .pom-Dateien erhalten Sie mehrere entsprechende .ascSignatur-Dateien, die mit "-----BEGIN PGP SIGNATURE-----" beginnen: type %MVN_REPO%\de\meinefirma\meinprojekt\MvnSign\1.0-SNAPSHOT\MvnSign-1.0SNAPSHOT.jar.asc Java-Programme (und andere Programme) mit dem exec-maven-plugin ausführen 1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein beliebiges Projekt: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnExec cd MvnExec tree /F 2. Fügen Sie in der pom.xml vor dem <dependencies>-Block hinzu: 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.6.0</version> <executions> <execution> <phase>test</phase> <goals> <goal>java</goal> </goals> <configuration> <mainClass>de.meinefirma.meinprojekt.Timestamp</mainClass> <arguments> <argument>Timestamp:</argument> <argument>yyyy-MM-dd HH:mm:ss,S</argument> </arguments> </configuration> </execution> </executions> </plugin> </plugins> </build> Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis Klasse: Timestamp.java die 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. package de.meinefirma.meinprojekt; import java.text.SimpleDateFormat; import java.util.Date; public class Timestamp { public static void main( String[] args ) { String prefix = ( args.length > 0 ) ? args[0] : "Timestamp:"; 40. String datetimePattern = ( args.length > 1 ) ? args[1] : "yyyyMM-dd HH:mm:ss,S"; 41. System.out.println( "\n" + prefix + " " + (new SimpleDateFormat( datetimePattern ).format( new Date() )) + "\n" ); 42. } 43. } 44. So sieht Ihre Verzeichnisstruktur aus: 45. 46. [\MeinWorkspace\MvnExec] 47. |- [src] 48. | |- [main] 49. | | '- [java] 50. | | '- [de] 51. | | '- [meinefirma] 52. | | '- [meinprojekt] 53. | | |- App.java 54. | | '- Timestamp.java 55. | '- [test] 56. | '- [java] 57. | '- [de] 58. | '- [meinefirma] 59. | '- [meinprojekt] 60. | '- AppTest.java 61. '- pom.xml 62. Geben Sie im Kommandozeilenfenster ein: cd \MeinWorkspace\MvnExec mvn compile mvn exec:java -Dexec.mainClass="de.meinefirma.meinprojekt.Timestamp" mvn exec:java -Dexec.mainClass="de.meinefirma.meinprojekt.Timestamp" Dexec.args="'Anderer Prefix:' 'yyyy-MM-dd HH:mm'" Sie erhalten zwei verschiedene Timestamp-Ausgaben mit verschiedener Formatierung. Interessanter ist, dass die Ausführung des Java-Programms in der pom.xml an die Maventest-Lifecycle-Phase gekoppelt ist. Führen Sie aus: mvn package Auch diesmal erhalten Sie die Timestamp-Ausgabe. 63. Natürlich können Sie auch an andere Maven-Lifecycle-Phasen ankoppeln und beliebige andere Java- oder andere Programme ausführen. Sehen Sie sich die Doku zum exec-maven-plugin an. Guice und AOP Das folgende Beispiel zeigt den Einsatz von Google Guice für Dependency Injection (DI) und für aspektorientierte Programmierung (AOP). Das Beispiel demonstriert anhand der Berechnung von Fibonacci-Zahlen: wie mit Guice.createInjector() ein DI-Injector erzeugt wird, wie diesem Injector ein AOP-Modul hinzugefügt wird, wie dieses AOP-Modul eine Annotation mit einem Method-Interceptor verknüpft, wie eine geeignete Marker-Annotation implementiert wird (UseLoggerInterceptor), wie ein Method-Interceptor für Logging implementiert wird (LoggerInterceptor) und wie durch das einfache Hinzufügen der Annotation @UseLoggerInterceptor zu einer Methode Logging aktiviert wird. 1. Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Projekt: cd \MeinWorkspace md MvnGuiceAop cd MvnGuiceAop md src\main\java\de\meinefirma\meinprojekt 2. Erzeugen Sie im Projektverzeichnis die Projektkonfigurationsdatei: pom.xml 3. 4. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnGuiceAop</artifactId> 9. <version>1.0-SNAPSHOT</version> 10. <build> 11. <plugins> 12. <plugin> 13. <artifactId>maven-assembly-plugin</artifactId> 14. <version>3.0.0</version> 15. <configuration> 16. <descriptorRefs> 17. <descriptorRef>jar-with-dependencies</descriptorRef> 18. </descriptorRefs> 19. <archive> 20. <manifest> 21. <mainClass>de.meinefirma.meinprojekt.Main</mainClass> 22. </manifest> 23. </archive> 24. </configuration> 25. <executions> 26. <execution> 27. <id>make-assembly</id> 28. <phase>package</phase> 29. <goals> 30. <goal>single</goal> 31. </goals> 32. </execution> 33. </executions> 34. </plugin> 35. </plugins> 36. </build> 37. <dependencies> 38. <dependency> 39. <groupId>com.google.inject</groupId> 40. <artifactId>guice</artifactId> 41. <version>4.1.0</version> 42. </dependency> 43. <dependency> 44. <groupId>aopalliance</groupId> 45. <artifactId>aopalliance</artifactId> 46. <version>1.0</version> 47. </dependency> 48. </dependencies> 49. </project> 50. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende fünf Klassen: Main.java package de.meinefirma.meinprojekt; import com.google.inject.Guice; import com.google.inject.Injector; public class Main { public static void main( String[] args ) { Injector inj = Guice.createInjector( new AopModule() ); Fibonacci fib = inj.getInstance( Fibonacci.class ); int n = 7; System.out.println( "\nFibonacci( " + n + " ) ist " + fib.fibonacci( n ) ); } } Fibonacci.java package de.meinefirma.meinprojekt; public class Fibonacci { @UseLoggerInterceptor public long fibonacci( int n ) { return ( n < 2 ) ? n : (fibonacci( n - 1 ) + fibonacci( n - 2 )); } } AopModule.java package de.meinefirma.meinprojekt; import static com.google.inject.matcher.Matchers.annotatedWith; import static com.google.inject.matcher.Matchers.any; import com.google.inject.AbstractModule; public class AopModule extends AbstractModule { protected void configure() { bindInterceptor( any(), annotatedWith( UseLoggerInterceptor.class ), new LoggerInterceptor() ); } } UseLoggerInterceptor.java package de.meinefirma.meinprojekt; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention( RetentionPolicy.RUNTIME ) public @interface UseLoggerInterceptor { } LoggerInterceptor.java package de.meinefirma.meinprojekt; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import java.lang.reflect.Method; import java.util.Arrays; public class LoggerInterceptor implements MethodInterceptor { public Object invoke( MethodInvocation methInvoc ) throws Throwable { Method mth = methInvoc.getMethod(); Object[] arg = methInvoc.getArguments(); String cll = mth.getName() + Arrays.deepToString( arg ); System.out.print( cll + ": " ); Object result = methInvoc.proceed(); System.out.println( " " + cll + " --> " + result ); return result; } } 51. Geben Sie im Kommandozeilenfenster ein: cd \MeinWorkspace\MvnGuiceAop mvn package java -jar target/MvnGuiceAop-1.0-SNAPSHOT-jar-with-dependencies.jar Sie sehen zu jedem fibonacci()-Methodenaufruf jeweils eine Meldung vor und eine Meldung nach dem Methodenaufruf. Eigenes Maven-Plugin (Mojo) In Java programmierte Maven-Plugins bestehen aus Mojos. Ein Mojo ("Maven (plain) old Java Object") ist eine Java-Klasse die das Interface org.apache.maven.plugin.Mojo implementiert (oder org.apache.maven.plugin.AbstractMojo erweitert) und damit ein Plugin-Goal realisiert. Siehe auch guide-java-plugin-development, maven-plugin-api und mojo-api-specification. Mojo mit Parameter 1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace) und erzeugen Sie ein Mojo-Projekt: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DarchetypeArtifactId=maven-archetype-mojo DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnTimestampPlugin cd MvnTimestampPlugin 2. Sehen Sie sich die generierte pom.xml an und beachten Sie insbesondere das <packaging> und die <dependency>: 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. ... <packaging>maven-plugin</packaging> ... <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>2.2.1</version> </dependency> ... Löschen Sie im Verzeichnis src\main\java\de\meinefirma\meinprojekt die MyMojo.java und legen Sie stattdessen in diesem Verzeichnis die Mojo-Datei TimestampMojo.java mit folgendem Inhalt an: package de.meinefirma.meinprojekt; import java.text.SimpleDateFormat; import java.util.Date; import org.apache.maven.plugin.AbstractMojo; /** * @goal timestamp */ public class TimestampMojo extends AbstractMojo { /** @parameter */ String prefix = "Timestamp:"; /** @parameter */ String datetimePattern = "yyyy-MM-dd HH:mm:ss,S"; @Override public void execute() { getLog().info( prefix + " " + (new SimpleDateFormat( datetimePattern ).format( new Date() )) ); 35. } 36. } Bitte beachten Sie die für das Maven-Plugin wichtigen Angaben @goal... und @parameter.... Sie können das Goal über @phase auch an eine bestimmte Lifecycle-Phase binden. Weiteres zu @phase, @goal, @parameter und weiteren Annotationen finden Sie unter AbstractMojo, Mojo API Specification und Guide to Developing Java Plugins. 37. Die Projektstruktur sieht jetzt so aus: cd \MeinWorkspace\MvnTimestampPlugin tree /F [\MeinWorkspace\MvnTimestampPlugin] |- [src] | '- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- TimestampMojo.java '- pom.xml 38. Die allgemeine Syntax, um ein Goal auf der Kommandozeile auszuführen, lautet: "mvn groupID:artifactID:[version:]goal" (der Versionsteil kann manchmal weggelassen werden). Bauen Sie das Projekt und führen Sie für einen ersten Test das Goal per Kommandozeile aus: cd \MeinWorkspace\MvnTimestampPlugin mvn clean install mvn de.meinefirma.meinprojekt:MvnTimestampPlugin:1.0-SNAPSHOT:timestamp Sie erhalten: [INFO] --- MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (default-cli) @ MvnTimestampPlugin --[INFO] Timestamp: 2010-10-09 20:42:37,90 39. Interessant wird es jedoch erst, wenn Sie das neue Maven-Plugin in anderen Projekten verwenden und beispielsweise mit Lifecycle-Phasen verknüpfen. Erweitern Sie die pom.xml irgendeines beliebigen Projekts (z.B. MvnJaxbApp) im <build><plugins>...Block um folgendes <plugin>-Element: 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. <plugin> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnTimestampPlugin</artifactId> <version>1.0-SNAPSHOT</version> <configuration> <prefix>+++</prefix> <datetimePattern>HH:mm:ss</datetimePattern> </configuration> <executions> <execution> <id>nach clean</id> <phase>clean</phase> <goals> <goal>timestamp</goal> </goals> </execution> <execution> <id>nach compile</id> <phase>compile</phase> 60. 61. 62. 63. 64. 65. <goals> <goal>timestamp</goal> </goals> </execution> </executions> </plugin> Außer der Verknüpfung mit den beiden Lifecycle-Phasen clean und compile findet auch die Konfiguration der beiden Parameter prefix und datetimePattern statt. Führen Sie jetzt im Projektverzeichnis dieses anderen Projekts aus: cd \MeinWorkspace\MvnJaxbApp-mit-TimestampMojo [anpassen!] mvn clean package Sie erhalten: ... [INFO] --... [INFO] --MvnJaxbApp [INFO] +++ ... [INFO] --MvnJaxbApp ... [INFO] --MvnJaxbApp [INFO] +++ ... maven-clean-plugin:2.5:clean (default-clean) @ MvnJaxbApp -MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (nach clean) @ --20:52:03 maven-compiler-plugin:3.6.1:compile (default-compile) @ --MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (nach compile) @ --20:52:05 Mojo-PluginContext 1. Wir wollen das Plugin-Mojo so erweitern, dass es nicht nur die aktuelle Zeit anzeigt, sondern zusätzlich die Dauer vom letzten Aufruf bis zu diesem Aufruf berechnet. Dazu ist eine Kommunikation zwischen den verschiedenen Plugin-Aufrufen notwendig. Eine solche Kommunikation (auch zwischen verschiedenen Plugins) ist über die "PluginContext"-Map möglich. Ersetzen Sie im MvnTimestampPlugin-Projekt im Unterverzeichnis src\main\java\de\meinefirma\meinprojekt den Inhalt der TimestampMojo.java durch Folgendes: 2. 3. 4. 5. 6. 7. 8. package de.meinefirma.meinprojekt; import java.text.SimpleDateFormat; import java.util.*; import org.apache.maven.plugin.AbstractMojo; 9. /** 10. * @goal timestamp 11. */ 12. public class TimestampMojo extends AbstractMojo 13. { 14. /** @parameter */ String prefix = "Timestamp:"; 15. /** @parameter */ String datetimePattern = "yyyy-MM-dd HH:mm:ss,S"; 16. 17. @Override 18. public void execute() 19. { 20. final String ctxTimeKey = "TimestampMojo-Time"; 21. Date date = new Date(); 22. Map ctx = getPluginContext(); 23. Long timeZuletzt = (Long) ctx.get( ctxTimeKey ); 24. ctx.put( ctxTimeKey, new Long( date.getTime() ) ); 25. String dauer = ( timeZuletzt == null ) ? "" : 26. ", Dauer: " + (date.getTime() - timeZuletzt.longValue()) + " ms"; 27. getLog().info( prefix + " " + 28. (new SimpleDateFormat( datetimePattern ).format( date )) + dauer ); 29. } 30. } 31. Bauen Sie das Timestamp-Plugin neu: cd \MeinWorkspace\MvnTimestampPlugin mvn clean install Führen Sie wieder im Projektverzeichnis des anderen Projekts aus: cd \MeinWorkspace\MvnJaxbApp-mit-TimestampMojo [anpassen!] mvn clean package Diesmal ist die Ausgabe erweitert um die Anzeige der "Dauer": ... [INFO] --... [INFO] --MvnJaxbApp [INFO] +++ ... [INFO] --MvnJaxbApp ... [INFO] --MvnJaxbApp [INFO] +++ ... maven-clean-plugin:2.5:clean (default-clean) @ MvnJaxbApp -MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (nach clean) @ --10:48:00 maven-compiler-plugin:3.6.1:compile (default-compile) @ --MvnTimestampPlugin:1.0-SNAPSHOT:timestamp (nach compile) @ --10:48:01, Dauer: 1234 ms Mojo-JUnit-Test 1. Es gibt verschiedene Verfahren, um Maven-Plugins automatisiert testen zu können. Einige sind beschrieben unter Introduction / Testing Styles. 2. Um das maven-plugin-testing-harness-Plugin verwenden zu können, erweitern Sie die pom.xml des MvnTimestampPlugins im <dependencies>...-Block um folgendes <dependency>-Element: 3. 4. 5. 6. 7. 8. 9. <dependency> <groupId>org.apache.maven.shared</groupId> <artifactId>maven-plugin-testing-harness</artifactId> <version>1.1</version> <scope>test</scope> </dependency> 10. Erzeugen Sie folgendermaßen weitere Unterverzeichnisse: cd \MeinWorkspace\MvnTimestampPlugin md src\test\resources md src\test\java\de\meinefirma\meinprojekt 11. Für den Test wird nur eine einfache abgespeckte POM benötigt. Erzeugen Sie im src\test\resources-Verzeichnis die Datei test-pom.xml mit folgendem Inhalt: 12. 13. <project> 14. <modelVersion>4.0.0</modelVersion> 15. <groupId>test</groupId> 16. <artifactId>Test</artifactId> 17. <build> 18. <plugins> 19. <plugin> 20. <groupId>de.meinefirma.meinprojekt</groupId> 21. <artifactId>MvnTimestampPlugin</artifactId> 22. <configuration> 23. <prefix>+++</prefix> 24. <datetimePattern>HH:mm:ss</datetimePattern> 25. </configuration> 26. </plugin> 27. </plugins> 28. </build> 29. </project> 30. Erzeugen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis TimestampMojoTest.java mit folgendem Inhalt: 31. 32. package de.meinefirma.meinprojekt; 33. 34. import java.io.File; 35. import java.util.*; 36. import org.apache.maven.plugin.logging.SystemStreamLog; 37. import org.apache.maven.plugin.testing.AbstractMojoTestCase; 38. 39. public class TimestampMojoTest extends AbstractMojoTestCase die Datei 40. { 41. public void testTimestampMojo() throws Exception 42. { 43. Map pluginContext = new HashMap(); 44. String log1 = executeMojo( pluginContext ); 45. String log2 = executeMojo( pluginContext ); 46. assertTrue( log1.length() < log2.length() ); 47. assertTrue( !log1.contains( "Dauer" ) ); 48. assertTrue( log2.contains( "Dauer" ) ); 49. } 50. 51. private String executeMojo( Map pluginContext ) throws Exception 52. { 53. String testPom = getBasedir() + "/src/test/resources/test-pom.xml"; 54. String artifactId = "MvnTimestampPlugin"; 55. StringBuffer log = new StringBuffer(); 56. TimestampMojo mojo = new TimestampMojo(); 57. configureMojo( mojo, artifactId, new File( testPom ) ); 58. mojo.setPluginContext( pluginContext ); 59. mojo.setLog( new TestLog( log ) ); 60. mojo.execute(); 61. String prefix = (String) getVariableValueFromObject( mojo, "prefix" ); 62. assertNotNull( prefix ); 63. assertEquals( prefix, log.substring( 0, prefix.length() ) ); 64. return log.toString(); 65. } 66. 67. class TestLog extends SystemStreamLog 68. { 69. StringBuffer log; 70. 71. public TestLog( StringBuffer log ) 72. { 73. this.log = log; 74. } 75. 76. // @Override 77. public void info( CharSequence content ) 78. { 79. log.append( content ); 80. } 81. } 82. } Die Variable testPom muss den Pfad zu einer geeigneten POM-Datei enthalten, in welcher das Plugin MvnTimestampPlugin eingetragen ist. TimestampMojoTest erweitert AbstractMojoTestCase, damit die Methoden getBasedir(), configureMojo() und getVariableValueFromObject() zur Verfügung stehen. Bitte beachten Sie die Injizierung der Context-Map über setPluginContext() und des Testloggers über setLog(). Letzteres wird benötigt, um die log-Ausgaben abzufangen. executeMojo() wird zweimal aufgerufen: Beim ersten Mal wird nur der Timestamp geloggt. Beim zweiten Mal wird auch die Dauer geloggt, was durch "assertTrue( log2.contains( "Dauer" ) )" überprüft wird. Bitte beachten Sie, dass TestLog nur eine einzige der vielen Log-Methoden überschreibt, was für diesen Test reicht, aber für andere Tests eventuell zu wenig sein kann. 83. Ihre Verzeichnisstruktur sieht jetzt so aus: cd \MeinWorkspace\MvnTimestampPlugin tree /F --> [\MeinWorkspace\MvnTimestampPlugin] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- TimestampMojo.java | '- [test] | |- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- TimestampMojoTest.java | '- [resources] | '- test-pom.xml '- pom.xml 84. Führen Sie den JUnit-Test aus: cd \MeinWorkspace\MvnTimestampPlugin mvn test Mojo-Hilfstexte 1. Erweitern Sie die pom.xml um erläuternde Hilfstexte, damit folgendes Kommando Hilfe bieten kann: mvn help:describe Dplugin=de.meinefirma.meinprojekt:MvnTimestampPlugin:1.0-SNAPSHOT Ddetail Test-Jar Manchmal werden nur für Tests benötigte wichtige Testhilfsklassen auch in anderen MavenModulen benötigt. Die Weitergabe von Testhilfsklassen an andere Module wird durch das maven-jar-plugin über das test-jar-Goal unterstützt, wie das folgende Beispiel zeigt (siehe auch: Guide to using attached tests). 1. Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Maven-Projekt: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnTestJar1 cd MvnTestJar1 2. Erzeugen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis die wichtige Testhilfsklasse: MeinWichtigesTestUtil.java 3. 4. package de.meinefirma.meinprojekt; 5. 6. public class MeinWichtigesTestUtil 7. { 8. public static int hilfsmethode( int a, int b ) 9. { 10. return a * b; 11. } 12. } 13. Ersetzen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis den Inhalt der AppTest.java durch: 14. 15. package de.meinefirma.meinprojekt; 16. 17. import junit.framework.Assert; 18. import junit.framework.TestCase; 19. 20. public class AppTest extends TestCase 21. { 22. public void testApp() 23. { 24. Assert.assertEquals( 6, MeinWichtigesTestUtil.hilfsmethode( 2, 3 ) ); 25. } 26. } 27. Führen Sie im Kommandozeilenfenster den JUnit-Test aus und installieren Sie das MvnTestJar1-Ergebnisartefakt im lokalen Maven-Repository (passen Sie den Pfad zum lokalen Maven-Repository an): mvn test mvn clean install Die Ergebnisse werden im lokalen Maven-Repository generiert. Setzen Sie folgende Environmentvariable entsprechend Ihres Repo-Pfades, um die Ergebnisse zu untersuchen, beispielsweise: set MVN_REPO=%USERPROFILE%\.m2\repository oder set MVN_REPO=D:\Tools\Maven3-Repo Sehen Sie sich an: dir %MVN_REPO%\de\meinefirma\meinprojekt\MvnTestJar1\1.0-SNAPSHOT Sie erhalten u.a.: MvnTestJar1-1.0-SNAPSHOT.jar 28. Erzeugen Sie ein zweites Maven-Projekt: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnTestJar2 cd MvnTestJar2 copy ..\MvnTestJar1\src\test\java\de\meinefirma\meinprojekt\AppTest.java src\test\java\de\meinefirma\meinprojekt /Y mvn test Da die Dependency zu MvnTestJar1 fehlt, erhalten Sie erwartungsgemäß die Fehlermeldung: [ERROR] COMPILATION ERROR : ...\AppTest.java: cannot find ... MeinWichtigesTestUtil 29. Erweitern Sie die pom.xml des neuen MvnTestJar2-Projekts um eine Dependency zum MvnTestJar1-Projekt, indem Sie folgenden Block einfügen: 30. 31. 32. 33. 34. 35. 36. <dependency> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnTestJar1</artifactId> <version>1.0-SNAPSHOT</version> <scope>test</scope> </dependency> 37. Wenn Sie erneut "mvn test" ausführen, erhalten Sie weiterhin die Fehlermeldung: [ERROR] COMPILATION ERROR : ...\AppTest.java: cannot find ... MeinWichtigesTestUtil Der Grund ist, dass die Testhilfsklasse MeinWichtigesTestUtil nicht im Auslieferartefakt MvnTestJar1-1.0-SNAPSHOT.jar enthalten ist, da sie nur für JUnitTests benötigt wird. 38. Jetzt kommt der Trick dieses Programmierbeispiels: Um wichtige Testhilfsklassen auch in Tests in anderen Modulen verwenden zu können, werden sie in gesonderten Auslieferartefakten weitergegeben, wie im Folgenden beschrieben wird. 39. Ändern Sie in der pom.xml des neuen MvnTestJar2-Projekts folgendermaßen die MvnTestJar1-Dependency: 40. 41. 42. 43. 44. 45. 46. 47. <dependency> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>MvnTestJar1</artifactId> <version>1.0-SNAPSHOT</version> <type>test-jar</type> <scope>test</scope> </dependency> Beachten Sie die "<type>test-jar</type>"-Zeile. 48. Fügen Sie in der pom.xml des ersteren MvnTestJar1-Projekts folgendermaßen das maven-jar-plugin mit dem test-jar-Goal hinzu: 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> <executions> <execution> <goals> <goal>test-jar</goal> </goals> </execution> </executions> </plugin> </plugins> </build> Führen Sie im MvnTestJar1-Projekt erneut "mvn install" aus: cd ..\MvnTestJar1 mvn install dir %MVN_REPO%\de\meinefirma\meinprojekt\MvnTestJar1\1.0-SNAPSHOT Diesmal erhalten Sie zwei Ergebnis-Jar-Archive: MvnTestJar1-1.0-SNAPSHOT-tests.jar MvnTestJar1-1.0-SNAPSHOT.jar 67. Jetzt funktionieren auch im MvnTestJar2-Projekt die Tests (inkl. MeinWichtigesTestUtil): cd ..\MvnTestJar2 mvn test 68. Das maven-jar-plugin kann noch Vieles mehr, siehe beispielsweise: Ausführbare JarDatei und REST-Client mit REST-JUnit-Test. JUnit-Tests mit JUnit 5 Eine Alternative zu JUnit 4 ist das modernere JUnit 5. Es nutzt die Vorteile von Java 8, z.B. die Funktionale Programmierung mit Lambda-Ausdrücken und das Stream-API. 1. Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Projekt: cd \MeinWorkspace md MvnJUnit5 cd MvnJUnit5 md src\main\java\de\meinefirma\meinprojekt md src\test\java\de\meinefirma\meinprojekt tree /F 2. Erstellen Sie im MvnJUnit5-Projektverzeichnis die Projektkonfigurationsdatei: pom.xml 3. 4. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnJUnit5</artifactId> 9. <version>1.0-SNAPSHOT</version> 10. <build> 11. <plugins> 12. <plugin> 13. <groupId>org.apache.maven.plugins</groupId> 14. <artifactId>maven-compiler-plugin</artifactId> 15. <version>3.6.1</version> 16. <configuration> 17. <source>8</source> 18. <target>8</target> 19. </configuration> 20. </plugin> 21. </plugins> 22. </build> 23. <dependencies> 24. <dependency> 25. <groupId>org.junit.jupiter</groupId> 26. <artifactId>junit-jupiter-api</artifactId> 27. <version>5.0.0-M4</version> 28. <scope>test</scope> 29. </dependency> 30. <dependency> 31. <groupId>org.junit.jupiter</groupId> 32. <artifactId>junit-jupiter-engine</artifactId> 33. <version>5.0.0-M4</version> 34. <scope>test</scope> 35. </dependency> 36. <dependency> 37. <groupId>org.junit.platform</groupId> 38. <artifactId>junit-platform-runner</artifactId> 39. <version>1.0.0-M4</version> 40. <scope>test</scope> 41. </dependency> 42. </dependencies> 43. </project> Überprüfen Sie unter junit-jupiter-api, ob es mittlerweile eine neuere JUnit-5-Version gibt. 44. Erstellen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis die Anwendungsklasse: App.java 45. 46. package de.meinefirma.meinprojekt; 47. 48. public class App 49. { 50. public static void main( String[] args ) { 51. if( args != null && args.length > 1 ) { 52. System.out.println( args[0] + " / " + args[1] + " = " + 53. calc( Long.valueOf( args[0] ), Long.valueOf( args[1] ) ) ); 54. } 55. } 56. 57. public static long calc( long val1, long val2 ) { 58. return val1 / val2; 59. } 60. } 61. Erstellen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis die JUnit5Testklasse: AppTest.java 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. package de.meinefirma.meinprojekt; import import import import import import java.time.Duration; java.util.stream.Stream; org.junit.jupiter.api.*; org.junit.platform.runner.JUnitPlatform; org.junit.runner.RunWith; static org.junit.jupiter.api.Assertions.*; @RunWith( JUnitPlatform.class ) public class AppTest { @BeforeAll static void beforeAll() { System.out.println( "Einmalig vor allen Tests ..." ); } @AfterAll static void afterAll() { System.out.println( "... fertig" ); } @BeforeEach void beforeEach() { System.out.println( "Vor jedem einzelnen Test ..." ); } @Test @DisplayName( "Teste App.main() mit Timeout" ) void testeAppMain() { assertTimeoutPreemptively( Duration.ofMillis( 500 ), () -> App.main( new String[] { "7", "3" } ) ); 93. } 94. 95. @Test @DisplayName( "Teste 3 / 2" ) 96. void testeCalc() { 97. System.out.println( "3 / 2 ..." ); 98. assertEquals( 1, App.calc( 3, 2 ), "Ergebnis muss 1 sein" ); 99. } 100. 101. @Test @DisplayName( "Teste Exception nach Division by zero" ) 102. void testeExeption() { 103. System.out.println( "1 / 0 ..." ); 104. App app = new App(); 105. ArithmeticException ex = assertThrows( ArithmeticException.class, () -> App.calc( 1, 0 ) ); 106. assertEquals( "/ by zero", ex.getMessage(), "Exception-Message" ); 107. } 108. 109. @Test @DisplayName( "Mehrere Tests zusammengefasst" ) 110. void testeAssertAll() { 111. System.out.println( "20 / ?? ..." ); 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. "i=" 122. 123. 124. } assertAll( "Mehrere Tests", () -> assertEquals( 6, App.calc( 20, 3 ) ), () -> assertEquals( 5, App.calc( 20, 4 ) ), () -> assertEquals( 4, App.calc( 20, 5 ) ) ); } @TestFactory @DisplayName( "DynamicTest mit TestFactory" ) Stream<DynamicTest> testeDynamicTest() { System.out.println( "1, 2, 3 / 2 ..." ); return Stream.of( 1, 2, 3 ).map( i -> DynamicTest.dynamicTest( + i, () -> assertTrue( i > App.calc( i, 2 ) ) ) ); } Sehen Sie sich zu den JUnit-5-Ausdrücken die JUnit-5-API-Doku an. 125. Ihr Projekteverzeichnis sieht jetzt so aus: 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. [\MeinWorkspace\MvnJUnit5] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- App.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml 142. Führen Sie die fünf Testmethoden aus: cd \MeinWorkspace\MvnJUnit5 mvn test Sie erhalten beispielsweise (die Reihenfolge kann variieren): ------------------------------------------------------T E S T S ------------------------------------------------------Running de.meinefirma.meinprojekt.AppTest Einmalig vor allen Tests ... Vor jedem einzelnen Test ... 7 / 3 = 2 Vor jedem einzelnen Test ... 3 / 2 ... Vor jedem einzelnen Test ... 1 / 0 ... Vor jedem einzelnen Test ... 20 / ?? ... Vor jedem einzelnen Test ... 1, 2, 3 / 2 ... ... fertig Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 [INFO] ----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ----------------------------------------------------------------------- 143. Die per @DisplayName() gesetzten Texte werden beispielsweise angezeigt, wenn Sie die Tests in JetBrains IntelliJ IDEA ausführen. Parallelisierte Testausführung mit TestNG Eine weitere Alternative zu JUnit ist TestNG. Hierzu gibt es auch Unterstützung durch das bereits bekannte maven-surefire-plugin, womit im Folgenden die Parallelisierung von Tests demonstriert wird. 1. Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Projekt: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnTestNG cd MvnTestNG tree /F [\MeinWorkspace\MvnTestNG] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- App.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- AppTest.java '- pom.xml 2. Ersetzen Sie den Inhalt der pom.xml durch: 3. 4. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnTestNG</artifactId> 9. <version>1.0-SNAPSHOT</version> 10. <packaging>jar</packaging> 11. <name>MvnTestNG</name> 12. <build> 13. <plugins> 14. <plugin> 15. <groupId>org.apache.maven.plugins</groupId> 16. <artifactId>maven-surefire-plugin</artifactId> 17. <version>2.20</version> 18. <configuration> 19. <parallel>methods</parallel> 20. <threadCount>10</threadCount> 21. </configuration> 22. </plugin> 23. <plugin> 24. <groupId>org.apache.maven.plugins</groupId> 25. <artifactId>maven-compiler-plugin</artifactId> 26. <version>3.6.1</version> 27. <configuration> 28. <source>1.7</source> 29. <target>1.7</target> 30. </configuration> 31. </plugin> 32. </plugins> 33. </build> 34. <dependencies> 35. <dependency> 36. <groupId>org.testng</groupId> 37. <artifactId>testng</artifactId> 38. <version>6.11</version> 39. <scope>test</scope> 40. </dependency> 41. </dependencies> 42. </project> 43. Ersetzen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis der App.java durch: 44. 45. package de.meinefirma.meinprojekt; 46. 47. import java.text.SimpleDateFormat; 48. import java.util.Date; 49. 50. public class App 51. { 52. public static void main( String[] args ) throws InterruptedException 53. { den Inhalt 54. final SimpleDateFormat dfHhMmSsS = new SimpleDateFormat( "HH:mm:ss.S" ); 55. String s = ( args != null && args.length > 0 ) ? args[0] : "."; 56. System.out.println( "\n" + dfHhMmSsS.format( new Date() ) + " " + Thread.currentThread().getName() + " Start" ); 57. for( int i = 0; i < 10; i++ ) { 58. Thread.sleep( 100 ); 59. System.out.print( s ); 60. Thread.sleep( 100 ); 61. } 62. System.out.println( "\n" + dfHhMmSsS.format( new Date() ) + " " + Thread.currentThread().getName() + " Ende" ); 63. } 64. } 65. Löschen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis die AppTest.java und legen Sie in diesem Verzeichnis folgende Testklassen an: App1Test.java package de.meinefirma.meinprojekt; import org.testng.annotations.Test; public class App1Test { @Test public void testApp() throws InterruptedException { App.main( new String[] { "1" } ); } } App2Test.java package de.meinefirma.meinprojekt; import org.testng.annotations.Test; public class App2Test { @Test public void testApp() throws InterruptedException { App.main( new String[] { "2" } ); } } App3Test.java package de.meinefirma.meinprojekt; import org.testng.annotations.Test; public class App3Test { @Test public void testApp3() throws InterruptedException { App.main( new String[] { "3" } ); } @Test public void testApp4() throws InterruptedException { App.main( new String[] { "4" } ); } @Test public void testApp5() throws InterruptedException { App.main( new String[] { "5" } ); } } 66. Ihr Projekteverzeichnis sieht jetzt so aus: 67. 68. [\MeinWorkspace\MvnTestNG] 69. |- [src] 70. | |- [main] 71. | | '- [java] 72. | | '- [de] 73. | | '- [meinefirma] 74. | | '- [meinprojekt] 75. | | '- App.java 76. | '- [test] 77. | '- [java] 78. | '- [de] 79. | '- [meinefirma] 80. | '- [meinprojekt] 81. | '- App1Test.java 82. | '- App2Test.java 83. | '- App3Test.java 84. '- pom.xml 85. Führen Sie die fünf Testmethoden in den drei Testklassen aus: cd \MeinWorkspace\MvnTestNG mvn test Sie erhalten beispielsweise: ------------------------------------------------------T E S T S ------------------------------------------------------11:22:33.0 pool-1-thread-1 Start 11:22:33.0 pool-1-thread-4 Start 11:22:33.0 pool-1-thread-2 Start 11:22:33.0 pool-1-thread-5 Start 11:22:33.0 pool-1-thread-3 Start 31245134521342513245312541354231524135423154231254 11:22:35.0 11:22:35.0 11:22:35.0 11:22:35.0 11:22:35.0 pool-1-thread-1 pool-1-thread-5 pool-1-thread-3 pool-1-thread-2 pool-1-thread-4 Ende Ende Ende Ende Ende Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.1 sec [INFO] ----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ----------------------------------------------------------------------- Wie Sie gut erkennen können, werden die fünf Testmethoden in fünf parallelen Threads ausgeführt. Bitte beachten Sie: Durch die Einstellung <parallel>methods</parallel> werden nicht nur Klassen, sondern auch die Methoden innerhalb der Klasse App3Test.java parallel ausgeführt. 86. Ersetzen Sie testweise in der pom.xml die Zeile 87. 88. <parallel>methods</parallel> durch <parallel>false</parallel> und führen Sie die Tests erneut aus: Während bei ersterer paralleler Testausführung als Gesamttestzeit ("Time elapsed") ein Wert knap über 2 Sekunden gemeldet wurde, wird diesmal mit serieller Ausführung ein Wert über 10 Sekunden gemeldet, also fast das fünffache. 89. Wenn Sie TestNG-Tests in Eclipse ausführen wollen, müssen Sie das TestNG-EclipsePlug-in installieren, siehe http://testng.org/doc/eclipse.html und http://beust.com/eclipse/. JMockit JMockit ist ein Mocking-Framework, welches während Testausführungen Methoden anderer Klassen simuliert und ersetzt und bestimmte Ergebnisse vortäuscht. Das Besondere an JMockit ist, dass es auch Konstruktoren sowie statische und finale Methoden mocken kann. Alternativen zu JMockit sind Mockito und EasyMock sowie die Erweiterung PowerMock. 1. Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie ein neues Projekt: cd \MeinWorkspace md MvnJMockit cd MvnJMockit md src\main\java\de\meinefirma\meinprojekt md src\test\java\de\meinefirma\meinprojekt 2. Erzeugen Sie im Projektverzeichnis die Projektkonfigurationsdatei: pom.xml 3. 4. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnJMockit</artifactId> 9. <version>1.0-SNAPSHOT</version> 10. <dependencies> 11. <dependency> 12. <groupId>com.googlecode.jmockit</groupId> 13. <artifactId>jmockit</artifactId> 14. <version>1.7</version> 15. </dependency> 16. <dependency> 17. <groupId>junit</groupId> 18. <artifactId>junit</artifactId> 19. <version>4.12</version> 20. <scope>test</scope> 21. </dependency> 22. </dependencies> 23. </project> Beachten Sie, dass jmockit vor junit in den dependencies eingetragen sein muss. 24. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis die zu testende Klasse: Mittagspausenzeit.java 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. package de.meinefirma.meinprojekt; import java.time.LocalDateTime; public class Mittagspausenzeit { public static boolean isMittagspause() { int stunde = LocalDateTime.now().getHour(); return stunde >= 12 && stunde <= 13; } } Erzeugen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis Testklasse: MittagspausenzeitTest.java die 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. package de.meinefirma.meinprojekt; import java.time.LocalDateTime; import mockit.*; import org.junit.*; public class MittagspausenzeitTest { @Test public void test12uhr( @Mocked final LocalDateTime dateTime ) { new Expectations() {{ LocalDateTime.now(); result = dateTime; dateTime.getHour(); result = Integer.valueOf( 12 ); }}; boolean isMittagspause = Mittagspausenzeit.isMittagspause(); Assert.assertTrue( isMittagspause ); } } Damit der Test zu jeder Tageszeit ausgeführt werden kann, wird als Ergebnis von LocalDateTime.now().getHour() 12 Uhr vorgetäuscht. Sehen Sie sich das JMockit Testing Toolkit Tutorial und die JMockit Toolkit API Doku an, sowie @Mocked und Expectations. 60. Ihr Projekteverzeichnis sieht jetzt so aus: 61. 62. [\MeinWorkspace\MvnJMockit] 63. |- [src] 64. | |- [main] 65. | | '- [java] 66. | | '- [de] 67. | | '- [meinefirma] 68. | | '- [meinprojekt] 69. | | '- Mittagspausenzeit.java 70. | '- [test] 71. | '- [java] 72. | '- [de] 73. | '- [meinefirma] 74. | '- [meinprojekt] 75. | '- MittagspausenzeitTest.java 76. '- pom.xml 77. Führen Sie die den Test aus: cd \MeinWorkspace\MvnJMockit mvn test Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit Das folgende Beispiel enthält zwei verschiedene Testarten, die beide automatisiert ausgeführt werden können: Einen einfachen JUnit-Komponententest (der ohne Servlet-Engine funktioniert) Mit HtmlUnit und HttpUnit realisierte Integrationstests, die nur mit laufender ServletEngine (z.B. Jetty) funktionieren Erläuterungen zu den verschiedenen Testarten "Komponententest" und "Integrationstest" finden Sie unter java-fit.htm#Testebenen. Zum besseren Verständnis und um es einfach zu halten sind in diesem Beispiel Komponententests und Integrationstests in einem einzigen Maven-Projekt vereint. In realen Projekten kann es manchmal sinnvoll sein, die Integrationstests in einem getrennten MavenProjekt zu realisieren. Die Verwendung von sowohl HtmlUnit als auch HttpUnit zusammen in einem Projekt ist normalerweise nicht sinnvoll, da beide Testframeworks ähnliche Ziele abdecken. Insbesondere im folgenden Beispiel ist der einzige Sinn, beides demonstrieren zu können. 1. Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. \MeinWorkspace) und erzeugen Sie eine Webapp-Projektstruktur: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DarchetypeArtifactId=maven-archetype-webapp DgroupId=de.meinefirma.meinprojekt -DartifactId=MvnHtmlIntTest cd MvnHtmlIntTest md src\main\java\de\meinefirma\meinprojekt md src\test\java\de\meinefirma\meinprojekt md src\test\java\integrationtest rd src\main\resources tree /F Sie erhalten: [\MeinWorkspace\MvnHtmlIntTest] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | '- index.jsp | '- [test] | '- [java] | |- [de] | | '- [meinefirma] | | '- [meinprojekt] | '- [integrationtest] '- pom.xml 2. Ersetzen Sie im Projektverzeichnis den Inhalt der pom.xml durch: 3. 4. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 6. <modelVersion>4.0.0</modelVersion> 7. <groupId>de.meinefirma.meinprojekt</groupId> 8. <artifactId>MvnHtmlIntTest</artifactId> 9. <version>1.0-SNAPSHOT</version> 10. <packaging>war</packaging> 11. <name>MvnHtmlIntTest: Webapp mit Integrationstest</name> 12. <build> 13. <finalName>${project.artifactId}</finalName> 14. <plugins> 15. <plugin> 16. <!-- Die Versionen 8.1.16.v20140903 und 9.2.2.v20140723 funktionieren, 17. aber 9.2.3.v20140905 und 9.3.10.v20160621 funktionieren nicht: --> 18. <groupId>org.eclipse.jetty</groupId> 19. <artifactId>jetty-maven-plugin</artifactId> 20. <version>9.2.2.v20140723</version> 21. <configuration> 22. <scanIntervalSeconds>10</scanIntervalSeconds> 23. <stopPort>9999</stopPort> 24. <stopKey>geheim</stopKey> 25. <stopWait>1</stopWait> 26. <webAppConfig> 27. <contextPath>/${project.artifactId}</contextPath> 28. </webAppConfig> 29. </configuration> 30. <executions> 31. <execution> 32. <id>start-jetty</id> 33. <phase>pre-integration-test</phase> 34. <goals> 35. <goal>run</goal> 36. </goals> 37. <configuration> 38. <scanIntervalSeconds>0</scanIntervalSeconds> 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. <daemon>true</daemon> </configuration> </execution> <execution> <id>stop-jetty</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.20</version> <configuration> <systemPropertyVariables> <port>8080</port> </systemPropertyVariables> <excludes> <exclude>**/integrationtest/*Test.java</exclude> </excludes> </configuration> <executions> <execution> <id>integration-tests</id> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration> <skip>false</skip> <excludes> <exclude>none</exclude> </excludes> <includes> <include>**/integrationtest/*Test.java</include> </includes> </configuration> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>net.sourceforge.htmlunit</groupId> <artifactId>htmlunit</artifactId> <version>2.20</version> <scope>test</scope> </dependency> <dependency> <groupId>org.httpunit</groupId> <artifactId>httpunit</artifactId> <version>1.7.2</version> <scope>test</scope> 96. </dependency> 97. <dependency> 98. <groupId>junit</groupId> 99. <artifactId>junit</artifactId> 100. <version>4.12</version> 101. <scope>test</scope> 102. </dependency> 103. </dependencies> 104. </project> Erläuterungen zu den Konfigurationsoptionen des Jetty-Plugins finden Sie unter http://eclipse.org/jetty/documentation/current/jetty-maven-plugin.html (sehen Sie sich insbesondere scanIntervalSeconds und daemon an). Erläuterungen zu den Optionen des Surefire-Test-Plugins finden Sie unter http://maven.apache.org/plugins/maven-surefire-plugin/. Ersetzen Sie im src\main\webapp-Verzeichnis den Inhalt der index.jsp durch: 106. 107. <html> 108. <head><title>Startseite</title></head> 109. <body> 110. <h2>Startseite</h2> 111. <p><a name="BerechnungsFormular" href="BerechnungsFormular.jsp">Berechnungsformular</a></p> 112. </body> 113. </html> 114. Fügen Sie im src\main\webapp-Verzeichnis folgende BerechnungsFormular.jsp hinzu: 115. 116. <%@ page import="de.meinefirma.meinprojekt.MeineBean" %> 117. <html> 118. <head><title>Webseite mit Berechnungsformular</title></head> 119. <body> 120. <h2>Webseite mit Berechnungsformular</h2> 121. <% String wert1 = request.getParameter( "wert1" ); 122. String wert2 = request.getParameter( "wert2" ); 123. if( wert1 == null ) { wert1 = ""; } 124. if( wert2 == null ) { wert2 = ""; } 125. String ergebnis = (new MeineBean()).berechne( wert1, wert2 ); %> 126. <form name="meinFormular" method="post"><pre> 127. Wert1 <input type="text" name="wert1" value='<%= wert1 %>' size=10 maxlength=10><br> 128. Wert2 <input type="text" name="wert2" value='<%= wert2 %>' size=10 maxlength=10><br> 129. <input type="submit" name="berechne" value="berechne"> <input type="text" name="ergebnis" value='<%= ergebnis %>' size=10 readonly><br> 130. </pre></form> 131. </body> 132. </html> 133. Erstellen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis eine JavaBean: MeineBean.java 134. 105. 135. package de.meinefirma.meinprojekt; 136. 137. public class MeineBean 138. { 139. public String berechne( String d1, String d2 ) 140. { 141. try { 142. return "" + (Double.parseDouble( d1 ) + Double.parseDouble( d2 )); 143. } catch( Exception ex ) { 144. return ""; 145. } 146. } 147. } 148. Erstellen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis einen Komponenten-JUnit-Test: MeineBeanTest.java 149. 150. package de.meinefirma.meinprojekt; 151. 152. import org.junit.*; 153. 154. public class MeineBeanTest 155. { 156. @Test 157. public void testMeineBean() 158. { 159. MeineBean meineBean = new MeineBean(); 160. Assert.assertEquals( "3.0", meineBean.berechne( "1", "2" ) ); 161. } 162. } 163. Erstellen Sie im src\test\java\integrationtest-Verzeichnis einen HtmlUnit-Integrationstest: HtmlUnitTest.java 164. 165. package integrationtest; 166. 167. import org.junit.*; 168. import com.gargoylesoftware.htmlunit.WebClient; 169. import com.gargoylesoftware.htmlunit.html.*; 170. 171. public class HtmlUnitTest 172. { 173. @Test 174. public void test() throws Exception 175. { 176. String port = System.getProperty( "port" ); 177. test( port, "1", "2", "3.0" ); 178. } 179. 180. public String test( String port, String wert1, String wert2, String ergebnis ) throws Exception 181. { 182. WebClient webClient = new WebClient(); 183. HtmlPage page = (HtmlPage) webClient.getPage( "http://localhost:" + port + "/MvnHtmlIntTest/" ); 184. Assert.assertEquals( "Titel: ", "Startseite", page.getTitleText() ); 185. page = (HtmlPage) page.getAnchorByName( "BerechnungsFormular" ).click(); 186. Assert.assertEquals( "Titel: ", "Webseite mit Berechnungsformular", page.getTitleText() ); 187. HtmlForm form = page.getFormByName( "meinFormular" ); 188. form.getInputByName( "wert1" ).setValueAttribute( wert1 ); 189. form.getInputByName( "wert2" ).setValueAttribute( wert2 ); 190. page = (HtmlPage) form.getInputByName( "berechne" ).click(); 191. String e = page.getFormByName( "meinFormular" ).getInputByName( "ergebnis" ).getValueAttribute(); 192. if( ergebnis != null ) { Assert.assertEquals( "Ergebnis: ", ergebnis, e ); } 193. return e; 194. } 195. } 196. Erstellen Sie im src\test\java\integrationtest-Verzeichnis einen HttpUnitIntegrationstest: HttpUnitTest.java 197. 198. package integrationtest; 199. 200. import org.junit.*; 201. import com.meterware.httpunit.*; 202. 203. public class HttpUnitTest 204. { 205. @Test 206. public void test() throws Exception 207. { 208. String port = System.getProperty( "port" ); 209. test( port, "1", "2", "3.0" ); 210. } 211. 212. public String test( String port, String wert1, String wert2, String ergebnis ) throws Exception 213. { 214. WebConversation conversation = new WebConversation(); 215. WebRequest request = new GetMethodWebRequest( "http://localhost:" + port + "/MvnHtmlIntTest/" ); 216. WebResponse response = conversation.getResponse( request ); 217. Assert.assertEquals( "ReturnCode: ", 200, response.getResponseCode() ); 218. Assert.assertEquals( "ReturnMsg: ", "OK", response.getResponseMessage() ); 219. Assert.assertEquals( "Titel: ", "Startseite", response.getTitle() ); 220. response = response.getLinkWithName( "BerechnungsFormular" ).click(); 221. Assert.assertEquals( "Titel: ", "Webseite mit Berechnungsformular", response.getTitle() ); 222. WebForm form = response.getFormWithName( "meinFormular" ); 223. form.setParameter( "wert1", wert1 ); 224. form.setParameter( "wert2", wert2 ); 225. form.getSubmitButton( "berechne" ).click(); 226. response = conversation.getCurrentPage(); 227. form = response.getFormWithName( "meinFormular" ); 228. String e = form.getParameterValue( "ergebnis" ); 229. if( ergebnis != null ) { Assert.assertEquals( "Ergebnis: ", ergebnis, e ); } 230. return e; 231. } 232. } Die Abfrage if( ergebnis != null ) erscheint im Moment noch etwas unsinnig: Sie wird erst später in MeinFixture im HTML-Akzeptanztest mit Fit benötigt. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F): [\MeinWorkspace\MvnHtmlIntTest] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | '- MeineBean.java | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | |- BerechnungsFormular.jsp | | '- index.jsp | '- [test] | '- [java] | |- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- MeineBeanTest.java | '- [integrationtest] | |- HtmlUnitTest.java | '- HttpUnitTest.java '- pom.xml Manueller Test im Webbrowser: cd \MeinWorkspace\MvnHtmlIntTest mvn jetty:run Warten Sie bis "Started Jetty Server" erscheint und rufen Sie dann im Webbrowser auf: http://localhost:8080/MvnHtmlIntTest Klicken Sie auf den Berechnungsformular-Link, tragen Sie auf der folgenden Webseite zwei Zahlen ein und betätigen Sie "berechne": Sie erhalten die Summe. Beenden Sie Jetty mit "Strg + C". 259. Testen verschiedener Maven-Kommandos: Normaler Build (MvnHtmlIntTest.war wird im target-Verzeichnis erzeugt): mvn clean package dir target\*.war Nur einen einzelnen bestimmten Komponententest ausführen: mvn test -Dtest=de.meinefirma.meinprojekt.MeineBeanTest Build ohne Tests: mvn package -Dmaven.test.skip=true 260. Manueller Aufruf einzelner bestimmter Integrationstests: Versuchen Sie zuerst testweise einen Integrationstest ohne laufenden Jetty aufzurufen und sehen Sie sich die Fehlermeldung in target\surefirereports\integrationtest.HtmlUnitTest.txt an: mvn test -Dtest=integrationtest.HtmlUnitTest type target\surefire-reports\integrationtest.HtmlUnitTest.txt Starten Sie jetzt in einem Kommandozeilenfenster Jetty mit der Webanwendung: mvn jetty:run Starten Sie in einem zweiten Kommandozeilenfenster die Integrationstests (diesmal ohne Fehlermeldung): cd \MeinWorkspace\MvnHtmlIntTest mvn test -Dtest=integrationtest.HtmlUnitTest mvn test -Dtest=integrationtest.HttpUnitTest mvn test -Dtest=integrationtest.*Test Beenden Sie Jetty mit "Strg + C". 261. Automatisierter Integrationstest: Der wichtigste Trick in diesem Beispiel ist die vollständig automatisierte Ausführung der Integrationstests inklusive automatischem Start des Jetty-Servers in einem zusätzlichen Hintergrund-Thread (über <daemon>true</daemon>), Ausführung der Integrationstests und anschließend der automatischen Beendigung von Jetty. Sie sollten normalerweise nicht "mvn integration-test", sondern stattdessen "mvn verify" verwenden, damit auch die "post-integration-test"-Phase ausgeführt wird: mvn clean verify Sie erhalten zwei Testphasen, zuerst default-test und dann integration-tests: ... [INFO] --- maven-surefire-plugin:2.20:test (default-test) @ MvnHtmlIntTest --... ------------------------------------------------------T E S T S ------------------------------------------------------Running de.meinefirma.meinprojekt.MeineBeanTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 ... [INFO] --- jetty-maven-plugin:9.2.2.v20140723:run (start-jetty) @ MvnHtmlIntTest --... [INFO] Started Jetty Server [INFO] [INFO] --- maven-surefire-plugin:2.20:test (integration-tests) @ MvnHtmlIntTest --... ------------------------------------------------------T E S T S ------------------------------------------------------Running integrationtest.HtmlUnitTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 Running integrationtest.HttpUnitTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 Results : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 ... [INFO] --- jetty-maven-plugin:9.2.2.v20140723:stop (stop-jetty) @ MvnHtmlIntTest --... [INFO] Server reports itself as stopped [INFO] ----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ----------------------------------------------------------------------- 262. Dependencies: Lassen Sie sich folgendermaßen einen Dependency-Baum anzeigen und sehen Sie sich an, wie Maven automatisch Versionskonflikte bei den Abhängigkeiten gelöst hat: mvn dependency:tree -Dverbose 263. Eclipse: Um Maven-Projekte in Eclipse bearbeiten zu können, müssen Sie verfahren wie oben unter Maven mit Eclipse beschrieben ist. Ein besonderer Vorteil des Jetty-Plugins ist, dass Sie über das scanIntervalSecondsAttribut steuern können, ob Änderungen direkt in den Webcontainer übernommen werden, ohne dass ein Redeployment notwendig ist. Testen Sie dies, indem Sie zuerst Jetty über "mvn jetty:run" starten und anschließend in Eclipse Änderungen zum Beispiel an BerechnungsFormular.jsp, MeineBean.java oder anderen Dateien durchführen. 264. Infos zu HttpUnit und HtmlUnit: Ein weiteres Programmierbeispiel zu HttpUnit finden Sie unter java-httpunit.htm. Weiteres zu HttpUnit finden Sie unter http://httpunit.sourceforge.net und http://httpunit.sourceforge.net/doc/api/. Weiteres zu HtmlUnit finden Sie unter http://htmlunit.sourceforge.net und http://htmlunit.sourceforge.net/apidocs/. 265. Erweiterungen: Wie Sie Ihre Integrationstests mit Fit und Cargo erweitern, erfahren Sie weiter unten. Um die Datenbank mit bestimmten Inhalten vorzubereiten, können Sie das DbUnit Maven Plugin verwenden. Wenn Sie erweiterte Möglichkeiten für Integrationstests benötigen, sollten Sie sich das Maven Failsafe Plugin ansehen. Siehe hierzu auch den Sonatype-Blog-Eintrag Part 1 und zur Testabdeckung in Integrationstests mit emma4it den Part 2. Automatisierter Integrationstest mit Jetty und JWebUnit JWebUnit leistet Ähnliches wie HtmlUnit und HttpUnit, aber ist oft einfacher und übersichtlicher. 1. Voraussetzung für das folgende Beispiel ist die Durchführung des vorherigen Beispiels Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit (im Folgenden MvnHtmlIntTest genannt). 2. Erzeugen Sie ein neues Projektverzeichnis MvnJWebUnitIntTest und kopieren Sie dorthin das MvnHtmlIntTest-Projekt: cd \MeinWorkspace md MvnJWebUnitIntTest cd MvnJWebUnitIntTest xcopy ..\MvnHtmlIntTest\*.* /S 3. Entfernen Sie in der pom.xml im MvnJWebUnitIntTest-Projektverzeichnis die beiden <dependency>...-Blöcke zu htmlunit und httpunit und fügen Sie stattdessen folgenden neuen <dependency>...-Block ein: 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. <dependency> <groupId>net.sourceforge.jwebunit</groupId> <artifactId>jwebunit-htmlunit-plugin</artifactId> <version>3.3</version> <scope>test</scope> </dependency> Löschen Sie im src\test\java\integrationtest-Verzeichnis die beiden Dateien HtmlUnitTest.java und HttpUnitTest.java und erzeugen Sie stattdessen im selben Verzeichnis die neue Testklasse JWebUnitTest.java: package integrationtest; import net.sourceforge.jwebunit.junit.WebTestCase; public class JWebUnitTest extends WebTestCase { public void test() { setBaseUrl( "http://localhost:8080/MvnHtmlIntTest/" ); beginAt( "/index.jsp" ); assertTitleEquals( "Startseite" ); clickLinkWithText( "Berechnungsformular" ); assertTitleEquals( "Webseite mit Berechnungsformular" ); setTextField( "wert1", "1" ); setTextField( "wert2", "2" ); submit(); assertTextFieldEquals( "ergebnis", "3.0" ); } } Falls Sie die JWebUnit-XPath-Methoden probieren wollen: Den Ergebniswert können Sie beispielsweise abfragen mit: getElementAttributeByXPath( "//input[lower-case(@type)='text' and @name='ergebnis']", "value" ); 32. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F): 33. 34. [\MeinWorkspace\MvnJWebUnitIntTest] 35. |- [src] 36. | |- [main] 37. | | |- [java] 38. | | | '- [de] 39. | | | '- [meinefirma] 40. | | | '- [meinprojekt] 41. | | | '- MeineBean.java 42. | | '- [webapp] 43. | | |- [WEB-INF] 44. | | | '- web.xml 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. | | |- BerechnungsFormular.jsp | | '- index.jsp | '- [test] | '- [java] | |- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- MeineBeanTest.java | '- [integrationtest] | '- JWebUnitTest.java '- pom.xml 56. Führen Sie den Integrationstest aus (wieder mit Jetty im Hintergrund-Thread): cd \MeinWorkspace\MvnJWebUnitIntTest mvn clean verify 57. Weiteres zu JWebUnit finden Sie unter http://jwebunit.sourceforge.net, http://jwebunit.sourceforge.net/apidocs/ und http://jwebunit.sourceforge.net/apidocs/net/sourceforge/jwebunit/junit/WebTestCase.html . Automatisierter Integrationstest mit Selenium Mit Selenium können Sie über automatisierte Integrationstests Webanwendungen über einen Webbrowser testen. Dies hat den Vorteil, dass JavaScript und Ajax leichter und realitätsnäher getestet werden. Allerdings gibt es auch einen Nachteil: Da die Installation eines Webbrowsers vorausgesetzt wird (außer bei Benutzung des HtmlUnitDriver), ist die Ausführung auf beliebigen PCs und Betriebssystemen nicht mehr so einfach zu gewährleisten und die Ausführung in Continuous-Integration-Systemen könnte unsicherer sein. Falls Sie nur einfache HTML-Anwendungen ohne DHTML, JavaScript und Ajax testen wollen, können HtmlUnit und HttpUnit besser geeignet sein. Bitte beachten Sie die drei grundsätzlich verschiedenen Selenium-Versionen: Selenium 1, auch Selenium RC (Remote Control) genannt: Selenium 1 (RC) ist der Vorgänger von Selenium 2 (WebDriver) und sollte möglichst nicht mehr für neue Anwendungen verwendet werden. Selenium 1 (RC) basiert auf der Verwendung eines für den Test gestarteten Proxy-Servers (Selenium-Server), welcher den gewünschten Webbrowser bedient. Die Testprogrammierung beginnt typischerweise mit zwei Zeilen ähnlich zu: Selenium selenium = new DefaultSelenium( "localhost", 4444, "*firefox", "http://www.meintesthost.de" ); selenium.start(); Siehe: Selenium 1 (Selenium RC) und Migrating From Selenium RC to Selenium WebDriver. Selenium 2, auch Selenium WebDriver genannt: Anders als Selenium 1 verwendet Selenium 2 (WebDriver) keinen Proxy-Server, sondern spricht den gewünschten Webbrowser direkt an, über browserspezifische WebDriver. Beispielsweise für den Firefox-Webbrowser könnte das Testprogramm mit einer Zeile beginnen ähnlich zu: WebDriver webDriver = new FirefoxDriver(); Siehe: Selenium WebDriver und Selenium-Doku. Das unten gezeigte Beispiel demonstriert, wie zur Laufzeit der gewünschten Webbrowser gewählt werden kann. Selenium 2 (WebDriver) bietet ein neues und verbessertes Programmier-API. Für Migrationszwecke kann aber auch in Selenium 2 auf viele Funktionen des alten Selenium-1-Programmier-APIs zurückgegriffen werden, indem folgendermaßen das alte Selenium-Objekt erzeugt wird: Selenium selenium = new WebDriverBackedSelenium( webDriver, "http://www.meintesthost.de" ); Selenium IDE: Selenium IDE ist eine Erweiterung (Plug-in) für den Firefox-Webbrowser, womit schnell und einfach im Firefox-Webbrowser ablauffähige Skripte zu Benutzerinteraktionen erstellt und abgespielt werden können (record-and-playback), beispielsweise zum Reproduzieren von Fehlersituationen oder zur Vorbereitung von automatisierten Tests (Prototyping). Die mit Selenium IDE generierbaren Skripte sind nicht für Integrationsund Regressionstests vorgesehen, wo hohe Stabilität und Reproduzierbarkeit wichtig ist. Siehe: Selenium IDE Übersicht und Selenium IDE Doku. Das folgende Beispiel verwendet ausschließlich Selenium 2 (WebDriver). 1. Sehen Sie sich die Selenium-Doku, das Interface WebDriver und die Packages org.openqa.selenium und org.openqa.selenium.support.ui an. 2. Legen Sie ein neues Projekt an: cd \MeinWorkspace md MvnSelenium cd MvnSelenium md src\test\java\integrationtest 3. Erzeugen Sie im Projektverzeichnis die Projektkonfiguration: pom.xml 4. 5. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 6. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 7. <modelVersion>4.0.0</modelVersion> 8. <groupId>de.meinefirma.meinprojekt</groupId> 9. <artifactId>MvnSelenium</artifactId> 10. <version>1.0-SNAPSHOT</version> 11. <packaging>jar</packaging> 12. <name>MvnSelenium</name> 13. <build> 14. <plugins> 15. <plugin> 16. <groupId>org.apache.maven.plugins</groupId> 17. <artifactId>maven-compiler-plugin</artifactId> 18. <version>3.6.1</version> 19. <configuration> 20. <source>1.7</source> 21. <target>1.7</target> 22. </configuration> 23. </plugin> 24. </plugins> 25. </build> 26. <dependencies> 27. <dependency> 28. <groupId>org.seleniumhq.selenium</groupId> 29. <artifactId>selenium-java</artifactId> 30. <version>3.4.0</version> 31. <scope>test</scope> 32. </dependency> 33. <dependency> 34. <groupId>org.seleniumhq.selenium</groupId> 35. <artifactId>selenium-htmlunit-driver</artifactId> 36. <version>2.52.0</version> 37. <scope>test</scope> 38. </dependency> 39. <dependency> 40. <groupId>junit</groupId> 41. <artifactId>junit</artifactId> 42. <version>4.12</version> 43. <scope>test</scope> 44. </dependency> 45. </dependencies> 46. </project> 47. 48. Erzeugen Sie im src\test\java\integrationtest-Verzeichnis den WebDriver-Wrapper: WebDriverWrapper.java 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. package integrationtest; import import import import import import import org.openqa.selenium.*; org.openqa.selenium.chrome.ChromeDriver; org.openqa.selenium.edge.EdgeDriver; org.openqa.selenium.firefox.FirefoxDriver; org.openqa.selenium.htmlunit.HtmlUnitDriver; org.openqa.selenium.ie.InternetExplorerDriver; org.openqa.selenium.safari.SafariDriver; /** * Wrapper fuer Selenium-WebDriver fuer waehlbaren Webbrowser und als AutoCloseable 63. */ 64. public class WebDriverWrapper implements AutoCloseable 65. { 66. private WebDriver webDriver; 67. 68. /** 69. * Selenium-WebDriver fuer waehlbaren Webbrowser instanziieren. 70. * @param browserName leer fuer HtmlUnitDriver oder 71. * FF, Chrome, Edge, IE oder Safari fuer gewuenschten Webbrowser. 72. * @param webDriverExePath leer fuer HtmlUnitDriver, FF und Safari; 73. * fuer Chrome und IE wird Pfad zum externen WebDriver.exe benoetigt, 74. * z.B. zu chromedriver.exe, iexploredriver.exe bzw. MicrosoftWebDriver.exe. 75. */ 76. public WebDriverWrapper( String browserName, String webDriverExePath ) 77. { 78. if( browserName != null ) { 79. String brwsr = browserName.trim().toLowerCase(); 80. String wbdrv = ( webDriverExePath != null ) ? webDriverExePath.trim() : null; 81. if( brwsr.contains( "firefox" ) || brwsr.contains( "ff" ) ) { 82. webDriver = new FirefoxDriver(); 83. } else if( brwsr.contains( "chrome" ) ) { 84. if( wbdrv != null ) { System.setProperty( "webdriver.chrome.driver", wbdrv ); } 85. webDriver = new ChromeDriver(); 86. } else if( brwsr.contains( "edge" ) ) { 87. if( wbdrv == null ) { 88. wbdrv = "C:\\Program Files (x86)\\Microsoft Web Driver\\MicrosoftWebDriver.exe"; 89. } 90. System.setProperty( "webdriver.edge.driver", wbdrv ); 91. webDriver = new EdgeDriver(); 92. } else if( brwsr.contains( "internetexplorer" ) || brwsr.contains( "ie" ) ) { 93. if( wbdrv != null ) { System.setProperty( "webdriver.ie.driver", wbdrv ); } 94. webDriver = new InternetExplorerDriver(); 95. } else if( brwsr.contains( "safari" ) ) { 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. } 116. webDriver = new SafariDriver(); } } if( webDriver == null ) { webDriver = new HtmlUnitDriver(); } } public WebDriver getWebDriver() { return webDriver; } @Override public void close() { webDriver.quit(); webDriver = null; } 117. 118. Erzeugen Sie im src\test\java\integrationtest-Verzeichnis die Testklasse: SeleniumSimpleTest.java 119. 120. 121. package integrationtest; 122. 123. import org.junit.*; 124. import org.openqa.selenium.*; 125. import org.openqa.selenium.support.ui.*; 126. 127. /** 128. * Einfacher Selenium-Test mit der Wikipedia-Webseite.<br> 129. * Wahlweise ausfuehrbar mit manueller main()-Methode 130. * oder automatisiert als JUnit-Test. 131. */ 132. public class SeleniumSimpleTest 133. { 134. public static final String BROWSERNAME_KEY = "_BROWSERNAME"; 135. public static final String WEBDRIVEREXEPATH_KEY = "_WEBDRIVEREXEPATH"; 136. 137. /** 138. * Manuell ausfuehrbarer Selenium-Test.<br> 139. * @param args 1. Parameter leer fuer HtmlUnitDriver oder 140. * FF, Chrome, Edge, IE oder Safari fuer gewuenschten Webbrowser.<br> 141. * 2. Parameter leer fuer HtmlUnitDriver, FF und Safari; 142. * fuer Chrome und IE wird Pfad zum externen WebDriver.exe benoetigt, 143. * z.B. zu chromedriver.exe, iexploredriver.exe bzw. MicrosoftWebDriver.exe.<br> 144. */ 145. public static void main( String[] args ) 146. { 147. String browserName = null, webDriverExePath = null; 148. if( args != null ) { 149. if( args.length > 0 ) { browserName = args[0]; } 150. if( args.length > 1 ) { webDriverExePath = args[1]; } 151. } 152. testSelenium( browserName, webDriverExePath ); 153. } 154. 155. /** 156. * Automatisiert ausfuehrbarer JUnit-Selenium-Test.<br> 157. * Falls die Umgebungsvariablen _BROWSERNAME und _WEBDRIVEREXEPATH 158. * gesetzt sind, kann darueber der gewuenschte Webbrowser definiert werden.<br> 159. * _BROWSERNAME: leer fuer HtmlUnitDriver oder 160. * FF, Chrome, Edge, IE oder Safari fuer gewuenschten Webbrowser.<br> 161. * _WEBDRIVEREXEPATH: leer fuer HtmlUnitDriver, FF und Safari; 162. * fuer Chrome und IE wird Pfad zum externen WebDriver.exe benoetigt, 163. * z.B. zu chromedriver.exe, iexploredriver.exe bzw. MicrosoftWebDriver.exe.<br> 164. */ 165. @Test 166. public void testSelenium() 167. { 168. String browserName = System.getProperty( BROWSERNAME_KEY ); 169. String webDriverExePath = System.getProperty( WEBDRIVEREXEPATH_KEY ); 170. Assert.assertTrue( testSelenium( browserName, webDriverExePath ) ); 171. } 172. 173. private static boolean testSelenium( String browserName, String webDriverExePath ) 174. { 175. final String url = "http://de.wikipedia.org"; 176. final String ttl = "Selenium \u2013 Wikipedia"; 177. 178. // WebDriver starten: 179. try( WebDriverWrapper wdwrap = new WebDriverWrapper( browserName, webDriverExePath ) ) { 180. WebDriver webDriver = wdwrap.getWebDriver(); 181. WebDriverWait wait = new WebDriverWait( webDriver, 10 ); 182. 183. // Webseite laden, Daten eintragen und Antwort anfordern: 184. webDriver.get( url ); 185. WebElement element = wait.until( ExpectedConditions.presenceOfElementLocated( By.id( "searchInput" ) ) ); 186. String titelAbfrageseite = webDriver.getTitle(); 187. element.sendKeys( "Selenium" ); 188. element = webDriver.findElement( By.name( "go" ) ); 189. element.click(); 190. 191. // Ergebnisse pruefen: 192. boolean ok = wait.until( ExpectedConditions.titleIs( ttl ) ).booleanValue(); 193. String titelErgebnisseite = webDriver.getTitle(); 194. String hauptueberschrift = webDriver.findElement( By.id( "firstHeading" ) ).getText(); 195. ok &= ttl.equals( titelErgebnisseite ) && "Selenium".equals( hauptueberschrift ); 196. System.out.println( "\n-----------------------------------------------------------" ); 197. if( browserName != null ) { 198. System.out.println( "Webbrowsername: " + browserName ); 199. } 200. System.out.println( "WebDriver: " + webDriver.getClass().getSimpleName() ); 201. System.out.println( "Test-URL: " + url ); 202. System.out.println( "Titel der Abfrage-Seite: " + titelAbfrageseite ); 203. System.out.println( "Titel der Ergebnis-Seite: " + titelErgebnisseite ); 204. System.out.println( "Hauptueberschrift: " + hauptueberschrift ); 205. System.out.println( ( ok ) ? "OK" : "Fehler" ); 206. System.out.println( "-----------------------------------------------------------\n" ); 207. return ok; 208. } 209. } 210. } 211. 212. Der Test startet einen Selenium-WebDriver im WebDriverWrapper, 213. öffnet die Wikipedia-Startseite in webDriver.get( url ), 214. sucht das Input-Textfeld "searchInput", 215. trägt den Suchbegriff "Selenium" ein und überprüft die Ergebnis-Webseite. 216. 217. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F): 218. 219. 220. [\MeinWorkspace\MvnSelenium] 221. |- [src] 222. | '- [test] 223. | '- [java] 224. | '- [integrationtest] 225. | |- SeleniumSimpleTest.java 226. | 227. '- pom.xml 228. '- WebDriverWrapper.java 229. 230. Starten Sie den Integrationstest ohne Angabe eines Webbrowsers: 231. cd \MeinWorkspace\MvnSelenium mvn clean test Sie erhalten einige Fehlermeldungen zur HTML-Seite und zuletzt die Erfolgsmeldung: -----------------------------------------------------------WebDriver: HtmlUnitDriver Test-URL: http://de.wikipedia.org Titel der Abfrage-Seite: Wikipedia – Die freie Enzyklopädie Titel der Ergebnis-Seite: Selenium – Wikipedia Hauptueberschrift: Selenium OK ------------------------------------------------------------ Als WebDriver wurde HtmlUnitDriver verwendet, der keine WebbrowserInstallation voraussetzt. 232. 233. Falls Sie Mozilla Firefox installiert haben, schließen Sie alle Firefox-Fenster, und starten Sie den Integrationstest folgendermaßen: 234. mvn clean test -D_BROWSERNAME=ff Der Firefox-Webbrowser wird gestartet, Sie können die Seitenaufrufe verfolgen, und es wird gemeldet, dass der FirefoxDriver-WebDriver verwendet wurde: ... WebDriver: ... FirefoxDriver 235. 236. Falls Sie Windows 10 installiert haben, schließen Sie alle EdgeWebbrowser-Fenster, und starten Sie den Integrationstest folgendermaßen: 237. mvn clean test -D_BROWSERNAME=Edge Der Edge-Webbrowser wird gestartet, Sie können die Seitenaufrufe verfolgen, und es wird gemeldet, dass der EdgeDriver-WebDriver verwendet wurde: ... WebDriver: ... EdgeDriver Voraussetzung hierfür ist die einmalige Installation des MicrosoftWebDrivers MicrosoftWebDriver.exe über die Installationsdatei MicrosoftWebDriver.msi, welche Sie über https://www.microsoft.com/en-us/download/details.aspx?id=48212 erhalten. Automatisierter Integrationstest mit Fit 1. 2. 3. 4. 5. 6. Ausführliche Infos zu Fit finden Sie in java-fit.htm. Wechseln Sie in Ihr Projekte-Verzeichnis und erzeugen Sie das neue Projekt MvnFitTest: 7. cd \MeinWorkspace md MvnFitTest cd MvnFitTest md src\main\java\de\meinefirma\meinprojekt md src\test\fit md src\test\java\fixtures tree /F [\MeinWorkspace\MvnFitTest] '- [src] |- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] '- [test] |- [fit] '- [java] '- [fixtures] 8. 9. Erzeugen Sie im MvnFitTest-Projektverzeichnis die Projektkonfigurationsdatei: pom.xml 10. 11. 12. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 13. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 14. <modelVersion>4.0.0</modelVersion> 15. <groupId>de.meinefirma.meinprojekt</groupId> 16. <artifactId>MvnFitTest</artifactId> 17. <version>1.0-SNAPSHOT</version> 18. <packaging>jar</packaging> 19. <name>MvnFitTest</name> 20. <build> 21. <plugins> 22. <plugin> 23. <groupId>org.codehaus.mojo</groupId> 24. <artifactId>fit-maven-plugin</artifactId> 25. <version>2.0-beta-3</version> 26. <executions> 27. <execution> 28. <id>fixture</id> 29. <phase>integration-test</phase> 30. <configuration> 31. <sourceDirectory>src/test/fit</sourceDirectory> 32. <caseSensitive>true</caseSensitive> 33. <sourceIncludes>**/*FitTest.html</sourceIncludes> 34. <parseTags> 35. <parseTag>table</parseTag> 36. <parseTag>tr</parseTag> 37. <parseTag>td</parseTag> 38. </parseTags> 39. <outputDirectory>target/fit</outputDirectory> 40. <ignoreFailures>false</ignoreFailures> 41. </configuration> 42. <goals> 43. <goal>run</goal> 44. </goals> 45. </execution> 46. </executions> 47. </plugin> 48. </plugins> 49. </build> 50. <dependencies> 51. <dependency> 52. <groupId>com.c2.fit</groupId> 53. <artifactId>fit</artifactId> 54. <version>1.1</version> 55. </dependency> 56. </dependencies> 57. </project> 58. 59. Erläuterungen zu den fit-maven-plugin-<configuration>-Optionen finden Sie unter http://mojo.codehaus.org/fit-maven-plugin/run-mojo.html. Falls Sie die Fit-Tests nicht mit HTML-Seiten, sondern mit Wiki-Seiten steuern wollen, finden Sie Konfigurationshinweise unter http://mojo.codehaus.org/fit-maven-plugin/usage.html. 60. 61. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis die (schon bekannte) Bean: MeineBean.java 62. 63. 64. package de.meinefirma.meinprojekt; 65. 66. public class MeineBean 67. { 68. public String berechne( String d1, String d2 ) 69. { 70. try { 71. return "" + (Double.parseDouble( d1 ) + Double.parseDouble( d2 )); 72. } catch( Exception ex ) { 73. return ""; 74. } 75. 76. } 77. 78. 79. } Erzeugen Sie im src\test\java\fixtures-Verzeichnis die Fit-FixtureKlasse: MeinFixture.java 80. 81. 82. package fixtures; 83. 84. public class MeinFixture extends fit.ColumnFixture 85. { 86. public String wert1; 87. public String wert2; 88. 89. public String berechne() 90. { 91. return (new de.meinefirma.meinprojekt.MeineBean()).berechne( wert1, wert2 ); 92. } 93. } 94. 95. 96. 97. Die Fit-Testbeschreibung erfolgt als HTML-Tabelle (erste Zeile: Fixture-Angabe, zweite Zeile: Eingabeparameter und erwartete zu berechnende Ausgabewerte, weitere Zeilen: Testfälle). 98. Erzeugen Sie im src\test\fit-Verzeichnis die Fit-Testklasse: MeinFitTest.html <!doctype html public "-//w3c//dtd html 4.0 transitional//en"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso8859-1"> <title>Mein Fit-Test</title> </head> <body> <h2>Mein Fit-Test</h2> <table border="1" cellspacing="0" cellpadding="3"> <tr><td colspan="3"> fixtures.MeinFixture </td></tr> <tr><td> wert1 </td><td> wert2 </td><td> berechne() </td></tr> <tr><td> 0 </td><td> &nbsp; </td><td> &nbsp; </td></tr> <tr><td> <tr><td> <tr><td> <tr><td> </table> 1 +1 -42 1.234 </td><td> </td><td> </td><td> </td><td> 2 -1 13 5.678 </td><td> </td><td> </td><td> </td><td> <br> <table border="1" cellspacing="0" cellpadding="3"> <tr><td colspan="2">fit.Summary</td></tr> </table> </body> </html> Sehen Sie sich die Testfall-Beschreibungen an: start src\test\fit\MeinFitTest.html fixtures.MeinFixture wert1 wert2 berechne() 0 1 2 3.0 +1 -1 0.0 -42 13 -29.0 1.234 5.678 6.912 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. Überprüfen Sie Ihre Projektstruktur mit tree /F: [\MeinWorkspace\MvnFitTest] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- MeineBean.java | '- [test] | |- [fit] 3.0 0.0 -29.0 6.912 </td></tr> </td></tr> </td></tr> </td></tr> 113. 114. 115. 116. 117. 118. | | '- MeinFitTest.html | '- [java] | '- [fixtures] | '- MeinFixture.java '- pom.xml 119. 120. Führen Sie den Akzeptanztest als Maven-Integrationstest aus: 121. cd \MeinWorkspace\MvnFitTest mvn clean verify Sie erhalten: [INFO] --- fit-maven-plugin:2.0-beta-3:run (fixture) @ MvnFitTest --[INFO] Running Fixture with input file src\test\fit\MeinFitTest.html and output file target\fit\MeinFitTest.html [INFO] ----------------------------------------------------------------------[INFO] BUILD SUCCESS [INFO] ----------------------------------------------------------------------- 122. 123. Sehen Sie sich das Ergebnis als HTML-Seite an (grün bedeutet "ok"): 124. start target\fit\MeinFitTest.html fixtures.MeinFixture wert1 wert2 berechne() 0 null 1 2 3.0 +1 -1 0.0 -42 13 -29.0 1.234 5.678 6.912 fit.Summary counts 4 right, 0 wrong, 0 ignored, 0 exceptions 125. 126. Ändern Sie in src\test\fit\MeinFitTest.html einen der Vorgabewerte in der berechne()-Spalte, 127. führen Sie den Akzeptanztest erneut aus und sehen Sie sich die Fehlermeldungen im Kommandozeilenfenster und in der Ergebnis-HTML-Seite an, 128. zum Beispiel so: 129. 130. 131. [INFO] [fit:run {execution: fixture}] 132. [INFO] Running Fixture with input file src\test\fit\MeinFitTest.html and output file target\fit\MeinFitTest.html 133. [INFO] ----------------------------------------------------------------------134. [ERROR] BUILD ERROR 135. [INFO] ----------------------------------------------------------------------136. [INFO] Failed to execute FitRunner 137. Embedded error: Fixture failed with counts: 3 right, 1 wrong, 0 ignored, 0 exceptions 138. fixtures.MeinFixture wert1 wert2 berechne() 0 null 1 2 4.0 expected 3.0 actual +1 -1 0.0 -42 13 -29.0 1.234 5.678 6.912 139. fit.Summary counts 3 right, 1 wrong, 0 ignored, 0 exceptions 140. Automatisierter HTML-Akzeptanztest mit Jetty und Fit 1. 2. Im letzten Beispiel hat das Fit-Fixture direkt die Berechnungs-Bean angesprochen. 3. Das folgende Beispiel setzt stattdessen auf einer höheren Ebene an und ermittelt das Ergebnis über die HTML-Webseite. 4. 5. Voraussetzung für das folgende Beispiel ist die Durchführung der beiden obigen Beispiele 6. Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit und Automatisierter Integrationstest mit Fit. 7. 8. Erzeugen Sie ein neues Projektverzeichnis MvnHtmlFitTest und kopieren Sie dorthin Teile der beiden Projekte: 9. cd \MeinWorkspace md MvnHtmlFitTest cd MvnHtmlFitTest xcopy ..\MvnHtmlIntTest\*.* /S xcopy ..\MvnFitTest\src\test\*.* src\test\ /S tree /F 10. 11. Ihre Projektstruktur sieht jetzt so aus: 12. 13. 14. [\MeinWorkspace\MvnHtmlFitTest] 15. |- [src] 16. | |- [main] 17. | | |- [java] 18. | | | '- [de] 19. | | | '- [meinefirma] 20. | | | '- [meinprojekt] 21. | | | '- MeineBean.java 22. | | '- [webapp] 23. | | |- [WEB-INF] 24. | | | '- web.xml 25. | | |- BerechnungsFormular.jsp 26. | | '- index.jsp 27. | '- [test] 28. | |- [fit] 29. | | '- MeinFitTest.html 30. | '- [java] 31. | |- [de] 32. | | '- [meinefirma] 33. | | '- [meinprojekt] 34. | | '- MeineBeanTest.java 35. | |- [fixtures] 36. | | '- MeinFixture.java 37. | '- [integrationtest] 38. | |- HtmlUnitTest.java 39. | '- HttpUnitTest.java 40. '- pom.xml 41. 42. 43. 44. Fügen Sie aus der pom.xml des MvnFitTest-Projekts sowohl den <plugin>... als auch den <dependency>...-Block ein in die 45. pom.xml des MvnHtmlFitTest-Projekts. 46. 47. 48. Ändern Sie im src\test\java\fixtures-Verzeichnis die MeinFixture.java zu: 49. 50. 51. package fixtures; 52. 53. public class MeinFixture extends fit.ColumnFixture 54. { 55. public String wert1; 56. public String wert2; 57. 58. public String berechne() throws Exception 59. { 60. return (new integrationtest.HtmlUnitTest()).test( "8080", wert1, wert2, null ); 61. } 62. } 63. Statt HtmlUnitTest könnten Sie genauso gut auch HttpUnitTest nehmen. Bitte beachten Sie, dass das Fit-Fixture jetzt nicht mehr direkt die Berechnungs-Bean anspricht, sondern über die HTML-Webseite das Ergebnis ermittelt. 64. 65. 66. Führen Sie die Tests aus: cd \MeinWorkspace\MvnHtmlFitTest mvn clean verify start target\fit\MeinFitTest.html Sie erhalten die bereits oben gezeigten Erfolgsmeldungen. Automatisierter Integrationstest mit Cargo für WebLogic, JBoss, WildFly und Tomcat Installation von Oracle WebLogic 12.2.1 (falls Sie WebLogic verwenden wollen) 1. 2. 3. 4. Installationsbeschreibungen finden Sie unter Oracle WebLogic. Installation von JBoss (falls Sie JBoss verwenden wollen) 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Downloaden Sie den JBoss Application Server von http://jboss.org/jbossas (z.B. jboss-as-distribution-6.1.0.Final.zip). Entzippen Sie das JBoss-Archive zum Beispiel nach \Tools\JBoss. Testen Sie die JBoss-Installation (passen Sie den Pfad zu Ihrer JBossInstallation an): cd \Tools\JBoss\bin run.bat (bzw. standalone.bat) Warten Sie bis "... JBoss ... Started ..." erscheint. start http://localhost:8080 Eine JBoss-Übersichtsseite erscheint. Beenden Sie JBoss mit "Strg + C". 11. 12. 13. 14. Weitere Installationshinweise (z.B. Datasource) finden Sie unter jee-ejb2.htm#InstallationJBoss. Installation von WildFly (falls Sie WildFly verwenden wollen) 1. 2. 3. 4. 5. 6. 7. Downloaden Sie den WildFly Application Server von http://www.wildfly.org (z.B. wildfly-8.0.0.Beta1.zip). Entzippen Sie das WildFly-Archive zum Beispiel nach \Tools\WildFly. 8. 9. Testen Sie die WildFly-Installation (passen Sie den Pfad zu Ihrer WildFly-Installation an): 10. cd \Tools\WildFly\bin standalone.bat Warten Sie bis "... WildFly ... Started ..." erscheint. start http://localhost:8080 Eine WildFly-Welcome-Übersichtsseite erscheint. Beenden Sie WildFly mit "Strg + C". Installation von Tomcat (falls Sie Tomcat verwenden wollen) 1. 2. 3. 4. Zum Tomcat finden Sie eine Installationsbeschreibung unter jsp-install.htm#InstallationUnterWindows. Automatisierter Integrationstest mit dem Cargo-Plugin sowie mit HttpUnit und HtmlUnit 1. 2. Voraussetzung für das folgende Beispiel ist die Installation von WebLogic, JBoss, WildFly, Tomcat oder eines anderen Servers, 3. den Cargo unterstützt. 4. 5. 6. 7. 8. 9. 10. 11. Eine weitere Voraussetzung ist die Durchführung (oder des Downloads) des obigen Beispiels Automatisierter Integrationstest mit Jetty, HtmlUnit und HttpUnit. Erzeugen Sie ein neues Projekt mit dem Projektverzeichnis MvnAppSrvIntTest: 12. cd \MeinWorkspace md MvnAppSrvIntTest cd MvnAppSrvIntTest xcopy ..\MvnHtmlIntTest\*.* /S 13. 14. 15. Ändern Sie Folgendes in der pom.xml: Entfernen Sie den gesamten folgenden Jetty-<plugin>-Block (aber nicht den maven-surefire-plugin-Block): <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> ... </plugin> Fügen Sie stattdessen folgenden Cargo-<plugin>-Block ein: <plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <version>1.6.3</version> <configuration> <container> <containerId>${appsrv.containerId}</containerId> <home>${appsrv.srvhome}</home> </container> <configuration> <type>existing</type> <home>${appsrv.dmnhome}</home> <properties> <!-- Falls WebLogic: Anpassen: --> <cargo.weblogic.administrator.user>weblogic</cargo.weblogic.administrat or.user> <cargo.weblogic.administrator.password>weblogic0</cargo.weblogic.admini strator.password> <cargo.weblogic.server>AdminServer</cargo.weblogic.server> <cargo.servlet.port>${port}</cargo.servlet.port> </properties> </configuration> <wait>false</wait> </configuration> <executions> <execution> <id>start-container</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>stop-container</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> Ersetzen Sie die Zeile <port>8080</port> durch: <port>${port}</port> Fügen Sie vor dem </project>-Ende-Tag ein (passen Sie die Pfade an Ihre Application-Server-Installation an): <profiles> <profile> <id>WebLogic</id> <activation> <property> <name>env.APPSRV</name> <value>WebLogic</value> </property> </activation> <properties> <!-- Falls WebLogic: Anpassen: --> <appsrv.containerId>weblogic122x</appsrv.containerId> <appsrv.srvhome>C:\WebLogic\wlserver</appsrv.srvhome> <appsrv.dmnhome>C:\WebLogic\user_projects\domains\MeineDomain</appsrv.d mnhome> <port>7001</port> </properties> </profile> <profile> <id>JBoss</id> <activation> <property> <name>env.APPSRV</name> <value>JBoss</value> </property> </activation> <properties> <!-- Falls JBoss: Anpassen: --> <appsrv.containerId>jboss61x</appsrv.containerId> <appsrv.srvhome>D:\Tools\JBoss</appsrv.srvhome> <appsrv.dmnhome>${appsrv.srvhome}/server/default</appsrv.dmnhome> <port>8080</port> </properties> </profile> <profile> <id>WildFly</id> <activation> <property> <name>env.APPSRV</name> <value>WildFly</value> </property> </activation> <properties> <!-- Falls WildFly: Anpassen: --> <appsrv.containerId>wildfly8x</appsrv.containerId> <appsrv.srvhome>D:\Tools\WildFly</appsrv.srvhome> <appsrv.dmnhome>${appsrv.srvhome}/standalone</appsrv.dmnhome> <port>8080</port> </properties> </profile> <profile> <id>Tomcat</id> <activation> <property> <name>env.APPSRV</name> <value>Tomcat</value> </property> </activation> <properties> <!-- Falls Tomcat: Anpassen: --> <appsrv.containerId>tomcat8x</appsrv.containerId> <appsrv.srvhome>D:\Tools\Tomcat</appsrv.srvhome> <appsrv.dmnhome>${appsrv.srvhome}</appsrv.dmnhome> <port>8080</port> </properties> </profile> </profiles> Natürlich können Sie analog auch andere Application Server einbinden. 16. 17. 18. Lassen Sie sich anzeigen, welche Profile zur Verfügung stehen: cd \MeinWorkspace\MvnAppSrvIntTest mvn help:all-profiles 19. 20. Führen Sie für Ihren App-Server (automatisiert) die Schritte des Integrationstests aus (Vorbereitung, App-Server-Start, Deployment, Integrationstest, App-Server-Stopp): 21. mvn verify -P WebLogic mvn verify -P JBoss mvn verify -P WildFly mvn verify -P Tomcat Bitte beachten Sie, dass der App-Server automatisch hoch- und heruntergefahren wird. 22. 23. 24. 25. 26. Falls Sie eine Fehlermeldung erhalten ähnlich zu: 10.4.5 404 Not Found The server has not found anything matching the Request-URI. com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException: 404 Not Found for http://localhost:7001/MvnHtmlIntTest/ Dann fügen Sie im fehlgeschlagenen Integrationstest (z.B. HtmlUnitTest.java) vor der Zeile "WebClient webClient = new WebClient();" die zusätzliche Zeile "Thread.sleep( 3000 );" ein, damit der Webserver das Deployment vollständig fertigstellen kann. 27. 28. Im Falle des JBoss Application Servers erhalten Sie: 29. 30. 31. ... 32. [INFO] --- maven-surefire-plugin:2.20:test (default-test) @ MvnHtmlIntTest --33. ------------------------------------------------------34. T E S T S 35. ------------------------------------------------------36. Running de.meinefirma.meinprojekt.MeineBeanTest 37. Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 38. ... 39. [INFO] [stalledLocalDeployer] Deploying [\MeinWorkspace\MvnAppSrvIntTest\target\MvnHtmlIntTest.war] to [\Tools\JBoss/server/default/deploy]... 40. ... 41. [INFO] [talledLocalContainer] JBoss 6.1.0 started on port [8080] 42. ... 43. [INFO] --- maven-surefire-plugin:2.20:test (integration-tests) @ MvnHtmlIntTest --44. [INFO] Surefire report directory: \MeinWorkspace\MvnAppSrvIntTest\target\surefire-reports 45. ------------------------------------------------------46. T E S T S 47. ------------------------------------------------------48. Running integrationtest.HtmlUnitTest 49. Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 50. Running integrationtest.HttpUnitTest 51. Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 52. Results : 53. Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 54. ... 55. [INFO] [talledLocalContainer] JBoss 6.1.0 is stopped 56. [INFO] ----------------------------------------------------------------------57. [INFO] BUILD SUCCESS 58. [INFO] ----------------------------------------------------------------------59. 60. 61. Das .war-Archiv wurde in Autodeploy-Verzeichnisse unterhalb von ${appsrv.dmnhome} kopiert: 62. Im Falle von WebLogic nach: ${appsrv.dmnhome}\autodeploy, 63. im Falle von JBoss nach: ${appsrv.dmnhome}\deploy, 64. im Falle von WildFly nach: ${appsrv.dmnhome}\deployments und 65. 66. im Falle von Tomcat nach: ${appsrv.dmnhome}\webapps. 67. 68. 69. Unter selenium.htm finden Sie weitere Cargo-Beispiele, zum Beispiel mit dem zipUrlInstaller, mit dem Sie die Installation von Servern automatisieren können. 70. 71. 72. 73. 74. 75. 76. 77. 78. Cargo bietet einige weitere interessante Optionen, beispielsweise das Mergen von WAR-Dateien und die Möglichkeit, Tomcat im Security Mode zu starten. Weiters zum Cargo-Maven-Plugin finden Sie unter http://cargo.codehaus.org/Maven2+plugin und http://cargo.codehaus.org/Maven2+Plugin+Reference+Guide. 79. 80. 81. 82. 83. Sehen Sie sich auch die Beispiele zu Tests mit OpenEJB und Arquillian an. Integrationstests mit dem Maven Failsafe Plugin Anforderungen an Integrationstests Häufige Anforderungen an Integrationstests im Maven-Umfeld sind: 1. 2. 3. Trennung der verschiedenen Testebenen: "Unittests" (= "Modultests", "Komponententests"), die ohne weitere Systeme funktionsfähig sind und so schnell sind, dass sie "häufig" aufgerufen werden, und "Integrationstests" (eventuell auch "Systemtests"), die nur zusammen mit weiteren Systemen (z.B. DB, App-Server etc.) ausgeführt werden können und seltener aufgerufen werden (z.B. in Continuous-Integration-Systemen). 4. "mvn test" soll nur die "schnellen Unittests" ausführen. 5. "mvn verify" soll die "schnellen Unittests" und die "Integrationstests" ausführen 6. (normalerweise sollte nicht "mvn integration-test", sondern stattdessen "mvn verify" verwendet werden, 7. damit auch die "post-integration-test"-Phase ausgeführt wird). 8. Wünschenswert wäre, wenn bei "mvn install" die Wahl besteht, 9. ob hierbei nur die "schnellen Unittests" oder auch die "Integrationstests" ausgeführt werden. 10. Wünschenswert wäre, wenn bei "mvn verify" im Falle fehlschlagender Integrationstests der Testlauf nicht einfach abgebrochen wird, 11. sondern trotzdem die der integration-test-Phase nachgeschaltete post-integration-test-Phase ausgeführt wird, 12. damit Ressourcen korrekt geschlossen und beendet werden, und erst danach in der verify-Phase abbgebrochen wird. Integrationstests mit dem Surefire-Plugin Mit dem Maven Surefire Plugin lassen sich die ersten drei Anforderungen nur umständlich und die letzten beiden Anforderungen so gut wie gar nicht umsetzen. Siehe hierzu das obige Beispiel eines Automatisierten Integrationstests. Der wichtigste Abschnitt in der pom.xml lautet dort (gekürzt): <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.20</version> <configuration> <excludes> <exclude>**/integrationtest/*Test.java</exclude> </excludes> </configuration> <executions> <execution> <id>integration-tests</id> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration> <skip>false</skip> <excludes> <exclude>none</exclude> </excludes> <includes> <include>**/integrationtest/*Test.java</include> </includes> </configuration> </execution> </executions> </plugin> Integrationstests mit dem Failsafe-Plugin Mit dem Maven Failsafe Plugin lassen sich alle fünf Anforderungen umsetzen. Dazu muss der maven-surefire-plugin-Block entfernt werden und durch folgenden maven-failsafe-plugin-Block ersetzt werden: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.20</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> Außerdem müssen für die Dateinamen folgende Konventionen beachtet werden (die auch anders konfiguriert werden können): "**/Test*.java", "**/*Test.java", "**/*TestCase.java": für "Unittests", siehe Surefire Plugin: Inclusions of Tests. "**/IT*.java", "**/*IT.java", "**/*ITCase.java": für "Integrationstests", siehe Failsafe Plugin: Inclusions of Tests. Defaultmäßig werden beim "mvn install"-Kommando die Integrationstests immer vorher ausgeführt (integration-test- und verify-Phasen). Bei Verwendung des Failsafe-Plugins können die Integrationstests übergangen werden mit: "mvn install -DskipITs". Anwendung auf den obigen Integrationstest mit Cargo Folgendermaßen können Sie obigen Automatisierten Integrationstest mit Cargo auf das Failsafe-Plugin umstellen: 1. 2. Entfernen Sie aus der pom.xml den kompletten maven-surefire-pluginBlock und 3. fügen Sie stattdessen einen maven-failsafe-plugin-Block ein. 4. Allerdings müssen Sie dabei für dieses spezielle Beispiel die Konfiguration der Portnummer über die System-Environmentvariable übernehmen: 5. 6. 7. <plugin> 8. <groupId>org.apache.maven.plugins</groupId> 9. <artifactId>maven-failsafe-plugin</artifactId> 10. <version>2.20</version> 11. <configuration> 12. <systemPropertyVariables> 13. <port>${port}</port> 14. </systemPropertyVariables> 15. </configuration> 16. <executions> 17. <execution> 18. <goals> 19. <goal>integration-test</goal> 20. <goal>verify</goal> 21. 22. 23. 24. 25. </goals> </execution> </executions> </plugin> 26. 27. Im src\test\java\integrationtest-Verzeichnis müssen zu HtmlUnitTest.java und HttpUnitTest.java 28. sowohl die beiden Dateinamen als auch in beiden Dateien die Klassennamen umbenannt werden in HtmlUnitIT bzw. HttpUnitIT. 29. 30. 31. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F): 32. 33. 34. [\MeinWorkspace\MvnAppSrvFailsaveIntTest] 35. |- [src] 36. | |- [main] 37. | | |- [java] 38. | | | '- [de] 39. | | | '- [meinefirma] 40. | | | '- [meinprojekt] 41. | | | '- MeineBean.java 42. | | '- [webapp] 43. | | |- [WEB-INF] 44. | | | '- web.xml 45. | | |- BerechnungsFormular.jsp 46. | | '- index.jsp 47. | '- [test] 48. | '- [java] 49. | |- [de] 50. | | '- [meinefirma] 51. | | '- [meinprojekt] 52. | | '- MeineBeanTest.java 53. | '- [integrationtest] 54. | |- HtmlUnitIT.java 55. | '- HttpUnitIT.java 56. '- pom.xml 57. 58. 59. Führen Sie folgende Kommandos aus (bitte Profilnamen entsprechend Ihres App-Servers wählen): 60. Es werden nur die auf "Test" endenden "schnellen Unittests" ausgeführt. Es werden die Unittests und die beiden auf "IT" mvn verify -P WebLogic endenden Integrationstests ausgeführt. Vor der Installation des Ergebnisartefakts im lokalen mvn install -P WebLogic Repository werden die Unittests und die Integrationstests ausgeführt. Vor der Installation des Ergebnisartefakts im lokalen Repository werden nur die Unittests ausgeführt, die Integrationstests werden ausgelassen. Es erscheint die mvn install -P WebLogic -DskipITs Meldung: maven-failsafe-plugin: Tests are skipped. mvn test 61. 62. 63. Ändern Sie src\test\java\integrationtest\HttpUnitIT.java so ab, dass der Test fehlschlägt. 64. Führen Sie wieder "mvn verify -P WebLogic" aus: 65. Anders als mit dem Surefire-Plugin wird trotz des fehlschlagenden Tests zuerst der WebLogic ordentlich heruntergefahren, 66. bevor das Maven-Kommando in der verify-Phase den Fehler meldet und abbricht. 67. 68. 69. Grundsätzlich sollten Sie immer das gewählte Character-Encoding in der pom.xml definieren 70. (so vermeiden Sie auch die Warnungen des Failsafe-Plugins wegen fehlender Angaben zum Encoding), 71. zum Beispiel so (passen Sie das Encoding an): 72. 73. 74. <properties> 75. <project.build.sourceEncoding>ISO-88591</project.build.sourceEncoding> 76. <project.reporting.outputEncoding>UTF8</project.reporting.outputEncoding> 77. </properties> 78. Webapp mit Wicket Mit Apache Wicket können komponentenbasierte Webanwendungen erstellt werden, inklusive AJAX-Unterstützung. Der Entwickler hat dabei nur mit Java und HTML zu tun und benötigt kein JSP, JSF und JavaScript. Weiteres zu Wicket finden Sie unter http://wicket.apache.org und http://wicket.sourceforge.net/apidocs. Das Maven-Archetype-Plugin bietet Unterstützung für Apache Wicket. Das folgende Beispiel implementiert wieder das bereits bekannte Webformular, welches wieder über eine MeineBean.berechne()-Methode zwei Zahlen addiert (stellvertretend für Businesslogik). 1. 2. 3. Generieren Sie ein Wicket-Projekt: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicketarchetype-quickstart -DgroupId=de.meinefirma.meinprojekt DartifactId=MvnWicket cd MvnWicket tree /F [\MeinWorkspace\MvnWicket] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | |- HomePage.html | | | |- HomePage.java | | | '- WicketApplication.java | | |- [resources] | | | '- log4j2.xml | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | '- logo.png | | '- style.css | '- [test] | |- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | |- Start.java | | '- TestHomePage.java | |- [jetty] | | |- jetty-http.xml | | |- jetty-https.xml | | |- jetty-ssl.xml | | '- jetty.xml | '- [resources] | '- keystore '- pom.xml 4. 5. 6. Testen Sie die vorläufige Quickstart-Webanwendung: mvn jetty:run start http://localhost:8080 --> ... Congratulations! Your quickstart works! ... Beenden Sie Jetty mit "Strg + C". 7. 8. Fügen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis in der HomePage.html 9. an geeigneter Stelle (z.B. hinter der "<h2>Congratulations!</h2>"Zeile) folgende Zeile hinzu: 10. 11. 12. <p><wicket:link><a href="BerechnungsFormular.html">Mein Berechnungsformular</a></wicket:link></p> 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende BerechnungsFormular.html: <html> <head><title>Webseite mit Berechnungsformular</title></head> <body> <h2>Webseite mit Berechnungsformular</h2> <form wicket:id="meinFormular" method="post"><pre> Wert1 <input wicket:id="wert1" type="text" size=10 maxlength=10><br> 24. Wert2 <input wicket:id="wert2" type="text" size=10 maxlength=10><br> 25. <input wicket:id="berechne" type="submit" value="berechne"> <input wicket:id="ergebnis" type="text" size=10 readonly><br> 26. </pre></form> 27. <span wicket:id="FehlerFeedback"/> 28. </body> 29. </html> 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende BerechnungsFormular.java: package de.meinefirma.meinprojekt; import import import import import java.io.Serializable; org.apache.wicket.markup.html.WebPage; org.apache.wicket.markup.html.form.*; org.apache.wicket.markup.html.panel.FeedbackPanel; org.apache.wicket.model.CompoundPropertyModel; public class BerechnungsFormular extends WebPage { public BerechnungsFormular() { Form<MeinDatenModel> form = new Form<MeinDatenModel>( "meinFormular" ) { 48. private static final long serialVersionUID = 1L; 49. @Override protected void onSubmit() { 50. MeinDatenModel datenModel = getModelObject(); 51. datenModel.ergebnis = (new MeineBean()).berechne( datenModel.wert1, datenModel.wert2 ); 52. } 53. }; 54. form.setModel( new CompoundPropertyModel<MeinDatenModel>( new MeinDatenModel() ) ); 55. form.add( new RequiredTextField<Double>( "wert1" ) ); 56. form.add( new RequiredTextField<Double>( "wert2" ) ); 57. form.add( new Button( "berechne" ) ); 58. form.add( new TextField<Double>( "ergebnis" ) ); 59. add( form ); 60. add( new FeedbackPanel( "FehlerFeedback" ) ); 61. } 62. 63. class MeinDatenModel implements Serializable 64. { 65. private static final long serialVersionUID = 1L; 66. public Double wert1, wert2, ergebnis; 67. } 68. } 69. 70. 71. 72. 73. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis MeineBean.java: 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. package de.meinefirma.meinprojekt; public class MeineBean { public Double berechne( Number d1, Number d2 ) { return ( d1 == null || d2 == null ) ? null : new Double( d1.doubleValue() + d2.doubleValue() ); } } Testen Sie die Wicket-Webanwendung: mvn clean jetty:run start http://localhost:8080 Klicken Sie auf Mein Berechnungsformular, tragen Sie zwei Zahlen ein und lassen Sie die Summe berechnen. Sie können auch Dezimalzahlen eingeben: Dabei müssen Sie als Dezimalpunkt bei deutscher Webbrowser-Einstellung ein Komma (und keinen Punkt) verwenden. Lassen Sie ein Feld leer, tragen Sie in das andere Feld Buchstaben ein und sehen Sie sich die Fehlermeldungen an. Beenden Sie Jetty mit "Strg + C". Vert.x-HelloWorld Vert.x ist ein reaktives, modulares, nachrichtenorientiertes, polyglottes Framework, mit dem sich klassische Anwendungen, Webanwendungen und Microservices realisieren lassen. Infos zu Vert.x finden Sie in der Vert.x Documentation und im Vert.x Tutorial von Jakob Jenkov. Das folgende Beispiel zeigt: eine minimale Vert.x-Hello-World-Webanwendung inklusive Webserver, und wie mit dem maven-shade-plugin eine direkt ausführbare "Fat-Jar" inklusive aller Abhängigkeiten erstellt werden kann. Führen Sie folgende Schritte aus: 1. 2. 3. Legen Sie ein Projektverzeichnis MvnVertxHelloWorld an und erzeugen Sie darin folgende Verzeichnisstruktur: cd \MeinWorkspace md MvnVertxHelloWorld cd MvnVertxHelloWorld md src\main\java\de\meinefirma\meinprojekt 4. 5. Erzeugen Sie im MvnVertxHelloWorld-Projektverzeichnis folgende Projektkonfigurationsdatei: pom.xml 6. 7. 8. <?xml version="1.0" encoding="UTF-8"?> 9. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 10. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 11. <modelVersion>4.0.0</modelVersion> 12. <groupId>de.meinefirma.meinprojekt</groupId> 13. <artifactId>MvnVertxHelloWorld</artifactId> 14. <version>1.0-SNAPSHOT</version> 15. <properties> 16. <exec.mainClass>de.meinefirma.meinprojekt.HelloWorldVertx</exec.mainCla ss> 17. </properties> 18. <dependencies> 19. <dependency> 20. <groupId>io.vertx</groupId> 21. <artifactId>vertx-core</artifactId> 22. <version>3.4.1</version> 23. </dependency> 24. </dependencies> 25. <build> 26. <pluginManagement> 27. <plugins> 28. <plugin> 29. <artifactId>maven-compiler-plugin</artifactId> 30. <version>3.6.1</version> 31. <configuration> 32. <source>1.8</source> 33. <target>1.8</target> 34. </configuration> 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourc eTransformer"> 52. <manifestEntries> 53. <Main-Class>${exec.mainClass}</Main-Class> 54. </manifestEntries> 55. </transformer> 56. <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransf ormer"> 57. <resource>METAINF/services/io.vertx.core.spi.VerticleFactory</resource> 58. </transformer> 59. </transformers> 60. <artifactSet> 61. </artifactSet> 62. <outputFile>${project.build.directory}/${project.artifactId}${project.version}-fat.jar</outputFile> 63. </configuration> 64. </execution> 65. </executions> 66. </plugin> 67. </plugins> 68. </build> 69. </project> 70. 71. 72. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende Vert.x-Klasse: HelloWorldVertx.java 73. 74. 75. package de.meinefirma.meinprojekt; 76. 77. import io.vertx.core.Vertx; 78. 79. public class HelloWorldVertx 80. { 81. public static void main( String[] args ) 82. { 83. Vertx.vertx().createHttpServer().requestHandler( req -> req.response().end( "Hello World." ) ).listen( 8080 ); 84. } 85. } 86. 87. 88. 89. Ihre Projektverzeichnisstruktur sieht jetzt so aus: tree /F [\MeinWorkspace\MvnVertxHelloWorld] |- [src] | '- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- HelloWorldVertx.java '- pom.xml 90. 91. 92. Rufen Sie im Kommandozeilenfenster auf:: cd \MeinWorkspace\MvnVertxHelloWorld mvn clean package java -jar target/MvnVertxHelloWorld-1.0-SNAPSHOT-fat.jar start http://localhost:8080 Es wird ein Webserver mit der Vert.x-Webanwendung gestartet und Sie erhalten im Webbrowser: Hello World. 93. 94. 95. 96. In realen Vert.x-Anwendungen werden statt der hier gezeigten einfachen Variante die Funktionalitäten normalerweise in 97. Verticles 98. aufgeteilt. 99. OSGi-Bundle mit dem Maven-Bundle-Plugin OSGi ist ein leistungsfähiges dynamisches Komponentensystem für Java. Im Folgenden wird anhand eines kurzen Beispiels gezeigt, wie mit dem Maven-Bundle-Plugin des Apache-Felix-Projekts ein OSGi-Bundle erstellt werden kann. 1. 2. Falls Sie noch kein OSGi installiert haben: 3. Sie können wahlweise eine komplette OSGi-Umgebung installieren, zum Beispiel wie für 4. Eclipse Equinox 5. beschrieben. 6. Für dieses einfache Beispiel genügt es, wenn Sie alternativ lediglich eine einzelne OSGi-jar-Bibliothek downloaden 7. und in Ihr Projektverzeichnis kopieren, nämlich die 8. org.eclipse.osgi_3.9.1.v20140110-1610.jar von 9. http://archive.eclipse.org/equinox/drops/R-KeplerSR2-201402211700. 10. Achtung: Bei neueren OSGi-Framework-Versionen lässt sich leider nicht mehr ganz so einfach wie im folgenden Beispiel 11. die Konsole starten, siehe hierzu: 12. "Incompatibilities" / "5. Built-in Equinox OSGi console removed". 13. 14. 15. Legen Sie ein Projektverzeichnis MvnOsgi an und erzeugen Sie darin folgende Verzeichnisstruktur: 16. cd \MeinWorkspace md MvnOsgi\configuration md MvnOsgi\src\main\java\de\meinefirma\meinprojekt cd MvnOsgi tree /F [\MeinWorkspace\MvnOsgi] |- [configuration] |- [src] | '- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] '- org.eclipse.osgi_3.9.1.v20140110-1610.jar OSGi-Installation] 17. 18. [falls keine andere Erzeugen Sie im MvnOsgi-Projektverzeichnis folgende Projektkonfigurationsdatei: pom.xml 19. 20. 21. <project> 22. <modelVersion>4.0.0</modelVersion> 23. <groupId>de.meinefirma.meinprojekt</groupId> 24. <artifactId>MvnOsgi</artifactId> 25. <version>1.0-SNAPSHOT</version> 26. <packaging>bundle</packaging> 27. <name>MvnOsgi</name> 28. <build> 29. <plugins> 30. <plugin> 31. <groupId>org.apache.felix</groupId> 32. <artifactId>maven-bundle-plugin</artifactId> 33. <version>3.0.1</version> 34. <extensions>true</extensions> 35. <configuration> 36. <instructions> 37. <!-- Falls Sie etwas zu exportieren haben: 38. <Export-Package>de.meinefirma.meinprojekt.intf...</ExportPackage> --> 39. <Private-Package>de.meinefirma.meinprojekt.*</PrivatePackage> 40. <BundleActivator>de.meinefirma.meinprojekt.Activator</Bundle-Activator> 41. </instructions> 42. </configuration> 43. </plugin> 44. </plugins> 45. </build> 46. <dependencies> 47. <dependency> 48. <groupId>org.apache.felix</groupId> 49. <artifactId>org.osgi.core</artifactId> 50. <version>1.4.0</version> 51. </dependency> 52. </dependencies> 53. </project> 54. 55. 56. Erzeugen Sie im configuration-Verzeichnis folgende OSGiKonfigurationsdatei: config.ini 57. 58. 59. osgi.console.enable.builtin=true 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende Bundle-Java-Klasse: Activator.java package de.meinefirma.meinprojekt; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; public class Activator implements BundleActivator { @Override public void start( BundleContext context ) throws Exception { System.out.println( context.getBundle().getSymbolicName() + Hallo OSGi-Welt." ); 76. } 77. 78. @Override 79. public void stop( BundleContext context ) throws Exception 80. { 81. System.out.println( context.getBundle().getSymbolicName() + Tschau OSGi-Welt." ); 82. } 83. } 84. 85. 86. 87. Ihre Projektverzeichnisstruktur sieht jetzt so aus: tree /F [\MeinWorkspace\MvnOsgi] |- [configuration] | '- config.ini |- [src] | '- [main] | '- [java] ": ": | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- Activator.java |- org.eclipse.osgi_3.9.1.v20140110-1610.jar OSGi-Installation] '- pom.xml 88. 89. [falls keine andere Geben Sie im Kommandozeilenfenster (und in der OSGi-Konsole) Folgendes ein (passen Sie den OSGi-Dateinamen an): 90. cd \MeinWorkspace\MvnOsgi mvn package java -jar org.eclipse.osgi_3.9.1.v20140110-1610.jar -console -clean install file:target\MvnOsgi-1.0-SNAPSHOT.jar start ss uninstall 1 close Sie erhalten: osgi> install file:target\MvnOsgi-1.0-SNAPSHOT.jar start Bundle id is 1 de.meinefirma.meinprojekt.MvnOsgi: Hallo OSGi-Welt. osgi> ss id State Bundle 0 ACTIVE org.eclipse.osgi_3.9.1.v20140110-1610 1 ACTIVE de.meinefirma.meinprojekt.MvnOsgi_1.0.0.SNAPSHOT osgi> uninstall 1 de.meinefirma.meinprojekt.MvnOsgi: Tschau OSGi-Welt. osgi> close 91. 92. Falls Sie nicht die org.eclipse.osgi_3.9.1.v20140110-1610.jar downgeloaded haben, sondern eine andere OSGi-Installation verwenden wollen, 93. müssen Sie das java...-Kommando entsprechend ändern, 94. zum Beispiel für eine Installation entsprechend der 95. Beschreibung zu Eclipse Equinox folgendermaßen 96. (passen Sie den Pfad und den OSGi-Dateinamen an): 97. java -jar C:\Tools\equinoxSDK\plugins\org.eclipse.osgi_3.9.1.v20140110-1610.jar -console -clean 98. 99. Bei Problemen sehen Sie sich die .log-Datei im configurationUnterverzeichnis an. 100. 101. 102. Erläuterungen finden Sie beim nahezu identischen 103. OSGi-HelloWorld-Programmierbeispiel. 104. Dort sind auch weitere ausführbare Kommandos und weitere OSGiProgrammierbeispiele aufgeführt. 105. Suche mit Lucene Das Java-basierende Lucene zählt zu den bekanntesten Text-Suchmaschinen. Lucene ist Open Source, sehr leistungsfähig und enthält viele Features. Das folgende "Lucene-HelloWorld-Programmierbeispiel" zeigt eine einfache Textsuche über Dateien in einem Verzeichnis inklusive der Unterverzeichnisse. 1. 2. Legen Sie ein Projektverzeichnis MvnLucene an und erzeugen Sie darin folgende Verzeichnisstruktur: 3. cd \MeinWorkspace md MvnLucene\src\main\java\de\meinefirma\meinprojekt md MvnLucene\src\test\java\de\meinefirma\meinprojekt cd MvnLucene tree /F [\MeinWorkspace\MvnLucene] '- [src] |- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] '- [test] '- [java] '- [de] '- [meinefirma] '- [meinprojekt] 4. 5. Erzeugen Sie im MvnLucene-Projektverzeichnis folgende Projektkonfigurationsdatei: pom.xml 6. 7. 8. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 9. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 10. <modelVersion>4.0.0</modelVersion> 11. <groupId>de.meinefirma.meinprojekt</groupId> 12. <artifactId>MvnLucene</artifactId> 13. <version>1.0-SNAPSHOT</version> 14. <packaging>jar</packaging> 15. <name>MvnLucene</name> 16. <build> 17. <plugins> 18. <plugin> 19. <artifactId>maven-assembly-plugin</artifactId> 20. <version>3.0.0</version> 21. <configuration> 22. <descriptorRefs> 23. <descriptorRef>jar-with-dependencies</descriptorRef> 24. </descriptorRefs> 25. <archive> 26. <manifest> 27. <mainClass>de.meinefirma.meinprojekt.LuceneSimpleUtil</mainClass> 28. </manifest> 29. </archive> 30. </configuration> 31. <executions> 32. <execution> 33. <id>make-assembly</id> 34. <phase>package</phase> 35. <goals> 36. <goal>single</goal> 37. </goals> 38. </execution> 39. </executions> 40. </plugin> 41. </plugins> 42. </build> 43. <dependencies> 44. <dependency> 45. <groupId>org.apache.lucene</groupId> 46. <artifactId>lucene-core</artifactId> 47. <version>4.10.4</version> 48. </dependency> 49. <dependency> 50. <groupId>org.apache.lucene</groupId> 51. <artifactId>lucene-queryparser</artifactId> 52. <version>4.10.4</version> 53. </dependency> 54. <dependency> 55. <groupId>org.apache.lucene</groupId> 56. <artifactId>lucene-analyzers-common</artifactId> 57. <version>4.10.4</version> 58. </dependency> 59. <dependency> 60. <groupId>junit</groupId> 61. <artifactId>junit</artifactId> 62. <version>4.12</version> 63. <scope>test</scope> 64. </dependency> 65. </dependencies> 66. </project> 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende Java-Klasse: LuceneSimpleUtil.java package de.meinefirma.meinprojekt; import import import import import import import import import import import import java.io.*; java.nio.charset.Charset; java.util.*; org.apache.lucene.analysis.Analyzer; org.apache.lucene.analysis.standard.StandardAnalyzer; org.apache.lucene.document.*; org.apache.lucene.index.*; org.apache.lucene.index.IndexWriterConfig.OpenMode; org.apache.lucene.queryparser.classic.QueryParser; org.apache.lucene.search.*; org.apache.lucene.store.*; org.apache.lucene.util.Version; public class LuceneSimpleUtil { public static void main( String[] args ) throws Exception { if( args.length != 2 && args.length != 3 ) { System.out.println( "\nIndizieren mit 3 Parametern:\n" + " PfadZuDokumenten CharacterEncoding PfadZumIndex\n" + "Suchen mit 2 Parametern:\n" + " PfadZumIndex SuchWort\n" ); } 98. 99. if( args.length == 3 ) { System.out.println( "\nIndexerstellung im Verzeichnis: " + args[2] ); 100. System.out.println( "Zu indizierende Dateien im Verzeichnis: " + args[0] ); 101. System.out.println( "Character-Encoding: " + args[1] ); 102. System.out.println( "Indizierte Dateien:" ); 103. System.out.println( LuceneSimpleUtil.indexFiles( args[0], args[1], args[2], true ) ); 104. } 105. if( args.length == 2 ) { 106. System.out.println( "\nIndex im Verzeichnis: " + args[0] ); 107. System.out.println( "Suchwort '" + args[1] + "' gefunden in:" ); 108. List<Document> docs = LuceneSimpleUtil.searchFiles( args[0], args[1], 100 ); 109. for( int i = 0; i < docs.size(); i++ ) { 110. System.out.println( docs.get( i ).get( "path" ) ); 111. } 112. } 113. } 114. 115. public static List<Document> searchFiles( String indexPath, String queryString, int maxResults ) throws Exception 116. { 117. if( queryString == null || queryString.trim().length() == 0 ) { return null; } 118. List<Document> docs = new ArrayList<Document>(); 119. IndexReader reader = DirectoryReader.open( FSDirectory.open( new File( indexPath ) ) ); 120. IndexSearcher searcher = new IndexSearcher( reader ); 121. Analyzer analyzer = new StandardAnalyzer( Version.LUCENE_4_9 ); 122. QueryParser parser = new QueryParser( Version.LUCENE_4_9, "contents", analyzer ); 123. Query query = parser.parse( queryString.trim() ); 124. TopDocs results = searcher.search( query, maxResults ); 125. ScoreDoc[] hits = results.scoreDocs; 126. for( ScoreDoc scoreDoc : hits ) { 127. docs.add( searcher.doc( scoreDoc.doc ) ); 128. } 129. reader.close(); 130. analyzer.close(); 131. return docs; 132. } 133. 134. public static List<File> indexFiles( String docsPath, String charEncoding, String indexPath, boolean createNew ) throws IOException 135. { 136. if( docsPath == null ) { return null; } 137. final File docDir = new File( docsPath ); 138. if( !docDir.exists() || !docDir.canRead() ) { return null; } 139. Directory dir = FSDirectory.open( new File( indexPath ) ); 140. Analyzer analyzer = new StandardAnalyzer( Version.LUCENE_4_9 ); 141. IndexWriterConfig iwc = new IndexWriterConfig( Version.LUCENE_4_9, analyzer ); 142. iwc.setOpenMode( ( createNew ) ? OpenMode.CREATE : OpenMode.CREATE_OR_APPEND ); 143. IndexWriter writer = new IndexWriter( dir, iwc ); 144. List<File> filesIndexed = indexDocs( writer, docDir, Charset.forName( charEncoding ) ); 145. writer.close(); 146. analyzer.close(); 147. dir.close(); 148. return filesIndexed; 149. } 150. 151. private static List<File> indexDocs( IndexWriter writer, File file, Charset charEnc ) throws IOException 152. { 153. List<File> filesIndexed = new ArrayList<File>(); 154. if( file.canRead() ) { 155. if( file.isDirectory() ) { 156. String[] files = file.list(); 157. if( files != null ) { 158. for( int i = 0; i < files.length; i++ ) { 159. filesIndexed.addAll( indexDocs( writer, new File( file, files[i] ), charEnc ) ); 160. } 161. } 162. } else { 163. BufferedReader fileReader; 164. try { 165. fileReader = new BufferedReader( new InputStreamReader( new FileInputStream( file ), charEnc ) ); 166. } catch( FileNotFoundException fnfe ) { 167. return filesIndexed; 168. } 169. try { 170. Document doc = new Document(); 171. Field pathField = new StringField( "path", file.getPath(), Field.Store.YES ); 172. doc.add( pathField ); 173. doc.add( new LongField( "modified", file.lastModified(), Field.Store.NO ) ); 174. doc.add( new TextField( "contents", fileReader ) ); 175. if( writer.getConfig().getOpenMode() == OpenMode.CREATE ) { 176. writer.addDocument( doc ); 177. } else { 178. writer.updateDocument( new Term( "path", file.getPath() ), doc ); 179. } 180. filesIndexed.add( file ); 181. } finally { 182. fileReader.close(); 183. } 184. } 185. } 186. return filesIndexed; 187. } 188. } 189. 190. Sehen Sie sich hierzu die Lucene-API-Doku an: Lucene core API. 191. 192. Erzeugen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis folgende Modultest-Klasse: LuceneSimpleUtilTest.java 193. 194. 195. package de.meinefirma.meinprojekt; 196. 197. import static org.junit.Assert.*; 198. import java.io.File; 199. import java.nio.charset.StandardCharsets; 200. import java.util.List; 201. import org.apache.lucene.document.Document; 202. import org.junit.Test; 203. 204. public class LuceneSimpleUtilTest 205. { 206. @Test 207. public void testLuceneSearch() throws Exception 208. { 209. String docsPath = "src"; 210. String indexPath = "target/LuceneIndex"; 211. String charEnc = StandardCharsets.UTF_8.toString(); 212. String suchWort = "Lucene"; 213. 214. System.out.println( "Indexerstellung im Verzeichnis: " + indexPath ); 215. System.out.println( "Zu indizierende Dateien im Verzeichnis: " + docsPath ); 216. System.out.println( "Character-Encoding: " + charEnc ); 217. System.out.println( "Indizierte Dateien:" ); 218. List<File> filesIndexed = LuceneSimpleUtil.indexFiles( docsPath, charEnc, indexPath, true ); 219. System.out.println( filesIndexed ); 220. assertNotNull( filesIndexed ); 221. assertEquals( 2, filesIndexed.size() ); 222. 223. System.out.println( "Suchwort '" + suchWort + "' gefunden in:" ); 224. List<Document> docs = LuceneSimpleUtil.searchFiles( indexPath, suchWort, 100 ); 225. for( Document doc : docs ) { 226. System.out.println( doc.get( "path" ) ); 227. } 228. assertEquals( 1, docs.size() ); 229. } 230. } 231. 232. 233. Ihre Projektverzeichnisstruktur sieht jetzt so aus: 234. tree /F [\MeinWorkspace\MvnLucene] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | '- LuceneSimpleUtil.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- LuceneSimpleUtilTest.java '- pom.xml 235. 236. In dem JUnit-Test LuceneSimpleUtilTest wird gezeigt, wie Sie Lucene programmatisch ansprechen können. 237. Geben Sie im Kommandozeilenfenster Folgendes ein: 238. cd \MeinWorkspace\MvnLucene mvn test Sie erhalten: Running de.meinefirma.meinprojekt.LuceneSimpleUtilTest Indexerstellung im Verzeichnis: target/LuceneIndex Zu indizierende Dateien im Verzeichnis: src Character-Encoding: UTF-8 Indizierte Dateien: [src\main\java\de\meinefirma\meinprojekt\LuceneSimpleUtil.java, src\test\java\de\meinefirma\meinprojekt\LuceneSimpleUtilTest.java] Suchwort 'Lucene' gefunden in: src\test\java\de\meinefirma\meinprojekt\LuceneSimpleUtilTest.java 239. 240. Sie können LuceneSimpleUtil auch per Kommandozeilenfenster aufrufen. 241. Im folgenden Beispiel wird wieder in allen Dateien in allen Verzeichnissen unter src nach Lucene gesucht: 242. cd \MeinWorkspace\MvnLucene mvn package java -jar target\MvnLucene-1.0-SNAPSHOT-jar-with-dependencies.jar --> Indizieren mit 3 Parametern: PfadZuDokumenten CharacterEncoding PfadZumIndex Suchen mit 2 Parametern: PfadZumIndex SuchWort Indizierung durch Angabe von "PfadZuDokumenten CharacterEncoding PfadZumIndex": java -jar target\MvnLucene-1.0-SNAPSHOT-jar-with-dependencies.jar src ISO-8859-1 LuceneIndex --> Indexerstellung im Verzeichnis: LuceneIndex Zu indizierende Dateien im Verzeichnis: src Character-Encoding: ISO-8859-1 Indizierte Dateien: [src\main\java\de\meinefirma\meinprojekt\LuceneSimpleUtil.java, src\test\java\de\meinefirma\meinprojekt\LuceneSimpleUtilTest.java] Suchen durch Angabe von "PfadZumIndex SuchWort": java -jar target\MvnLucene-1.0-SNAPSHOT-jar-with-dependencies.jar LuceneIndex Lucene --> Index im Verzeichnis: LuceneIndex Suchwort 'Lucene' gefunden in: src\test\java\de\meinefirma\meinprojekt\LuceneSimpleUtilTest.java 243. 244. Indizieren Sie statt in src alle Dateien in einem anderen Verzeichnis und testen Sie andere Suchwörter. 245. Java-EE-Anwendungen (Servlet, JSP, JSF, JPA, EJB3) Maven-Projekte für die Java Enterprise Edition finden Sie zum Beispiel unter: Webanwendung mit Servlet, JSP, JavaBean und DB-Zugriff für GlassFish, WebLogic, JBoss und Geronimo Webanwendung mit JSF und JPA für GlassFish Java EE mit EJB3 für GlassFish, WebLogic, JBoss und Geronimo Maven mit Docker Beispiele zur Verwendung von Docker mit Maven finden Sie unter: Docker-Image mit Maven erzeugen mit dem spotify docker-maven-plugin Docker mit Maven steuern mit dem rhuss docker-maven-plugin Maven-Repository Zugang zu Internet-Repositories über Firmen-Proxy In Firmennetzwerken ist häufig der direkte Internetzugang für Tools wie Maven gesperrt. Um dennoch benötigte Libs aus den Maven-Repositories im Internet verwenden zu können, kann der Cntlm Authentication Proxy verwendet werden (wenn die IT-Abteilung das erlaubt). Er steht für verschiedene Betriebssysteme zur Verfügung. Hier folgt eine Kurzanleitung für Windows-Workstations, die übers Firmennetzwerk nur über eine NTLM-Authentifizierung ins Internet gelangen. Installieren Sie Cntlm wie beschrieben unter Eclipse-Updates über Firmen-Firewalls mit dem Cntlm Authentication Proxy (die Eclipse-spezifischen Einstellungen können Sie weglassen). Erweitern Sie Ihre lokale %M2_HOME%\conf\settings.xml zum Beispiel so (passen Sie den Pfad an und setzen Sie als Portnummer dieselbe wie bei Cntlm ein): <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <localRepository>D:\Tools\Maven3-Repo</localRepository> <proxies> <proxy> <active>true</active> <protocol>http</protocol> <host>127.0.0.1</host> <port>3128</port> </proxy> </proxies> </settings> Team-Repository über freigegebenes Verzeichnis Wenn nur ein festes Repertoire an Libs mehreren Entwicklern zur Verfügung gestellt werden soll, können Sie ein Maven-Repository als einfaches Verzeichnis einstellen. Allerdings werden so keine fehlenden Libs übers Internet nachgeladen. Erweitern Sie Ihre lokale settings.xml zum Beispiel so: <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <localRepository>D:\Tools\Maven3-Repo</localRepository> <mirrors> <mirror> <id>MeineMirrorId</id> <mirrorOf>central</mirrorOf> <url>file://\\<MeinPC>\<MeinFreigabeName>\m2-Central-Repository</url> </mirror> </mirrors> </settings> Repository-Manager Falls ein Repository-Manager (im Beispiel: Archiva) installiert ist, können Sie ihn über folgenden Eintrag in der settings.xml einstellen: <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <localRepository>D:\Tools\Maven3-Repo</localRepository> <mirrors> <mirror> <id>archiva</id> <mirrorOf>*</mirrorOf> <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/internal</url> </mirror> </mirrors> </settings> Installation und Konfiguration des Repository-Managers Archiva Archiva ist einer der bekanntesten Repository-Manager. Installieren Sie ihn wie beschrieben unter System Administrators Guide to Apache Archiva. Einige eventuell benötigte Schritte sind auch im Folgenden aufgeführt. 1. Downloaden Sie apache-archiva-1.3.4-bin.zip von 2. http://archiva.apache.org/download.cgi 3. und entzippen Sie die Datei, zum Beispiel nach 4. \Tools\Archiva. 5. Suchen Sie in 6. \Tools\Archiva\conf\jetty.xml 7. nach "jetty.port" und setzen Sie bei default="8080" eine andere Portnummer ein, beispielsweise "4422". 8. 9. (Freie Portnummern finden Sie unter http://www.iana.org/assignments/port-numbers.) 10. 11. 12. 13. Falls Sie unter 64-bit-Windows installieren: Der Archiva-Wrapper funktioniert zurzeit nur mit einem 32-bit-JDK. Dieses muss installiert (z.B. nach C:\Program Files (x86)\Java\jdk1.7-32bit) und bei Archiva eingetragen werden. 14. Hierzu in der 15. \Tools\Archiva\conf\wrapper.conf 16. die Zeile 17. wrapper.java.command=java ändern zu: wrapper.java.command=C:\Program Files (x86)\Java\jdk1.7-32bit\bin\java Außerdem können einige Archiva-Kommandos (z.B. "bin\archiva.bat install") nur in einem Kommandozeilenfenster mit Administratorrechten ausgeführt werden: Start | Alle Programme | Zubehör | "Eingabeaufforderung" mit rechter Maustaste und "Als Administrator ausführen". 18. 19. Starten Sie Archiva über: \Tools\Archiva\bin\archiva.bat console Wenn "Started [email protected]:4422" erscheint, starten Sie die Archiva-Weboberfläche: start http://localhost:4422/archiva 20. Legen Sie einen Admin-Account an. 21. Falls Sie in einem Firmennetzwerk hinter einem Proxy sitzen und den 22. Cntlm Authentication Proxy installiert haben, legen Sie in Archiva einen Network Proxy an. 23. Klicken Sie in der linken Spalte unter "Administration" auf 24. "Network Proxies" 25. und dann auf "Add Network Proxy" und tragen Sie ein: 26. Identifier: MeineNetworkProxyID Protocol: Hostname: Port: Username: Password: 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. http 127.0.0.1 3128 Save Network Proxy. In der \Tools\Archiva\conf\archiva.xml entsteht der Eintrag: <networkProxies> <networkProxy> <id>MeineNetworkProxyID</id> <host>127.0.0.1</host> <port>3128</port> <username/> <password/> </networkProxy> </networkProxies> 44. Tragen Sie den Network Proxy in alle Proxy Connectors ein. 45. Klicken Sie in der linken Spalte auf 46. "Proxy Connectors" 47. und tragen Sie dann bei allen Proxy Connectoren über "Edit Proxy Connector" ein: 48. Network Proxy: MeineNetworkProxyID 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. Save Proxy Connector. In der \Tools\Archiva\conf\archiva.xml entstehen die Einträge: <proxyConnector> ... <proxyId>MeineNetworkProxyID</proxyId> ... </proxyConnector> 61. Fügen Sie folgendes Repository hinzu. 62. 63. 64. Klicken Sie in der linken Spalte auf "Repositories" und dann rechts unter "Remote Repositories" auf "Add" und tragen Sie ein: 65. Identifier: maven1-repository.dev.java.net Name: URL: Type: 66. 67. Java.net Repository for Maven 1 http://download.java.net/maven/1/ Maven 1.x Repository Add Repository. 68. Fügen Sie einen Proxy Connector für das "Java.net Repository for Maven 1" hinzu: 69. Klicken Sie in der linken Spalte auf 70. "Proxy Connectors" 71. und dann auf "Add" und tragen Sie ein: 72. MeineNetworkProxyID Managed Repository: internal Remote Repository: maven1-repository.dev.java.net yes Cache failures: once Releases: fix Checksum: never Snapshots: Network Proxy: 73. 74. Add Proxy Connector. 75. Erweitern Sie auf dem Entwickler-PC die lokale 76. %M2_HOME%\conf\settings.xml 77. wie oben beschrieben und testen Sie, 78. ob Sie mit dem Archiva Repository ihre Maven-Projekte bauen können. 79. Stoppen Sie Archiva mit "Strg+C". 80. Installieren Sie Archiva als Hintergrund-Dienst: 81. \Tools\Archiva\bin\archiva.bat install Wenn Sie jetzt Windows neu starten, läuft Archiva automatisch als Dienst. Steuern können Sie dies unter: "Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste" (oder alternativ: rechter Mausklick auf "Computer" bzw. "Arbeitsplatz" | "Verwalten" | "Dienste und Anwendungen" | "Dienste"). Für kleine Teams in Firmen kann praktisch sein, dass der so installierte Archiva-Dienst bereits funktionsfähig und von anderen PCs aus erreichbar läuft, ohne dass sich jemand auf dem Archiva-Server im Firmennetzwerk anmelden muss. 82. 83. Falls Sie bei korrekt installiertem Archiva-Dienst nur den ArchivaDienst stoppen oder starten wollen, können Sie das net-Kommando verwenden: 84. net stop archiva net start archiva Distribution-Deployment mit dem Repository-Manager Archiva Mit mvn install kopieren Sie Ihre Ergebnisartefakte in Ihr lokales Maven-Repository. Falls Sie im Team arbeiten und Ihre Kollegen Ihre Ergebnisartefakte verwenden können sollen, müssen Sie diese mit mvn deploy in ein gemeinsam genutzes Remote Repository kopieren. Dies kann ein projektspezifisches, teamspezifisches, firmenspezifisches oder ein übers Internet erreichbares sein. Im Folgenden werden die dafür benötigten Einstellungen am Beispiel des Archiva-Repository-Managers aufgeführt. 1. 2. 3. 4. 5. Archiva einrichten wie oben beschrieben. Der Archiva-Administrator legt in Archiva die Benutzer für das Distribution-Deployment an (im Folgenden "<DeployerName>" genannt): 6. 7. 1. http://<ArchivaServer>:<ArchivaPort>/archiva 2. (z.B. http://localhost:4422/archiva) starten. 8. 1. Als admin anmelden und "User Management" wählen. 9. 1. "Neuen Benutzer anlegen" klicken und Felder ausfüllen. 10. 1. Unter "Resource Roles": Alle vier Checkboxen für "Repository Manager" und "Repository Observer" sowie "internal" und "snapshots" aktivieren. 11. 12. 13. 14. Der Entwickler/Deployer ändert das Kennwort und kontrolliert den Archiva-Account: 15. 16. 1. http://<ArchivaServer>:<ArchivaPort>/archiva öffnen. 17. 1. Falls bereits angemeldet (z.B. als admin): abmelden. 18. 1. Als <DeployerName> anmelden. 19. 1. Kennwort ändern. 20. 21. 22. 23. 24. 25. Auf dem Deployer-PC die %M2_HOME%\conf\settings.xml erweitern (dabei korrekte Archiva-URL und korrekten DeployerBenutzer eintragen): 26. 27. 28. <?xml version="1.0" encoding="UTF-8"?> 29. <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 30. xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> 31. 32. <localRepository>D:\Tools\Maven3-Repo</localRepository> 33. 34. <mirrors> 35. <mirror> 36. <id>archiva</id> 37. <mirrorOf>*</mirrorOf> 38. <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/internal</ url> 39. </mirror> 40. </mirrors> 41. 42. <servers> 43. <server> 44. <id>archiva</id> 45. <username>..._DeployerName_...</username> 46. <password>....................</password> 47. </server> 48. </servers> 49. 50. </settings> 51. 52. 53. 54. Auf dem Deployer-PC ein Testprojekt anlegen: cd \MeinWorkspace mvn archetype:generate -DinteractiveMode=false -DgroupId=test1 DartifactId=Test1 cd Test1 55. 56. 57. 58. 59. 60. 61. 62. Auf dem Deployer-PC für den Test die pom.xml des Test1-Projekts erweitern (dabei korrekte Archiva-URL eintragen): <distributionManagement> <repository> <id>archiva</id> <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/internal</ url> 63. </repository> 64. <snapshotRepository> 65. <id>archiva</id> 66. <url>http://<ArchivaServer>:<ArchivaPort>/archiva/repository/snapshots< /url> 67. </snapshotRepository> 68. </distributionManagement> 69. Diese POM-Einstellung erfolgt am besten in einer "Corporate POM (oder Projekt-Master-POM)". 70. 71. 72. Snapshot-Deployment: cd \MeinWorkspace\Test1 mvn deploy 73. 74. 75. Release-Deployment: In der pom.xml die Zeile <version>1.0-SNAPSHOT</version> ändern zu: <version>1.0</version> und aufrufen: mvn deploy 76. 77. 78. 79. 80. 81. 82. 83. Kontrolle: http://<ArchivaServer>:<ArchivaPort>/archiva/browse aufrufen und beide deployte Artefakte überprüfen. Doku zum Maven-Deploy-Plugin: 84. 85. 86. 87. 88. 89. 90. 91. 92. http://maven.apache.org/plugins/maven-deploy-plugin und http://maven.apache.org/plugins/maven-deploy-plugin/usage.html Sehen Sie sich auch die Hinweise zum Release-Auslieferungsprozess an. Repository-Manager Nexus Alternativ zu Archiva können Sie auch Nexus verwenden: 1. 2. 3. 4. Sehen Sie sich die Nexus-Referenzdoku an. 5. 6. 7. Downloaden Sie Nexus OSS (als Zipdatei, z.B. nexus-2.10.0-02-bundle.zip) und entzippen Sie das Archiv, z.B. nach 8. \Tools\Nexus. 9. 10. 11. 12. Der Nexus-Default-Port ist 8081. Da dieser Port auch von vielen anderen Anwendungen bevorzugt wird, sollten Sie einen anderen Port einstellen, 13. beispielsweise 4422. Tragen Sie die gewünschte Portnummer bei application-port=... in der 14. \Tools\Nexus\conf\nexus.properties 15. ein. 16. 17. 18. 19. 20. 21. 22. 23. Öffnen Sie ein Kommandozeilenfenster mit Admin-Rechten und verzweigen Sie in das Nexus-bin-Verzeichnis, z.B.: \Tools\Nexus\bin. Um Nexus für einen ersten Test zu starten, führen Sie aus (und warten, bis "Started [email protected]:4422" oder "Started [email protected]:4422" erscheint): cd \Tools\Nexus\bin nexus.bat console 24. 25. 26. 27. Um Nexus als Windows-Dienst zu installieren und automatisch zu starten, führen Sie aus: nexus.bat install net start nexus Überprüfen Sie die Parameter in der Windows-Registry unter: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\nexus-webapp. 28. 29. 30. 31. Starten Sie Nexus und sehen Sie sich die Admin-Konsole an: Starten Sie http://localhost:4422/nexus, klicken Sie oben rechts auf "Log In" und geben Sie als Username "admin" und als Password "admin123" ein. 32. 33. 34. 35. 36. Überprüfen Sie die Einstellungen, insbesondere unter "Views/Repositories" | "Repositories" sowie "Administration" | "Server". Beachten Sie, dass Sie je nach Nexus-Konfiguration nicht alle Menüeinträge und Parameter sehen können, 37. wenn Sie sich remote über 38. http://NexusPcName:4422/nexus verbinden. 39. In diesem Fall müssen Sie Nexus direkt lokal auf dem Nexus-PC mit localhost aufrufen, also über http://localhost:4422/nexus. 40. 41. Falls Ihr Netzwerk nur über einen Proxy ins Internet gelangt (wie es in Firmen häufig der Fall ist), 42. sehen Sie sich obige Bemerkungen zum Firmen-Proxy an. 43. 44. Auf der soeben erwähnten Nexus-Admin-Seite können Sie unter "Administration" | "Server" | "Default HTTP Proxy Settings (optional)" 45. den Proxy-Host und -Port eintragen. 46. 47. 48. 49. 50. Falls Sie über "Views/Repositories" | "Repositories" | "Add..." ein neues Repository hinzufügen: Vergessen Sie nicht, das neu angelegte Repository auch der Gruppe "Public Repositories" hinzuzufügen. 51. 52. 53. Sie sollten für alle Repositories konfigurieren: 54. File Content Validation = True 55. Checksum Policy = StrictIfExists 56. Dies ist vor allem dann wichtig, wenn Sie sich in einem Firmennetzwerk hinter einem Proxy befinden, siehe 57. http://books.sonatype.com/nexus-book/reference/confignx-sectmanage-repo.html. 58. Solche Proxys antworten häufig beim Download mit zwischengeschalteten HTML-Seiten. 59. Falls Sie beim Build auf "unerklärliche Fehler" (z.B. ClassNotFoundException oder NoClassDefFoundError) stoßen: 60. Überprüfen Sie im Nexus-Datenverzeichnis alle vermeintlichen *.jar-Dateien, ob sie in Wirklichkeit nur HTML-Inhalt haben. 61. Falls Sie eine solche fehlerhafte *.jar-Datei finden: Löschen Sie das gesamte Verzeichnis dieser Datei und führen Sie "Repair Index" aus. 62. Falls es keine andere Möglichkeit gibt, als eine .jar-Datei manuell downzuloaden und manuell der Nexus-Datenbank hinzuzufügen: 63. Denken Sie daran, zusätzlich auch die .jar.sha1-Datei hinzuzufügen und anschließend Nexus neu zu starten. 64. Außerdem müssen Sie eventuell beim Client-PC in dessen lokalem Maven-Repository alle Verzeichnisse löschen, 65. welche auf *.lastUpdated endende Dateien enthalten. 66. 67. 68. Auf den Workstations, auf denen Maven den Nexus-Repository-Server verwenden soll, muss jeweils in der 69. %M2_HOME%\conf\settings.xml 70. der Inhalt des <mirrors>-Tags ersetzt werden durch 71. (siehe auch Configuring Maven): 72. 73. 74. <mirror> 75. <id>nexus</id> 76. <mirrorOf>*</mirrorOf> 77. <url>http://__NEXUS_SERVER__:__NEXUS_PORT__/nexus/content/groups/public </url> 78. </mirror> 79. 80. <!-- Wird fuer Distribution-Deployment ins Remote-Maven-Repository benoetigt 81. (siehe "<distributionManagement>" in Master-POM) 82. (nur einfuegen, wenn Account wirklich existiert): --> 83. <servers> 84. <server> 85. <id>nexus</id> 86. <username>__MEIN_NEXUS_BENUTZERNAME__</username> 87. <password>__MEIN_NEXUS_KENNWORT__</password> 88. </server> 89. </servers> 90. Dabei "__NEXUS_SERVER__:__NEXUS_PORT__" durch korrekte Werte ersetzen, zum Beispiel "localhost:4422". 91. 92. 93. 94. Falls Sie eigene Artefakte (z.B. JDBC-Treiber oder JPA-Provider) 95. ins Nexus-Repository deployen wollen, loggen Sie sich als Admin ein und wählen Sie: 96. links Menüpunkt: "Repositories" | Mitte oben Zeile: "Releases" | Mitte unten Reiter: "Artifact Upload" | ... 97. 98. 99. Falls Sie folgende Fehlermeldung erhalten: 100. "[ERROR] Plugin ... or one of its dependencies could not be resolved: Failed to read artifact descriptor for ...: Could not transfer artifact ... from/to nexus: Not authorized, ReasonPhrase:Unauthorized: 101. Dann überprüfen Sie sehr genau die <server>-Account-Daten. 102. 103. 104. Falls Sie folgende Fehlermeldung erhalten: 105. "[ERROR] Failed to execute goal on project ...: Could not resolve dependencies for project ...: Failure to find ... in ... was cached in the local repository, resolution will not be reattempted until the update interval of nexus has elapsed or updates are forced -> [Help 1]", 106. und falls Sie die Wartezeit nicht abwarten wollen: 107. Löschen Sie vor einem neuen Versuch im lokalen Maven-Repository alle Verzeichnisse, welche auf *.lastUpdated endende Dateien enthalten. 108. 109. 110. Falls Sie folgende Fehlermeldung erhalten: 111. "Unable to update index for Nexus ...": 112. In Nexus als admin einloggen und 113. entweder unter Views/Repositories | Repositories mit der rechten Maustaste auf Public Repositories und "Repair Index" klicken 114. oder unter Administration | Scheduled Tasks die Task "Repair Repositories Index" einrichten und ausführen. 115. Falls Sie Eclipse und das 116. M2Eclipse-Plugin 117. installiert haben, müssen Sie eventuell zusätzlich Eclipse schließen und den Cache unter 118. %M2_REPO%\.cache\m2e 119. (z.B. %USERPROFILE%\.m2\repository\.cache\m2e 120. oder 121. \Tools\Maven3-Repo\.cache\m2e) 122. löschen. 123. 124. 125. Falls Sie beim Start des Nexus-Servers als Windows-Dienst diese Fehlermeldung erhalten: 126. Der Dienst ... konnte nicht gestartet werden. Fehler 1067: Der Prozess wurde unerwartet beendet. und in der \Tools\Nexus\logs\wrapper.log steht: wrapper | Unable to execute Java command. angegebene Datei nicht finden. (0x2) Das System kann die wrapper | "java" -... ... wrapper | Critical error: wait for JVM process failed Dann ersetzen Sie in der \Tools\Nexus\bin\jsw\conf\wrapper.conf die Zeile wrapper.java.command=java durch wrapper.java.command=C:\Program Files\Java\jdk1.7\bin\java Continuous Integration mit Jenkins / Hudson und Maven 3 Continuous Integration mit Jenkins oder Hudson ermöglicht die kontinuierliche Überwachung, ob Systeme funktionsfähig sind, sowie ob die ins Versionskontrollsystem (= "VCS", = "SCM", = Source Code Management, z.B. Git, Mercurial, Subversion, CVS) eingecheckten Module kompilierfähig sind, zusammenpassen und ob alle Tests (auch Integrationstests) fehlerfrei durchlaufen werden. Im Folgenden werden einige wichtige Schritte kurz beschrieben, um Jenkins für Maven-3-Projekte einzurichten. Die meisten Hinweise gelten genau so auch für Hudson. 1. 2. Die folgende Beschreibung geht von einem installierten Maven 3 und von bereits vorhanden und ins VCS/SCM eingecheckten Modulen aus. 3. 4. 5. Jenkins legt seine Arbeitsdateien defaultmäßig in einem .jenkinsUnterverzeichnis im 6. <user-home>-Verzeichnis (z.B. 7. C:\Users\%USERNAME%) ab. 8. Zumindest in Firmen sollten Sie dies vermeiden, 9. weil häufig das <user-home>-Verzeichnis bei jedem PC-Herunterfahren gesichert 10. und beim Starten wiederhergestellt wird, was unnötig BackupSpeicherplatz kosten und diese Vorgänge verlangsamen würde. 11. Um dies zu vermeiden, erzeugen Sie ein neues Verzeichnis und übergeben dies als Umgebungsvariable, zum Beispiel so: md C:\Tools\Jenkins set JENKINS_HOME=C:\Tools\Jenkins 12. 13. Downloaden Sie die Jenkins-Installations-WAR-Datei (z.B. jenkins1.556.war) von 14. http://jenkins-ci.org, 15. zum Beispiel in das bei %JENKINS_HOME% definierte Verzeichnis (z.B. C:\Tools\Jenkins). 16. 17. 18. Um zukünftige Updates zu erleichtern und um die mitgelieferten Skripte verwenden zu können, 19. sollten Sie die Versionsnummer aus dem Dateinamen entfernen. 20. Umbenennen Sie die downgeloadete Datei in jenkins.war. 21. 22. 23. 24. Prüfen Sie, ob die gewünschte Portnummer (z.B. 4424) noch frei ist: netstat -a netstat -an | find ":4424" 25. 26. Wechseln Sie in das Verzeichnis mit der jenkins.war-Datei und rufen Sie auf 27. (eventuell statt mit 4424 mit anderer Portnummer): 28. cd C:\Tools\Jenkins start "Jenkins" java -jar jenkins.war --httpPort=4424 Warten Sie, bis "Completed initialization" erscheint. start http://localhost:4424 29. 30. Wählen Sie im Jenkins-Hauptmenü "Jenkins verwalten" | "System konfigurieren": 31. 32. Überprüfen Sie, ob das "Jenkins Home-Verzeichnis" korrekt ist (z.B. C:\Tools\Jenkins). Stellen Sie unter "Anzahl Build-Prozessoren" ein, in wie vielen parallelen Threads die Jobs abgearbeitet werden sollen. Falls Sie keine Parallelität wollen, stellen Sie 1 ein. Unter "Maven Installationen": "Name" vergeben (z.B. "Maven 3"). "Automatisch installieren" deaktivieren. "MAVEN_HOME" eingeben (z.B. D:\Tools\Maven3). Falls Sie CVS verwenden und sich das Verzeichnis zur cvs.exe nicht im PATH befindet, geben Sie unter "CVS"/"CVS Befehl" den vollständigen Pfad zur cvs.exe ein (z.B. unter 64-bit-Windows: C:\Program Files (x86)\cvsnt\cvs.exe). 33. 34. 35. "E-Mail Benachrichtigung" ausfüllen falls gewünscht. "Jenkins URL" anpassen. "Übernehmen". Wählen Sie im Jenkins-Hauptmenü "Neuen Job anlegen": 36. "Job Name" eintragen (z.B. identisch zum Projektmodulnamen). Beachten: Für Maven-3-Projekte funktioniert nicht: "Maven 2 Projekt bauen". Falls es keine Auswahlmöglichkeit für Maven-3-Projekte gibt (wie z.B. bei Hudson 1.384), wählen: "Free Style"-Softwareprojekt bauen. "OK" "Source-Code-Management": Z.B. "CVS": "CVSROOT" (z.B. :pserver:<Benutzername>@<CvsServerUrl>:<Pfad>), "Module": Projektmodulname. "Build-Auslöser": Z.B. "Builds zeitgesteuert starten", "Zeitplan" z.B. "11 * * * *". "Buildverfahren": "Build-Schritt hinzufügen", "Maven Goals aufrufen", bei "Maven Version" den oben eingetragen Namen (z.B. "Maven 3"), "Goals" z.B. "clean install site". Statt "Maven Goals aufrufen" können Sie unter Windows auch "Windows-Batchdatei" wählen um Kommandozeilenbefehle ausführen. Zum Beispiel können Sie Ergebnisse kopieren, etwa so: xcopy "%WORKSPACE%\target\site" "\Tools\Tomcat\webapps\ROOT\sitereport\%JOB_NAME%\site\" /S Oder Sie überprüfen mit cURL REST-Webservices, etwa so: curl -s "http://meinrestserver:8080/rest/meinservice?parm1=7&parm2=Xyz" | find "Ergebnis=42" "Post-Build-Aktionen": Eventuell "Javadoc veröffentlichen", "Veröffentliche JUnit-Testergebnisse" mit "Testberichte in XML-Format: target/surefire-reports/*.xml". "Übernehmen". 37. 38. 39. 40. 41. Jenkins setzt einige Jenkins Environment Variables, die Sie während der Jenkins-Jobs verwenden können. Einige der Werte können innerhalb der Jenkins-Job-Beschreibungen hilfreich sein, 42. zum Beispiel um Kopierkommandos zu formulieren (z.B. WORKSPACE und JOB_NAME, s.o.). 43. Andere Werte können auch zur Laufzeit interessant sein (z.B. BUILD_ID, BUILD_NUMBER und CVS_BRANCH). 44. Sie können sie während des Buildprozesses automatisch in 45. Properties-Dateien oder in die 46. MANIFEST.MF eintragen. 47. 48. 49. Die bereits im Betriebssystem konfigurierten Environment Variablen (Umgebungsvariablen) 50. stehen innerhalb der Jenkins-Jobs nicht so ohne weiteres zur Verfügung. 51. Wenn Sie bestimmte Umgebungsvariablen benötigen, beispielsweise NLS_LANG für Oracle SqlPlus, 52. dann müssen Sie diese Umgebungsvariablen entweder innerhalb Jenkins global setzen 53. (Jenkins verwalten | System konfigurieren | Globale Eigenschaften | Umgebungsvariablen) 54. oder innerhalb der einzelnen Jenkins-Jobs setzen 55. (notfalls z.B. per "set NLS_LANG=German_Germany.AL32UTF8"). 56. Alternativ können Sie Umgebungsvariablen auch mit speziellen Jenkins-Plugins konfigurieren. 57. 58. 59. Jenkins sorgt normalerweise dafür, dass alle vom Jenkins-Job gestarteten Prozesse automatisch am Ende des Jenkins-Jobs beendet werden. 60. Dies ist normalerweise das gewünschte Verhalten. 61. Falls Sie in einem Jenkins-Job Prozesse starten wollen, die nicht von Jenkins beendet werden sollen, 62. können Sie dies erreichen, indem Sie in diesem Job die Umgebungsvariable BUILD_ID setzen, zum Beispiel so: 63. "set BUILD_ID=dontKillMe". Siehe hierzu: 64. https://wiki.jenkins-ci.org/display/JENKINS/ProcessTreeKiller. 65. Falls Sie den von Jenkins gesetzten Inhalt der 66. BUILD_ID 67. während des Jobs verwenden wollen, müssen Sie die BUILD_ID vorher in eine andere Umgebungsvariable retten. 68. 69. 70. 71. 72. Falls Sie CVS verwenden: Ein CVS-Kommandozeilenclient muss installiert sein, zum Beispiel per cvsnt_setup.exe aus WinCvs2_0_2-4.zip (der cvsnt-Server-Teil kann während der Installation deaktiviert werden und WinCvs braucht nicht installiert werden). Falls Sie folgende Fehlermeldung erhalten: "cvs checkout: Empty password used - try 'cvs login' with a real password": Dann führen Sie (unter dem richtigen Benutzer) einmalig einen Login ins CVS per Kommandozeile durch, z.B. so: cvs.exe -d:pserver:<Benutzername>@<CvsServerUrl>:<Pfad> login Dabei entsteht entweder im C:\Users\%USERNAME%-Verzeichnis die Passwortdatei .cvspass oder in der Windows-Registry ein cvspass-Eintrag entweder unter HKEY_CURRENT_USER\Software\cvsnt oder unter HKEY_USERS\.DEFAULT\Software\cvsnt. Falls Sie von Jenkins aus SonarQube-Reports erstellen wollen, Maven 3 verwenden und Schwierigkeiten mit dem SonarQube-Plugin (https://wiki.jenkins-ci.org/display/JENKINS/SonarQube+plugin) haben, dann können Sie SonarQube auch leicht ohne das SonarQube-Plugin in Jenkins einbinden: 1. 2. 3. 4. 5. 6. 7. 8. Installieren und starten Sie den SonarQube-Server, wie oben unter Sourcecodeanalyse mit SonarQube beschrieben ist (am besten als automatisch startender Dienst). Klicken Sie im Jenkins-Hauptmenü auf den gewünschten "Job" und wählen Sie "Konfigurieren": "Buildverfahren", "Build-Schritt hinzufügen", "Maven Goals aufrufen", 9. bei "Maven Version" den oben eingetragen Namen (z.B. "Maven 3"), 10. "Goals" "clean install sonar:sonar". 11. Falls Sie viele Jenkins-Jobs haben, kann es sinnvoll sein, mit dem "Nested View Plugin" zusätzliche Gliederungsebenen einzuführen. Dabei wird in der summarischen Ansicht angezeigt, ob es innerhalb der Ordner Fehlschläge gibt, oder ob alles ok ist. 1. Nested-View-Plugin-Homepage für Jenkins Normalerweise empfiehlt es sich, Jenkins als automatisch beim Booten startenden Dienst einzurichten. Dann läuft Jenkins bereits, ohne dass sich ein Benutzer anmelden muss. Unter Windows erfolgt dies folgendermaßen: 1. 2. 3. Bei neueren Jenkins-Versionen können Sie folgendermaßen verfahren: Starten Sie Jenkins (wie oben beschrieben) und klicken Sie unter "Jenkins verwalten" auf den Menüpunkt "Als Windows-Dienst installieren". 4. 5. 6. Falls dieser Menüpunkt "Als Windows-Dienst installieren" bei Ihrer Jenkins/Hudson-Version fehlt (z.B. in Hudson 1.398), 7. verfahren Sie folgendermaßen: 8. 9. 1. Entzippen Sie in einem temporären Verzeichnis Ihre hudson...war-Datei (z.B. hudson-1.398.war). 10. 1. Dann entzippen Sie die darin enthaltene WEB-INF\lib\hudson-core1.398.jar. 11. 1. Anschließend kopieren Sie aus dem darin enthaltenen windowsservice-Verzeichnis die 2. hudson.exe und hudson.xml ins HUDSON_HOME-Verzeichnis. 12. 13. 14. 15. Öffnen Sie die jenkins.xml im Jenkins-Home-Verzeichnis (z.B. C:\Tools\Jenkins). 16. Suchen Sie nach dem Eintrag zur jenkins-...war-Datei (z.B. jenkins.war) und 17. korrigieren Sie diesen Eintrag entsprechend dem Namen Ihrer jenkins-...war-Datei (also z.B. zu jenkins.war). 18. Suchen Sie weiter nach "--httpPort" und tragen Sie dort die gewünschte Portnummer ein (z.B. 4424). 19. 20. 21. 22. Suchen Sie in der Windows-Diensteverwaltung nach dem jenkins-Dienst über: "Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste". 23. 24. 25. 26. 27. 28. Falls es noch keinen jenkins-Dienst gibt, öffnen Sie ein Kommandozeilenfenster mit Administratorrechten über: Start | Alle Programme | Zubehör | "Eingabeaufforderung" mit rechter Maustaste und "Als Administrator ausführen". Darin führen Sie aus: cd C:\Tools\Jenkins jenkins.exe install 29. 30. 31. Starten Sie Jenkins entweder erstmalig oder neu über: "Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste" | Doppelklick auf "jenkins" | "Beenden"/"Starten"). 32. 33. 34. 35. Falls Jenkins-Jobs bestimmte Benutzerrechte benötigen (z.B. für den VCS/SCM-Account), wählen Sie: "Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste" | Doppelklick auf "jenkins" | Reiter "Anmelden" | "Dieses Konto". 36. 37. 38. Falls Sie beim Starten des Dienstes diese Fehlermeldung erhalten: 39. Dienst "jenkins" wurde auf "Lokaler Computer" gestartet und dann angehalten. Einige Dienste werden automatisch angehalten, wenn sie nicht von anderen Diensten oder Programmen verwendet werden. 40. und die Windows-Ereignisanzeige (unter Start | Systemsteuerung | [System und Sicherheit] | Verwaltung | Computerverwaltung | System | Ereignisanzeige) zeigt an: 41. Der Dienst kann nicht gestartet werden. System.ComponentModel.Win32Exception: Das System kann die angegebene Datei nicht finden. 42. Dann ersetzen Sie in der jenkins.xml die Zeile: 43. <executable>java</executable> durch: <executable>C:\Program Files\Java\jdk1.7\bin\java</executable> 44. 45. Falls Sie eine Fehlermeldung erhalten ähnlich zu einer der folgenden: 46. The system cannot find the file specified. 47. FATAL: Befehlsausführung fehlgeschlagen. 48. CreateProcess error=2, Das System kann die angegebene Datei nicht finden. 49. ERROR: JAVA_HOME not found in your environment. 50. Dann ersetzen Sie in der config.xml die Zeile: 51. <jdks/> durch: <jdks> <jdk> <name>jdk1.7</name> <home>C:\Program Files\Java\jdk1.7</home> <properties/> </jdk> </jdks> 52. 53. Falls Jenkins beim nächsten Windows-Start nicht automatisch startet, obwohl der Dienste-Starttyp auf "Automatisch" steht, 54. wählen Sie: 55. "Start" | "Systemsteuerung" | ["System und Sicherheit"] | "Verwaltung" | "Dienste" | Doppelklick auf "jenkins" 56. und dann entweder: 57. "Starttyp: Automatisch (Verzögerter Start)" 58. oder: 59. Reiter "Wiederherstellung" | "Bei Fehlern: Dienst neu starten" und eine geeignete Wartezeit einstellen. 60. 61. 62. Falls Jenkins ohne sichtbare Fehlermeldung und ohne Meldung in den Jenkins-Logdateien nicht automatisch startet, 63. suchen Sie in der Windows-Ereignisanzeige unter der "Quelle" "Service Control Manager" nach Meldungen zu Jenkins, 64. etwa nach folgenden Fehlermeldungen: 65. Das Zeitlimit (30000 ms) wurde beim Verbindungsversuch mit dem Dienst jenkins erreicht. 66. Der Dienst "jenkins" wurde aufgrund folgenden Fehlers nicht gestartet: 67. Der Dienst antwortete nicht rechtzeitig auf die Start- oder Steuerungsanforderung. 68. 69. 70. 71. Modifizieren Sie entsprechend der Meldung die Startparameter. Falls Sie bei korrekt installiertem Jenkins-Dienst nur den JenkinsDienst stoppen oder starten wollen, können Sie das net-Kommando verwenden: 72. net stop jenkins net start jenkins 73. 74. Falls Sie den Jenkins-Dienst deinstallieren wollen: Dies können Sie wahlweise über 75. jenkins.exe uninstall oder sc delete jenkins Weitere Themen: andere TechDocs | Maven-Multimodulprojekte | maven.apache.org © 2015 Torsten Horn, Aachen