Maven Definitive Guide De
Upcoming SlideShare
Loading in...5
×
 

Maven Definitive Guide De

on

  • 9,729 views

 

Statistics

Views

Total Views
9,729
Views on SlideShare
9,726
Embed Views
3

Actions

Likes
1
Downloads
119
Comments
0

2 Embeds 3

http://www.techgig.com 2
http://115.112.206.131 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Maven Definitive Guide De Maven Definitive Guide De Document Transcript

  • Thomas Locher (Sonatype, Inc.), Tim O'Brien (Sonatype, Inc.), John Casey (Sonatype, Inc.), Brian Fox (Sonatype, Inc.), Bruce Snyder (Sonatype, Inc.), Jason Van Zyl (Sonatype, Inc.), Eric Redmond () Copyright © 2006-2008
  • Copyright ....................................................................................................... xii 1. Creative Commons BY-ND-NC ......................................................... xii Vorwort zur deutschen Ausgabe: 0.5 ............................................................ xiv Vorwort .......................................................................................................... xv 1. Anleitung zu diesem Buch .................................................................. xv 2. Ihr Feedback ....................................................................................... xvi 3. Typographische Konventionen ......................................................... xvii 4. Konventionen in Bezug auf Maven .................................................. xvii 5. Danksagung ......................................................................................xviii 1. Einführung von Apache Maven ................................................................... 1 1.1. Maven ... Was ist das? ........................................................................ 1 1.2. Konvention über Konfiguration ......................................................... 2 1.3. Die gemeinsame Schnittstelle ............................................................ 3 1.4. Universelle Wiederverwendung durch Maven-Plugins ..................... 5 1.5. Konzeptionelles Modell eines "Projekts" .......................................... 6 1.6. Ist Maven eine Alternative zu XYZ? ................................................. 8 1.7. Ein Vergleich von Maven und Ant .................................................... 9 1.8. Zusammenfassung ............................................................................ 14 2. Installieren und Ausführen von Maven ...................................................... 15 2.1. Überprüfen der Java-Installation ...................................................... 15 2.2. Herunterladen von Maven ................................................................ 16 2.3. Installation von Maven ..................................................................... 16 2.3.1. Maven Installation unter Mac OSX ....................................... 17 2.3.2. Maven Installation unter Microsoft Windows ....................... 18 2.3.3. Maven Installation unter Linux .............................................. 19 2.3.4. Maven Installation unter FreeBSD oder OpenBSD ............... 19 2.4. Testen einer Maven Installation ....................................................... 19 2.5. Spezielle Installationshinweise ........................................................ 20 2.5.1. Benutzerdefinierte Konfiguration und-Repository ................ 21 2.5.2. Aktualisieren einer Maven-Installation .................................. 22 2.5.3. Upgrade einer Maven-Installation von Maven 1.x auf Maven 2.x ..................................................................................................... 23 2.6. Maven De-Installieren ...................................................................... 24 ii
  • Maven: The Definitive Guide 2.7. Hilfe bekommen beim Arbeiten mit Maven .................................... 24 2.8. Das Maven Hilfe Plugin ................................................................... 26 2.8.1. Beschreibung eines Maven Plugin ......................................... 27 2.9. Die Apache Software Lizenz ........................................................... 29 I. Maven by Example ..................................................................................... 31 3. Ein einfaches Maven Projekt .............................................................. 33 3.1. Einleitung .................................................................................. 33 3.1.1. Das Herunterladen der Beispiele dieses Kapitels ............ 33 3.2. Erstellen eines einfachen Projekts ............................................. 34 3.3. Der Aufbau eines einfachen Projekts ........................................ 36 3.4. Einfaches Projekt Objekt Modell (POM) .................................. 37 3.5. Kern Konzepte .......................................................................... 39 3.5.1. Maven Plugins und Ziele ................................................. 39 3.5.2. Maven Lifecycle .............................................................. 42 3.5.3. Maven Koordinaten ......................................................... 46 3.5.4. Maven Repositories ......................................................... 49 3.5.5. Maven Abhängigkeits-Management (Dependency Management) ............................................................................. 52 3.5.6. Site-Generierung und Reporting ..................................... 55 3.6. Zusammenfassung ..................................................................... 56 4. Anpassen eines Maven Projektes ........................................................ 57 4.1. Einleitung .................................................................................. 57 4.1.1. Herunterladen der Beispiele dieses Kapitels ................... 57 4.2. Eine kurze Einführung in das "Simple Weather" Projekt ......... 57 4.2.1. Yahoo! Wetter Dienst RSS ............................................. 58 4.3. Erstellen des "Simple Weather" Projektes ................................ 59 4.4. Anpassen der Projektinformationen .......................................... 60 4.5. Einfügen neuer Abhängigkeiten ................................................ 62 4.6. Quellcode von "Simple Weather" ............................................. 64 4.7. Resourcen Hinzufügen .............................................................. 71 4.8. Ausführen des "Simple Weather" Programms .......................... 72 4.8.1. Das Exec Maven Plugin .................................................. 74 4.8.2. Erkundung der Projekt Abhängigkeiten .......................... 74 4.9. Erstellen von Unit-Tests ............................................................ 77 iii
  • Maven: The Definitive Guide 4.10. Hinzufügen von Gebietsbezogenen Unit Tests ....................... 80 4.11. Hinzufügen einer Unit-Test Ressource ................................... 81 4.12. Ausführen von Unit-Tests ....................................................... 83 4.12.1. Ignorieren fehlgeschlagener Unit Tests ......................... 84 4.12.2. Überspringen von Unit-Tests ........................................ 86 4.13. Builden einer paketierten, Befehlszeilen orientierten Anwendung ...................................................................................... 87 4.13.1. Anbinden des Assembly Goals zur Packetierungs Phase 89 5. Eine einfache Web-Anwendung ......................................................... 91 5.1. Einleitung .................................................................................. 91 5.1.1. Herunterladen der Beispiele dieses Kapitels ................... 91 5.2. Eine kurze Einführung in die "Simple Web" Anwendung ........ 91 5.3. Erstellen des "Simple Web" Projekts ........................................ 92 5.4. Konfigurieren des Jetty-Plugins ................................................ 93 5.5. Das Hinzufügen eines einfachen Servlets ................................. 96 5.6. Das Hinzufügen von J2EE-Abhängigkeiten ............................. 98 5.7. Zusammenfassung ................................................................... 100 6. Ein multi-modulares Projekt ............................................................. 101 6.1. Einleitung ................................................................................ 101 6.1.1. Herunterladen der Beispiele dieses Kapitels ................. 101 6.2. Das "Simple Parent" Projekt (parent=Elternteil) .................... 102 6.3. Das "Simple Weather" Modul ................................................. 103 6.4. Das "Simple-Web" Anwendungs-Modul ................................ 106 6.5. Erstellung des Multi-Projekt-Moduls ...................................... 109 6.6. Starten der Web-Anwendung .................................................. 110 7. Multi-module Enterprise Project ....................................................... 112 7.1. Einleitung ................................................................................ 112 7.1.1. Herunterladen der Beispiele dieses Kapitels ................. 112 7.1.2. Multi-Modul Enterprise Projekt .................................... 113 7.1.3. Technologie des Beispiels ............................................. 116 7.2. Das "Simple Parent" Projekt - Top Level ............................... 117 7.3. Das Simple Model" Modul - Das Objektmodell ..................... 119 7.4. Das "Simple Weather" Modul - Die Dienste .......................... 125 7.5. Das "Simple Persist" Modul - Die Datenabstraktion .............. 129 iv
  • Maven: The Definitive Guide 7.6. Das "Simple Web" Modul - Das Web-Interface ..................... 139 7.7. Aufrufen der Web-Anwendung ............................................... 153 7.8. Das "Simple Command" Modul - Das Kommandozeilen Modul ......................................................................................................... 154 7.9. Aufrufen der Kommandozeilen-Anwendung .......................... 162 7.10. Fazit ....................................................................................... 164 7.10.1. Programmierung gegen Interface-Projekte ................. 166 8. Optimirung und Überarbeitung der POMs ........................................ 168 8.1. Einführung ............................................................................... 168 8.2. POM Bereinigung ................................................................... 169 8.3. Optimirung der Abhängigkeiten ............................................. 170 8.4. Optimirung der Plugins ........................................................... 176 8.5. Optimierung unter Zuhilfenahmen des Maven Dependency Plugin ......................................................................................................... 178 8.6. Abschliessende POMs ............................................................. 182 8.7. Fazit ......................................................................................... 190 II. Maven Reference ..................................................................................... 192 9. The Project Object Model ................................................................. 193 9.1. Introduction ............................................................................. 193 9.2. The POM ................................................................................. 193 9.2.1. The Super POM ............................................................. 196 9.2.2. The Simplest POM ........................................................ 200 9.2.3. The Effective POM ....................................................... 201 9.2.4. Real POMs .................................................................... 201 9.3. POM Syntax ............................................................................ 202 9.3.1. Project Versions ............................................................ 202 9.3.2. Property References ...................................................... 205 9.4. Project Dependencies .............................................................. 207 9.4.1. Dependency Scope ........................................................ 208 9.4.2. Optional Dependencies ................................................. 210 9.4.3. Dependency Version Ranges ........................................ 211 9.4.4. Transitive Dependencies ............................................... 213 9.4.5. Conflict Resolution ....................................................... 215 9.4.6. Dependency Management ............................................. 217 v
  • Maven: The Definitive Guide 9.5. Project Relationships ............................................................... 219 9.5.1. More on Coordinates ..................................................... 220 9.5.2. Multi-module Projects ................................................... 221 9.5.3. Project Inheritance ......................................................... 223 9.6. POM Best Practices ................................................................. 227 9.6.1. Grouping Dependencies ................................................ 227 9.6.2. Multi-module vs. Inheritance ........................................ 229 10. Der Build Lebenszyklus .................................................................. 237 10.1. Einführung ............................................................................. 237 10.1.1. Lebenszyklus: clean .................................................... 237 10.1.2. Standard Lebenszyklus: default .................................. 241 10.1.3. Lebenszyklus: site ....................................................... 243 10.2. Package-spezifische Lebenszyklen ....................................... 244 10.2.1. jar ................................................................................. 245 10.2.2. pom .............................................................................. 246 10.2.3. plugin ........................................................................... 246 10.2.4. ejb ................................................................................ 247 10.2.5. war ............................................................................... 248 10.2.6. ear ................................................................................ 248 10.2.7. Andere Packetierungs Typen ...................................... 249 10.3. Gebräuchliche Lebenszyklus Goals ...................................... 251 10.3.1. Ressourcen Verarbeiten ............................................... 251 10.3.2. compile ........................................................................ 255 10.3.3. Verarbeiten von Test Ressourcen ................................ 257 10.3.4. Kompilieren der Test Klassen (testCompile) .............. 258 10.3.5. test ............................................................................... 258 10.3.6. install ........................................................................... 260 10.3.7. deploy .......................................................................... 260 11. Build Profiles .................................................................................. 262 11.1. What Are They For? .............................................................. 262 11.1.1. What is Build Portability ............................................. 262 11.1.2. Selecting an Appropriate Level of Portability ............. 264 11.2. Portability through Maven Profiles ....................................... 265 11.2.1. Overriding a Project Object Model ............................. 268 vi
  • Maven: The Definitive Guide 11.3. Profile Activation .................................................................. 269 11.3.1. Activation Configuration ............................................. 271 11.3.2. Activation by the Absence of a Property .................... 272 11.4. Listing Active Profiles .......................................................... 273 11.5. Tips and Tricks ...................................................................... 274 11.5.1. Common Environments ............................................... 274 11.5.2. Protecting Secrets ........................................................ 276 11.5.3. Platform Classifiers ..................................................... 278 11.6. Summary ............................................................................... 280 12. Maven Assemblies .......................................................................... 282 12.1. Introduction ........................................................................... 282 12.2. Assembly Basics ................................................................... 283 12.2.1. Predefined Assembly Descriptors ............................... 284 12.2.2. Building an Assembly ................................................. 285 12.2.3. Assemblies as Dependencies ....................................... 288 12.2.4. Assembling Assemblies via Assembly Dependencies 289 12.3. Overview of the Assembly Descriptor .................................. 293 12.4. The Assembly Descriptor ...................................................... 296 12.4.1. Property References in Assembly Descriptors ............ 296 12.4.2. Required Assembly Information ................................. 296 12.5. Controlling the Contents of an Assembly ............................. 298 12.5.1. Files Section .............................................................. 298 12.5.2. FileSets Section ......................................................... 299 12.5.3. Default Exclusion Patterns for fileSets .................... 302 12.5.4. dependencySets Section ............................................. 303 12.5.5. moduleSets Sections ................................................... 317 12.5.6. Repositories Section .................................................... 325 12.5.7. Managing the Assembly’s Root Directory .................. 326 12.5.8. componentDescriptors and containerDescriptorHandlers ............................................. 327 12.6. Best Practices ........................................................................ 328 12.6.1. Standard, Reusable Assembly Descriptors .................. 328 12.6.2. Distribution (Aggregating) Assemblies ...................... 332 12.7. Summary ............................................................................... 337 vii
  • Maven: The Definitive Guide 13. Properties and Ressource Filterung ................................................. 338 13.1. Einleitung .............................................................................. 338 13.2. Maven Properties .................................................................. 338 13.2.1. Maven Projekt Einstellungen ...................................... 339 13.2.2. Properties der Maven Einstellungen (settings.xml) .... 342 13.2.3. Properties der Umgebungsvariablen ........................... 342 13.2.4. Java System Properties ................................................ 343 13.2.5. Benuzerdefinierte Properties ....................................... 345 13.3. Ressource Filterung ............................................................... 346 14. Maven in Eclipse: m2eclipse .......................................................... 351 14.1. Einführung ............................................................................. 351 14.2. m2eclipse ............................................................................... 351 14.3. Installation des m2eclipse Plugins ........................................ 352 14.3.1. Installieren der Voraussetzungen ................................ 353 14.3.2. Installation von m2eclipse ........................................... 355 14.4. Aufschalten der Maven Konsole ........................................... 356 14.5. Erstellen eines Maven Projekts ............................................. 357 14.5.1. Auschecken eines Maven Projektes von einem SCM Repository ............................................................................... 358 14.5.2. Erstellen eines Maven Projekts auf der Basis eines Maven Archetyps ................................................................................ 360 14.5.3. Erstellen eines Maven Moduls .................................... 364 14.6. Erstellen einer Maven POM Datei ........................................ 367 14.7. Importieren von Maven Projekten ........................................ 371 14.7.1. Importiren eines Maven Projektes ............................... 373 14.7.2. Materialisieren eines Maven Projektes ....................... 375 14.8. Starten von Maven Builds ..................................................... 379 14.9. Mit Maven Projekten arbeiten ............................................... 382 14.9.1. Zufügen und Updaten von Abhängigkeiten und Plugins 384 14.9.2. Erstellen eines Maven Modules .................................. 387 14.9.3. Herunterladen der Quelldatei(en) ................................ 387 14.9.4. Öffnen von Projektseiten ............................................. 387 14.9.5. Auflösen von Abhängigkeiten ..................................... 388 14.10. Arbeiten mit den Maven Repositorien ................................ 388 viii
  • Maven: The Definitive Guide 14.10.1. Suchen von Maven Artefakten sowie Java Klassen .. 389 14.10.2. Indizierung von Maven Repositorien ........................ 394 14.11. Der neue graphische POM Editor ....................................... 398 14.12. Projektabhängigkeiten mit m2eclipse analysieren .............. 404 14.13. Maven Einstellungen ........................................................... 409 14.14. Zusammenfassung ............................................................... 417 15. Site Generation ................................................................................ 419 15.1. Introduction ........................................................................... 419 15.2. Building a Project Site with Maven ...................................... 420 15.3. Customizing the Site Descriptor ........................................... 422 15.3.1. Customizing the Header Graphics ............................... 423 15.3.2. Customizing the Navigation Menu ............................. 424 15.4. Site Directory Structure ......................................................... 426 15.5. Writing Project Documentation ............................................ 427 15.5.1. APT Example .............................................................. 427 15.5.2. FML Example ............................................................. 428 15.6. Deploying Your Project Website .......................................... 429 15.6.1. Configuring Server Authentication ............................. 430 15.6.2. Configuring File and Directory Modes ....................... 431 15.7. Customizing Site Appearance ............................................... 432 15.7.1. Customizing the Site CSS ........................................... 432 15.7.2. Create a Custom Site Template ................................... 433 15.7.3. Reusable Website Skins .............................................. 438 15.7.4. Creating a Custom Theme CSS ................................... 440 15.7.5. Customizing Site Templates in a Skin ........................ 441 15.8. Tips and Tricks ...................................................................... 443 15.8.1. Inject XHTML into HEAD ......................................... 443 15.8.2. Add Links under Your Site Logo ................................ 443 15.8.3. Add Breadcrumbs to Your Site ................................... 444 15.8.4. Add the Project Version .............................................. 445 15.8.5. Modify the Publication Date Format and Location ..... 446 15.8.6. Using Doxia Macros .................................................... 447 16. Repository Management with Nexus .............................................. 449 17. Writing Plugins ............................................................................... 451 ix
  • Maven: The Definitive Guide 17.1. Introduction ........................................................................... 451 17.2. Programming Maven ............................................................. 451 17.2.1. What is Inversion of Control? ..................................... 452 17.2.2. Introduction to Plexus ................................................. 453 17.2.3. Why Plexus? ................................................................ 454 17.2.4. What is a Plugin? ......................................................... 455 17.3. Plugin Descriptor .................................................................. 456 17.3.1. Top-level Plugin Descriptor Elements ........................ 458 17.3.2. Mojo Configuration ..................................................... 459 17.3.3. Plugin Dependencies ................................................... 463 17.4. Writing a Custom Plugin ....................................................... 463 17.4.1. Creating a Plugin Project ............................................. 463 17.4.2. A Simple Java Mojo .................................................... 464 17.4.3. Configuring a Plugin Prefix ........................................ 466 17.4.4. Logging from a Plugin ................................................ 470 17.4.5. Mojo Class Annotations .............................................. 471 17.4.6. When a Mojo Fails ...................................................... 473 17.5. Mojo Parameters ................................................................... 474 17.5.1. Supplying Values for Mojo Parameters ...................... 474 17.5.2. Multi-valued Mojo Parameters .................................... 477 17.5.3. Depending on Plexus Components ............................. 479 17.5.4. Mojo Parameter Annotations ...................................... 479 17.6. Plugins and the Maven Lifecycle .......................................... 481 17.6.1. Executing a Parallel Lifecycle ..................................... 481 17.6.2. Creating a Custom Lifecycle ....................................... 482 17.6.3. Overriding the Default Lifecycle ................................ 484 18. Writing Plugins in Alternative Languages ...................................... 487 18.1. Writing Plugins in Ant .......................................................... 487 18.2. Creating an Ant Plugin .......................................................... 487 18.3. Writing Plugins in JRuby ...................................................... 490 18.3.1. Creating a JRuby Plugin .............................................. 491 18.3.2. Ruby Mojo Implementations ....................................... 493 18.3.3. Logging from a Ruby Mojo ........................................ 496 18.3.4. Raising a MojoError .................................................... 497 x
  • Maven: The Definitive Guide 18.3.5. Referencing Plexus Components from JRuby ............ 497 18.4. Writing Plugins in Groovy .................................................... 498 18.4.1. Creating a Groovy Plugin ............................................ 499 19. Using Maven Archetypes ................................................................ 501 19.1. Introduction to Maven Archetypes ........................................ 501 19.2. Using Archetypes .................................................................. 502 19.2.1. Using an Archetype from the Command Line ............ 502 19.2.2. Using the Interactive generate Goal ............................ 503 19.2.3. Using an Archetype from m2eclipse ........................... 506 19.3. Available Archetypes ............................................................ 506 19.3.1. Common Maven Archetypes ....................................... 506 19.3.2. Notable Third-Party Archetypes ................................. 508 19.4. Publishing Archetypes .......................................................... 511 A. Appendix: Detailinformationen zur settings.xml-Datei .......................... 514 A.1. Übersicht ....................................................................................... 514 A.2. Die Details der settings.xml Datei ................................................ 515 A.2.1. Einfache Wertangaben ........................................................ 515 A.2.2. Servers ................................................................................. 516 A.2.3. Spiegelrepositorien .............................................................. 517 A.2.4. Proxies ................................................................................. 518 A.2.5. Profiles ................................................................................ 520 A.2.6. Activation ............................................................................ 520 A.2.7. Properties ............................................................................. 522 A.2.8. Repositories ......................................................................... 523 A.2.9. Plugin Repositories ............................................................. 525 A.2.10. Aktive Profile .................................................................... 526 B. Appendix: Alternativen zu den Sun Spezifikationen .............................. 528 xi
  • Copyright Copyright 2008 Sonatype, Inc. Online version published by Sonatype, Inc., 654 High Street, Suite 220, Palo Alto, CA, 94301. Print version published by O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. Nutshell Handbook, the Nutshell Handbook logo, and the O'Reilly logo are registered trademarks of O'Reilly Media, Inc. The Developer's Notebook series designations, the look of a laboratory notebook, and related trade dress are trademarks of O'Reilly Media, Inc. Java(TM) and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc., in the United States and other countries. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and Sonatype, Inc. was aware of a trademark claim, the designations have been printed in caps or initial caps. While every precaution has been taken in the preparation of this book, the publisher and authors assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein. 1. Creative Commons BY-ND-NC This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States license. For more information about this license, see http://creativecommons.org/licenses/by-nc-nd/3.0/us/. You are free to share, copy, distribute, display, and perform the work under the following conditions: • You must attribute the work to Sonatype, Inc. with a link to http://www.sonatype.com. xii
  • Copyright • You may not use this work for commercial purposes. • You may not alter, transform, or build upon this work. If you redistribute this work on a web page, you must include the following link with the URL in the about attribute listed on a single line (remove the backslashes and join all URL parameters): <div xmlns:cc="http://creativecommons.org/ns#" about="http://creativecommons.org/license/results-one?q_1=2&q_1=1 &field_commercial=n&field_derivatives=n&field_jurisdiction=us &field_format=StillImage&field_worktitle=Maven%3A+Guide &field_attribute_to_name=Sonatype%2C+Inc. &field_attribute_to_url=http%3A%2F%2Fwww.sonatype.com &field_sourceurl=http%3A%2F%2Fwww.sonatype.com%2Fbook &lang=en_US&language=en_US&n_questions=3"> <a rel="cc:attributionURL" property="cc:attributionName" href="http://www.sonatype.com">Sonatype, Inc.</a> / <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/us/"> CC BY-NC-ND 3.0</a> </div> When downloaded or distributed in a jurisdiction other than the United States of America, this work shall be covered by the appropriate ported version of Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 license for the specific jurisdiction. If the Creative Commons Attribution-Noncommercial-No Derivative Works version 3.0 license is not available for a specific jurisdiction, this work shall be covered under the Creative Commons Attribution-Noncommercial-No Derivate Works version 2.5 license for the jurisdiction in which the work was downloaded or distributed. A comprehensive list of jurisdictions for which a Creative Commons license is available can be found on the Creative Commons International web site at http://creativecommons.org/international. If no ported version of the Creative Commons license exists for a particular jurisdiction, this work shall be covered by the generic, unported Creative Commons Attribution-Noncommercial-No Derivative Works version 3.0 license available from http://creativecommons.org/licenses/by-nc-nd/3.0/. xiii
  • Vorwort zur deutschen Ausgabe: 0.5 Vor Ihnen liegt die deutsche Übersetzung der "Definitiv Guide to Apache Maven". Ein Werk das, wie das englische Original, zum Ziel hat Brücken zu schlagen. Brücken nicht nur zwischen Technikern und Techniken, sondern auch Brücken zwischen unseren verschiedenen Kulturen. In meiner täglichen Erfahrung gibt es eine grosse Zahl Arbeitsgruppen und Teams welche mehr als eine, oft sogar so viele Nationalitäten wie Mitglieder umfassen. Das Kommunikationsmedium ist meist der kleinste gemeinsame Nenner: (Sowie-)Englisch. Wie oft habe ich nach tage- und nächtelangem Suchen feststellen müssen, dass der Fehler eigentlich ganz offensichtlich war, wenn Man(n) nur die (englische) Anleitung verstanden hätte! Maven ist ein mächtiges Tool, häufiger missverstanden und nur rudimentär eingesetzt - insbesondere weil die Zusammenhänge unklar, die Dokumentation in Englisch schwierig und lückenhaft ist. Da ich vom Wert dieses Werkzeugs überzeugt bin habe ich alles gegeben dieses Werkzeug auch dem deutschen Sprachraum "native" zugänglich zu machen. Ich erhoffe mir hiervon auch, Entwickler zu ermutigen sich in der grossen bestehenden Maven Community einzubringen, Themen aufzugreifen und weiter zu führen. Diesen Entwicklern möchte ich Sicherheit bieten indem ich Ihnen eine Referenz zur Seite stelle, die sie verstehen. Mit Blick auf unsere multinationalen Gruppen habe ich versucht das Werk so nahe am englischen Original zu belassen als möglich. Sie werden also auf Abschnittebene vergleichen können. Sollten Sie unsicher sein was Ihr Kollege tatsächlich meint, so zücken Sie Ihr deutsches Exemplar, sehen nach welchen Abschnitt im englischen Original Ihr Kollege referenziert - et voilà - lesen Sie in Ihrer Muttersprache was gemeint ist. Thomas Locher Zürich, im Januar 2008 PS: Habe ich Ihnen bereits empfohlen die englische Originalausgabe zu kaufen? xiv
  • Vorwort Maven is ein Build Werkzeug, ein Projekt Management Werkzeug ein abstrakter Container um Build Aufträge abzuarbeiten. Es stellt ein Werkzeug dar, welches unumgänglich ist für alle Projekte welche über die einfachsten Anforderungen hinauswachsen und sich plötzlich in der Lage finden konsistent eine grosse Zahl voneinander abhängiger Bibliotheken zu Builden, welche sich auf dutzende oder gar hunderte Komponenten (auch) von Drittparteien abstützen. Es ist ein Werkzeug, das dazu beigetragen hat einen Grossteil des Verwaltungsaufwandes für Komponenten aus dem Alltag von Millionen von Fachspezialisten zu eliminieren. Es hat vielen Organisationen geholfen sich weiterzuentwickeln, hinweg über die Phase des täglichen Kampf mit dem Build Management, hin zu einer neuen Phase in welcher der Aufwand einen Build zu erhalten und zu pflegen nicht mehr eine Grenzgrösse des Software Designs darstellt. Dieses Buch ist ein erster Versuch eines umfassenden Werkes bezüglich Maven. Es baut auf die gesammelten Erfahrungen und der Arbeit aller Autoren vorgängiger Titel bezüglich Maven auf und Sie sollten dieses Werk nicht als fertiges Werkstück ansehen sondern vielmehr als der erster Wurf einer langen Reihe von Updates welche dieser Ausgabe folgen werden. Auch wenn es Maven bereits einige Jahre gibt, so empfinden die Autoren dieses Werkes dennoch, dass Maven erst gerade begonnen hat die kühnen Versprechen einzulösen welche auf den Weg gegeben wurden. Die Autoren -wie auch die Firma- welche hinter diesem Buch stehen, Sonatype glauben fest daran, dass dieses Werk dazu beitragen wird, eine neue Welle der Innovation und Entwicklung in Umfeld von Maven und dessen Ökosystem loszutreten. 1. Anleitung zu diesem Buch Nehmen Sie dieses Buch in die Hand und lesen Sie einige Seiten des Textes. Am Ende der Seite werden Sie, sollten Sie die HTML Ausgabe lesen, entweder den Link zur nächsten Seite folgen wollen, oder im Fall der gedruckten Ausgabe werden Sie die Ecke heben und weiterblättern. Sollten Sie vor einem Computer xv
  • Vorwort sitzen, so können Sie sicherlich das eine oder andere Beispiel durchspielen um dem Text zu folgen. Was Sie nicht tun sollten, auch nicht in Wut, ist das Buch in die Richtung eines Mitmenschen zu werfen - es ist einfach zu schwer! Dieses Buch gliedert sich in drei Abschnitte: eine Einführung, einen Beispielteil und eine Referenz. Die Einführung besteht aus zwei Kapiteln: Einführung und Installation. Der Beispielteil führt Maven ein, indem einige echte Anwendungsbeispiele eingeführt werden und Ihnen anhand der Struktur der Beispiele weitere Motivation und Erklärung gegeben werden. Sollte Maven für Sie neu sein, so fangen Sie an, den Beispielteil zu lesen. Die Referenz ist weniger als Einführung gedacht, sondern mehr als Nachschlagewerk. Jedes Kapitel des Referenzteils ziehlt genau auf ein Thema ab und geht hierbei auf die tiefstmöglichen Details ein. So wird das Kapitel über die Erstellung von Plugins anhand von einigen Beispielen und einer Anzahl Listen und Tabellen abgehandelt. Beide Abschnitte bieten Erklärungen und doch werden grundverschiedene Wege eingeschlagen. Während der beispielhafte Teil sich auf den Zusammenhang innerhalb des Maven Projekt stützt, konzentriert sich der Referenzteil jeweils auf ein einziges Thema. Sie können natürlich auch im Buch herumspringen; der beispielhafte Teil ist in keiner Weise Voraussetzung für den Referenzteil, aber Sie werden den Referenzteil besser verstehen, sollten Sie bereits den beispielhaften Teil gelesen haben. Maven wird am Besten anhand von Beispielen gelernt, sobald Sie die Beispiele durchgeabeitet haben werden Sie eine gute Referenzquelle brauchen, um sich Maven Ihren Bedürfnissen anzupassen. 2. Ihr Feedback Wir haben dieses Buch nicht geschrieben, um unserem Herausgeber ein Word Dokument zu senden, eine Veröffentlichung zu feiern und einander zu Beglückwünschen was für eine gute Arbeit wir geleistet haben. Dieses Buch ist nicht "fertig", ja dieses Buch wird wohl nie "fertig" werden. Das behandelte Thema ändert sich ständig, weitet sich aus; und so verstehen wir dieses Buch als ein ständig weitergehender Dialog mit der Maven Community. Dieses Buch herauszugeben bedeutet, dass die wahre Arbeit erst gerade begonnen hat, und Sie, der Leser, spielen eine zentrale Rolle dieses Buch zu erweitern, verbessern und xvi
  • Vorwort aktuell zu halten. Sollten Sie etwas bemerken das fehlerhaft ist: ein Rechtschreibfehler, schlechter Code, eine offenkundige Lüge, dann teilen Sie uns dies mit! Schreiben Sie uns eine Email an: book@sonatype.com. Die weitergehende Rolle dieses Buches ist abhängig von Ihrem Feedback. Wir sind interessiert daran zu erfahren, was funktioniert und was nicht. Wir würden gerne erfahren, sollte dieses Buch Informationen enthalten welche Sie nicht verstehen. Ganz besonders wollen wir erfahren, sollten Sie den Eindruck haben dises Buch wäre schrecklich. Positive und negative Rückmeldungen sind uns willkommen. Wir nehmen uns das Recht heraus nicht jeder Meinung zuzustimmen, aber in jedem Fall bekommen Sie eine gebührende Antwort. 3. Typographische Konventionen Dieses Buch verwendet die folgenden typographischen Konventionen: Kursiv Wird für wichtige Begriffe, Programm- und Dateinamen, URLs, Ordner und Verzeichnispfade und zur Hervorhebung/Einführung von Begriffen verwendet. Nichtproportonalschrift Wird für Programmcode verwendet (Java Klassennamen, -methoden, Variablen, Properties, Datentypen, Datenbank Elemente, und andere Codebeispiele welche in den Text eingestreut sind). Fette Nichtproportonalschrift Wird verwendet für Befehle welche Sie auf der Kommandozeile eingeben, sowie um neue Codefragmente in fortlaufenden Beispielen hervorzuheben. Kursive Nichtproportonalschrift Wird verwendet um Ausgabetext zu kennzeichnen. 4. Konventionen in Bezug auf Maven xvii
  • Vorwort Dieses Buch folgt gewissen Konventionen bezüglich Namensgebung und Schriftsatz bezüglich Apache Maven. Compiler Plugin Maven Plugins werden gross geschrieben. Goal create Maven Goal Namen werden in nichtproportionaler Schrift wiedergegeben. "plugin" Maven kreist stark um die Nutzung von Plugins, aber Sie werden keine umfassende Definition von Plugin in einem Wörterbuch finden. Dieses Buch benutzt den Terminus Plugin, da er einprägsam ist und auch weil er sich in der Maven Community so eingebürgert hat. Maven Lebenszyklus, Maven Standard Verzeichnis Layout, Maven Plugin, Projekt Objekt Modell Die Kernkonzepte von Maven werden im Text gross geschrieben, insbesondere wenn darauf Bezug genommen wird. Goal Parameter Ein Parameter eines Maven Goal wird in nichtproportionalschrift wiedergegeben. Phase compile Lebenszyklusphasen werden in nichtproportionalschrift wiedergegeben. 5. Danksagung Sonatype möchte sich insbesondere bei allen Mitwirkenden bedanken: die folgende Liste ist in keiner Weise abschliessend oder vollständig und alle haben mitgewirkt um die Qualität dieses Werkes zu verbessern. Vielen Dank an Mangalaganesh Balasubramanian, Bertrand Florat, Chad Gorshing, Ali Colabawala, Bozhidar Batzov sowie Mark Stewart. Besonderen Dank auch an Joel Costigliola für die Hilfe und Korrektur des Kapitels bezüglich Spring Web. Stan Guillory war schon xviii
  • Vorwort so etwas wie ein Teilautor, zählt man die grosse Zahl der Korrekturen und Verbesserungsvorschläge welcher er einbrachte. Vielen Dank Stan. Speziellen Dank auch an Richard Coatsby von Bamboo der die Rolle des einstweiligen Grammatik-Beraters aufgenommen hat. Vielen Dank an alle partizipierenden Teilautoren einschliesslich Eric Redmond. Vielen Dank auch an die folgenden Mitwirkenden welche Fehler entweder in Form einer Email oder zum zugehörigen Get Satisfaction Site gemeldet haben: Paco Soberón, Ray Krueger, Steinar Cook, Henning Saul, Anders Hammar, "george_007", "ksangani", Niko Mahle, Arun Kumar, Harold Shinsato, "mimil", "-thrawn-", Matt Gumbley. Sollten Sie Ihren Get Satisfaction Benutzernamen in dieser Liste aufgeführt sehen und würden diesen gerne durch Ihren Namen ersetzt wissen, so senden Sie uns eine kurze Mail an book@sonatype.com. xix
  • Chapter 1. Einführung von Apache Maven Es gibt zwar schon eine Reihe Online Veröffentlichungen zum Thema Apache Maven, aber was bis heute fehlt, ist eine umfassende, gut geschriebenen Einführung von Maven, welche zugleich als altgediente Referenz herhalten kann. Was wir hier versucht haben, ist eine solche gesamthafte Einführung gepaart mit einer umfassenden Referenz zu erstellen. 1.1. Maven ... Was ist das? Die Antwort auf diese Frage hängt ganz von Ihrer Perspektive ab. Die große Mehrheit der Benutzer wird Maven als "Build"-Werkzeug einordnen: ein Werkzeug um aus Quellcode einsatzfähige Artefakte (Programme) zu erzeugen. Build Ingenieure und Projektmanager mögen Maven als etwas umfassenderes sehen: ein (technisches) Projektmanagement-Tool. Wo liegt der Unterschied? Ein Build-Werkzeug wie Ant ist ausschließlich auf die Vorverarbeitung, Kompilierung, Prüfung und Verteilung ausgelegt. Ein Projekt-Management-Tool wie Maven liefert eine Obermenge von Funktionen welche in einem Build- und Paketier-Werkzeug zur Anwendung kommen. Neben der Bereitstellung von Build Funktionalitäten, bietet Maven auch die Möglichkeit Berichte/Auswertungen (Reports) anzufertigen, Websites zu generieren und den Kontakt zwischen den Mitgliedern einer Arbeitsgruppe/eines Teams zu ermöglichen. Hier eine formale Definition von Apache Maven: Maven ist ein Projektmanagement-Tool welches ein umfassendes Projektmodell beinhaltet und eine Reihe von Normen bereitstellt. Darüber hinaus verfügt es über einen definierten Projekt-Lebenszyklus, ein Abhängigkeits-Management-System sowie Logik, welche die Ausführung von Plugin-Goals in definierten Phasen eines Lebenszykluses ermöglicht. Wenn Sie Maven einsetzen, beschreiben Sie Ihr Projekt in einem genau definierten Projekt Objekt Modell, im weiteren Text POM genannt. Maven kann dann auf dieses Modell überspannende Regelwerke aus der Menge gemeinsam genutzter (oder auch massgeschneiderter) Plugins anwenden. Lassen Sie sich nicht von der Tatsache dass Maven ein "Projektmanagement" 1
  • Einführung von Apache Maven Werkzeug darstellt, erschrecken. Sollten Sie lediglich nach einem Build-Tool Ausschau gehalten haben, so wird Maven Ihnen dies ebenfalls bieten. In der Tat, die ersten Kapitel dieses Buches befassen sich mit dem häufigsten Anwendungsfall: Der Verwendung von Maven um Ihr Projekt zu kompilieren, testen und zu verteilen. 1.2. Konvention über Konfiguration Konvention über Konfiguration ist ein einfaches Konzept: Systeme, Bibliotheken und Frameworks sollten von vernünftige Standardwerten ausgehen, ohne unnötige Konfigurationen zu benötigen - Systeme sollten "einfach funktionieren". Gängige Frameworks wie Ruby on Rails und EJB3 haben damit begonnen, sich an diesen Grundsätzen zu orientieren. Dies in Reaktion auf die Komplexität der Konfiguration von Frameworks wie etwa der anfänglichen EJB 2.1 Spezifikationen. Das Konzept 'Konvention über Konfiguration' wird am Beispiel der EJB 3 Persistenz schön veranschaulicht: alles, was Sie tun müssen, um ein bestimmtes Bean persistent zu speichern ist, dieses mit einer @Entitiy-Annotation zu versehen. Das Framework übernimmt Tabellen-und Spaltennamen basierend auf den Namen der Klasse und den Namen der Attribute. Es besteht die Möglichkeit, mittels sogenannter 'Hooks' - wenn nötig - die gesetzten Standardwerte zu übersteuern. In den meisten Fällen werden Sie feststellen, dass der Einsatz der vom Framework bereitgestellten Namen zu einer schnelleren Projektdurchführung führt. Maven greift dieses Konzept auf und stellt vernünftige Standard-Verhalten für Projekte bereit. Ohne weitere Anpassungen, wird davon ausgegangen, dass sich Quellcode im Verzeichnis ${basedir}/src/main/java und Ressourcen sich im Verzeichnis ${basedir}/src/main/resources befinden. Von Tests wird davon ausgegangen, dass diese sich im Verzeichnis ${basedir}/src/test befinden, sowie ein Projekt standardmäßig ein JAR-Archive bereitstellt. Maven geht davon aus, dass Sie den kompilierten Byte-Code unter ${basedir}/target/classes ablegen wollen und anschließend ein ausführbares JAR-Archive erstellen, welches im Verzeichnis ${basedir}/target abgelegt wird. Zwar mag diese Lösung trivial anmuten, jedoch bedenken Sie bitte die Tatsache, dass die meisten Ant-basierten Projekte die Lage dieser Verzeichnisse in jedem Teilprojekt festlegen müssen. Die 2
  • Einführung von Apache Maven Umsetzung von Konvention über Konfiguration in Maven geht noch viel weiter als nur einfache Verzeichnis Standorte festzulegen, Maven-Core-Plugins kennen ein einheitliches System von Konventionen für die Kompilierung von Quellcode, der Paketierung sowie Verteilung der Artefakten, der Generierung von (Dokumentations-) Web-Seiten, und vielen anderen Prozessen. Die Stärke von Maven beruht auf der Tatsache, dass Maven mit einer 'vorbestimmten Meinung' ausgelegt ist: Maven hat einen definierten Lebenszyklus sowie eine Reihe von Plugins welche unter gemeinsamen Annahmen in der Lage sind, Software zu erstellen. Sollten Sie nach den Konventionen arbeiten, erfordert Maven fast keinen Aufwand - einfach Ihre Quellen in das richtige Verzeichnis legen - Maven kümmert sich um den Rest. Ein Nachteil der Verwendung von Systemen welche dem Grundsatz der Konvention über Konfiguration folgen ist, dass Endbenutzer oftmals die Gefahr sehen, dass sie gezwungen werden eine bestimmte Methode anzuwenden - einem bestimmten Ansatz folgen müssen. Zwar ist es sicherlich richtig, dass der Kern von Maven auf einigen wenigen Grundannahmen basiert, welche nicht in Frage gestellt werden sollten, dennoch kann man die meisten Vorgaben auf die jeweilig vorherrschenden Bedürfnisse anpassen und konfigurieren. So kann zum Beispiel die Lage des Projekt-Quellcodes sowie dessen Ressourcen konfiguriert werden, oder die Namen der resultierenden JAR-Archive angepasst werden. Durch die Entwicklung von massgefertigten Plugins, kann fast jedes Verhalten an die Bedürfnisse Ihrer Umgebung angepasst werden. Sollten Sie der Konvention nicht folgen wollen, ermöglicht Maven es Ihnen Voreinstellungen anzupassen um Ihren spezifischen Anforderungen Rechnung zu tragen. 1.3. Die gemeinsame Schnittstelle In der Zeit vor Maven, das eine gemeinsame Schnittstelle für den Build von Software bereitstellte, hatte gewöhnlich jedes einzelne Projekt eine Person, welche sich der Verwaltung des völlig eigenen Build Systems widmete. Entwickler mussten einige Zeit aufwenden, um neben der tatsächlichen Entwicklung herauszufinden, welche Eigenheiten beim Build einer neuen Software zu berücksichtigen waren, zu welcher sie beitragen wollten. Im Jahr 2001, gab es 3
  • Einführung von Apache Maven völlig unterschiedliche Build Konzepte/Systeme welche für den Build eines Projekts wie Turbine, POI oder Tomcat zur Anwendung kamen. Jedesmal, wenn ein neues Source Code-Analyse-Tool zur statischen Analyse von Quellcode veröffentlicht wurde, oder wenn jemand ein neues Unit-Test Framework herausgab welches man einsetzen wollte/sollte, musste man alles stehen und liegen lassen, um herauszufinden, wie dieses neue Werkzeug in das Projekt und dessen Build Umgebung einzupassen wäre. Wie konnte man Unit-Tests laufen lassen? Es gab tausend verschiedene Antworten. Dieses Umfeld war geprägt von tausenden endlosen Auseinandersetzungen bezüglich der 'richtigen' Werkzeuge und Build-Vorgehen. Das Zeitalter vor Maven war ein Zeitalter der Ineffizienz: das Zeitalter der "Build-Ingenieure". Heute setzen die meisten Open-Source-Entwickler bereits auf Maven, oder nutzen dieses um neue Software-Projekten aufzusetzen. Dieser Übergang ist weniger ein Wechsel von einem Build Werkzeug zu einem anderen, sondern viel mehr eine Entwicklung, hin zum Einsatz einer gemeinsamen Build-Schnittstelle für Projekte. So wie Software-Systeme modularer wurden, wurden Build Werkzeuge komplexer und die Zahl der (Teil-)Projekte wuchs in den Himmel. In der Zeit vor Maven, mussten Sie, wollten Sie etwa ein Projekt wie Apache ActiveMQ oder Apache ServiceMix aus Subversion auschecken und aus den Quellen erstellen, mit mindestens einer Stunde Aufwand rechnen, die Sie damit verbrachten herauszufinden, wie das Build-System des einzelnen Projekts funktionierte. Was waren die Voraussetzungen, um das Projekt zu builden? Welche Bibliotheken muss man herunterladen? Wo bekomme ich diese? Welche Targets kann ich im Build ausführen? Im besten Fall dauerte es ein paar Minuten um herauszufinden wie ein neues Projekt zu builden war, - und im schlimmsten Fall (wie die alte Servlet-API-Umsetzung des Jakarta-Projekts), war ein Projekt aufbauen so schwierig, das es mehrere Stunden dauerte nur um an einen Punkt zu gelangen, an dem ein neuer Mitarbeiter den Quellcode bearbeiten konnte und das Projekt kompilierte. Heutzutage machen Sie einen Projekt Check-Out und führen mvn install aus. Während Maven eine Reihe von Vorteilen einschließlich der Verwaltung von Abhängigkeiten und Wiederverwendung von gemeinsamer Logik zum Build durch Plugins bietet, ist der Hauptgrund für den Erfolg der, dass es gelungen ist eine 4
  • Einführung von Apache Maven einheitliche Schnittstelle für den Build von Software bereitzustellen. Wenn Sie sehen, dass ein Projekt wie Apache Wicket Maven verwendet, können Sie davon ausgehen, dass Sie in der Lage sein werden, Projekt ohne weiten Aufwand auszuchecken und aus den Quellen mittels mvn zu bauen und zu installieren. Sie wissen, wo der Zündschlüssel hinkommt, Sie wissen, dass das Gas-Pedal auf der rechten Seite und die Bremse auf der Linken ist. 1.4. Universelle Wiederverwendung durch Maven-Plugins Der Herzstück von Maven ist ziemlich dumm. Es weiss nicht viel mehr als ein paar XML-Dokumenten zu parsen sowie einen Lebenszyklus und ein paar Plugins zu verwalten. Maven wurde so konzipiert, dass die meiste Verantwortung auf eine Anzahl Maven-Plugins delegiert werden kann, welche den Lebenszyklus von Maven beeinflussen sowie gewisse Goals erreichen können. Der Großteil der Arbeit geschieht in Maven Goals. Hier passieren Dinge wie die Kompilierung von Quellcode, der Paketierung von Bytecode, die Erstellung von Websites, und jede andere Aufgabe die um einen Build zu erfüllen notwenig ist. Die Maven Installation nach dem Download von Apache weiß noch nicht viel über die Paketierung eines WAR-Archivs oder dem Ausführen eines JUnit-Tests; der größte Teil der Intelligenz von Maven ist in den Plugins enthalten und diese bezieht Maven aus dem Maven-Repository. In der Tat, das erste Mal, als Sie einen Aufruf wie z.B. mvn install auf Ihrer neuen Maven Installation absetzten, holte sich Maven die meisten der Core Maven Plugins aus dem zentralen Maven-Repository. Das ist mehr als nur ein Trick, um die Download-Größe von Maven in der Verteilung zu beeinflussen, dieses Verhalten ist es, das es erlaubt, durch die Erweiterung eines Plugins die Fähigkeiten des Builds zu verändern. Die Tatsache, dass Maven sowohl die Abhängigkeiten wie auch die Plugins aus einem Remote-Repository lädt ermöglicht universelle Wiederverwendung von Build-Logik. Das Maven Surefire Plugin ist das Plugin, welches für die Ausführung von Unit-Tests verantwortlich zeichnet. Irgendwo zwischen Version 1.0 und der aktuell 5
  • Einführung von Apache Maven verbreiteten Version hat jemand beschlossen, dieses neben der Unterstützung von JUnit auch um die Unterstützung für das TestNG Unit-Test Framework zu erweitern. Dies geschah in einer Art und Weise, dass die Abwärtskompatibilität erhalten blieb. Für die Benutzer des Surefire Plugin im Einsatz von JUnit3 Tests veränderte sich nichts, weiterhin werden die Tests kompiliert und ausgeführt, wie dies zuvor der Fall war. Aber Sie erhalten eine neue Funktionalität, denn sollten Sie nun Unit Tests nach dem TestNG Framework ausführen wollen, so haben Sie nun auch diese Möglichkeit, dank der Bemühungen der Maintainer des Surefire Plugin. Das Plugin erhielt auch die Fähigkeit annotierte JUnit4 Unit Tests zu unterstützen. Alle diese Erweiterungen wurden bereitgestellt, ohne dass Sie Maven aktiv aktualisieren, oder neue Software installieren mussten. Und ganz zentral, Sie mussten nichts an Ihrem Projekt ändern, abgesehen von einer Versionsnummer für ein Plugin in einem POM. Es ist dieser Mechanismus welche sich auf wesentlich mehr als nur das Surefire Plugin auswirkt: Projekte werden mittels einem Compiler Plugin kompiliert, mittels JAR-Plugin in JAR-Archive gepackt, es gibt es Plugins zum Erstellen von Berichten, Plugins zur Ausführung von JRuby und Groovy-Code, sowie Plugins zum Veröffentlichen von Websites auf Remote-Servern. Maven hat gemeinsame Aufgaben in Plug-Ins herausgelöst welche zentral gewartet sowie universell eingesetzt werden. Wann immer Veränderungen in einem Bereich des Buildens umgesetzt werden, wann immer ein neues Unit-Test Framework freigegeben oder ein neues Werkzeug bereit gestellt wird. Sie sind nicht gezwungen, diese Änderungen in ihren Build einzupflegen um dies zu unterstützen. Sie profitieren von der Tatsache, dass Plugins von einem zentral gewarteten Remote-Repository heruntergeladen werden. Das ist die wahre Bedeutung von universeller Wiederverwendung durch Maven Plugins. 1.5. Konzeptionelles Modell eines "Projekts" Maven unterhält ein (abstraktes) Modell eines Projekts, Sie verarbeiten also nicht nur Quellcode Dateien in Binärdateien, Sie entwickeln zugleich eine Beschreibung des Software-Projekts und ordnen diesem eine Reihe einzigartiger Koordinaten zu. Sie beschreiben die Attribute des Projekts: Wie ist die Projektlizenzierung 6
  • Einführung von Apache Maven geregelt? Wer entwickelt und trägt zum Projekt bei? Zu welchen anderen Projekten bestehen Abhängigkeiten? Maven ist mehr als nur ein "Build-Tool", es ist mehr als nur eine Verbesserung von Werkzeugen wie make™ und Ant™; es ist eine Plattform, umfasst eine neue Semantik bezüglich Software-Projekten und Software-Entwicklung. Diese Definition eines Modells für jedes Projekt ermöglicht Funktionen wie: Dependency Management / Abhängigkeits Verwaltung Da ein Projekt einzigartig mittels einer Gruppenkennung (groupId), Artefaktenkennung (artifactId) und Version (version) identifiziert wird, ist nun möglich, diese Koordinaten zur Abhängigkeitsverwaltung einzusetzen. Remote-Repositories Mit Blick auf das Abhängigkeitsmanagement ist es nun möglich diese im Maven Projekt Objekt Modell eingeführten Koordinaten einzusetzen um Maven Repositorien aufzubauen. Universelle Wiederverwendung der Build-Logik Plugins werden ausgerichtet auf das Projekt Objekt Model (POM) gebaut, sie sind nicht dafür ausgelegt auf bestimmte Dateien an bestimmten Orten zuzugreifen. Alles wird in das Modell abstrahiert, Plugin-Konfiguration und Anpassung geschieht im Modell. Tool Portabilität / Integration Werkzeuge wie Eclipse, NetBeans oder IntelliJ haben jetzt einen gemeinsamen Ort um auf Projektinformationen zuzugreifen. Vor dem Aufkommen von Maven, gab es für jede IDE eine spezifische Art und Weise in welcher diese Daten abgelegt wurden, was im Wesentlichen einem benutzerdefinierten POM entspricht. Maven standardisiert diese Beschreibung und während jeder IDE weiterhin deren eigenes Datenablagesystem unterhalten kann, lässt sich dieses nun leicht aus dem Modell heraus generieren. Einfache Suche und Filterung von Projekt-Artefakten Werkzeuge wie Nexus ermöglichen es Ihnen schnell und einfach Archive auf der Basis der im POM enthaltenen Daten zu indexieren und zu durchsuchen. 7
  • Einführung von Apache Maven Maven hat eine Grundlage für die Anfänge einer konsistenten semantischen Beschreibung eines Software-Projekts geschaffen. 1.6. Ist Maven eine Alternative zu XYZ? Natürlich, Maven stellt eine Alternative zu Ant dar, aber Apache Ant ist nach wie vor ein großartiges, weit verbreitetes Werkzeug. Es stellt seit Jahren den amtierende Champion der Java Build Tools, und Sie können auch weiterhin Ant-Build-Skripte einfach in Ihr Maven Projekt integrieren. Das ist auch eine gebräuchliche Art, Maven einzusetzen. Andererseits, jetzt da mehr und mehr Open-Source-Projekte sich auf Maven zubewegen und Maven als Projekt Management Plattform einsetzen, haben aktive Entwickler begonnen zu erkennen, dass Maven nicht nur die Aufgabe der Build Verwaltung unterstützt, sondern insgesamt zur Förderung einer gemeinsamen Schnittstelle zwischen Entwicklern und Software Projekte beiträgt. Maven hat viele Ausprägungen eine Plattform als nur eines Werkzeugs. Wenn Sie die Auffassung vertreten würden, Maven als eine Alternative zu Ant anzusehen, so würden Sie Äpfel mit Birnen vergleichen. Maven umfasst mehr als nur ein einen Werkzeugkasten um Projekte zu Builden. Dies ist das zentrale Argument, welches alle Vergleiche der Art Maven/Ant, Maven/Buildr, Maven/Gradle unerheblich macht. Maven ist nicht bestimmt von der Mechanik Ihres Build-Systems, es fusst nicht auf dem Skripting der verschiedenen Aufgaben Ihres Builds, sondern es geht weit mehr um die Förderung einer Reihe von Standards, einer gemeinsamen Schnittstelle, eines Leben-Zykluses, einem Standard-Repository Format, einem Standard-Verzeichnis Layout, usw. Es geht sicherlich nicht darum, welches POM-Format zum Einsatz kommt, ob XML, YAML, Ruby oder Maven. Maven bietet weit mehr als dies: Maven und bezieht sich auf viel mehr als nur das Werkzeug. Wenn dieses Buch von Maven spricht, so bezieht sich dies auf die Zusammenstellung von Software, Systemen und Standards, welche Maven unterstützt. Buildr, Ivy, Gradle alle diese Werkzeuge interagieren mit den Repository-Format welches zu definieren Maven beigetragen hat. Sie könnten genauso einen Build auf der Basis von Buildr einzig unter Zuhilfenahme von einem Tool wie Nexus aufbauen, Nexus wird in vorgestellt in Kapitel 16: Repository-Manager. 8
  • Einführung von Apache Maven Während Maven eine Alternative für viele dieser Tools darstellt, muss die Gemeinschaft der Open Source Entwickler darüber hinaus kommen können, Technologie als ein kontinuierliches Nullsummenspiel zwischen unfreundlichen Konkurrenten in einer kapitalistischen Wirtschaft zu sehen. Dies mag im Wettbewerb von Großunternehmen und Ihrem Bezug zu einander sinnvoll erscheinen, hat aber wenig Relevanz auf die Art und Weise, wie Open-Source-Communities arbeiten. Die Überschrift "Wer gewinnt? Ant oder Maven?" ist nicht sehr konstruktiv. Zwingen Sie uns, diese Frage zu beantworten, werden wir uns definitiv auf die Seite von Maven schlagen und darlegen, dass Maven eine gegenüber Ant überlegene Alternative darstellt die darüberhinaus die Grundlage für eine Technologie des Buildes legt. Gleichzeitig bitten wir zu berücksichtigen, dass die Grenzen von Maven in ständiger Bewegung sind, die Maven Gemeinde ist immerzu versucht, neue Wege zu mehr Ökumene, mehr Interoperabilität, größerer Gemeinschaft zu schaffen. Die Kernkomponenten von Maven sind der deklarative Build, die Abhängigkeitsverwaltung (Dependency Management), Repository Verwaltung und breite Wiederverwendbarkeit durch den Einsatz von Plugins. Es sind aber die spezifische Inkarnationen dieser Ideen zu einem gegebenen Zeitpunkt weniger wichtig, als das Ziel dass die Open-Source-Community in Zusammenarbeit zur Verringerung der Ineffizienz von "Enterprise Scale Builds" beizutragen. 1.7. Ein Vergleich von Maven und Ant Während der vorangegangene Abschnitt Ihnen verständlich gemacht haben sollte, dass die Autoren dieses Buches keinerlei Interesse an der Schaffung oder Vertiefung einer Fehde zwischen Apache Ant und Apache Maven haben, ist uns die Tatsache bewusst, dass die meisten Organisationen eine Entscheidung zwischen Ant und Maven treffen (müssen). In diesem Abschnitt werden wir daher diese beiden Werkzeuge gegenüberstellen. Ant läuft beim Build-Prozess zu seiner vollen Grösse auf: Es ist ein Build-System nach dem Vorbild von make mit Targets und Abhängigkeiten. Jeder Target besteht aus einer Reihe von Anweisungen, die in XML beschrieben werden. Es gibt eine Task copy, eine Task javac sowie eine Task JAR. Wenn Sie Ant benutzen, müssen 9
  • Einführung von Apache Maven Sie Ant mit der speziellen, spezifischen Anweisungen für die Zusammenstellung und dem Packaging Ihres Projektes aufrufen. Sehen Sie sich das folgende Beispiel einer einfachen Ant build.xml-Datei an: Example 1.1. Eine einfache Ant build.xml-Datei <project name="my-project" default="dist" basedir="."> <description> simple example build file </description> <!-- set global properties for this build --> <property name="src" location="src/main/java"/> <property name="build" location="target/classes"/> <property name="dist" location="target"/> <target name="init"> <!-- Create the time stamp --> <tstamp/> <!-- Create the build directory structure used by compile --> <mkdir dir="${build}"/> </target> <target name="compile" depends="init" description="compile the source " > <!-- Compile the java code from ${src} into ${build} --> <javac srcdir="${src}" destdir="${build}"/> </target> <target name="dist" depends="compile" description="generate the distribution" > <!-- Create the distribution directory --> <mkdir dir="${dist}/lib"/> <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file --> <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/> </target> <target name="clean" description="clean up" > <!-- Delete the ${build} and ${dist} directory trees --> <delete dir="${build}"/> <delete dir="${dist}"/> </target> </project> An diesem einfachen Beispiel eines Ant Skriptes, können Sie sehen, wie genau man definieren muss, was man von Ant erwartet. Ein Target ist javac, hier werden 10
  • Einführung von Apache Maven die Quelldateien in Binärdaten verarbeitet, dabei ist es wichtig, dass man genau angibt, wo die Quell- sowie Zieldateien abgelegt werden (/src/main/java resp. /target/classes). Ebenfalls müssen Sie Ant anweisen, aus den resultierenden Dateien ein JAR-Archiv zu erstellen. Während einige der neueren Entwicklungen dazu beitragen, dass Ant weniger prozedural orientiert arbeitet, ist die Entwickler-Erfahrung dennoch die, einer in XML abgefassten prozeduralen Programmiersprache. Vergleichen Sie das Vorgehen von Maven mit dem vorigen Beispiel: In Maven, um ein JAR-Archive aus einer Reihe von Java Quellcode Dateien zu erstellen, ist alles, was Sie tun müssen, ein einfaches POM (pom.xml Datei) zu generieren. Platzieren Sie den Quellcode in ${basedir}/src/main/java und führen Sie dann mvn install von der Befehlszeile aus . Das Beispiel der Maven pom.xml Datei, welches zum selben Ergebnis führt wie zuvor das Ant-Skript sehen Sie unten. Example 1.2. Muster einer Maven pom.xml-Datei <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>my-project</artifactId> <version>1.0</version> </project> Das ist alles was Sie in der zugehörigen pom.xml Datei benötigen. Der Aufruf von mvn install wird die Resourcen sowie Quelldateien kompilieren, bestehende Unit-Tests ausführen, JAR-Archive erstellen und dieses in einem lokalen Repository anderen Projekten zur Verfügung stellen. Ohne die pom.xml Datei abzuändern, können Sie durch den Aufruf von mvn site im Zielverzeichnis eine index.html wiederfinden, welche den Link zu einer Dokumentations-Web-Seite auf der Basis von JavaDoc sowie einigen Standard Reports bereitstellt. Zugegeben, dies ist das einfachste, mögliche Beispiel-Projekt. Ein Projekt, das nur Quellcode enthält und ein JAR-Archive erzeugt. Ein Projekt welches der Maven Konvention unterliegt und keinerlei Abhängigkeiten oder Anpassungen berücksichtigt. Sobald wir die Verhaltensweisen der Plugins anpassen wird unsere pom.xml Datei wachsen. In den grössten Projekten werden Sie komplexe Maven 11
  • Einführung von Apache Maven POMs finden, welche eine grosse Anzahl Plugin-Anpassungen sowie Abhängigkeitserklärungen enthalten. Aber, selbst wenn das POM Ihres Projekts erheblich wächst, enthält dies eine ganz andere Art von Daten und Informationen denn das Skript einer entsprechenden Ant-Datei. Maven POMs enthalten Deklarationen wie: "Dies ist eine JAR-Projekt", und "Der Quellcode ist unter /src/main/java abgelegt". Wohingegen Ant-Build-Dateien explizite Anweisung enthalten: "Dies ist ein Projekt", "Die Quelle ist in /src/main/java", "javac gegen dieses Verzeichnis ausführen", "Ablegen der Ergebnisse in /target/classses", "Erstellen eines JAR-Archives aus der. ...". Wo Ant genauen Prozess-Anweisungen bedurfte, gab es etwas innerhalb des Maven Builds, das einfach "wusste", wo der Quellcode zu finden ist und wie dieser zu verarbeiten ist. Die Unterschiede zwischen den Ant und Maven in diesem Beispiel sind: Apache Ant • Ant kennt keine formalen Konventionen bezüglich einer gemeinsamen Projekt Verzeichnis-Struktur. Sie müssen Ant genau sagen, wo sich die Quelldateien befinden und wo die Ausgabe abgelegt werden soll. Informelle Konventionen haben sich im Laufe der Zeit herausgebildet, aber diese wurden nicht im Produkt kodifiziert. • Ant ist Prozedural, Sie müssen Ant genau sagen, was zu tun ist und wann dies zu tun. Sie mussten definieren: erst kompilieren, dann kopieren, dann paketieren. • Ant kennt keinen Lebenszyklus, Sie mussten Targets definieren und deren Abhängigkeiten. Sie mussten die Abfolge der Schritte manuell für jeden Target festlegen. Apache Maven • Maven arbeitet nach Konventionen. Da Sie diese beachteten, wusste Maven wo Ihr Quellcode sich befand. Es stellt den Bytecode unter target/classes und es erzeugte ein JAR-Archive in /target. 12
  • Einführung von Apache Maven • Maven ist deklarative. Alles was Sie tun musste, war eine pom.xml Datei zu erzeugen sowie Ihre Quelle im Standard-Verzeichnis ablegen. Maven kümmerte sich um den Rest. • Maven kennt einen Lebenszyklus, welchen Sie mit dem Aufruf von mvn install angestossen haben. Dieser Aufruf bringt Maven dazu, eine Abfolge von Schritten abzuarbeiten, bis das Ende des Lebenszykluses erreicht ist. Als Nebeneffekt dieser Reise durch den Lebenszyklus führt Maven eine Reihe von Standard-Plugin-Goals aus, diese erledigen Arbeiten wie z.B. kompilieren oder dem Erstellen eines JAR-Archives. Maven hat eine eingebaute Intelligenz bezüglich der gemeinsamen Projektaufgaben in Form von Maven-Plugins. Sollten Sie Unit Tests ausführen wollen, ist alles was Sie tun müssen, die Tests zu schreiben und unter ${basedir}/src/test/java abzulegen, führen Sie eine Test bezogene Abhängigkeit zu entweder TestNG oder JUnit ein, und starten Sie mvn test. Sollten Sie anstelle eines JAR-Archives eine Web Anwendung erstellen, so ist alles was Sie tun müssen den Projekttyp auf "war" umstellen und Ihr Applikationsverzeichnis (docroot) auf ${basedir}/src/main/webapp setzen. Klar, können Sie all dies auch mit Ant bewerkstelligen, aber Sie werden alle Anweisungen von Grund auf festhalten. In Ant würden Sie erst einmal herausfinden, wo die JUnit-JAR-Datei sich befindet, dann müssten Sie einen Klassenpfad erstellen welcher auch die JUnit-Archive Datei enthält, und anschliessend Ant mitteilen wo sich der Quellcode befindet. Schliesslich würden Sie einen Target erstellen die Quellen der Test Dateien zu kompilieren und zu guter Letzt die Tests mit JUnit auszuführen. Ohne Unterstützung von Technologien wie antlibs und Ivy (und auch mit diesen unterstützenden Technologien) stellt sich bei Ant das Gefühl eines prozeduralen Build Ablaufs ein. Eine effiziente Zusammenstellung einiger Maven POMs in einem Projekt welches sich an die Maven-Konventionen hält erzeugt überraschend wenig XML im Vergleich zur Ant-Alternative. Ein weiterer Vorteil von Maven ist die Abhängigkeit von weithin benutzten Maven-Plugins. Heutzutage benutzt jeder das Maven Surefire Plugin für Unit-Tests, sobald jemand ein weiteres Unit Test Framework einbindet, können Sie neue Fähigkeiten in Ihrem eigenen Build durch 13
  • Einführung von Apache Maven die einfache Änderung der Version eines einzelnen, bestimmten Maven Plugins nutzen. Die Entscheidung über die Verwendung Maven oder Ant ist heutzutage nicht binär. Ant hat noch immer einen Platz in in komplexen Builds. Wenn Ihr aktueller Build-Prozess einige sehr individuelle Prozesse enthält, oder wenn Sie etliche Ant-Skripte geschrieben haben um einen bestimmten Prozess in einer bestimmten Weise, die sich nicht an die Normen Maven hält, abzudecken, können Sie diese Skripte dennoch mit Maven abarbeiten. Ant wird als Kernkomponente in Form eines Plugins von Maven zur Verfügung gestellt. Benuzerdefinierte Maven-Plugins können in Ant geschrieben werden und Maven Projekte können so konfiguriert werden, das Ant-Skripte innerhalb des Maven-Projekt-Lebenszykluses abgearbeitet werden. 1.8. Zusammenfassung Diese Einführung wurde absichtlich kurz gehalten. Wir haben Ihnen umrissen, was Maven ist und wie es zu einem guten Build-Prozess beiträgt, sowie über die Zeit hinweg diesen verbessern hilft. Im nächste Kapitel werden wir am Beispiel eines einfachen Projektes aufzeigen welche phänomenalen Aufgaben mit dem kleinstmöglich Aufwand an Konfiguration geleistet werden können. 14
  • Chapter 2. Installieren und Ausführen von Maven Dieses Kapitel enthält detaillierte Anweisungen für die Installation von Maven auf einer Reihe von unterschiedlichen Plattformen. Statt ein bestimmtes Mass der Vertrautheit mit der Installation von Software und dem Einstellen von Umgebungsvariablen vorauszusetzen, haben wir uns entschieden, so detailliert wie möglich zu sein, um Probleme welche von unvollständigen Installation herrühren von vornherein zu minimieren. die einzige Voraussetzung welche angenommen wird ist die, einer bestehenden, geeigneten und vollständigen Java Umgebung, eines Java Development Kit (JDK). Sollten Sie nur Interesse an der Installation haben, so können Sie nach dem Lesen der Abschnitte "2.2 Herunterladen von Maven" sowie "2.3 Installation von Maven" zu den weiterführenden Kapiteln des Buches übergehen. Wenn Sie an einer detaillierten Beschreibung interessiert sind, so gibt Ihnen dieses Kapitel die entsprechenden Informationen sowie eine Erklärung der Bedeutung der Apache Software License, Version 2.0. 2.1. Überprüfen der Java-Installation Obschon Maven auch auf Java 1.4 unterstützt ist, geht dieses Buch davon aus, dass sie mindestens auf Java 5 aufsetzen. Wählen Sie den neusten stabilen JDK welcher für Ihre Plattform erhältlich ist. Alle in diesem Buch aufgeführten Beispiele sind unter Java 5 oder Java 6 lauffähig. % java -version java version "1.6.0_02" Java(TM) SE Runtime Environment (build 1.6.0_02-b06) Java HotSpot(TM) Client VM (build 1.6.0_02-b06, mixed mode, sharing) Maven ist unter allen Java-kompatibel zertifiziert Entwicklungsumgebungen, sowie einigen weiteren, nicht-zertifizierten Implementierungen von Java lauffähig. Die Beispiele in diesem Buch wurden gegen die offizielle Java Development Kit Implementation wie diese von Sun Microsystems Webseite (http://java.sun.com) 15
  • Installieren und Ausführen von Maven heruntergeladen werden kann, geschrieben und getestet. Sollten Sie mit einer Linux-Distribution arbeiten, müssen Sie eventuell die Java Entwicklungsumgebung selbst herunterladen und installieren. Bitte stellen Sie sicher, dass diese auch zum Einsatz kommt: durch Aufruf von "java -fullversion ". Nun, da Sun Microsystem den Quellcode von Java unter einer Open Source Lizenz offengelegt hat, tritt diesbezüglich hoffentlich schon bald eine Besserung ein und es werden auch auf puristisch Linux-Distributionen die originalen Sun JRE sowie JDKs installiert. Bis zu diesem Tag müssen Sie selbst Hand anlegen, Java herunterladen und installieren. 2.2. Herunterladen von Maven Sie können Maven von der Apache Maven-Projekt-Website unter http://maven.apache.org/download.html herunterladen. Beim Herunterladen von Maven, wählen Sie die neuste Version von Apache Maven vom Website. Die aktuell neuste Version zur Zeit als dieses Buch geschrieben wurde war Maven 2.0.9. Sollten Sie die Apache Software Lizenz noch nicht kennen, so machen sie sich - vor dem Einsatz des Produktes - mit deren Bedingungen vertraut. Weitergehende Informationen diesbezüglich finden Sie in Abschnitt "2.8 Die Apache Software Lizenz". 2.3. Installation von Maven Die Unterschiede zwischen den Betriebssystemen wie Mac OS X und Microsoft Windows sind gross, daneben gibt auch subtile Unterschiede zwischen den verschiedenen Versionen von Windows oder Varianten von Linux zu beachten. Zum Glück ist der Prozess der Installation von Maven auf all diesen Betriebssystemen relativ einfach und schmerzlos. Die folgenden Abschnitte geben einen Überblick über die empfohlene Best-Practice der Installation von Maven unter einer Vielzahl von Betriebssystemen. 16
  • Installieren und Ausführen von Maven 2.3.1. Maven Installation unter Mac OSX Am Anfang benötigen Sie zunächst die Software, diese bekommen Sie unter: http://maven.apache.org/download.html. Laden Sie die aktuelle Version von Maven in einem für Sie passenden Format herunter. Wählen Sie einen geeigneten Platz als "Heimat" und entpacken Sie das Archiv dort. Wenn Sie zum Beispiel das Verzeichnis /usr/local/apache-maven-2.0.9 gewählt haben, so macht es Sinn einen symbolischen Link zu erzeugen. Dies erleichtert die Arbeit und erspart die Anpassung von Umgebungsvariablen im Falle eines Upgrades der Version. /usr/local % cd /usr/local /usr/local % ln -s apache-maven-2.0.9 maven /usr/local % export M2_HOME=/usr/local/maven /usr/local % export PATH=${M2_HOME}/bin:${PATH} Ist Maven einmal installiert ist, müssen Sie noch ein paar Schritte unternehmen, damit Maven korrekt funktionieren kann. Zu aller erst sollten Sie das bin-Verzeichnis Ihrer Maven Distribution (in diesem Beispiel /usr/local/Apache-Maven/bin) in den Befehlspfad aufnehmen. Ausserdem sollten Sie noch die Umgebungsvariable M2_HOME auf das Maven-Top-Level-Verzeichnis setzen (in diesem Beispiel /usr/local/Maven). Note Installations-Anweisungen sind die gleichen für OSX Tiger und OSX Leopard. Es wurde berichtet, dass Maven 2.0.6 mit einer Vorschau-Version von XCode ausgeliefert wird. Sollten Sie XCode installiert haben, führen Sie mvn von der Befehlszeile aus, und überprüfen Sie die Verfügbarkeit. XCode installiert Maven unter /usr/share/Maven. Wir empfehlen die Installation der jüngsten Version von Maven 2.0.9, da hier eine Reihe von Bugfixes und Verbesserungen eingeflossen sind. Um die Konfiguration beider Umgebungsvariablen bei jedem Start zu setzen, ist es notwendig diese in einem Skript abzulegen welches bei jedem Systemstart abgearbeitet wird. Fügen Sie daher die folgenden Zeilen der Datei .bash_login an: export M2_HOME=/usr/local/maven 17
  • Installieren und Ausführen von Maven export PATH=${M2_HOME}/bin:${PATH} Mit dem Anfügen dieser Zeilen wird Maven Ihrer Umgebung zugefügt, und Sie bekommen die Möglichkeit Maven von der Kommandozeile zu starten. Note Diese Installationsanleitung geht davon aus, dass Sie unter der bash-Shell arbeiten. 2.3.1.1. Maven Installation unter Mac OSX mit MacPorts Sollten Sie MacPorts einsetzen, so können Sie Maven installieren, indem Sie die folgenden Befehle auf der Kommandozeile eingeben: $ sudo port install maven2 Password: ****** ---> Fetching maven2 ---> Attempting to fetch apache-maven-2.0.9-bin.tar.bz2 from http://www.apache.org/dist/mav ---> Verifying checksum(s) for maven2 ---> Extracting maven2 ---> Configuring maven2 ---> Building maven2 with target all ---> Staging maven2 into destroot ---> Installing maven2 2.0.9_0 ---> Activating maven2 2.0.9_0 ---> Cleaning maven2 Für weiterführende Informationen bezüglich der Maven 2 Portierung verweisen wir auf Portfile. Bezüglich weiterführender Informationen zu MacPorts und wie dies zu installieren ist: MacPorts Projekt Seite (Engl.). 2.3.2. Maven Installation unter Microsoft Windows Die Installation von Maven unter Windows ist sehr ähnlich wie jene unter Mac OSX, die wichtigsten Unterschiede finden sich bezüglich des Installationsortes und der Festlegung der Umgebungsvariablen. Dieses Buch geht von einem Maven Installations-Verzeichnis von "C:Program FilesMaven-2.0.9" aus, aber es wird keinen Unterschied machen, sollten Sie Maven in ein anderes Verzeichnis installieren, solange Sie die Umgebungsvariablen entsprechend anpassen. Nach 18
  • Installieren und Ausführen von Maven dem Entpacken des Maven Installationspaketes müssen Sie zwei Umgebungsvariablen - PATH und M2_HOME - setzen. Um dies zu bewerkstelligen, geben Sie auf der Kommandozeile folgende Befehlen ein: C:Userstobrien > set M2_HOME=c:Program Filesapache-maven-2.0.9 C:Userstobrien > set PATH=%PATH%;%M2_HOME%bin Das Setzen der Umgebungsvariablen in der Kommando-Zeile erlaubt Ihnen die Ausführung von Maven während Ihrer aktuellen Sitzung und zwingt Sie diese bei jedem Neustart erneut zu setzen. Sie sollten diese Variablen daher den Systemeinstellungen zufügen, öffnen Sie hierzu das Menue "Systemeinstellungen" von dort "System" und wählen Sie "". 2.3.3. Maven Installation unter Linux Zur Installation von Maven auf einer Linux-Maschine, folgen Sie der Vorgehensweise in Abschnitt 2.3.2: "Installation von Maven auf Mac OSX". 2.3.4. Maven Installation unter FreeBSD oder OpenBSD Zur Installation von Maven auf einem FreeBSD oder OpenBSD Maschine, folgen Sie der Vorgehensweise in Abschnitt 2.3.2: "Installation von Maven auf Mac OSX" 2.4. Testen einer Maven Installation Ist Maven erst einmal installiert, können Sie die Version überprüfen, indem Sie mvn -v in der Befehlszeile eingeben. Bei einer korrekten Installation von Maven sollte eine Ausgabe wie folgt erscheinen: $ mvn -v Maven 2.0.9 Sollten Sie diese Ausgabe erhalten, wissen Sie dass Maven verfügbar und bereit ist um eingesetzt zu werden. Wenn nicht, hat Ihr Betriebssystem den mvn Befehl nicht gefunden. Überprüfen Sie ob die PATH-Umgebungsvariable sowie M2_HOME 19
  • Installieren und Ausführen von Maven (Gross/Kleinschreibung beachten!) richtig gesetzt sind. 2.5. Spezielle Installationshinweise Das herunter zu ladende Maven Paket hat eine Grösse von etwa 1,5 MiB [1], diese Schlankheit hat es erreicht, da der Kern von Maven mit dem Ziel entwickelt wurde, Plugins und Abhängigkeiten von einem Remote-Repository dynamisch und auf Abruf zuzuladen. Wenn Sie Anfangen mit Maven zu arbeiten, lädt Maven zunächst Plugins in ein lokales Repository. Dieses wird im Abschnitt 2.5.1 "Benutzerdefinierte Konfiguration und -Repository" beschrieben. Im Fall, dass Sie neugierig sind, lassen Sie uns einen Blick darauf werfen, was sich im Maven Installationsverzeichnis befindet. 1 /usr/local/maven $ ls -p1 LICENSE.txt NOTICE.txt README.txt bin/ boot/ conf/ lib/ Die Datei LICENSE.txt enthält die Software-Lizenz für Apache Maven. Diese Lizenz ist in einigen Details in Abschnitt 2.8 "Über die Apache Software Lizenz" beschrieben. NOTICE.txt enthält einige Hinweise zu Abhängigkeiten von Bibliotheken, von denen Maven abhängig ist. README.txt enthält Installationsanweisungen. Das Verzeichnis /bin enthält das "mvn"-Skript, mit welchem Maven aufgerufen wird. /boot enthält eine JAR-Datei (classworlds-1.1.jar), welche den Class-Loader unter welchem Maven ausgeführt wird bereitstellt. /conf enthält eine Datei settings.xml, diese wird eingesetzt um globale Einstellungen festzulegen um das Verhalten von Maven zu definieren und anzupassen. Sollten Sie Maven Ihren Gegebenheiten anpassen 1 Haben Sie je eine 200-GB-Festplatte gekauft, nur um herauszufinden, dass diese weniger als 200 GiB fasst, wenn Sie diese installiert haben? Computer verstehen Gibibytes, aber Einzelhändler verkaufen Produkte mit Gigabyte. MiB steht für die Mebibyte und ist definiert als 2^20 oder 1024^2. Diese binäre Präfix Standards sind von der IEEE, CIPM, und und IEC übernommen. Für weitere Informationen über Kibibytes, Mebibytes, Gibibytes und Tebibytes siehe auch http://en.wikipedia.org/wiki/Mebibyte. 20
  • Installieren und Ausführen von Maven müssen, hat es sich herausgebildet, dass man die Einstellungen der settings.xml durch eine Datei settings.xml im Verzeichnis ~/.m2 übersteuert. /lib enthält eine einzige JAR-Datei (Maven-2.0.9-uber.jar), welche die Hauptimplementierung (Core) von Maven enthält. Note Mit der Ausnahme einer shared Unix Installation, sollten Sie auf die Anpassung der settings.xml unter M2_HOME/conf vermeiden. Die Anpassung der globalen settings.xml-Datei der eigentlichen Maven Installation ist gewöhnlich unnötig und erschwert den upgrade zwischen den Maven Installationen unnötigerweise, da Sie daran denken müssen, die angepasste settings.xml wieder von der bisherigen zur neuen Maven Installation zurückzukopieren. Sollten Sie dennoch gezwungen sein, die settings.xml anzupassen, so sollten Sie die 'persönliche' settings.xml unter ~/.m2/settings.xml anpassen. 2.5.1. Benutzerdefinierte Konfiguration und-Repository Sobald Sie beginnen sich mit Maven ausgiebig auseinander zu setzen, werden Sie feststellen, dass Maven einige lokale, benutzerspezifische Konfigurationsdateien und ein lokales Archiv in Ihrem Home-Verzeichnis angelegt hat. Unter ~/.m2 befinden sich: ~/.m2/settings.xml Eine Datei mit der benutzerspezifischen Konfiguration der Authentifizierung, Repositorien und anderen Informationen, um das Verhalten von Maven zu steuern. ~/.m2/repository/ Dieses Verzeichnis enthält Ihr lokales Maven-Repository. Wenn Sie eine Abhängigkeit von einem Remote-Maven-Repository herunterladen, so wird diese in ihrem lokalen Repository zwischengespeichert. 21
  • Installieren und Ausführen von Maven Note Unter Unix (und OSX), bezeichnet die Tilde "~" Ihr Home-Verzeichnis (d.h. ~/bin bezieht sich auf /home/tobrien/bin). Unter Windows, werden wir uns ebenfalls mit ~ auf Ihr Home-Verzeichnis beziehen. Unter Windows XP ist Ihr Home-Verzeichnis C:Dokumente und Einstellungen<Benutzername>, unter Windows Vista wird Ihr Home-Verzeichnis mit C:Users<Benutzername> bezeichnet. Im Folgenden bitten wir Sie entsprechend Ihres Systems den Pfad anzupassen. 2.5.2. Aktualisieren einer Maven-Installation So Sie unter Unix/OSX arbeiten und die Installation wie oben unter Abschnitt 2.3.2 "Installation von Maven auf Mac OSX" oder Abschnitt 2.3.3 "Installation von Maven auf Linux" beschrieben ausgeführt haben, so ist es ein Kinderspiel eine Maveninstallation zu Aktualisieren: Installieren Sie einfach die neuere Version von Maven (/usr/local/maven-2.future) neben der bestehenden Version von Maven (/usr/local/maven-2.0.9). Ändern Sie dann den symbolischen Link /usr/local/Maven von /usr/local/maven-2.0.9/ auf /usr/local/maven-2.future. Fertig! Da Ihre M2_HOME Variable bereits auf /usr/local/Maven zeigt, müssen keine weiteren Anpassungen ausgeführt werden. Unter Windows, entpacken Sie einfach die neue Version von Maven unter C:Program FilesMaven-2.future und aktualisieren Sie Ihre M2_HOME Variable über die Systemsteuerungen. Note Sollten Sie die globale Datei settings.xml von /M2_HOME/conf angepasst haben, so müssen Sie diese Änderungen in das /conf-Verzeichnis der neuen Installation Übertragen. 22
  • Installieren und Ausführen von Maven 2.5.3. Upgrade einer Maven-Installation von Maven 1.x auf Maven 2.x Beim Upgrade von <term>Maven 1</term> auf <term>Maven 2</term> werden Sie eine ganz neue POM Struktur sowie ein neues Repository benutzen. Haben Sie bereits ein benutzerspezifisches Maven 1 Repository für massgeschneiderte Artefakte erstellt, so können Sie mit Hilfe des Nexus Repository Manager diese so darstellen, dass sie von Maven 2 Anwendungen benutzt werden kann. Bezüglich weiterer Informationen zum Nexus Repository Manager verweisen wir auf Kapitel 16: Repository Manager. Zusätzlich zum Einsatz von Werkzeugen wie Nexus, können Sie Referenzen auf Repositorien so einstellen, dass diese das vorgängige Format unterstützen. Bezüglich weiterer Informationen zum Konfiguration einer Referenz auf ein Maven 1 Repository siehe auch Abschnitt A.2.8: "Repositorien". Sollten Sie eine Anzahl bestehender Maven 1 Projekte unterhalten, interessieren Sie sich bestimmt für das Maven One Plugin. Das Plugin wurde entworfen, um Projekte von Maven 1 auf Maven 2 zu portieren. Ein bestehendes Maven 1 Projekt können sie portieren in dem Sie das Goal one:convert wie folgt aufrufen: $ cd my-project $ mvn one:convert one:convert wird eine Maven 1 project.xml Datei einlesen und in eine pom.xml-Datei konvertieren welches zu Maven 2 kompatibel ist. Sollten Sie allerdings ein Maven 1 Build Skript mittels Jelly angepasst haben, so müssen Sie andere Wege gehen. Während Maven 1 Jelly basierte Anpassungen favorisierte, ist der bevorzugte Weg der Anpassung von Maven 2 Skripten mittels benutzerdefinierten Plugins, skriptbaren Plugins oder dem Maven Antrun Plugin. Das Wichtigste was es bei der Migration von Maven 1 auf Maven 2 zu beachten gilt ist, dass Maven 2 auf einem grundsätzlich verschiedenen Unterbau aufbaut. Maven 2 setzt auf Lebenszyklusphasen und definiert das Verhältnis zwischen Plugins auf eine andere Art und Weise. Sollten Sie eine derartige Migration 23
  • Installieren und Ausführen von Maven vornehmen, so müssen Sie Zeit einplanen, um sich mit den Unterschieden der zwei Versionen vertraut machen. Obschon es nahe liegt sich mit der neuen POM Struktur auseinanderzusetzen, sollten Sie sich zunächst auf das Lebenszykluskonzept konzentrieren. Sobald Sie das Lebenszykluskonzept verstanden haben, steht Ihnen nichts mehr im Weg, Maven zur vollen Stärke einzusetzen. 2.6. Maven De-Installieren Die meisten Installationsanleitungen für Maven beinhalten das Entpacken des Maven Bündel in ein Verzeichnis und das Setzen einiger Umgebungsvariablen. Sollten Sie also Maven von Ihrem Arbeitsplatz entfernen müssen, so müssen Sie lediglich das Maven Installationsverzeichnis löschen und die Umgebungsvariablen entfernen. Sie sollten ebenfalls das Verzeichnis ~/.m2 entfernen, da dies Ihr lokales Repository enthält. 2.7. Hilfe bekommen beim Arbeiten mit Maven Während dieses Buch das Ziel hat, ein umfassendes Nachschlagewerk zu sein, wird es Themen geben welche wir verpasst haben oder besondere Umstände und Situationen sowie Tipps, welche nicht abgedeckt werden. Der Kern von Maven ist sehr einfach, die eigentliche Arbeit geschieht in den Maven Plugins; und deren gibt es zu viele um alle in diesem Buch abdecken zu können. Sie werden zwangsläufig auf Schwierigkeiten stoßen und Eigenheiten finden, welche in diesem Buch nicht berücksichtigt wurden. In diesen Fällen empfehlen wir Ihnen die Suche nach Antworten an folgenden Orten: http://maven.apache.org Dies sollte der erste Ausgangspunk einer jeden Suche sein. Der Maven-Website enthält eine Fülle von Informationen und Dokumentation. Jedes offizielle Plugin hat ein paar Seiten Dokumentation und es gibt eine Reihe von "Quick Start"-Dokumenten, die zusätzlich zum Inhalt dieses Buches hilfreich sein 24
  • Installieren und Ausführen von Maven werden. Während die Maven Website eine Fülle von Informationen enthält, kann es aber auch frustrierend, verwirrend und überwältigend sein, dort zu suchen. Die Seite enthält ein spezielles Google-Suchfeld welches die wichtigsten Informationsquellen bezüglich Maven durchsucht. Diese Suchmaske liefert bessere Ergebnisse als eine generische Google-Suche. Maven User Mailing List Die Maven User Mailing List ist der Ort für Nutzer um Fragen zu stellen. Bevor Sie sich mit einer Frage an die User Mailing List wenden, durchsuchen Sie bitte vorangegangene Diskussionen zum betreffenden Thema. Es zeugt von schlechtem Stil eine Frage stellen, die bereits zuvor gestellt wurde, ohne vorher zu prüfen, ob in den Archiven bereits eine Antwort schlummert. Es gibt eine Reihe von nützlichen Mailing-List Archiv Browsern um Ihnen die Arbeit zu erleichtern. Für unsere Recherchen hat sich Nabble als am nützlichsten erwiesen. Sie können die User Mailing List Archive hier finden: http://www.nabble.com/Maven---Users-f178.html. Eine Beschreibung, wie Sie der User Mailingliste beitreten können finden sie hier http://maven.apache.org/mail-lists.html. http://www.sonatype.com Sonatype unterhält eine Online-Kopie dieses Buches und andere Tutorials im Zusammenhang mit Apache Maven. Note Trotz der größten Bemühungen von einigen sehr engagierten Maven Mitwirkenden, ist die Maven Website schlecht organisiert und voller unvollständiger (und manchmal) irreführender Auszügen und Dokumentationen. In der Gemeinschaft der Maven Entwickler gibt es leider einen Mangel gemeinsamer Standards der Plugin-Dokumentation. Einige Plugins sind sehr gut dokumentiert, während andere nicht einmal die elementarsten Anweisungen für den Gebrauch ausweisen. Oft der erfolgversprechendste Ansatz die Suche nach einer Lösung in den Archiven der User Mailing Lists. Und wenn Sie wirklich helfen wollen unterbreiten Sie einen Patch der Maven Website (oder diesem Buch). 25
  • Installieren und Ausführen von Maven 2.8. Das Maven Hilfe Plugin Im Laufe des Buches werden wir Maven-Plugins Einführen, sprechen von Maven Project Object Model (POM)-Dateien, Einstellungen und Profilen. Es werden Zeiten kommen, in denen Sie ein Werkzeug benötigen, um Ihnen dabei zu helfen, den Sinn einiger in Maven genutzter Modelle zu erkennen sowie die Zielsetzungen bestimmter Plugins nachzuvollziehen. Das Maven Hilfe Plugin ermöglicht Ihnen die in Maven aktiven Profile auszugeben, das tatsächlich aktive Projekt Objekt Modell (POM) anzusehen sowie die Attribute der Maven Plugins darzustellen. Note Für einen konzeptionellen Überblick über das POM und Plug-Ins verweisen wir Sie auf Kapitel 3, Ein einfaches Maven-Projekt. Das Hilfe-Maven Plugin hat vier Goals. Die ersten drei Goals: -active-profiles, effective-pom, sowie effective-settings beschreiben je ein bestimmtes Projekt und müssen daher im Hauptverzeichnis des Projekts aufgerufen werden. Das letzte Goal - describe - ist etwas komplexer, es gibt Informationen über ein Plug-In oder dessen Zielsetzung aus. help:active-profiles Gibt die Profile (project, user, global) aus, welche für diesen Build aktiv sind help:effective-pom Zeigt das tatsächlich eintretende POM für den gegenwärtigen Build unter Berücksichtigung der aktiven Profile aus help:effective-settings Gibt die Einstellungen des Projekts wieder, unter Berücksichtigung von Profilerweiterungen sowie der Vererbung aus den globalen Einstellungen auf die Benutzer spezifischen Einstellungen help:describe Beschreibt die Attribute eines Plugins. Dieses Goal muss einzig nicht unter 26
  • Installieren und Ausführen von Maven einem bestehendes Projekt Verzeichnis aufgerufen werden. Es muss mindestens die groupId und artifactId des Plugin welches Sie beschreiben wollen gegeben werden 2.8.1. Beschreibung eines Maven Plugin Sobald Sie beginnen mit Maven zu arbeiten, werden Sie die meiste Zeit damit verbringen zu versuchen mehr Informationen zu Maven-Plugins zu bekommen: Wie funktionieren Plugins? Was sind die Konfigurations-Parameter? Was sind die Goals? Das Goal help:describe werden Sie regelmässig benutzen um an diese Informationen zu gelangen. Mit dem plugin-Parameter können Sie spezifizieren welches Plugin Sie untersuchen möchten. Hierzu geben sie entweder das Plugin-Prefix an (z.B. maven-help-plugin als help) oder die groupId:artifact[:version], wobei die Version optional ist. hier ein Beispiel; der folgende Befehl verwendet das Hilfe Plugin Goal describe um Informationen über das Maven Hilfe Plugin auszugeben. $ mvn help:describe -Dplugin=help ... Group Id: org.apache.maven.plugins Artifact Id: maven-help-plugin Version: 2.0.1 Goal Prefix: help Description: The Maven Help plugin provides goals aimed at helping to make sense out of the build environment. It includes the ability to view the effective POM and settings files, after inheritance and active profiles have been applied, as well as a describe a particular plugin goal to give usage information. ... Die Ausgabe des Goals describe mit dem Plugin-Parameter gibt die Koordinaten des Plugins aus, dessen Goal, Prefix und eine kurze Beschreibung. Obwohl diese Daten hilfreich sind, werden Sie in der Regel ausführlichere Informationen suchen. Um dies zu erreichen, führen Sie das Goal help:describe mit dem Parameter full aus, wie im folgenden beschrieben: $ mvn help:describe -Dplugin=help -Dfull ... Group Id: org.apache.maven.plugins Artifact Id: maven-help-plugin 27
  • Installieren und Ausführen von Maven Version: 2.0.1 Goal Prefix: help Description: The Maven Help plugin provides goals aimed at helping to make sense out of the build environment. It includes the ability to view the effective POM and settings files, after inheritance and active profiles have been applied, as well as a describe a particular plugin goal to give usage information. Mojos: =============================================== Goal: 'active-profiles' =============================================== Description: Lists the profiles which are currently active for this build. Implementation: org.apache.maven.plugins.help.ActiveProfilesMojo Language: java Parameters: ----------------------------------------------- [0] Name: output Type: java.io.File Required: false Directly editable: true Description: This is an optional parameter for a file destination for the output of this mojo...the listing of active profiles per project. ----------------------------------------------- [1] Name: projects Type: java.util.List Required: true Directly editable: false Description: This is the list of projects currently slated to be built by Maven. ----------------------------------------------- This mojo doesn't have any component requirements. =============================================== ... removed the other goals ... Diese Option ist ideal um alle Goals eines Plugins zu finden, sowie deren 28
  • Installieren und Ausführen von Maven Parameter. Aber manchmal ist dies weit mehr Informationen als notwendig. Um Informationen über ein einziges Goal zu erlangen, setzen Sie mit dem Plugin-Parameter auch den mojo-Parameter zu. Der folgende Aufruf listet alle Informationen über das Goal compile des Compiler-Plugins. $ mvn help:describe -Dplugin=compiler -Dmojo=compile -Dfull Note Was? Ein Mojo? In Maven, ein Plugin Goal ist bekannt als "Mojo". 2.9. Die Apache Software Lizenz Apache Maven ist unter der Apache Software Lizenz, Version 2.0 freigegeben. Wenn Sie die Lizenz lesen möchten, lesen Sie diese Lizenz abgelegt in ${M2_HOME}/LICENSE oder lesen Sie diese auf der Webseiten der Open-Source-Initiative http://www.opensource.org/licenses/apache2.0.php nach. Es gibt eine gute Chance, dass Sie, wenn Sie dieses Buch lesen, kein Rechtsanwalt sind. Wenn Sie sich fragen, was die Apache Lizenz, Version 2.0 bedeutet, dann hat die Apache Software Foundation hat eine sehr hilfreich "Häufig gestellte Fragen (FAQ)"- Seite bezüglich der Lizenz zusammengestellt. Sie finden diese unter http://www.apache.org/foundation/licence-FAQ.html. Da die FAQ in Englisch gefasst sind, hier nur eine Beispielhafte Übersetzung der Frage "Ich bin kein Jurist. Was bedeutet das alles ?" Die Apache Lizenz, Version 2.0, Erlaubt Ihnen • die Apache Software kostenlos herunterladen und zu verwenden. Ganz oder teilweise, für persönliche, Firmen-interne oder kommerzielle Nutzung einzusetzen. • Apache Software in oder-Distributionen oder Pakete welche Sie erstellen einzusetzen Verbietet Ihnen: 29
  • Installieren und Ausführen von Maven • Apache Software oder davon abgeleitete Software ohne korrekte Angabe der Herkunft zu verbreiten; • die Benutzung jeglicher Marken der Apache Software Stiftung in einer solchen Weise zu dass daraus abzuleiten ist, dass die Stiftung Ihr Produkt unterstützt • die Benutzung jeglicher Marken der Apache Software Stiftung in einer solchen Weise zu dass daraus abzuleiten ist, dass Sie die Apache Software erschaffen haben. Zwingt Sie nicht • die Source der Apache Software selbst, oder irgendwelcher davon abgeleiteten Produkte welche Sie daraus hergestellt haben einer Distribution oder einem Produkt welches Sie zusammengestellt haben beizulegen. • Änderungen welche sie an der Software der Apache Software Stiftung angebracht haben dieser zurückzugeben (Solcher Feedback wird ermutigt) 30
  • Part I. Maven by Example Das erste Buch zum Thema Maven welches erschienen ist, war das "Maven Developer's Notebook" von O'Reilly, dieses Buch, führte Maven in einer Reihe von einfachen Schritten ein. Die Idee hinter dem Developer's Notebook-Serie war, dass Entwickler am Besten lernen, wenn sie neben einem anderen Entwickler sitzen, und dem Denkprozess folgen, welcher ein anderer Entwickler beschreitet um sich eine neue Materie anzueignen und zu nutzen. Obschon die Serie der Developer's Notebooks erfolgreich war, gibt es eine entscheidende Kehrseite am Notebook-Format: Notebooks sind, par Definition, Ziel orientiert. Sie geleiten durch eine Abfolge von Schritten zu einem bestimmten Ziel. Größere Nachschlagewerke oder "Animal" Bücher sind dazu da, eine umfassende Materialsammlung über das gesamte Thema darzustellen. Beide Bücher haben Vor-und Nachteile, aber die Veröffentlichung des einen ohne das andere ist ein Rezept für zukünftige Probleme. Zur Verdeutlichung des Problems, nehmen Sie einmal an, dass zehntausend Menschen nachdem sie "A Developer's Notebook" gelesen haben, alle wissen werden, wie man ein einfaches Projekt aufsetzt, dem Beispiel halber ein Maven Projekt welches aus einer Reihe von Quelldateien ein WAR-Archive generiert. Aber, sobald diese Personen tiefer gehende Informationen nachschlagen möchten oder die eine oder andere Besonderheit zum Beispiel des "Assembly" Plugins erfahren wollen, stecken sie in so etwas wie einer Sackgasse. Da es kein gut geschriebenes Referenzmanual für Maven gibt, müssen sie nun auf die Plugin-Dokumentation auf dem Maven Site zurückgreifen oder alternativ sich durch eine Reihe von Mailinglisten quälen. Wenn die Leute erst einmal in der Maven Dokumentation graben, fangen sie an, tausende von schlecht geschrieben HTML-Dokumente des Maven Websites zu lesen, geschrieben von Hunderten von verschiedenen Entwicklern, jeder mit einer anderen Vorstellung davon, was es bedeutet, ein Plugin zu dokumentieren: Hunderte von Entwicklern mit unterschiedlichen sprachlichen Stilen, Ausdrücken und Muttersprachen. Trotz der größten Bemühungen von Hunderten von gut meinenden Freiwilligen, ist die Lektüre der Plugin-Dokumentation auf der Website Maven, ist im besten Fall frustrierend - und im schlimmsten Fall ein Grund Maven aufzugeben. Oftmals 31
  • bleiben Maven Nutzer stecken, nur weil sie auf dem Maven Site schlicht keine Antwort auf ihr Problem finden können. Während das erste Maven Developer's Notebook neue Benutzer zu Maven hinzog, und diese mit grundlegenden Kenntnissen zur Nutzung von Maven ausstattete, wuchs schon bald Frustration in dieser Gruppe, denn sie konnten beim besten Willen kein prägnantes, gut geschriebenes Referenz-Handbuch finden. Dieser Mangel eines aussagekräftiges (oder definierenden) Referenz-Handbuches hat Maven für einige Jahre zurückgehalten; es wurde etwas wie eine dämpfende Kraft in der Maven User-Community. Dieses Buch zielt darauf ab, diese Situation zu ändern, indem sie sowohl ein Update des ursprünglichen Maven Entwickler Notebook in Teil I, "Maven by Example" darstellt, und auch den ersten Versuch eines umfassenden Nachschlagewerks in Teil II, "Maven-Referenz". Was haben Sie in Ihren Händen halten, (oder auf dem Bildschirm sehen) sind eigentlich zwei Bücher in einem. In diesem Teil des Buches, werden wir den erzählenden, fortschreitenden Stil des Developer's Notebooks beibehalten, denn es ist wertvolles Material, welches dem Einsteiger hilft, anhand von Beispielen zu lernen, eben "Maven by Example". In der ersten Hälfte des Buches wird Maven an Hand von Beispielen eingeführt, um dann in Teil II, der "Maven-Referenz" Lücken zu schliessen, uns in Details zu ergeben und fortgeschrittene Themen einzuführen welche sonst unter Umständen den neuen Benutzer von Maven irritieren und vom Weg abbringen könnte. Während Teil II: Maven-Referenz auf einen Tabellenverweis oder einem vom Beispiel entkoppelten Programmausdruck setzt, setzt Teil I: Maven by Example auf die Kraft einer guten Vorbildwirkung sowie einer durchgehenden Storyline. Mit Abschluss von Teil I: Maven by Example, sollten Sie das Rüstzeug haben, das Sie brauchen, um mit Maven für ein paar Monate auszukommen. Wahrscheinlich werden sie nur auf Teil II: Maven-Referenz zurückkommen, um eine Anpassung eines Maven-Plugins vorzunehmen, oder wenn Sie weitere Details zu den einzelnen Plugins benötigen. 32
  • Chapter 3. Ein einfaches Maven Projekt 3.1. Einleitung In diesem Kapitel führen wir ein einfaches Projekt ein, welches wir von Grund auf mit dem Maven Archetype Plugin erstellen. Diese Anwendung bietet uns die Gelegenheit einige grundlegende Konzepte von Maven einzuführen, während Sie einfach der Entwicklung des Projektes folgen. Sollten Sie bereits zuvor Maven eingesetzt haben, werden Sie festgestellt haben, dass Maven mit sehr wenigen Details gut zurecht kommt. Ihr Build wird "einfach funktionieren", und Sie brauchen sich nur um Details zu kümmern, wenn es notwendig ist, das Standardverhalten zu ändern oder ein angepasstes Plugin zu erstellen. Allerdings, sollten Sie gezwungen sein sich um die Details von Maven zu kümmern, so ist ein gutes Verständnis der grundlegenden Konzepte von wesentlicher Bedeutung. Dieses Kapitel zielt darauf ab, das einfachste mögliche Maven-Projekt einzuführen und daran Ihnen einige der grundlegenden Konzepte, welche die Maven zu einer soliden Build Plattform machen, vorzustellen. Nach dem Lesen dieses Kapitels werden Sie ein grundlegendes Verständnis des Build Lifecycles, des Maven Repositories, der Abhängigkeits-Verwaltung (Dependency Management) und dem Projekt Objekt Modell (POM) haben. 3.1.1. Das Herunterladen der Beispiele dieses Kapitels Dieses Kapitel entwickelt ein sehr einfaches Beispiel, welches dazu dient einige Kern-Konzepte von Maven vorzustellen. Indem Sie den Schritten des Kapitels folgen, sollte es nicht notwendig werden die Beispiel herunterzuladen um den von Maven erstellten Quellcode einzusehen. Um dieses Beispiel zu erstellen werden wir das Archetype Maven Plugin einsetzen. Dieses Kapitel ändert das Projekt in keiner Weise. Sollten Sie es vorziehen, dieses Kapitel anhand der letzendlichen Quellcode-Beispiele zu lesen, so kann das Beispielprojekt dieses Kapitels zusammen mit den anderen Beispielen dieses Buchs von http://books.sonatype.com/maven-book/mvn-examples-1.0.zip oder 33
  • Ein einfaches Maven Projekt http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie dann zum Verzeichnis /ch03. Darin finden Sie ein Verzeichnis mit dem Namen /simple welches den Quellcode für dieses Kapitel enthält. 3.2. Erstellen eines einfachen Projekts Um ein neues Projekt unter Maven zu beginnen, setzen Sie das Archetype Maven-Plugin von der Befehlszeile aus ein. $ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 -DartifactId=simple -DpackageName=org.sonatype.mavenbook [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'archetype'. [INFO] artifact org.apache.maven.plugins:maven-archetype-plugin: checking for updates from central [INFO] ----------------------------------------------------------------------- [INFO] Building Maven Default Project [INFO] task-segment: [archetype:create] (aggregator-style) [INFO] -------------------------------------------------------------------- [INFO] [archetype:create] [INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates from central [INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch03 [INFO] Parameter: packageName, Value: org.sonatype.mavenbook [INFO] Parameter: basedir, Value: /Users/tobrien/svnw/sonatype/examples [INFO] Parameter: package, Value: org.sonatype.mavenbook [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: artifactId, Value: simple [INFO] * End of debug info from resources from generated POM * [INFO] Archetype created in dir: /Users/tobrien/svnw/sonatype/examples/simple mvn ist der Aufruf von Maven2; archetype:create bezeichnet ein Maven Goal. So Sie Ant kennen, ist ein Maven Goal gleichbedeutend mit einem Ant Target. Beide beschreiben eine Arbeitseinheit eines Builds. Die -Dname=wert Wertepaare stellen Argumente dar, welche an das Goal weitergereicht werden und nehmen die Form von -D Eigenschaft an, ähnlich deren welche Sie der Java Virtual Machine über die Befehlszeile weitergeben. Der Zweck des Goals archetype:create ist, schnell ein Projekt auf der Grundlage eines Archetypen zu erstellen. Ein Archetyp in diesem Kontext ist definiert als "ein originalgetreues Modell oder Typmuster nach dem andere, ähnliche Dinge gemustert sind, ein Prototyp" [1][2]. Von Maven 34
  • Ein einfaches Maven Projekt werden etliche Archetypen bereitgehalten, diese reichen von der einfachen Swing Anwendung bis hin zur komplexen Web-Applikation. In diesem Kapitel werden wir den aller einfachsten Archtetypen nutzen um ein einfaches Gerüst eines Starter-Projekts zu erstellen. Das Plugin hat das Präfix "archetype" und das entsprechende Goal ist "create". Sobald wir das Projekt generiert haben, sehen Sie sich das Verzeichnis /simple und die von Maven darin generierte Verzeichnisstruktur näher an: simple/‚ simple/pom.xmlƒ /src/ /src/main/„ /main/java /src/test/… /test/java Der erzeugte Verzeichnisbaum hält sich an das Maven Standard Layout, wir werden später auf die Einzelheiten eingehen, aber für den Moment konzentrieren wir uns auf die wenigen grundsätzlichen Verzeichnisse: ‚ Das Archetype Maven Plugin erstellt ein Verzeichnis, das mit der artifactId einhergeht: /simple. Dieses Verzeichnis ist bekannt als das Projekt Basis-Verzeichnis. ƒ Jedes Maven-Projekt benötigt was als als Projekt Objekt Modell (POM) bekannt ist, in einer Datei mit dem Namen pom.xml. Diese Datei beschreibt das Projekt, konfiguriert Plugins, und definiert Abhängigkeiten. „ Unser Projekt-Quellcode und zugehörige Ressourcen werden unter /src/main abgelegt. Im Falle unseres einfachen Java-Projekt wird dies aus ein paar Java-Klassen und einigen Property-Dateien bestehen. In einem anderen Projekt könnte dies das Root-Dokument einer Web-Anwendung sein, oder Konfigurationsdateien für einen Applikations-Server. In einem Java-Projekt werden die Java-Klassen in /src/main/java sowie Ressoucen in /src/main/resources abgelegt. … Unsere Projekt-Testfälle befinden sich unter /src/test. In diesem Verzeichnis werden Java-Klassen wie z.B. JUnit oder TestNG Tests (/src/test/java) abgelegt, sowie Klassenpfad relevante Ressourcen für Tests unter /src/test/resources. 35
  • Ein einfaches Maven Projekt Das Maven Archetype Plugin erzeugt eine einzige Klasse com.sonatype.maven.App. Dies ist eine dreizeilige Java-Klasse mit einer statischen main()-Funktion welche eine Nachricht ausgibt: package org.sonatype.mavenbook; /** * Hello world! * */ public class App { public static void main( String[] args ) { System.out.println( "Hello World!" ); } } Der einfachste Maven Archetyp generiert das einfachst mögliche Programm: ein Programm, welches "Hallo Welt!" auf der Standard-Ausgabe ausgibt. 3.3. Der Aufbau eines einfachen Projekts Sobald Sie das Projekt mit dem Maven Archetype Plugin erstellt haben, indem Sie die Anweisungen aus Abschnitt 3.2: Erstellen eines einfachen Projekts ausführen, werden Sie es builden und als Anwendung paketieren wollen. Um dies zu bewerkstelligen, rufen Sie im Verzeichnis in welchem sich die pom.xml-Datei befindet den Befehl mvn install von der Befehlszeile auf. $ mvn install [INFO] Scanning for projects... [INFO] ---------------------------------------------------------------------- [INFO] Building simple [INFO] task-segment: [install] [INFO] ---------------------------------------------------------------------- [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Compiling 1 source file to /simple/target/classes [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Compiling 1 source file to /simple/target/test-classes [INFO] [surefire:test] [INFO] Surefire report directory: /simple/target/surefire-reports 36
  • Ein einfaches Maven Projekt ------------------------------------------------------- T E S T S ------------------------------------------------------- Running org.sonatype.mavenbook.AppTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.105 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [jar:jar] [INFO] Building jar: /simple/target/simple-1.0-SNAPSHOT.jar [INFO] [install:install] [INFO] Installing /simple/target/simple-1.0-SNAPSHOT.jar to ~/.m2/repository/com/sonatype/maven/ch03/simple/1.0-SNAPSHOT/ simple-1.0-SNAPSHOT.jar Sie haben soeben das einfachste mögliche Maven Projekt erstellt, kompiliert, getestet, paketiert und installiert. Um sich zu überzeugen, dass die Anwendung tatsächlich lauffähig ist, starten Sie sie von der Befehlszeile: $ java -cp target/simple-1.0-SNAPSHOT.jar org.sonatype.mavenbook.App Hello World! 3.4. Einfaches Projekt Objekt Modell (POM) Bei der Ausführung von Maven richtet dieses sich nach dem Projekt Objekt Modell (POM), um an Informationen über das Projekt zu gelangen. Das POM liefert Antworten auf Fragen wie: Welche Art von Projekte ist es? Wie lautet der Projektname? Gibt es irgendwelche Anpassungen am Build bezüglich diesem Projekt? Hier die grundlegende pom.xml-Datei wie sie vom Archetype Maven Plugin Goal create erstellt wurde. Example 3.1. Einfachstes Projekt pom.xml Datei <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch03</groupId> <artifactId>simple</artifactId> 37
  • Ein einfaches Maven Projekt <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>simple</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project> Diese pom.xml-Datei stellt das elementarste POM dar mit welchem Sie je in einem Maven Projekt zu tun haben werden. In der Regel ist eine POM-Datei wesentlich komplexer: Abhängigkeiten werden definiert und Plugin Verhalten angepasst. Die ersten Elemente- groupId, artifactId, packaging sowie version sind als Maven-Koordinaten bekannt. Sie sind der eindeutigen Identifizierung eines Projekts gewidmet. name und url sind beschreibende Elemente des POM, welche in einer einfach lesbare Art den Namen des Projektes sowie einer zugeordneten Projekt-Website wiedergeben. Zuletzt wird im dependency Element eine einzige Beziehung als "Test-Scoped" Abhängigkeit zum JUnit-Test Framework unter dem Namen JUnit definiert. Diese Themen werden im Abschnitt 3.5: "Kernkonzepte" ausgeführt. Zum jetzigen Zeitpunkt ist alles was sie wissen müssen, dass die pom.xml-Datei Maven zum Leben erweckt. Bei der Ausführung von Maven wird dieses gesteuert durch eine Kombination von Einstellungen innerhalb der pom.xml-Datei, einer Art "Super"-POM welche sich im Installationsverzeichnis von Maven befindet, sowie (möglicherweise) einigen benutzerspezifischen Einstellungen. Um sich das "tatsächliche" POM, oder das POM, gegen welches Maven ausgeführt wird anzusehen, geben Sie im Stammverzeichnis Ihres Projekts den folgenden Befehl ein: $ mvn help:effective-pom Mit der Ausführung bekommen Sie ein wesentlich umfangreicheres POM, das auch die Maven Standardeinstellungen enthält 38
  • Ein einfaches Maven Projekt 3.5. Kern Konzepte Nachdem Sie nun zum ersten Mal Maven eingesetzt haben, ist ein guter Zeitpunkt gekommen einige der Kernkonzepte von Maven einzuführen. Im vorigen Beispiel haben Sie, aufbauend auf ein einfaches POM, ein einfaches Projekt generiert, welches im Standard Layout von Maven (der Konvention folgend) strukturiert die Quelldateien abgelegt hat. Sie haben dann Maven mit einer Lebenszyklusphase als Argument aufgerufen und damit ausgelöst, dass Maven eine Anzahl Plugin Goals abgearbeitet hat. Zuletzt haben Sie einen Maven Artifact in Ihr lokales Repository installiert. Moment! Was ist ein "Lebenszyklus"? Was ist ein "lokales Repository"? Die folgenden Abschnitte beschreiben einige der für Maven zentralen Konzepte. 3.5.1. Maven Plugins und Ziele Im vorigen Abschnitt haben wir Maven mit zwei unterschiedlichen Kommandozeilen-Argumenten aufgerufen. Der erste Befehl war ein einziges Plugin Goal, das Goal create des Archetype Maven Plugin. Der zweite Aufruf von Maven war hingegen gekoppelt mit einer Lifecycle-Phase - install. Um ein einzelnes (bestimmtes) Maven Plugin Goal auszuführen, benutzten wir die Syntax mvn archetype:create, hierbei ist archetype die Kennung eines Plugins und create die Kennung eines Goals. Wann immer Maven ein Plugin Goal ausführt, gibt es die Plugin- sowie die Goal-Kennung auf der Standardausgabe aus: $ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 -DartifactId=simple -DpackageName=org.sonatype.mavenbook ... [INFO] [archetype:create] [INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates from central ... Ein Maven-Plugin ist eine Sammlung von einem oder mehreren Goals. Beispiele von Maven-Plugins sind einfache Core-Plugins wie das JAR-Plugin welches das Goal zur Erzeugung von JAR-Archiven beinhaltet; das Compiler-Plugin mit den Goals Source Code kompilieren oder Unit Test Quellcode zu kompilieren oder das Surefire Plugin welches Goals zum Abarbeiten von Unit Tests sowie der Report 39
  • Ein einfaches Maven Projekt Generierung diesbezüglich beinhaltet. Andere, stärker spezialisierte Maven-Plugins sind z. B. das Hibernate3 Plugin zur Integration mit der beliebten Hibernate Persistenz Bibliothek, dem JRuby Plugin, welches erlaubt Ruby als Teil des Builds auszuführen sowie Maven Plugins in Ruby zu schreiben. Maven sieht auch vor benutzerdefinierte Plugins einzubinden. Ein benutzerdefiniertes Plugin kann in Java geschrieben sein, oder aber auch in einer von vielen anderen Sprachen darunter Ant, Groovy, BeanShell, und, wie bereits erwähnt, Ruby. Figure 3.1. Ein Plugin enthält Goals Ein Goal ist eine spezifische Aufgabe, welche alleinstehend, oder zusammen mit anderen Goals als Teil eines größeren Builds ausgeführt werden kann. Ein Goal ist eine "Arbeitseinheit" in Maven. Beispiele von Goals sind das Goal compile des Compiler-Plugin, es kompiliert den gesamten Quellcode für ein Projekt, oder das Goal test des Surefire Plugin welches Unit-Tests ausführen kann. Goals werden durch Konfigurations-Parameter bestimmt, welche erlauben das Verhalten anzupassen. Ein Beispiel, das Goal compile des Compiler-Plugin definiert eine Reihe von Konfigurations-Parameter, mit denen Sie die gewünschte JDK-Version Version bestimmen, sowie über den Einsatz von Compiler-Optimierungen bestimmen können. Im vorigen Beispiel haben wir über die Befehlszeile die Parameter groupId und artifactId an das Goal create des Archetype Plugin weitergegeben (-DgroupId=com.sonatype.maven.ch03 sowie -DartifactId=simple). Darüber hinaus haben wir den Parameter packageName als com.sonatype.maven an das Goal create weitergegeben. Hätten wir den Parameter packageName ausgelassen, wäre die Voreinstellung com.sonatype.maven.ch03 40
  • Ein einfaches Maven Projekt zum Zug gekommen. Note Im weiteren Text werden wir häufig, wenn wir auf ein Plugin Goal verweisen eine verkürzte Notation benutzen: pluginId:goalId; Ein Beispiel: um sich auf das Goal create des Archetype Maven Plugin zu beziehen schreiben wir archetype:create. Goals legen Parameter fest, für welche vernünftige Standardwerte definiert sein können. Im vorangehenden Beispiel archtetype:create, wurde der Archetype nicht spezifiziert, es wurde lediglich eine groupId sowie eine artifactId weitergegeben. Hier kommen wir das erste mal mit Konvention vor Konfiguration in Berührung. Die Konvention, oder der Standard, des Goals create ist die Generierung eines einfachen Projektes namens Quickstart. Das Goal create definiert einen Parameter archetype:artifactId welcher auf den Standardwert maven-archetype-quickstart gesetzt ist. Der Archetyp 'Quickstart' erzeugt eine minimale Projekthülse, mit zugehörigem POM sowie einer einzigen Klasse. Das Archetype Plugin ist wesentlich mächtiger als dieses Beispiel andeutet, jedoch ist es eine gute Möglichkeit, um neue Projekte schnell zu starten. Später in diesem Buch, zeigen wir Ihnen, wie das Archetype Plugin benutzt werden kann, um komplexere Projekte wie Web-Anwendungen zu generieren, und wie Sie das Archetype Plugin benutzen können um Ihren eigenen Satz von Projekten zu definieren. Der Kern von Maven hat nur wenig zu tun mit den spezifischen Aufgaben welche am Build Ihres Projektes beteiligt sind. Von sich aus weiss Maven nicht wie man Ihre Quelldateien kompiliert oder gar ein JAR-Archive packt. Maven delegiert all diese Arbeiten an Maven-Plugins wie das Compiler Plugin oder das JAR-Plugin. Diese können, sobald sie gebraucht werden, aus dem zentralen Maven Repository heruntergeladen und periodisch aktualisiert werden. Wenn Sie Maven herunterladen, so bekommen Sie lediglich den Kern oder Core von Maven. Dieser besteht aus einer sehr grundlegenden Shell, welche nur in der Lage ist die Befehlszeile zu analysieren, den Klassenpfad zu verwalten, eine POM-Datei zu analysieren und nach Bedarf Plugins herunterzuladen. Indem das Compiler Plugin 41
  • Ein einfaches Maven Projekt vom Core getrennt gehalten wurde und über einen Update Mechanismus verfügt, ist es ein Leichtes für den Benutzer, auch die neusten Compiler Optionen zum Einsatz zu bringen. Auf diese Weise ermöglichen Maven-Plugins universelle Wiederverwendbarkeit von übergreifender Buildlogik, Sie definieren nicht einen Kompilierungs-Task innerhalb des Builds, Sie benutzen das Compiler Plugin wie es von jedem Maven Benutzer eingesetzt wird. Wird das Plugin weiterentwickelt, kann jedes Projekt auf der Basis von Maven sofort den Nutzen daraus ziehen (und sollten Sie das Compiler Plugin nicht mögen, so können Sie es übersteuern und Ihr eigenes zum Einsatz bringen!). 3.5.2. Maven Lifecycle Der zweite Aufruf des vorangegangenen Abschnitts war mvn package. Bei diesem Aufruf wurde kein Plugin Goal angegeben, statt dessen wurde eine Lebenszyklusphase angegeben. Eine Phase ist ein Schritt in dem was Maven den "Build Lifecycle" nennt. Der Build Lebenszyklus ist eine geordnete Folge von Phasen welche am Aufbau eines Projekts beteiligt sind. Maven unterstützen kann eine Reihe ganz unterschiedlicher Lebenszyklen. Der am häufigsten zum Einsatz kommende ist der Standard-Lebenszyklus. Dieser beginnt mit eine Phase der Validierung der grundlegenden Integrität des Projekts und endet mit einer Phase der Bereitstellung von einem Projekt für die Produktion. Lifecycle Phasen sind bewusst vage gehalten, im einzelnen lediglich definiert als Validierung, Test oder Verteilung kann eine Phase für verschiedene Projekte ganz unterschiedliche Bedeutung haben. Zur Verdeutlichung ein Beispiel: die Phase package bedeutet in einem Projekt welches ein JAR-Archive erstellt: 'erstelle ein JAR-Archive', hingegen in einem Projekt welches eine Webanwendung zum Ziel hat: 'Erstelle ein WAR-Archive'. Abbildung 3.2 "Ein Lebenszyklus besteht aus einer Abfolge von Phasen" zeigt eine vereinfachte Darstellung des Maven Standard Lebenszykluses. Plugin-Goals können an Lebenszyklus-Phasen gebunden werden. Mit dem Durchschreiten des Lebenszykluses arbeitet Maven die Goals der einzelnen Phasen ab. An jeder Phase können keine (0) oder mehrere Goals gebunden sein. Im vorangegangenen Abschnitt, beim Aufruf von mvn package, haben Sie vielleicht bemerkt, dass mehr als ein Ziel ausgeführt wurde. Sehen Sie sich die Ausgabe nach 42
  • Ein einfaches Maven Projekt dem Ausführen von mvn package genau an, Sie werden sehen, dass verschiedene Goals ausgeführt wurden. Als dieses einfache Beispiel die Phase package erreichte, führte es das Goal jar des Jar Plugins aus. Da unser einfaches Schnellstart-Projekt (standardmäßig) den Paketierungstyp jar setzt, wird das Goal jar:jar an die Phase package gebunden. Figure 3.2. Ein Goal wird an eine Phase gebunden Wir wissen nun, dass die Phase package für ein Projekt mit jar-Paketierung ein JAR-Archive erstellen wird. Aber wie steht es um die vorangehenden Goals, deren wie etwa compiler:compile und surefire:test? Diese Goals werden von Maven beim Durchschreiten der vorhergehenden Phasen des Lebenszykluses abgearbeitet. Der Aufruf einer bestimmten Phase löst aus, dass alle vorgängigen Phasen in entsprechender Reihenfolge abgearbeitet werden. Mit dem Abschluss der spezifizierten Phase endet die Bearbeitung. Jede Phase entspricht keinem oder mehreren abzuarbeitenden Goals, und da wir keine weitere Plugin-Konfiguration oder Anpassung durchgeführt haben, bindet dieses Beispiel lediglich eine Reihe von Standard-Plugin-Goals an den Standard-Lebenszyklus. Läuft Maven entlang des definierten Standard-Lebenszykluses bis zur Phase package, werden die folgenden Goals in der angegebenen Reihenfolge ausgeführt: resources:resources Das Goal resources des Resouces Plugin wird an die Phase resources gebunden. Dieses Goal kopiert alle Ressourcen von /src/main/resources und allen anderen, konfigurierten Ressource- Verzeichnissen in das Ausgabe-Verzeichnis. compiler:compile 43
  • Ein einfaches Maven Projekt Das Goal compile des Compiler Plugin wird an die Phase compile gebunden. Dieses Ziel kompiliert den gesamten Quellcode von /src/main/java oder jedem anderen konfigurierten Quellen-Verzeichnisse in das Ausgabe-Verzeichnis. resources:testResources Das Goal testResources des Resources Plugin wird an die Phase test-resources gebunden. Dieses Goal kopiert alle Ressourcen von /src/test/resources und jedem anderen, konfigurierten Test-Ressource Verzeichnis in ein Test Ausgabe Verzeichnis. compiler:testCompile Das Goal testCompile des Compiler-Plugin wird an die Phase test-compile gebunden. Dieses Goal kompiliert die Testfälle von /src/test/java und allen anderen konfigurierten Test-Quellen-Verzeichnissen in ein Test Ausgabe Verzeichnis. surefire:test Das Goal test des Surefire Plugin wird an die Phase test gebunden. Dieses Ziel führt alle Tests aus und generiert Ausgabedateien der detaillierten Ergebnissen. Standardmäßig wird dieses Goal den Build beenden, sollte ein Test scheitern. jar:jar Das Goal jar des JAR-Plugin wird an die Phase package gebunden. Dieses Goal paketiert das Ausgabeverzeichnis und erstellt ein JAR-Archiv. 44
  • Ein einfaches Maven Projekt 45
  • Ein einfaches Maven Projekt Figure 3.3. Gebundene Goals werden mit den zugehörigen Phasen ausgeführt. (Anmerkung: Es bestehen mehr Phasen als oben abgebildet, dies ist ein Auszug der Möglichkeiten) Wir fassen zusammen: beim Ausführen von mvn package durchläuft Maven alle Lebenszyklus Phasen bis und mit package, und führt hierbei in Abfolge alle Goals aus, welche zugehörig an Phasen gebunden sind. Anstelle der Angabe eines Maven Lifecycle Goals, können Sie das gleiche Ergebnis erreichen, indem Sie eine Abfolge von Plugin Goals angeben: mvn resources:resources compiler:compile resources:testResources compiler:testCompile surefire:test jar:jar install:install Die Ausführung der Phase package ist der Verwaltung aller beteiligten Goals eines bestimmten Builds vorzuziehen. Ein derartiges Vorgehen erlaubt den Projekten auch die Einhaltung genau definierter Standards. Es ist der Lebenszyklus, welcher einem Entwickler erlaubt von von einem Maven-Projekt zu einem anderen zu springen, ohne sich um die Details der einzelnen Builds zu kümmern. Schaffen Sie es ein Maven-Projekt zu builden, so schaffen Sie dies für alle! 3.5.3. Maven Koordinaten Das Archetype Plugin erstellte ein Projekt mit einer Datei namens pom.xml. Dies ist das Projekt Objekt Modell (POM), eine deklarative Beschreibung eines Projekts. Wenn Maven ein Goal ausführt hat jedes Goal Zugang zu den im Projekt POM definierten Informationen. Wenn das Goal jar:jar ein JAR-Archive erstellen soll, sieht es in der POM Datei nach um herauszufinden wie das JAR-Archive heissen soll. Soll das Goal compiler:compiler Java Quelldateien kompilieren greift das Goal auf die POM Dateien zurück, um zu sehen ob dort Compiler Eigenschaften definiert sind. Goals werden immer im Kontext eines POMs ausgeführt. Goals sind Aktionen welche wir auf ein Projekt anwenden möchten, 46
  • Ein einfaches Maven Projekt und Projekte sind in POM Dateien abgebildet. Das POM bezeichnet ein Projekt, stellt einen Satz von Identifikationsmerkmale (Koordinaten) für ein Projekt, und definiert die Beziehungen zwischen diesem Projekt und andern Projekten durch die Angabe von Abhängigkeiten, Eltern und Voraussetzungen. Ein POM kann auch Plugin-Verhalten beeinflussen sowie Informationen über die Entwicklergemeinde und an einem Projekt beteiligten Entwicklern bereitstellen. Maven Koordinaten stellen eine Anzahl Identifikatoren, welche zur eindeutigen Identifikation eines Projekts, einer Abhängigkeit oder eines Maven Plugins genutzt werden können. Werfen Sie einen Blick auf das folgende POM. Figure 3.4. Maven Projekt Koordinaten Wir haben die Maven Koordinaten für dieses Projekt: groupId, artifactId, version und packaging vorgestellt. Die Einheit dieser Identifikatoren bildet die Koordinaten eines Projekts[3]. Genau wie in jedem anderen Koordinatensystem, fixieren diese Koordinaten einen bestimmten Punkt im Raum: vom Allgemeinen zum Spezifischen. Maven bestimmt ein Projekt mit dessen Koordinaten, so sich ein 47
  • Ein einfaches Maven Projekt Projekt auf ein anderes bezieht, entweder als Abhängigkeit, Plugin oder auch elterliche Projekt Referenz. Maven Koordinaten werden gewöhnlich durch einen Doppelpunkt getrennt in folgender Form wiedergegeben: groupId:artifactId:version:packaging. Die Koordinate des im obigen Beispiel einer pom.xml-Datei eines Projektes sind daher folgendermassen wiedergegeben: mavenbook:my-app:jar:1.0-SNAPSHOT. Diese Notation gilt auch für Projekt-Abhängigkeiten; unser Projekt basiert auf JUnit Version 3.8.1, es enthält daher eine Abhängigkeit zu junit:junit:jar:3.8.1 . 1 groupId Die "Gruppe" (Firma, Team, Organisation, Projekt, oder anderweitige Gruppe). Es gilt die ungeschriebene Regel, dass die groupId in der Reverse-Domain-Name Notation der Organisation die das Projekt erstellt, wiedergegeben wird. Projekte von Sonatype hätte eine groupId welche mit com.sonatype beginnt, Projekte der Apache Software Foundation hätte eine groupId welche mit org.apache beginnt. artifactId Eine eindeutige hierarchische Kennung unter der groupId, identifiziert ein einzelnes Projekt. version Eine bestimmte Version eines Projekts. Freigegebene Projekte haben eine bestimmte, festgelegte Versionskennung. Diese bezieht sich immer auf genau eine Version des Projekts. Projekte in der aktiven Entwicklung können eine spezielle Kennung bekommen, diese markiert die angegebene Version als eine "Momentaufnahme". Der Identifikator packaging ist ebenfalls ein wichtiger Bestandteil der Maven Koordinaten, aber nicht Teil der eindeutigen Projektkennung. Eine Projektdarstellung der Art groupId:artifactId:version bezeichnet ein Projekt einzigartig (kanonisch). Es ist nicht möglich ein weiteres Projekt mit dem gleichen 1 Es gibt eine weitere, fünfte Koordinate: classifier. Diese kommt nur selten zum Einsatz und wir werden die Einführung daher auf später im Buch zurückstellen. Nehmen Sie sich die Freiheit diese Koordinate im Moment zu ignorieren. 48
  • Ein einfaches Maven Projekt Bezeichnern zu erstellen. packaging Die Art in welcher das Projekt 'gepackt' ist, dies ist auf den Standardwert 'jar' gesetzt. Ein Projekt vom Packaging Typ 'jar' wird als JAR-Archive bereitgestellt, eines des Types 'war' in Form eines WAR-Archives. Diese vier Elemente bestimmen den Schlüssel um ein Projekt eindeutig im weiten Raum "mavenisierter" Projekte aufzufinden. Maven-Repositories (öffentliche, private und lokale) sind nach diesen Identifikatoren organisiert. Sobald ein Projekt im lokalen Maven-Repository eingestellt ist, steht es augenblicklich für alle weiteren Projekte (welche gegen dieses Repository arbeiten) bereit, eingebunden zu werden. Alles was zu tun bleibt, ist die Koordinaten in einem anderen Projekt als Abhängigkeit einzutragen. Figure 3.5. Der 'Maven'-Raum stellt ein Koordinatensystem für Projekte 3.5.4. Maven Repositories Beim ersten Ausführen von Maven, werden Sie feststellen, dass Maven eine Reihe von Dateien von einem Maven Remote Repository herunterlädt. Sollte die 49
  • Ein einfaches Maven Projekt Erstellung des einführenden Projektes das erste Mal sein, dass Sie Maven aufrufen, so ist das erste was passiert, dass Maven die neuste Version des Resource Plugins herunterlädt, ausgelöst durch das Goal resources:resource. Maven lädt Artefakte und Plugins aus einem Remote Repository sobald diese benötigt werden. Einer der Gründe, weshalb der Maven Download ist so klein ist (1,5 MiB) ist, dass Maven nicht mit allerlei Plugins ausgeliefert wird. Ausgeliefert wird nur das Allernötigste, alles weitere holt sich Maven bei Bedarf aus einem Remote-Repository. Maven kommt hierzu mit einer Standardeinstellung eines Remote Repositories (http://repo1.maven.org/maven2) von welchem es versuchen wird die Core Maven-Plugins und deren Abhängigkeiten herunterzuladen. Oftmals werden Sie an einem Projekt arbeiten, welches auf Bibliotheken aufbaut, welche weder frei noch öffentlich zu verbreiten sind. In einem solchen Fall müssen Sie entweder ein benutzerdefiniertes Repository innerhalb Ihrer Organisation aufbauen oder die Abhängigkeiten manuell herunterladen und installieren. Die Standard (Remote) Repositorien können durch Verweise auf benutzerdefinierte Maven Repositorien ersetzt oder ergänzt werden. Es gibt eine Reihe Produkte welche Organisationen dabei unterstützen eigene Repositorien zu verwalten sowie Spiegel-Repositorien von öffentlichen Maven-Repositorien zu erstellen. Was macht ein Maven Repository zu einem Maven Repository? Ein Maven-Repository zeichnet sich durch seine Struktur aus, ein Repository ist eine Sammlung von Projekt Artefakten in einer Struktur und Form welche für Maven leicht leicht nachvollziehbar ist. Die Artefakte in einem Maven-Repository sind in einer Verzeichnis-Struktur abgelegt, welche eng mit den Maven Projekt Koordinaten korrespondieren. Sie können sich diese Struktur ansehen: öffnen Sie mit einem Web-Browser das zentrale Maven Repository auf http://repo1.maven.org/maven2/. Sie können dort ein Artefakt mit den Koordinaten org.apache.commons:commons-email:1.1 im Verzeichnis /org/apache/commons/commons-email/1.1/ in einer Datei namens "commons-email.jar" vorfinden. Der Standard für ein Maven-Repository ist es, einen Artefakten in folgender Struktur relativ zum Wurzelverzeichnis des Repositories abzulegen: /<groupId>/< artifactId>/<version>/< artifactId>-<version>.<packaging> 50
  • Ein einfaches Maven Projekt Maven lädt Artefakte und Plugins von einem Remote-Repository auf Ihren lokalen Rechner herunter und speichert diese Artefakte in Ihrem lokalen Maven-Repository. Ist ein Artefakt einmal von einem Remote Repository in das lokale Repository heruntergeladen, so wird Maven kein weiteres mal auf das Remote Repository zugreifen, da Maven immer zuerst auf das lokale Repository zugreifen wird, vor es anderswo sucht. Unter Windows XP befindet sich Ihr lokales Repository wahrscheinlich unter C:Dokumente und EinstellungenBenutzername.m2Repository, unter Windows Vista befindet sich Ihr lokales Repository unter C:BenutzerBenutzername.m2Repository auf Unix-Systemen werden Sie Ihr lokales Maven-Repository unter ~/.m2/repository wiederfinden. Sobald Sie ein Projekt wie das aus dem vorherigen Abschnitt erstellen, wird in der Phase install ein Goal ausgeführt welches den neuen Artifakten in Ihrem lokalen Maven Repository ablegt. Sie sollten nun in der Lage sein, das Artefakt Ihres eingängigen Projektes in Ihrem lokalen Maven Repository wiederzufinden. Beim Aufruf von mvn install installiert Maven den Projekt Artefakt in Ihrem lokalen Repository. Probieren Sie es aus. $ mvn install ... [INFO] [install:install] [INFO] Installing .../simple-1.0-SNAPSHOT.jar to ~/.m2/repository/com/sonatype/maven/simple/1.0-SNAPSHOT/ simple-1.0-SNAPSHOT.jar ... Betrachten Sie die Ausgabe dieses Befehls, Sie können sehen, wie Maven unseres Projekt's JAR-Archiv im lokalen Maven-Repository ablegt. Maven nutzt das lokale Repository um Artefakte zwischen lokalen Projekten zu teilen. Sagen wir, Sie entwickeln zwei Projekte, Projekt A und Projekt B, dabei ist Projekt B von einem Artefakt von Maven-Projekt A abhängig. So wird Maven den Artefakt beim Build von Projekt B aus Ihrem lokalen Repository holen. Maven Repositorien sind beides, ein lokaler Zwischenspeicher (Cache) von Artefakten welche von einem Remote Repository heruntergeladen wurden, wie auch ein Mechanismus welcher erlaubt, dass Ihre Projekte zueinander abhängig sein können. 51
  • Ein einfaches Maven Projekt 3.5.5. Maven Abhängigkeits-Management (Dependency Management) In dem einfachen Beispiel dieses Kapitels, löst Maven die Koordinaten der JUnit-Abhängigkeit junit:junit:3.8.1 in einen Pfad /junit/junit/3.8.1/junit-3.8.1.jar des Maven Repositories auf. Die Möglichkeit, Artefakte in einem Repository basierend auf Maven Koordinaten zu lokalisieren gibt uns die Möglichkeit Abhängigkeiten in einem Projekt POM zu definieren. Sehen Sie sich die pom.xml-Datei genauer an. Sie werden einen Abschnitt finden, welcher sich mit den Abhängigkeiten (dependencies) befasst, dieser Abschnitt enthält eine einzige Abhängigkeit: JUnit. Ein komplexeres Projekt würde mehr als eine einzelne Abhängigkeit beinhalten, oder auch Abhängigkeiten welche von anderen Artefakten abhängen. Dies ist eine der mächtigsten Funktionen von Maven: die Unterstützung von transitiven Abhängigkeiten. Nehmen wir an, Ihr Projekt hängt von einer Bibliothek ab, welche wiederum von fünf oder zehn anderen Bibliotheken abhängig ist (etwa wie Spring oder Hibernate). Statt alle diese Abhängigkeiten nachzuverfolgen und in Ihrem pom.xml explizit aufzulisten können Sie schlicht diejenige Abhängigkeit aufnehmen, welche Sie beabsichtigen. Maven wird die implizit abhängigen Bibliotheken für Sie auflösen. Maven wird auch auftretende Konflikte auflösen, diesbezüglich bietet Maven die Möglichkeit das Verhalten anzupassen sowie gewisse transitive Abhängigkeiten auszuschliessen. Werfen wir einen Blick auf eine Abhängigkeit, welche im vorangegangenen Beispiel in Ihr lokales Repository heruntergeladen wurde. Sehen Sie in Ihrem lokalen Repository-Pfad unter ~/.m2/repository/junit/junit/3.8.1/ nach. Wie Ihnen nach der Lektüre des Kapitels bekannt ist, gibt es eine Datei junit-3.8.1.jar sowie eine junit-3.8.1.pom-Datei und zusätzlich eine Anzahl Dateien; Prüfsummen welche Maven nutzt, um die Echtheit eines heruntergeladenen Artefakts zu überprüfen. Beachten Sie, dass Maven nicht nur das JUnit JAR-Archive herunterlädt. Maven lädt zusätzlich das POM des Artefakts herunter. Die Tatsache, dass Maven zusätzlich zum Artefakt auch die zugehörige POM Dateien herunterlädt ist von zentraler Bedeutung für die Unterstützung von transitiven Abhängigkeiten durch Maven. 52
  • Ein einfaches Maven Projekt Sie werden feststellen, dass Maven bei der Installation Ihres Projekt-Artefaktes in das lokale Maven-Repository zugleich eine leicht modifizierte Version der zugehörigen pom.xml-Datei im selben Verzeichnis des JAR-Archives ablegt. Zugleich eine POM-Datei im Repository abzulegen gibt anderen Projekten Informationen zu diesem Projekt, als Wichtigstes dessen Abhängigkeiten. Projekt B, abhängig von Projekt A, hängt auch von den Abhängigkeiten des Projekts A ab. Wenn Maven eine Abhängigkeit zu einem Artefakt anhand einer Reihe von Artefaktkoordinaten auflöst, so ruft Maven die POM-Dateien auf und nimmt sich deren Abhängigkeiten an, um bestehende transitive Abhängigkeiten aufzulösen. Jene transitiven Abhängigkeiten werden sodann den Projektabhängigkeiten zugefügt. Eine Abhängigkeit in Maven ist nicht nur eben ein JAR-Archiv. Es ist eine POM-Datei, welche weitere Abhängigkeiten von anderen Artefakten erklären kann. Solche Abhängigkeiten der Abhängigkeiten nennt man transitive Abhängigkeiten und sie werden durch die Tatsache, dass das Maven-Repository mehr als nur den Bytecode speichert ermöglicht. Maven speichert Meta-Daten bezüglich der Artefakte. Die folgende Abbildung zeigt ein mögliches Szenario transitiver Abhängigkeiten. 53
  • Ein einfaches Maven Projekt Figure 3.6. Wie Maven transitive Abhängigkeiten auflöst / Resolving transitive Dependencies In der vorangegangenen Abbildung, ist ein Projekt abhängig von den Projekten B und C. Projekt B baut auf Projekt D auf, Projekt C baut auf Projekt E auf. Die Gesamtmenge aller Abhängigkeiten von A sind Projekte B, C, D, E, jedoch musste Projekt A lediglich die Projekte B und C deklarieren. Transitive Abhängigkeiten können sich immer dann als nützlich erweisen, wenn Ihr Projekt sich auf ein anderes Projekt abstützt, welches weitere Abhängigkeiten deklariert (z.B. Hibernate, Apache Struts, oder das Spring Framework). Maven bietet Ihnen auch die Möglichkeit, transitive Abhängigkeiten von der Aufnahme in den Klassenpfad auszuschliessen. Maven kennt auch das Konzept des Gültigkeitsbereichs (Scope). Die pom.xml-Datei des Beispielprojektes kennt nur eine einzige Abhängigkeit junit:junit:jar:3.8.1 in einem Gültigkeitsbereich test. Wenn eine Abhängigkeit bezüglich eines Gültigkeitsbereich test definiert ist, so bedeutet dies, dass diese nicht dem Goal compile des Compiler Plugins zur Verfügung steht. 54
  • Ein einfaches Maven Projekt Die Abhängigkeit wird dem Klassenpfad nur für die Goals compiler:testCompile sowie surefire:test zugefügt. Bei der Erstellung eines JAR-Archivs für ein Projekt werden Abhängigkeiten nicht mit eingeschlossen, es wird lediglich gegen diese kompiliert. Beim Erstellen eines WAR- oder EAR-Archives können Sie konfigurieren, ob Sie Abhängigkeiten in den erstellten Artefakt einschliessen möchten. Ebenfalls möglich ist der Ausschluss bestimmter Abhängigkeiten abhängig vom gewählten Gültigkeitsbereich. Der Gültigkeitsbereich provided bestimmt, dass die Abhängigkeit benötigt wird um zu kompilieren, aber nicht in den Erstellten Artefakt eingebunden werden soll. Dieser Gültigkeitsbereich (provided) ist nützlich, wenn Sie eine Web Applikation erstellen; Sie müssen z.B. gegen die Servlet Spezifikation kompilieren, jedoch werden Sie vermeiden, das Servlet API JAR-Archiv in das /WEB-INF/lib Verzeichnis Ihrer Webanwendung abzulegen. 3.5.6. Site-Generierung und Reporting Eine weitere wichtige Möglichkeit ist die Fähigkeit von Maven Dokumentationen und Reports zu Generieren. Führen Sie im Stammverzeichnis der Beispielanwendung folgenden Aufruf aus: $ mvn site Der Aufruf wird Lebenszyklus Phase site auslösen. Im Gegensatz zu den Standard-Build Lifecycle Phasen welche sich mit der Verwaltung von Codegeneration, der Manipulation von Ressourcen, Kompilierung, Packetierung und so weiter befassen betrifft diese Phase ausschließlich die Bearbeitung von Website-Content im Rahmen des /src/site Verzeichnisses und der Generierung von Reports. Nach Abschluss diesen Befehls, sollten Sie einen Projekt-Website im Verzeichnis /target/site vorfinden. Nach dem Laden der Datei /target/site/index.html sollten Sie das Skelett eines Projekt (Web-)Sites sehen. Diese Shell enthält bereits einige Berichte unter der Rubrik "Projekt Reports" der linken Navigation im Menü, sowie Informationen über das Projekt, dessen Abhängigkeiten und der Entwickler im Zusammenhang mit dem Projekt in der Rubrik "Projekt-Information". Die Projekt-Website dieses Beispiels ist größtenteils 55
  • Ein einfaches Maven Projekt leer, da das POM nur sehr wenige Informationen über die reinen Koordinatenpunkte hinaus enthält: den Namen und die URL des Projektsites sowie der einzigen Test Abhängigkeit. Sie werden feststellen, dass auf dieser Website bereits einige Standard-Berichte bereitstehen, so gibt es einen Bericht, welcher den Ausgang der Unit-Tests zusammenfasst. Dieser Report spiegelt alle im Projekt erfolgten Unit Tests wieder, sowie im einzelnen deren Erfolg oder Misserfolg. Ein weiterer Report erzeugt die JavaDoc des Projekt API's. Maven bietet eine vollständige Palette von konfigurierbaren Reports, wie zum Beispiel dem Clover Report, welcher Aufschluss über die erfolgte Testabdeckung gibt, der JXR Report, ein Report welcher querverweisende HTML-Seiten generiert und speziell für Code-Reviews nützlich ist, der PMD Report, welcher, aufbauend auf eine Quellcodeanalyse, Aufschluss über eine Reihe von problematischen Kodierungen gibt, sowie der JDepend Report, welcher die Abhängigkeiten zwischen verschiedenen Packages einer Quellcodebasis analysiert. Die Reports der Website werden in der pom.xml Datei eines Builds konfiguriert. 3.6. Zusammenfassung Wir haben ein einfaches Projekt erstellt, in ein JAR-Archive gepackaged und diese JAR-Archive im lokalen Repository installiert um es anderen Projekten zur Verfügung zu stellen; sodann haben wir eine Projektwebsite der zugehörigen Dokumentation generiert. Wir konnten das alles bewerkstelligen, ohne auch nur einen einzige Zeile Code zu schreiben. Darüberhinaus haben wir Zeit aufgewandt einige der Kernkonzepte von Maven zu erläutern. Im nächsten Kapitel werden wir damit beginnen, die pom.xml-Datei anzupassen, weitere Abhängigkeiten einzufügen sowie einige Unit Tests zu konfigurieren. 56
  • Chapter 4. Anpassen eines Maven Projektes 4.1. Einleitung Dieses Kapitel baut auf das vorangehende Kapitel (3), insbesondere auf das eingeführte "Simple Maven Projekt" auf. Wir erstellen mit dem Archetype Maven Plugin ein einfaches Projekt, fügen einige Abhängigkeiten ein, erstellen Quellcode und passen das Projekt unseren Bedürfnissen an. Am Ende dieses Kapitels, werden Sie wissen, wie man es am besten angeht, reale Projekte mit Maven zu erstellen. 4.1.1. Herunterladen der Beispiele dieses Kapitels In diesem Kapitel werden wir mit Ihnen ein einfaches, nützliches Programm zur Interaktion mit einem Yahoo! Wetter Web-Service erstellen. Während Sie in der Lage sein sollten, der Entwicklung dieses Kapitels ohne Beispiel-Quellcode zu folgen, empfehlen wir das Herunterladen einer Kopie der Quellcodes als Referenz. Das Beispielprojekt dieses Kapitel zusammen mit den anderen Beispielen dieses Buchs kann von http://books.sonatype.com/maven-book/mvn-examples-1.0.zip oder http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie dann zum Unterverzeichnis /ch04. Darin befindet sich ein Verzeichnis mit dem Namen /simple weather welches den Quellcode für dieses Kapitel enthält. 4.2. Eine kurze Einführung in das "Simple Weather" Projekt Bevor wir loslegen, lassen Sie uns einen Schritt zurücktreten und das Wetter Projekt vorstellen. Was ist das, das 'Simple Weather' Projekt? Das einfache 57
  • Anpassen eines Maven Projektes Wetterdienst-Projekt ist ein Beispiel, das speziell konstruiert wurde um einige der Funktionen von Maven vorzustellen. Es handelt sich um eine Anwendung, welche beispielhaft die Art der Anwendung vorstellt, welche Sie zu bauen angefragt werden. Die 'Simple Weather' Anwendung ist ein ein grundlegendes Beispiel einer befehlszeilenorientierten Anwendung, welche auf eine angegebene Postleitzahl Daten vom Yahoo! RSS WetterDienst abruft. Die Anwendung analysiert den Rückgabewert und gibt diesen auf der Konsole aus. Es gab eine Reihe von Gründen welche uns bewogen dieses Beispiel auszuwählen: zum einen ist es unkompliziert, der Benutzer macht eine Eingabe über die Befehlszeile; diese nehmen wir auf, schicken die Anfrage an den Yahoo! Wetter Dienst (RSS), analysieren das Ergebnis und geben zuletzt einige Daten auf den Bildschirm aus. Dieses Beispiel besteht aus einer einfachen main()-Klasse und einigen Hilfsklassen, es gibt kein Enterprise Framework einzuführen, lediglich einfaches XML-Parsen sowie einige Logging-Anweisungen. Zum anderen gibt dieses Beispiel uns eine gute Gelegenheit einige interessante Bibliotheken wie Velocity, Dom4J und Log4J einzuführen - Obwohl dieses Buch sich auf die Einführung von Maven konzentriert, werden wir uns nicht scheuen, die Gelegenheit zur Einführung interessanter Utilities wahrzunehmen. Schließlich ist es ein Beispiel, das in einem einzigen Kapitel eingeführt, entwickelt, und deployed werden kann. 4.2.1. Yahoo! Wetter Dienst RSS Bevor Sie diese Anwendung bauen, sollten Sie etwas mehr über die Yahoo! Wetter Dienste (RSS-Feeds) erfahren. Allem voraus weisen wir darauf hin, dass der Dienst unter den folgenden Konditionen angeboten wird: "Die Datendienste werden zur Nutzung von Einzelpersonen sowie Non-Profit-Organisationen kostenlos, im Rahmen von persönlichen, nicht kommerziellen Anwendungen angeboten. Sie sind angehalten auf die Nutzung der Yahoo! Wetter Dienste im Rahmen Ihres Angebotes hinzuweisen." Mit anderen Worten, wenn Sie daran dachten, diese Dienste in Ihren 58
  • Anpassen eines Maven Projektes kommerziellen Webauftritt zu integrieren, dann gehen Sie bitte noch einmal über die Bücher: die Nutzung der Dienste ist lediglich für den privaten, nicht kommerziellen Gebrauch gestattet. Die Nutzung zu welcher wir in diesem Kapitel anregen ist beschränkt auf die persönliche Weiterbildung. Weitere Informationen bezüglich der Yahoo! Wetter Dienst Konditionen finden Sie in der Yahoo Wetter! API-Dokumentation: http://developer.yahoo.com/weather/. 4.3. Erstellen des "Simple Weather" Projektes In einem ersten Schritt, lassen Sie uns mit Hilfe des Archetype Maven Plugin ein grundlegendes Skelett der Anwendung erstellen. Führen Sie den folgenden Aufruf aus, um ein neues Projekt zu generieren: $ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch04 -DartifactId=simple-weather -DpackageName=org.sonatype.mavenbook -Dversion=1.0 [INFO] [archetype:create] [INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates from central [INFO] ------------------------------------------------------------------ [INFO] Using following parameters for creating Archetype: maven-archetype-quickstart:RELEASE [INFO] ------------------------------------------------------------------ [INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch04 [INFO] Parameter: packageName, Value: org.sonatype.mavenbook [INFO] Parameter: basedir, Value: ~/examples [INFO] Parameter: package, Value: org.sonatype.mavenbook [INFO] Parameter: version, Value: 1.0 [INFO] Parameter: artifactId, Value: simple-weather [INFO] *** End of debug info from resources from generated POM *** [INFO] Archetype created in dir: ~/examples/simple-weather Sobald das Archetype Maven Plugin das Projekt erstellt hat, wechseln Sie in das Verzeichnis der "Simple Weather" Anwendung und werfen Sie einen Blick auf die pom.xml Datei. Sie sollten das folgende XML-Dokument vorfinden: Example 4.1. Erstes POM des "Simple Weather" Projekts <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 59
  • Anpassen eines Maven Projektes http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch04</groupId> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <version>1.0</version> <name>simple-weather2</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> </project> Bitte beachten Sie, dass wir den Parameter version und das Goal archetype:create übergeben haben. Hiermit wird der Standardwert des 1.0-SNAPSHOT überschrieben. In diesem Projekt entwickeln wir die Version 1.0 des "Simple Weather" Beispielprogramms, wie Sie leicht am Element version der pom.xml-Datei ablesen können. 4.4. Anpassen der Projektinformationen Bevor wir mit dem Schreiben von Quellcode loslegen, Lassen Sie uns die Projektinformationen ein wenig Anpassen. Wir wollen etliche Informationen bezüglich der Lizenzrechte, der Organisation sowie der beteiligten Entwickler einfügen. Dies alles sind Standard-Informationen welche wir erwarten, dass diese in den meisten Projekten gebraucht werden. Die folgende Auflistung zeigt den 60
  • Anpassen eines Maven Projektes Ausschnitt der XML-Datei, der die Informationen zur Organisation, der Lizenzierung sowie bezüglich der Entwickler bereitstellt. Example 4.2. Informationen bezüglich der Organisation, rechtlicher Belange sowie der Entwickler in der pom.xml-Datei <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/maven-v4_0_0.xsd"> ... <name>simple-weather</name> <url>http://www.sonatype.com</url> <licenses> <license> <name>Apache 2</name> <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> <distribution>repo</distribution> <comments>A business-friendly OSS license</comments> </license> </licenses> <organization> <name>Sonatype</name> <url>http://www.sonatype.com</url> </organization> <developers> <developer> <id>jason</id> <name>Jason Van Zyl</name> <email>jason@maven.org</email> <url>http://www.sonatype.com</url> <organization>Sonatype</organization> <organizationUrl>http://www.sonatype.com</organizationUrl> <roles> <role>developer</role> </roles> <timezone>-6</timezone> </developer> </developers> ... </project> Die Auslassungspunkte ("...") in Beispiel 4.2, "Informationen bezüglich der Organisation, rechtlicher Belange sowie der Entwickler in der pom.xml-Datei" 61
  • Anpassen eines Maven Projektes sind eine Abkürzung für ein stark gekürztes Listing. Wenn Sie in diesem Buch auf eine pom.xml Datei stossen, welche direkt hinter dem Element-Tag project mit "..." beginnt und mit "..." direkt vor dem Element project aufhört, so zeigt dies an, dass wir nicht die gesamte pom.xml-Datei wiedergeben. In diesem Fall wurden die Tag-Elemente licenses, organization und developers vor dem Element dependencies eingefügt. 4.5. Einfügen neuer Abhängigkeiten Die einfache "Simpel Weather" Anwendung umfasst folgende drei Aufgaben: Abrufen von XML-Daten vom Yahoo! Wetter Dienst, analysieren des zurückgegebenen XML von Yahoo und schliesslich die formatierte Ausgabe auf der Konsole. Um diese Aufgaben zu erledigen, müssen wir einige neue Abhängigkeiten zu unserem Projekt pom.xml hinzufügen. Um die XML-Antwort des Yahoo! Dienstes zu analysieren werden wir <term>Dom4J</term> und <term>Jaxen</term> einsetzen. Um die Ausgabe des Kommandozeilenprogramms zu formatieren setzen wir <term>Velocity</term> ein und es ist ebenfalls notwendig, eine Abhängigkeit zu <term>Log4J</term> hinzuzufügen, um Ereignisse zu loggen. Nachdem wir diese Abhängigkeiten einbringen, sieht unsere Beispiel pom.xml-Datei wie folgt aus: Example 4.3. Hinzufügen der Abhängigkeiten von Dom4J, Jaxen, Velocity und Log4J <project> [...] <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> 62
  • Anpassen eines Maven Projektes </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> [...] </project> Wie Sie sehen, haben wir zum bestehenden Element der Test bezogenen Abhängigkeit JUnit, vier weitere Elemente dependency eingefügt. Nach dem Einfügen der Abhängigkeiten sowie dem Aufruf von mvn install werden Sie sehen, wie Maven diese Abhängigkeiten wie auch eine Reihe transitiver Abhängigkeiten in Ihr lokales Maven Repository herunterlädt. Nun wie wussten wir die Koordinaten dieser Abhängigkeiten? "Weiss" man schlicht die entsprechenden Werte für groupId und artifactId? Manche Artefakte sind so weit verbreitet (z.B. Log4J), dass Sie, wann immer Sie sie brauchen, sich an die Werte der groupId und artifactId erinnern werden. Velocity, Dom4J und Jaxen haben wir unter Einsatz einer hilfreichen Website (http://www.mvnrepository.com) aufgelöst. Diese Website bietet eine Maske zur Suche über das Maven-Repository an. Sie können die Site benutzen um Abhängigkeiten herauszufinden. Um dies zu verifizieren, versuchen Sie es selbst: laden Sie die Seite http://www.mvnrepository.com und suchen Sie nach einigen häufig verwendeten 63
  • Anpassen eines Maven Projektes Bibliotheken wie Hibernate oder dem Spring Framework. Wenn Sie auf dieser Site nach einem Artefakt suchen, werden Ihnen der artefactId-Wert sowie alle im zentralen Repository verfügbaren version-Werte ausgegeben. Ein Klick auf die Details einer bestimmten Version lädt eine Seite, welche das Element dependency wiedergibt, dieses können Sie kopieren und in die pom.xml Datei Ihres eigenen Projekt einfügen. Wenn Sie eine Abhängigkeit finden müssen, so lohnt es sich, mvnrepository.com zu benutzen. Auch werden Sie dann werden festestellen, dass bestimmte Bibliotheken über mehr als eine groupId verfügen. Dieses Werkzeug wird Ihnen helfen das Maven-Repository zu verstehen. 4.6. Quellcode von "Simple Weather" Das "Simple Weather" Beispielprogramm besteht aus fünf Java-Klassen: org.sonatype.mavenbook.weather.Main Diese Klasse enthält eine statische main()-Funktion, es ist der Ausgangspunkt für dieses System. org.sonatype.mavenbook.weather.Weather Die Weather-Klasse ist ein einfaches Java-Bean, welches die Koordinaten unseres Wetterberichtes, sowie eine Anzahl wichtiger Daten wie z.B Temperatur und Luftfeuchtigkeit vorhält. org.sonatype.mavenbook.weather.YahooRetriever Die YahooRetriever Klasse stellt eine Verbindung zum Yahoo! Wetter Dienst her und liefert einen InputStream der Daten des Dienstes. org.sonatype.mavenbook.weather.YahooParser Die YahooParser Klasse analysiert das zurückgegebene XML vom Yahoo! Wetter Dienst und wandelt es in ein Weather-Objekt um. org.sonatype.mavenbook.weather.WeatherFormatter Die WeatherFormatter Klasse setzt auf einem Weather Objekt auf, erstellt einen VelocityContext, und führt ein Velocity Template (Vorlage) daruaf aus. 64
  • Anpassen eines Maven Projektes Wir wollen an diese Stelle nicht näher auf den Quellcode des Beispiels eingehen, werden aber alle erforderlichen Quellen des Beispiels im Buch bereitstellen, so dass Sie "Simple Weather" zum Laufen bekommen. Wir gehen davon aus, dass die meisten von Ihnen den Quellcode heruntergeladen haben, wollen aber mit den Beispielen des Buches aber auch jene Leser berücksichtigen, welche den Beispielen innerhalb des Kapitels Schritt-für-Schritt folgen. Die folgenden Abschnitte legen den Quellcode der Klassen des "Simple Weather" Projekt dar. Diese Klassen sollten alle in das gleiche Paket com.sonatype.maven.weather abgelegt werden. Lassen Sie uns die App sowie die AppTest Klassen welche das Goal archetype:create erzeugt hat, entfernen und unsere neuen Klassen dem Package zufügen. In einem Maven-Projekt, liegen alle Quellen eines Projektes unter /src/main/java. Aus dem Basis-Verzeichnis des neuen Projekts heraus, führen Sie die folgenden Befehle aus: $ cd src/test/java/org/sonatype/mavenbook $ rm AppTest.java $ cd ../../../../../.. $ cd src/main/java/org/sonatype/mavenbook $ rm App.java $ mkdir weather $ cd weather Sie haben ein neues Paket com.sonatype.maven.weather erstellt. Nun sollten wir etliche Klassen in diesem Verzeichnis erstellen. Benutzen Sie hierfür Ihren Lieblings-Text-Editor und erstellen Sie eine neue Datei mit dem Namen Weather.java mit folgendem Inhalt: Example 4.4. "Simple Weather" Wetter Objekt Modell package org.sonatype.mavenbook.weather; public class Weather { private String city; private String region; private String country; private String condition; private String temp; private String chill; private String humidity; 65
  • Anpassen eines Maven Projektes public Weather() {} public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getRegion() { return region; } public void setRegion(String region) { this.region = region; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getCondition() { return condition; } public void setCondition(String condition) { this.condition = condition; } public String getTemp() { return temp; } public void setTemp(String temp) { this.temp = temp; } public String getChill() { return chill; } public void setChill(String chill) { this.chill = chill; } public String getHumidity() { return humidity; } public void setHumidity(String humidity) { this.humidity = humidity; } } Die Wetter Klasse definiert ein einfaches Bean, welches die Informationen vorhält, welche aus den vom Yahoo! Wetter-Dienst zurückgegebenen Daten geparsed werden. Der Yahoo! Wetter-Dienst bietet eine Fülle an Informationen, ausgehend von den Sonnenaufgangs und -untergangszeiten, bis hin zur Windrichtung sowie -geschwindigkeit. Im Ansinnen, die Beispiele des Buchs so einfach wie möglich zu gestalten, enthält unser Weeather-Objekt-Modell nur die Temperatur, Chill, Feuchtigkeit und sowie eine textuelle Beschreibung der gegenwärtigen Bedingungen. Im gleichen Verzeichnis erstellen Sie nun eine Datei mit dem Namen Main.java. Diese Haupt-Klasse wird die statische main()-Funktion beinhalten, welche der Einstiegspunkt dieses Beispiels ist. Example 4.5. Einfache Weather-Hauptklasse package org.sonatype.mavenbook.weather; import java.io.InputStream; import org.apache.log4j.PropertyConfigurator; 66
  • Anpassen eines Maven Projektes public class Main { public static void main(String[] args) throws Exception { // Configure Log4J PropertyConfigurator.configure(Main.class.getClassLoader() .getResource("log4j.properties")); // Read the Zip Code from the Command-line (if none supplied, use 60202) String zipcode = "60202"; try { zipcode = args[0]); } catch( Exception e ) {} // Start the program new Main(zipcode).start(); } private String zip; public Main(String zip) { this.zip = zip; } public void start() throws Exception { // Retrieve Data InputStream dataIn = new YahooRetriever().retrieve( zip ); // Parse Data Weather weather = new YahooParser().parse( dataIn ); // Format (Print) Data System.out.print( new WeatherFormatter().format( weather ) ); } } Die oben gezeigte main()-Funktion konfiguriert Log4J indem es eine Ressource (log4j.properties) auf dem Klassenpfad sucht, um anschliessend einen ZIP-Code/eine Postleitzahl von der Befehlszeile zu lesen. Wird eine während des Leseversuchs eine Exception geworfen, wird standardmässig auf die Postleitzahl 60202 zurückgegriffen. Sobald dem Programm eine Postleitzahl zugeführt wird, wird main() instanziert und deren start()-Methode aufgerufen. Die start()-Methode ruft die YahooRetriever Klasse auf, um das XML-Wetter abzurufen. Die Klasse YahooRetriever liefert einen InputStream, welcher an die Klasse YahooParser weitergeleitet wird. YahooParser analysiert das zurückgegebene Yahoo! Wetter XML und erstellt ein Weather-Objekt. Schließlich nimmt der WeatherFormatter 67
  • Anpassen eines Maven Projektes das Weather Objekt und spuckt eine formatierte Zeichenfolge aus, welche auf der Standardausgabe ausgegeben wird. Erstellen Sie nun im gleichen Verzeichnis eine Datei mit dem Namen YahooRetriever.java mit folgendem Inhalt: Example 4.6. "Simple Weather" YahooRetriever Klasse package org.sonatype.mavenbook.weather; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import org.apache.log4j.Logger; public class YahooRetriever { private static Logger log = Logger.getLogger(YahooRetriever.class); public InputStream retrieve(int zipcode) throws Exception { log.info( "Retrieving Weather Data" ); String url = "http://weather.yahooapis.com/forecastrss?p=" + zipcode; URLConnection conn = new URL(url).openConnection(); return conn.getInputStream(); } } Diese einfache Klasse öffnet eine URLConnection auf die Yahoo! Wetter-API und liefert einen InputStream. Um den InputStream verarbeiten zu können müssen wir im gleichen Verzeichnis eine Datei YahooParser.java erstellen. Example 4.7. "Simple Weather" YahooParser Klasse package org.sonatype.mavenbook.weather; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.io.SAXReader; public class YahooParser { 68
  • Anpassen eines Maven Projektes private static Logger log = Logger.getLogger(YahooParser.class); public Weather parse(InputStream inputStream) throws Exception { Weather weather = new Weather(); log.info( "Creating XML Reader" ); SAXReader xmlReader = createXmlReader(); Document doc = xmlReader.read( inputStream ); log.info( "Parsing XML Response" ); weather.setCity( doc.valueOf("/rss/channel/y:location/@city") ); weather.setRegion( doc.valueOf("/rss/channel/y:location/@region") ); weather.setCountry( doc.valueOf("/rss/channel/y:location/@country") ); weather.setCondition( doc.valueOf("/rss/channel/item/y:condition/@text") ); weather.setTemp( doc.valueOf("/rss/channel/item/y:condition/@temp") ); weather.setChill( doc.valueOf("/rss/channel/y:wind/@chill") ); weather.setHumidity( doc.valueOf("/rss/channel/y:atmosphere/@humidity") ); return weather; } private SAXReader createXmlReader() { Map<String,String> uris = new HashMap<String,String>(); uris.put( "y", "http://xml.weather.yahoo.com/ns/rss/1.0" ); DocumentFactory factory = new DocumentFactory(); factory.setXPathNamespaceURIs( uris ); SAXReader xmlReader = new SAXReader(); xmlReader.setDocumentFactory( factory ); return xmlReader; } } Der YahooParser ist die komplexeste Klasse in diesem Beispiel. Wir werden nicht in die Einzelheiten von Dom4J oder Jaxen eingehen, aber die Klasse benötigt dennoch ein wenig Erklärung. Die parse()-Methode von YahooParser nimmt einen InputStream auf und gibt ein Weather-Objekt zurück. Um dies zu tun, muss sie das XML-Dokument per DOM4J analysieren. Da wir an Elementen des Yahoo!Wetter Namespaces interessiert sind, ist es notwendig einen Namespace bewussten SAXReader in der Methode createXmlReader() zu erstellen. Sobald wir diesen Reader erstellt haben und damit das Dokument analysiert haben, bekommen wir ein org.dom4j.Document-Objekt zurück. Statt durch alle Kind-Elemente zu iterieren, adressieren wir die für uns relevanten Informationen direkt mit Hilfe 69
  • Anpassen eines Maven Projektes eines XPath-Ausdrucks. Dom4J liefert in diesem Beispiel das XML-Parsing und Jaxen die XPath-Funktionalität. Haben wir einmal ein Weather-Objekt erzeugt, müssen wir unsere Ausgabe für den Gebrauch lesbar formatieren. Erstellen Sie im Verzeichnis der andern Klassen eine Datei mit dem Namen WeatherFormatter.java. Example 4.8. "Simple Weather" WeatherFormatter Klasse package org.sonatype.mavenbook.weather; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import org.apache.log4j.Logger; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; public class WeatherFormatter { private static Logger log = Logger.getLogger(WeatherFormatter.class); public String format( Weather weather ) throws Exception { log.info( "Formatting Weather Data" ); Reader reader = new InputStreamReader( getClass().getClassLoader() .getResourceAsStream("output.vm")); VelocityContext context = new VelocityContext(); context.put("weather", weather ); StringWriter writer = new StringWriter(); Velocity.evaluate(context, writer, "", reader); return writer.toString(); } } Die Klasse WeatherFormatter verwendet Velocity, um basierend auf einer Vorlage, die Ausgabe darzustellen. Die format()-Methode nimmt eine Weather Bean auf und gibt eine formatierte Zeichenkette zurück. Das erste, was die format()-Methode macht, ist eine Velocity Vorlage namens poutput.vm vom Klassenpfad zu laden. Wir erstellen dann einen VelocityContext welcher mit einem einzigen Weather Objekt namens weather bestückt wird. Ein StringWriter wird erstellt, um die Ergebnisse mit der Vorlage zu fusionieren. Die Vorlage wird mit 70
  • Anpassen eines Maven Projektes einem Aufruf an Velocity.evaluate() ausgewertet und die Ergebnisse als String zurückgegeben. Bevor wir dieses Beispiel ausführen können, müssen wir noch einige Ressourcen auf unserem Klassenpfad erstellen. 4.7. Resourcen Hinzufügen Dieses Projekt hängt von zwei Klassenpfad Ressourcen ab: Die main()-Klasse konfiguriert Log4J mit log4j.properties vom Klassenpfad, und die WeatherFormatter Klasse greift auf eine Velocity Vorlage namens output.vm zurück. Beide Ressourcen müssen im Standard-Package (oder dem Wurzelverzeichnis des Klassenpfades) vorhanden sein. Um diese Ressourcen zuzufügen, müssen wir im Wurzelverzeichnis ein neues Verzeichnis /project-src/main/resources erstellen. Da das Verzeichnis nicht nicht vom Goal archetype:create geschaffen wurde, müssen wir dieses durch folgende Befehle aus dem Projekt Verzeichnis heraus erstellen: $ cd src/main $ mkdir resources $ cd resources Sobald das Ressourcen-Verzeichnis erstellt ist, können wir die beiden Ressourcen zufügen. Zunächst fügen Sie die Datei log4j.properties in das Ressourcen-Verzeichnis ein. Example 4.9. "Simple Weather" Log4J Konfigurationsdatei # Set root category priority to INFO and its only appender to CONSOLE. log4j.rootCategory=INFO, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=INFO log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p %c{1} %x - %m%n Diese Datei log4j.properties konfiguriert Log4J alle Log-Nachrichten mit 71
  • Anpassen eines Maven Projektes einem PatternLayout an die Standard-Ausgabe zu senden. Schließlich brauchen wir eine Velocity Vorlage output.vm um die Ausgabe der Applikation darzustellen. Erstellen Sie output.vm im Ressourcen-Verzeichnis. Example 4.10. "Simple Weather" Output-Velocity-Template ********************************* Current Weather Conditions for: ${weather.city}, ${weather.region}, ${weather.country} Temperature: ${weather.temp} Condition: ${weather.condition} Humidity: ${weather.humidity} Wind Chill: ${weather.chill} ********************************* Diese Vorlage enthält eine Reihe von Verweisen auf eine Variable namens weather. Die Variable weather ist das Weather Bean, welches an den WeatherFormatter gegeben wurde, die Syntax ${weather.temp} ist eine Kurzform um die Werte der Weather Bean aufzurufen und anzuzeigen. Nun, da wir allen Projekt-Code an den richtigen Stellen haben, können wir Maven benutzen, um dieses Beispiel aufzurufen. 4.8. Ausführen des "Simple Weather" Programms Mit dem Exec Maven Plugin des Codehouse-Mojo-Projekts können wir das Programm ausführen. Um die main()-Klasse auszuführen, geben Sie den folgenden Befehl aus dem Projekt-Basis-Verzeichnis ein: $ mvn install /$ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main ... [INFO] [exec:java] 0 INFO YahooRetriever - Retrieving Weather Data 134 INFO YahooParser - Creating XML Reader 333 INFO YahooParser - Parsing XML Response 420 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: 72
  • Anpassen eines Maven Projektes Evanston, IL, US Temperature: 45 Condition: Cloudy Humidity: 76 Wind Chill: 38 ********************************* ... We didn’t supply a command-line argument to the Main class, so we ended up with the default zip code, 60202. To supply a zip code, we would use the -Dexec.args argument and pass in a zip code: $ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main -Dexec.args="70112" ... [INFO] [exec:java] 0 INFO YahooRetriever - Retrieving Weather Data 134 INFO YahooParser - Creating XML Reader 333 INFO YahooParser - Parsing XML Response 420 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: New Orleans, LA, US Temperature: 82 Condition: Fair Humidity: 71 Wind Chill: 82 ********************************* [INFO] Finished at: Sun Aug 31 09:33:34 CDT 2008 ... Da wir kein Kommandozeilen-Argument angegeben haben, wurde die Anwendung mit der Standard-Postleitzahl "60202" ausgeführt. Wie Sie sehen können, haben wir das "Simple Weather" Tool erfolgreich aufgerufen, Daten von Yahoo! Wetter abgerufen, das Ergebnis analysiert, formatiert und die sich daraus ergebenden Daten mit Hilfe von Velocity ausgegeben. Wir haben all dies erreicht, ohne dabei viel mehr zu tun als unseres Projekt's Source-Code erstellen, sowie minimale Konfiguration an der pom.xml vorzunehmen. Beachten Sie, dass kein "Build-Prozess" beteiligt war. Wir mussten nicht festlegen wie oder wo die Java-Compiler unseren Quellecode kompiliert, wir haben nichts zu tun, um das Build-System zu finden, anzugeben wie der Bytecode ausgeführt werden soll um die Beispiel-Anwendung auszuführen. Alles, was notwendig war, um etliche Abhängigkeiten einzufügen war, die entsprechenden Maven Koordinaten zu finden 73
  • Anpassen eines Maven Projektes und in der pom.xml Datei einzutragen. 4.8.1. Das Exec Maven Plugin Der Exec-Plugin ermöglicht Ihnen die Ausführung von Java-Klassen und andere Skripten. Es ist keines der Kern- oder Core-Maven Plugins, aber es steht Ihnen vom Mojo Projekt gehostet von Codehaus zur Verfügung. Für eine vollständige Beschreibung der Exec-Plugins, führen Sie: $ mvn help:describe -Dplugin=exec -Dfull aus Es wird alle Goals, welche das Exec Maven Plugin zur Verfügung stellt, auflisten. Das Help Plugin, wird auch alle gültigen Parameter des Exec-Plugins aufzeigen, sollten Sie dessen Verhalten anpassen wollen. Um dem Exec Plugin Kommandozeilenargumente mitzugeben, sehen Sie in der Dokumentation des Plugins nach, welche von help:describe wiedergegeben wird. Obschon das Exec-Plugin nützlich ist, solltens Sie es nicht ausserhalb der laufenden Tests während der Entwicklungsphase zur Ausführung von Anwendungen benutzen. Als robustere Lösung, verwenden Sie das Maven Plugin Assembly welches im Abschnitt 4.13: "Erstellen einer packetierten Kommandozeilen Anwendung" eingeführt wird. 4.8.2. Erkundung der Projekt Abhängigkeiten Das Exec-Plugin ermöglicht es uns, die Anwendung auszuführen, ohne die Abhängigkeiten explizit in den Klassenpfad aufzunehmen. In jedem anderen Build-System, wäre es notwendig geworden, alle Abhängigkeiten in eine Art /lib-Verzeichnis zu kopieren, welches eine Sammlung von JAR-Dateien enthält. Dann hätten wir ein einfaches Skript, welches den Bytecode des Programms sowie alle unsere Abhängigkeiten in den Klassenpfad einschliesst. Nur dann könnten wir java com.sonatype.maven.weather.Main ausführen. Das Exec Plugin nutzt die Tatsache, dass Maven bereits um die Erstellung und Verwaltung Ihres Klassenpfades und der Abhängigkeiten weiss. 74
  • Anpassen eines Maven Projektes Während dies ist zwar bequem ist, ist es dennoch schön zu wissen, was genau in Ihren Klassenpfad eingeschlossen wird. Während des Projekt direkt nur von wenigen Bibliotheken wie Dom4J, Log4J, Jaxen, und Velocity abhängt, stützen diese sich auf weitere, transitive Abhängigkeiten. Um herauszufinden, wie Ihr Klassenpfad tatsächlich aussieht, können Sie das Dependency Maven Plugin zur Ausgabe einer Liste der aufgelösten Abhängigkeiten benutzen. Um die Liste der Abhängigkeiten des "Simple Weather" Projekts auszugeben, führen Sie das Goal dependency:resolve aus. $ mvn dependency:resolve ... [INFO] [dependency:resolve] [INFO] [INFO] The following files have been resolved: [INFO] com.ibm.icu:icu4j:jar:2.6.1 (scope = compile) [INFO] commons-collections:commons-collections:jar:3.1 (scope = compile) [INFO] commons-lang:commons-lang:jar:2.1 (scope = compile) [INFO] dom4j:dom4j:jar:1.6.1 (scope = compile) [INFO] jaxen:jaxen:jar:1.1.1 (scope = compile) [INFO] jdom:jdom:jar:1.0 (scope = compile) [INFO] junit:junit:jar:3.8.1 (scope = test) [INFO] log4j:log4j:jar:1.2.14 (scope = compile) [INFO] oro:oro:jar:2.0.8 (scope = compile) [INFO] velocity:velocity:jar:1.5 (scope = compile) [INFO] xalan:xalan:jar:2.6.0 (scope = compile) [INFO] xerces:xercesImpl:jar:2.6.2 (scope = compile) [INFO] xerces:xmlParserAPIs:jar:2.6.2 (scope = compile) [INFO] xml-apis:xml-apis:jar:1.0.b2 (scope = compile) [INFO] xom:xom:jar:1.0 (scope = compile) Wie Sie sehen, hat unser Projekt eine große Zahl von Abhängigkeiten. Auch wenn wir nur direkte Abhängigkeiten von vier Bibliotheken angegeben haben, scheinen insgesamt 15 Abhängigkeiten zu bestehen. Dom4J hängt von Xerces und dem XML-Parser-APIs ab, Jaxen hängt davon ab, dass Xalan sich im Klassenpfad befindet. Das Dependency Maven Plugin wird ihnen die endgültige Kombination von Abhängigkeiten unter welchen Ihr Projekt kompiliert wird ausgeben. Wollen Sie den gesamten Baum aller Abhängigkeiten sehen, so können Sie das Goal dependency:tree aufrufen: $ mvn dependency:tree ... [INFO] [dependency:tree] [INFO] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0 [INFO] +- log4j:log4j:jar:1.2.14:compile 75
  • Anpassen eines Maven Projektes [INFO] +- dom4j:dom4j:jar:1.6.1:compile [INFO] | - xml-apis:xml-apis:jar:1.0.b2:compile [INFO] +- jaxen:jaxen:jar:1.1.1:compile [INFO] | +- jdom:jdom:jar:1.0:compile [INFO] | +- xerces:xercesImpl:jar:2.6.2:compile [INFO] | - xom:xom:jar:1.0:compile [INFO] | +- xerces:xmlParserAPIs:jar:2.6.2:compile [INFO] | +- xalan:xalan:jar:2.6.0:compile [INFO] | - com.ibm.icu:icu4j:jar:2.6.1:compile [INFO] +- velocity:velocity:jar:1.5:compile [INFO] | +- commons-collections:commons-collections:jar:3.1:compile [INFO] | +- commons-lang:commons-lang:jar:2.1:compile [INFO] | - oro:oro:jar:2.0.8:compile [INFO] +- org.apache.commons:commons-io:jar:1.3.2:test [INFO] - junit:junit:jar:3.8.1:test ... Sollten Sie wirklich abenteuerlich sein, oder einfach nur die volle Liste der Abhängigkeiten sehen wollen, einschliesslich aller Artefakte, die aufgrund von Konflikten oder anderen Gründen abgelehnt wurden, führen Sie Maven mit dem Debug-Flag aus. $ mvn install -X ... [DEBUG] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0 (selected for null) [DEBUG] log4j:log4j:jar:1.2.14:compile (selected for compile) [DEBUG] dom4j:dom4j:jar:1.6.1:compile (selected for compile) [DEBUG] xml-apis:xml-apis:jar:1.0.b2:compile (selected for compile) [DEBUG] jaxen:jaxen:jar:1.1.1:compile (selected for compile) [DEBUG] jaxen:jaxen:jar:1.1-beta-6:compile (removed - ) [DEBUG] jaxen:jaxen:jar:1.0-FCS:compile (removed - ) [DEBUG] jdom:jdom:jar:1.0:compile (selected for compile) [DEBUG] xml-apis:xml-apis:jar:1.3.02:compile (removed - nearer: 1.0.b2) [DEBUG] xerces:xercesImpl:jar:2.6.2:compile (selected for compile) [DEBUG] xom:xom:jar:1.0:compile (selected for compile) [DEBUG] xerces:xmlParserAPIs:jar:2.6.2:compile (selected for compile) [DEBUG] xalan:xalan:jar:2.6.0:compile (selected for compile) [DEBUG] xml-apis:xml-apis:1.0.b2. [DEBUG] com.ibm.icu:icu4j:jar:2.6.1:compile (selected for compile) [DEBUG] velocity:velocity:jar:1.5:compile (selected for compile) [DEBUG] commons-collections:commons-collections:jar:3.1:compile (selected for compile) [DEBUG] commons-lang:commons-lang:jar:2.1:compile (selected for compile) [DEBUG] oro:oro:jar:2.0.8:compile (selected for compile) [DEBUG] junit:junit:jar:3.8.1:test (selected for test) In der Debug-Ausgabe sehen wir die Eingeweide des Abhängigkeit-Management-Systems bei der Arbeit. Was Sie hier sehen, ist der vollständige Baum aller Abhängigkeiten für dieses Projekt. Maven listet die vollen 76
  • Anpassen eines Maven Projektes Maven Koordinaten für alle Ihre Projekt-Abhängigkeiten und die Abhängigkeiten derer Abhängigkeiten (und die Abhängigkeiten der Abhängigkeiten der Abhängigkeiten). Sie können ersehen, dass "Simple Weather" von Jaxen abhängt, welches von xom abhängig ist was wiederum auf icu4j aufbaut. Aus dieser Ausgabe können Sie erkennen, dass Maven einen Graphen der Abhängigkeiten erstellt, hierbei Duplikate eliminiert und etwaige Versionskonflikte löst. Sollten Sie bezüglich Abhängigkeiten auf Probleme stossen, so ist es oft hilfreich ein wenig tiefer im Baum den Abhängigkeiten zu schürfen. Durch Einschalten der Debugausgabe können Sie den Maven Abhängigkeits-Mechanismus bei der Arbeit sehen. 4.9. Erstellen von Unit-Tests Maven hat eine eingebaute Unterstützung für Unit-Tests. Testen ist ein Teil des ursprünglichen Maven Lebenszyklus. Lassen Sie uns ein paar Unit-Tests zum "Simple Weather" Projekt hinzufügen. Zuerst werden wir ein Package com.sonatype.maven.weather unter /src/test/java erstellen. $ cd src/test/java $ cd org/sonatype/mavenbook $ mkdir -p weather/yahoo $ cd weather/yahoo An dieser Stelle werden wir zwei Unit-Tests erstellen. Der erste Unit Test wird die Klasse YahooParser, der zweite die WeatherFormatter Klasse testen. Erstellen Sie eine Datei mit dem Namen YahooParserTest.java mit folgendem Inhalt im Package weather: Example 4.11. "Simple Weather" YahooParserTest Unit-Test package org.sonatype.mavenbook.weather.yahoo; import java.io.InputStream; import junit.framework.TestCase; import org.sonatype.mavenbook.weather.Weather; import org.sonatype.mavenbook.weather.YahooParser; 77
  • Anpassen eines Maven Projektes public class YahooParserTest extends TestCase { public YahooParserTest(String name) { super(name); } public void testParser() throws Exception { InputStream nyData = getClass().getClassLoader().getResourceAsStream("ny-weather.xml"); Weather weather = new YahooParser().parse( nyData ); assertEquals( "New York", weather.getCity() ); assertEquals( "NY", weather.getRegion() ); assertEquals( "US", weather.getCountry() ); assertEquals( "39", weather.getTemp() ); assertEquals( "Fair", weather.getCondition() ); assertEquals( "39", weather.getChill() ); assertEquals( "67", weather.getHumidity() ); } } Dieser YahooParserTest erweitert die von JUnit definierte Klasse TestCase. Es folgt das übliche Muster für einen JUnit-Test: ein Konstruktor, der ein einziges String Argument annimmt und den Konstruktor der Superklasse aufruft und eine Reihe von public Methoden welche im Namen mit "test" beginnen, welche als Unit-Tests aufgerufen werden. Wir definieren ein einfaches Testvorgehen: testParser, welches den YahooParser überprüft, indem es ein XML-Dokument mit bekannten Werten auswertet. Das Test XML-Dokument heisst ny-weather.xml und wird aus dem Klassenpfad geladen. Wir fügen Test Ressourcen in Abschnitt 4.11: "Hinzufügen von Unit Test Ressourcen" an. In unserem Verzeichnisaufbau des Maven-Projekts befindet sich die Datei ny-weather.xml im Verzeichnis der Test-Ressourcen -- ${basedir}/src/test/resources unter /com/sonatype/maven/wetter/yahoo/ny-weather.xml . Die Datei wird als InputStream eingelesen und an die parse()-Methode des YahooParser geleitet. Die parse()-Methode liefert ein Weather Objekt zurück,welches über eine Anzahl von assertEquals() Aufrufen, eine innerhalb der Klasse TestCase definierte Methode, geprüft wird. Im gleichen Verzeichnis erstellen Sie eine Datei mit dem Namen WeatherFormatterTest.java. 78
  • Anpassen eines Maven Projektes Example 4.12. "Simple Weather" WeatherFormatterTest Unit-Test package org.sonatype.mavenbook.weather.yahoo; import java.io.InputStream; import org.apache.commons.io.IOUtils; import org.sonatype.mavenbook.weather.Weather; import org.sonatype.mavenbook.weather.WeatherFormatter; import org.sonatype.mavenbook.weather.YahooParser; import junit.framework.TestCase; public class WeatherFormatterTest extends TestCase { public WeatherFormatterTest(String name) { super(name); } public void testFormat() throws Exception { InputStream nyData = getClass().getClassLoader().getResourceAsStream("ny-weather.xml"); Weather weather = new YahooParser().parse( nyData ); String formattedResult = new WeatherFormatter().format( weather ); InputStream expected = getClass().getClassLoader().getResourceAsStream("format-expected.dat"); assertEquals( IOUtils.toString( expected ).trim(), formattedResult.trim() ); } } Der zweite Unit Test dieses einfachen Projekts testet die Klasse WeatherFormatter. Wie bereits die Klasse YahooParserTest, erweitert auch die Klasse WeatherFormatterTest die Klasse TestCase des JUnit Frameworks. Die einzige Testfunktion liest die gleiche Testressource von ${basedir}/src/test/resources umter dem /com/sonatype/Maven/Wetter/yahoo-Verzeichnis unter Einbezug des Unit Test Klassenpfades. Wir fügen Test Ressourcen wie im Abschnitt 4.11: "Hinzufügen Unit Test Ressourcen" beschrieben an. WeatherFormatterTest zieht diese Beispiel-Eingabedatei durch den YahooParser an, welcher ein Weather Objekt zurückgibt an, dieses Objekt wird dann mit dem WeatherFormatter ausgegeben. Da WeatherFormatter eine Zeichenkette ausgibt, müssen wir gegen eine gesetzte Eingabe testen. Unser "gesetzter" Input wurde einer Text-Datei mit 79
  • Anpassen eines Maven Projektes dem Namen format-expected.dat erfasst, welche sich im gleichen Verzeichnis wie ny-weather.xml befindet. Um die Test-Ausgabe mit der erwarteten Ausgabe zu vergleichen, wird diese als InputStream eingelesen und unter Einbezug der Klasse IOUtils aus Apache Commons IO in einen String umgewandelt. Diese Zeichenfolge wird dann mittels assertEquals() mit der Testausgabe verglichen. 4.10. Hinzufügen von Gebietsbezogenen Unit Tests In der Klasse WeatherFormatterTest benutzten wir ein Dienstprogramm aus Apache Commons IO- die Klasse IOUtils. IOUtils bietet eine Reihe von hilfreichen statische Funktionen, welche den Grossteil der Arbeit der Input/Output-Operationen übernehmen. In diesem speziellen Unit Test benutzen wir IOUtils.toString() um die format-expected.dat Klassenpfadressource in einen String zu verwandeln. Wir hätten dies auch ohne den Einsatz von Commons IO bewerkstelligen können, aber es hätte zusätzliche sechs oder sieben Zeilen Code bedingt um mit den verschiedenen InputStreamReader- und StringWriter-Objekten umzugehen. Der Hauptgrund für den Einsatz der Commons IO Bibliothek war, uns eine Ausrede zu geben, Test-scoped (abgegrenzte) Abhängigkeiten am Beispiel von Commons IO einzuführen. Eine Test-scoped Abhängigkeit ist eine Abhängigkeit, welche nur auf während der Testkompilierung und Testausführung auf dem Klassenpfad eingeschlossen ist. Sollte Ihr Projekt als war- oder ear-Archiv packetiert werden, so würde eine Test-scoped Abhängigkeit nicht in das Projekt-Output-Archiv einbezogen werden. Um eine Test-scoped Abhängigkeit zu erstellen, fügen Sie das folgende dependency-Element in Ihrem Projekt in das Element dependencies ein . Example 4.13. Das Hinzufügen einer Test-scoped Abhängigkeit <project> ... <dependencies> ... <dependency> <groupId>org.apache.commons</groupId> 80
  • Anpassen eines Maven Projektes <artifactId>commons-io</artifactId> <version>1.3.2</version> <scope>test</scope> </dependency> ... </dependencies> </project> Nachdem Sie diese Abhängigkeit der pom.xml Datei zugefügt haben, führen Sie mvn dependency:resolve aus: Sie sollten sehen, dass commons-io nun als eine Abhängigkeit mit Gebietsbezug test aufgeführt wird. Noch eine Kleinigkeit wird benötigt, bevor wir bereit sind diese Projekt Unit-Tests auszuführen. Wir müssen die Klassenpfad Ressourcen erstellen, von welchen diese Unit-Tests abhängen. Geltungsbereiche werden ausführlich im Abschnitt 9.4.1: "Geltungsbereiche von Abhängigkeiten" dargestellt. 4.11. Hinzufügen einer Unit-Test Ressource Ein Unit Test hat Zugriff auf eine Reihe von testspezifischen Ressourcen. Häufig werden Sie Dateien mit den erwarteten Ergebnissen und Dummy-Dateien mit Eingabetexten auf dem Testklassenpfad ablegen. In diesem Projekt legen wir ein Test XML-Dokument für den YahooParserTest namens ny-weather.xml sowie eine Datei mit der erwarteten Ausgabe des WeatherFormatter unter format-expected.dat ab. Um Test Ressourcen einzufügen, müssen Sie zunächst das Verzeichnis /src/test/resources erstellen. Dies ist das Standard-Verzeichnis, in welchem Maven nach Unit-Test Ressourcen sucht. Erstellen Sie dieses Verzeichnis indem Sie die folgenden Befehle aus Ihrem Projekt-Basis-Verzeichnis heraus ausführen. $ cd src/test $ mkdir resources $ cd resources Sobald Sie das Ressourcen-Verzeichnis erstellt haben, erstellen Sie eine Datei mit dem Namen format-expected.dat in diesem Verzeichnis. 81
  • Anpassen eines Maven Projektes Example 4.14. "Simple Weather" WeatherFormatterTest erwartete Ausgabe ********************************* Current Weather Conditions for: New York, NY, US Temperature: 39 Condition: Fair Humidity: 67 Wind Chill: 39 ********************************* Diese Datei sollte vertraut aussehen. Es ist die gleiche Ausgabe, welche generiert wurde, als Sie das "Simple Weather" Projekt mit dem Exec Maven Exec starteten. Die zweite Datei welche Sie benötigen werden und daher im Ressourcen-Verzeichnis erstellen sollten ist ny-weather.xml. Example 4.15. "Simple Weather" YahooParserTest XML-Eingabe <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"> <channel> <title>Yahoo! Weather - New York, NY</title> <link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link> <description>Yahoo! Weather for New York, NY</description> <language>en-us</language> <lastBuildDate>Sat, 10 Nov 2007 8:51 pm EDT</lastBuildDate> <ttl>60</ttl> <yweather:location city="New York" region="NY" country="US" /> <yweather:units temperature="F" distance="mi" pressure="in" speed="mph" /> <yweather:wind chill="39" direction="0" speed="0" /> <yweather:atmosphere humidity="67" visibility="1609" pressure="30.18" rising="1" /> <yweather:astronomy sunrise="6:36 am" sunset="4:43 pm" /> <image> <title>Yahoo! Weather</title> <width>142</width> <height>18</height> <link>http://weather.yahoo.com/</link> <url>http://l.yimg.com/us.yimg.com/i/us/nws/th/main_142b.gif</url> </image> <item> <title>Conditions for New York, NY at 8:51 pm EDT</title> 82
  • Anpassen eines Maven Projektes <geo:lat>40.67</geo:lat> <geo:long>-73.94</geo:long> <link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link> <pubDate>Sat, 10 Nov 2007 8:51 pm EDT</pubDate> <yweather:condition text="Fair" code="33" temp="39" date="Sat, 10 Nov 2007 8:51 pm EDT" /> <description><![CDATA[ <img src="http://l.yimg.com/us.yimg.com/i/us/we/52/33.gif" /><br /> <b>Current Conditions:</b><br /> Fair, 39 F<BR /><BR /> <b>Forecast:</b><BR /> Sat - Partly Cloudy. High: 45 Low: 32<br /> Sun - Sunny. High: 50 Low: 38<br /> <br /> ]]></description> <yweather:forecast day="Sat" date="10 Nov 2007" low="32" high="45" text="Partly Cloudy" code="29" /> <yweather:forecast day="Sun" date="11 Nov 2007" low="38" high="50" text="Sunny" code="32" /> <guid isPermaLink="false">10002_2007_11_10_20_51_EDT</guid> </item> </channel> </rss> Diese Datei enthält ein Test XML-Dokument für den YahooParserTest. Wir speichern diese Datei, um den YahooParser zu testen ohne eine XML-Antwort vom Yahoo! Wetterdienst abzurufen. 4.12. Ausführen von Unit-Tests Nun, da die Unit Tests zu Ihrem Projekt bestehen, führen wir diese aus! Sie brauchen nichts weiter zu tun um die Unit Tests anzustossen: die Testphase ist ein normaler Bestandteil des Maven Lifecycles. Die Maven Tests werden automatisch ausgeführt sobald Sie mvn package oder mvn install aufrufen. Wenn Sie möchten, dass alle Phasen des Lebenszyklus bis einschließlich der Testphase ausgeführt werden, rufen Sie mvn test auf. $ mvn test ... [INFO] [surefire:test] [INFO] Surefire report directory: ~/examples/simple-weather/target/ surefire-reports 83
  • Anpassen eines Maven Projektes ------------------------------------------------------- T E S T S ------------------------------------------------------- Running org.sonatype.mavenbook.weather.yahoo.WeatherFormatterTest 0 INFO YahooParser - Creating XML Reader 177 INFO YahooParser - Parsing XML Response 239 INFO WeatherFormatter - Formatting Weather Data Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.547 sec Running org.sonatype.mavenbook.weather.yahoo.YahooParserTest 475 INFO YahooParser - Creating XML Reader 483 INFO YahooParser - Parsing XML Response Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec Results : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 Der Aufruf von mvn test von der Befehlszeile veranlasst Maven zur Ausführung aller Phasen des Lebenszyklus' bis und mit der Testphase. Das Surefire Maven Plugin hat ein Goal test, welches an die Testphase gebunden ist. Dieses Goal test führt alle Unit-Tests des Projektes aus, welche sich unter /src/test/java befinden und einen Dateinamen von **/Test*.java, **/*Test.java sowie **/*TestCase.java haben. Für unser Projekt können Sie sehen, dass das Surefire Plugin mit dem Goal test die Unittests WeatherFormatterTest und YahooParserTest ausgeführt hat. Bei der Ausführung des Surefire Maven Plugins werden nicht nur die Tests ausgeführt, es werden auch XML- und Text-Berichte erzeugt und unter dem Verzeichnis ${basedir}/target/surefire-reports abgelegt. Sollten Ihre Tests fehlschlagen, so können Sie in diesem Verzeichnis von den Unit Tests angelegte Detailsangaben wie Fehlermeldungen oder Stack Traces der Unit Tests finden. 4.12.1. Ignorieren fehlgeschlagener Unit Tests Oft werden Sie an einem System arbeiten dessen Unit Tests scheitern. Sollten Sie Test-Driven Development (TDD) praktizieren, so ist die Anzahl der gescheiterten Tests ein Mass dafür, wie nahe der Vollendung Ihr Projekt ist. Sollten Sie trotz gescheiterter Unit-Tests dennoch gerne ein Produkt erstellen, so müssen Sie Maven anweisen, diese Unit Tests zu ignorieren. Das Standardverhalten von Maven ist es, nach einem gescheiterten Test den Build abzubrechen. Um den Build auch bei 84
  • Anpassen eines Maven Projektes auftreten gescheiterter Tests weiterzuführen, müssen sie den Konfigurationseintrag testFailureIgnore des Surefire Maven Plugins auf "true" setzen. Example 4.16. Ignorieren von gescheiterten Unit-Test Fällen <project> [...] <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> </plugins> </build> [...] </project> Die Plugin Dokumentation (http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html) zeigt, dass dieser Parameter einen Ausdruck erklärt: Example 4.17. Plugin-Parameter-Ausdrücke testFailureIgnore Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite convenient on occasion. * Type: boolean * Required: No * Expression: ${maven.test.failure.ignore} // testFailureIgnore Setzen Sie diesen Parameter auf "true" um scheiternde Testfälle // Die Benutzung des Parameters wird NICHT EMPFOHLEN, kann aber zu Zeiten recht nütz Dieser Ausdruck kann von der Befehlszeile mit dem 'D-Parameter' gesetzt werden: $ mvn test -Dmaven.test.failure.ignore=true 85
  • Anpassen eines Maven Projektes 4.12.2. Überspringen von Unit-Tests Sie können Maven auch so konfigurieren dass bestimmte Unit Tests ganz übersprungen werden. Vielleicht arbeiten Sie an einem sehr großen System, in welchem die Unit-Tests Minuten in Anspruch nehmen und Sie wollen nicht warten bis alle Unit-Tests abgeschlossen sind, um ein Produkt zu erstellen. Möglicherweise müssen Sie mit einem Legacy-System arbeiten, welches eine Reihe von scheiternden Unit-Tests umfasst. Anstelle der Behebung der Unit-Test Fehlern möchten Sie lediglich ein JAR-Archive erstellen. Maven sieht für diese Fälle vor, und stellt die Fähigkeit mit dem Parameter skip des Surefire Plugins zum überspringen von Unit-Tests bereit. Um beim Aufruf von der Befehlszeile die Unit Tests zu überspringen, fügen Sie einfach den Parameter maven.test.skip Ihrem angestrebten Goal an: $ mvn install -Dmaven.test.skip=true ... [INFO] [compiler:testCompile] [INFO] Not compiling test sources [INFO] [surefire:test] [INFO] Tests are skipped. ... Wenn das Surefire Plugin das Goal test erreicht, überspringt es die Unit-Tests, sollte der Parameter maven.test.skip auf "true" gesetzt sein. Eine weitere Art Maven zu konfigurieren Unit Tests zu überspringen ist, diese Konfiguration in Ihrer Projekt pom.xml-Datei vorzunehmen. Um dies zu tun, würde man ein Element plugin Ihrer Build Konfiguration zufügen. Example 4.18. Überspringen von Unit-Tests <project> [...] <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> 86
  • Anpassen eines Maven Projektes </build> [...] </project> 4.13. Builden einer paketierten, Befehlszeilen orientierten Anwendung In Abschnitt 4.8, "Ausführen des "Simple Weather" Programms", haben wir die "Simple Weather" Anwendung unter Verwendung des Exec Maven Plugins ausgeführt. Auch wenn das Exec Plugin die Anwedung ausgeführt und und Ausgaben erzeugt hat, sollte man Maven nicht als Container zur Ausführung Ihrer Anwendungen ansehen. Sollten Sie Ihre Anwendung an andere weitergeben wollen, so werden sie dies sicher als JAR-Archive, oder als Archiv in ZIP oder GZIP Format tun wollen. Das folgende Vorgehen gibt einen Überblick über den Prozess mit Hilfe einer vordefinierter Assembly Definitionsdatei sowie dem Assembly Maven Plugin ein verteilbares JAR Archive zu erstellen, welches den Anwendungs-(Byte) Code sowie alle notwendigen Abhängigkeiten enthält. Das Assembly Maven Plugin ist ein Plugin welches Sie zur Zusammenstellung beliebiger Pakete zur Verteilung Ihrer Anwendung benutzen können. Sie können das Assembly Plugin dazu einsetzen, das Resultat in beliebiger Form zusammenzustellen, indem Sie eine darauf zugeschnittene Assembly Definitionsdatei erstellen. In einem späteren Kapitel werden wir Ihnen zeigen wie man eine zugeschnittene Assembly Definitionsdatei erstellt, um die "Simple Weather" in einem komplexeren Archiv zu deployen. In diesem Kapitel werden wir das vordefinierte Format jar-with-dependencies einsetzen. Um das Assembly Maven Plugin einzusetzen, müssen wir die folgende Anpassung an der bestehenden Konfiguration in der pom.xml-Datei vornehmen: Example 4.19. Konfigurieren der Assembly Maven Definition <project> [...] <build> <plugins> 87
  • Anpassen eines Maven Projektes <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </build> [...] </project> Sobald Sie die Konfigurationsänderung hinzugefügt haben, starten Sie den Build mit dem Aufruf mvn assembly:assembly. $ mvn install assembly:assembly ... [INFO] [jar:jar] [INFO] Building jar: ~/examples/simple-weather/target/simple-weather-1.0.jar [INFO] [assembly:assembly] [INFO] Processing DependencySet (output=) [INFO] Expanding: .m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar into /tmp/archived-file-set.1437961776.tmp [INFO] Expanding: .m2/repository/commons-lang/commons-lang/2.1/ commons-lang-2.1.jar into /tmp/archived-file-set.305257225.tmp ... (Maven Expands all dependencies into a temporary directory) ... [INFO] Building jar: ~/examples/simple-weather/target/ simple-weather-1.0-jar-with-dependencies.jar Nach dem Abarbeiten befindet sich unser Ergebnis im Verzeichnis /target in der Datei simple-weather-1.0-jar-with-dependencies.jar. Wieder können wir die main()-Klasse ausführen. Um die "Simple Weather" aufzurufen, geben sie folgenden Befehl auf der Kommandozeile im Projekt-Basis-Verzeichnis ein: $ cd target $ java -cp simple-weather-1.0-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 10002 0 INFO YahooRetriever - Retrieving Weather Data 221 INFO YahooParser - Creating XML Reader 399 INFO YahooParser - Parsing XML Response 474 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: New York, NY, US 88
  • Anpassen eines Maven Projektes Temperature: 44 Condition: Fair Humidity: 40 Wind Chill: 40 ********************************* Das Format 'jar-with-dependencies' erstellt ein einziges JAR-Archiv, welches allen Bytecode der "Simple Weather" Anwendung sowie den entpackten Bytecode aller Abhängigkeiten enthält. Dieses etwas unkonventionelle Format eines Archivs kommt in einer 9 MiB Datei mit rund 5290 Klassen daher, aber dies ist ein einfaches Format, um Anwendungen welche Sie mit Maven entwickelt haben zu verteilen. Später in diesem Buch, werden wir Ihnen zeigen, wie Sie eine benutzerdefinierte Assembly Definitionsdatei erstellen um eine gewöhnliche Standard-Distribution zu erstellen. 4.13.1. Anbinden des Assembly Goals zur Packetierungs Phase Unter Maven 1 wurde ein Build massgeschneidert indem man eine Anzahl von Plugin Goals direkt aneinanderknüpfte. Jedes Plugin Goal hatte Vorbedingungen und definierte einen Bezug zu anderen Plugin Goals. Mit der Ankunft von Maven 2 wurde das Lebenszykluskonzept eingeführt, und Plugin Goals sind nun zu einer Anzahl Phasen zugeordnet, welche in einem Standard Lebenszyklus bestehen. Das Lebenszykluskonzept ist eine solide Grundlage, welche es vereinfacht, die Abfolge der Plugin Goals zu bestimmen und zu koordinieren welche Goals in einem bestimmten Build ausgeführt werden. Unter Maven 1 war der Bezug zwischen den Goals direkt, unter Maven 2 besteht der Bezug von Plugin Goals immer zu einer Menge von gemeinsamen Lebenszyklus Phasen. Obschon es zulässig ist, ein Goal direkt - wie beschrieben - von der Kommandozeile auszuführen, steht es eher im Einklang mit der Architektur von Maven 2, das Assembly mit dem Aufruf des Goals assembly:assembly während einer Phase des Maven Lebenszykluses aufzurufen. Die folgenden Plugin-Konfiguration konfiguriert das Maven Plugin Assembly, um das angefügte Goal während der Phase package des Maven Standard 89
  • Anpassen eines Maven Projektes Lebenszykluses auszuführen. Das angefügte Goal führt das gleiche aus, wie schon das Goal assembly. Um das Goal assembly:attached der Phase package zuzuordnen benutzen wir das Element executions innerhalb des Elements plugin der Sektion build des Projekt POMs. Example 4.20. Anbinden des Assembly Goals zur Packetierungs Phase <project> [...] <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> <executions> <execution> <id>simple-command</id> <phase>package</phase> <goals> <goal>attached</goal> </goals> </execution> </executions> </plugins> </build> [...] </project> Sobald Sie diese Änderung Ihres POMs angefügt haben müssen Sie nur noch mvn package aufrufen, um Ihr Assembly zu generieren. Die Konfiguration der execution wird sicherstellen, dass das Goal assembly:attached ausgeführt wird, sobald Maven in die Lebenszyklusphase package eintritt. 90
  • Chapter 5. Eine einfache Web-Anwendung 5.1. Einleitung In diesem Kapitel erstellen wir mit Hilfe des Archetype Maven Plugin eine einfache Web-Anwendung. Wir führen diese Web-Applikation ein, benutzen hierfür einen Servlet-Container Jetty, fügen einige Abhängigkeiten ein, schreiben ein einfaches Servlet und erzeugen ein WAR-Archiv. Am Ende dieses Kapitels, werden Sie in der Lage sein Maven zur Beschleunigung der Entwicklung von Web-Applikationen einzusetzen. 5.1.1. Herunterladen der Beispiele dieses Kapitels Das Beispiel in diesem Kapitel wird mit dem Archetype Maven Plugin erzeugt. Während Sie in der Lage sein sollten, der Entwicklung dieses Kapitels ohne Beispiel-Quellcode zu folgen, empfehlen wir das Herunterladen einer Kopie der Quellcodes als Referenz. Das Beispielprojekt dieses Kapitel zusammen mit den anderen Beispielen dieses Buchs kann von http://books.sonatype.com/maven-book/mvn-examples-1.0.zip oder http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie dann zum Verzeichnis /ch05. Darin finden Sie ein Verzeichnis mit dem Namen /simple web welches den Quellcode für dieses Kapitel enthält. 5.2. Eine kurze Einführung in die "Simple Web" Anwendung Wir haben dieses Kapitel gezielt auf Plain-Old Web Applikationen (POWA) konzentriert - ein Servlet und eine JSP-Seite. Wir werden Ihnen in den nächsten 20 Seiten sicher nicht zeigen wie Sie Ihre Struts 2, Tapestry, Wicket, JSF, oder Waffle Anwendung erstellen, auch nicht wie Sie die Integration eines IoC Containers wie 91
  • Eine einfache Web-Anwendung Plexus, Guice oder Spring Framework bewerkstelligen. Das Ziel dieses Kapitels ist es, Ihnen die grundlegenden Möglichkeiten welche Maven zur Entwicklung von Web-Anwendungen bietet vorzustellen. Nicht mehr und nicht weniger! Später in diesem Buch, werden wir einen Blick auf die Entwicklung von zwei Web-Anwendungen werfen: einer, welche Hibernate, Velocity sowie das Spring Framework nutzt, und eine andere unter Verwendungen von Plexus. 5.3. Erstellen des "Simple Web" Projekts Um das Web Applikationsprojekt zu erstellen, führen Sie mvn archetype:create mit einer artifactId und einer groupId aus. Wählen Sie die archetypeArtifactId maven-archetype-webapp. Bei der Ausführung wird die entsprechende Verzeichnis-Struktur und das grundlegende Maven POM erstellt. ~/examples$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch05 -DartifactId=simple-webapp -DpackageName=org.sonatype.mavenbook -DarchetypeArtifactId=maven-archetype-webapp [INFO] [archetype:create] [INFO] ---------------------------------------------------------------------- [INFO] Using parameters for creating Archetype: maven-archetype-webapp:RELEASE [INFO] ---------------------------------------------------------------------- [INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch05 [INFO] Parameter: packageName, Value: org.sonatype.mavenbook [INFO] Parameter: basedir, Value: ~/examples [INFO] Parameter: package, Value: org.sonatype.mavenbook [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: artifactId, Value: simple-webapp [INFO] *************** End of debug info from resources from generated POM ** [INFO] Archetype created in dir: ~/examples/simple-webapp Sobald das Archetype Maven Plugin das Projekt erstellt hat, gehen Sie in das Verzeichnis /simple-web und werfen Sie einen Blick auf die dort abgelegte pom.xml-Datei. Sie sollten das folgenden XML-Dokument vorfinden: Example 5.1. Erstes POM des "Simple Web" Projekt <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> 92
  • Eine einfache Web-Anwendung <groupId>org.sonatype.mavenbook.ch05</groupId> <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>simple-webapp Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> </project> Beachten Sie dass das Element packaging den Wert 'war' enthält. Ein Projekt des Types war package, wird eine WAR-Datei im Zielverzeichnis erstellen, das standardmäßig den Namen ${artifactId}-${version}.war trägt. Für dieses Projekt, wird das standardmäßige WAR in /target/simple-webapp-1.0-SNAPSHOT.war abgelegt. Im "Simple Web" Projekt haben wir den Namen der WAR-Datei vorbestimmt, indem wir ein Element finalName in die Projekt Build Konfiguration eingefügt haben. Mit dem finalName von 'simple-webapp' generiert die Phase package ein WAR-Archiv in /target/simple-webapp.war. 5.4. Konfigurieren des Jetty-Plugins Sobald Sie Ihre Web Anwendung kompiliert, getestet und verpackt haben, werden Sie sie wahrscheinlich einen Servlet-Container einsetzen wollen und diese mit der 93
  • Eine einfache Web-Anwendung vom Archetype Maven Plugin erstellten index.jsp-Datei testen wollen. Normalerweise wäre der Ablauf hierzu entsprechend: Herunterladen eines Containers wie etwas Jetty oder Apache Tomcat, Auspacken einer Distribution, Kopieren Ihres Anwendungs WAR-Archivs in ein /webapp-Verzeichnis, schliesslich starten des Containers. Obschon man das alles tun kann, gibt es keine Notwendigkeit, dies zu tun. Stattdessen können Sie das Jetty Maven Plugin einsetzen, und Ihre Web-Applikation aus Maven heraus starten. Um dies zu tun, müssen wir das Jetty Maven Plugin in unserem Projekt POM konfigurieren. Fügen Sie das folgende Plugin-Element Ihrer Projekt Build Konfiguration an: Example 5.2. Konfigurieren des Jetty-Plugins <project> [...] <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> </plugin> </plugins> </build> [...] </project> Nachdem Sie das Jetty Plugin in der Projekt pom.xml-Datei konfiguriert haben. können Sie das Goal jetty:run aufrufen, um Ihre Web Anwendung im Jetty Container zu starten. Rufen sie mvn jetty:run wie folgt auf: ~/examples$ mvn jetty:run ... [INFO] [jetty:run] [INFO] Configuring Jetty for project: simple-webapp Maven Webapp [INFO] Webapp source directory = ~/svnw/sonatype/examples/simple-webapp/src/main/webapp [INFO] web.xml file = ~/svnw/sonatype/examples/simple-webapp/src/main/webapp/WEB-INF/web.xml [INFO] Classes = ~/svnw/sonatype/examples/simple-webapp/target/classes 2007-11-17 22:11:50.532::INFO: Logging to STDERR via org.mortbay.log.StdErrLog [INFO] Context path = /simple-webapp [INFO] Tmp directory = determined at runtime 94
  • Eine einfache Web-Anwendung [INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml [INFO] Web overrides = none [INFO] Webapp directory = ~/svnw/sonatype/examples/simple-webapp/src/main/webapp [INFO] Starting jetty 6.1.6rc1 ... 2007-11-17 22:11:50.673::INFO: jetty-6.1.6rc1 2007-11-17 22:11:50.846::INFO: No Transaction manager found 2007-11-17 22:11:51.057::INFO: Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server Nach erfolgreichem Starten des Servlet-Containers durch Maven, laden Sie die URL http://localhost:8080/simple-webapp/ in einen Web-Browser. Die durch Archetype generierte index.jsp-Datei ist trivial: sie enthält eine Überschrift mit dem Text: "Hallo Welt!". Maven erwartet, dass das Document Root der Web-Applikation in /src/main/webapp liegt. In diesem Verzeichnis finden Sie auch die Datei index.jsp welche im Beispiel 5.3: "Inhalt von /src/main/webapp/index.jsp" aufgelistet wird. Example 5.3. Inhalt von /src/main/webapp/index.jsp <html> <body> <h2>Hello World!</h2> </body> </html> Unter /src/main/webapp/WEB-INF finden Sie den kleinsten möglichen Web-Applikations Beschrieb; der web.xml. Example 5.4. Inhalt von src/main/webapp/WEB-INF/web.xml <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> </web-app> 95
  • Eine einfache Web-Anwendung 5.5. Das Hinzufügen eines einfachen Servlets Eine Web-Applikation mit einer einzigen JSP-Seite und keinen konfigurierten Servlets ist so gut wie nutzlos. Lassen Sie uns ein einfaches Servlet hinzufügen und die entsprechenden Änderungen in der pom.xml- sowie der web.xml-Datei vornehmen. Zu Beginn müssen wir ein neues Package namens com.sonatype.maven.web im Verzeichnis /src/main/java erstellen $ mkdir -p src/main/java/org/sonatype/mavenbook/web $ cd src/main/java/org/sonatype/mavenbook/web Sobald Sie dieses Package kreiert haben, wechseln Sie in das Verzeichnis (/src/main/java/com/sonatype/maven/web) und erstellen Sie eine Klasse SimpleServlet in einer Datei SimpleServlet.java, welche den folgenden Quellcode enthält. Example 5.5. SimpleServlet Klasse package org.sonatype.mavenbook.web; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class SimpleServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println( "SimpleServlet Executed" ); out.flush(); out.close(); } } Unsere Klasse SimpleServlet ist eben dies, ein einfaches Servlet, welches eine einfache Nachricht auf den responseWriter schreibt. Um dieses Servlet Ihrer Web-Anwendung hinzuzufügen und auf einen Anfragepfad abzubilden, fügen Sie die folgenden Servlet-und Servlet-Mapping-Elemente zu Ihrer Projekt 96
  • Eine einfache Web-Anwendung web.xml-Datei an. Die web.xml-Datei finden Sie unter /src/main/webapp/WEB-INF. Example 5.6. Mapping the Simple Servlet <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>simple</servlet-name> <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>simple</servlet-name> <url-pattern>/simple</url-pattern> </servlet-mapping> </web-app> Nun ist alles vorhanden, um das Servlet zu testen, die Klasse befindet sich unter /src/main/java und die web.xml-Datei wurde aktualisiert. Bevor wir das Jetty-Plugin starten, kompilieren Sie Ihr Projekt, durch den Aufruf von mvn compile: ~/examples$ mvn compile ... [INFO] [compiler:compile] [INFO] Compiling 1 source file to ~/examples/ch05/simple-webapp/target/classes [INFO] ------------------------------------------------------------------------ [ERROR] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Compilation failure /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[4,0] package javax.servlet does not exist /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[5,0] package javax.servlet.http does not exist /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[7,35] cannot find symbol symbol: class HttpServlet public class SimpleServlet extends HttpServlet { /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[8,22] 97
  • Eine einfache Web-Anwendung cannot find symbol symbol : class HttpServletRequest location: class org.sonatype.mavenbook.web.SimpleServlet /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[9,22] cannot find symbol symbol : class HttpServletResponse location: class org.sonatype.mavenbook.web.SimpleServlet /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[10,15] cannot find symbol symbol : class ServletException location: class org.sonatype.mavenbook.web.SimpleServlet Die Erstellung schlägt fehl, Ihr Maven-Projekt weist keine Abhängigkeit zum Servlet-API aus. Im nächsten Abschnitt werden wir diese hinzufügen und das Servlet-API in das Projekt POM eintragen. 5.6. Das Hinzufügen von J2EE-Abhängigkeiten Um ein Servlet zu schreiben benötigen Sie die Servlet-API-Spezifikation. Um die Servlet-API-Spezifikation als eine Abhängigkeit zu Ihrem Projekt POM hinzuzufügen, fügen Sie das folgende dependency Element Ihrer pom.xml an. Example 5.7. Einfügen der Servlet-Spezifikation 2.4 als Abhängigkeit <project> [...] <dependencies> [...] <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> </dependencies> [...] </project> Es ist auch darauf hinzuweisen, dass wir für diese Abhängigkeit den Geltungsbereich provided verwendet haben. Dies sagt Maven, dass das Archiv 98
  • Eine einfache Web-Anwendung seitens des Containers bereitgestellt wird, und nicht in das war-Archiv einfliessen soll. Wenn Sie daran interessiert waren, ein benutzerdefiniertes JSP-Tag für dieses einfache Web-Anwendung zu erstellen, so bräuchten Sie eine Abhängigkeit zur JSP-Spezifikation 2.0. Verwenden Sie die folgende Konfiguration, um diese Abhängigkeit zu schaffen. Example 5.8. Hinzufügen der 2.0 JSP-Spezifikation als Abhängigkeit <project> [...] <dependencies> [...] <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies> [...] </project> Sobald Sie die Servlet Spezifikationsimplementierung als Abhängigkeit hinzugefügt haben, führen Sie mvn clean install gefolgt von mvn jetty:run aus. [tobrien@t1 simple-webapp]$ mvn clean install ... [tobrien@t1 simple-webapp]$ mvn jetty:run [INFO] [jetty:run] ... 2007-12-14 16:18:31.305::INFO: jetty-6.1.6rc1 2007-12-14 16:18:31.453::INFO: No Transaction manager found 2007-12-14 16:18:32.745::INFO: Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server An diesem Punkt sollten Sie in der Lage sein, die Ausgabe des SimpleServlet zu erhalten. Von der Befehlszeile können Sie mittels curl die Ausgabe des Servlet zur Standard-Ausgabe umleiten: ~/examples$ curl http://localhost:8080/simple-webapp/simple SimpleServlet Executed 99
  • Eine einfache Web-Anwendung 5.7. Zusammenfassung Nach dem Lesen dieses Kapitels sollten Sie in der Lage sein eine einfache Web-Applikation zu erstellen. In diesem Kapitel wurde nicht näher auf die Millionen verschiedener Möglichkeiten eingegangen wie man eine komplette Web-Anwendung erschafft. Weitere Kapitel werden einen umfassenderen Überblick über Projekte geben, bei denen einige der populären Web-Frameworks und Technologien zum Einsatz kommen. 100
  • Chapter 6. Ein multi-modulares Projekt 6.1. Einleitung In diesem Kapitel werden wir ein Multi-Modul Projekt erstellen, welches die Beispiele der beiden vorangegangenen Kapitel verbindet. Die "Simple Weather" Anwendung von Chapter 4, Anpassen eines Maven Projektes (Kapitel 4: "Anpassen eines Maven Projekts") wird verbunden mit der "Simple Web" Applikation von Chapter 5, Eine einfache Web-Anwendung (Kapitel 5: "Eine einfache Web-Anwendung"), mit dem Ergebnis einer Web Anwendung welche die Wetterdaten zu einer gegebenen Postleitzahl abruft und die Wettervorhersage auf einer Webseite darstellt. Am Ende dieses Kapitels werden Sie in der Lage sein, unter Verwendung von Maven komplexe, multi-modulare Projekte zu entwickeln. 6.1.1. Herunterladen der Beispiele dieses Kapitels Das multi-modulare Projekt welches wir in diesem Kapitel erstellen werden, besteht aus modifizierten Versionen der Projekte der vorhergehenden Kapitel: 4 (Kapitel 4: "Anpassen eines Maven Projekts") sowie 5 (Kapitel 5: "Eine einfache Web-Anwendung"). Wir werden dieses multi-modulare Projekt nicht mit dem Archetype Maven Plugin generieren. Wir empfehlen Ihnen eine Kopie der Beispiel-Code als zusätzliche Referenz beim Lesen der Inhalte in diesem Kapitel herunterzuladen. Das Beispielprojekt dieses Kapitels zusammen mit den anderen Beispielen aus diesem Buch kann von http://books.sonatype.com/maven-book/mvn-examples-1.0.zip oder http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie dann zum Verzeichnis /ch06. Darin finden Sie ein Verzeichnis mit dem Namen /simple-parent welches das multi-modulare Projekt dieses Kapitels enthält. Innerhalb des Verzeichnisses /simple-parent werden Sie auf eine pom.xml-Datei sowie zwei Unterverzeichnisse /simple weather und /simple web app stossen welche den Quellcode für dieses Kapitel enthalten. 101
  • Ein multi-modulares Projekt 6.2. Das "Simple Parent" Projekt (parent=Elternteil) Ein multi modulares Projekt wird durch ein "Eltern"-POM und dessen referenzierende Submodule definiert. Im Verzeichnis /simple-parent finden Sie die "Eltern" POM Datei pom.xml, auch das 'top-level' POM genannt, Example 6.1. simple-parent Projekt POM <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <name>Chapter 6 Simple Parent Project</name> <modules> <module>simple-weather</module> <module>simple-webapp</module> </modules> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> 102
  • Ein multi-modulares Projekt </dependency> </dependencies> </project> Wie Sie sehen, definiert das top-level POM die Maven Koordinaten groupId als com.sonatype.maven, artifactId als simple-parent und version als 1.0. Das top level POM definiert keinen Ausgabetyp JAR- oder WAR-Archiv wie das bislang der Fall, sondern bezieht sich auf andere Maven Projekte. Das entsprechende Element package eines top level POM welches lediglich ein Projekt Objekt Modell darstellt, ist 'pom'. Der nächste Abschnitt in der pom.xml-Datei führt die Sub-Module auf. Diese Module werden im Element module aufgeführt, und befinden sich je in einem eigenen Unterverzeichnis. Maven kennt diese Struktur und sucht in den Unterverzeichnissen nach weiteren pom.xml-Dateien. Diese werden dann in die Liste der Maven Projekte des Builds aufgenommen. Zuletzt setzen wir eine Anzahl Einstellungen welche von allen Untermodulen geerbt werden. Das "Simple Parent" Modul setzt die Zielplattform aller Untermodule als Java 5 Plattform. Da das Compiler-Plugin standardmässig an den Lebenszyklus gekettet ist, können wir den Abschnitt pluginManagement hierzu benutzen. Wir werden pluginManagement später im Detail beleuchten, für den Moment ist es einfacher, diese Trennung zwischen Bereitstellung von Plugin Standard-Konfigurationen und dem tatsächlichen 'binden' der Plugins darzustellen wenn sie auf diese Weise getrennt werden. Das Element dependency fügt als globale Abhängigkeiten JUnit 3.8.1 ein. Sowohl die Konfiguration sowie die Abhängigkeiten werden von allen Untermodulen geerbt. Der Einsatz der POM Vererbung erlaubt es Ihnen, gemeinsam genutzte Abhängigkeiten, wie z.B. JUnit oder Log4J, universell zu definieren. 6.3. Das "Simple Weather" Modul Das erste Untermodul welches wir uns genauer ansehen wollen, ist das Untermodul "Simple Weather". Dieses Modul enthält alle Klassen, welche zum Abrufen und Verarbeiten des Yahoo! Wetterdienstes benötigt werden. 103
  • Ein multi-modulares Projekt Example 6.2. POM des Untermodul "Simple-Weather" <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <name>Chapter 6 Simple Weather API</name> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> 104
  • Ein multi-modulares Projekt <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> <scope>test</scope> </dependency> </dependencies> </project> Im POM des Moduls "Simple Weather" sehen wir, wie dieses Modul die elterlichen Koordinaten in Form von Maven Koordianten referenziert. Das elterliche POM enthielt die Koordinaten groupId com.sonatype.maven, artifactId simple-parent sowie version 1.0 Beachten Sie hier, dass wir weder die Koordinate groupID noch version definieren müssen, diese sind bereits im "elterlichen" POM definiert. Example 6.3. Die Klasse WeatherService package org.sonatype.mavenbook.weather; import java.io.InputStream; public class WeatherService { public WeatherService() {} public String retrieveForecast( String zip ) throws Exception { // Retrieve Data InputStream dataIn = new YahooRetriever().retrieve( zip ); // Parse Data Weather weather = new YahooParser().parse( dataIn ); // Format (Print) Data return new WeatherFormatter().format( weather ); } } Die Klasse WeatherService ist unter /src/main/java/de/sonatype/Maven/Weather definiert, und ruft lediglich die drei in Chapter 4, Anpassen eines Maven Projektes (Kapitel 4: "Anpassen eines Maven Projekts") definierten Objekte auf. Im Beispiel dieses Kapitels, erstellen 105
  • Ein multi-modulares Projekt wir ein separates Projekt, welches Service-Objekte bereitstellt, die vom Web-Applikations Projekt angesprochen werden. Dies ist ein gewöhnliches Pattern von Enterprise Anwendungen. Oftmals besteht eine komplexe Anwendung aus weit mehr als einer einzigen (einfachen) Web-Applikation. Eine solche Anwendung könnte aus einer Reihe von Web Anwendungen sowie weiteren Befehlszeilenanwendungen bestehen. Sie werden in diesem Fall die gemeinsame Logik in einer Service Klasse zusammenfassen und für eine Anzahl Projekte als Dienst bereitstellen. Dies gibt uns eine Rechtfertigung für die Erstellung einer Klasse WeatherService. Sie können daran sehen, wie die "Simple Web" Anwendung auf die bereitgestellten Service Objekte welche in "Simple Weather" bereitgestellt werden zugreift. Die Methode retrieveForecast() hat einen Parameter Postleitzahl. Dieser wird an die Methode retrieve() der Klasse YahooRetriever gegeben, welche das entsprechende XML-Fragment beim Yahoo Wetter Dienst einholt. Das erhaltene XML wird an die Methode parse() übergeben, welche ein Objekt Weather zurückgibt. Das Objekt Weather wird dann in Form einer lesbaren, formatierten Zeichenkette ausgegeben. 6.4. Das "Simple-Web" Anwendungs-Modul Das Untermodul "Simple Web" ist das zweite, referenzierte Sub-Modul des Projekts. Das darin enthaltene Web-Anwendungsprojekt ist vom Untermodul WeatherService abhängig und es enthält einige einfache Servlets zur Präsentation der Ergebnisse der Yahoo! Wetterdienst Abfrage. Example 6.4. POM des Untermodul "Simple-Webapp" <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> 106
  • Ein multi-modulares Projekt <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <name>simple-webapp Maven Webapp</name> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-weather</artifactId> <version>1.0</version> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> </plugin> </plugins> </build> </project> Das "Simple Web" Modul definiert ein einfaches Servlet, welches eine Postleitzahl aus einem HTTP-Request ausliest, den weatherService aus Example 6.3, “Die Klasse WeatherService” (Beispiel 6.3: "Die klasse WeatherService") aufruft, und das Resultat auf den Writer schreibt. Example 6.5. Simple-Webapp WeatherServlet package org.sonatype.mavenbook.web; import org.sonatype.mavenbook.weather.WeatherService; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class WeatherServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String zip = request.getParameter("zip" ); WeatherService weatherService = new WeatherService(); 107
  • Ein multi-modulares Projekt PrintWriter out = response.getWriter(); try { out.println( weatherService.retrieveForecast( zip ) ); } catch( Exception e ) { out.println( "Error Retrieving Forecast: " + e.getMessage() ); } out.flush(); out.close(); } } Im WeatherServlet, instanziieren wir die Klasse "SimpleWeather" des SimpleWeather Projekts. Die Postleitzahl welche in den Anfrage-Parametern übermittelt wird, geht an die Methode retrieveForecast(), der zurückgegebene Text wird auf den Writer geleitet. Schlussendlich besteht die web.xml-Datei der "Simple Web" Anwendung, welche unter /src/main/webapp/WEB-INF zu finden ist, und alles zusammenbindet. Die Servlet-und Servlet-Mapping-Elemente definieren hierbei einen Pfad von /weather auf das WeatherServlet. Example 6.6. web.xml der Simple-Webapp <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>simple</servlet-name> <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class> </servlet> <servlet> <servlet-name>weather</servlet-name> <servlet-class>org.sonatype.mavenbook.web.WeatherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>simple</servlet-name> <url-pattern>/simple</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>weather</servlet-name> <url-pattern>/weather</url-pattern> </servlet-mapping> </web-app> 108
  • Ein multi-modulares Projekt 6.5. Erstellung des Multi-Projekt-Moduls Mit dem "Simple Weather" Projekt welches den allgemeinen Code zur Interaktion mit dem Yahoo! Wetterdienst erstellt, sowie der "Simple Web" Anwendung welche die Webanwendung in Form eines Servlets bereitstellt, es ist an der Zeit, zu Kompilieren und Packetieren, sowie Bereitstellen der Anwendung in Form eines WAR-Archivs. Um das zu bewerkstelligen, müssen Sie die beiden Projekte in der richtigen Reihenfolge kompilieren und installieren. Da "Simple Web" von "Simple Weather" abhängig ist, muss diese zuerst kompiliert werden und als JAR-Archive bereitgestellt werden. Tun Sie dies durch den Aufruf von mvn clean install aus dem Verzeichnis der Mutterapplikation heraus auf der Befehlszeile. ~/examples/ch06/simple-parent$ mvn clean install [INFO] Scanning for projects... [INFO] Reactor build order: [INFO] Simple Parent Project [INFO] simple-weather [INFO] simple-webapp Maven Webapp [INFO] ---------------------------------------------------------------------- [INFO] Building simple-weather [INFO] task-segment: [clean, install] [INFO] ---------------------------------------------------------------------- [...] [INFO] [install:install] [INFO] Installing simple-weather-1.0.jar to simple-weather-1.0.jar [INFO] ---------------------------------------------------------------------- [INFO] Building simple-webapp Maven Webapp [INFO] task-segment: [clean, install] [INFO] ---------------------------------------------------------------------- [...] [INFO] [install:install] [INFO] Installing simple-webapp.war to simple-webapp-1.0.war [INFO] [INFO] ---------------------------------------------------------------------- [INFO] Reactor Summary: [INFO] ---------------------------------------------------------------------- [INFO] Simple Parent Project ............................... SUCCESS [3.041s] [INFO] simple-weather ...................................... SUCCESS [4.802s] [INFO] simple-webapp Maven Webapp .......................... SUCCESS [3.065s] [INFO] ---------------------------------------------------------------------- Wenn Maven gegen ein Projekt mit Submodulen ausgeführt wird, lädt Maven zunächst das höchststehende POM, dann lokalisiert es alle abhängigen POM-Dateien. Maven bringt alle Komponenten POM-Inhalte in das, was man den 109
  • Ein multi-modulares Projekt Maven Reaktor nennt, innerhalb werden die Komponenten POM auf deren Abhängigkeiten analysiert. Der Reaktor definiert die Reihenfolge der Komponenten und stellt deren Abarbeitung und Installation sicher. Note Der Reaktor wird die Reihenfolge der Module gemäß der Definition in der POM erhalten, soweit dies möglich ist. Eine hilfreiches mentales Modell hierfür ist das Bild, dass Module mit Abhängigkeiten von Geschwister-Projekten solange in der Reihenfolge "nach hinten" gerückt werden, bis deren Abhängigkeitsverhältnisse befriedigt sind. In seltenen Fällen kann es nützlich sein, in die Reihenfolge der Module einzugreifen, - zum Beispiel, wenn Sie ein häufig instabiles Modul gegen Anfang des Build abarbeiten möchten. Nachdem der Reaktor ausgearbeitet hat, in in welcher Reihenfolge die Projekte abgearbeitet werden müssen, führt Maven die angegebenen Goals für die jeweiligen Module des Multi-Moduls aus. In diesem Beispiel können Sie erkennen, wie Maven "Simple Weather" vor "Simple Web" stellt, und auf dem jeweiligem Modul einen Befehl mvn clean install abarbeitet. Note Beim Ausführen von Maven von der Befehlszeile sollten Sie die Lebenszyklusphase clean vor allen anderen Phasen spezifizieren. Mit dem Aufruf von clean stellen Sie sicher, dass alle alten Output-Artefakte gelöscht werden, vor die Anwendung kompiliert und paketiert wird. clean auszuführen ist nicht notwendig, aber es stellt sicher dass Sie einen "sauberen Build" erstellen. 6.6. Starten der Web-Anwendung Nach der Installation des Multi-Modul-Projekts mittels mvn clean install aus dem Stammverzeichnis von "Simple Project" heraus, können Sie direkt in das 110
  • Ein multi-modulares Projekt Unterverzeichnis /Simple Web wechseln und dort das Goal run des Jetty Plugins aufrufen: ~/examples/ch06/simple-parent/simple-webapp $ mvn jetty:run [INFO] ---------------------------------------------------------------------- [INFO] Building simple-webapp Maven Webapp [INFO] task-segment: [jetty:run] [INFO] ---------------------------------------------------------------------- [...] [INFO] [jetty:run] [INFO] Configuring Jetty for project: simple-webapp Maven Webapp [...] [INFO] Webapp directory = ~/examples/ch06/simple-parent/ simple-webapp/src/main/webapp [INFO] Starting jetty 6.1.6rc1 ... 2007-11-18 1:58:26.980::INFO: jetty-6.1.6rc1 2007-11-18 1:58:26.125::INFO: No Transaction manager found 2007-11-18 1:58:27.633::INFO: Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server Sobald Jetty mit dem Startvorgang abgeschlossen hat, laden Sie http://localhost:8080/simple-webapp/weather?zip=01201 in einen Browser und Sie sollten sehen, wie die Wettervorhersage formatiert ausgegeben wird. 111
  • Chapter 7. Multi-module Enterprise Project 7.1. Einleitung In diesem Kapitel werden wir, aufbauend auf die Beispiele der Kapitel 6: Ein Multi-Projekt-Modul sowie Kapitel 5. Eine einfache Web-Anwendung, ein Multi-Modul-Projekt entwickeln. In dieses Beispiel werden wir das Spring Framework und auch Hibernate einbinden, um eine einfache Web-Anwendung und gleichzeitig ein einfaches Befehlszeilen-Dienstprogramm zum Lesen von Daten des Yahoo! Wetter Dienstes zu erstellen. Den Quellcode des "Simple Weather" Beispiels aus Chapter 4, Anpassen eines Maven Projektes (Kapitel 4: Anpassen eines Maven Projekts) werden wir mit dem Quellcode aus Chapter 5, Eine einfache Web-Anwendung (Kapitel 5: Eine einfache Web-Anwendung) kombinieren. Im Laufe der Entwicklung dieses Multi Modul Projekts werden wir Fragenstellungen bezüglich des Einsatzes von Maven erörtern und verschiedene Möglichkeiten der Umsetzung darlegen, um den Aufbau modularer Projekte zu fördern und die Wiederverwendung von Komponenten aufzuzeigen. 7.1.1. Herunterladen der Beispiele dieses Kapitels Das multi-modulare Projekt welches wir in diesem Kapitel erstellen werden, besteht aus modifizierten Versionen der Projekte der vorhergehenden Kapitel; Chapter 4, Anpassen eines Maven Projektes (Kapitel 4: Anpassen eines Maven Projekts) sowie Chapter 5, Eine einfache Web-Anwendung (Kapitel 5: Eine einfache Web-Anwendung). Wir werden dieses multi-modulare Projekt nicht mit dem Archetype Maven Plugin generieren. Wir empfehlen Ihnen eine Kopie der Beispiel-Code als zusätzliche Referenz beim Lesen der Inhalte in diesem Kapitel herunterzuladen. Das Beispielprojekt dieses Kapitel zusammen mit den anderen Beispielen dieses Buchs kann von http://www.sonatype.com/book/mvn-examples-1.0.zip oder http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie 112
  • Multi-module Enterprise Project dann zum Verzeichnis /ch07. Darin finden Sie ein Verzeichnis mit dem Namen /simple-parent welches das multi modulare Projekt dieses Kapitels enthält. Innerhalb des Verzeichnisses /simple-parent werden Sie auf eine pom.xml-Datei sowie zwei Unterverzeichnisse /simple weather und /simple web app stossen welche den Quellcode für dieses Kapitel enthalten. 7.1.2. Multi-Modul Enterprise Projekt Die Komplexität einer extensiven Enterprise Applikation aufzuzeigen würde den Rahmen dieses Buches bei weitem sprengen. Solche Projekte sind gekennzeichnet durch den Einsatz mehrerer Datenbanken, der Integration externer Systeme und Teilprojekte welche weiter z.B. nach Abteilungen aufgeteilt werden. Solcherlei Projekte beinhalten in der Regel tausende von Zeilen Quellcode, und reflektieren die Arbeit von Dutzenden oder Hunderten von Software-Entwicklern. Da ein solches Beispiel den Rahmen des Buches sprengen würde, können wir Ihnen hier lediglich anhand eines Beispielprojekts die Komplexitäten größerer Enterprise-Anwendungen aufzeigen. Im Abschluss geben wir einige Ratschläge der modularen Strukturierung welche über dieses Kapitel hinausgehen. In diesem Kapitel wenden wir uns einem Multi Modul Projekt zu, welches zwei Benutzerschnittstellen definiert: ein Befehlszeilen-Abfrage-Tool für die Yahoo! Wetter-Dienste, sowie eine Web-Anwendung welche auf diese Dienste aufbaut. Beide Anwendungen speichern die Resultate der Abfragen in einer eingebetteten Datenbank. Beide Anwendungen erlauben es dem Benutzer auf historisierte Abfragewerte der eingebetteten Datenbank zuzugreifen. Beide Anwendungen teilen sich die Anwendungslogik und auch eine Persistenz Bibliothek. Dieses Kapitel baut auf den Code zur Auswertung des Yahoo! Wetter Dienstes auf, welcher in Chapter 4, Anpassen eines Maven Projektes (Kapitel 4 Anpassen eines Maven Projekts) eingeführt wurde. Dieses Projekt gliedert sich in fünf Untermodule wie in Figure 7.1, “Beziehungen der Module einer komplexen Enterprise Anwendung” (Abbildung 7.1: "Beziehungen der Module einer komplexen Enterprise Anwendung") veranschaulicht wird. 113
  • Multi-module Enterprise Project Figure 7.1. Beziehungen der Module einer komplexen Enterprise Anwendung Aus Figure 7.1, “Beziehungen der Module einer komplexen Enterprise Anwendung” (Abbildung 7.1: "Beziehungen der Module einer komplexen Enterprise Anwendung") sehen Sie, dass es fünf Untermodule des "Eltern"-Projekt gibt. Diese sind: simple-model Dieses Modul definiert ein einfaches Objektmodell, welches die vom Yahoo! Wetter-Dienst zurückgegebenen Daten abbildet. Dieses Objektmodell beinhaltet die Objekte Weather, Condition, Atmosphere, Location und Wind (Wetterlage, Zustand, Atmosphäre, Ort sowie Windrichtung). Beim parsen der vom Yahoo! Wetter Dienst zurückgegebenen XML-Antwort werden weather Objekte erstellt, welche an die Anwendung weitergegeben werden. Dieses Projekt beinhaltet Objekte welche mit Hibernate 3 Annotations versehen sind um die Zuordnung eines jeden Objektes zu einem Datensatz in der Datenbank sicherzustellen. 114
  • Multi-module Enterprise Project simple-weather Dieses Modul enthält alle Logik welche erforderlich ist, um Daten des Yahoo! Wetter-Dienstes anzuziehen und das zurückgegebene XML auszuwerten. Aus dem zurückgegebenen XML werden anschliessend Objekte wie diese in "Simple Model" definiert wurden, erstellt. Das "Simple Weather"-Modul ist abhängig vom Modul "Simple Model". "Simple Weather" stellt einen Dienst bereit ("WeatherService"), welcher sowohl von "Simple Command" wie auch von "Simple Web" konsumiert wird. simple-persist Dieses Modul enthält einige Data Access Objekte (DAO). Konfiguriert, um Weather Objekte in einer eingebetteten Datenbank abzuspeichern. Beide Benutzerschnittstellen dieser multi-modularen Anwendung (Web/Befehlszeile) bauen auf einfache DAOs (Data Access Objekts=Datenzugriffsobjekte) auf, welche zur Speicherung (speichern=to persist) herangezogen werden. Die Modellobjekte welche in "Simple Model" bereitgestellt werden, können von den DAOs sowohl verstanden wie auch zurückgegeben werden. "Simple Persist" ist direkt von "Simple Model" abhängig und definiert weitere Abhängigkeiten zu den Modellobjekten, gekennzeichnet durch Hibernate Annotations. simple-webapp Das Web-Anwendungs Projekt definiert zwei Spring MVC-Controller-Implementierungen welche unter Verwendung des WeatherService definiert werden, sowie die DAOs aus "Simple Persist". "Simple Web" verfügt über eine direkte Abhängigkeit zu "Simple Weather" sowie eine transitive Abhängigkeit zu "Simple Model". simple-command Dieses Modul enthält eine einfache Befehlszeilen-Anwendung welche dazu eingesetzt werden kann, die Yahoo! Wetter-Dienste abzufragen. Das Modul enthält eine statisch definierte Klasse main() welche mit WeatherService aus "Simple Weather" sowie der DAOs welche in "Simple Persist" definiert wurden arbeitet. "Simple Command" hat eine direkte Abhängigkeit zu "Simple 115
  • Multi-module Enterprise Project Weather" sowie "Simple Persist"; es besteht eine transitive Abhängigkeit zu "Simple Model". Hiermit ist das Beispiel in diesem Kapitel vorgestellt; es ist einfach genug um es in einem Lehrbuch einzuführen, und doch komplex genug um eine Aufspaltung in fünf Untermodule zu rechtfertigen. Während unser künstliches Beispiel ein Modell-Projekt von fünf Klassen, eine Persistenz-Bibliothek mit zwei Service-Klassen und eine Parsing-Bibliothek mit fünf oder sechs Klassen hat, kann ein tatsächliches "Reale-Welt-System" leicht ein Modell-Projekt mit hunderten von Klassen, mehrere Persistenz- und Service-Bibliotheken welche über mehrere Bereiche reichen, umfassen. Während wir versucht haben sicherzustellen, dass der Quellcode des Beispiels einfach genug ist, um diesen in einer Sitzung zu verstehen, haben wir uns ebenso bemüht ein modulares Beispiel zu erstellen. Es wäre nachvollziehbar, würden Sie nach der Betrachtung des Quellcodes, sich von Maven abwenden und den Eindruck mitnehmen, Maven ermuntere dazu Komplexität einzuführen, da das Beispiel lediglich fünf Klassen umfasst. Jedoch sollten Sie immer bedenken, dass wir keine Mühen scheuten, das einfache Beispiel anzureichern, um die Möglichkeiten zur Bearbeitung komplexer Fragestellungen beispielhaft aufzuzeigen und darzustellen, insbesondere die von Maven gegebenen Multi-Modul-Funktionalitäten. 7.1.3. Technologie des Beispiels Die in diesem Kapitel eingesetzten Technologien umfassen solche, welche zwar populär sind, aber nicht in direktem Zusammenhang mit dem Einsatz von Maven stehen. Die angedeuteten Technologien sind: das Spring Framework™ und Hibernate™. Das Spring Framework stellt einen 'Inversion of Control (IoC) Container' bereit, sowie eine Reihe von Frameworks welche auf die Vereinfachung der Interaktion mit verschiedenen J2EE-Bibliotheken abzielen. Mit dem Spring Framework als Fundament der Anwendungsentwicklung erhalten Sie Zugriff auf eine Reihe von hilfreichen Abstraktionen, welche Ihnen einen Großteil der mühseligen Fummelei im Umgang mit z.B. Persistenz Frameworks wie Hibernate oder iBatis™ oder auch Enterprise-APIs wie JDBC, JNDI und JMS nehmen. Das Spring Framework hat in den vergangenen Jahren stark an Popularität gewonnen, insbesondere als Alternative zu den schwergewichtigen Enterprise Standards aus 116
  • Multi-module Enterprise Project dem Hause Sun Microsystems. Hibernate ist ein weit verbreitetes Object-Relational-Mapping- (ORM-) Framework, das eine Interaktion mit relationalen Datenbanken bereitstellt, als ob es sich um eine Sammlung von Java-Objekten handele. Dieses Beispiel konzentriert sich auf den Aufbau einer einfachen Web-Anwendung und eines Befehlszeilen-Programms, welche, aufbauend auf das Spring Framework und Hibernate, eine Reihe von wiederverwendbaren Komponenten bereitstellt, welche es erlauben die bestehenden Wetter-Daten in einer eingebettete Datenbank abzulegen (persistieren). Wir haben uns entschlossen diese Frameworks einzubinden, um Ihnen am Beispiel aufzuzeigen, wie man beim Einsatz von Maven diese Technologien einbinden kann. Obschon es in das Kapitel eingestreut kurze Einführungen zu diesen Technologien gibt, so ist es nicht das erklärte Ziel dieses Kapitels diese Technologien im Detail darzustellen. Bezüglich weiterführender Dokumentation dieser Frameworks verweisen wir: bezüglich Spring auf die Projekt Website http://www.springframework.org, bezüglich Hibernate auf den Projekt Website http://www.hibernate.org. Dieses Kapitel setzt als eingebettete Datenbank auf HSQLDB, weitere Informationen zu dieser Datenbank finden Sie auf der Projekt Website http://hsqldb.org. 7.2. Das "Simple Parent" Projekt - Top Level Das "Simple Parent" Hauptmodul hat ein POM welches auf fünf Untermodule verweist: (simple-command), (simple-model), (simple-weather), (simple-persist) und (simple-webapp). Das Top-Level POM (pom.xml-Datei) ist im Example 7.1, “Simple Parent POM Projekt” (Beispiel 7.1, "Simple Parent POM Projekt") dargestellt. Example 7.1. Simple Parent POM Projekt <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> 117
  • Multi-module Enterprise Project <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <name>Chapter 7 Simple Parent Project</name> <modules> <module>simple-command</module> <module>simple-model</module> <module>simple-weather</module> <module>simple-persist</module> <module>simple-webapp</module> </modules> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project> Note Sollten Sie bereits mit Maven POMs vertraut sein, so werden Sie jetzt bemerken, dass dieses top level POM kein Element dependencyManagement definiert. Das Element dependencyManagement erlaubt es Ihnen, Abhängigkeitsversionen in einem einzigen top-level POM zu definieren und wird in Chapter 8, Optimirung und 118
  • Multi-module Enterprise Project Überarbeitung der POMs (Kapitel 8: Optimieren und Refaktorieren von POMs) eingeführt. Bemerken Sie bitte die Ähnlichkeiten dieses Hauptmodul POMs zu jenem Hauptmodul POM des Projekts in Example 6.1, “simple-parent Projekt POM” (Kapitel 6.1: "Simple Parent Projekt POM"). Der einzige, wirkliche Unterschied zwischen diesen beiden POMs ist die Liste der Untermodule. Wo das vorangegangene Beispiel nur zwei Untermodule aufführte, hat dieses Hauptmodul deren fünf. In den nächsten Abschnitten wird jedes dieser fünf Untermodule in einigen Details dargestellt. Da unsere Beispiel Java-Annotationen einsetzt, haben wir es so konfiguriert, dass der Compiler auf die Java 5 Virtual Machine (JVM) aufsetzt. 7.3. Das Simple Model" Modul - Das Objektmodell Das erste, was die meisten Enterprise Projekte benötigen, ist ein Objekt-Modell. Ein Objekt-Modell erfasst die Grundmenge aller Domain-Objekte eines Systems. Ein Banken-System könnten ein Objekt-Modell haben, welches aus einem Konto, Kunden- und Transaktions-Objekt besteht, ein System zum Erfassen und Verbreiten von Sportergebnisse besässe ein Mannschafts- und ein Spiel-Objekt. Was auch immer Ihr Projekt umfasst, die Chancen stehen gut, dass Sie die entsprechenden Konzepte Ihres Systems in einem Objekt-Modell erfasst haben. Eine gängige Praxis im Umgang mit Maven ist es, dieses Modul separate auszulagern um es fortan als häufig referenziertes Modul weiterzuführen. In unserem System erfassen wir jede Abfrage des Yahoo! Wetter-Dienstes mit einem Objekt, das Objekt weather, welches weitere vier Objekte referenziert: Windrichtung, Chill und Geschwindigkeit werden in einem Objekt Wind erfasst. Standortdaten einschließlich der Postleitzahl, Stadt, Region und Land sind in einer Klasse Location zusammengefasst. Die atmosphärischen Bedingungen wie die Feuchtigkeit, maximale Sichtbarkeit, Luftdruck, und ob der Druck steigt oder fällt, wird in einer Klasse Atmosphere gehalten. Eine textuelle Beschreibung der 119
  • Multi-module Enterprise Project Zustände, der Temperatur, und die Daten der Beobachtung werden einer Klasse Condition zusammengefasst. Figure 7.2. Einfaches Objektmodell für Wetter-Daten Die pom.xml-Datei für dieses schlichte Modell hat nur eine Abhängigkeitsdefinition welche der Erläuterung bedarf: Unser Objekt-Modell wird mit Hibernate Annotations realisiert. Wir verwenden Annotations um die Modell-Objekte auf Tabellen einer relationalen Datenbank abzubilden. Die Abhängigkeit besteht zu org.hibernate: hibernate-annotaions: 3.3.0.ga. Werfen Sie einen Blick in diese pom.xml sowie einige der kommenden Beispiele worin dies verdeutlicht wird: Example 7.2. pom.xml des Projektmoduls: simple-model <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> 120
  • Multi-module Enterprise Project <version>1.0</version> </parent> <artifactId>simple-model</artifactId> <packaging>jar</packaging> <name>Simple Object Model</name> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> </dependencies> </project> Unter /src/main/java/de/sonatype/Maven/weather/model liegt Weather.java welche das annotierte Wetter-Objekt-Modell beinhaltet. Das Weather-Objekt ist ein einfaches Java Bean. Das bedeutet, es werden private Member Variablen gesetzt (id, location, condition, wind, amosphere und date) welche durch public Getter und Setter-Methoden offengelegt werden. Dabei wird folgende Regel umgesetzt: gibt es ein String-Property mit dem Namen "name" so wird eine public Getter-Methode ohne Argumente namens getName() gesetzt, sowie eine public Setter Methode mit einem Argument setName(String name); analog für andere Typen. Während wir hier die Getter und Setter-Methode für das id-Property zeigen, haben wir die Mehrheit der anderen Properties ausgelassen um an dieser Stelle einer Anzahl Bäume das Leben zu wahren. Example 7.3. Annotiertes Wetter Modell Objekt package org.sonatype.mavenbook.weather.model; import javax.persistence.*; import java.util.Date; @Entity @NamedQueries({ @NamedQuery(name="Weather.byLocation", 121
  • Multi-module Enterprise Project query="from Weather w where w.location = :location") }) public class Weather { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; @ManyToOne(cascade=CascadeType.ALL) private Location location; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Condition condition; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Wind wind; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Atmosphere atmosphere; private Date date; public Weather() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } // Alle weiteren getter/setter Methoden ausgelassen... } In der Klasse Weather, setzen wir auf Hibernate Annotationen, um dem "simple persist" Projekt Anleitung zu geben. Diese Annotationen werden von Hibernate benutzt, um ein Objekt auf eine Tabelle einer relationalen Datenbank abzubilden. Während eine ausführliche Einführung in die Hibernate Annotationen weit über den Rahmen dieses Kapitels hinausgeht, hier eine kurze Erklärung für Neugierige: Die Annotation @Entity bezeichnet diese Klasse als zu persistierende Einheit, die @Table Annotation haben wir ausgelassen, Hibernate wird daher den Klassennamen als Tabellennamen einsetzen und so das Objekt Weather auf eine Tabelle Weather abbilden. Die @NamedQueries Annotation definiert eine Abfrage, welche vom WeatherDAO aus dem "Simple Persist" Projekt verwendet wird. Die Abfragesprache, welche von der @NamedQuery Annotation benutzt wird ist Hibernate Query Language (HQL). Jede Variable wird mit Annotationen versehen welche den Typ, die Spalte sowie jegliche Beziehungen der Spalte zu anderen 122
  • Multi-module Enterprise Project Spalten definiert: Id Das Property id wird mit @Id gekennzeichnet. Dies zeichnet das Property id als das Property aus, welches den Primärschlüssel bezüglich der Tabelle in der Datenbank trägt. Die Kennzeichnung @GeneratedValue bestimmt wie neue Primärschlüssel generiert werden. Im Falle von id, bestimmen wir den GenerationType IDENTITY, welcher auf die Verwendung des Datenbank eigenen Generierungsalgoryhtmus aufsetzt. Location Jede Instanz eines Wetter-Objekts (Weather) ist an eine Instanz eines Standort Objekts (Location) gebunden. Ein Standort Objekt repräsentiert eine Postleitzahl und die Kennzeichnung @ManyToOne stellt sicher, dass Wetter-Objekte, welche an den selben Standort gebunden sind auch auf dieselbe Instanz zeigen. Das Attribut cascade der Annotation @ManyToOne stellt sicher, dass wir bei jedem persistieren des Wetter Objekts auch das zugehörige Standort Objekt persistieren. Condition, Wind, Atmosphere Jedes dieser Objekte wird mit einer Kennzeichnung von @OneToOne und dem CascadeType ALL abgebildet. Dies bedeutet, dass jedes Mal, wenn wir ein Wetter-Objekt speichern, eine Zeile in der Tabelle Weather, aber auch in den Tabellen Condition, Wind und Atmosphere erzeugt wird. Date Das Property date ist nicht annotiert, das bedeutet, dass Hibernate in Bezug auf die Spalten alle Standardwerte benutzt um diese Zuordung zu definieren. Der Spaltenname wird als date gesetzt, und der Datentyp der Spalte auf den entsprechenden Typ um dem date-Objekt zu entsprechen. Note Sollten Sie ein Property in der Abbildung nicht berücksichtingen, so würden Sie dieses Property mit @Transient annotieren. 123
  • Multi-module Enterprise Project Next, take a look at one of the secondary model objects, Condition, shown in Example 7.4, “'Condition' Modell Objekt des Projektes "simple-model"”. This class also resides in src/main/java/org/sonatype/mavenbook/weather/model. Als nächstes, schauen Sie sich das weiterführende Objekt Modell Condition an. Diese Klasse ist ebenfalls unter /src/main/java/de/sonatype/maven/weather/model abgelegt. Example 7.4. 'Condition' Modell Objekt des Projektes "simple-model" package org.sonatype.mavenbook.weather.model; import javax.persistence.*; @Entity public class Condition { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private String text; private String code; private String temp; private String date; @OneToOne(cascade=CascadeType.ALL) @JoinColumn(name="weather_id", nullable=false) private Weather weather; public Condition() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } // Alle weiteren getter/setter Methoden ausgelassen... } Die Klasse Condition entspricht der Klasse weather. Sie wird mit @Entity annotiert, und hat eine gleichartige Kennzeichnung des Properties id. Die Properties text, code, temp sowie date werden alle auf Standardwerten belassen, das Property weather ist mit der Kennzeichnung @OneToOne versehen sowie einer weiteren Annotation welche das entsprechende Objekt Weather mit dem Fremdschlüssel des Namens weather_id in Verbindung bringt. 124
  • Multi-module Enterprise Project 7.4. Das "Simple Weather" Modul - Die Dienste Das nächste Modul welches wir betrachten werden, ist so etwas wie ein "Dienst". Das "Simple Weather" Modul ist das Modul, das alle für den Abruf und die Analyse der Daten vom Yahoo! Wetter Dienst notwendige Logik enthält. Obwohl das "Simple Weather" Modul aus drei Java-Klassen und einem JUnit-Test besteht, stellt es eine einzige Komponente WeatherService, dar. Dieser wird sowohl durch "Simple Web" der Webapplikation, und auch "Simple Command" konsumiert. Häufig enthalten Enterprise Projekte ein oder mehrere API-Module welche kritische Business-Logik oder Logik zur Interaktion mit externen Systemen beinhalten. Ein Banken System könnten ein Modul beinhalten, welches Daten eines externen Providers abruft und analysiert; ein System zur Anzeige von Sportergebnisse könnte einen XML-Datenstrom verarbeiten um in Echtzeit die Ergebnisse von Basketball oder Fußball darzustellen. In unserem Beispiel kapselt dieses Modul alle notwendigen Netzwerk-Aktivitäten sowie das XML-Parsing zur Interaktion mit dem Yahoo! Wetter Dienst. Andere Module können auf dieses Modul aufbauen, Sie rufen einfach die Methode retrieveForecast() der Klasse WeatherService auf. Diese nimmt als Argument eine Postleitzahl und liefert ein Weather-Objekt zurück. Example 7.5. POM des simple-weather Moduls <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <name>Simple Weather API</name> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-model</artifactId> 125
  • Multi-module Enterprise Project <version>1.0</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> <scope>test</scope> </dependency> </dependencies> </project> Das "Simple Weather" POM erweitert das darüberliegende (elterliche) POM, setzt das Packaging auf jar und fügt die folgenden Abhängigkeiten ein: org.sonatype.mavenbook.ch07:simple-model:1.0 "Simple-Weather" analysiert den Yahoo! Wetter Dienst (RSS) und verpackt dieses in ein Objekt Weather. Es verfügt über eine direkte Abhängigkeit zum "Simple Model" Modul. log4j:log4j:1.2.14 "Simple Weather" setzt auf die Bibliothek Log4J™ zum Ausgeben von Log-Nachrichten auf. dom4j:dom4j:1.6.1 and jaxen:jaxen:1.1.1 Diese Beide Abhängigkeiten sind notwendig um das vom Yahoo! Wetter Dienst zurückgegebene XML zu verarbeiten. 126
  • Multi-module Enterprise Project org.apache.commons:commons-io:1.3.2 (scope=test) Diese Abhängigkeit mit Gültigkeitsbereich test wird von der Klasse YahooParserTest benötigt. Next is the WeatherService class, shown in Example 7.6, “Die Klasse WeatherService”. This class is going to look very similar to the WeatherService class from Example 6.3, “Die Klasse WeatherService”. Although the WeatherService is the same, there are some subtle differences in this chapter’s example. This version’s retrieveForecast() method returns a Weather object, and the formatting is going to be left to the applications that call WeatherService. The other major change is that the YahooRetriever and YahooParser are both bean properties of the WeatherService bean. Als nächstes betrachten wir die Klasse WeatherService (Example 7.6, “Die Klasse WeatherService”), diese Klasse ist sehr ähnlich zur Klasse WeatherService aus Example 6.3, “Die Klasse WeatherService” (Beispiel 6.3: Die WeatherService Klasse). Während die Klasse WeatherService die Gleiche ist, gibt es in diesem Kapitel einige subtile Unterschiede des Beispiels zu beachten. Diese Version der Methode retrieveForecast() liefert ein Objekt Weather zurück und überlässt die Formatierung der aufrufenden Anwendungen. Die andere große Änderung ist, dass die Klassen YahooRetriever und YahooParser beide Bean Properties des Beans WeatherService sind. Example 7.6. Die Klasse WeatherService package org.sonatype.mavenbook.weather; import java.io.InputStream; import org.sonatype.mavenbook.weather.model.Weather; public class WeatherService { private YahooRetriever yahooRetriever; private YahooParser yahooParser; public WeatherService() {} public Weather retrieveForecast(String zip) throws Exception { // Daten holen InputStream dataIn = yahooRetriever.retrieve(zip); 127
  • Multi-module Enterprise Project // Daten auswerten Weather weather = yahooParser.parse(zip, dataIn); return weather; } public YahooRetriever getYahooRetriever() { return yahooRetriever; } public void setYahooRetriever(YahooRetriever yahooRetriever) { this.yahooRetriever = yahooRetriever; } public YahooParser getYahooParser() { return yahooParser; } public void setYahooParser(YahooParser yahooParser) { this.yahooParser = yahooParser; } } Schlussendlich haben wir in diesem Projekt eine XML-Datei. Diese wird vom Spring Framework benutzt, um den so genannten ApplicationContext zu erstellen. Zunächst einige Erläuterungen: unsere beiden Anwendungen, die Web-Anwendung und das Befehlszeilen-Dienstprogramm, müssen mit der Klasse WeatherService interagieren. Sie tun dies indem sie aus dem Spring ApplicationContext eine Instanz der Klasse mit dem Namen WeatherService holen. Unsere Web-Applikation, verwendet einen Spring MVC-Controller welcher einer Instanz des WeatherService zugeordnet ist, und unsere Dienstprogramm lädt die Klasse WeatherService von einem Spring ApplicationContext in einer statischen Methode main(). Um die Wiederverwendbarkeit zu fördern, haben wir unter /src/main/resources eine Datei applicationContext-weather.xml beigefügt, welche im Klassenpfad zur Verfügung steht. Module, welche eine Abhängigkeit zum "Simple Weather" Modul haben, können diesen Context mit dem ClasspathXmlApplicationContext des Spring Framework laden. Hierauf können Sie auf eine benannte Instanz der Klasse WeatherService namens weatherService zugreifen. 128
  • Multi-module Enterprise Project Example 7.7. Spring Application Context fdes simple-weather Moduls <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd default-lazy-init="true"> <bean id="weatherService" class="org.sonatype.mavenbook.weather.WeatherService"> <property name="yahooRetriever" ref="yahooRetriever"/> <property name="yahooParser" ref="yahooParser"/> </bean> <bean id="yahooRetriever" class="org.sonatype.mavenbook.weather.YahooRetriever"/> <bean id="yahooParser" class="org.sonatype.mavenbook.weather.YahooParser"/> </beans> Diese Konfiguration definiert drei Beans: yahooParser, yahooRetriever und weatherService. Das Bean weatherService ist eine Instanz von WeatherService, und das XML-Dokument belegt die Properties yahooParser und yahooRetriever mit Verweisen auf benannten Instanzen der entsprechenden Klassen. Sehen Sie in der Datei applicationContext-weather.xml die Definition der Architektur eines Teilsystems in diesem Multi-Modul-Projekt. Projekte wie "Simple Web" oder "Simple Command" können diesen Context referenzieren, und somit eine Instanz des WeatherService abrufen welcher bereits in Bezug zu Instanzen von YahooRetriever und YahooParser steht. 7.5. Das "Simple Persist" Modul - Die Datenabstraktion Dieses Modul definiert zwei sehr einfache Daten Zugriffsobjekte (Data Access Objects: DAO). Ein DAO ist ein Objekt, das eine Schnittstelle für die Persistenz bereitstellt. 129
  • Multi-module Enterprise Project Note Warum Persistenz und nicht Speicherung? Persistenz ist der allgemeine Begriff, während Speicherung sich hinlänglich auf ein lokales Medium bezieht, was in keinem Fall gegeben sein muss. In einer Anwendung welche auf Object-Relational-Mapping (ORM) Werkzeuge wie Hibernate zur Abbildung aufbaut, werden in der Regel DAOs um die zu persistierenden Objekte erstellt. In diesem Projekt definieren wir zwei DAO-Objekte: WeatherDAO und LocationDAO. Die Klasse WeatherDAO erlaubt es uns, Wetter-Objekte in einer Datenbank abzuspeichern bzw. Wetter-Objekte anhand eines Schlüssels (weatherId) oder auch zu einem bestimmten Standort abzurufen. Das LocationDAO besitzt eine Methode, welche es uns erlaubt zu einer gegebenen Postleitzahl das entspreche Standort-Objekt zu holen. Lassen Sie uns einen Blick auf die "Simple Persist" POM werfen: Example 7.8. POM des "simple-persist" Moduls <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-persist</artifactId> <packaging>jar</packaging> <name>Simple Persistence API</name> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-model</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> 130
  • Multi-module Enterprise Project <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> </dependencies> </project> Diese POM-Datei referenziert "Simple Parent" als darüberliegendes, elterliches POM, sowie eine Anzahl Abhängigkeiten. Im Rahmen des "Simple Persist" Moduls sind diese: org.sonatype.mavenbook.ch07:simple-model:1.0 Genau wie bereits im "Simple Weather" Modul, wird hier eine Abhängigkeit zum grundlegenden Objekt Modell wie es in "Simple Model" definiert ist, beschrieben. org.hibernate:hibernate:3.2.5.ga Wir definieren eine Abhängigkeit zu Hibernate™ Version 3.2.5.ga, aber wie Sie richtig bemerkt haben, wird eine (transitive) Abhängigkeit von Hibernate ausgeschlossen. Wir tun dies, da die Abhängigkeit javax.transaction:javax 131
  • Multi-module Enterprise Project nicht im öffentlichen Maven-Repository verfügbar ist. Diese Abhängigkeit ist eine dieser Abhängigkeiten zu Sun Microsystems Quellcode, welche es noch nicht in die freien zentralen Maven-Repositories geschafft hat. Um zu verhindern, dass ein immer eine ärgerlicher Meldung uns auf den notwendigen Download dieser (unfreien) Abhängigkeit hinweist, schliessen wir diese einfach aus, und fügen eine neue Abhängigkeit hinzu ... javax.servlet:servlet-api:2.4 Da das Projekt ein Servlet beinhaltet, müssen wir das Servlet API version 2.4 einschliessen. org.springframework:spring:2.0.7 Dies umfasst das gesamte Spring Framework als Abhängigkeit . Note Es ist generell besser, eine Abhängigkeit zu den Komponenten von Spring zu definieren, zu denen diese tatsächlich besteht. Das Spring Framework-Projekt kommt Ihnen hier sehr entgegen, indem es genau definierende Artefakte wie z.B. spring-hibernate3 erschaffen hat.) Warum sich von Spring abhängig machen? Bei der Integration von Hibernate, erlaubt uns der Einsatz von Spring Helfer-Klassen wie die Klasse HibernateDaoSupport auszunützen. Um Ihnen ein Beispiel dafür zu geben, was unter Einsatz des HibernateDaoSupport möglich ist, werfen Sie einen Blick auf den Quellcode für das WeatherDAO: Example 7.9. WeatherDAO Klasse des "simple persist" Moduls package org.sonatype.mavenbook.weather.persist; import java.util.ArrayList; import java.util.List; import org.hibernate.Query; import org.hibernate.Session; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.sonatype.mavenbook.weather.model.Location; 132
  • Multi-module Enterprise Project import org.sonatype.mavenbook.weather.model.Weather; public class WeatherDAO extends HibernateDaoSupport# { public WeatherDAO() {} public void save(Weather weather) {# getHibernateTemplate().save( weather ); } public Weather load(Integer id) {# return (Weather) getHibernateTemplate().load( Weather.class, id); } @SuppressWarnings("unchecked") public List<Weather> recentForLocation( final Location location ) { return (List<Weather>) getHibernateTemplate().execute( new HibernateCallback() {# public Object doInHibernate(Session session) { Query query = getSession().getNamedQuery("Weather.byLocation"); query.setParameter("location", location); return new ArrayList<Weather>( query.list() ); } }); } } Das ist alles! Nein wirklich, das ist die gesamte Klasse mit welcher Sie neue Zeilen einfügen, anhand von Primärschlüsseln Werte suchen, oder auch alle Zeilen in der Weather-Tabelle finden welche eine Referenz auf einen Ort setzen. Sie werden verstehen, dass wir an dieser Stelle nicht einfach das Buch schliessen können um die notwendigen fünfhundert Seiten Erklärung der Feinheiten des Hibernate Frameworks einzuschieben, aber was wir tun können ist, Ihnen ein paar kurze Erklärungen geben: ‚ Die Klasse leitet sich von HibernateDaoSupport ab. Das bedeutet, dass die Klasse mit einer Hibernate SessionFactory verbunden ist, welche zum Einsatz kommt Hibernate Session Objekte zu erstellen. In Hibernate wird jeder Aufruf über ein Session-Objekt ausgeführt. Eine Session vermittelt Zugang zu der zugrunde liegenden Datenbank und kümmert sich um die Verwaltung der Verbindung zur JDBC-Datenquelle. Die Erweiterung von HibernateDaoSupport bedeutet auch, dass wir Zugriff auf 133
  • Multi-module Enterprise Project HibernateTemplates mittels getHibernateTemplate() haben. Um Ihnen ein Beispiel davon zu geben, was mit HibernateTemplates möglich ist ... ƒ ...die Methode save() nimmt sich einer Instanz eines Wetter Objekts an, und ruft die Methode save() eines HibernateTemplate auf. Das HibernateTemplate verwandelt den Aufruf in einfache Hibernate Operationen um, und wandelt jegliche Datenbank spezifische Exeptions in entsprechende Runtime-Exceptions um. In diesem Fall rufen wir save() auf, um einen Datensatz in der Datenbank, in der Tabelle Weather abzulegen. Alternativen zu diesem Aufruf wären update() sollten wir einen Eintrag aktualisieren, oder saveOrUpdate(), welches den Datensatz entweder anlegt oder aktualisiert, abhängig von der Anwesenheit eines Properties id des Objekts Weather, das nicht Null sein darf. „ Die Methode load() ist ein weiterer Einzeiler, welcher lediglich eine Methode einer Instanz des HibernateTemplates aufruft. Die Methode load() des HibernateTemplates hat zwei Parameter eine Klasse sowie eine serialisierbares Objekt. In diesem Fall entspricht das serialisierbare Objekt dem Wert der id des Objekts Weather welches geladen werden soll. … Diese letzte Methode recentForLocation() beruft sich auf ein NamedQuery das im Wetter-Objekt-Modell festgelegt wurde. Sollten Sie sich erinnern, wir haben im Wetter-Objekt-Modell eine Abfrage "Weather.byLocation" in der Form "from Weather w where w.location = :location" definiert. Wir Laden dieses NamedQuery mit einem Verweis auf ein Hibernate Session-Objekt in einem HibernateCallback der ausgeführt wird, indem die Methode execute() eines HibernateTemplate aufgerufen wird. In dieser Methode sehen Sie, wie wir die benannten Parameter mit dem Parameter des Aufrufs der Methode recentForLocation() bevölkern. Now is a good time for some clarification. HibernateDaoSupport and HibernateTemplate are classes from the Spring Framework. They were created by the Spring Framework to make writing Hibernate DAO objects painless. To support this DAO, we’ll need to do some configuration in the simple-persist Spring ApplicationContext definition. The XML document shown in Example 7.10, “Spring Application Context Definition für das "simple-persist" Modul” is stored in src/main/resources in a file named applicationContext-persist.xml. 134
  • Multi-module Enterprise Project Es ist an der Zeit für einige Klarstellungen: HibernateDaoSupport und HibernateTemplate sind Klassen des Spring Framework™. Sie wurden von den Entwicklern des Spring Framework bereitgestellt, um das Bearbeiten von Hibernate DAO-Objekte schmerzloser zu gestalten. Um dieses DAO zu unterstützen, müssen wir noch einige wenige Einstellungen in der Definition des Spring ApplicationContext von "Simple Persist" vornehmen. Das folgende XML-Dokument ist unter /src/main/resources in einer Datei mit dem Namen applicationContext-persist.xml abgelegt. Example 7.10. Spring Application Context Definition für das "simple-persist" Modul <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" default-lazy-init="true"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="annotatedClasses"> <list> <value>org.sonatype.mavenbook.weather.model.Atmosphere</value> <value>org.sonatype.mavenbook.weather.model.Condition</value> <value>org.sonatype.mavenbook.weather.model.Location</value> <value>org.sonatype.mavenbook.weather.model.Weather</value> <value>org.sonatype.mavenbook.weather.model.Wind</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql">false</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.transaction.factory_class"> org.hibernate.transaction.JDBCTransactionFactory </prop> <prop key="hibernate.dialect"> org.hibernate.dialect.HSQLDialect </prop> <prop key="hibernate.connection.pool_size">0</prop> <prop key="hibernate.connection.driver_class"> org.hsqldb.jdbcDriver </prop> <prop key="hibernate.connection.url"> jdbc:hsqldb:data/weather;shutdown=true </prop> <prop key="hibernate.connection.username">sa</prop> 135
  • Multi-module Enterprise Project <prop key="hibernate.connection.password"></prop> <prop key="hibernate.connection.autocommit">true</prop> <prop key="hibernate.jdbc.batch_size">0</prop> </props> </property> </bean> <bean id="locationDAO" class="org.sonatype.mavenbook.weather.persist.LocationDAO"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="weatherDAO" class="org.sonatype.mavenbook.weather.persist.WeatherDAO"> <property name="sessionFactory" ref="sessionFactory"/> </bean> </beans> Dieser ApplicationContext erreicht eine Anzahl verschiedener Dinge: zunächst wird ein SessionFactory Bean bereitgestellt, ein Bean von welchem die DAOs die Hibernate Session Objekte beziehen. Dieses Bean ist eine Instanz des AnnotatedServiceFactoryBean und wird mit einer Anzahl annotatedClasses bereitgestellt. Hier sei bemerkt, dass die Liste der bereitgestellten annotierten Klassen die Liste der Klassen darstellt, welche in unserem Modul "Simple Model" definiert wurden. Als nächstes wird die sessionFactory entsprechend einer Reihe von Hibernate Konfigurationseigenschaften (hibernateProperties) konfiguriert. In diesem Beispiel definieren die HibernateProperties eine Reihe von Einstellungen: hibernate.dialect Diese Einstellung definiert, welcher SQL-Dialekt für unsere Datenbank generiert wird. Da wir als Datenbank HSQLDB einsetzen, ist der gewählte Datenbank Dialekt org.hibernate.dialect.HSQLDialect. Hibernate unterstützt die SQL-Dialekte aller wichtigen Datenbank Systeme wie Oracle, MySQL, PostgreSQL und SQL Server. hibernate.connection.* In diesem Beispiel werden die JDBC-Verbindungsparameter aus der Spring Konfiguration heraus gesetzt. Unsere Anwendungen sind so konfiguriert, dass sie gegen eine HSQLDB unter dem Verzeichnis ./data/weather arbeiten. In 136
  • Multi-module Enterprise Project einer echten Enterprise Applikation ist es wahrscheinlicher, dass Sie die Datenbankkonfiguration mittles JNDI externalisieren und somit von Ihrem Quellcode trennen. Schließlich werden in der Definitionsdatei der Bean die beiden DAO-Objekte des "Simple Persist" Moduls erstellt und mit einer Referenz auf die eben erstellte sessionFactory Bean versehen. Genau wie im Spring Applikations- Context von "Simple Weather" definiert diese Datei applicationContext-persit.xml die Architektur eines Submoduls einer grösseren Anwendung. Sollten Sie mit einer grösseren Anzahl von Persistenzklassen arbeiten, bietet es sich unter Umständen an, diese in einem eigenen applicationContext ausserhalb Ihrer Anwendung zusammenzufassen. There’s one last piece of the puzzle in simple-persist. Later in this chapter, we’re going to see how we can use the Maven Hibernate3 plugin to generate our database schema from the annotated model objects. For this to work properly, the Maven Hibernate3 plugin needs to read the JDBC connection configuration parameters, the list of annotated classes, and other Hibernate configuration from a file named hibernate.cfg.xml in src/main/resources. The purpose of this file (which duplicates some of the configuration in applicationContext-persist.xml) is to allow us to leverage the Maven Hibernate3 plugin to generate Data Definition Language (DDL) from nothing more than our annotations. See Example 7.11, “hibernate.cfg.xml des "simple-persist" Moduls”. Als letztes Puzzleteil des "Simple Persist" Beispiels werden wir später im Kapitel erfahren, wie wir mit der Hilfe des Hibernate3 Maven Plugins aus den annotierten Modellobjekten unser Datenbankschema erzeugen können. Damit dies funktioniert, muss das Hibernate3 Maven Plugin die Parameter der JDBC-Verbindungskonfiguration, die Liste der kommentierten Klassen und andere Hibernate-Konfigurations Parameter aus einer Datei mit dem Namen hibernate.cfg.xml unter /src/main/resources auslesen können. Der Zweck dieser Datei (welche einige Konfigurationen der applicationContext-persist.xml dupliziert) ist es, das Hibernate3 Maven Plugin so zu nutzen, dass wir aus nichts weiter als unseren annotierten Klassen die gesamte DDL erstellen können. 137
  • Multi-module Enterprise Project Example 7.11. hibernate.cfg.xml des "simple-persist" Moduls <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- SQL Dialekt --> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <!-- Datenbank Verbindungsparamenter --> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="connection.url">jdbc:hsqldb:data/weather</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <property name="connection.shutdown">true</property> <!-- JDBC connection pool (den gestellten pool benutzen) --> <property name="connection.pool_size">1</property> <!-- Aktivierung des automatischen Hibernate Session Context Management --> <property name="current_session_context_class">thread</property> <!-- Deaktivierung des second Level Cache --> <property name="cache.provider_class"> org.hibernate.cache.NoCacheProvider </property> <!-- Ausgabe allen SQL auf stdout --> <property name="show_sql">true</property> <!-- deaktivierung der Batch funktionalität von HSQLDB um Fehler korrekt weiterzugeben - <property name="jdbc.batch_size">0</property> <!-- Auflisten aller bestehender Property Dateien --> <mapping class="org.sonatype.mavenbook.weather.model.Atmosphere"/> <mapping class="org.sonatype.mavenbook.weather.model.Condition"/> <mapping class="org.sonatype.mavenbook.weather.model.Location"/> <mapping class="org.sonatype.mavenbook.weather.model.Weather"/> <mapping class="org.sonatype.mavenbook.weather.model.Wind"/> </session-factory> </hibernate-configuration> Die Inhalte von Example 7.10, “Spring Application Context Definition für das "simple-persist" Modul” (Beispiel 7.10: "'Simple Persist' Spring Application 138
  • Multi-module Enterprise Project Context") und Example 7.1, “Simple Parent POM Projekt” (Beispiel 7.1: "Simple Parent POM Project" ) sind redundant. Während die Datei SpringApplicationContext.xml von der Web- sowie der Befehlszeilen-Anwendung benutzt wird, findet die Datei hibernate.cfg.xml nur Einsatz zur Unterstützung des Hibernate3 Maven Plugins. Später in diesem Kapitel werden wir sehen, wie man mittels der Datei hibernate.cfg.xml und dem Hibernate3 Maven Plugin ein Datenbank-Schema, basierend auf dem kommentierten Objektmodell wie dieses in "Simple Model" besteht, generieren kann. Es ist die Datei hibernate.cfg.xml welche die Eigenschaften der JDBC-Verbindung konfiguriert und die annotierten Modelklassen in form einer Aufzählung dem Hibernate3 Maven Plugin bereitstellt. 7.6. Das "Simple Web" Modul - Das Web-Interface Die Web-Anwendung wird in einem einfachen Web Applikations Projekt definiert. Dieses einfache Web Applikations Projekt definiert zwei Spring MVC Controller Objekte: WeatherController und HistoryController. Beide Controller werden auf Komponenten der "Simple Weather" und "Simple Persist" Module abgebildet. Der Spring Container wird in der zu dieser Applikation zugehörigen Datei web.xml konfiguriert. Diese referenziert die Datei applicationContext-weather.xml unter "Simple Weather" sowie die Datei applicationContext-persist.xml in "Simple Persist". Die Komponenten-Architektur von dieser einfachen Web-Anwendung ist in Figure 7.3, “Abhängigkeiten des Spring MVC Controllers zu den Komponenten aus 'Simple Weather' sowie 'Simple Persist'” (Abbildung 7.3: "Abhängigkeiten des Spring MVC Controllers zu den Komponenten aus 'Simple Weather' sowie 'Simple Persist' ") dargestellt. 139
  • Multi-module Enterprise Project Figure 7.3. Abhängigkeiten des Spring MVC Controllers zu den Komponenten aus 'Simple Weather' sowie 'Simple Persist' Das POM des Moduls "Simple Webapp" wird unten aufgeführt: Example 7.12. POM fder simple-webapp <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <name>Simple Web Application</name> <dependencies> 140
  • Multi-module Enterprise Project <dependency> # <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-weather</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-persist</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> # <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <dependencies># <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> # <artifactId>hibernate3-maven-plugin</artifactId> <version>2.0</version> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> # </component> </components> </configuration> 141
  • Multi-module Enterprise Project <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </plugin> </plugins> </build> </project> Mit dem fortschreiten des Buches werden Sie festellen, dass die Datei pom.xml an Länge gewinnt. In diesem POM werden wir vier Abhängigkeiten und zwei Plugins konfigurieren. Lassen Sie uns Schritt für Schritt das POM durchgehen und die für die Konfiguration wichtigen Punkte genauer erläutern: ‚ Das "Simple Web" Applikationsmodul definiert vier Abhängigkeiten: die Servlet-Spezifikation 2.4, die "Simple Weather" Dienste, die "Simple Persist" Applikations-Bibliothek sowie das gesamte Spring Framework 2.0.7. ƒ Das Jetty Maven Plugin könnte nicht einfacher in dieses Projekt einzufügen sein: wir fügen einfach ein Plugin-Element, welches die entsprechenden Koordinaten groupId und artifactId setzt, ein. Die Tatsache, dass dieses Plugin so trivial zu konfigurieren ist bedeutet, dass die Plugin-Entwickler einen guten Dienst getan haben. Es wurden sinnvolle Standardwerte gesetzt, welche wir in den meisten Fällen nicht übersteuern müssen. Sollten Sie dennoch die Standardeinstellungen übersteuern wollen/müssen, so tun Sie dies indem Sie ein Konfigurationselement mit den entsprechenden Werten einfügen. „ In unserer Build Konfiguration werden wir das Hibernate3 Maven Plugin so konfigurieren, dass dieses gegen eine eingebettete Instanz einer HSSQL Datenbank läuft. Damit das Hibernate3 Maven Plugin auf die Datenbank zugreifen kann, muss diese einen Verweis auf die JDBC-Treiber der HSSQL-Datenbank im Klassenpfad bekommen. Um diese Abhängigkeit eines Plugins bereitzustellen, erklären wir diese direkt im Plugin-Element. Hier referenzieren wir hsqldb:hsqldb:1.8.0.7™. Das Hibernate Plugin 142
  • Multi-module Enterprise Project benötigt die JDBC-Treiber ebenfalls, um die Datenbank zu erstellen. Aus diesem Grund haben wir diese dort ebenfalls referenziert. … Es ist hier im Hibernate Maven Plugin ist, wo dieses POM anfängt interessant zu werden: Im nächsten Abschnitt werden wir das Goal hbm2ddl ausführen, um die HSQLDB Struktur zu erstellen. In dieser Datei pom.xml schliessen wir ebenfalls eine Referenz auf die Version 2.0 des Hibernate3 Maven Plugin von Codehouse MoJo ein. † Das Hibernate3 Maven Plugin kann auf verschiedenerlei Wegen an die Hibernate Mapping Informationen gelangen; abhängig vom jeweiligen Einsatzszenario des Hibernate Plugins. Sollten sie Hibernate Mapping XML(.hbm.xml)-Dateien einsetzen und hätten vor, Modellklassen mit Hilfe des Goals hbmjava zu generieren, so würden Sie Ihre Implementierung auf configuration stellen. Würden Sie jedoch Hiberante3 einsetzen um eine bestehende Datenbank zu re-engineeren, um daraus die entsprechenden .hbm.xml Dateien zu generieren und die Klassen dann daraus abzuleiten, so würden Sie die Implementierung jdbcconfiguration wählen. In unserem Fall benutzen wir einfach ein annotiertes Objekt Modell um eine Datenbank zu generieren. In anderen Worten, wir haben bereits unser Hibernate Mapping, jedoch keine bestehende Datenbank. In diesem Fall ist wählen wir annotationconfiguration. Das Hiberante3 Maven Plugin wird unter Section 7.7, “Aufrufen der Web-Anwendung” (Abschnitt 7.7: "Aufrufen der Web-Anwendung") näher beleuchtet. Note Ein häufiger auftretender Fehler ist die Verwendung der extensions-Konfiguration um Abhängigkeiten von Plugins zu definieren. Wir raten von dieser Praxis dringend ab, denn dies kann dazu führen, dass Ihr Klassenpfad -neben anderen unschönen Nebeneffekten- projektweit "verschmutzt" wird. Außerdem wird in der Version 2.1 genau diese Funktionalität stark überarbeitet werden, was bedeutet, dass Sie hier in jedem Fall noch einmal Hand anlegen müssten. Die einzige "normale" Nutzung für Extensions ist die, eine neue Paketdefinition zu erstellen. 143
  • Multi-module Enterprise Project Nun werden wir uns den beiden Spring MVC Controllern und deren Konfiguration zuwenden. Beide dieser Controller beziehen sich auf Beans welche in "Simple Weather" bzw "Simple Persist" definiert wurden. Example 7.13. simple-webapp WeatherController package org.sonatype.mavenbook.web; import org.sonatype.mavenbook.weather.model.Weather; import org.sonatype.mavenbook.weather.persist.WeatherDAO; import org.sonatype.mavenbook.weather.WeatherService; import javax.servlet.http.*; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class WeatherController implements Controller { private WeatherService weatherService; private WeatherDAO weatherDAO; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { String zip = request.getParameter("zip"); Weather weather = weatherService.retrieveForecast(zip); weatherDAO.save(weather); return new ModelAndView("weather", "weather", weather); } public WeatherService getWeatherService() { return weatherService; } public void setWeatherService(WeatherService weatherService) { this.weatherService = weatherService; } public WeatherDAO getWeatherDAO() { return weatherDAO; } public void setWeatherDAO(WeatherDAO weatherDAO) { this.weatherDAO = weatherDAO; } } Die Klasse WeatherController implementiert das Spring MVC Controller Interface, welches die Existenz einer Methode handleRequest() in der oben angegebenen 144
  • Multi-module Enterprise Project Form vorschreibt. Wenn Sie das Innenleben dieser Methode betrachten, so werden Sie feststellen, dass diese die Methode retrieveForecast() der Instanzvariablen weatherService aufruft. Anders als im vorgehenden Beispiel in welchem die Klasse WeatherService durch ein Servlet instanziert wurde, ist weatherController ein Bean, mit einem weatherService Property. Es liegt in der Verantwortung des Spring IoC Containers die Verdrahtung der Komponennte weatherService bereitzustellen. Darüber hinaus werden Sie bemerkt haben, dass wir in dieser Implementierung auf den weatherFormatter verzichten, an dessen Statt geben wir das Objekt Weather, welches von retrieveForecast() zurückgegeben wird an den Konstruktor von ModelAndView. Die Klasse ModelAndView werden wir benutzen um Velocity Templates darzustellen, welche eine Referenz zu einer Variablen ${weather} halten. Das zugehörige Template weather.vm liegt unter /src/main/webapp/WEB-INF/vm abgelegt und wird in ??? (Beispiel 7.14) ausgeführt. In the WeatherController, before we render the output of the forecast, we pass the Weather object returned by the WeatherService to the save() method on WeatherDAO. Here we are saving this Weather object—using Hibernate—to an HSQLDB database. Later, in HistoryController, we will see how we can retrieve a history of weather forecasts that were saved by the WeatherController. Innerhalb des WeatherController, geben wir das vom WeatherService zurückgegebene Objekt Weather an die Methode save() des WeatherDAO. Dort wird das Objekt Weather mit Hilfe von Hibernate in der HSQLDB abgespeichert. Später im HistoryController werden wir sehen wie man die Vergangenheit bezüglich einer Wettervorhersage welche von HSQLDB gespeichert wurde, zurückgeholen kann. Example 7.14. Die von WeatherController wiedergegebene Vorlage weather.vm <b>Derzeitiges Wetter von: ${weather.location.city}, ${weather.location.region}, ${weather.location.country}</b><br/> <ul> <li>Temperatur: ${weather.condition.temp}</li> <li>Wetterlage: ${weather.condition.text}</li> 145
  • Multi-module Enterprise Project <li>Feuchtigkeit: ${weather.atmosphere.humidity}</li> <li>Wind Chill Faktor: ${weather.wind.chill}</li> <li>Datum: ${weather.date}</li> </ul> Die Syntax der Velocity Vorlagen ist einfach, Variablen werden durch vorangestelltes Dollarzeichen in geschwungenen Klammern markiert ${Variable}. Der Ausdruck zwischen den geschweiften Klammern verweist auf ein Property, oder ein Property eines Properties der Variable weather, welche durch den WeatherController an das Template weitergegeben wurde. Der HistoryController wird eingesetzt, um auf Prognosen aus der Vergangenheit zuzugreifen welche zuvor vom WeatherController angefragt wurden. Immer, wenn wir wieder eine Prognose mittels dem WeatherController abrufen, speichert der Controller das Objekt Weather mittels dem WeatherDAO in der Datenbank. Das WeatherDAO nutzt Hibernate um das Objekt Weather in eine Reihe von Zeilen zu zerlegen und in eine Anzahl in Relation stehende Datenbanktabellen abzuspeichern. Der HistoryController ist im Example 7.15, “simple-web HistoryController” (Beispiel 7.15: "Simple Web" HistoryController) ausgeführt. Example 7.15. simple-web HistoryController package org.sonatype.mavenbook.web; import java.util.*; import javax.servlet.http.*; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import org.sonatype.mavenbook.weather.model.*; import org.sonatype.mavenbook.weather.persist.*; public class HistoryController implements Controller { private LocationDAO locationDAO; private WeatherDAO weatherDAO; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { String zip = request.getParameter("zip"); Location location = locationDAO.findByZip(zip); List<Weather> weathers = weatherDAO.recentForLocation( location ); Map<String,Object> model = new HashMap<String,Object>(); 146
  • Multi-module Enterprise Project model.put( "location", location ); model.put( "weathers", weathers ); return new ModelAndView("history", model); } public WeatherDAO getWeatherDAO() { return weatherDAO; } public void setWeatherDAO(WeatherDAO weatherDAO) { this.weatherDAO = weatherDAO; } public LocationDAO getLocationDAO() { return locationDAO; } public void setLocationDAO(LocationDAO locationDAO) { this.locationDAO = locationDAO; } } Der HistoryController ist mit zwei in "Simple Persist" definierten DAO-Objekten verdrahtet. Dabei sind die DAOs Bean-Properties des HistoryControllers: WeatherDAO und LocationDAO. Die Aufgabe des HistoryController ist, eine Liste von Weather Objekten entsprechend einer bestimmten Postleitzahl zurückzugeben. Wenn das WeatherDAO ein Objekt Weather in die Datenbank speichert, so legt es nicht eine Postleitzahl ab, sonder es speichert zusätzlich eine Referenz auf ein Objekt Location welches an das Objekt Weather gebunden ist, wie in "Simple Model" definiert. Um eine Liste von Weather-Objekte abzurufen, ruft der HistoryController zunächst das Objekt Lokation auf, welches der Postleitzahl entspricht. Dies wird durch den Aufruf der Methode findByZip() des LocationDAO bewerkstelligt. Nachdem ein Objekt Lokation abgerufen wurde, versucht der HistoryController die vorgängigen Weather Objekte mit Referenz auf das Objekt Lokation abzurufen. Das Ergebnis, welches in in der Form List<Weather> besteht, wird dann in eine HashMap von zwei Variablen der Velocity Vorlage history.vm wie im ??? (Beispiel 7.16: "history.vm dargestellt vom HistoryController") dargestellt, umgewandelt. 147
  • Multi-module Enterprise Project Example 7.16. history.vm dargestellt vom HistoryController <b> Derzeitige Wetterlage von: ${location.city}, ${location.region}, ${location.country} </b> <br/> #foreach( $weather in $weathers ) <ul> <li>Temperatur: $weather.condition.temp</li> <li>Zustand: $weather.condition.text</li> <li>Feuchtigkeit: $weather.atmosphere.humidity</li> <li>Wind Chill Faktor: $weather.wind.chill</li> <li>Datum: $weather.date</li> </ul> #end Die Vorlage history.vm is unter /src/main/webapp/WEB-INF/vm abgelegt und verweist auf die Variable location um Angaben zum Standort/Lokation der Prognose, welche vom WeatherDAO zurückgegeben wurde auszugeben. Diese Vorlage unterliegt einer Velocity Kontrollstruktur, #foreach, welche sicherstellt, dass durch alle Elemente der Variablen weathers geschlauft wird. Jedes Element in weathers wird einer Variable weather zugeordnet und mit der Vorlage welche sich zwischen #foreach und #end befindet, verknüpft und ausgegeben. You've seen these Controller implementations, and you've seen that they reference other beans defined in simple-weather and simple-persist, they respond to HTTP requests, and they yield control to some mysterious templating system that knows how to render Velocity templates. All of this magic is configured in a Spring application context in src/main/webapp/WEB-INF/weather-servlet.xml. This XML configures the controllers and references other Spring-managed beans, it is loaded by a ServletContextListener which is also configured to load the applicationContext-weather.xml and applicationContext-persist.xml from the classpath. Let's take a closer look at the weather-servlet.xml shown in ???. Sie haben nun die Controller-Implementierung gesehen, und Sie haben gesehen, dass sich deren Implementierung auf weitere Beans welche in "Simple Weather" sowie "Simple Persist" definiert sind, stützt. Sie reagieren auf HTTP-Anfragen, 148
  • Multi-module Enterprise Project und übernehmen die Kontrolle eines misteriösen Templating-Systems welches weiß, wie man Velocity-Vorlagen verarbeitet. Diese ganze Magie wurde im Spring applicationContext definiert, welcher sich unter /src/main/webapp/WEB-INF/weather-servlet.xml befindet. Diese XML-Konfiguration konfiguriert die Controller und definiert weitere Referenzen zu Beans welche von Spring verwaltet werden. die Datei weather-servlet.xml wird von einem ServletContextListener geladen, welcher zugleich so konfiguriert ist, dass er die Datei applicationContext-weather.xml sowie applicationContext-persist.xml vom Klassenpfad lädt. Lassen Sie uns die datei weather-servlet.xml einmal genauer anschauen. Example 7.17. Spring Controller Konfiguration weather-servlet.xml <beans> <bean id="weatherController" # class="org.sonatype.mavenbook.web.WeatherController"> <property name="weatherService" ref="weatherService"/> <property name="weatherDAO" ref="weatherDAO"/> </bean> <bean id="historyController" class="org.sonatype.mavenbook.web.HistoryController"> <property name="weatherDAO" ref="weatherDAO"/> <property name="locationDAO" ref="locationDAO"/> </bean> <!-- Sie können mehr als einen Controller definieren --> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/weather.x"> # <ref bean="weatherController" /> </entry> <entry key="/history.x"> <ref bean="historyController" /> </entry> </map> </property> </bean> <bean id="velocityConfig" # class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath" value="/WEB-INF/vm/"/> </bean> 149
  • Multi-module Enterprise Project <bean id="viewResolver" # class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".vm"/> <property name="exposeSpringMacroHelpers" value="true"/> </bean> </beans> ‚ Die Datei weather-servlet.xml definiert die beiden Controller als Spring-verwaltete Beans. weatherController besitzt zwei Properties, welche Verweise auf weatherService und weatherDAO darstellen; historyController verweist auf die Beans weatherDAO und locationDAO. Wenn dieser ApplicationContext erstellt wird, so geschieht dies in einem Umfeld, welches den Zugang zu den ApplicationContexten definiert, sowohl zum "Simple Persist" wie auch zum "Simple Weather" Application Context. Im ??? Beispiel werden Sie sehen, wie man Spring konfigurieren kann, um mehrere Komponenten aus verschiedenen Spring Konfigurations-Dateien zusammenzuführen. ƒ Das Bean urlMapping definiert URL-Muster, welche den WeatherController und den HistoryController aufrufen. In diesem Beispiel benutzen wir SimpleUrlHandlerMapping und bilden /weather.x auf den WeatherController sowie /history.x auf den HistoryController ab. „ Da wir mit der Velocity-Templating-Engine arbeiten, sind wir gezwungen etliche Konfigurations-Optionen weiterzugeben. Hierzu weisen wir in der Datei velocityConfig.xml Velocity an, Vorlagen immer unter /WEB-INF/vm zu suchen. … Zuletzt wird der viewResolver mit der Klasse VelocityViewResolver konfiguriert. Es gibt bereits eine Reihe von Implementierungen des ViewResolvers in Spring; diese reichen von einem Standard-ViewResolver um JSTL/JSP-Seiten zu generieren, und reicht bis zu solchen welcher Freemaker Templates erstellen können. In unserem Beispiel werden wir die Velocity-Templating-Engine konfigurieren, sowie die Standard-Präfix und Suffixe setzen, welche automatisch an die Namen der Vorlagen angefügt werden und an ModelAndView weitergegeben werden. 150
  • Multi-module Enterprise Project Finally, the simple-webapp project was a web.xml which provides the basic configuration for the web application. The web.xml file is shown in ???. Schließlich ist das "Simple Web" Projekt eine Webanwendung, und benötigt somit eine Datei web.xml als Basiskonfiguration der Anwendung. Die zugehörige Datei web.xml ist ??? (7.18: "web.xml von 'Simple Web' ") wiedergegeben. Example 7.18. web.xml von 'Simple Web' <web-app id="simple-webapp" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>Simple Web Application</display-name> <context-param> # <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext-weather.xml classpath:applicationContext-persist.xml </param-value> </context-param> <context-param> # <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </context-param> <listener> # <listener-class> org.springframework.web.util.Log4jConfigListener </listener-class> </listener> <listener> <listener-class> # org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> # <servlet-name>weather</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> # 151
  • Multi-module Enterprise Project <servlet-name>weather</servlet-name> <url-pattern>*.x</url-pattern> </servlet-mapping> </web-app> ‚ Hier ein kleines Kusntstück das uns die Wiederverwendung des applicationContext-weather.xml sowie applicationContest.persist.xml innerhalb dieses Projekts erlaubt. Die contextConfigLocation wird vom ContextLoaderListener zur Erstellung eines ApplicationContext benutzt. Beim Erstellen des Weather Servlets wird die Datei weather-servlet.xml -wie in ??? (Beispiel 7.17: Spring Controller Konfiguration weather-servlet.xml) dargestellt - im Zusammenhang mit dem applicationContext welcher aus der contextConfigLocation geladen wird, ausgewertet. Auf diese Weise können Sie in einem anderen Projekt eine Anzahl Beans definieren und auf diese über den Klassenpfad zugreifen. Da sowohl "Simple Persist" als auch "Simple Weather" als JAR-Archiv unter /WEB-INF/lib verfügbar sein werden, müssen wir nur noch das Präfix classpath: benutzen, um diese Dateien zu referenzieren. (Eine andere Möglichkeit wäre gewesen, diese Dateien nach /WEB-INF zu kopieren, um sie dort mit einem Konstrukt wie etwa /WEB-INF/applicationContext-persist.xml zu referenzieren). ƒ Die log4jConfigLocation wird verwendet, um dem Log4JConfigListener mitzuteilen, wo dieser die Log4J Logging-Konfiguration finden kann. In diesem Beispiel weisen wir log4J an, unter /WEB-INF/log4j.properties zu suchen. „ Hiermit stellen wir sicher, dass das Log4J Subsystem konfiguriert ist, sobald die Web-Applikation gestartet wird. Es ist wichtig, den Log4JConfigListener vor dem ContextLoaderListener zu platzieren, da Sie anderenfalls unter Umständen wichtige Logging-Nachrichten verpassen, welche auf Probleme hinweisen können, die den ordungsgemässen Start der Anwendung verhindern. Insbesondere wenn Sie eine besonders große Anzahl Beans unter der Verwaltung von Spring haben, und eines der Beans beim Applikationsstart umfällt, wird Ihre Anwendung scheitern. Haben Sie dann bereits zuvor das Logging initialisiert, so haben Sei vielleicht die Möglichkeit 152
  • Multi-module Enterprise Project eine Warnung oder eine Fehlermeldung aufzufangen; andernfalls werden Sie wohl keinerlei Ahnung haben warum Ihnen der Anwendungsstart misglückt ist. … Der ContextLoaderListener ist im Wesentlichen der Spring Container. Beim Start einer Anwendung erstellt dieser Listner einen ApplicationContext aus dem Wert des contextConfigLocation Parameters. † Wir definieren ein Spring MVC DispatcherServlet mit dem Namen weather. Hierdurch wird Spring unter /WEB-INF/weather-servlet.xml nach einer Spring Konfigurationsdatei suchen. Sie können so viele DispatcherServlets erstellen, wie Sie benötigen, ein DispatcherServlet kann eine oder mehrere Spring MVC-Controller-Implementierungen enthalten ‡ Alle in der Endung ".x" endenden Anfragen werden zum Weather Servlet geleitet. Wir möchten hier darauf hinweisen, dass die Endung ".x" keine tiefere Bedeutung hat, es ist eine willkürliche Auswahl welche Sie mit jedem beliebigen URL-Muster ersetzen können. 7.7. Aufrufen der Web-Anwendung Um die Web-Anwendung aufrufen zu können müssen Sei zunächst mittels dem Hibernate3 Maven Plugin die Datenbank erstellen. Um dies zu tun, geben Sie bitte das folgende Kommando auf der Befehlszeile im Verzeichnis des "Simple Web" Projekts ein : $ mvn hibernate3:hbm2ddl [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'hibernate3'. [INFO] org.codehaus.mojo: checking for updates from central [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 7 Simple Web Application [INFO] task-segment: [hibernate3:hbm2ddl] [INFO] ------------------------------------------------------------------------ [INFO] Preparing hibernate3:hbm2ddl ... 10:24:56,151 INFO org.hibernate.tool.hbm2ddl.SchemaExport - export complete [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ 153
  • Multi-module Enterprise Project Nach Abschluss der Ausführung sollte ein Verzeichnis ${basedir}/data erstellt sein welches die HSQLDB Datenbank enthält. Um die Web Applikation nun zu starten geben Sie folgenden Befehl ein: $ mvn jetty:run [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'jetty'. [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 7 Simple Web Application [INFO] task-segment: [jetty:run] [INFO] ------------------------------------------------------------------------ [INFO] Preparing jetty:run ... [INFO] [jetty:run] [INFO] Configuring Jetty for project: Chapter 7 Simple Web Application ... [INFO] Context path = /simple-webapp [INFO] Tmp directory = determined at runtime [INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml [INFO] Web overrides = none [INFO] Starting jetty 6.1.7 ... 2008-03-25 10:28:03.639::INFO: jetty-6.1.7 ... 2147 INFO DispatcherServlet - FrameworkServlet 'weather': initialization completed in 1654 ms 2008-03-25 10:28:06.341::INFO: Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server Nach dem Start von Jetty, können Sie mit dem Aufruf http://localhost:8080/simple-webapp/weather.x?zip=60202 das Wetter von Evanston, IL aufrufen. Mit der Änderung der Postleitzahl sollte es Ihnen möglich sein Ihren eigenen Wetterbericht abzurufen. Derzeitige Wetterlage von: Evanston, IL, US * Temperatur: 42 * Zustand: Partly Cloudy * Feuchtigkeit: 55 * Wind Chill Faktor: 34 * Datum: Tue Mar 25 10:29:45 CDT 2008 7.8. Das "Simple Command" Modul - Das Kommandozeilen Modul 154
  • Multi-module Enterprise Project Das "Simple Command" Modul ist die Befehlszeilen-Version der "Simple Web" Anwendung. Es ist ein Dienstprogramm, das auf den selben Abhängigkeiten aufbaut: "Simple Weather" und "Simple Persist". Statt der Interaktion über einen Web-Browser, rufen Sie für diese Anwendung ein Dienstprogramm 'simple-command' von der Befehlszeile aus auf. Figure 7.4. Dienstprogramm aufbauend auf die Modue Simple-Weather und Simple-Persist Example 7.19. POM des Simple-Command Moduls <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> 155
  • Multi-module Enterprise Project </parent> <artifactId>simple-command</artifactId> <packaging>jar</packaging> <name>Simple Command Line Tool</name> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>hibernate3-maven-plugin</artifactId> <version>2.1</version> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> </component> </components> </configuration> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </plugin> </plugins> 156
  • Multi-module Enterprise Project </build> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-weather</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-persist</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </project> Dieses POM wird die Erstellung eines JAR files veranlassen, welches die Klasse org.sonatype.mavenbook.weather.Main beinhaltet. Diese Klasse wird in Example 7.20, “Klasse Main des Simple-Command Moduls” (Beispiel 7.20:"Klasse Main des Simple-Command Moduls" dargestellt. In dieser pom.xml-Datei werden wir das Maven Plugin Assembly um einen bereits mitgelieferten "Assembly Beschrieb" mit dem Namen "jar-with-dependencies" konfigurieren. Dieses Plugin wird sodann ein JAR- Archive erstellen, welches allen Bytecode beinhaltet welcher gebraucht wird, um das Projekt auszuführen, wie auch alle weitergehenden Abhängigkeiten. Example 7.20. Klasse Main des Simple-Command Moduls package org.sonatype.mavenbook.weather; import java.util.List; import org.apache.log4j.PropertyConfigurator; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; 157
  • Multi-module Enterprise Project import org.sonatype.mavenbook.weather.model.Location; import org.sonatype.mavenbook.weather.model.Weather; import org.sonatype.mavenbook.weather.persist.LocationDAO; import org.sonatype.mavenbook.weather.persist.WeatherDAO; public class Main { private WeatherService weatherService; private WeatherDAO weatherDAO; private LocationDAO locationDAO; public static void main(String[] args) throws Exception { // Configure Log4J PropertyConfigurator.configure(Main.class.getClassLoader().getResource( "log4j.properties")); // Read the Zip Code from the Command-line (if none supplied, use 60202) String zipcode = "60202"; try { zipcode = args[0]; } catch (Exception e) { } // Read the Operation from the Command-line (if none supplied use weather) String operation = "weather"; try { operation = args[1]; } catch (Exception e) { } // Start the program Main main = new Main(zipcode); ApplicationContext context = new ClassPathXmlApplicationContext( new String[] { "classpath:applicationContext-weather.xml", "classpath:applicationContext-persist.xml" }); main.weatherService = (WeatherService) context.getBean("weatherService"); main.locationDAO = (LocationDAO) context.getBean("locationDAO"); main.weatherDAO = (WeatherDAO) context.getBean("weatherDAO"); if( operation.equals("weather")) { main.getWeather(); } else { main.getHistory(); } } private String zip; public Main(String zip) { this.zip = zip; } 158
  • Multi-module Enterprise Project public void getWeather() throws Exception { Weather weather = weatherService.retrieveForecast(zip); weatherDAO.save( weather ); System.out.print(new WeatherFormatter().formatWeather(weather)); } public void getHistory() throws Exception { Location location = locationDAO.findByZip(zip); List<Weather> weathers = weatherDAO.recentForLocation(location); System.out.print(new WeatherFormatter().formatHistory(location, weathers)); } } Die Klasse Main hält eine Referenz auf die Klassen WeatherDAO, LocationDAO und WeatherService. Folgende Aufgaben werden durch deren statische Methode main() wahrgenommen: • Die Postleitzahl aus dem ersten Argument ([0]) der Befehlszeile auslesen • Das Kommando aus dem zweiten Argument ([1]) der Befehlszeile auslesen. Ist das Kommando "weather", so werden die aktuellen Wetterdaten vom Webservice angefragt. Ist das Kommando "history" so werden die Wetterdaten aus der lokalen Datenbank abgerufen. • Laden des Spring ApplicationContext unter Bezug auf zwei XML Dateien, welche von den Modulen "Simple Persist" und "Simple Weather" geladen wurden. • Instanzieren der Klasse Main • Befruchten von WeatherService, WeatherDAO sowie LocationDAO mit Beans aus dem Spring ApplicationContext • Aufrufen der entsprechenden Methode getWeather() oder getHistory() entsprechend dem übergebenen Kommando Als Teil der Web Applikation benutzen wir Spring VelocityViewResolver um eine Velocity Vorlage darzustellen. Für die befehlszeilenorientierte Applikation ist es notwendig eine einfache Klasse zu erstellen, welche unsere Wetterdaten mit 159
  • Multi-module Enterprise Project Hilfe einer Velocity Vorlage darstellt. Example 7.21, “WeatherFormatter stellt Wetterdaten unter Einbezug einer Velocity Vorlage dar” (Beispiel 7.21: "WeatherFormatter stellt Wetterdaten unter Einbezug einer Velocity Vorlage dar") ist der Auszug der Klasse WeatherFormatter, einer Klasse mit zwei Methoden welche den Wetterbericht und die Wettergeschichte darstellen. Example 7.21. WeatherFormatter stellt Wetterdaten unter Einbezug einer Velocity Vorlage dar package org.sonatype.mavenbook.weather; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.util.List; import org.apache.log4j.Logger; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.sonatype.mavenbook.weather.model.Location; import org.sonatype.mavenbook.weather.model.Weather; public class WeatherFormatter { private static Logger log = Logger.getLogger(WeatherFormatter.class); public String formatWeather( Weather weather ) throws Exception { log.info( "Formatting Weather Data" ); Reader reader = new InputStreamReader( getClass().getClassLoader(). getResourceAsStream("weather.vm")); VelocityContext context = new VelocityContext(); context.put("weather", weather ); StringWriter writer = new StringWriter(); Velocity.evaluate(context, writer, "", reader); return writer.toString(); } public String formatHistory( Location location, List<Weather> weathers ) throws Exception { log.info( "Formatting History Data" ); Reader reader = new InputStreamReader( getClass().getClassLoader(). getResourceAsStream("history.vm")); VelocityContext context = new VelocityContext(); context.put("location", location ); context.put("weathers", weathers ); StringWriter writer = new StringWriter(); 160
  • Multi-module Enterprise Project Velocity.evaluate(context, writer, "", reader); return writer.toString(); } } Die Vorlage weather.vm gibt die zugehörige Stadt, den Staat, die Region sowie die aktuelle Temperatur aus. Die Vorlage history.vm gibt die Örtlichkeit aus, und iteriert dann über alle gespeicherten Wetterwerte der lokalen Datenbank. Diese beiden Vorlagen befinden sich unter ${basedir}/src/main/resource. Example 7.22. Die velocity Vorlage weather.vm **************************************** Current Weather Conditions for: ${weather.location.city}, ${weather.location.region}, ${weather.location.country} **************************************** * Temperature: ${weather.condition.temp} * Condition: ${weather.condition.text} * Humidity: ${weather.atmosphere.humidity} * Wind Chill: ${weather.wind.chill} * Date: ${weather.date} Example 7.23. Die Velocity Vorlage history.vm Weather History for: ${location.city}, ${location.region}, ${location.country} #foreach( $weather in $weathers ) **************************************** * Temperature: $weather.condition.temp * Condition: $weather.condition.text * Humidity: $weather.atmosphere.humidity * Wind Chill: $weather.wind.chill * Date: $weather.date #end 161
  • Multi-module Enterprise Project 7.9. Aufrufen der Kommandozeilen-Anwendung Das Projekt Modul simple-command ist konfiguriert, ein einziges JAR Archiv, welches den Bytecode des Projektes sowie aller Bytecode der Abhängigkeiten enthält, zu erstellen. Um diese Baueinheit (Assembly) zu erzeugen, rufen Sie das Goal assembly des Maven Assembly Plugin von innerhalb des Verzeichnisses des Projektes "Simple Command" auf: $ mvn assembly:assembly [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 7 Simple Command Line Tool [INFO] task-segment: [assembly:assembly] (aggregator-style) [INFO] ------------------------------------------------------------------------ [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Nothing to compile - all classes are up to date [INFO] [surefire:test] ... [INFO] [jar:jar] [INFO] Building jar: .../simple-parent/simple-command/target/simple-command.jar [INFO] [assembly:assembly] [INFO] Processing DependencySet (output=) [INFO] Expanding: .../.m2/repository/.../simple-weather-1-SNAPSHOT.jar into /tmp/archived-file-set.93251505.tmp [INFO] Expanding: .../.m2/repository/.../simple-model-1-SNAPSHOT.jar into /tmp/archived-file-set.2012480870.tm [INFO] Expanding: .../.m2/repository/../hibernate-3.2.5.ga.jar into /tmp/archived-file-set.1296516202.tm ... skipping 25 lines of dependency unpacking ... [INFO] Expanding: .../.m2/repository/.../velocity-1.5.jar into /tmp/archived-file-set.379482 [INFO] Expanding: .../.m2/repository/.../commons-lang-2.1.jar into /tmp/archived-file-set.1329200163.tm [INFO] Expanding: .../.m2/repository/.../oro-2.0.8.jar into /tmp/archived-file-set.199315532 [INFO] Building jar: .../simple-parent/simple-command/target/simple-command-jar-with-depende Der Build schreitet durch die Lebenszyklusphasen Kompilieren des Bytecodes, Aufrufen der Tests und schliesslich Erstellen des JAR-Archives. Zuletzt erstellt das Goal assembly:assembly ein einziges JAR Archiv inklusive aller Abhängigkeiten, indem es allen abhängigen Bytecode in temporäre Verzeichnisse entpackt um 162
  • Multi-module Enterprise Project diesen schliesslich in einem einzigen JAR Archiv zusammenzufassen und im Zielverzeichnis /target unter dem Namen simple-command-jar-with-dependencies.jar abzulegen. Dieses "über" JAR Archiv kommt auf stolze 15 MB. Vor Sie nun das kommandozeilenorientierte Programm aufrufen, müssen Sie noch das Goal hbm2ddl des Maven Hibernate3 Plugin aufrufen, um die HSQL Datenbank zu erstellen. Sie tun dies, indem Sie den folgenden Befehl innerhalb des Verzeichnisses von "Simple Command" absetzen: $ mvn hibernate3:hbm2ddl [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'hibernate3'. [INFO] org.codehaus.mojo: checking for updates from central [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 7 Simple Command Line Tool [INFO] task-segment: [hibernate3:hbm2ddl] [INFO] ------------------------------------------------------------------------ [INFO] Preparing hibernate3:hbm2ddl ... 10:24:56,151 INFO org.hibernate.tool.hbm2ddl.SchemaExport - export complete [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ Nach dem Aufruf sollten Sie innerhalb des "Simple Command" Verzeichnisses ein Verzeichnis /data vorfinden. Dieses Verzeichnis beinhaltet die HSQL Datenbank. Um nun das befehlszeilenorientierte Programm aufzurufen, setzen Sie den folgenden Befehl von innerhalb des "Simple Command" Verzeichnisses ab: $ java -cp target/simple-command-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 60202 2321 INFO YahooRetriever - Retrieving Weather Data 2489 INFO YahooParser - Creating XML Reader 2581 INFO YahooParser - Parsing XML Response 2875 INFO WeatherFormatter - Formatting Weather Data **************************************** Current Weather Conditions for: Evanston, IL, US **************************************** * Temperature: 75 * Condition: Partly Cloudy * Humidity: 64 * Wind Chill: 75 163
  • Multi-module Enterprise Project * Date: Wed Aug 06 09:35:30 CDT 2008 Um eine Abfrage der vergangenen Werte abzusetzen, geben Sie den folgenden Befehl ein: $ java -cp target/simple-command-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 60202 history 2470 INFO WeatherFormatter - Formatting History Data Weather History for: Evanston, IL, US **************************************** * Temperature: 39 * Condition: Heavy Rain * Humidity: 93 * Wind Chill: 36 * Date: 2007-12-02 13:45:27.187 **************************************** * Temperature: 75 * Condition: Partly Cloudy * Humidity: 64 * Wind Chill: 75 * Date: 2008-08-06 09:24:11.725 **************************************** * Temperature: 75 * Condition: Partly Cloudy * Humidity: 64 * Wind Chill: 75 * Date: 2008-08-06 09:27:28.475 7.10. Fazit Nun, wir haben viel Zeit damit verbracht, uns Themen anzunehmen, welche nicht im Zusammenhang mit Maven stehen. Wir haben dies getan, um ein vollständiges und aussagekräftiges Beispielprojekt mit Bezug zur realen Welt zu implementieren. Wir haben uns keine schnellen Abkürzungen geleistet, um Ihnen ein hochglanzpoliertes fix fertiges Produkt zu liefern. Auch war unser Ziel nicht, Sie mit einer Ruby on Rails artigen Zauberei zur Annahme zu führen, dass Sie nun in wenigen Minuten eine fix-fertige Java-Enterprise-Anwendung hinstellen könnten. - Hiervon gibt es bereits zu viele Beispiel im Markt, zu viele Menschen welche Ihnen das einfachste, umfassenste Framework zu verkaufen versuchen, welches Ihnen erlaubt mit Null Investition von Zeit und Aufmerksamkeit 164
  • Multi-module Enterprise Project weiterzukommen. Unser Ziel diese Kapitels war es, Ihnen ein umfassendes Bild zu vermitteln: das gesamte Ökosystem eines Multi-Modul Projekt Builds. Was wir bieten ist Maven im Rahmen einer Anwendung einer Art ähnlich deren, welche Sie in freier Wildbahn antreffen, darzustellen. Es ist nicht unsere Art Ihnen eine Art Fast-Food anzubieten: 10 Minuten Bildschirm Show, etwas Schlamm in die Richtung Apache Ant geworfen und Sie davon überzeugt haben Maven einzusetzen! Sollten Sie sich nach dem Lesen dieses Kapitels fragen, was es mit Maven zu tun hat, so waren wir erfolgreich. Wir haben eine Reihe von komplex vernetzten Projekten, unter Einsatz von populären Frameworks erzeugt, und wir haben diese unter Verwendung von deklarativen Methoden verbunden. Die Tatsache, dass mehr als 60% dieses Kapitel der Erklärung von Spring und Hibernate gewidment wurde, sollte Ihnen aufzeigen, dass Maven grösstenteils zurücksteht. Es funktionierte einfach. Es erlaubt uns, uns auf die Anwendung selbst zu konzentrieren, und nicht so sehr auf der Build-Prozess. Sie werden bemerken, dass haben wir ein Projekt erstellt haben, welches Daten in einer Datenbank speichert, ohne ein einziges SQL-Statement abzusetzen. Daraufhin haben Sie eine Web-Anwendung erstellt, ohne sich mit den Erstellung eines Build Skriptes inklusive 20 plus Abhängigkeiten herumzuschlagen um am Ende ein WAR-Archiv zu erstellen. Anstelle Zeit damit zu verbringen Maven zu diskutieren, verbrachten wir die Zeit damit Spring und Hiberante in den Grundzügen einzuführen. Sie können das eingeführte Skelett Projekt dieses Kapitels als Grundlage für Ihre eigene Projekte verwenden, und die Chancen stehen gut, dass, sollten Sie das tun, Sie sich in der Lage befinden über die Zeit hinweg mehr und mehr Module nach Bedarf anzulegen. So umfasst das ursprüngliche Projekt, auf welchem dieses Beispiel beruht zwei verschiedene Modell-Projekte, mit zwei Persistenzprojekten welche den Anschuss an völlig unterschiedliche Datenbanksysteme übernehmen, mehrere Web-Applikationen, sowie eine Java-Mobil Anwendung. Insgesamt stützt sich das reale Welt-System auf mindestens 15 miteinander verknüpften Module. Was wir hiermit sagen wollen ist, Sie haben soeben das komplexeste Beispiel welches in diesem Buch dargestellt wird gesehen, aber Sie sollten auch wissen, dass dieses Beispiel nur Kratzer an der Oberfläche dessen was mit Maven möglich ist, darstellt. 165
  • Multi-module Enterprise Project 7.10.1. Programmierung gegen Interface-Projekte Dieses Kapitel stieg in die Tiefe der Multi-Modul-Projekte hinab und war komplexer als die einfachen Beispiele in Chapter 6, Ein multi-modulares Projekt (Kapitel 6: Ein Multi-Projekt-Modul), und dennoch war es eine starke Vereinfachung eines Reale- Welt-Projektes. In einem größeren Projekt finden Sie vielleicht sogar selbst ein System ähnlich Figure 7.5, “Programmierung gegen Interface-Projekte” (Abbildung 7.5) wieder. Figure 7.5. Programmierung gegen Interface-Projekte Wenn wir den Begriff Interface Projekt benutzen, so beziehen wir uns auf ein Maven Projekt, welches einzig aus Interfaces und Konstanten besteht. In Figure 7.5, “Programmierung gegen Interface-Projekte” (Abbildung 7.5 Programmierung gegen ein Interface Projekt), stellen die Einheiten persist-api 166
  • Multi-module Enterprise Project sowie parse-api Interfaceprojekte dar. Sollten big-command sowie big-webapp gegen diese Interface Projekte programmiert werden, ist es ein leichtes zu einem späteren Zeitpunkt die Implementierung der Persistenz gegen eine andere auszutauschen. Auf dieser Abbildung werden zwei Implementierungen dargestellt: eine welche die Persistenz aufbauend auf einen XML-Store implementiert, sowie einer weiteren auf der Basis einer relationalen Datenbank. Bei der Anwendung der in diesem Kapitel vorgestellten Konzepte ist es einfach zu sehen, wie Sie, getrieben durch einen an das Programm übergebenen Wert, eine andere Spring ApplicationContext XML Datei übergeben, um damit die unterliegende Persistenzimplementierung auszutauschen. Wie bereits im OO-Design der Applikation, ist oftmals sinnvoll das Interface einer API von der Implementierung einer API in seperate Maven Projekte aufzutrennen. 167
  • Chapter 8. Optimirung und Überarbeitung der POMs 8.1. Einführung In Chapter 7, Multi-module Enterprise Project (Kapitel 7: "Multi-Modul Enterprise Projekt"), zeigten wir auf, wie die verschiedenen Teile eines Maven Builds zusammenkommen, um eine voll funktionsfähige multi modularre Anwendung zu erstellen. Während das Beispiel des Kapitels eine wirklichkeitsnahe Anwendung wiederspiegelt - eine Anwendung mit Datenbankintegration, einem Web-Service, und sogar zwei Benutzer Schnittstellen: eine in Form einer Web-Anwendung, und eine als Dienstprogramm; so bleibt das Beispiel-Projekt des Kapitels letztendlich ein konstruiertes. Um die Komplexität eines realen Projektes abzubilden bräuchte es ein Buch von weit grösserem Umfang als das, das Sie gerade lesen. Anwendungen des täglichen Lebens entwickeln sich im Laufe der Jahre und werden oftmals von großen, unterschiedlichen Gruppen von Entwicklern gewartet, welche jeweils ganz verschiedene Schwerpunkte setzen. In einem solchen Projekt, müssen Sie häufig Entscheidungen und Designs gegeneinander abwägen, welche jemand anderes getätigt hat. In diesem Kapitel treten wir nun einen Schritt zurück von den Beispielen, aus Part I, “Maven by Example” (Teil I: Maven by Example), und stellen uns die Frage, ob es irgendwelche Optimierungen gibt, welche jetzt, wo wir mehr über Maven wissen, Sinn machen. Maven ist ein vielfältiges Werkzeug, welches so einfach oder auch komplex sein kann, wie Sie es gerade benötigen. Aus diesem Grund gibt es oft viele verschiedene Möglichkeiten, um die gleiche Aufgabe zu bewerkstelligen, und oft gibt es keinen einzigen "richtigen" Weg bei der Konfiguration Ihres Maven-Projektes. Bitte verstehen Sie den letzten Satz nicht falsch. Versuchen Sie nun nicht mit Maven etwas zu lösen, für das Maven nicht konzipiert wurde! Auch wenn Maven eine Vielfalt verschiedener Ansätze unterstützt, gibt es natürlich den "Maven Way", und Sie werden mit Maven produktiver sein wenn Sie diesen einschlagen. Das Ziel diesen Kapitels ist es, einige der Optimierungen weiterzugeben, welche Sie auch 168
  • Optimirung und Überarbeitung der POMs auf ein bestehendes Maven-Projekt anwenden können. Warum haben wir nicht gleich ein optimiertes POM eingeführt, warum der Umweg? POMs pädagogisch sinnvoll zu gestalten, und POMs in der Praxis effizient zu gestalten, sind zwei sehr unterschiedliche Anforderungen. Zwar ist es sicherlich sehr viel einfacher, eine bestimmte Einstellung in Ihrem Profil in der ~/.m2/settings.xml zu definieren, als deren Einbindung in der pom.xml Datei, aber wenn man ein Buch schreibt damit dieses gelesen wird, geht es auch meist darum das Tempo richtig zu wählen und dafür zu sorgen, dass nicht Konzepte eingeführt werden, bevor Sie dazu bereit sind. In Part I, “Maven by Example” (Teil I: Maven by Example), haben wir uns grosse Mühe gegeben, den Leser nicht mit zu viel Information zu überlasten, und haben darum einige grundlegende Konzepte wie das des Elements dependencyManagement bewusst übersprungen. Dieses Element wird nun in diesem Kapitel eingeführt. Im Part I, “Maven by Example” (Teil I: "Maven by Example") dieses Buches gibt es Beispiele, in welchen die Autoren eine Abkürzung gewählt haben, oder über ein wichtiges Detail grosszügig hinweggeschritten sind, einfach um Sie zügig zu den Kernpunkten des Kapitels weiterzuleiten. Sie konnten lernen wie man ein Maven Projekt aufsetzt, kompiliert und installiert, ohne dass Sie durch hunderte Seiten Detailspezifikation waten mussten, welche jeden letzten Schalter erklärte, der Ihnen zur Verfügung gestanden hätte. Wir haben diesen Weg bewusst gewählt, da es uns wichtig schien, den neuen Maven Benutzer schnell zu einem Ergebnis zu führen, statt Ihn auf einem langen mäandernden Weg zu begleiten, welcher uns durch eine schier endlos erscheinende Weite führt. Sobald Sie damit begonnen haben, selbst regelmässig Maven einzusetzen, sollten Sie wissen, wie man eigene Projekte und POMs analysiert. In diesem Kapitel treten wir nun einen Schritt zurück, und werfen einen Blick auf das was wir zum Abschlus von Chapter 7, Multi-module Enterprise Project (Kapitel 7: "Multi-Modul Enterprise Projekt") tatsächlich bekommen haben. 8.2. POM Bereinigung Die Optimierung eines multi-modularen POMs wird am besten in mehreren Phasen durchgeführt, denn es gilt sich auf unterschiedliche Bereiche zu konzentrieren. Im 169
  • Optimirung und Überarbeitung der POMs Normalfall suchen wir nach Wiederholungen innerhalb eines POM und über dessen Geschwister-POMs hinweg. Wenn Sie am Anfang stehen, oder wenn sich ein Projekt noch immer sehr schnell entwickelt, es ist akzeptabel, einige Abhängigkeiten und Plugin-Konfigurationen hier und da zu duplizieren, aber so wie das Projekt reift und die Anzahl der Module sich erhöht, sollten Sie sich Zeit nehmen um die gemeinsamen Abhängigkeiten sowie Plugin Konfigurationen zu refaktorieren/überarbeiten. Die Erstellung effizienter POMs trägt Sie ein grosses Stück des Weges, die Komplexität Ihres Projekts zu verwalten während dieses noch immer wächst. Wo immer Verdopplungen von Informationen auftauchen können Sie davon ausgehen, dass es in der Regel einen besseren Weg gibt. 8.3. Optimirung der Abhängigkeiten Wenn Sie einen Blick in die verschiedenen POMs in Chapter 7, Multi-module Enterprise Project (Kapitel 7: "Multi-Modul Enterprise Projekt") werfen, beachten Sie mehrere bestehende Muster der Replikation/Wiederholung. Das erste Muster, das wir sehen können ist, dass einige Abhängigkeiten wie Spring und Hibernate-Annotations in mehreren Modulen definiert werden. Die Hibernate Abhängigkeit hat in jeder Definition die Ausgrenzung von javax.transaction repliziert. Das zweite Muster von Wiederholung ist, dass manchmal mehrere Abhängigkeiten in einem Zusammenhang stehen und sich die gleiche Version teilen. Dies ist häufig der Fall, wenn ein Projekt Release aus mehreren eng gekoppelten Komponenten besteht. Sehen Sie sich zum Beispiel die Abhängigkeiten der Hibernate-Annotations sowie der Hibernate-Commons-Annotations an: beide tragen als Version 3.3.0.ga, und wir können davon ausgehen, dass die Version der beiden Abhängigkeiten sich auch in Zukunft im Gleichschritt bewegen wird. Sowohl die Hibernate-Annotations wie auch Hibernate-Commons-Annotations sind Komponenten des gleichen Projekts, freigegeben von JBoss. Wann immer es einen neuen Projekt Release gibt, werden diese beiden Abhängigkeiten sich ändern. Das letzte Muster der Replikation ist die Wiederholung der Geschwister-Modul Abhängigkeiten und Geschwister-Modul-Versionen. Maven bietet einfache Mechanismen, mit denen Sie alle Fälle von Duplikation in das Top Level POM (Parent POM) ziehen 170
  • Optimirung und Überarbeitung der POMs können. Ein kleine Diskrepanz der Version einer Abhängigkeit eines Projektes bezüglich eines Bytecode-Manipulations Bibliothek mit dem Namen ASM, drei Ebenen tiefer in der Projekt-Hierarchie, führt leicht zur vollständigen Blockade einer Web-Applikation, welche von einer ganz anderen Gruppe von Entwicklern bearbeitet wird, und von diesem speziellen Modul abhängig ist. Unit-Tests bestehen, da diese gegen eine Version der Abhängigkeit testen, und scheitern fürchterlich in der Produktion, in welcher - in einem WAR-Archive gebündelt - eine ganz andere Version dieser Abhängigkeit vorliegt. Sollten Sie Dutzende von Projekten am Laufen haben, welche alle mit etwas wie Hibernate Annotations arbeiten und deren Deklaration sich ständig wiederholt und dupliziert, mit allen Abhängigkeiten und Ausschlüssen, so wird die durchschnittliche Zeit zwischen grossen Build Fehlern eher klein sein. So wie Ihre Maven Projekte immer weiter wachsen und komplexer werden, werden Ihre Listen von Abhängigkeiten wachsen; und Sie werden nicht umhinkommen, Versionen und Deklarationen Ihrer Abhängigkeiten in Ihrem Top Level POM zu konsolidieren. Die Wiederholung von Geschwister-Modul-Versionen bereiten das Feld für einen besonders fieses Problem, welches zwar nicht direkt durch Maven verschuldet wird, aber dessen man sich erst bewusst wird, nachdem Sie ein paar Mal von diesem Fehler gebissen wurden. So Sie das Maven Release Plugin benutzen, ist die Verwaltung der Geschwister Versionen und deren Abhängigkeiten kein Thema, Versionen werden jeweils automatisch aktualisiert. Wenn Simple Web Version 1.3-Snapshot von Simple Persist Version 1.3-Snapshot abhängig ist, und Sie geben beide Module in der Version 1.3 frei, so ist das Maven Release-Plugin schlau genug, um die Versionen innerhalb Ihrer gesamten multi-modularen Projekt POMs automatisch anzupassen. Wenn Sie die Freigabe mittels dem Release-Plugin vornehmen werden automatisch alle Versionen in Ihrem Build auf 1.4-Snapshot hochgezählt und das Release-Plugin wird darüber hinaus allen Code in das Repository einchecken. Ein großes multi-modulares Projekt freizugeben könnte also nicht einfacher sein, bis ... Probleme treten auf, wenn Entwickler Änderungen am POM einfliessen lassen, und sich in eine laufende Freigabe einmischen. Oftmals fügt ein Entwickler im Konfliktfall zwei Abhängigkeiten zusammen, oder agiert falsch im Fall eines 171
  • Optimirung und Überarbeitung der POMs Modul Konflikts, so dass er unwissentlich auf eine frühere Version zurückrollt. Da die aufeinander folgenden Versionen der Abhängigkeit gewöhnlich kompatibel sind, zeigt sich dies nicht, wenn der Entwickler seinen Build erstellt, und es zeigt sich ebenso wenig in einem Continuous Integration Build-System. Stellen Sie sich einen sehr komplexen Build vor, der Trunk besteht aus Komponenten des 1.4-Snapshot, und jetzt müssen Sie sich vorstellen, dass Entwickler A eine Komponente tief in der Hierarchie des Projekts aktualisiert, welche von der Version 1.3-Snapshot der Komponente B abhängt. Obwohl die meisten Entwickler bereits auf Version 1.4-Snapshot bauen, wird es beim Build keinen Fehler geben, solange wie 1.3-SNAPSHOT und 1.4-SNAPSHOT der Komponente B kompatibel sind. Maven wird auch weiterhin den Build auf der Basis der 1.3-SNAPSHOT-Version von Komponente B ab dem lokalen Repository des Entwicklers durchführen. Alles scheint ganz reibungslos zu funktionieren, der Build des Projektes läuft durch, der Continuous Integration Build funktioniert ebenfalls, ein paar esoterische Fehler im Zusammenhang mit der Komponente B, aber diese werden auf das Werk hinterlistiger Kobolde abgetan. Mittlerweile pumpt eine Pumpe im Reaktor Raum und baut stetig Druck auf, bis schliesslich etwas bricht ... Jemand, nennen wir ihn Herr Unachtsam, hatte beim Zusammenführen einen Konflikt in Komponente A, um diesen zu lösen, wurde irrtümlicherweise die Abhängigkeit von Komponente A auf Komponente B Version 1.3-SNAPSHOT gekoppelt, während der Rest des Projekts stets weiter marschiert. Ein Gruppe Entwickler haben die ganze Zeit versucht, den Fehler in Komponente B zu beheben, und diese stehen vor einem Rätsel, warum Ihnen dies offenbar in der Produktion nicht gelingt. Schließlich befasst sich jemand mit Komponente A und realisiert, dass die Abhängigkeit auf die falsche Version zeigt. Es bleibt zu hoffen, dass der Fehler nicht groß genug war, um Geld oder Leben zu kosten, aber Herr Unachtsam ist beschämt, und seine Kollegen vertrauen ihm ein bisschen weniger als vor der ganzen Abhängigkeits-Sache. (Ich hoffe zwar, dass Herr Unachtsam realisiert, dass es sich hier um einen Benutzerfehler handelt, und das Maven nicht Schuld war. Leider ist es mehr als wahrscheinlich Herr Unachtsam startet eine furchtbare Blog-Tirade und beschwert sich endlos über Maven, allein schon um sich besser zu fühlen.) 172
  • Optimirung und Überarbeitung der POMs Glücklicherweise ist die Weiderholung von Abhängigkeiten und die Unstimmigkeit von Geschwisterversionen einfach vermeidbar. Es braucht nur einige kleine Veränderungen. Der erste Schritt ist, alle Abhängigkeiten welche in mehr als einem Projekt vorkommen zu finden und diese in das darüber stehende POM im Abschnitt dependencyManagement aufzunehmen. Die Geschwister Abhängigkeiten blenden wir für den Moment aus. Das Simple Parent POM beinhaltet nun die folgenden Einträge: <project> ... <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement> ... </project> Sobald die Abhängigkeiten im übergeordneten POM eingetragen sind, müssen wir 173
  • Optimirung und Überarbeitung der POMs diese Einträge aus den 'Kind'-POM entfernen. Andernfalls werden die bestehenden Einträge die des übergeordneten Abschnitts dependencyManagement übersteuern. Der Einfachheit halber werden wir hier nur das Simple Model Modul POM zeigen: <project> ... <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> </dependency> </dependencies> ... </project> Das nächste, das wir angehen sollten, ist die Replikation der Hibernate-Annotations sowie der Hibernate-Commons-Annotations, da diese Versionen immer übereinstimmen sollten. Wir tun dies, indem wir ein Property mit dem Namen hibernate-annotations-version erstellen. Der daraus resultierende Abschnitt des übergeordneten POM sieht wie folgt aus: <project> ... <properties> <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version> </properties> <dependencyManagement> ... <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> ... </dependencyManagement> ... </project 174
  • Optimirung und Überarbeitung der POMs Zum Schluss nehmen wir uns noch der Geschwister-Abhängigkeit an. Einen Weg welchen wir beschreiten könnten wäre, wie zuvor auch, diese Abhängigkeiten nach oben in das übergeordnete POM in den Abschnitt dependencyManagement zu verschieben, genau wie die Abhängigkeiten zuvor, mit einer definierten Versionen der Geschwister-Projekte im Top-Level Projekt POM. Dies ist sicherlich ein gültiges Konzept. Allerdings können wir dieses Problem mittles dem Einsatz zweier eingebauter Variablen lösen: ${project.groupId} und ${project.version}. Da es sich um Geschwister Abhängigkeiten handelt, macht es nicht viel Sinn, einen numerischen Wert im übergeordneten POM zu definieren, daher stützen wir uns auf die eingebaute Eigenschaft ${project.version}. Da alle Geschwister derselben Gruppe angehören, können wir weitere Zukunftssicherheit einbauen, indem wir auf das bestehende POM mittels der Eigenschaft ${project.groupId} verweisen. Der Abschnitt der Abhängigkeiten des Simple Command Moduls sieht nun wie folgt aus: <project> ... <dependencies> ... <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-weather</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-persist</artifactId> <version>${project.version}</version> </dependency> ... </dependencies> ... </project> Zusammenfassend hier die beiden Optimierungen welche wir angewandt haben, um Wiederholungen in der Deklaration der Abhängigkeiten zu reduzieren: Ziehen Sie gemeinsame Abhängigkeiten in das übergeordnete POM in den Abschnitt dependencyManagement 175
  • Optimirung und Überarbeitung der POMs Wenn mehr als ein Projekt von einer Abhängigkeit Gebrauch macht, können Sie die Abhängigkeit im Element dependencyManagement des übergeordneten POM erfassen. Im übergeordneten POM werden Version und Ausschlüsse gesetzt, in den untergeordneten POM müssen sie lediglich die Koordinaten groupId und artifactId angeben. Untergeordnete Projekte können die Version sowie die Ausschlüsse weglassen, sollte die Abhängigkeit im Element dependencyManagement deklariert sein. Benutzen Sie auf die eingebauten Variablen 'version' und 'groupId' Um sich auf ein Geschwisterprojekt zu beziehen, verwenden Sie ${project.version} und ${project.groupId}. Geschwister-Projekte umfassen fast immer die gleiche groupId und Release-Version. Mittels ${project.version} entschärfen Sie das oben aufgeführte Problem der Geschwister-Version Missverhältnisse. 8.4. Optimirung der Plugins Betrachten wir die verschiedenen Plugin-Konfigurationen, können wir sehen, dass die Abhängigkeiten zu HSQL DB an mehreren Orten wiederholt wird. Leider ist das Element dependencyManagement nicht auf Plugin-Abhängigkeiten anwendbar, dennoch können wir ein Property benutzen, um die verschiedenen Versionen zu konsolidieren. Tendeziell definieren komplexe multi-modulare Maven-Projekte alle Versionen im Top-Level-POM. Dieses Top-Level-POM wird dann zu einem Dreh-und Angelpunkt bezüglich allen Veränderungen, welche Auswirkungen auf das gesamte Projekt haben. Stellen Sie sich die Version als Java String in einer Java Klasse vor; bei einer ständigen Wiederholung werden Sie wahrscheinlich eine Variable einsetzen wollen, damit, wenn es den String zu ändern gilt, Sie dies nur an einem Ort durchführen müssen. Das 'Hochziehen' der Version von HSQLDB in ein Property des Top-Level-POM liefert uns fogenden Eintrag: <project> ... <properties> <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version> <hsqldb.version>1.8.0.7</hsqldb.version> </properties> ... 176
  • Optimirung und Überarbeitung der POMs </project> Das nächste, was wir bemerken ist, dass die Hibernate3-Maven-Plugin-Konfiguration doppelt, sowohl im Simple Web- sowie im Simple Command Modul vorkommt. Entsprechend der Abhängigkeiten können wir die Plugin-Konfiguration in der Top-Level-POM Datei genau wie Abhängigkeiten im Element dependencyManagement definieren. Hierfür benutzen wir das Element pluginManagement in der Top Level POM Datei. <project> ... <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>hibernate3-maven-plugin</artifactId> <version>2.1</version> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> </component> </components> </configuration> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> </dependency> </dependencies> </plugin> </plugins> </pluginManagement> </build> ... </project> 177
  • Optimirung und Überarbeitung der POMs 8.5. Optimierung unter Zuhilfenahmen des Maven Dependency Plugin Grössere Projekte, haben eine Tendenz, dass mit wachsender Anzahl Abhängigkeiten auch zusätzliche Abhängigkeiten in ein POM einfliessen. Über die Zeit hinweg werden sich die Abhängigkeiten verändern, und so werden Sie über kurz oder lang auch verwaiste Abhängigkeiten beherbergen, genauso wie Sie Bibliotheken anziehen werden, welche nicht deklariert sind. Da Maven 2.x transitive Abhängigkeiten im Geltungsbereich des Compilers einschliesst, kann es passieren, dass Ihr Projekt zwar fehlerfrei kompiliert, jedoch in der Produktion nicht lauffähig ist. Nehmen Sie zum Beispiel ein Projekt welches auf eine weit verbreitete Bibliothek wie Jakarta Commons BeanUtils aufsetzt. Statt diese explizit zu referenzieren, verlassen Sie sich auf die transitive Abhängigkeit, da Sie ebenfalls Hibernate einsetzen, welches diese Library transitiv deklariert. Ihr Projekt kompiliert und läuft wie erwartet, bis Sie eines Tages Hibernate updaten und hierbei auf eine Version wechseln, welche nicht mehr auf BeanUtils aufbaut. Plötzlich bekommen Sie viele Fehler, deren Grund nicht gerade offensichtlich ist. Darüber hinaus kann Maven auftretende Konflikte nicht lösen, da die Abhängigkeit nicht explizit deklariert wurde. Eine bewährte Faustregel ist es, alle ausdrücklich im Code deklarierten Abhängigkeiten explizit zu deklarieren. Wenn Sie Jakarta Commons Beanutils importieren, so sollten Sie diese auch explizit in der pom.xml-Datei festhalten. Glücklicherweise kann Ihnen hier Maven helfen, denn durch Bytecode Analyse ist Maven in der Lage, solche direkten Abhängigkeiten aufzudecken. Lassen Sie uns das zuvor optimierte POM auf Fehler durchleuchten: $ mvn dependency:analyze [INFO] Scanning for projects... [INFO] Reactor build order: [INFO] Chapter 8 Simple Parent Project [INFO] Chapter 8 Simple Object Model [INFO] Chapter 8 Simple Weather API [INFO] Chapter 8 Simple Persistence API [INFO] Chapter 8 Simple Command Line Tool [INFO] Chapter 8 Simple Web Application [INFO] Chapter 8 Parent Project [INFO] Searching repository for plugin with prefix: 'dependency'. 178
  • Optimirung und Überarbeitung der POMs ... [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Object Model [INFO] task-segment: [dependency:analyze] [INFO] ------------------------------------------------------------------------ [INFO] Preparing dependency:analyze [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Nothing to compile - all classes are up to date [INFO] [dependency:analyze] [WARNING] Used undeclared dependencies found: [WARNING] javax.persistence:persistence-api:jar:1.0:compile [WARNING] Unused declared dependencies found: [WARNING] org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile [WARNING] org.hibernate:hibernate:jar:3.2.5.ga:compile [WARNING] junit:junit:jar:3.8.1:test ... [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Web Application [INFO] task-segment: [dependency:analyze] [INFO] ------------------------------------------------------------------------ [INFO] Preparing dependency:analyze [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] No sources to compile [INFO] [dependency:analyze] [WARNING] Used undeclared dependencies found: [WARNING] org.sonatype.mavenbook.ch08:simple-model:jar:1.0:compile [WARNING] Unused declared dependencies found: [WARNING] org.apache.velocity:velocity:jar:1.5:compile [WARNING] javax.servlet:jstl:jar:1.1.2:compile [WARNING] taglibs:standard:jar:1.1.2:compile [WARNING] junit:junit:jar:3.8.1:test Die vorangegangene, gekürzte Darstellung zeigt die Ausgabe des Goals dependency:analyse. Dieses Goal analysisert, ob es irgendwelche indirekten Abhängigkeiten gibt, oder Abhängigkeiten, welche zwar referenziert, aber nicht 179
  • Optimirung und Überarbeitung der POMs deklariert werden. Im Projekt Simple Model zeigt das Plugin beispielhaft auf, wie die Abhängigkeit javax.persistence:persistence-api eine "nicht deklarierte" Abhängigkeit darstellt. Um dies genauer zu untersuchen, welchseln Sie in das Verzeichnis des Simple Model Projekt und rufen dort das Goal dependency:tree auf. Dies wird Ihnen eine Auflistung aller direkten und transitiven Abhängigkeiten ausgeben. $ mvn dependency:tree [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'dependency'. [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Object Model [INFO] task-segment: [dependency:tree] [INFO] ------------------------------------------------------------------------ [INFO] [dependency:tree] [INFO] org.sonatype.mavenbook.ch08:simple-model:jar:1.0 [INFO] +- org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile [INFO] | - javax.persistence:persistence-api:jar:1.0:compile [INFO] +- org.hibernate:hibernate:jar:3.2.5.ga:compile [INFO] | +- net.sf.ehcache:ehcache:jar:1.2.3:compile [INFO] | +- commons-logging:commons-logging:jar:1.0.4:compile [INFO] | +- asm:asm-attrs:jar:1.5.3:compile [INFO] | +- dom4j:dom4j:jar:1.6.1:compile [INFO] | +- antlr:antlr:jar:2.7.6:compile [INFO] | +- cglib:cglib:jar:2.1_3:compile [INFO] | +- asm:asm:jar:1.5.3:compile [INFO] | - commons-collections:commons-collections:jar:2.1.1:compile [INFO] - junit:junit:jar:3.8.1:test [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ Aus dieser Ausgabe, können wir ablesen, dass das bemängelte persistence-api durch Hibernate eingeflossen ist. Ein flüchtiger Scan des Quellcodes aus diesem Modul wird uns zeigen dass hier tatsächlich viele direkte Referenzen auf die Abhängigkleit javax.persistence vorliegen. Die einfache Lösung besteht darin einen Verweis auf die Abhängigkeit in das POM aufzunehmen. In diesem Beispiel setzen wir die Abhängigkeit zur Version in den Abschnitt dependencyManagement des POM von Simple Parent, da die Abhängikeit im Zusammenhang mit Hibernate steht, und Hibernate dort deklariert wird. Irgendwann wird der Zeipunkt gekommen sein, und Sie werden die Version von Hibernate upgraden. Die Nähe der Auflistung des persistence-api wird es Ihnen dann klarer aufzeigen, dass hier eine Abhängigkeit besteht. 180
  • Optimirung und Überarbeitung der POMs Beim betrachten der Ausgabe von dependency:analyse des Simple Web Moduls werden Sie feststellen, dass Sie eine Referenz zum Simple Model erstellen sollten. Ihr Quellcode in Simple Web referenziert direkt Objekte aus Simple Model welches jedoch nur transitiv über Simple Persist eingebunden ist. Da es sich hier um eine Abhängigkeit unter Geschwistern handelt welche beide die gleiche groupId sowie version haben, können Sie die Abhängigkeit im POM von Simple Web in der Form ${project.goupId} und ${project.version} vornehmen. Wie konnte Maven diese Abhängigkeiten überhaupt aufdecken? Woher weiss dependency:analyse überhaupt welche Klassen und andere Abhängigkeiten von Ihrem Bytecode referenziert werden? Das Dependency Plugin benutzt den ObjectWeb ASM Toolkit um den Bytecode zu analysieren. Mittels ASM durchläuft das Dependency Plugin den gesamten Code und erstellt einen Baum aller Klassen welche im aktuellen Projekt referenziert werden. Anschliessend durchläuft das Werkzeug alle deklarierten und transitiven Abhängigkeiten und steicht alle Klassen welche den direkten Abhängigkeiten zugeordnet werden können. Klassen welche nicht Teil der direkten Abhängigkeitsliste sind, müssen somit der transitiven Abhängigkeitsliste entspringen. Diese können als eingebundene, jedoch nicht deklarierte Abhängigkeit (used, undeclared dependencies) ausgegeben werden. Im Gegensatz dazu ist die Liste der deklarierten, aber nicht eingesetzten Abhängigkeiten ungleich schwieriger zu validieren, und weit weniger nützlich als die der gebrauchten, nicht deklarierternAbhängigkeiten. Zum einen werden manche der Abhängigkeiten nur während der Laufzeit oder in Tests angezogen, diese sind im Bytecode nicht ersichtlich. Beim Betrachten der Liste wird dies offensichtlich: so ist zum Beispiel JUnit Teil der Liste, was zu erwarten ist, da es nur zum Test eingesetzt wird. Sie werden auch feststellen, dass Velocity sowie das Servlet API auf der Liste der Abhängigkeiten des Simpel Web Moduls erscheint. Das ist nicht weiter verwunderlich, da während das Projekt keine direkten Verweise auf die Klassen dieser Artefakte hat, diese nach wie vor im laufenden Betrieb unverzichtbar sind. Seien Sie vorsichtig mit dem Entfernen unbenutzer, deklarierter Abhängigkeiten (unused, declared dependencies), denn ausser Sie haben eine sehr gute Testabdeckung fügen Sie hierbei leicht Laufzeitfehler ein. Eine noch bösere 181
  • Optimirung und Überarbeitung der POMs Überraschung rührt von Bytecode Optimierern her: So ist es zum Beispiel legal den Wert einer Konstanten zu ersetzen um damit die bestehende Referenz wegzuoptimieren. Die Entfernung dieser Abhängigkeit führt jedoch dazu, dass die Kompilierung scheitert, obschon das Tool zeigt, dass diese ungenutzt ist. Es ist abzusehen, dass zukünftige Versionen des Dependency Maven-Plugin wird verbesserte Techniken zur Erkennung solcherlei Problemstellungen bereitstellen werden. Es ist zu empfehlen, dass Sie ab und zu dependency:analyse ausführen, um solcherlei Fehler des POM aufzudecken. Das Goal kann derart konfigueriert werden, dass beim Auftreten bestimmter Konditionen der Build abbricht, alternativ wird ein Report bereitgestellt. 8.6. Abschliessende POMs Abschliessend sollen hier nun die endgültigen, optimierten pom.xml-Dateien als Referenz für dieses Kapitel widergegeben werden. Hier ist das Top-Level POM für Simple Parent: Example 8.1. Abschliessendes POM des Moduls Simple-Parent <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <name>Chapter 8 Simple Parent Project</name> <modules> <module>simple-command</module> <module>simple-model</module> <module>simple-weather</module> <module>simple-persist</module> <module>simple-webapp</module> </modules> <build> 182
  • Optimirung und Überarbeitung der POMs <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>hibernate3-maven-plugin</artifactId> <version>2.1</version> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> </component> </components> </configuration> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> </dependency> </dependencies> </plugin> </plugins> </pluginManagement> </build> <properties> <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version> <hsqldb.version>1.8.0.7</hsqldb.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> 183
  • Optimirung und Überarbeitung der POMs <version>1.0</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project> The POM shown in Example 8.2, “Abschliessendes POM des Mouls Simple-Command” captures the POM for simple-command, the command-line version of the tool. Die folgenden POM erfasst das Simple Command Tool, die Kommando-Zeilen Version des Tools (Dienstprogramm). Example 8.2. Abschliessendes POM des Mouls Simple-Command <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 184
  • Optimirung und Überarbeitung der POMs http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-command</artifactId> <packaging>jar</packaging> <name>Chapter 8 Simple Command Line Tool</name> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>org.sonatype.mavenbook.weather.Main</mainClass> <addClasspath>true</addClasspath> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-weather</artifactId> <version>${project.version}</version> </dependency> <dependency> 185
  • Optimirung und Überarbeitung der POMs <groupId>${project.groupId}</groupId> <artifactId>simple-persist</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> </dependency> </dependencies> </project> Darauf folgend das POM des Simple Model Projekt. Simple Model beinhaltet alle Objekt Modelle der gesamten Anwendung. Example 8.3. Abschlissendes POM des Moduls Simple-Model <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-model</artifactId> <packaging>jar</packaging> <name>Chapter 8 Simple Object Model</name> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> </dependency> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> </dependency> </dependencies> 186
  • Optimirung und Überarbeitung der POMs </project> Im folgenden das POM des Simple Persist Moduls. Hierin wird die gesamte Persistenzlogik durch Hibernate gekapselt. Example 8.4. Abschliessendes POM fdes Moduls Simple-Persist <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-persist</artifactId> <packaging>jar</packaging> <name>Chapter 8 Simple Persistence API</name> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-model</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> 187
  • Optimirung und Überarbeitung der POMs </dependency> </dependencies> </project> Dann das Simple Weather Projekt, das Projekt, welches die gesamte Arbeit mit dem Yahoo! Wetter RSS-Feed und dessen Analyse zu tun hat. Dieses Projekt ist von Simple Model abhängig. Example 8.5. Abschlissendes POM des Moduls Simple-Weather <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <name>Chapter 8 Simple Weather API</name> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-model</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> 188
  • Optimirung und Überarbeitung der POMs <version>1.3.2</version> <scope>test</scope> </dependency> </dependencies> </project> Schließlich ist das Simple Web Projekt, eine Web-Applikation welche die abgerufenen Wettervorhersagen in einer HSQLDB Datenbank speichert, sowie mit Simple Weather und den daraus bereitgestellten Bibliotheken kommuniziert. Example 8.6. Abschliessendes POM des Moduls Simple-Webapp <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <name>Chapter 8 Simple Web Application</name> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-model</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-weather</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-persist</artifactId> <version>${project.version}</version> </dependency> <dependency> 189
  • Optimirung und Überarbeitung der POMs <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.9</version> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> </dependency> </dependencies> </plugin> </plugins> </build> </project> 8.7. Fazit Dieses Kapitel hat aufgezeigt, welche Techniken Ihnen zur Verbesserung der Kontrolle Ihrer Abhängigkeiten und Plugins zur Verfügung stehen, um Ihre zukünftigen Builds wartungsfreundlich zu gestalten. Wir empfehlen in regelmäßigen Abständen der Überprüfung Ihres Builds wie aufgezeigt, um sicherzustellen, dass Wiederholungen und damit verbundene potenzielle Krisenherde minimiert werden. Wie Ihr Projekt reift, werden Sie zwangsläufig 190
  • Optimirung und Überarbeitung der POMs neue Abhängigkeiten einführen, oder Sie werden feststellen, dass eine Einzelabhängigkeit nun an 10 oder mehr Orten zum Einsatz kommt und somit besser höher aufgehängt wird. Die Liste der zum Einsatz kommenden bzw. nicht kommenden Abhängigkeiten verändert sich im Laufe der Zeit und lässt sich mit dem Dependency Maven Plugin leicht bearbeiten. 191
  • Part II. Maven Reference 192
  • Chapter 9. The Project Object Model 9.1. Introduction This chapter covers the central concept of Maven—the Project Object Model. The POM is where a project’s identity and structure are declared, builds are configured, and projects are related to one another. The presence of a pom.xml file defines a Maven project. 9.2. The POM Maven projects, dependencies, builds, artifacts: all of these are objects to be modeled and described. These objects are described by an XML file called a Project Object Model. The POM tells Maven what sort of project it is dealing with and how to modify default behavior to generate output from source. In the same way a Java web application has a web.xml that describes, configures, and customizes the application, a Maven project is defined by the presence of a pom.xml. It is a descriptive declaration of a project for Maven; it is the figurative “map” that Maven needs to understand what it is looking at when it builds your project. You could also think of the pom.xml as analogous to a Makefile or an Ant build.xml. When you are using GNU make to build something like MySQL, you’ll usually have a file named Makefile that contains explicit instructions for building a binary from source. When you are using Apache Ant, you likely have a file named build.xml that contains explicit instructions for cleaning, compiling, packaging, and deploying an application. make, Ant, and Maven are similar in that they rely on the presence of a commonly named file such as Makefile, build.xml, or pom.xml, but that is where the similarities end. If you look at a Maven pom.xml, the majority of the POM is going to deal with descriptions: Where is the source code? Where are the resources? What is the packaging? If you look at an Ant build.xml file, you’ll see something entirely different. You’ll see explicit 193
  • The Project Object Model instructions for tasks such as compiling a set of Java classes. The Maven POM is declarative, and although you can certainly choose to include some procedural customizations via the Maven Ant plugin, for the most part you will not need to get into the gritty procedural details of your project’s build. The POM is also not specific to building Java projects. While most of the examples in this book are geared towards Java applications, there is nothing Java-specific in the definition of a Maven Project Object Model. While Maven's default plugins are targeted at building JAR artifacts from a set of source, tests, and resources, there is nothing preventing you from defining a POM for a project that contains C# sources and produces some proprietary Microsoft binary using Microsoft tools. Similarly, there is nothing stopping you from defining a POM for a technical book. In fact, the source for this book and this book's examples is captured in a multi-module Maven project which uses one of the many Maven Docbook plugins to apply the standard Docbook XSL to a series of chapter XML files. Others have created Maven plugins to build Adobe Flex code into SWCs and SWFs, and yet others have used Maven to build projects written in C. We've established that the POM describes and declares, it is unlike Ant or Make in that it doesn't provide explicit instructions, and we've noted that POM concepts are not specific to Java. Diving into more specifics, take a look at Figure 9.1, “The Project Object Model” for a survey of the contents of a POM. 194
  • The Project Object Model Figure 9.1. The Project Object Model The POM contains four categories of description and configuration: General project information This includes a project’s name, the URL for a project, the sponsoring organization, and a list of developers and contributors along with the license for a project. Build settings In this section, we customize the behavior of the default Maven build. We can change the location of source and tests, we can add new plugins, we can attach plugin goals to the lifecycle, and we can customize the site generation parameters. Build environment The build environment consists of profiles that can be activated for use in different environments. For example, during development you may want to deploy to a development server, whereas in production you want to deploy to a 195
  • The Project Object Model production server. The build environment customizes the build settings for specific environments and is often supplemented by a custom settings.xml in ~/.m2. This settings file is discussed in Chapter 11, Build Profiles and in the section Section A.2, “Die Details der settings.xml Datei”. POM relationships A project rarely stands alone; it depends on other projects, inherits POM settings from parent projects, defines its own coordinates, and may include submodules. 9.2.1. The Super POM Before we dive into some examples of POMs, let's take a quick look at the Super POM. All Maven project POMs extend the Super POM which defines a set of defaults shared by all projects. This Super POM is a part of the Maven installation, and can be found in the maven-2.0.9-uber.jar file in ${M2_HOME}/lib. If you look in this JAR file, you will find a file named pom-4.0.0.xml under the org.apache.maven.project package. The Super POM for Maven is shown in Example 9.1, “The Super POM”. Example 9.1. The Super POM <project> <modelVersion>4.0.0</modelVersion> <name>Maven Default Project</name> <repositories> <repository> <id>central</id> # <name>Maven Repository Switchboard</name> <layout>default</layout> <url>http://repo1.maven.org/maven2</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>central</id> # <name>Maven Plugin Repository</name> 196
  • The Project Object Model <url>http://repo1.maven.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories> <build> # <directory>target</directory> <outputDirectory>target/classes</outputDirectory> <finalName>${pom.artifactId}-${pom.version}</finalName> <testOutputDirectory>target/test-classes</testOutputDirectory> <sourceDirectory>src/main/java</sourceDirectory> <scriptSourceDirectory>src/main/scripts</scriptSourceDirectory> <testSourceDirectory>src/test/java</testSourceDirectory> <resources> <resource> <directory>src/main/resources</directory> </resource> </resources> <testResources> <testResource> <directory>src/test/resources</directory> </testResource> </testResources> </build> <pluginManagement># <plugins> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.1</version> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-1</version> </plugin> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> </plugin> <plugin> <artifactId>maven-dependency-plugin</artifactId> <version>2.0</version> </plugin> 197
  • The Project Object Model <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.3</version> </plugin> <plugin> <artifactId>maven-ear-plugin</artifactId> <version>2.3.1</version> </plugin> <plugin> <artifactId>maven-ejb-plugin</artifactId> <version>2.1</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-javadoc-plugin</artifactId> <version>2.4</version> </plugin> <plugin> <artifactId>maven-plugin-plugin</artifactId> <version>2.3</version> </plugin> <plugin> <artifactId>maven-rar-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-release-plugin</artifactId> <version>2.0-beta-7</version> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-site-plugin</artifactId> <version>2.0-beta-6</version> </plugin> <plugin> <artifactId>maven-source-plugin</artifactId> <version>2.0.4</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.4.2</version> </plugin> <plugin> 198
  • The Project Object Model <artifactId>maven-war-plugin</artifactId> <version>2.1-alpha-1</version> </plugin> </plugins> </pluginManagement> <reporting> <outputDirectory>target/site</outputDirectory> </reporting> </project> The Super POM defines some standard configuration variables that are inherited by all projects. Those values are captured in the annotated sections: ‚ The default Super POM defines a single remote Maven repository with an ID of central. This is the central Maven repository that all Maven clients are configured to read from by default. This setting can be overridden by a custom settings.xml file. Note that the default Super POM has disabled snapshot artifacts on the central Maven repository. If you need to use a snapshot repository, you will need to customize repository settings in your pom.xml or in your settings.xml. Settings and profiles are covered in Chapter 11, Build Profiles and in Section A.2, “Die Details der settings.xml Datei”. ƒ The central Maven repository also contains Maven plugins. The default plugin repository is the central Maven repository. Snapshots are disabled, and the update policy is set to “never,” which means that Maven will never automatically update a plugin if a new version is released. „ The build element sets the default values for directories in the Maven Standard Directory layout. … Starting in Maven 2.0.9, default versions of core plugins have been provided in the Super POM. This was done to provide some stability for users that are not specifying versions in their POMs. 199
  • The Project Object Model Figure 9.2. The Super POM is always the base Parent 9.2.2. The Simplest POM All Maven POMs inherit defaults from the Super POM (introduced earlier in the section Section 9.2.1, “The Super POM””). If you are just writing a simple project that produces a JAR from some source in src/main/java, want to run your JUnit tests in src/test/java, and want to build a project site using mvn site, you don’t have to customize anything. All you would need, in this case, is the simplest possible POM shown in Example 9.2, “The Simplest POM”. This POM defines a groupId, artifactId, and version: the three required coordinates for every project. Example 9.2. The Simplest POM <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simplest-project</artifactId> <version>1</version> </project> 200
  • The Project Object Model Such a simple POM would be more than adequate for a simple project—e.g., a Java library that produces a JAR file. It isn’t related to any other projects, it has no dependencies, and it lacks basic information such as a name and a URL. If you were to create this file and then create the subdirectory src/main/java with some source code, running mvn package would produce a JAR in target/simple-project-1.jar. 9.2.3. The Effective POM This simplest POM brings us to the concept of the “effective POM.” Since POMs can inherit configuration from other POMs, you must always think of a Maven POM in terms of the combination of the Super POM, plus any parent POMs, and finally the current project’s POM. Maven starts with the Super POM and then overrides default configuration with one or more parent POMs. Then it overrides the resulting configuration with the current project’s POM. You end up with an effective POM that is a mixture of various POMs. If you want to see a project’s effective POM, you’ll need to run the effective-pom goal in the Maven Help plugin, which was introduced earlier in the section Section 2.8, “Das Maven Hilfe Plugin”.” To run the effective-pom goal, execute the following in a directory with a pom.xml file: $ mvn help:effective-pom Executing the effective-pom goal should print out an XML document capturing the merge between the Super POM and the POM from Example 9.2, “The Simplest POM”. 9.2.4. Real POMs Instead of typing up a contrived set of POMs to walk you through step-by-step, you should take a look at the examples in Part I, “Maven by Example”. Maven is something of a chameleon; you can pick and choose the features you want to take advantage of. Some open source projects may value the ability to list developers and contributors, generate clean project documentation, and manage releases automatically using the Maven Release plugin. On the other hand, someone 201
  • The Project Object Model working in a corporate environment on a small team might not be interested in the distribution management capabilities of Maven nor the ability to list developers. The remainder of this chapter is going to discuss features of the POM in isolation. Instead of bombarding you with a 10-page listing of a set of related POMs, we’re going to focus on creating a good reference for specific sections of the POM. In this chapter, we discuss relationships between POMs, but we don’t illustrate such a project here. If you are looking for such an illustration, refer to Chapter 7, Multi-module Enterprise Project. 9.3. POM Syntax The POM is always in a file named pom.xml in the base directory of a Maven project. This XML document can start with the XML declaration, or you can choose to omit it. All values in a POM are captured as XML elements. 9.3.1. Project Versions A Maven project’s version encodes a release version number that is used to group and order releases. Maven versions contain the following parts: major version, minor version, incremental version, and qualifier. In a version, these parts correspond to the following format: <major version>.<minor version>.<incremental version>-<qualifier> For example, the version "1.3.5" has a major version of 1, a minor version of 3, and an incremental version of 5. The version "5" has a major version of 5 and no minor or incremental version. The qualifier exists to capture milestone builds: alpha and beta releases, and the qualifier is separated from the major, minor, and incremental versions by a hyphen. For example, the version "1.3-beta-01" has a major version of 1, a minor version of 3, and a qualifier of "beta-01". Keeping your version numbers aligned with this standard will become very important when you want to start using version ranges in your POMs. Version ranges, introduced in Section 9.4.3, “Dependency Version Ranges”, allow you to specify a dependency on a range of versions, and they are only supported because 202
  • The Project Object Model Maven has the ability to sort versions based on the version release number format introduced in this section. If your version release number matches the format <major>.<minor>.<incremental>-<qualifier> then your versions will be compared properly; "1.2.3" will be evaluated as a more recent build than "1.0.2", and the comparison will be made using the numeric values of the major, minor, and incremental versions. If your version release number does not fit the standard introduced in this section, then your versions will be compared as strings; "1.0.1b" will be compared to "1.2.0b" using a String comparison. 9.3.1.1. Version Build Numbers One gotcha for release version numbers is the ordering of the qualifiers. Take the version release numbers “1.2.3-alpha-2” and “1.2.3-alpha-10,” where the “alpha-2” build corresponds to the 2nd alpha build, and the “alpha-10” build corresponds to the 10th alpha build. Even though “alpha-10” should be considered more recent than “alpha-2,” Maven is going to sort “alpha-10” before “alpha-2” due to a known issue in the way Maven handles version numbers. Maven is supposed to treat the number after the qualifier as a build number. In other words, the qualifier should be "alpha", and the build number should be 2. Even though Maven has been designed to separate the build number from the qualifier, this parsing is currently broken. As a result, "alpha-2" and "alpha-10" are compared using a String comparison, and "alpha-10" comes before "alpha-2" alphabetically. To get around this limitation, you will need to left-pad your qualified build numbers. If you use "alpha-02" and "alpha-10" this problem will go away, and it will continue to work once Maven properly parses the version build number. 9.3.1.2. SNAPSHOT Versions Maven versions can contain a string literal to signify that a project is currently under active development. If a version contains the string “SNAPSHOT,” then Maven will expand this token to a date and time value converted to UTC (Coordinated Universal Time) when you install or release this component. For 203
  • The Project Object Model example, if your project has a version of “1.0-SNAPSHOT” and you deploy this project’s artifacts to a Maven repository, Maven would expand this version to “1.0-20080207-230803-1” if you were to deploy a release at 11:08 PM on February 7th, 2008 UTC. In other words, when you deploy a snapshot, you are not making a release of a software component; you are releasing a snapshot of a component at a specific time. Why would you use this? SNAPSHOT versions are used for projects under active development. If your project depends on a software component that is under active development, you can depend on a SNAPSHOT release, and Maven will periodically attempt to download the latest snapshot from a repository when you run a build. Similarly, if the next release of your system is going to have a version "1.4", your project would have a version "1.4-SNAPSHOT" version until it was formally released. As a default setting, Maven will not check for SNAPSHOT releases on remote repositories, to depend on SNAPSHOT releases, users must explicitly enable the ability to download snapshots using a repository or pluginRepository element in the POM. When releasing a project you should resolve all dependencies on SNAPSHOT versions to dependencies on released versions. If a project depends on a SNAPSHOT, it is not stable as the dependencies may change over time. Artifacts published to non-snapshot Maven repositories such as http://repo1.maven.org/maven2 cannot depend on SNAPSHOT versions, as Maven's Super POM has snapshot's disabled from the Central repository. SNAPSHOT versions are for development only. 9.3.1.3. LATEST and RELEASE Versions When you depend on a plugin or a dependency, you can use the a version value of LATEST or RELEASE. LATEST refers to the latest released or snapshot version of a particular artifact, the most recently deployed artifact in a particular repository. RELEASE refers to the last non-snapshot release in the repository. In general, it is not a best practice to design software which depends on a non-specific version of an artifact. If you are developing software, you might want to use 204
  • The Project Object Model RELEASE or LATEST as a convenience so that you don't have to update version numbers when a new release of a third-party library is released. When you release software, you should always make sure that your project depends on specific versions to reduce the chances of your build or your project being affected by a software release not under your control. Use LATEST and RELEASE with caution, if at all. Starting with Maven 2.0.9, Maven locks down the version numbers of common and core Maven plugins in the super POM to standardize a core set of Maven plugins for a particular version of Maven. This change was introduced to Maven 2.0.9 to bring stability and reproducibility to Maven builds. Before Maven 2.0.9, Maven would automatically update core Maven plugins using the LATEST version. This behavior led to a number of surprises when bugs was introduced into core plugins or functionality changed in a core plugin which subsequently broke a build. When Maven automatically updated core plugins, it was noted that there was little guarantee that a build would be reproducible as plugins could be updated whenever a new version was pushed to the central repository. Starting with Maven 2.0.9, Maven, essentially, ships with a core set of locked down plugin versions. Non-core plugins, or plugins without versions assigned in the Super POM will still use the LATEST version to retrieve a plugin artifact from the repository. It is for this reason that you should assign explicit version numbers to any custom or non-core plugins used in your build. 9.3.2. Property References A POM can include references to properties preceded by a dollar sign and surrounded by two curly braces. For example, consider the following POM: <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>project-a</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <build> <finalName>${project.groupId}-${project.artifactId}</finalName> </build> </project> 205
  • The Project Object Model If you put this XML in a pom.xml and run mvn help:effective-pom, you will see that the output contains the line: ... <finalName>org.sonatype.mavenbook-project-a</finalName> ... When Maven reads a POM, it replaces references to properties when it loads the POM XML. Maven properties occur frequently in advanced Maven usage, and are similar to properties in other systems such as Ant or Velocity. They are simply variables delimited by ${...}. Maven provides three implicit variables which can be used to access environment variables, POM information, and Maven Settings: env The env variable exposes environment variables exposed by your operating system or shell. For example, a reference to ${env.PATH} in a Maven POM would be replaced by the ${PATH} environment variable (or %PATH% in Windows). project The project variable exposes the POM. You can use a dot-notated (.) path to reference the value of a POM element. For example, in this section we used the groupId and artifactId to set the finalName element in the build configuration. The syntax for this property reference was: ${project.groupId}-${project.artifactId}. settings The settings variable exposes Maven settings information. You can use a dot-notated (.) path to reference the value of an element in a settings.xml file. For example, ${settings.offline} would reference the value of the offline element in ~/.m2/settings.xml. Note You may see older builds that use ${pom.xxx} or just ${xxx} to reference POM properties. These methods have been deprecated and only ${project.xxx} should be used. 206
  • The Project Object Model In addition to the three implicit variables, you can reference system properties and any custom properties set in the Maven POM or in a build profile: Java System Properties All properties accessible via getProperties() on java.lang.System are exposed as POM properties. Some examples of system properties are: ${user.name}, ${user.home}, ${java.home}, and ${os.name}. A full list of system properties can be found in the Javadoc for the java.lang.System class. x Arbitrary properties can be set with a properties element in a pom.xml or settings.xml, or properties can be loaded from external files. If you set a property named fooBar in your pom.xml, that same property is referenced with ${fooBar}. Custom properties come in handy when you are building a system that filters resources and targets different deployment platforms. Here is the syntax for setting ${foo}=bar in a POM: <properties> <foo>bar</foo> </properties> For a more comprehensive list of available properties, see Chapter 13, Properties and Ressource Filterung. 9.4. Project Dependencies Maven can manage both internal and external dependencies. An external dependency for a Java project might be a library such as Plexus, the Spring Framework, or Log4J. An internal dependency is illustrated by a web application project depending on another project that contains service classes, model objects, or persistence logic. Example 9.3, “Project Dependencies” shows some examples of project dependencies. Example 9.3. Project Dependencies <project> 207
  • The Project Object Model ... <dependencies> <dependency> <groupId>org.codehaus.xfire</groupId> <artifactId>xfire-java5</artifactId> <version>1.2.5</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> </dependencies> ... </project> The first dependency is a compile dependency on the XFire SOAP library from Codehaus. You would use this type of dependency if your project depended on this library for compilation, testing, and during execution. The second dependency is a test-scoped dependency on JUnit. You would use a test-scoped dependency when you need to reference this library only during testing. The last dependency in Example 9.3, “Project Dependencies” is a dependency on the Servlet 2.4 API. The last dependency is scoped as a provided dependency. You would use a provided scope when the application you are developing needs a library for compilation and testing, but this library is supplied by a container at runtime. 9.4.1. Dependency Scope Example 9.3, “Project Dependencies” briefly introduced three of the five dependency scopes: compile, test, and provided. Scope controls which dependencies are available in which classpath, and which dependencies are included with an application. Let’s explore each scope in detail: 208
  • The Project Object Model compile compile is the default scope; all dependencies are compile-scoped if a scope is not supplied. compile dependencies are available in all classpaths, and they are packaged. provided provided dependencies are used when you expect the JDK or a container to provide them. For example, if you were developing a web application, you would need the Servlet API available on the compile classpath to compile a servlet, but you wouldn’t want to include the Servlet API in the packaged WAR; the Servlet API JAR is supplied by your application server or servlet container. provided dependencies are available on the compilation classpath (not runtime). They are not transitive, nor are they packaged. runtime runtime dependencies are required to execute and test the system, but they are not required for compilation. For example, you may need a JDBC API JAR at compile time and the JDBC driver implementation only at runtime. test test-scoped dependencies are not required during the normal operation of an application, and they are available only during test compilation and execution phases. The test scope was previously introduced in Section 4.10, “Hinzufügen von Gebietsbezogenen Unit Tests”.” system The system scope is similar to provided except that you have to provide an explicit path to the JAR on the local file system. This is intended to allow compilation against native objects that may be part of the system libraries. The artifact is assumed to always be available and is not looked up in a repository. If you declare the scope to be system, you must also provide the systemPath element. Note that this scope is not recommended (you should always try to reference dependencies in a public or custom Maven repository). 209
  • The Project Object Model 9.4.2. Optional Dependencies Assume that you are working on a library that provides caching behavior. Instead of writing a caching system from scratch, you want to use some of the existing libraries that provide caching on the file system and distributed caches. Also assume that you want to give the end user an option to cache on the file system or to use an in-memory distributed cache. To cache on the file system, you’ll want to use a freely available library called EHCache (http://ehcache.sourceforge.net/), and to cache in a distributed in-memory cache, you want to use another freely available caching library named SwarmCache (http://swarmcache.sourceforge.net/). You’ll code an interface and create a library that can be configured to use either EHCache or SwarmCache, but you want to avoid adding a dependency on both caching libraries to any project that depends on your library. In other words, you need both libraries to compile this library project, but you don't want both libraries to show up as transitive runtime dependencies for the project that uses your library. You can accomplish this by using optional dependencies as shown in Example 9.4, “Declaring Optional Dependencies”. Example 9.4. Declaring Optional Dependencies <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>my-project</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>1.4.1</version> <optional>true</optional> </dependency> <dependency> <groupId>swarmcache</groupId> <artifactId>swarmcache</artifactId> <version>1.0RC2</version> <optional>true</optional> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.13</version> 210
  • The Project Object Model </dependency> </dependencies> </project> Once you've declared these dependencies as optional, you are required to include them explicitly in the project that depends on my-project. For example, if you were writing an application which depended on my-project and wanted to use the EHCache implementation, you would need to add the following dependency element to your project. <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>my-application</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>org.sonatype.mavenbook</groupId> <artifactId>my-project</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>swarmcache</artifactId> <version>1.4.1</version> </dependency> </dependencies> </project> In an ideal world, you wouldn’t have to use optional dependencies. Instead of having one large project with a series of optional dependencies, you would separate the EHCache-specific code to a my-project-ehcache submodule and the SwarmCache-specific code to a my-project-swarmcache submodule. This way, instead of requiring projects that reference my-project to specifically add a dependency, projects can just reference a particular implementation project and benefit from the transitive dependency. 9.4.3. Dependency Version Ranges You don’t just have to depend on a specific version of a dependency; you can specify a range of versions that would satisfy a given dependency. For example, 211
  • The Project Object Model you can specify that your project depends on version 3.8 or greater of JUnit, or anything between versions 1.2.10 and 1.2.14 of JUnit. You do this by surrounding one or more version numbers with the following characters: (, ) Exclusive quantifiers [, ] Inclusive quantifiers For example, if you wished to access any JUnit version greater than or equal to 3.8 but less than 4.0, your dependency would be as shown in Example 9.5, “Specifying a Dependency Range: JUnit 3.8 - JUnit 4.0”. Example 9.5. Specifying a Dependency Range: JUnit 3.8 - JUnit 4.0 <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>[3.8,4.0)</version> <scope>test</scope> </dependency> If you want to depend on any version of JUnit no higher than 3.8.1, you would specify only an upper inclusive boundary, as shown in Example 9.6, “Specifying a Dependency Range: JUnit <= 3.8.1”. Example 9.6. Specifying a Dependency Range: JUnit <= 3.8.1 <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>[,3.8.1]</version>ex-de <scope>test</scope> </dependency> A version before or after the comma is not required, and means +/- infinity. For example, "[4.0,)" means any version greater than or equal to 4.0. "(,2.0)" is any 212
  • The Project Object Model version less than 2.0. "[1.2]" means only version 1.2, and nothing else. Note When declaring a "normal" version such as 3.8.2 for Junit, internally this is represented as "allow anything, but prefer 3.8.2." This means that when a conflict is detected, Maven is allowed to use the conflict algorithms to choose the best version. If you specify [3.8.2], it means that only 3.8.2 will be used and nothing else. If somewhere else there is a dependency that specifies [3.8.1], you would get a build failure telling you of the conflict. We point this out to make you aware of the option, but use it sparingly and only when really needed. The preferred way to resolve this is via dependencyManagement. 9.4.4. Transitive Dependencies A transitive dependency is a dependency of a dependency. If project-a depends on project-b, which in turn depends on project-c, then project-c is considered a transitive dependency of project-a. If project-c depended on project-d, then project-d would also be considered a transitive dependency of project-a. Part of Maven’s appeal is that it can manage transitive dependencies and shield the developer from having to keep track of all of the dependencies required to compile and run an application. You can just depend on something like the Spring Framework and not have to worry about tracking down every last dependency of the Spring Framework. Maven accomplishes this by building a graph of dependencies and dealing with any conflicts and overlaps that might occur. For example, if Maven sees that two projects depend on the same groupId and artifactId, it will sort out which dependency to use automatically, always favoring the more recent version of a dependency. Although this sounds convenient, there are some edge cases where transitive dependencies can cause some configuration issues. For these scenarios, you can use a dependency exclusion. 213
  • The Project Object Model 9.4.4.1. Transitive Dependencies and Scope Each of the scopes outlined earlier in the section Section 9.4.1, “Dependency Scope”” affects not just the scope of the dependency in the declaring project, but also how it acts as a transitive dependency. The easiest way to convey this information is through a table, as in Table 9.1, “How Scope Affects Transitive Dependencies”. Scopes in the top row represent the scope of a transitive dependency. Scopes in the leftmost column represent the scope of a direct dependency. The intersection of the row and column is the scope that is assigned to a transitive dependency. A blank cell in this table means that the transitive dependency will be omitted. Table 9.1. How Scope Affects Transitive Dependencies Direct Scope Transitive Scope compile provided runtime test compile compile - runtime - provided provided provided provided - runtime runtime - runtime - test test - test - To illustrate the relationship of transitive dependency scope to direct dependency scope, consider the following example. If project-a contains a test scoped dependency on project-b which contains a compile scoped dependency on project-c. project-c would be a test-scoped transitive dependency of project-a. You can think of this as a transitive boundary which acts as a filter on dependency scope. Transitive dependencies which are provided and test scope usually do not affect a project. The exception to this rule is that a provided scoped transitive dependency to a provided scope direct dependency is still a provided dependency of a project. Transitive dependencies which are compile and runtime scoped 214
  • The Project Object Model usually affect a project regardless of the scope of a direct dependency. Transitive dependencies which are compile scoped will have the same scope irregardless of the scope of the direct dependency. Transitive dependencies which are runtime scoped will generally have the same scope of the direct dependency except when the direct dependency has a scope of compile. When a transitive dependency is runtime scoped and a direct is compile scoped the direct dependency the transitive dependency will have an effective scope of runtime. 9.4.5. Conflict Resolution There will be times when you need to exclude a transitive dependency, such as when you are depending on a project that depends on another project, but you would like to either exclude the dependency altogether or replace the transitive dependency with another dependency that provides the same functionality. Example 9.7, “Excluding a Transitive Dependency” shows an example of a dependency element that adds a dependency on project-a, but excludes the transitive dependency project-b. Example 9.7. Excluding a Transitive Dependency <dependency> <groupId>org.sonatype.mavenbook</groupId> <artifactId>project-a</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>project-b</artifactId> </exclusion> </exclusions> </dependency> Often, you will want to replace a transitive dependency with another implementation. For example, if you are depending on a library that depends on the Sun JTA API, you may want to replace the declared transitive dependency. Hibernate is one example. Hibernate depends on the Sun JTA API JAR, which is not available in the central Maven repository because it cannot be freely 215
  • The Project Object Model redistributed. Fortunately, the Apache Geronimo project has created an independent implementation of this library that can be freely redistributed. To replace a transitive dependency with another dependency, you would exclude the transitive dependency and declare a dependency on the project you wanted instead. Example 9.8, “Excluding and Replacing a Transitive Dependency” shows an example of a such replacement. Example 9.8. Excluding and Replacing a Transitive Dependency <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-jta_1.1_spec</artifactId> <version>1.1</version> </dependency> </dependencies> In Example 9.8, “Excluding and Replacing a Transitive Dependency”, there is nothing marking the dependency on geronimo-jta_1.1_spec as a replacement, it just happens to be a library which provides the same API as the original JTA dependency. Here are some other reasons you might want to exclude or replace transitive dependencies: 1. The groupId or artifactId of the artifact has changed, where the current project requires an alternately named version from a dependency's version - resulting in 2 copies of the same project in the classpath. Normally Maven would capture this conflict and use a single version of the project, but when groupId or artifactId are different, Maven will consider this to be two different libraries. 216
  • The Project Object Model 2. An artifact is not used in your project and the transitive dependency has not been marked as an optional dependency. In this case, you might want to exclude a dependency because it isn't something your system needs and you are trying to cut down on the number of libraries distributed with an application. 3. An artifact which is provided by your runtime container thus should not be included with your build. An example of this is if a dependency depends on something like the Servlet API and you want to make sure that the dependency is not included in a web application's WEB-INF/lib directory. 4. To exclude a dependency which might be an API with multiple implementations. This is the situation illustrated by Example 9.8, “Excluding and Replacing a Transitive Dependency”; there is a Sun API which requires click-wrap licensing and a time-consuming manual install into a custom repository (Sun's JTA JAR) versus a freely distributed version of the same API available in the central Maven repository (Geronimo's JTA implementation). 9.4.6. Dependency Management Once you've adopted Maven at your super complex enterprise and you have two hundred and twenty inter-related Maven projects, you are going to start wondering if there is a better way to get a handle on dependency versions. If every single project that uses a dependency like the MySQL Java connector needs to independently list the version number of the dependency, you are going to run into problems when you need to upgrade to a new version. Because the version numbers are distributed throughout your project tree, you are going to have to manually edit each of the pom.xml files that reference a dependency to make sure that you are changing the version number everywhere. Even with find, xargs, and awk, you are still running the risk of missing a single POM. Luckily, Maven provides a way for you to consolidate dependency version numbers in the dependencyManagement element. You'll usually see the 217
  • The Project Object Model dependencyManagement element in a top-level parent POM for an organization or project. Using the dependencyManagement element in a pom.xml allows you to reference a dependency in a child project without having to explicitly list the version. Maven will walk up the parent-child hierarchy until it finds a project with a dependencyManagement element, it will then use the version specified in this dependencyManagement element. For example, if you have a large set of projects which make use of the MySQL Java connector version 5.1.2, you could define the following dependencyManagement element in your multi-module project's top-level POM. Example 9.9. Defining Dependency Versions in a Top-level POM <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>a-parent</artifactId> <version>1.0.0</version> ... <dependencyManagement> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.2</version> </dependency> ... <dependencies> </dependencyManagement> Then, in a child project, you can add a dependency to the MySQL Java Connector using the following dependency XML: <project> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook</groupId> <artifactId>a-parent</artifactId> <version>1.0.0</version> </parent> <artifactId>project-a</artifactId> ... <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> 218
  • The Project Object Model </dependency> </dependencies> </project> You should notice that the child project did not have to explicitly list the version of the mysql-connector-java dependency. Because this dependency was defined in the top-level POM's dependencyManagement element, the version number is going to propagate to the child project's dependency on mysql-connector-java. Note that if this child project did define a version, it would override the version listed in the top-level POM's dependencyManagement section. That is, the dependencyManagement version is only used when the child does not declare a version directly. Dependency management in a top-level POM is different from just defining a dependency on a widely shared parent POM. For starters, all dependencies are inherited. If mysql-connector-java were listed as a dependency of the top-level parent project, every single project in the hierarchy would have a reference to this dependency. Instead of adding in unnecessary dependencies, using dependencyManagement allows you to consolidate and centralize the management of dependency versions without adding dependencies which are inherited by all children. In other words, the dependencyManagement element is equivalent to an environment variable which allows you to declare a dependency anywhere below a project without specifying a version number. 9.5. Project Relationships One of the compelling reasons to use Maven is that it makes the process of tracking down dependencies (and dependencies of dependencies) very easy. When a project depends on an artifact produced by another project we say that this artifact is a dependency. In the case of a Java project, this can be as simple as a project depending on an external dependency like Log4J or JUnit. While dependencies can model external dependencies, they can also manage the dependencies between a set of related projects, if project-a depends on project-b, Maven is smart enough to know that project-b must be built before 219
  • The Project Object Model project-a. Relationships are not only about dependencies and figuring out what one project needs to be able to build an artifact. Maven can model the relationship of a project to a parent, and the relationship of a project to submodules. This section gives an overview of the various relationships between projects and how such relationships are configured. 9.5.1. More on Coordinates Coordinates define a unique location for a project, they were first introduced in Chapter 3, Ein einfaches Maven Projekt. Projects are related to one another using Maven Coordinates. project-a doesn't just depend on project-b; a project with a groupId, artifactId, and version depends on another project with a groupId, artifactId, and version. To review, a Maven Coordinate is made up of three components: groupId A groupId groups a set of related artifacts. Group identifiers generally resemble a Java package name. For example, the groupId org.apache.maven is the base groupId for all artifacts produced by the Apache Maven project. Group identifiers are translated into paths in the Maven Repository; for example, the org.apache.maven groupId can be found in /maven2/org/apache/maven on repo1.maven.org. artifactId The artifactId is the project's main identifier. When you generate an artifact, this artifact is going to be named with the artifactId. When you refer to a project, you are going to refer to it using the artifactId. The artifactId, groupId combination must be unique. In other words, you can't have two separate projects with the same artifactId and groupId; artifactIds are unique within a particular groupId. Note While '.'s are commonly used in groupIds, you should try to avoid 220
  • The Project Object Model using them in artifactIds. This can cause issues when trying to parse a fully qualified name down into the subcomponents. version When an artifact is released, it is released with a version number. This version number is a numeric identifier such as "1.0", "1.1.1", or "1.1.2-alpha-01". You can also use what is known as a snapshot version. A snapshot version is a version for a component which is under development, snapshot version numbers always end in SNAPSHOT; for example, "1.0-SNAPSHOT", "1.1.1-SNAPSHOT", and "1-SNAPSHOT". Section 9.3.1.1, “Version Build Numbers” introduces versions and version ranges. There is a fourth, less-used qualifier: classifier You would use a classifier if you were releasing the same code but needed to produce two separate artifacts for technical reasons. For example, if you wanted to build two separate artifacts of a JAR, one compiled with the Java 1.4 compiler and another compiled with the Java 6 compiler, you might use the classifier to produce two separate JAR artifacts under the same groupId:artifactId:version combination. If your project uses native extensions, you might use the classifier to produce an artifact for each target platform. Classifiers are commonly used to package up an artifact's sources, JavaDocs or binary assemblies. When we talk of dependencies in this book, we often use the following shorthand notation to describe a dependency: groupId:artifactId:version. To refer to the 2.5 release of the Spring Framework, we would refer to it as org.springframework:spring:2.5. When you ask Maven to print out a list of dependencies with the Maven Dependency plugin, you will also see that Maven tends to print out log messages with this shorthand dependency notation. 9.5.2. Multi-module Projects 221
  • The Project Object Model Multi-module projects are projects which contain a list of modules to build. A multi-module project always has a packaging of pom, and rarely produces an artifact. A multi-module project exists only to group projects together in a build. Figure 9.3, “Multi-module Project Relationships” shows a project hierarchy which includes two parent projects with packaging of pom, and three projects with packaging of jar. Figure 9.3. Multi-module Project Relationships The directory structure on the file system would also mirror the module relationships. A set of projects illustrated by Figure 9.3, “Multi-module Project Relationships” would have the following directory structure: top-group/pom.xml top-group/sub-group/pom.xml top-group/sub-group/project-a/pom.xml top-group/sub-group/project-b/pom.xml top-group/project-c/pom.xml The projects are related to one another because top-group and sub-group are referencing sub-modules in a POM. For example, the org.sonatype.mavenbook:top-group project is a multi-module project with 222
  • The Project Object Model packaging of type pom. top-group's pom.xml would include the following modules element: Example 9.10. top-group modules element <project> <groupId>org.sonatype.mavenbook</groupId> <artifactId>top-group</artifactId> ... <modules> <module>sub-group</module> <module>project-c</module> </modules> ... </project> When Maven is reading top-group POM it will look at the modules element and see that top-group references the projects sub-group and project-c. Maven will then look for a pom.xml in each of these subdirectories. Maven repeats this process for each of the submodules: it will read the sub-group/pom.xml and see that the sub-group project references two projects with the following modules element: Example 9.11. sub-group modules element <project> ... <modules> <module>project-a</module> <module>project-b</module> </modules> ... </project> Note that we call the projects under the multi-module projects "modules" and not "children" or "child projects". This is purposeful, so as not to confuse projects grouped by multi-module projects with projects that inherit POM information from each other. 9.5.3. Project Inheritance 223
  • The Project Object Model There are going to be times when you want a project to inherit values from a parent POM. You might be building a large system, and you don't want to have to repeat the same dependency elements over and over again. You can avoid repeating yourself if your projects make use of inheritance via the parent element. When a project specifies a parent, it inherits the information in the parent project's POM. It can then override and add to the values specified in this parent POM. All Maven POMs inherit values from a parent POM. If a POM does not specify a direct parent using the parent element, that POM will inherit values from the Super POM. Example 9.12, “Project Inheritance” shows the parent element of project-a which inherits the POM defined by the a-parent project. Example 9.12. Project Inheritance <project> <parent> <groupId>com.training.killerapp</groupId> <artifactId>a-parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>project-a</artifactId> ... </project> Running mvn help:effective-pom in project-a would show a POM that is the result of merging the Super POM with the POM defined by a-parent and the POM defined in project-a. The implicit and explicit inheritance relationships for project-a are shown in Figure 9.4, “Project Inheritance for a-parent and project-a”. 224
  • The Project Object Model Figure 9.4. Project Inheritance for a-parent and project-a When a project specifies a parent project, Maven uses that parent POM as a starting point before it reads the current project's POM. It inherits everything, including the groupId and version number. You'll notice that project-a does not specify either, both groupId and version are inherited from a-parent. With a parent element, all a POM really needs to define is an artifactId. This isn't mandatory, project-a could have a different groupId and version, but by not providing values, Maven will use the values specified in the parent POM. If you start using Maven to manage and build large multi-module projects, you will often be creating many projects which share a common groupId and version. When you inherit a POM, you can choose to live with the inherited POM information or to selectively override it. The following is a list of items a Maven POM inherits from its parent POM: • identifiers (at least one of groupId or artifactId must be overridden.) • dependencies 225
  • The Project Object Model • developers and contributors • plugin lists • reports lists • plugin executions (executions with matching ids are merged) • plugin configuration When Maven inherits dependencies, it will add dependencies of child projects to the dependencies defined in parent projects. You can use this feature of Maven to specify widely used dependencies across all projects which inherit from a top-level POM. For example, if your system makes universal use of the Log4J logging framework, you can list this dependency in your top-level POM. Any projects which inherit POM information from this project will automatically have Log4J as a dependency. Similarly, if you need to make sure that every project is using the same version of a Maven plugin, you can list this Maven plugin version explicitly in a top-level parent POM's pluginManagement section. Maven assumes that the parent POM is available from the local repository, or available in the parent directory (../pom.xml) of the current project. If neither location is valid this default behavior may be overridden via the relativePath element. For example, some organizations prefer a flat project structure where a parent project's pom.xml isn't in the parent directory of a child project. It might be in a sibling directory to the project. If your child project were in a directory ./project-a and the parent project were in a directory named ./a-parent, you could specify the relative location of parent-a's POM with the following configuration: <project> <parent> <groupId>org.sonatype.mavenbook</groupId> <artifactId>a-parent</artifactId> <version>1.0-SNAPSHOT</version> <relativePath>../a-parent/pom.xml</relativePath> </parent> <artifactId>project-a</artifactId> </project> 226
  • The Project Object Model 9.6. POM Best Practices Maven can be used to manage everything from simple, single-project systems to builds that involve hundreds of inter-related submodules. Part of the learning process with Maven isn't just figuring out the syntax for configuring Maven, it is learning the "Maven Way"—the current set of best practices for organizing and building projects using Maven. This section attempts to distill some of this knowledge to help you adopt best practices from the start without having to wade through years of discussions on the Maven mailing lists. 9.6.1. Grouping Dependencies If you have a set of dependencies which are logically grouped together. You can create a project with pom packaging that groups dependencies together. For example, let's assume that your application uses Hibernate, a popular Object-Relational mapping framework. Every project which uses Hibernate might also have a dependency on the Spring Framework and a MySQL JDBC driver. Instead of having to include these dependencies in every project that uses Hibernate, Spring, and MySQL you could create a special POM that does nothing more than declare a set of common dependencies. You could create a project called persistence-deps (short for Persistence Dependencies), and have every project that needs to do persistence depend on this convenience project: Example 9.13. Consolidating Dependencies in a Single POM Project <project> <groupId>org.sonatype.mavenbook</groupId> <artifactId>persistence-deps</artifactId> <version>1.0</version> <packaging>pom</packaging> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>${hibernateVersion}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> 227
  • The Project Object Model <version>${hibernateAnnotationsVersion}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-hibernate3</artifactId> <version>${springVersion}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysqlVersion}</version> </dependency> </dependencies> <properties> <mysqlVersion>(5.1,)</mysqlVersion> <springVersion>(2.0.6,)</springVersion> <hibernateVersion>3.2.5.ga</hibernateVersion> <hibernateAnnotationsVersion>3.3.0.ga</hibernateAnnotationsVersion> </properties> </project> If you create this project in a directory named persistence-deps, all you need to do is create this pom.xml and run mvn install. Since the packaging type is pom, this POM is installed in your local repository. You can now add this project as a dependency and all of its dependencies will be added to your project. When you declare a dependency on this persistence-deps project, don't forget to specify the dependency type as pom. Example 9.14. Declaring a Dependency on a POM <project> <description>This is a project requiring JDBC</description> ... <dependencies> ... <dependency> <groupId>org.sonatype.mavenbook</groupId> <artifactId>persistence-deps</artifactId> <version>1.0</version> <type>pom</type> </dependency> </dependencies> </project> 228
  • The Project Object Model If you later decide to switch to a different JDBC driver (for example, JTDS), just replace the dependencies in the persistence-deps project to use net.sourceforge.jtds:jtds instead of mysql:mysql-java-connector and update the version number. All projects depending on persistence-deps will use JTDS if they decide to update to the newer version. Consolidating related dependencies is a good way to cut down on the length of pom.xml files that start having to depend on a large number of dependencies. If you need to share a large number of dependencies between projects, you could also just establish parent-child relationships between projects and refactor all common dependencies to the parent project, but the disadvantage of the parent-child approach is that a project can have only one parent. Sometimes it makes more sense to group similar dependencies together and reference a pom dependency. This way, your project can reference as many of these consolidated dependency POMs as it needs. Note Maven uses the depth of a dependency in the tree when resolving conflicts using a nearest-wins approach. Using the dependency grouping technique above pushes those dependencies one level down in the tree. Keep this in mind when choosing between grouping in a pom or using dependenctManagement in a parent POM 9.6.2. Multi-module vs. Inheritance There is a difference between inheriting from a parent project and being managed by a multimodule project. A parent project is one that passes its values to its children. A multimodule project simply manages a group of other subprojects or modules. The multimodule relationship is defined from the topmost level downwards. When setting up a multimodule project, you are simply telling a project that its build should include the specified modules. Multimodule builds are to be used to group modules together in a single build. The parent-child relationship is defined from the leaf node upwards. The parent-child relationship deals more with the definition of a particular project. When you associate a child with its parent, you are telling Maven that a project’s POM is derived from 229
  • The Project Object Model another. To illustrate the decision process that goes into choosing a design that uses inheritance vs. multi-module or both approaches consider the following two examples: the Maven project used to generate this book and a hypothetical project that contains a number of logically grouped modules. 9.6.2.1. Simple Project First, let's take a look at the maven-book project. The inheritance and multi-module relationships are shown in Figure 9.5, “maven-book Multi-module vs. Inheritance”. Figure 9.5. maven-book Multi-module vs. Inheritance When we build this Maven book you are reading, we run mvn package in a multi-module project named maven-book. This multi-module project includes two submodules: book-examples and book-chapters. Neither of these projects share the same parent, they are related only in that they are modules in the maven-book 230
  • The Project Object Model project. book-examples builds the ZIP and TGZ archives you downloaded to get this book's example. When we run the book-examples build from book-examples/ directory with mvn package, it has no knowledge that it is a part of the larger maven-book project. book-examples doesn't really care about maven-book, all it knows in life is that its parent is the top-most sonatype POM and that it creates an archive of examples. In this case, the maven-book project exists only as a convenience and as a aggregator of modules. The book projects do all define a parent. Each of the three projects: maven-book, book-examples, and book-chapters all list a shared "corporate" parent — sonatype. This is a common practice in organizations which have adopted Maven, instead of having every project extend the Super POM by default, some organizations define a top-level corporate POM that serves as the default parent when a project doesn't have any good reason to depend on another. In this book example, there is no compelling reason to have book-examples and book-chapters share the same parent POM, they are entirely different projects which have a different set of dependencies, a different build configuration, and use drastically different plugins to create the content you are now reading. The sonatype POM gives the organization a change to customize the default behavior of Maven and supply some organization-specific information to configure deployment settings and build profiles. 9.6.2.2. Multi-module Enterprise Project Let's take a look at an example that provides a more accurate picture of a real-world project where inheritance and multi-module relationships exist side by side. Figure 9.6, “Enterprise Multi-module vs. Inheritance” shows a collection of projects that resemble a typical set of projects in an enterprise application. There is a top-level POM for the corporation with an artifactId of sonatype. There is a multi-module project named big-system which references sub-modules server-side and client-side. 231
  • The Project Object Model Figure 9.6. Enterprise Multi-module vs. Inheritance What's going on here? Let's try to deconstruct this confusing set of arrows. First, let's take a look at big-system. The big-system might be the project that you would run mvn package on to build and test the entire system. big-system references submodules client-side and server-side. Each of these projects effectively rolls up all of the code that runs on either the server or on the client. Let's focus on the server-side project. Under the server-side project we have a project called server-lib and a multi-module project named web-apps. Under web-apps we have two Java web applications: client-web and admin-web. Let's start with the parent/child relationships from client-web and admin-web to web-apps. Since both of the web applications are implemented in the same web 232
  • The Project Object Model application framework (let's say Wicket), both projects would share the same set of core dependencies. The dependencies on the Servlet API, the JSP API, and Wicket would all be captured in the web-apps project. Both client-web and admin-web also need to depend on server-lib, this dependency would be defined as a dependency between web-apps and server-lib. Because client-web and admin-web share so much configuration by inheriting from web-apps, both client-web and admin-web will have very small POMs containing little more than identifiers, a parent declaration, and a final build name. Next we focus on the parent/child relationship from web-apps and server-lib to server-side. In this case, let's just assume that there is a separate working group of developers which work on the server-side code and another group of developers that work on the client-side code. The list of developers would be configured in the server-side POM and inherited by all of the child projects underneath it: web-apps, server-lib, client-web, and admin-web. We could also imagine that the server-side project might have different build and deployment settings which are unique to the development for the server side. The server-side project might define a build profile that only makes sense for all of the server-side projects. This build profile might contain the database host and credentials, or the server-side project's POM might configure a specific version of the Maven Jetty plugin which should be universal across all projects that inherit the server-side POM. In this example, the main reason to use parent/child relationships is shared dependencies and common configuration for a group of projects which are logically related. All of the projects below big-system are related to one another as submodules, but not all submodules are configured to point back to parent project that included it as a submodule. Everything is a submodule for reasons of convenience, to build the entire system just go to the big-system project directory and run mvn package. Look more closely at the figure and you'll see that there is no parent/child relationship between server-side and big-system. Why is this? POM inheritance is very powerful, but it can be overused. When it makes sense to share dependencies and build configuration, a parent/child relationship should be used. When it doesn't make sense is when there are distinct differences between two projects. Take, for example, the server-side and client-side projects. It is 233
  • The Project Object Model possible to create a system where client-side and server-side inherited a common POM from big-system, but as soon as a significant divergence between the two child projects develops, you then have to figure out creative ways to factor out common build configuration to big-system without affecting all of the children. Even though client-side and server-side might both depend on Log4J, they also might have distinct plugin configurations. There's a certain point defined more by style and experience where you decide that minimal duplication of configuration is a small price to pay for allowing projects like client-side and server-side to remain completely independent. Designing a huge set of thirty plus projects which all inherit five levels of POM configuration isn't always the best idea. In such a setup, you might not have to duplicate your Log4J dependency more than once, but you'll also end up having to wade through five levels of POM just figure out how Maven calculated your effective POM. All of this complexity to avoid duplicating five lines of dependency declaration. In Maven, there is a "Maven Way", but there are also many ways to accomplish the same thing. It all boils down to preference and style. For the most part, you won't go wrong if all of your submodules turn out to define back-references to the same project as a parent, but your use of Maven may evolve over time. 9.6.2.3. Prototype Parent Projects Take the following example shown in Figure 9.7, “Using parent projects as "prototypes" for specialized projects” as another hypothetical and creative way to use inheritance and multi-modules builds to reuse dependencies. 234
  • The Project Object Model Figure 9.7. Using parent projects as "prototypes" for specialized projects Figure 9.7, “Using parent projects as "prototypes" for specialized projects” is yet another way to think about inheritance and multi-module projects. In this example, you have two distinct systems. system-a and system-b each define independent applications. system-a defines two modules a-lib and a-swing. system-a and a-lib both define the top-level sonatype POM as a parent project, but the a-swing project defines swing-proto as a parent project. In this system, swing-proto supplies a foundational POM for Swing applications and the struts-proto project provides a foundational POM for Struts 2 web applications. While the sonatype POM provides high level information such as the groupId, organization information, and build profiles, struts-proto defines all of the dependencies that you need to create a struts application. This approach would work well if your 235
  • The Project Object Model development is characterized by many independent applications which each have to follow the same set of rules. If you are creating a lot of struts applications but they are not really related to one another, you might just define everything you need in struts-proto. The downside to this approach is that you won't be able to use parent/child relationships within the system-a and system-b project hierarchies to share information like developers and other build configuration. A project can only have one parent. The other downside of this approach is that as soon as you have one project that "breaks the mold" you'll either have to override the prototype parent POM or find a way to factor customizations into the shared parent without those customizations affecting all the children. In general, using POMs as prototypes for specialized project "types" isn't a recommended practice. 236
  • Chapter 10. Der Build Lebenszyklus 10.1. Einführung Maven modelliert Projekte als Nomen welche von einem Projekt Objekt Modell (POM) beschrieben werden. Das POM fängt die Identität des Projektes ein: Was beinhaltet das Projekt? Welche Art der Packetierung benötigt das Projekt? Ist das Projekt ein abgeleitetes Projekt? Was sind die Abhängigkeiten? Im vorangegangenen Kapitel haben wir eingeführt wie ein Projekt beschrieben werden kann, was wir nicht eingeführt haben sind die Mechanismen mittels welchen Maven auf diesen Objekten agieren kann. Im Maven Universum werden Verben durch Goals, welche in Maven Plugins verpackt und an Lebenszyklusphasen gebunden sind, dargestellt. Ein Maven Lebenszyklus besteht aus einer Reihe namentlich benannter Lebenszyklusphasen: prepare-resources, compile, package und install neben anderen. Es gibt also eine Phase welche die Kompilierung beschreibt und eine Phase welche die Packetierung umfasst. Darüber hinaus gibt es vorbereitende und nachbereitende Phasen, welche benutzt werden, um Goals welche vor oder gerade nach einer bestimmten Phase abgearbeitet werden müssen zu registrieren. Sobald Sie Maven aufrufen um einen Build zu erstellen, rufen Sie tatsächlich Maven dazu auf eine bestimmte Kette von Phasen zu durchlaufen und alle an eine Phase gebndene Goals abzuarbeiten. Ein Build Lebenszyklus ist eine Abfolge von Phasen welche existiert, um einer Anzahl Goals zu ordnen. Diese Goals sind gewählt und an Phasen gebunden abhängig vom entsprechenden Packetierungstyp des Projektes in Verarbeitung. In Maven gibt es drei Standard Lebenszyklen: clean, default (auch build genannt) und site. Dieses Kapitel wird erläutern, wie Maven Goals an Lebenszyklusphasen bindet und wie Lebenszyklen angepasst werden können. Die Standard Lebenszyklusphasen werden ebenfalls eingeführt. 10.1.1. Lebenszyklus: clean Der erste Lebenszyklus für welchen Sie sich interessieren werden ist zugleich der 237
  • Der Build Lebenszyklus einfachste Lebenszyklus von Maven. Der Aufruf von mvn clean started den Lebenszyklus clean welcher aus drei Phasen besteht: • pre-clean • clean • post-clean Die interessante Phase des Lebenszyklus clean ist die Phase clean. Das Goal clean (clean:clean) des Plugins clean wird an die Phase clean des Lebenszyklus clean gebunden. (Noch ganz sauber oder was!?!) Das Goal clean:clean löscht die Ergebnisse des vorgehenden Builds indem es das Zielverzeichnis löscht. Sollten Sie das Zielverzeichnis nicht weiter angepasst haben, so ist dieses Zielverzeichnis das Verzeichnis ${basedir}/target wie dies im Super POM definiert ist. Die Ausführung des Goals clean:clean wird nicht direkt mit dem Aufruf mvn clean:clean gestartet, statt dessen wird die Phase clean des Lebenszyklus zur Ausführung gebracht. Die Ausführung der Phase clean ermöglicht Maven auch Goals welche an die Phase pre-clean gebunden sind auszuführen. Ein Beispiel: nehmen wir an Sie möchten das Goal antrun:run während der Phase pre-clean anstossen, um eine Nachricht auszugeben, oder um ein Archiv des Project Build Verzeichnis anzulegen bevor dieses gelöscht wird. Der Aufruf von clean:clean wird den Lebenszyklus gar nicht ausführen, jedoch wird die Angabe der Phase clean auslösen, dass die drei Lebenszyklusphasen bis zur Phase clean ausgeführt werden. Example 10.1, “Auslösen eines Goals in der Phase pre-clean” (Beispiel 10.1: Auslösen eines Goals in der Phase pre-clean) zeigt beispielhaft eine Build Konfiguration welche das Goal antrun:run and die Phase pre-clean bindet um eine Warnung auszugeben, welche mitteilt, dass der Projektartefakt gelöscht werden soll. In diesem Beispiel wird das Goal antrun:run benutzt, um ein beliebiges Ant Kommando auszuführen um zu prüfen ob es den Artefakten gibt. Besteht der Artefakt und soll gelöscht werden, so wird dies auf der Konsole ausgegeben. Example 10.1. Auslösen eines Goals in der Phase pre-clean 238
  • Der Build Lebenszyklus <project> ... <build> <plugins>... <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>file-exists</id> <phase>pre-clean</phase> <goals> <goal>run</goal> </goals> <configuration> <tasks> <!-- adds the ant-contrib tasks (if/then/else used below) --> <taskdef resource="net/sf/antcontrib/antcontrib.properties" /> <available file="${project.build.directory}/${project.build.finalName}.${project.packagin property="file.exists" value="true" /> <if> <not> <isset property="file.exists" /> </not> <then> <echo>No ${project.build.finalName}.${project.packaging} to delete</echo> </then> <else> <echo>Deleting ${project.build.finalName}.${project.packaging}</echo> </else> </if> </tasks> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>ant-contrib</groupId> <artifactId>ant-contrib</artifactId> <version>1.0b2</version> </dependency> </dependencies> </plugin> </plugins> </build> </project> Das Ausführen von mvn clean auf einem Projekt mit der oben gegebenen Build 239
  • Der Build Lebenszyklus Konfiguration wird eine Ausgabe ähnlich der unten wiedergegebenen erzeugen. [INFO] Scanning for projects... [INFO] ---------------------------------------------------------------------- [INFO] Building Your Project [INFO] task-segment: [clean] [INFO] ---------------------------------------------------------------------- [INFO] [antrun:run {execution: file-exists}] [INFO] Executing tasks [echo] Deleting your-project-1.0-SNAPSHOT.jar [INFO] Executed tasks [INFO] [clean:clean] [INFO] Deleting directory ~/corp/your-project/target [INFO] Deleting directory ~/corp/your-project/target/classes [INFO] Deleting directory ~/corp/your-project/target/test-classes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1 second [INFO] Finished at: Wed Nov 08 11:46:26 CST 2006 [INFO] Final Memory: 2M/5M [INFO] ------------------------------------------------------------------------ Zusätzlich zur Möglichkeit Maven so zu konfigurieren, dass es ein Goal während der Phase pre-clean ausführt wird, kann man das Clean Plugin so anzupassen, dass es Dateien über denen des Zielverzeichnisses hinaus löscht. Sie können das Plugin so konfigurieren, dass es bestimmte in einer Aufzählung angegebene Dateien löscht. Das unten aufgeführte Beispiel konfiguriert clean so, dass es unter Einsatz der standard Ant Wildcards (* und **) alle .class Dateien in einem Verzeichnis mit dem Namen target-other/ löscht Example 10.2. Anpassen des Verhaltens des Clean Plugin <project> <modelVersion>4.0.0</modelVersion> ... <build> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <configuration> <filesets> <fileset> <directory>target-other</directory> <includes> <include>*.class</include> 240
  • Der Build Lebenszyklus </includes> </fileset> </filesets> </configuration> </plugin> </plugins> </build> </project> 10.1.2. Standard Lebenszyklus: default Die meisten Maven Benutzer sind mit dem Standardlebenszyklus vertraut. Dieser bildet das algemeine Modell des Build Prozesses einer Software Applikation ab. Die erste Phase ist validate und die letzte Phase deploy. Die Phasen des Maven Standard Lebenszyklus sind in Table 10.1, “Maven Lebenszyklusphasen” (Tabelle 10.1: Maven Lebenszyklusphasen) aufgeführt. Table 10.1. Maven Lebenszyklusphasen Lebenszyklus Phase Beschreibung validate Validieren, dass ein Projekt komplett ist, und dass alle notwendigen Informationen um einen Build zu erstellen verfügbar sind. generate-sources Generieren jeglicher Quellen welche benötigt werden um in ein Kompilat eingefügt zu werden. process-sources Verarbeiten der Quellen, zum Beispiel um Werte zu Filtern generate-resources Generieren der Ressourcen welche einem Paket beigefügt werden process-resources Kopieren und Verarbeiten der Ressourcen, Ablage in das Zielverzeichnis 241
  • Der Build Lebenszyklus Lebenszyklus Phase Beschreibung bereit um zu packetieren. compile Kompilieren der Quellen des Projektes. process-classes Nachverarbeitung der generierten Dateien der Kompilierung, zum Beispiel um Bytecode Modifikationen einzufügen. generate-test-sources Generieren von Test Quellcode welcher in das Kompilat eingefügt werden muss. process-test-sources Verarbeiten des Test Quellcodes, z.B. um Werte zu filtern generate-test-resources Erstellen der Test Ressourcen process-test-resources Verarbeiten und Kopieren der Ressourcen in das Test-Zielverzeichnis test-compile Kompilieren der Test Quelldateien in das Test Zielverzeichnis test Abarbeiten der Tests unter Einsatz eines geeigneten Unit Test Frameworks. Diese Tests sollten nicht voraussetzen, dass der Code Packetiert und Deployed wird. prepare-package Ausführen jeglicher notwendiger Schritte, um die Packetierung vorzubereiten, dies führt oftmals zu einer unpacketierten verarbeiteten Version des Pakets (Teil von Maven 2.1+) package Paketieren des Kompilates in ein verteilbares Format wie z.B. JAR, WAR oder EAR pre-integration-test Ausführen vorbereitender Schritte für Integrationstests. Dies kann die Erstellung 242
  • Der Build Lebenszyklus Lebenszyklus Phase Beschreibung des notwendigen Testumfeldes umfassen. integration-test Verarbeiten und deployen des Packages in eine Umgebung in welcher Integrationstests ausgeführt werden können. post-integration-test Ausführen von abschliessenden Schritten nachdem die Integrationstests ausgeführt wurden, dies kann auch die Bereinigung der Umgebung beinhalten. verify Jegliche Prüfungen ausführen um sicherzustellen dass das Package die erforderlichen Kriterien erfüllt. install Installieren des Packages in das lokale Repository um dort lokal als Abhängigkeit für andere Projekte bereitzustehen. deploy Kopiert das endgültige Paket in das ferne Repository um dieses anderen Entwicklern und Projekten bereitzustellen. Dieser Schritt ist gewöhnlich nur bei offiziellen Versionen von Bedeutung. 10.1.3. Lebenszyklus: site Maven kann mehr als nur Software Artefakte von Projekten erstellen. Es kann auch Projektdokumentation und Reports bezüglich dem Projekt oder eine Reihe von Projekten, erstellen. Projektdokumentation und Site Generierung haben einen eigenen Lebenszyklus zugeordnet, welcher vier Phasen kennt: 243
  • Der Build Lebenszyklus 1. pre-site 2. site 3. post-site 4. site-deploy Die an den Lebenszyklus site gebundenen Goals sind: 1. site - site:site 2. site-deploy -site:deploy Die Art der Packetierung ändert im allgemeinen diesen Lebenszyklus nicht, da Packetierungstypen sich primär mit der Artefakt Erzeugung befassen, nicht mit der Art des Sites der Generiert wird. Das Site Plugin started die Ausführung der Doxia Dokument Generierung und anderer Report generierender Plugins. Sie können einen Site eines Maven Projekts erstellen indem Sie das folgende Kommando absetzen: $ mvn site Die Generierung von Sites mittels Maven wird in Chapter 15, Site Generation (Kapitel 15: Site Generierung) weiter vertieft. 10.2. Package-spezifische Lebenszyklen Die genauen Goals welche an eine Phase gebunden werden sind bestimmt durch die Menge der Goals welche durch die projektspezifische Packetierung festgelegt wird. Ein Projekt welches vom Packetierungs Typ jar ist hat einen anderen Standardsatz Goals als das ein Projekt vom Typ war hat. Das Element packaging beeinflusst den Satz der Goals welche beim Build abgearbeitet werden müssen. Um Ihnen beispielhaft vorzuführen wie sehr packaging den Build beeinflusst, hier zwei Projekte: Eines mit packaging-Element pom und eines vom Typ jar. Das Projekt vom Typ pom wird das Goal site:attach-descriptor in der Phase 244
  • Der Build Lebenszyklus packaging abarbeiten, das Projekt vom Typ jar wird satt dessen das Goal jar:jar abarbeiten. The following sections describe the lifecycle for all built-in packaging types in Maven. Use these sections to find out what default goals are mapped to default lifecycle phases. Der folgende Abschnitt beschreibt den Lebenszyklus aller standardmässig verfügbaren Package Typen in Maven. Benutzen Sie diesen Abschnitt als Referenz um herauszufinden welche Goals standardmässig an welche Phase gebunden werden. 10.2.1. jar JAR ist der Standard Paketierungstyp, es ist der am häufigsten benutzte und die am meisten in Lebenszyklen angetroffene Konfiguration. Die Standard Goals des JAR Lebenszyklus sind in Table 10.2, “Standard Goals des JAR Packaging” (Tabelle 10.2: Standard Goals des JAR Packaging) aufgeführt. Table 10.2. Standard Goals des JAR Packaging Lebesnzyklus Phase Goal process-resources resources:resources compile compiler:compile process-test-resources resources:testResources test-compile compiler:testCompile test surefire:test package jar:jar install install:install deploy deploy:deploy 245
  • Der Build Lebenszyklus 10.2.2. pom POM ist der einfachste Paketierungstyp. Der generierte Artefakt ist sich selbst, kein JAR, WAR oder EAR. Es gibt keinen Code zu Testen oder Kompilieren, und es gibt auch keine Ressourcen zu verarbeiten. Die Standard Goals des POM Lebenszyklus sind in Table 10.3, “Standard Goals des POM Packaging” (Tabelle 10.3: Standard Goals des POM Packaging) aufgeführt: Table 10.3. Standard Goals des POM Packaging Lebenszyklus Phase Goal package site:attach-descriptor install install:install deploy deploy:deploy 10.2.3. plugin Dieser Paketierungstyp ist ähnlich dem der JAR Packetierung mit drei Unterschieden: plugin:descriptor, plugin:addPluginArtifactMetadata, und plugin:updateRegistry. Diese Goals generieren eine Descriptor Datei und führen gewisse Änderungen an den Repository Daten aus. Die Standard Goals des plugin Lebenszyklus sind in Table 10.4, “Standard Goals des plugin Packaging” (Tabelle 10.4: Standard Goals des plugin Packaging) aufgeführt: Table 10.4. Standard Goals des plugin Packaging Lebenszyklus Phase Goal generate-resources plugin:descriptor process-resources resources:resources compile compiler:compile process-test-resources resources:testResources 246
  • Der Build Lebenszyklus Lebenszyklus Phase Goal test-compile compiler:testCompile test surefire:test package jar:jar, plugin:addPluginArtifactMetadata install install:install, plugin:updateRegistry deploy deploy:deploy 10.2.4. ejb Enterprise Java Beans (EJBs) sind eine häufing angewandte Technologie für die Modell getriebene Entwicklung von Enterprise Java Applikationen. Maven unterstützt EJB 2 und EJB 3, obschon man das Plugin speziell konfigurieren muss um EJB 3 zu unterstützen. Standardmässig wird EJB 2.1 umgesetzt, wobei das Plugin nach bestimmten EJB Konfigurationsdateien sucht. Die Standard Goals des ejb Lebenszyklus sind in Table 10.5, “Standard Goals des ejb Packaging” (Tabelle 10.5: Standard Goals des ejb Packaging) aufgeführt: Table 10.5. Standard Goals des ejb Packaging Lebenszyklus Phase Goal process-resources resources:resources compile compiler:compile process-test-resources resources:testResources test-compile compiler:testCompile test surefire:test package ejb:ejb install install:install deploy deploy:deploy 247
  • Der Build Lebenszyklus 10.2.5. war Der Paketeierungstyp war ist ähnlich dem des Types jar oder ejb. Die Ausnahme hierbei ist das Goal war:war. Bitte beachten Sie, dass das war Plugin eine web.xml Konfigurationsdatei im Verzeichnis src/main/webapp/WEB-INF voraussetzt. Die Standard Goals des war Lebenszyklus sind in Table 10.6, “Standard Goals des war Packaging” (Tabelle 10.6: Standard Goals des war Packaging) aufgeführt: Table 10.6. Standard Goals des war Packaging Lebenszyklus Phase Goal process-resources resources:resources compile compiler:compile process-test-resources resources:testResources test-compile compiler:testCompile test surefire:test package war:war install install:install deploy deploy:deploy 10.2.6. ear EARs sind wohl das einfachste Java EE Konstrukt, bestehen Sie ja lediglich aus der Deployment Beschreibungsdatei (Descriptor) application.xml, Ressourcen und einigen Modulen. Das ear Plugin hat besitzt ein Goal mit dem Namen generate-application.xml welche die applicaiton.xml Datei auf der Basis des POM generiert. Die Standard Goals des ear Lebenszyklus sind in Table 10.7, “Standard Goals des POM Packaging” (Tabelle 10.7: Standard Goals des ear Packaging) aufgeführt: 248
  • Der Build Lebenszyklus Table 10.7. Standard Goals des POM Packaging Lebenszyklus Phase Goal generate-resources ear:generate-application-xml process-resources resources:resources package ear:ear install install:install deploy deploy:deploy 10.2.7. Andere Packetierungs Typen Dies ist keine abschliessende Liste aller für Maven verfügbarer Paketierungstypen. Es bestehen eine ganze Anzahl Paketierungs Formate verfügbar mittels externer Projekte und Plugins: der Typ native archive (NAR), die Typen SWF und SWC für Projekte welche Adobe Flash und Flex Inhalte erzeugen und viele andere. Sie können ebenfalls eigene Benutzer spezifische Paketierungs Typen definieren und den standardmässigen Lebenszyklus Ihren spezifischen Bedürfnissen anpassen. Um einen derart angepassten Paketierungstypen einzusetzen braucht es zwei Dinge: ein Plugin welches den angepassten Lebenszyklus definiert und ein Repository welches dieses Plugin beinhaltet. Manche angepassten Paketierungs Typen sind in Plugins definiert und über das zentrale Repository verfügbar. Hier ein Beispiel eines Projekts welches sich auf das Israfil Flex Plugin stützt und einen benutzerdefinierten Paketierungstyp SWF benutzt um die Ausgabe aus Adobe Flex Quellen zu erstellen. Example 10.3. Benutzerdefinierter Packetierungs -Type für Adobe Flex (SWF) <project> ... <packaging>swf</packaging> ... 249
  • Der Build Lebenszyklus <build> <plugins> <plugin> <groupId>net.israfil.mojo</groupId> <artifactId>maven-flex2-plugin</artifactId> <version>1.4-SNAPSHOT</version> <extensions>true</extensions> <configuration> <debug>true</debug> <flexHome>${flex.home}</flexHome> <useNetwork>true</useNetwork> <main>org/sonatype/mavenbook/Main.mxml</main> </configuration> </plugin> </plugins> </build> ... </project> In Section 17.6, “Plugins and the Maven Lifecycle” (Abschnitt 17.6 Plugins und der Maven Lebenszyklus) führen wir aus, wie Sie Ihren eigenen Paketierungstypen mit angepasstem Lebenszyklus erstellen. Das Beispiel sollte Ihnen aufzeigen was notwendig ist, um einen angepassten Paketierungstypen einzusetzen. Das einzige was Sie tatsächlich dazu beitragen müssen ist, das Plugin welches den angepassten Paketierungstyp mitbringt zu referenzieren. Das Israfil FLex Plugin ist ein Drittpartei Maven Plugin welches von Google Code bereitgehalten wird. Für weitere Informationen bezüglich des Plugins, oder den Schritten die notwendig sind um Adobe Flex zu Kompilieren, wenden Sie sich bitte an http://code.google.com/code.google.com/p/israfil-mojo. Das Plugin liefert die folgenden Lebenszyklen des SWF Packetierungs Typ. Table 10.8. Standard Lebenszyklus für SWF Packetierung Lebenszyklus Phase Goal compile flex2:compile-swc install install deploy deploy 250
  • Der Build Lebenszyklus 10.3. Gebräuchliche Lebenszyklus Goals Viele der Paketierungs-Lebenszyklen haben ähnliche Goals. Wenn Sie die Goals des war und des jar Lebenszyklus vergleichen, so werden Sie feststellen, dass diese sich lediglich in der Phase package unterscheiden. Die Phase package des Lebenszyklus war ruft war:war auf, die des Lebenszyklus jar ruft jar:jar auf. Die meisten der Lebenszyklen mit welchen Sie in Kontakt kommen werden teilen sich einige gemeinsame Lebenszyklus Goals um Ressourcen zu Managen, Tests Abzuarbeiten sowie Quellen zu Kompilieren. In diesem Abschnitt wollen wir nun einige dieser gemeinsamen Lebenszyklus Goals genauer betrachten. 10.3.1. Ressourcen Verarbeiten Die meisten Lebenszyklen binden das Goal resources:resources an die Phase process-resources. Diese Phase „verarbeitet“ Ressourcen und kopiert diese in das Zielverzeichnis. So Sie dieses nicht angepasst haben, gelten die im Super POM festegelegten Standardverzeichnisse, das heisst, Maven kopiert die Dateien von ${basedir}/src/main/resources nach ${project.build.outputDirectory}. Zusätzlich zu der reinen Kopieraktion kann Maven auch die Ressourcen filtern, d.h. Sie können gewisse Token in den Dateien ersetzen. Genau wie Variablen innerhalb der POM Datei mittels ${ … } markiert sind, so können Sie die gleiche Syntax einsetzen um Variablen innerhalb von Ressourcen zu referenzieren. Im Zusammenspiel mit Build-Profilen kann diese Eigenschaft dazu benutzt werden, um Builds für unterschiedliche Zielplattformen zu erstellen. Dies trifft man häufig in Umgebungen an, welche für das gleiche Projekt unterschiedliche Builds für das Development, Test, Integrationstest und Produktion erstellen müssen. Bezüglich weiter Informationen zu Build-Profilen verweisen wir auf Chapter 11, Build Profiles (Kapitel 11: Build Profile). Um Ressource Filterung zu veranschaulichen, stellen Sie sich vor, Sie habe ein Projekt mit einer XML-Datei unter /src/main/resources/META-INF/services.xml. Hierbei möchten Sie eine Konfigurationseinstellung in eine Property-Datei auslagern. In anderen Worten, Sie möchten zum Beispiel die Werte der JDBC URL, Benutzername und Passwort 251
  • Der Build Lebenszyklus der Datenbank nicht direkt in der service.xml Datei ablegen. Statt dessen möchten Sie eine Property Datei mit den variablen Werten Ihres Programms anlegen. Das ermöglicht Ihnen, alle Konfigurationsparameter in einer Datei zusammenzufassen, um diese an einem Ort zu ändern sobald Sie von einer Umgebung in eine andere wechseln. Sehen Sie sich also zuallererts den Inhalt der entsprechenden Datei services.xml unter /src/main/resources/META-INF an: Example 10.4. Einsatz von Properties in den Projektressourcen <service> <!-- This URL was set by project version ${project.version} --> <url>${jdbc.url}</url> <user>${jdbc.username}</user> <password>${jdbc.password}</password> </service> Die in der XML Datei zum Einsatz kommende Syntax ist die selbe welche bereits in der POM Datei zum Einsatz kommt. Die erste zum Einsatz kommende Variable ist sogar eine Maven-implizite Variable welche vom POM bereitgestellt wird. Die Variable projekt erlaubt den Zugriff auf die Werte des POM. Die folgenden drei Referenzen sind jdbc.url, jdbc.username und jdbc.passwort. Diese benutzerdefinierten Variablen wurden in der Property Datei unter src/main/filters/default.properties definiert. Example 10.5. Datei default.properties unter src/main/filters jdbc.url=jdbc:hsqldb:mem:mydb jdbc.username=sa jdbc.password= Um Ressourcenfilterung der Datei default.properties einzustellen, müssen innerhalb des POM zwei Dinge gesetzt werden: eine Liste der properties-Dateien innerhalb des Elements filters der Buildkonfiguration, sowie eine Maven Einstellung dass das Ressourcenverzeichnis der Filterung unterliegt. Standardmässig ist dieses Verhalten abgestellt und Ressourcen werden direkt in das Ausgabeverzeichnis kopiert, oder Maven übergeht diesen Schritt ganz. Diese 252
  • Der Build Lebenszyklus Einstellung ist standardmässig so gewählt, damit es nicht vorkommt, dass ohne ersichtlichen Grund alle bestehenden ${...}-Referenzen ersetzt werden, welche Sie gar nicht ersetzen wollten. Example 10.6. Ressourcen Filtern (Ersetzen von Properties) <build> <filters> <filter>src/main/filters/default.properties</filter> </filters> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> Wie mit allen Verzeichnissen unter Maven, ist es nicht Bedingung, das das Ressourcenverzeichnis unbedingt unter src/main/ressources liegt. Das ist lediglich der entsprechende Standardpfad wie er im Super POM festgelegt ist. Ebenfalls ist es gut zu wissen, dass Sie nicht gezwungen sind, alle Ihre Ressourcen in ein einzigen Verzeichniss zu konsolidieren. Sie können Ressourcen auch in getrennten Verzeichnissen unter src/main ablegen. Nehmen Sie einmal an, Sie arbeiten an einem Projekt mit hunderten von XML-Dokumenten und hunderten von Bildern. Anstatt nun alle diese Dateien unter dem Verzeichnis src/main/ressources gemischt abzulegen, ist es möglich, zwei Verzeichnisse src/main/xml und src/main/images anzulegen um die Inhalte dort abzulegen. Um weitere Verzeichnisse Ihrer Liste von Ressourceverzeichnissen zuzufügen, müssen Sie folgendes Element resource Ihrer Buildkonfiguration zufügen: Example 10.7. Festlegen zusätzlicher Ressource Verzeichnisse <build> ... <resources> <resource> <directory>src/main/resources</directory> </resource> <resource> 253
  • Der Build Lebenszyklus <directory>src/main/xml</directory> </resource> <resource> <directory>src/main/images</directory> </resource> </resources> ... </build> Sollten Sie ein Projekt erstellen, welches eine Befehlszeilenapplikation erzeugt, so finden Sie sich oftmals in einer Situation wieder, in welcher Sie einfache Shell Skripte erstellen, welche ein Jar-Dateien des Builds referenzieren. Sollten Sie dann das Assembly Plugin benutzen um ein Distributionspaket in Form eines ZIP- oder TAR-Archivs zu erstellen, so macht es Sinn, alle ihre Skripte in einem Verzeichniss wie etwa src/main/command zusammenzufassen. Im folgenden Ausschnitt einer POM Ressourcenkonfiguration können Sie sehen, wie wir Ressourcenfilterung und eine Referenz auf eine Projektvariable benutzen können, um den endgültigen Ausgabenamen des jar-Archives zu definieren. Weitere Informationen bezüglich des Maven Assembly Plugins finden Sie in Chapter 12, Maven Assemblies (Kapitel 12: Maven Assemblies). Example 10.8. Filterung von Skript Ressourcen <build> <groupId>org.sonatype.mavenbook</groupId> <artifactId>simple-cmd</artifactId> <version>2.3.1</version> ... <resources> <resource> <filtering>true</filtering> <directory>${basedir}/src/main/command</directory> <includes> <include>run.bat</include> <include>run.sh</include> </includes> <targetPath>${basedir}</targetPath> </resource> <resource> <directory>${basedir}/src/main/resources</directory> </resource> </resources> ... </build> 254
  • Der Build Lebenszyklus Der Aufruf von mvn process-resources dieses Projektes erzeugt zwei Dateien, run.sh sowie run.bat, beide unter ${basedir}. Wir haben innerhalb eines Elements resource diese zwei Dateien herausgepickt, Filterung eingestellt, und den Zielpfad (targetPath) auf ${basedir} gesetzt. In einem zweiten Elememt Resource haben wir definiert, dass die Ressourcen des Standard Ressourcenpfades in das Standardausgabeverzeichnis kopiert werden ohne diese zu filtern. Example 10.8, “Filterung von Skript Ressourcen” (Beispiel 10.8: Filterung von Skript Ressourcen) zeigt beispielhaft auf, wie Sie zwei Resourceverzeichnisse bestimmen und diese mit unterschiedlichen Filterkriterien sowie Zielverzeichnissen versehen. Das Projekt des Example 10.8, “Filterung von Skript Ressourcen” (Beispiels 10.8: Filterung von Skript Ressourcen) würde eine Datei run.bat unter src/main/command mit folgendem Inhalt enthalten: @echo off java -jar ${project.build.finalName}.jar %* Nach der Ausführung von mvn process-resources, erscheint eine Datei run.bat im Verzeichnis ${basedir} mit folgendem Inahlt: @echo off java -jar simple-cmd-2.3.1.jar %* Die Möglichkeit unterschiedliche Filter für speziefische Untergruppen von Ressourcen zu setzen, ist ein weiterere Grund weshalb Projekte mit vielen verschiedenen Arten von Artefakten es oftmals als hilfreich erachten, diese in verschiedenen Verziechnissen abzulegen. Die Alternative zum Ablegen verschiedener Ressourcetypen in unterschiedlichen Verzichnissen ist der Einsatz von komplexeren Sätzen von Ein- und Ausschlusskriterien, um die entsprechenden Ressource Dateien welche einem Muster genügen, zu spezifizieren. 10.3.2. compile Die allermeisten Lebenszyklen binden das Goal compile des Compiler Plugin an die Phase compile. Diese Phase ruft compile:compile auf, welches konfiguriert ist, alle Quellen zu verarbeiten und den resultierenden Bytecode im Build Ausgabeverzeichniss abzulegen. Sollten Sie die Standardeinstellungen des 255
  • Der Build Lebenszyklus SuperPOM nicht angepasst haben, so wird compile:compile alle Quellen unter src/main/java verarbeiten und das Ergebnis unter /target/classes ablegen. Das Compile Plugin ruft javac™ auf, und hat die Standardeinstellungen 1.3 und 1.1, in anderen Worten, das Compiler Plugin geht davon aus, dass Sie Java 1.3 konformen Code erzeugen und diesen an eine Java Virtual Machine 1.1 addressieren. Sollten Sie diese Einstellungen verändern wollen, so müssen sie die Ziel und Quellkonfiguration ihres Projektes dem Compiler Plugin mit auf den Weg geben. Sie tun dies mittels einem Eintrag in der POM Datei wie dies in Example 10.9, “Einstellen der Quell- und Zielkonfiguration des Compiler Plugins” (Beispiel 10.9: Einstellen der Quell- und Zielkonfiguration des Kompiler Plugins). Example 10.9. Einstellen der Quell- und Zielkonfiguration des Compiler Plugins <project> ... <build> ... <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> ... </build> ... </project> Beachten Sie, dass wir das Compiler Plugin konfigurieren, und nicht das spezifische Goal compile:compile. Um nur das spezifische Goal compile:compile zu konfigurieren, müssten wir das Element configuration unterhalb eines Elements execution des Goals compile:compile spezifizieren. Wir haben aber das Quell-und Zielverzeichnis des Compile Plugins konfiguriert, denn wir wollten nicht nur dieses eine Goal konfigurieren. Das Compiler Plugin kommt ebenfalls zum Einsatz, wenn Maven die Testquellen mittels dem Goal 256
  • Der Build Lebenszyklus compile:testCompile verarbeitet. Die Definition des Plugin erlaubt uns die Konfiguration einmal auf der Pluginebene für alle Goals vorzunehmen. Sollten Sie den Ort Ihrer Quelldateien umdefinieren müssen, so können Sie dies durch die Veränderung der Build Konfiguration erreichen. Sollten Sie zum Beispiel ihre Quellen unter src/java statt src/main/java ablegen, und sollten das Ergebnis unter classes anstatt target/classes ablegen wollen, so können Sie jederzeit die Standarwerte sourceDirectory (Quellverzeichnis) des SuperPOM übersteuern. Example 10.10. Übersteuern des Standard Quellverzeichnisses <build> ... <sourceDirectory>src/java</sourceDirectory> <outputDirectory>classes</outputDirectory> ... </build> Warning Obschon es Gründe geben mag die dafür sprechen, Maven ihren Wünschen bezüglich Verzeichnisstruktur anzupassen, so können wir hier nicht stark genug betonen, dass es besser ist Ihre eingene Struktur zugunsten der Maven Standardverzeichnisstruktur aufzugeben. Dies ist keinesfalls darin begründet, dass wir Sie von dieser Struktur bekehren wollen, sondern es ist einfacher für andere Entwickler, ihre Projektstruktur zu verstehen, sollte diese den grundlegenden Konventionen folgen. Vergessen Sie schon den Ansatz! - Tun Sie sich diesen Gefallen. 10.3.3. Verarbeiten von Test Ressourcen Die Phase process-test-resources lässt sich kaum von der Phase process-ressources unterscheiden. Es gibt einige triviale Unterschiede des POM, alles andere ist gleich. Sie können Test Ressourcen filtern wie die anderen 257
  • Der Build Lebenszyklus Ressourcen auch. Der standardmässige Ort der Testressourcen ist im Super POM mit src/test/resources gesetzt und das standardmässige Ausgabeverzeichnis von Testressourcen auf target/test-classes wie dies unter ${project.build.testOutputDirectory} definiert ist. 10.3.4. Kompilieren der Test Klassen (testCompile) Die Phase test-compile ist beinahe identisch mit der Phase compile. Der einzige Unterschied ist, dass test-compile das Goal compile:testCompile anzieht, um Testquellen vom Quellverzeichnis anzuziehen und die Ausgabe im Test-Ausgabeverzeichnis abzulegen. Sollten Sie die Standardverzeichnisse des SuperPOM nicht übersteuert haben, so wird compile:Test die Sourcen aus dem Verzeichnis src/test/java in das Verzeichnis target/test-classes kompilieren. Wie mit den Quellverzeichnissen gilt, sollten Sie den Ort der Test Verzeichnisse ändern wollen, so können Sie dies tun indem Sie testSourceDirectory und testOutputDirectory anpassen. Sollten Sie Testquellen unter src-test/ anstatt src/test/java ablegen und den Bytecode unter classes-test/ anstatt target/test-classes ablegen wollen, so wüden Sie auf die folgende Konfiguration vornehmen. Example 10.11. Übersteuern des Ortes der Testquellen und -ausgabe <build> ... <testSourceDirectory>src-test</testSourceDirectory> <testOutputDirectory>classes-test</testOutputDirectory> ... </build> 10.3.5. test Die meisten Lebenszyklen binden das Goal test des Surefire Plugin an die Phase test. Das Surefire Plugin ist das UnitTest Plugin von Maven. Das Standardverhalten des Surefire Plugin ist, nach allen Klassen welche im Test 258
  • Der Build Lebenszyklus Quellenverzeichnis auf *Test enden zu suchen, um diese als JUnit Tests auszuführen. Das Surefire Plugin kann auch konfiguriert werden die Tests als TestNG Unit Test abzuarbeiten. Nach der Ausführung von mvn test sollten Sie feststellen können, das das Surefire Plugin eine Anzahl Reports unter target/surefire-reports ablegt. Dieses Reportverzeichnis beinhaltet zwei Dateien für jeden ausgeführten Test: eine XML Datei welche Informationen bezüglich des Tests enthält und eine Textdatei welche die Ausgabe des UnitTest enthält. Sollte ein Problem während der Phase test auftreten und ein Unit Test nicht bestehen, so können Sie die Ausgabe von Maven zusammen mit dieser Datei benutzen, um den Grund des Abbruchs zu finden. Dieses Verzeichnis surefire-reports/ kommt ebenfalls zum Einsatz während der Site Generierung wobei eine einfach zu lesende Zusammenfassung der Unit Tests des Projektes erstellt wird. Sollten Sie an einem Projekt arbeiten, bei welchem einige abbrechende Unit Test bestehen, Sie möchten aber dennoch eine Ausgabe erzeugen, so müssen Sie das Surefire Plugin so konfigurieren, dass dieses die Ausführung beim Auftreten eines Fehlers nicht abbricht. Das Standardverhalten ist, dass der Build beim ersten auftreten eines Unit Test Fehlers abbricht. Um dieses Verhalten zu ändern, müssen Sie das Property testFailureIgnore des SureFire Plugin auf true setzen. Example 10.12. Konfiguration des Surefire Plugins um Testabbrüche zu ignorieren <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> ... </plugins> </build> Sollten Sie die Tests komplett übergehen wollen, so können Sie dies durch den 259
  • Der Build Lebenszyklus folgenden Aufruf erreichen: $ mvn install -Dmaven.test.skip=true Die Variable maven.test.skip beeinflusst beides, den Compiler und das Surefire Plugin. Geben Sie maven.test.skip=true an, so weisen Sie Maven an die Tests insgesamt zu ignorieren. 10.3.6. install Das Goal install des Install Plugin ist immer an die Lebenszyklusphase install gebunden. Das Goal install:install installiert den Artefakt in das lokale Repository. Sollten Sie ein Projekt mit der groupId von org.sonatype.mavenbook einen Artefakten mit artifactId von simple-test und einer version von 1.0.2 haben, so wird das Goal install:install das JAR von target/simple-test-1.0.2.jar in ~/.m2/repository/org/sonatype/mavenbook/simple-test/1.0.2/simpletest-1.0.2.jar kopieren. Ist das Projekt ein Projekt vom Typ POM Pakageing, so wird das POM in das lokale Repository eingestellt. 10.3.7. deploy Das Goal deploy des Plugins Deploy wird gewöhnlich an die Lebenszyklusphase deploy gebunden. Diese Phase wird benutzt, um einen Artefakten in ein entferntes Repository einzustellen. Gewöhnlich geschiet dies um ein entferntes Repository bei einer Freigabe aktuell zu halten. Der Deployment Vorgang kann eine einfache Kopieraktion einer Datei darstellen, oder aber auch eine Übertragung einer Datei mittles SCP und einem publicKey. Deployment Einstellungen beinhalten gewöhnlich Zugangsdaten zu einem entfernten Repository, aus welchem Grund Deployment Einstellungen gewöhnlich nicht in der pom.xml Datei gehalten werden. Statt dessen gehen diese gewöhnlich in die benutzerspezifische Datei ~/m2/settings.xml. Zu diesem Zeitpunkt reicht es zu wissen, das das Goal deploy:deploy an die Phase deploy gebunden ist und sich der Übertragung der Artefakten zu einem veröffentlichten Repository und dem damit verbundenen Update der Repository Informationen welche von einem solchen Vorgang 260
  • Der Build Lebenszyklus betroffen sein könnten, annimmt. 261
  • Chapter 11. Build Profiles 11.1. What Are They For? Profiles allow for the ability to customize a particular build for a particular environment; profiles enable portability between different build environments. What do we mean by different build environments? Two example build environments are production and development. When you are working in a development environment, your system might be configured to read from a development database instance running on your local machine while in production, your system is configured to read from the production database. Maven allows you to define any number of build environments (build profiles) which can override any of the settings in the pom.xml. You could configure your application to read from your local, development instance of a database in your "development" profile, and you can configure it to read from the production database in the "production" profile. Profiles can also be activated by the environment and platform, you can customize a build to run differently depending the Operating System or the installed JDK version. Before we talk about using and configuring Maven profiles, we need to define the concept of Build Portability. 11.1.1. What is Build Portability A build's "portability" is a measure of how easy it is to take a particular project and build it in different environments. A build which works without any custom configuration or customization of properties files is more portable than a build which requires a great deal of work to build from scratch. The most portable projects tend to be widely used open source projects like Apache Commons of Apache Velocity which ship with Maven builds which require little or no customization. Put simply, the most portable project builds tend to just work, out of the box, and the least portable builds require you to jump through hoops and configure platform specific paths to locate build tools. Before we show you how to achieve build portability, let's survey the different kinds of portability we are 262
  • Build Profiles talking about. 11.1.1.1. Non-Portable Builds The lack of portability is exactly what all build tools are made to prevent - however, any tool can be configured to be non-portable (even Maven). A non-portable project is buildable only under a specific set of circumstances and criteria (e.g., your local machine). Unless you are working by yourself and you have no plans on ever deploying your application to another machine, it is best to avoid non-portability entirely. A non-portable build only runs on a single machine, it is a "one-off". Maven is designed to discourage non-portable builds by offering the ability to customize builds using profiles. When a new developer gets the source for a non-portable project, they will not be able to build the project without rewriting large portions of a build script. 11.1.1.2. Environment Portability A build exhibits environment portability if it has a mechanism for customizing behavior and configuration when targeting different environments. A project that contains a reference to a test database in a test environment, for example, and a production database in a production environment, is environmentally portable. It is likely that this build has a different set of properties for each environment. When you move to a different environment, one that is not defined and has no profile created for it, the project will not work. Hence, it is only portable between defined environments. When a new developer gets the source for an environmentally portable project, they will have to run the build within a defined environment or they will have to create a custom environment to successfully build the project. 11.1.1.3. Organizational (In-House) Portability The center of this level of portability is a project's requirement that only a select few may access internal resources such as source control or an internally-maintained Maven repository. A project at a large corporation may depend on a database available only to in-house developers, or an open source 263
  • Build Profiles project might require a specific level of credentials to publish a web site and deploy the products of a build to a public repository. If you attempt to build an in-house project from scratch outside of the in-house network (for example, outside of a corporate firewall), the build will fail. It may fail because certain required custom plugins are unavailable, or project dependencies cannot be found because you don't have the appropriate credentials to retrieve dependencies from a custom remote repository. Such a project is portable only across environments in a single organization. 11.1.1.4. Wide (Universal) Portability Anyone may download a widely portable project's source, compile, and install it without customizing a build for a specific environment. This is the highest level of portability; anything less requires extra work for those who wish to build your project. This level of portability is especially important for open source projects, which depend on the ability for would-be contributors to easily download and build from source. Any developer could download the source for a widely portable project. 11.1.2. Selecting an Appropriate Level of Portability Clearly, you'll want to avoid creating the worst-case scenario: the non-portable build. You may have had the misfortune to work or study at an organization that had critical applications with non-portable builds. In such organizations, you cannot deploy an application without the help of a specific individual on a specific machine. In such an organization, it is also very difficult to introduce new project dependencies or changes without coordinating the change with the single person who maintains such a non-portable build. Non-portable builds tend to grow in highly political environments when one individual or group needs to exert control over how and when a project is built and deployed. "How do we build the system? Oh, we've got to call Jack and ask him to build it for us, no one else deploys to production." That is a dangerous situation which is more common that you would think. If you work for this organization, Maven and Maven profiles provide a way out of this mess. 264
  • Build Profiles On the opposite end of the portability spectrum are widely portable builds. Widely portable builds are generally the most difficult build systems to attain. These builds restrict your dependencies to those projects and tools that may be freely distributed and are publicly available. Many commercial software packages might be excluded from the most-portable builds because they cannot be downloaded before you have accepted a certain license. Wide portability also restricts dependencies to those pieces of software that may be distributed as Maven artifacts. For example, if you depend upon Oracle JDBC drivers, your users will have to download and install them manually; this is not widely portable as you will have to distribute a set of environment setup instructions for people interested in building your application. On the other hand, you could use a JDBC driver which is available from the public Maven repositories like MySQL or HSQLDB. As stated previously, open source projects benefit from having the most widely portable build possible. Widely portable builds reduce the inefficiencies associated with contributing to a project. In an open source project (such as Maven) there are two distinct groups: end-users and developers. When an end-user uses a project like Maven and decides to contribute a patch to Maven, they have to make the transition from using the output of a build to running a build. They have to first become a developer, and if it is difficult to learn how to build a project, this end-user has a disincentive to take the time to contribute to a project. In a widely portable project, an end-user doesn't have to follow a set or arcane build instructions to start becoming a developer, they can download the source, modify the source, build, and submit a contribution without asking someone to help them set up a build environment. When the cost of contributing source back to an open-source project is lower, you'll see an increase in source code contributions, especially casual contributions which can make the difference between a project's success and a project's failure. One side-effect of Maven's adoption across a wide group of open source projects is that it has made it easier for developers to contribute code to various open source projects. 11.2. Portability through Maven Profiles A profile in Maven is an alternative set of configuration values which set or 265
  • Build Profiles override default values. Using a profile, you can customize a build for different environments. Profiles are configured in the pom.xml and are given an identifier. Then you can run Maven with a command-line flag that tells Maven to execute goals in a specific profile. The following pom.xml uses a production profile to override the default settings of the Compiler plugin. Example 11.1. Using a Maven Profile to Override Production Compiler Settings <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>simple</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>simple</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <profiles># <profile> <id>production</id># <build># <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <debug>false</debug># <optimize>true</optimize> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project> 266
  • Build Profiles In this example, we've added a profile named production that overrides the default configuration of the Maven Compiler plugin, let's examine the syntax of this profile in detail. ‚ The profiles element is in the pom.xml, it contains one or more profile elements. Since profiles override the default settings in a pom.xml, the profiles element is usually listed as the last element in a pom.xml. ƒ Each profile has to have an id element. This id element contains the name which is used to invoke this profile from the command-line. A profile is invoked by passing the -P<profile_id> command-line argument to Maven. „ A profile element can contain many of the elements which can appear under the project element of a POM XML Document. In this example, we're overriding the behavior of the Compiler plugin and we have to override the plugin configuration which is normally enclosed in a build and a plugins element. … We're overriding the configuration of the Maven Compiler plugin. We're making sure that the bytecode produced by the production profile doesn't contain debug information and that the bytecode has gone through the compiler's optimization routines. To execute mvn install under the production profile, you need to pass the -Pproduction argument on the command-line. To verify that the production profile overrides the default Compiler plugin configuration, execute Maven with debug output enabled (-X) as follows: ~/examples/profile $ mvn clean install -Pproduction -X ... (omitting debugging output) ... [DEBUG] Configuring mojo 'o.a.m.plugins:maven-compiler-plugin:2.0.2:testCompile' [DEBUG] (f) basedir = ~examplesprofile [DEBUG] (f) buildDirectory = ~examplesprofiletarget ... [DEBUG] (f) compilerId = javac [DEBUG] (f) debug = false [DEBUG] (f) failOnError = true [DEBUG] (f) fork = false [DEBUG] (f) optimize = true [DEBUG] (f) outputDirectory = ~svnwsonatypeexamplesprofiletargettest-classes [DEBUG] (f) outputFileName = simple-1.0-SNAPSHOT [DEBUG] (f) showDeprecation = false [DEBUG] (f) showWarnings = false [DEBUG] (f) staleMillis = 0 267
  • Build Profiles [DEBUG] (f) verbose = false [DEBUG] -- end configuration -- ... (omitting debugging output) ... This excerpt from the debug output of Maven shows the configuration of the Compiler plugin under the production profile. As shown in the output, debug is set to false and optimize is set to true. 11.2.1. Overriding a Project Object Model While the previous example showed you how to override the default configuration properties of a single Maven plugin, you still don't know exactly what a Maven profile is allowed to override. The short-answer to that question is that a Maven profile can override almost everything that you would have in a pom.xml. The Maven POM contains an element under project called profiles containing a project's alternate configurations, and under this element are profile elements which define each profile. Each profile must have an id, and other than that, it can contain almost any of the elements one would expect to see under project. The following XML document shows all of the elements, a profile is allowed to override. Example 11.2. Elements Allowed in a Profile <project> <profiles> <profile> <build> <defaultGoal>...</defaultGoal> <finalName>...</finalName> <resources>...</resources> <testResources>...</testResources> <plugins>...</plugins> </build> <reporting>...</reporting> <modules>...</modules> <dependencies>...</dependencies> <dependencyManagement>...</dependencyManagement> <distributionManagement>...</distributionManagement> <repositories>...</repositories> <pluginRepositories>...</pluginRepositories> <properties>...</properties> </profile> 268
  • Build Profiles </profiles> </project> A profile can override an element shown with ellipses. A profile can override the final name of a project's artifact in a profile, the dependencies, and the behavior of a project's build via plugin configuration. A profile can also override the configuration of distribution settings depending on the profile; for example, if you need to publish an artifact to a staging server in a staging profile, you would create a staging profile which overrides the distributionManagement element in a profile. 11.3. Profile Activation In the previous section we showed you how to create a profile that overrides default behavior for a specific target environment. In the previous build the default build was designed for development and the production profile exists to provide configuration for a production environment. What happens when you need to provide customizations based on variables like operating systems or JDK version? Maven provides a way to "activate" a profile for different environmental parameters, this is called profile activation. Take the following example, assume that we have a Java library that has a specific feature only available in the Java 6 release: the Scripting Engine as defined in JSR-223. You've separated the portion of the library that deals with the scripting library into a separate Maven project, and you want people running Java 5 to be able to build the project without attempting to build the Java 6 specific library extension. You can do this by using a Maven profile that adds the script extension module to the build only when the build is running within a Java 6 JDK. First, let's take a look at our project's directory layout and how we want developers to build the system. When someone runs mvn install with a Java 6 JDK, you want the build to include the simple-script project's build, when they are running in Java 5, you would like to skip the simple-script project build. If you failed to skip the simple-script 269
  • Build Profiles project build in Java 5, your build would fail because Java 5 does not have the ScriptEngine on the classpath. Let's take a look at the library project's pom.xml: Example 11.3. Dynamic Inclusion of Submodules Using Profile Activation <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>simple</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>simple</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <profiles> <profile> <id>jdk16</id> <activation># <jdk>1.6</jdk> </activation> <modules># <module>simple-script</module> </modules> </profile> </profiles> </project> If you run mvn install under Java 1.6, you will see Maven descending into the simple-script subdirectory to build the simple-script project. If you are running mvn install in Java 1.5, the build will not try to build the simple-script submodule. Exploring this activation configuration in more detail: ‚ The activation element lists the conditions for profile activation. In this example, we've specified that this profile will be activated by Java versions that begin with "1.6". This would include "1.6.0_03", "1.6.0_02", or any 270
  • Build Profiles other string that began with "1.6". Activation parameters are not limited to Java version, for a full list of activation parameters, see Activation Configuration. ƒ In this profile we are adding the module simple-script. Adding this module will cause Maven to look in the simple-script/ subdirectory for a pom.xml. 11.3.1. Activation Configuration Activations can contain one of more selectors including JDK versions, Operating System parameters, files, and properties. A profile is activated when all activation criteria has been satisfied. For example, a profile could list an Operating System family of Windows, and a JDK version of 1.4, this profile will only be activated when the build is executed on a Windows machine running Java 1.4. If the profile is active then all elements override the corresponding project-level elements as if the profile were included with the -P command-line argument. The following example, lists a profile which is activated by a very specific combination of operating system parameters, properties, and a JDK version. Example 11.4. Profile Activation Parameters: JDK Version, OS Parameters, and Properties <project> ... <profiles> <profile> <id>dev</id> <activation> <activeByDefault>false</activeByDefault># <jdk>1.5</jdk># <os> <name>Windows XP</name># <family>Windows</family> <arch>x86</arch> <version>5.1.2600</version> </os> <property> <name>mavenVersion</name># <value>2.0.5</value> </property> <file> <exists>file2.properties</exists># <missing>file1.properties</missing> </file> 271
  • Build Profiles </activation> ... </profile> </profiles> </project> This previous example defines a very narrow set of activation parameters. Let's examine each activation criterion in detail: ‚ The activeByDefault element controls whether this profile is considered active by default. ƒ This profile will only be active for JDK versions that begin with "1.5". This includes "1.5.0_01", "1.5.1". „ This profile targets a very specific version of Windows XP, version 5.1.2600 on a 32-bit platform. If your project uses the native plugin to build a C program, you might find yourself writing projects for specific platforms. … The property element tells Maven to activate this profile if the property mavenVersion is set to the value 2.0.5. mavenVersion is an implicit property that is available to all Maven builds. † The file element allows us to activate a profile based on the presence (or absence) of files. The dev profile will be activated if a file named file2.properties exists in the base directory of the project. The dev profile will only be activated if there is no file named file1.properties file in the base directory of the project. 11.3.2. Activation by the Absence of a Property You can activate a profile based on the value of a property like environment.type. You can activate a development profile if environment.type equals dev, or a production profile if environment.type equals prod. You can also activate a profile in the absence of a property. The following configuration activates a profile if the property environment.type is not present during Maven execution. Example 11.5. Activating Profiles in the Absence of a Property 272
  • Build Profiles <project> ... <profiles> <profile> <id>development</id> <activation> <property> <name>!environment.type</name> </property> </activation> </profile> </profiles> </project> Note the exclamation point prefixing the property name. The exclamation point is often referred to as the "bang" character and signifies "not". This profile is activated when no ${environment.type} property is set. 11.4. Listing Active Profiles Maven profiles can be defined in either pom.xml, profiles.xml, ~/.m2/settings.xml, or ${M2_HOME}/conf/settings.xml. With these four levels, there's no good way of keeping track of profiles available to a particular project without remembering which profiles are defined in these four files. To make it easier to keep track of which profiles are available, and where they have been defined, the Maven Help plugin defines a goal, active-profiles, which lists all the active profiles and where they have been defined. You can run the active-profiles goal, as follows: $ mvn help:active-profiles Active Profiles for Project 'My Project': The following profiles are active: - my-settings-profile (source: settings.xml) - my-external-profile (source: profiles.xml) - my-internal-profile (source: pom.xml) 273
  • Build Profiles 11.5. Tips and Tricks Profiles can encourage build portability. If your build needs subtle customizations to work on different platforms or if you need your build to produce different results for different target platforms, project profiles increase build portability. Settings profiles generally decrease build portability by adding extra-project information that must be communicated from developer to developer. The following sections provide some guidelines and some ideas for applying Maven profiles to your project. 11.5.1. Common Environments One of the core motivations for Maven project profiles was to provide for environment-specific configuration settings. In a development environment, you might want to produce bytecode with debug information and you might want to configure your system to use a development database instance. In a production environment you might want to produce a signed JAR and configure the system to use a production database. In this chapter, we defined a number of environments with identifiers like dev and prod. A simpler way to do this would be to define profiles that are activated by environment properties and to use these common environment properties across all of your projects. For example, if every project had a development profile activated by a property named environment.type having a value of dev, and if those same projects had a production profile activated by a property named environment.type having a value of prod, you could create a default profile in your settings.xml that always set environment.type to dev on your development machine. This way, each project defines a dev profile activated by the same environment variable. Let's see how this is done, the following settings.xml defines a profile in ~/.m2/settings.xml which sets the environment.type property to dev. Example 11.6. ~/.m2/settings.xml defines a default profile setting environment.type <settings> 274
  • Build Profiles <profiles> <profile> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <environment.type>dev</environment.type> </properties> </profile> </profiles> </settings> This means that every time you run Maven on your machine, this profile will be activated and the property environment.type will have the value dev. You can then use this property to activate profiles defined in a project's pom.xml as follows. Let's take a look at how a project's pom.xml would define a profile activated by environment.type having the value dev. Example 11.7. Project Profile Activated by environment.type equalling 'dev' <project> ... <profiles> <profile> <id>development</id> <activation> <property> <name>environment.type</name> <value>dev</value> </property> </activation> <properties> <database.driverClassName>com.mysql.jdbc.Driver</database.driverClassName> <database.url> jdbc:mysql://localhost:3306/app_dev </database.url> <database.user>development_user</database.user> <database.password>development_password</database.password> </properties> </profile> <profile> <id>production</id> <activation> <property> <name>environment.type</name> <value>prod</value> </property> 275
  • Build Profiles </activation> <properties> <database.driverClassName>com.mysql.jdbc.Driver</database.driverClassName> <database.url>jdbc:mysql://master01:3306,slave01:3306/app_prod</database.url> <database.user>prod_user</database.user> </properties> </profile> </profiles> </project> This project defines some properties like database.url and database.user which might be used to configure another Maven plugin configured in the pom.xml. There are plugins available that can manipulate the database, run SQL, and plugins like the Maven Hibernate3 plugin which can generate annotated model objects for use in persistence frameworks. A few of these plugins, can be configured in a pom.xml using these properties. These properties could also be used to filter resources. In this example, because we've defined a profile in ~/.m2/settings.xml which sets environment.type to dev, the development profile will always be activated when we run Maven on our development machine. Alternatively, if we wanted to override this default, we could set a property on the command-line. If we need to activate the production profile, we could always run Maven with: ~/examples/profiles $ mvn install -Denvironment.type=prod Setting a property on the command-line would override the default property set in ~/.m2/settings.xml. We could have just defined a profile with an id of "dev" and invoked it directly with the -P command-line argument, but using this environment.type property allows us to code other project pom.xml files to this standard. Every project in your codebase could have a profile which is activated by the same environment.type property set in every user's ~/.m2/settings.xml. In this way, developers can share common configuration for development without defining this configuration in non-portable settings.xml files. 11.5.2. Protecting Secrets This best practice builds upon the previous section. In Project Profile Activated by environment.type equalling 'dev', the production profile does not contain the 276
  • Build Profiles database.password property. I've done this on purpose to illustrate the concept of putting secrets in you user-specific settings.xml. If you were developing an application at a large organization which values security, it is likely that the majority of the development group will not know the password to the production database. In an organization that draws a bold line between the development group and the operations group, this will be the norm. Developers may have access to a development and a staging environment, but they might not have (or want to have) access to the production database. There are a number of reasons why this makes sense, particularly if an organization is dealing with extremely sensitive financial, intelligence, or medical information. In this scenario, the production environment build may only be carried out by a lead developer or by a member of the production operations group. When they run this build using the prod environment.type, they will need to define this variable in their settings.xml as follows: Example 11.8. Storing Secrets in a User-specific Settings Profile <settings> <profiles> <profile> <activeByDefault>true</activeByDefault> <properties> <environment.type>prod</environment.type> <database.password>m1ss10nimp0ss1bl3</database.password> </properties> </profile> </profiles> </settings> This user has defined a default profile which sets the environment.type to prod and which also sets the production password. When the project is executed, the production profile is activated by the environment.type property and the database.password property is populated. This way, you can put all of the production-specific configuration into a project's pom.xml and leave out only the single secret necessary to access the production database. Note 277
  • Build Profiles Secrets usually conflict with wide portability, but this makes sense. You wouldn't want to share your secrets openly. 11.5.3. Platform Classifiers Let's assume that you have a library or a project that produces platform-specific customizations. Even though Java is platform-neutral, there are times when you might need to write some code that invokes platform-specific native code. Another possibility is that you've written some C code which is compiled by the Maven Native plugin and you want to produce a qualified artifact depending on the build platform. You can set a classifier with the Maven Assembly plugin or with the Maven Jar plugin. The following pom.xml produces a qualified artifact using profiles which are activated by Operation System parameters. For more information about the Maven Assembly plugin, see Chapter 12, Maven Assemblies. Example 11.9. Qualifying Artifacts with Platform Activated Project Profiles <project> ... <profiles> <profile> <id>windows</id> <activation> <os> <family>windows</family> </os> </activation> <build> <plugins> <plugin <artifactId>maven-jar-plugin</artifactId> <configuration> <classifier>win</classifier> </configuration> </plugin> </plugins> </build> </profile> <profile> <id>linux</id> <activation> <os> 278
  • Build Profiles <family>unix</family> </os> </activation> <build> <plugins> <plugin> <artifactId>maven-jar-plugin</artifactId> <configuration> <classifier>linux</classifier> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project> If the Operating System is in the Windows family, this pom.xml qualifies the JAR artifact with "-win". If the Operating System is in the Unix family, the artifact is qualified with "-linux". This pom.xml successfully adds the qualifiers to the artifacts, but it is more verbose than it need to be due to the redundant configuration of the Maven Jar plugin in both profiles. This example could be rewritten to use variable substitution to minimize redundancy as follows: Example 11.10. Qualifying Artifacts with Platform Activated Project Profiles and Variable Substitution <project> ... <build> <plugins> <plugin> <artifactId>maven-jar-plugin</artifactId> <configuration> <classifier>${envClassifier}</classifier> </configuration> </plugin> </plugins> </build> ... <profiles> <profile> <id>windows</id> <activation> <os> <family>windows</family> 279
  • Build Profiles </os> </activation> <properties> <envClassifier>win</envClassifier> </properties> </profile> <profile> <id>linux</id> <activation> <os> <family>unix</family> </os> </activation> <properties> <envClassifier>linux</envClassifier> </properties> </profile> </profiles> </project> In this pom.xml, each profile doesn't need to include a build element to configure the Jar plugin. Instead, each profile is activated by the Operating System family and sets the envClassifier property to either win or linux. This envClassifier is then referenced in the default pom.xml build element to add a classifier to the project's JAR artifact. The JAR artifact will be named ${finalName}-${envClassifier}.jar and included as a dependency using the following dependency syntax: Example 11.11. Depending on a Qualified Artifact <dependency> <groupId>com.mycompany</groupId> <artifactId>my-project</artifactId> <version>1.0</version> <classifier>linux</classifier> </dependency> 11.6. Summary When used judiciously, profiles can make it very easy to customize a build for 280
  • Build Profiles different platforms. If something in your build needs to define a platform-specific path for something like an application server, you can put these configuration points in a profile which is activated by an operating system parameter. If you have a project which needs to produce different artifacts for different environments, you can customize the build behavior for different environments and platforms via profile-specific plugin behavior. Using profiles, builds can become portable, there is no need to rewrite your build logic to support a new environment, just override the configuration that needs to change and share the configuration points which can be shared. 281
  • Chapter 12. Maven Assemblies 12.1. Introduction Maven provides plugins that are used to create the most common archive types, most of which are consumable as dependencies of other projects. Some examples include the JAR, WAR, EJB, and EAR plugins. As discussed in Chapter 10, Der Build Lebenszyklus these plugins correspond to different project packaging types each with a slightly different build process. While Maven has plugins and customized lifecycles to support standard packaging types, there are times when you'll need to create an archive or directory with a custom layout. Such custom archives are called Maven Assemblies. There are any number of reasons why you may want to build custom archives for your project. Perhaps the most common is the project distribution. The word ‘distribution’ means many different things to different people (and projects), depending on how the project is meant to be used. Essentially, these are archives that provide a convenient way for users to install or otherwise make use of the project’s releases. In some cases, this may mean bundling a web application with an application server like Jetty. In others, it could mean bundling API documentation alongside source and compiled binaries like jar files. Assemblies usually come in handy when you are building the final distribution of a product. For example, products like Nexus introduced in Chapter 16, Repository Management with Nexus, are the product of large multi-module Maven products, and the final archive you download from Sonatype was created using a Maven Assembly. In most cases, the Assembly plugin is ideally suited to the process of building project distributions. However, assemblies don’t have to be distribution archives; assemblies are intended to provide Maven users with the flexibility they need to produce customized archives of all kinds. Essentially, assemblies are intended to fill the gaps between the standard archive formats provided by project package types. Of course, you could write an entire Maven plugin simply to generate your 282
  • Maven Assemblies own custom archive format, along with a new lifecycle mapping and artifact-handling configuration to tell Maven how to deploy it. But the Assembly plugin makes this unnecessary in most cases by providing generalized support for creating your own archive recipe without spending so much time writing Maven code. 12.2. Assembly Basics Before we go any further, it’s best to take a minute and talk about the two main goals in the Assembly plugin: assembly:assembly, and the single mojo. I list these two goals in different ways because it reflects the difference in how they’re used. The assembly:assembly goal is designed to be invoked directly from the command line, and should never be bound to a build lifecycle phase. In contrast, the single mojo is designed to be a part of your everyday build, and should be bound to a phase in your project’s build lifecycle. The main reason for this difference is that the assembly:assembly goal is what Maven terms an aggregator mojo; that is, a mojo which is designed to run at most once in a build, regardless of how many projects are being built. It draws its configuration from the root project - usually the top-level POM or the command line. When bound to a lifecycle, an aggregator mojo can have some nasty side-effects. It can force the execution of the package lifecycle phase to execute ahead of time, and can result in builds which end up executing the package phase twice. Because the assembly:assembly goal is an aggregator mojo, it raises some issues in multi-module Maven builds, and it should only be called as a stand-alone mojo from the command-line. Never bind an assembly:assembly execution to a lifecycle phase. assembly:assembly was the original goal in the Assembly plugin, and was never designed to be part of the standard build process for a project. As it became clear that assembly archives were a legitimate requirement for projects to produce, the single mojo was developed. This mojo assumes that it has been bound to the correct part of the build process, so that it will have access to the project files and artifacts it needs to execute within the lifecycle of a large multi-module Maven project. In a multi-module environment, it will execute as 283
  • Maven Assemblies many times as it is bound to the different module POMs. Unlike assembly:assembly, single will never force the execution of another lifecycle phase ahead of itself. The Assembly plugin provides several other goals in addition to these two. However, discussion of these other mojos is beyond the scope of this chapter, because they serve exotic or obsolete use cases, and because they are almost never needed. Whenever possible, you should definitely stick to using assembly:assembly for assemblies generated from the command line, and to single for assemblies bound to lifecycle phases. 12.2.1. Predefined Assembly Descriptors While many people opt to create their own archive recipes - called assembly descriptors - this isn’t strictly necessary. The Assembly plugin provides built-in descriptors for several common archive types that you can use immediately without writing a line of configuration. The following assembly descriptors are predefined in the Maven Assembly plugin: bin The bin descriptor is used to bundle project LICENSE, README, and NOTICE files with the project’s main artifact, assuming this project builds a jar as its main artifact. Think of this as the smallest possible binary distribution for completely self-contained projects. jar-with-dependencies The jar-with-dependencies descriptor builds a JAR archive with the contents of the main project jar along with the unpacked contents of all the project’s runtime dependencies. Coupled with an appropriate Main-Class Manifest entry (discussed in “Plugin Configuration” below), this descriptor can produce a self-contained, executable jar for your project, even if the project has dependencies. project The project descriptor simply archives the project directory structure as it 284
  • Maven Assemblies exists in your file-system and, most likely, in your version control system. Of course, the target directory is omitted, as are any version-control metadata files like the CVS and .svn directories we’re all used to seeing. Basically, the point of this descriptor is to create a project archive that, when unpacked, can be built using Maven. src The src descriptor produces an archive of your project source and pom.xml files, along with any LICENSE, README, and NOTICE files that are in the project’s root directory. This precursor to the project descriptor produces an archive that can be built by Maven in most cases. However, because of its assumption that all source files and resources reside in the standard src directory, it has the potential to leave out non-standard directories and files that are nonetheless critical to some builds. 12.2.2. Building an Assembly The Assembly plugin can be executed in two ways: you can invoke it directly from the command line, or you can configure it as part of your standard build process by binding it to a phase of your project’s build lifecycle. Direct invocation has its uses, particularly for one-off assemblies that are not considered part of your project’s core deliverables. In most cases, you’ll probably want to generate the assemblies for your project as part of its standard build process. Doing this has the effect of including your custom assemblies whenever the project is installed or deployed into Maven’s repositories, so they are always available to your users. As an example of the direct invocation of the Assembly plugin, imagine that you wanted to ship off a copy of your project which people could build from source. Instead of just deploying the end-product of the build, you wanted to include the source as well. You won’t need to do this often, so it doesn’t make sense to add the configuration to your POM. Instead, you can use the following command: $ mvn -DdescriptorId=project assembly:single ... [INFO] [assembly:single] [INFO] Building tar : /Users/~/mvn-examples-1.0/assemblies/direct-invocation/ target/direct-invocation-1.0-SNAPSHOT-project.tar.gz [INFO] Building tar : /Users/~/mvn-examples-1.0/assemblies/direct-invocation/ 285
  • Maven Assemblies target/direct-invocation-1.0-SNAPSHOT-project.tar.bz2 [INFO] Building zip: /Users/~/mvn-examples-1.0/assemblies/direct-invocation/ target/direct-invocation-1.0-SNAPSHOT-project.zip ... Imagine you want to produce an executable JAR from your project. If your project is totally self-contained with no dependencies, this can be achieved with the main project artifact using the archive configuration of the JAR plugin. However, most projects have dependencies, and those dependencies must be incorporated in any executable JAR. In this case, you want to make sure that every time the main project JAR is installed or deployed, your executable JAR goes along with it. Assuming the main class for the project is org.sonatype.mavenbook.App, the following POM configuration will create an executable JAR: Example 12.1. Assembly Descriptor for Executable JAR <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.assemblies</groupId> <artifactId>executable-jar</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Assemblies Executable Jar Example</name> <url>http://sonatype.com/book</url> <dependencies> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-2</version> <executions> <execution> <id>create-executable-jar</id> <phase>package</phase> <goals> <goal>single</goal> 286
  • Maven Assemblies </goals> <configuration> <descriptorRefs> <descriptorRef> jar-with-dependencies </descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>org.sonatype.mavenbook.App</mainClass> </manifest> </archive> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> There are two things to notice about the configuration above. First, we’re using the descriptorRefs configuration section instead of the descriptorId parameter we used last time. This allows multiple assembly types to be built from the same Assembly plugin execution, while still supporting our use case with relatively little extra configuration. Second, the archive element under configuration sets the Main-Class manifest attribute in the generated JAR. This section is commonly available in plugins that create JAR files, such as the JAR plugin used for the default project package type. Now, you can produce the executable JAR simply by executing mvn package. Afterward, we’ll also get a directory listing for the target directory, just to verify that the executable JAR was generated. Finally, just to prove that we actually do have an executable JAR, we’ll try executing it: $ mvn package ... (output omitted) ... [INFO] [jar:jar] [INFO] Building jar: ~/mvn-examples-1.0/assemblies/executable-jar/target/ executable-jar-1.0-SNAPSHOT.jar [INFO] [assembly:single {execution: create-executable-jar}] [INFO] Processing DependencySet (output=) [INFO] Building jar: ~/mvn-examples-1.0/assemblies/executable-jar/target/ executable-jar-1.0-SNAPSHOT-jar-with-dependencies.jar ... (output omitted) ... $ ls -1 target 287
  • Maven Assemblies ... (output omitted) ... executable-jar-1.0-SNAPSHOT-jar-with-dependencies.jar executable-jar-1.0-SNAPSHOT.jar ... (output omitted) ... $ java -jar target/executable-jar-1.0-SNAPSHOT-jar-with-dependencies.jar Hello, World! From the output shown above, you can see that the normal project build now produces a new artifact in addition to the main JAR file. The new one has a classifier of jar-with-dependencies. Finally, we verified that the new JAR actually is executable, and that executing the JAR produced the desired output of “Hello, World!” 12.2.3. Assemblies as Dependencies When you generate assemblies as part of your normal build process, those assembly archives will be attached to your main project’s artifact. This means they will be installed and deployed alongside the main artifact, and are then resolvable in much the same way. Each assembly artifact is given the same basic coordinate (groupId, artifactId, and version) as the main project. However, these artifacts are attachments, which in Maven means they are derivative works based on some aspect of the main project build. To provide a couple of examples, source assemblies contain the raw inputs for the project build, and jar-with-dependencies assemblies contain the project’s classes plus its dependencies. Attached artifacts are allowed to circumvent the Maven requirement of one project, one artifact precisely because of this derivative quality. Since assemblies are (normally) attached artifacts, each must have a classifier to distinguish it from the main artifact, in addition to the normal artifact coordinate. By default, the classifier is the same as the assembly descriptor’s identifier. When using the built-in assembly descriptors, as above, the assembly descriptor’s identifier is generally also the same as the identifier used in the descriptorRef for that type of assembly. Once you’ve deployed an assembly alongside your main project artifact, how can you use that assembly as a dependency in another project? The answer is fairly straightforward. Recall the discussions in Section 3.5.3, “Maven Koordinaten” and 288
  • Maven Assemblies Section 9.5.1, “More on Coordinates” about project dependencies in Maven, projects depend on other projects using a combination of four basic elements, referred to as a project’s coordinates: groupId, artifactId, version, and packaging. In Section 11.5.3, “Platform Classifiers”, multiple platform-specific variants of a project’s artifact and available, and the project specifies a classifier element with a value of either win or linux to select the appropriate dependency artifact for the target platform. Assembly artifacts can be used as dependencies using the required coordinates of a project plus the classifier under which the assembly was installed or deployed. If the assembly is not a JAR archive, we also need to declare its type. 12.2.4. Assembling Assemblies via Assembly Dependencies How's that for a confusing section title? Let's try to set up a scenario which would explain the idea of assembling assemblies. Imagine you want to create an archive which itself contains some project assemblies. Assume that you have a multi-module build and you want to deploy an assembly which contains a set of related project assemblies. In this section's example, we create a bundle of "buildable" project directories for a set of projects that are commonly used together. For simplicity, we’ll reuse the two built-in assembly descriptors discussed above - project and jar-with-dependencies. In this particular example, it is assumed that each project creates the project assembly in addition to its main JAR artifact. Assume that every project in a multi-module build binds the single goal to the package phase and uses the project descriptorRef. Every project in a multi-module will inherit the configuration from a top-level pom.xml whose pluginManagement element is shown in Example 12.2, “Configuring the project assembly in top-level POM”. Example 12.2. Configuring the project assembly in top-level POM <project> ... <build> <pluginManagement> <plugins> 289
  • Maven Assemblies <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-2</version> <executions> <execution> <id>create-project-bundle</id> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <descriptorRefs> <descriptorRef>project</descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> </plugins> </pluginManagement> </build> ... </project> Each project POM references the managed plugin configuration from Example 12.2, “Configuring the project assembly in top-level POM” using a minimal plugin declaration in its build section shown in Example 12.3, “Activating the Assembly Plugin Configuration in Child Projects”. Example 12.3. Activating the Assembly Plugin Configuration in Child Projects <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> </plugin> </plugins> </build> To produce the set of project assemblies, run mvn install from the top-level directory. You should see Maven installing artifacts with classifiers in your local repository. 290
  • Maven Assemblies $ mvn install ... Installing ~/mvn-examples-1.0/assemblies/as-dependencies/project-parent/ second-project/target/second-project-1.0-SNAPSHOT-project.tar.gz to ~/.m2/repository/org/sonatype/mavenbook/assemblies/second-project/1.0-SNAPSHOT/ second-project-1.0-SNAPSHOT-project.tar.gz ... Installing ~/mvn-examples-1.0/assemblies/as-dependencies/project-parent/ second-project/target/second-project-1.0-SNAPSHOT-project.tar.bz2 to ~/.m2/repository/org/sonatype/mavenbook/assemblies/second-project/1.0-SNAPSHOT/ second-project-1.0-SNAPSHOT-project.tar.bz2 ... Installing ~/mvn-examples-1.0/assemblies/as-dependencies/project-parent/ second-project/target/second-project-1.0-SNAPSHOT-project.zip to ~/.m2/repository/org/sonatype/mavenbook/assemblies/second-project/1.0-SNAPSHOT/ second-project-1.0-SNAPSHOT-project.zip ... When you run install, Maven will copy the each project's main artifact and each assembly to your local Maven repository. All of these artifacts are now available for reference as dependencies in other projects locally. If your ultimate goal is to create a bundle which includes assemblies from multiple project, you can do so by creating another project which will include other project's assemblies as dependencies. This bundling project (aptly named project-bundle) is responsible for creating the bundled assembly. The POM for the bundling project would resemble the XML document listed in Example 12.4, “POM for the Assembly Bundling Project”. Example 12.4. POM for the Assembly Bundling Project <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.assemblies</groupId> <artifactId>project-bundle</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <name>Assemblies-as-Dependencies Example Project Bundle</name> <url>http://sonatype.com/book</url> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.assemblies</groupId> <artifactId>first-project</artifactId> <version>1.0-SNAPSHOT</version> 291
  • Maven Assemblies <classifier>project</classifier> <type>zip</type> </dependency> <dependency> <groupId>org.sonatype.mavenbook.assemblies</groupId> <artifactId>second-project</artifactId> <version>1.0-SNAPSHOT</version> <classifier>project</classifier> <type>zip</type> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-2</version> <executions> <execution> <id>bundle-project-sources</id> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <descriptorRefs> <descriptorRef> jar-with-dependencies </descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> This bundling project's POM references the two assemblies from first-project and second-project. Instead of referencing the main artifact of each project, the bundling project's POM specifies a classifier of project and a type of zip. This tells Maven to resolve the ZIP archive which was created by the project assembly. Note that the bundling project generates a jar-with-dependencies assembly. jar-with-dependencies does not create a particularly elegant bundle, it simply creates a JAR file with the unpacked contents of all of the dependencies. jar-with-dependencies is really just telling Maven to take all of the dependencies, unpack them, and then create a single archive which includes the 292
  • Maven Assemblies output of the current project. In this project, it has the effect of creating a single JAR file that puts the two project assemblies from first-project and second-project side-by-side. This example illustrates how the basic capabilities of the Maven Assembly plugin can be combined without the need for a custom assembly descriptor. It achieves the purpose of creating a single archive that contains the project directories for multiple projects side-by-side. This time, the jar-with-dependencies is just a storage format, so we don’t need to specify a Main-Class manifest attribute. To build the bundle, we just build the project-bundle project normally: $ mvn package ... [INFO] [assembly:single {execution: bundle-project-sources}] [INFO] Processing DependencySet (output=) [INFO] Building jar: ~/downloads/mvn-examples-1.0/assemblies/as-dependencies/ project-bundle/target/project-bundle-1.0-SNAPSHOT-jar-with-dependencies.jar To verify that the project-bundle assembly contains the unpacked contents of the assembly dependencies, run jar tf: $ jar tf target/project-bundle-1.0-SNAPSHOT-jar-with-dependencies.jar ... first-project-1.0-SNAPSHOT/pom.xml first-project-1.0-SNAPSHOT/src/main/java/org/sonatype/mavenbook/App.java first-project-1.0-SNAPSHOT/src/test/java/org/sonatype/mavenbook/AppTest.java ... second-project-1.0-SNAPSHOT/pom.xml second-project-1.0-SNAPSHOT/src/main/java/org/sonatype/mavenbook/App.java second-project-1.0-SNAPSHOT/src/test/java/org/sonatype/mavenbook/AppTest.java After reading this section, the title should make more sense. You've assembled assemblies from two projects into an assembly using a bundling project which has a dependency on each of the assemblies. 12.3. Overview of the Assembly Descriptor When the standard assembly descriptors introduced in Section 12.2, “Assembly Basics” are not adequate, you will need to define your own assembly descriptor. The assembly descriptor is an XML document which defines the structure and 293
  • Maven Assemblies contents of an assembly. Figure 12.1. Assembly Descriptor Picture The assembly descriptor contains five main configuration sections, plus two additional sections: one for specifying standard assembly-descriptor fragments, called component descriptors, and another for specifying custom file processor classes to help manage the assembly-production process. Base Configuration This section contains the information required by all assemblies, plus some additional configuration options related to the format of the entire archive, such as the base path to use for all archive entries. For the assembly descriptor to be valid, you must at least specify the assembly id, at least one format, and at least 294
  • Maven Assemblies one of the other sections shown above. File Information The configurations in this segment of the assembly descriptor apply to specific files on the file system within the project’s directory structure. This segment contains two main sections: files and fileSets. You use files and fileSets to control the permissions of files in an assembly and to include or exclude files from an assembly. Dependency Information Almost all projects of any size depend on other projects. When creating distribution archives, project dependencies are usually included in the end-product of an assembly. This section manages the way dependencies are included in the resulting archive. This section allows you to specify whether dependencies are unpacked, added directly to the lib/ directory, or mapped to new file names. This section also allows you to control the permissions of dependencies in the assembly, and which dependencies are included in an assembly. Repository Information At times, it’s useful to isolate the sum total of all artifacts necessary to build a project, whether they’re dependency artifacts, POMs of dependency artifacts, or even a project’s own POM ancestry (your parent POM, its parent, and so on). This section allows you to include one or more artifact-repository directory structures inside your assembly, with various configuration options. The Assembly plugin does not have the ability to include plugin artifacts in these repositories yet. Module Information This section of the assembly descriptor allows you to take advantage of these parent-child relationships when assembling your custom archive, to include source files, artifacts, and dependencies from your project’s modules. This is the most complex section of the assembly descriptor, because it allows you to work with modules and sub-modules in two ways: as a series of fileSets (via the sources section) or as a series of dependencySets (via the binaries 295
  • Maven Assemblies section). 12.4. The Assembly Descriptor This section is a tour of the assembly descriptor which contains some guidelines for developing a custom assembly descriptor. The Assembly plugin is one of the largest plugins in the Maven ensemble, and one of the most flexible. 12.4.1. Property References in Assembly Descriptors Any property discussed in Section 13.2, “Maven Properties” can be referenced in an assembly descriptor. Before any assembly descriptor is used by Maven, it is interpolated using information from the POM and the current build environment. All properties supported for interpolation within the POM itself are valid for use in assembly descriptors, including POM properties, POM element values, system properties, user-defined properties, and operating-system environment variables. The only exceptions to this interpolation step are elements in various sections of the descriptor named outputDirectory, outputDirectoryMapping, or outputFileNameMapping. The reason these are held back in their raw form is to allow artifact- or module-specific information to be applied when resolving expressions in these values, on a per-item basis. 12.4.2. Required Assembly Information There are two essential pieces of information that are required for every assembly: the id, and the list of archive formats to produce. In practice, at least one other section of the descriptor is required - since most archive format components will choke if they don’t have at least one file to include - but without at least one format and an id, there is no archive to create. The id is used both in the archive’s file name, and as part of the archive’s artifact classifier in the Maven repository. The format string also controls the archiver-component instance that will create the final assembly archive. All assembly descriptors must contain an id and at least one format: 296
  • Maven Assemblies Example 12.5. Required Assembly Descriptor Elements <assembly> <id>bundle</id> <formats> <format>zip</format> </formats> ... </assembly> The assembly id can be any string that does not contain spaces. The standard practice is to use dashes when you must separate words within the assembly id. If you were creating an assembly to create an interesting unique package structure, you would give your an id of something like interesting-unique-package. It also supports multiple formats within a single assembly descriptor, allowing you to create the familiar .zip, .tar.gz, and .tar.bz2 distribution archive set with ease. If you don't find the archive format you need, you can also create a custom format. Custom formats are discussed in Section 12.5.8, “componentDescriptors and containerDescriptorHandlers”. The Assembly plugin supports several archive formats natively, including: • jar • zip • tar • bzip2 • gzip • tar.gz • tar.bz2 • rar 297
  • Maven Assemblies • war • ear • sar • dir The id and format are essential because they will become a part of the coordinates for the assembled archive. The example from Example 12.5, “Required Assembly Descriptor Elements” will create an assembly artifact of type zip with a classifier of bundle. 12.5. Controlling the Contents of an Assembly In theory, id and format are the only absolute requirements for a valid assembly descriptor; however, many assembly archivers will fail if they do not have at least one file to include in the output archive. The task of defining the files to be included in the assembly is handled by the five main sections of the assembly descriptor: files, fileSets, dependencySets, repositories, and moduleSets. To explore these sections most effectively, we’ll start by discussing the most elemental section: files. Then, we’ll move on to the two most commonly used sections, fileSets and dependencySets. Once you understand the workings of fileSets and dependencySets, it’s easier to understand repositories and moduleSets. 12.5.1. Files Section The files section is the simplest part of the assembly descriptor, it is designed for files that have a definite location relative to your project’s directory. Using this section, you have absolute control over the exact set of files that are included in your assembly, exactly what they are named, and where they will reside in the archive. 298
  • Maven Assemblies Example 12.6. Including a JAR file in an Assembly using files <assembly> ... <files> <file> <source>target/my-app-1.0.jar</source> <outputDirectory>lib</outputDirectory> <destName>my-app.jar</destName> <fileMode>0644</fileMode> </file> </files> ... </assembly> Assuming you were building a project called my-app with a version of 1.0, Example 12.6, “Including a JAR file in an Assembly using files” would include your project's JAR in the assembly’s lib/ directory, trimming the version from the file name in the process so the final file name is simply my-app.jar. It would then make the JAR readable by everyone and writable by the user that owns it (this is what the mode 0644 means for files, using Unix four-digit Octal permission notation). For more information about the format of the value in fileMode, please see the Wikipedia's explanation of four-digit Octal notation. You could build a very complex assembly using file entries, if you knew the full list of files to be included. Even if you didn’t know the full list before the build started, you could probably use a custom Maven plugin to discover that list and generate the assembly descriptor using references like the one above. While the files section gives you fine-grained control over the permission, location, and name of each file in the assembly archive, listing a file element for every file in a large archive would be a tedious exercise. For the most part, you will be operating on groups of files and dependencies using fileSets. The remaining four file-inclusion sections are designed to help you include entire sets of files that match a particular criteria. 12.5.2. FileSets Section Similar to the files section, fileSets are intended for files that have a definite 299
  • Maven Assemblies location relative to your project’s directory structure. However, unlike the files section, fileSets describe sets of files, defined by file and path patterns they match (or don’t match), and the general directory structure in which they are located. The simplest fileSet just specifies the directory where the files are located: <assembly> ... <fileSets> <fileSet> <directory>src/main/java</directory> </fileSet> </fileSets> ... </assembly> This file set simply includes the contents of the src/main/java directory from our project. It takes advantage of many default settings in the section, so let’s discuss those briefly. First, you’ll notice that we haven’t told the file set where within the assembly matching files should be located. By default, the destination directory (specified with outputDirectory) is the same as the source directory (in our case, src/main/java). Additionally, we haven’t specified any inclusion or exclusion file patterns. When these are empty, the file set assumes that all files within the source directory are included, with some important exceptions. The exceptions to this rule pertain mainly to source-control metadata files and directories, and are controlled by the useDefaultExcludes flag, which is defaulted to true. When active, useDefaultExcludes will keep directories like .svn/ and CVS/ from being added to the assembly archive. Section 12.5.3, “Default Exclusion Patterns for fileSets” provides a detailed list of the default exclusion patterns. If we want more control over this file set, we can specify it more explicitly. Example 12.7, “Including Files with fileSet” shows a fileSet element with all of the default elements specified. Example 12.7. Including Files with fileSet <assembly> ... <fileSets> 300
  • Maven Assemblies <fileSet> <directory>src/main/java</directory> <outputDirectory>src/main/java</outputDirectory> <includes> <include>**</include> </includes> <useDefaultExcludes>true</useDefaultExcludes> <fileMode>0644</fileMode> <directoryMode>0755</directoryMode> </fileSet> </fileSets> ... </assembly> The includes section uses a list of include elements, which contain path patterns. These patterns may contain wildcards such as ‘**’ which matches one or more directories or ‘*’ which matches part of a file name, and ‘?’ which matches a single character in a file name. Example 12.7, “Including Files with fileSet” uses a fileMode entry to specify that files in this set should be readable by all, but only writable by the owner. Since the fileSet includes directories, we also have the option of specifying a directoryMode that works in much the same way as the fileMode. Since a directories’ execute permission is what allows users to list their contents, we want to make sure directories are executable in addition to being readable. Like files, only the owner can write to directories in this set. The fileSet entry offers some other options as well. First, it allows for an excludes section with a form identical to the includes section. These exclusion patterns allow you to exclude specific file patterns from a fileSet. Include patterns take precedence over exclude patterns. Additionally, you can set the filtering flag to true if you want to substitute property values for expressions within the included files. Expressions can be delimited either by ${ and } (standard Maven expressions like ${project.groupId}) or by @ and @ (standard Ant expressions like @project.groupId@). You can adjust the line ending of your files using the lineEnding element; valid values for lineEnding are: keep Preserve line endings from original files. (This is the default value.) 301
  • Maven Assemblies unix Unix-style line endings lf Only a Line Feed Character dos MS-DOS-style line endings crlf Carriage-return followed by a Line Feed Finally, if you want to ensure that all file-matching patterns are used, you can use the useStrictFiltering element with a value of true (the default is false). This can be especially useful if unused patterns may signal missing files in an intermediary output directory. When useStrictFiltering is set to true, the Assembly plugin will fail if an include pattern is not satisfied. In other words, if you have an include pattern which includes a file from a build, and that file is not present, setting useStrictFiltering to true will cause a failure if Maven cannot find the file to be included. 12.5.3. Default Exclusion Patterns for fileSets When you use the default exclusion patterns, the Maven Assembly plugin is going to be ignoring more than just SVN and CVS information. By default the exclusion patterns are defined by the DirectoryScanner class in the plexus-utils project hosted at Codehaus. The array of exclude patterns is defined as a static, final String array named DEFAULTEXCLUDES in DirectoryScanner. The contents of this variable are shown in Example 12.8, “Definition of Default Exclusion Patterns from Plexus Utils”. Example 12.8. Definition of Default Exclusion Patterns from Plexus Utils public static final String[] DEFAULTEXCLUDES = { // Miscellaneous typical temporary files "**/*~", "**/#*#", 302
  • Maven Assemblies "**/.#*", "**/%*%", "**/._*", // CVS "**/CVS", "**/CVS/**", "**/.cvsignore", // SCCS "**/SCCS", "**/SCCS/**", // Visual SourceSafe "**/vssver.scc", // Subversion "**/.svn", "**/.svn/**", // Arch "**/.arch-ids", "**/.arch-ids/**", //Bazaar "**/.bzr", "**/.bzr/**", //SurroundSCM "**/.MySCMServerInfo", // Mac "**/.DS_Store" }; This default array of patterns excludes temporary files from editors like GNU Emacs, and other common temporary files from Macs and a few common source control systems (although Visual SourceSafe is more of a curse than a source control system). If you need to override these default exclusion patterns you set useDefaultExcludes to false and then define a set of exclusion patterns in your own assembly descriptor. 12.5.4. dependencySets Section One of the most common requirements for assemblies is the inclusion of a 303
  • Maven Assemblies project’s dependencies in an assembly archive. Where files and fileSets deal with files in your project, dependency files don't have a location in your project. The artifacts your project depends on have to be resolved by Maven during the build. Dependency artifacts are abstract, they lack a definite location, and are resolved using a symbolic set of Maven coordinates. While Since file and fileSet specifications require a concrete source path, dependencies are included or excluded from an assembly using a combination of Maven coordinates and dependency scopes. The simplest dependencySet is an empty element: <assembly> ... <dependencySets> <dependencySet/> </dependencySets> ... </assembly> The dependencySet above will match all runtime dependencies of your project (runtime scope includes the compile scope implicitly), and it will add these dependencies to the root directory of your assembly archive. It will also copy the current project’s main artifact into the root of the assembly archive, if it exists. Note Wait? I thought dependencySet was about including my project's dependencies, not my project's main archive? This counterintuitive side-effect was a widely-used bug in the 2.1 version of the Assembly plugin, and, because Maven puts an emphasis on backward compatibility, this counterintuitive and incorrect behavior needed to be preserved between a 2.1 and 2.2 release. You can control this behavior by changing the useProjectArtifact flag to false. While the default dependency set can be quite useful with no configuration whatsoever, this section of the assembly descriptor also supports a wide array of configuration options, allowing your to tailor its behavior to your specific requirements. For example, the first thing you might do to the dependency set 304
  • Maven Assemblies above is exclude the current project artifact, by setting the useProjectArtifact flag to false (again, its default value is true for legacy reasons). This will allow you to manage the current project’s build output separately from its dependency files. Alternatively, you might choose to unpack the dependency artifacts using by setting the unpack flag to true (this is false by default). When unpack is set to true, the Assembly plugin will combine the unpacked contents of all matching dependencies inside the archive’s root directory. From this point, there are several things you might choose to do with this dependency set. The next sections discuss how to define the output location for dependency sets and how include and exclude dependencies by scope. Finally, we’ll expand on the unpacking functionality of the dependency set by exploring some advanced options for unpacking dependencies. 12.5.4.1. Customizing Dependency Output Location There are two configuration options that are used in concert to define the location for a dependency file within the assembly archive: outputDirectory and outputFileNameMapping. You may want to customize the location of dependencies in your assembly using properties of the dependency artifacts themselves. Let's say you want to put all the dependencies in directories that match the dependency artifact's groupId. In this case, you would use the outputDirectory element of the dependencySet, and you would supply something like: <assembly> ... <dependencySets> <dependencySet> <outputDirectory>${artifact.groupId}</outputDirectory> </dependencySet> </dependencySets> ... </assembly> This would have the effect of placing every single dependency in a subdirectory that matched the name of each dependency artifact's groupId. If you wanted to perform a further customization and remove the version numbers from all dependencies. You could customize the the output file name for each 305
  • Maven Assemblies dependency using the outputFileNameMapping element as follows: <assembly> ... <dependencySets> <dependencySet> <outputDirectory>${artifact.groupId}</outputDirectory> <outputFileNameMapping> ${artifact.artifactId}.${artifact.extension} </outputFileNameMapping> </dependencySet> </dependencySets> ... </assembly> In the previous example, a dependency on commons:commons-codec version 1.3, would end up in the file commons/commons-codec.jar. 12.5.4.2. Interpolation of Properties in Dependency Output Location As mentioned in the Assembly Interpolation section above, neither of these elements are interpolated with the rest of the assembly descriptor, because their raw values have to be interpreted using additional, artifact-specific expression resolvers. The artifact expressions available for these two elements vary only slightly. In both cases, all of the ${project.*}, ${pom.*}, and ${*} expressions that are available in the POM and the rest of the assembly descriptor are also available here. For the outputFileNameMapping element, the following process is applied to resolve expressions: 1. If the expression matches the pattern ${artifact.*}: a. Match against the dependency’s Artifact instance (resolves: groupId, artifactId, version, baseVersion, scope, classifier, and file.*) b. Match against the dependency’s ArtifactHandler instance (resolves: expression) c. Match against the project instance associated with the dependency’s Artifact (resolves: mainly POM properties) 306
  • Maven Assemblies 2. If the expression matches the patterns ${pom.*} or ${project.*}: a. Match against the project instance (MavenProject) of the current build. 3. If the expression matches the pattern ${dashClassifier?} and the Artifact instance contains a non-null classifier, resolve to the classifier preceded by a dash (-classifier). Otherwise, resolve to an empty string. 4. Attempt to resolve the expression against the project instance of the current build. 5. Attempt to resolve the expression against the POM properties of the current build. 6. Attempt to resolve the expression against the available system properties. 7. Attempt to resolve the expression against the available operating-system environment variables. The outputDirectory value is interpolated in much the same way, with the difference being that there is no available ${artifact.*} information, only the ${project.*} instance for the particular artifact. Therefore, the expressions listed above associated with those classes (1a, 1b, and 3 in the process listing above) are unavailable. How do you know when to use outputDirectory and outputFileNameMapping? When dependencies are unpacked only the outputDirectory is used to calculate the output location. When dependencies are managed as whole files (not unpacked), both outputDirectory and outputFileNameMapping can be used together. When used together, the result is the equivalent of: <archive-root-dir>/<outputDirectory>/<outputFileNameMapping> When outputDirectory is missing, it is not used. When outputFileNameMapping is missing, its default value is: ${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension 307
  • Maven Assemblies 12.5.4.3. Including and Excluding Dependencies by Scope In Section 9.4, “Project Dependencies”, it was noted that all project dependencies have one scope or another. Scope determines when in the build process that dependency normally would be used. For instance, test-scoped dependencies are not included in the classpath during compilation of the main project sources; but they are included in the classpath when compiling unit test sources. This is because your project’s main source code should not contain any code specific to testing, since testing is not a function of the project (it’s a function of the project’s build process). Similarly, provided-scoped dependencies are assumed to be present in the environment of any eventual deployment. However, if a project depends on a particular provided dependency, it is likely to require that dependency in order to compile. Therefore, provided-scoped dependencies are present in the compilation classpath, but not in the dependency set that should be bundled with the project’s artifact or assembly. Also from Section 9.4, “Project Dependencies”, recall that some dependency scopes imply others. For instance, the runtime dependency scope implies the compile scope, since all compile-time dependencies (except for those in the provided scope) will be required for the code to execute. There are a number of complex relationships between the various dependency scopes which control how the scope of a direct dependency affects the scope of a transitive dependency. In a Maven Assembly descriptor, we can use scopes to apply different settings to different sets of dependencies accordingly. For instance, if we plan to bundle a web application with Jetty to create a completely self-contained application, we’ll need to include all provided-scope dependencies somewhere in the jetty directory structure we’re including. This ensures those provided dependencies actually are present in the runtime environment. Non-provided, runtime dependencies will still land in the WEB-INF/lib directory, so these two dependency sets must be processed separately. These dependency sets might look similar to the following XML. Example 12.9. Defining Dependency Sets Using Scope <assembly> 308
  • Maven Assemblies ... <dependencySets> <dependencySet> <scope>provided</scope> <outputDirectory>lib/${project.artifactId}</outputDirectory> </dependencySet> <dependencySet> <scope>runtime</scope> <outputDirectory> webapps/${webContextName}/WEB-INF/lib </outputDirectory> </dependencySet> </dependencySets> ... </assembly> Provided-scoped dependencies are added to the lib/ directory in the assembly root, which is assumed to be a libraries directory that will be included in the Jetty global runtime classpath. We’re using a subdirectory named for the project’s artifactId in order to make it easier to track the origin of a particular library. Runtime dependencies are included in the WEB-INF/lib path of the web application, which is located within a subdirectory of the standard Jetty webapps/ directory that is named using a custom POM property called webContextName. What we've done in the previous example is separate application-specific dependencies from dependencies which will be present in a Servlet contains global classpath. However, simply separating according to scope may not be enough, particularly in the case of a web application. It’s conceivable that one or more runtime dependencies will actually be bundles of standardized, non-compiled resources for use in the web application. For example, consider a set of web application which reuse a common set of Javascript, CSS, SWF, and image resources. To make these resources easy to standardize, it’s a common practice to bundle them up in an archive and deploy them to the Maven repository. At that point, they can be referenced as standard Maven dependencies - possibly with a dependency type of zip - that are normally specified with a runtime scope. Remember, these are resources, not binary dependencies of the application code itself; therefore, it’s not appropriate to blindly include them in the WEB-INF/lib directory. Instead, these resource archives should be separated from binary runtime dependencies, and 309
  • Maven Assemblies unpacked into the web application document root somewhere. In order to achieve this kind of separation, we’ll need to use inclusion and exclusion patterns that apply to the coordinates of a specific dependency. In other words, say you have three or four web application which reuse the same resources and you want to create an assembly that puts provided dependencies into lib/, runtime dependencies into webapps/<contextName>/WEB-INF/lib, and then unpacks a specific runtime dependency into your web application's document root. You can do this because the Assembly allows you to define multiple include and exclude patterns for a given dependencySet element. Read the next section for more development of this idea. 12.5.4.4. Fine Tuning: Dependency Includes and Excludes A resource dependency might be as simple as a set of resources (CSS, Javascript, and Images) in a project that has an assembly which creates a ZIP archive. Depending on the particulars of our web application, we might be able to distinguish resource dependencies from binary dependencies solely according to type. Most web applications are going to depend on other dependencies of type jar, and it is possible that we can state with certainty that all dependencies of type zip are resource dependencies. Or, we might have a situation where resources are stored in jar format, but have a classifier of something like resources. In either case, we can specify an inclusion pattern to target these resource dependencies and apply different logic than that used for binary dependencies. We’ll specify these tuning patterns using the includes and excludes sections of the dependencySet. Both includes and excludes are list sections, meaning they accept the sub-elements include and exclude respectively. Each include or exclude element contains a string value, which can contain wildcards. Each string value can match dependencies in a few different ways. Generally speaking, three identity pattern formats are supported: groupId:artifactId - version-less key You would use this pattern to match a dependency by only the groupId and the artifactId 310
  • Maven Assemblies groupId:artifactId:type[:classifier] - conflict id The pattern allows you to specify a wider set of coordinates to create a more specific include/exclude pattern. groupId:artifactId:type[:classifier]:version - full artifact identity If you need to get really specific, you can specify all the coordinates. All of these pattern formats support the wildcard character ‘*’, which can match any subsection of the identity and is not limited to matching single identity parts (sections between ‘:’ characters). Also, note that the classifier section above is optional, in that patterns matching dependencies that don’t have classifiers do not need to account for the classifier section in the pattern. In the example given above, where the key distinction is the artifact type zip, and none of the dependencies have classifiers, the following pattern would match resource dependencies assuming that they were of type zip: *:zip The pattern above makes use of the second dependency identity: the dependency’s conflict id. Now that we have a pattern that distinguishes resource dependencies from binary dependencies, we can modify our dependency sets to handle resource archives differently: Example 12.10. Using Dependency Excludes and Includes in dependencySets <assembly> ... <dependencySets> <dependencySet> <scope>provided</scope> <outputDirectory>lib/${project.artifactId}</outputDirectory> </dependencySet> <dependencySet> <scope>runtime</scope> <outputDirectory> webapps/${webContextName}/WEB-INF/lib </outputDirectory> <excludes> <exclude>*:zip</exclude> </excludes> </dependencySet> 311
  • Maven Assemblies <dependencySet> <scope>runtime</scope> <outputDirectory> webapps/${webContextName}/resources </outputDirectory> <includes> <include>*:zip</include> </includes> <unpack>true</unpack> </dependencySet> </dependencySets> ... </assembly> In Example 12.10, “Using Dependency Excludes and Includes in dependencySets”, the runtime-scoped dependency set from our last example has been updated to exclude resource dependencies. Only binary dependencies (non-zip dependencies) should be added to the WEB-INF/lib directory of the web application. Resource dependencies now have their own dependency set, which is configured to include these dependencies in the resources directory of the web application. The includes section in the last dependencySet reverses the exclusion from the previous dependencySet, so that resource dependencies are included using the same identity pattern (i.e. *:zip). The last dependencySet refers to the shared resource dependency and it is configured to unpack the shared resource dependency in the document root of the web application. Example 12.10, “Using Dependency Excludes and Includes in dependencySets” was based upon the assumption that our shared resources project dependency had a type which differed from all of the other dependencies. What if the share resource dependency had the same type as all of the other dependencies? How could you differentiate the dependency? In this case if the shared resource dependency had been bundled as a JAR with the classifier resources, you can change to the identity pattern and match those dependencies instead: *:jar:resources Instead of matching on artifacts with a type of zip and no classifier, we’re matching on artifacts with a classifier of resources and a type of jar. Just like the fileSets section, dependencySets support the useStrictFiltering 312
  • Maven Assemblies flag. When enabled, any specified patterns that don’t match one or more dependencies will cause the assembly - and consequently, the build - to fail. This can be particularly useful as a safety valve, to make sure your project dependencies and assembly descriptors are synchronized and interacting as you expect them to. By default, this flag is set to false for the purposes of backward compatibility. 12.5.4.5. Transitive Dependencies, Project Attachments, and Project Artifacts The dependencySet section supports two more general mechanisms for tuning the subset of matching artifacts: transitive selection options, and options for working with project artifacts. Both of these features are a product of the need to support legacy configurations that applied a somewhat more liberal definition of the word “dependency”. As a prime example, consider the project’s own main artifact. Typically, this would not be considered a dependency; yet older versions of the Assembly plugin included the project artifact in calculations of dependency sets. To provide backward compatibility with this “feature”, the 2.2 releases (currently at 2.2-beta-2) of the Assembly plugin support a flag in the dependencySet called useProjectArtifact, whose default value is true. By default, dependency sets will attempt to include the project artifact itself in calculations about which dependency artifacts match and which don’t. If you’d rather deal with the project artifact separately, set this flag to false. Tip The authors of this book recommend that you always set useProjectArtifact to false. As a natural extension to the inclusion of the project artifact, the project’s attached artifacts can also be managed within a dependencySet using the useProjectAttachments flag (whose default value is false). Enabling this flag allows patterns that specify classifiers and types to match on artifacts that are “attached” to the main project artifact; that is, they share the same basic groupId/artifactId/version identity, but differ in type and classifier from the main artifact. This could be useful for including JavaDoc or source jars in an 313
  • Maven Assemblies assembly. Aside from dealing with the project’s own artifacts, it’s also possible to fine-tune the dependency set using two transitive-resolution flags. The first, called useTransitiveDependencies (and set to true by default) simply specifies whether the dependency set should consider transitive dependencies at all when determining the matching artifact set to be included. As an example of how this could be used, consider what happens when your POM has a dependency on another assembly. That assembly (most likely) will have a classifier that separates it from the main project artifact, making it an attachment. However, one quirk of the Maven dependency-resolution process is that the transitive-dependency information for the main artifact is still used when resolving the assembly artifact. If the assembly bundles its project dependencies inside itself, using transitive dependency resolution here would effectively duplicate those dependencies. To avoid this, we simply set useTransitiveDependencies to false for the dependency set that handles that assembly dependency. The other transitive-resolution flag is far more subtle. It’s called useTransitiveFiltering, and has a default value of false. To understand what this flag does, we first need to understand what information is available for any given artifact during the resolution process. When an artifact is a dependency of a dependency (that is, removed at least one level from your own POM), it has what Maven calls a "dependency trail", which is maintained as a list of strings that correspond to the full artifact identities (groupId:artifactId:type:[classifier:]version) of all dependencies between your POM and the artifact that owns that dependency trail. If you remember the three types of artifact identities available for pattern matching in a dependency set, you’ll notice that the entries in the dependency trail - the full artifact identity - correspond to the third type. When useTransitiveFiltering is set to true, the entries in an artifact’s dependency trail can cause the artifact to be included or excluded in the same way its own identity can. If you’re considering using transitive filtering, be careful! A given artifact can be included from multiple places in the transitive-dependency graph, but as of Maven 2.0.9, only the first inclusion’s trail will be tracked for this type of matching. This can lead to subtle problems when collecting the dependencies for your project. 314
  • Maven Assemblies Warning Most assemblies don’t really need this level of control over dependency sets; consider carefully whether yours truly does. Hint: It probably doesn't. 12.5.4.6. Advanced Unpacking Options As we discussed previously, some project dependencies may need to be unpacked in order to create a working assembly archive. In the examples above, the decision to unpack or not was simple. It didn’t take into account what needed to be unpacked, or more importantly, what should not be unpacked. To gain more control over the dependency unpacking process, we can configure the unpackOptions element of the dependencySet. Using this section, we have the ability to choose which file patterns to include or exclude from the assembly, and whether included files should be filtered to resolve expressions using current POM information. In fact, the options available for unpacking dependency sets are fairly similar to those available for including files from the project directory structure, using the file sets descriptor section. To continue our web-application example, suppose some of the resource dependencies have been bundled with a file that details their distribution license. In the case of our web application, we’ll handle third-party license notices by way of a NOTICES file included in our own bundle, so we don’t want to include the license file from the resource dependency. To exclude this file, we simply add it to the unpack options inside the dependency set that handles resource artifacts: Example 12.11. Excluding Files from a Dependency Unpack <asembly> ... <dependencySets> <dependencySet> <scope>runtime</scope> <outputDirectory> webapps/${webContextName}/resources </outputDirectory> <includes> <include>*:zip</include> 315
  • Maven Assemblies </includes> <unpack>true</unpack> <unpackOptions> <excludes> <exclude>**/LICENSE*</exclude> </excludes> </unpackOptions> </dependencySet> </dependencySets> ... </assembly> Notice that the exclude we’re using looks very similar to those used in fileSet declarations. Here, we’re blocking any file starting with the word LICENSE in any directory within our resource artifacts. You can think of the unpack options section as a lightweight fileSet applied to each dependency matched within that dependency set. In other words, it is a fileSet by way of an unpacked dependency. Just as we specified an exclusion pattern for files within resource dependencies in order to block certain files, you can also choose which restricted set of files to include using the includes section. The same code that processes inclusions and exclusions on fileSets has been reused for processing unpackOptions. In addition to file inclusion and exclusion, the unpack options on a dependency set also provides a filtering flag, whose default value is false. Again, this should be familiar from our discussion of file sets above. In both cases, expressions using either the Maven syntax of ${property} or the Ant syntax of @property@ are supported. Filtering is a particularly nice feature to have for dependency sets, though, since it effectively allows you to create standardized, versioned resource templates that are then customized to each assembly as they are included. Once you start mastering the use of filtered, unpacked dependencies which store shared resources, you will be able to start abstracting repeated resources into common resource projects. 12.5.4.7. Summarizing Dependency Sets Finally, it’s worth mentioning that dependency sets support the same fileMode and directoryMode configuration options that file sets do, though you should 316
  • Maven Assemblies remember that the directoryMode setting will only be used when dependencies are unpacked. 12.5.5. moduleSets Sections Multi-module builds are generally stitched together using the parent and modules sections of interrelated POMs. Typically, parent POMs specify their children in a modules section, which under normal circumstances causes the child POMs to be included in the build process of the parent. Exactly how this relationship is constructed can have important implications for the ways in which the Assembly plugin can participate in this process, but we’ll discuss that more later. For now, it’s enough to keep in mind this parent-module relationship as we discuss the moduleSets section. Projects are stitched together into multi-module builds because they are part of a larger system. These projects are designed to be used together, and single module in a larger build has little practical value on its own. In this way, the structure of the project’s build is related to the way we expect the project (and its modules) to be used. If consider the project from the user's perspective, it makes sense that the ideal end goal of that build would be a single, distributable file that the user can consume directly with minimum installation hassle. Since Maven multi-module builds typically follow a top-down structure, where dependency information, plugin configurations, and other information trickles down from parent to child, it seems natural that the task of rolling all of these modules into a single distribution file should fall to the topmost project. This is where the moduleSet comes into the picture. Module sets allow the inclusion of resources that belong to each module in the project structure into the final assembly archive. Just like you can select a group of files to include in an assembly using a fileSet and a dependencySet, you can include a set of files and resources using a moduleSet to refer to modules in a multi-module build. They achieve this by enabling two basic types of module-specific inclusion: file-based, and artifact-based. Before we get into the specifics and differences between file-based and artifact-based inclusion of module resources into an assembly, let’s talk a little about selecting which modules to 317
  • Maven Assemblies process. 12.5.5.1. Module Selection By now, you should be familiar with includes/excludes patterns as they are used throughout the assembly descriptor to filter files and dependencies. When you are referring to modules in an assembly descriptor, you will also use the includes/excludes patterns to define rules which apply to different sets of modules. The difference in moduleSet includes and excludes is that these rules do not allow for wildcard patterns. (As of the 2.2-beta-2 release, this feature has not really seen much demand, so it hasn’t been implemented.) Instead, each include or exclude value is simply the groupId and artifactId for the module, separated by a colon, like this: groupId:artifactId In addition to includes and excludes, the moduleSet also supports an additional selection tool: the includeSubModules flag (whose default value is true). The parent-child relationship in any multi-module build structure is not strictly limited to two tiers of projects. In fact, you can include any number of tiers, or layers, in your build. Any project that is a module of a module of the current project is considered a sub-module. In some cases, you may want to deal with each individual module in the build separately (including sub-modules). For example, this is often simplest when dealing with artifact-based contributions from these modules. To do this, you would simply leave the useSubModules flag set to the default of true. When you’re trying to include files from each module’s directory structure, you may wish to process that module’s directory structure only once. If your project directory structure mirrors that of the parent-module relationships that are included in the POMs, this approach would allow file patterns like **/src/main/java to apply not only to that direct module’s project directory, but also to the directories of its own modules as well. In this case you don’t want to process sub-modules directly (they will be processed as subdirectories within your own project’s modules instead), you should set the useSubModules flag to false. Once we’ve determined how module selection should proceed for the module set 318
  • Maven Assemblies in question, we’re ready to choose what to include from each module. As mentioned above, this can include files or artifacts from the module project. 12.5.5.2. Sources Section Suppose you want to include the source of all modules in your project's assembly, but you would like to exclude a particular module. Maybe you have a project named secret-sauce which contains secret and sensitive code that you don't want to distribute with your project. The simplest way to accomplish this is to use a moduleSet which includes each project's directory in ${module.basedir.name} and which excludes the secret-sauce module from the assembly. Example 12.12. Includes and Excluding Modules with a moduleSet <assembly> ... <moduleSets> <moduleSet> <includeSubModules>false</includeSubModules> <excludes> <exclude> com.mycompany.application:secret-sauce </exclude> </excludes> <sources> <outputDirectoryMapping> ${module.basedir.name} </outputDirectoryMapping> <excludeSubModuleDirectories> false </excludeSubModuleDirectories> <fileSets> <fileSet> <directory>/</directory> <excludes> <exclude>**/target</exclude> </excludes> </fileSet> </fileSets> </sources> </moduleSet> </moduleSets> ... </assembly> 319
  • Maven Assemblies In Example 12.12, “Includes and Excluding Modules with a moduleSet”, since we’re dealing with each module’s sources it’s simpler to deal only with direct modules of the current project, handling sub-modules using file-path wildcard patterns in the file set. We set the includeSubModules element to false so we don't have to worry about submodules showing up in the root directory of the assembly archive. The exclude element will take care of excluding the secret-sauce module. We’re not going to include the project sources for the secret-sauce module; they’re, well, secret. Normally, module sources are included in the assembly under a subdirectory named after the module’s artifactId. However, since Maven allows modules that are not in directories named after the module project’s artifactId, it’s often better to use the expression ${module.basedir.name} to preserve the module directory’s actual name (${module.basedir.name} is the same as calling MavenProject.getBasedir().getName()). It is critical to remember that modules are not required to be subdirectories of the project that declares them. If your project has a particularly strange directory structure, you may need to resort to special moduleSet declarations that include specific project and account for your own project's idiosyncracies. Warning Try to minimize your own project's idiosyncracies, while Maven is flexible, if you find yourself doing too much configuration there is likely an easier way. Continuing through Example 12.12, “Includes and Excluding Modules with a moduleSet”, since we’re not processing sub-modules explicitly in this module set, we need to make sure sub-module directories are not excluded from the source directories we consider for each direct module. By setting the excludeSubModuleDirectories flag to false, this allows us to apply the same file pattern to directory structures within a sub-module of the one we’re processing. Finally in Example 12.12, “Includes and Excluding Modules with a moduleSet”, we’re not interested in any output of the build process for this module set. We exclude the target/ directory from all modules. 320
  • Maven Assemblies It’s also worth mentioning that the sources section supports fileSet-like elements directly within itself, in addition to supporting nested fileSets. These configuration elements are used to provide backward compatibility to previous versions of the Assembly plugin (versions 2.1 and under) that didn’t support multiple distinct file sets for the same module without creating a separate module set declaration. They are deprecated, and should not be used. 12.5.5.3. Interpolation of outputDirectoryMapping in moduleSets In Section 12.5.4.1, “Customizing Dependency Output Location”, we used the element outputDirectoryMapping to change the name of the directory under which each module’s sources would be included. The expressions contained in this element are resolved in exactly the same way as the outputFileNameMapping, used in dependency sets (see the explanation of this algorithm in Section 12.5.4, “dependencySets Section”). In Example 12.12, “Includes and Excluding Modules with a moduleSet”, we used the expression ${module.basedir.name}. You might notice that the root of that expression, module, is not listed in the mapping-resolution algorithm from the dependency sets section; this object root is specific to configurations within moduleSets. It works in exactly the same way as the ${artifact.*} references available in the outputFileNameMapping element, except it is applied to the module’s MavenProject, Artifact, and ArtifactHandler instances instead of those from a dependency artifact. 12.5.5.4. Binaries section Just as the sources section is primarily concerned with including a module in its source form, the binaries section is primarily concerned with including the module’s build output, or its artifacts. Though this section functions primarily as a way of specifying dependencySets that apply to each module in the set, there are a few additional features unique to module artifacts that are worth exploring: attachmentClassifier and includeDependencies. In addition, the binaries section contains options similar to the dependencySet section, that relate to the handling of the module artifact itself. These are: unpack, outputFileNameMapping, outputDirectory, directoryMode, and fileMode. Finally, module binaries can 321
  • Maven Assemblies contain a dependencySets section, to specify how each module’s dependencies should be included in the assembly archive. First, let’s take a look at how the options mentioned here can be used to manage the module’s own artifacts. Suppose we want to include the javadoc jars for each of our modules inside our assembly. In this case, we don’t care about including the module dependencies; we just want the javadoc jar. However, since this particular jar is always going to be present as an attachment to the main project artifact, we need to specify which classifier to use to retrieve it. For simplicity, we won’t cover unpacking the module javadoc jars, since this configuration is exactly the same as what we used for dependency sets earlier in this chapter. The resulting module set might look similar to Example 12.13, “Including JavaDoc from Modules in an Assembly”. Example 12.13. Including JavaDoc from Modules in an Assembly <assembly> ... <moduleSets> <moduleSet> <binaries> <attachmentClassifier>javadoc</attachmentClassifier> <includeDependencies>false</includeDependencies> <outputDirectory>apidoc-jars</outputDirectory> </binaries> </moduleSet> </moduleSets> ... </assembly> In Example 12.13, “Including JavaDoc from Modules in an Assembly”, we don’t explicitly set the includeSubModules flag, since it’s true by default. However, we definitely want to process all modules - even sub-modules - using this module set, since we’re not using any sort of file pattern that could match on sub-module directory structures within. The attachmentClassifier grabs the attached artifact with the javadoc classifier for each module processed. The includeDependencies element tells the Assembly plugin that we're not interested in any of the module's dependencies, just the javadoc attachment. Finally, the outputDirectory element tells the Assembly plugin to put all of the javadoc jars into a directory named apidoc-jars/ off of the assembly root directory. 322
  • Maven Assemblies Although we’re not doing anything too complicated in this example, it’s important to understand that the same changes to the expression-resolution algorithm discussed for the outputDirectoryMapping element of the sources section also applies here. That is, whatever was available as ${artifact.*} inside a dependencySet’s outputFileNameMapping configuration is also available here as ${module.*}. The same applies for outputFileNameMapping when used directly within a binaries section. Finally, let’s examine an example where we simply want to process the module’s artifact and its runtime dependencies. In this case, we want to separate the artifact set for each module into separate directory structures, according to the module’s artifactId and version. The resulting module set is surprisingly simply, and it looks like the listing in Example 12.14, “Including Module Artifacts and Dependencies in an Assembly”: Example 12.14. Including Module Artifacts and Dependencies in an Assembly <assembly> ... <moduleSets> <moduleSet> <binaries> <outputDirectory> ${module.artifactId}-${module.version} </outputDirectory> <dependencySets> <dependencySet/> </dependencySets> </binaries> </moduleSet> </moduleSets> ... </assembly> In Example 12.14, “Including Module Artifacts and Dependencies in an Assembly”, we’re using the empty dependencySet element here, since that should include all runtime dependencies by default, with no configuration. With the outputDirectory specified at the binaries level, all dependencies should be included alongside the module’s own artifact in the same directory, so we don’t even need to specify that in our dependency set. 323
  • Maven Assemblies For the most part, module binaries are fairly straightforward. In both parts - the main part, concerned with handling the module artifact itself, and the dependency sets, concerned with the module’s dependencies - the configuration options are very similar to those in a dependency set. Of course, the binaries section also provides options for controlling whether dependencies are included, and which main-project artifact you want to use. Like the sources section, the binaries section contains a couple of configuration options that are provided solely for backward compatibility, and should be considered deprecated. These include the includes and excludes sub-sections. 12.5.5.5. moduleSets, Parent POMs and the binaries Section Finally, we close the discussion about module handling with a strong warning. There are subtle interactions between Maven’s internal design as it relates to parent-module relationships and the execution of a module-set’s binaries section. When a POM declares a parent, that parent must be resolved in some way or other before the POM in question can be built. If the parent is in the Maven repository, there is no problem. H