SlideShare ist ein Scribd-Unternehmen logo
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. However, as of Maven 2.0.9 this can cause big problems if
that parent is a higher-level POM in the same build, particularly if that parent POM
expects to build an assembly using its modules’ binaries.
Maven 2.0.9 sorts projects in a multi-module build according to their
dependencies, with a given project’s dependencies being built ahead of itself. The
problem is the parent element is considered a dependency, which means the parent
project’s build must complete before the child project is built. If part of that
parent’s build process includes the creation of an assembly that uses module
binaries, those binaries will not exist yet, and therefore cannot be included, causing
the assembly to fail. This is a complex and subtle issue, which severely limits the
usefulness of the module binaries section of the assembly descriptor. In fact, it has
been filed in the bug tracker for the Assembly plugin at:
http://jira.codehaus.org/browse/MASSEMBLY-97. Hopefully, future versions of
Maven will find a way to restore this functionality, since the parent-first
requirement may not be completely necessary.



                                                                                    324
Maven Assemblies


12.5.6. Repositories Section
The repositories section represents a slightly more exotic feature in the assembly
descriptor, since few applications other than Maven can take full advantage of a
Maven-repository directory structure. For this reason, and because many of its
features closely resemble those in the dependencySets section, we won’t spend too
much time on the repositories section of the assembly descriptor. In most cases,
users who understand dependency sets should have no trouble constructing
repositories via the Assembly plugin. We're not going to motivate the
repositories section; we're not going to go through a the business of setting up a
use case and walking you through the process. We're just going to bring up a few
caveats for those of you who find the need to use the repostiories section.
Having said that, there are a two features particular to the repositories section that
deserve some mention. The first is the includeMetadata flag. When set to true it
includes metadata such as the list of real versions that correspond to -SNAPSHOT
virtual versions, and by default it’s set to false. At present, the only metadata
included when this flag is true is the information downloaded from Maven’s
central repository.
The second feature is called groupVersionAlignments. Again, this section is a list
of individual groupVersionAlignment configurations, whose purpose is to
normalize all included artifacts for a particular groupId to use a single version.
Each alignment entry consists of two mandatory elements - id and version - along
with an optional section called excludes that supplies a list of artifactId string
values which are to be excluded from this realignment. Unfortunately, this
realignment doesn’t seem to modify the POMs involved in the repository, neither
those related to realigned artifacts nor those that depend on realigned artifacts, so
it’s difficult to imagine what the practical application for this sort of realignment
would be.
In general, it’s simplest to apply the same principles you would use in dependency
sets to repositories when adding them to your assembly descriptor. While the
repositories section does support the above extra options, they are mainly provided
for backward compatibility, and will probably be deprecated in future releases.


                                                                                   325
Maven Assemblies


12.5.7. Managing the Assembly’s Root Directory
Now that we’ve made it through the main body of the assembly descriptor, we can
close the discussion of content-related descriptor sections with something lighter:
root-directory naming and site-directory handling.
Some may consider it a stylistic concern, but it’s often important to have control
over the name of the root directory for your assembly, or whether the root directory
is there at all. Fortunately, two configuration options in the root of the assembly
descriptor make managing the archive root directory simple:
includeBaseDirectory and baseDirectory. In cases like executable jar files, you
probably don’t want a root directory at all. To skip it, simply set the
includeBaseDirectory flag to false (it’s true by default). This will result in an
archive that, when unpacked, may create more than one directory in the unpack
target directory. While this is considered bad form for archives that are meant to be
unpacked before use, it’s not so bad for archives that are consumable as-is.
In other cases, you may want to guarantee the name of the archive root directory
regardless of the POM’s version or other information. By default, the
baseDirectory element has a value equal to
${project.artifactId}-${project.version}. However, we can easily set this
element to any value that consists of literal strings and expressions which can be
interpolated from the current POM, such as
${project.groupId}-${project.artifactId}. This could be very good news for
your documentation team! (We all have those, right?)
Another configuration available is the includeSiteDirectory flag, whose default
value is false. If your project build has also constructed a website document root
using the site lifecycle or the Site plugin goals, that output can be included by
setting this flag to true. However, this feature is a bit limited, since it only
includes the outputDirectory from the reporting section of the current POM (by
default, target/site) and doesn’t take into consideration any site directories that
may be available in module projects. Use it if you want, but a good fileSet
specification or moduleSet specification with sources configured could serve
equally well, if not better. This is yet another example of legacy configuration
currently supported by the Assembly plugin for the purpose of backward

                                                                                 326
Maven Assemblies

compatibility. Your mileage may vary. If you really want to include a site that is
aggregated from many modules, you'll want to consider using a fileSet or
moduleSet instead of setting includeSiteDirectory to true.



12.5.8. componentDescriptors and
containerDescriptorHandlers
To round out our exploration of the assembly descriptor, we should touch briefly
on two other sections: containerDescriptorHandlers and
componentDescriptors. The containerDescriptorHandlers section refers to
custom components that you use to extend the capabilities of the Assembly plugin.
Specifically, these custom components allow you to define and handle special files
which may need to be merged from the multiple constituents used to create your
assembly. A good example of this might be a custom container-descriptor handler
that merged web.xml files from constituent war or war-fragment files included in
your assembly, in order to create the single web-application descriptor required for
you to use the resulting assembly archive as a war file.
The componentDescriptors section allows you to reference external
assembly-descriptor fragments and include them in the current descriptor.
Component references can be any of the following:

   1. Relative file paths: src/main/assembly/component.xml

   2. Artifact references: groupId:artifactId:version[:type[:classifier]]

   3. Classpath resources: /assemblies/component.xml

    4. URLs: http://www.sonatype.com/component.xml
Incidentally, when resolving a component descriptor, the Assembly plugin tries
those different strategies in that exact order. The first one to succeed is used.
Component descriptors can contain many of the same content-oriented sections
available in the assembly descriptor itself, with the exception of moduleSets,
which is considered so specific to each project that it’s not a good candidate for


                                                                                     327
Maven Assemblies

reuse. Also included in a component descriptor is the
containerDescriptorHandlers section, which we briefly discussed above.
Component descriptors cannot contain formats, assembly id’s, or any configuration
related to the base directory of the assembly archive, all of which are also
considered unique to a particular assembly descriptor. While it may make sense to
allow sharing of the formats section, this has not been implemented as of the
2.2-beta-2 Assembly-plugin release.



12.6. Best Practices
The Assembly plugin provides enough flexibility to solve many problems in a
number of different ways. If you have a unique requirement for your project,
there's a good chance that you can use the methods documented in this chapter to
achieve almost any assembly structure. This section of the chapter details some
common best practices which, if adhered to, will make your experiences with the
assembly plugin more productive and less painful.


12.6.1. Standard, Reusable Assembly Descriptors
Up to now, we’ve been talking mainly about one-off solutions for building a
particular type of assembly. But what do you do if you have dozens of projects that
all need a particular type of assembly? In short, how can we reuse the effort we’ve
invested to get our assemblies just the way we like them across more than one
project without copying and pasting our assembly descriptor?
The simplest answer is to create a standardized, versioned artifact out of the
assembly descriptor, and deploy it. Once that’s done, you can specify that the
Assembly plugin section of your project’s POM include the assembly-descriptor
artifact as a plugin-level dependency, which will prompt Maven to resolve and
include that artifact in the plugin’s classpath. At that point, you can use the
assembly descriptor via the descriptorRefs configuration section in the Assembly
plugin declaration. To illustrate, consider this example assembly descriptor:
<assembly>
  <id>war-fragment</id>
  <formats>

                                                                               328
Maven Assemblies

    <format>zip</format>
  </formats>
 <includeBaseDirectory>false</includeBaseDirectory>
  <dependencySets>
    <dependencySet>
      <outputDirectory>WEB-INF/lib</outputDirectory>
    </dependencySet>
  </dependencySets>
  <fileSets>
    <fileSet>
      <directory>src/main/webapp</directory>
      <outputDirectory>/</outputDirectory>
      <excludes>
        <exclude>**/web.xml</exclude>
      </excludes>
    </fileSet>
  </fileSets>
</assembly>


Included in your project, this descriptor would be a useful way to bundle the
project contents so that it could be unpacked directly into an existing web
application in order to add to it (for adding an extending feature, say). However, if
your team builds more than one of these web-fragment projects, it will likely want
to reuse this descriptor rather than duplicating it. To deploy this descriptor as its
own artifact, we’re going to put it in its own project, under the
src/main/resources/assemblies directory.

The project structure for this assembly-descriptor artifact will look similar to the
following:
|-- pom.xml
`-- src
    `-- main
        `-- resources
             `-- assemblies
                 `-- web-fragment.xml


Notice the path of our web-fragment descriptor file. By default, Maven includes
the files from the src/main/resources directory structure in the final jar, which
means our assembly descriptor will be included with no extra configuration on our
part. Also, notice the assemblies/ path prefix, the Assembly plugin expects this
path prefix on all descriptors provided in the plugin classpath. It’s important that
we put our descriptor in the appropriate relative location, so it will be picked up by
the Assembly plugin as it executes.

                                                                                   329
Maven Assemblies

Remember, this project is separate from your actual web-fragment project now;
the assembly descriptor has become its own artifact with its own version and,
possibly, its own release cycle. Once you install this new project using Maven,
you’ll be able to reference it in your web-fragment projects. For clarity, the build
process should look something like this:
$ mvn install
(...)
[INFO] [install:install]
[INFO] Installing (...)/web-fragment-descriptor/target/
                  web-fragment-descriptor-1.0-SNAPSHOT.jar
       to /Users/~/.m2/repository/org/sonatype/mavenbook/assemblies/
          web-fragment-descriptor/1.0-SNAPSHOT/
          web-fragment-descriptor-1.0-SNAPSHOT.jar
[INFO] ---------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ---------------------------------------------------------------
[INFO] Total time: 5 seconds
(...)


Since there are no sources for the web-fragment-descriptor project, the resulting
jar artifact will include nothing but our web-fragment assembly descriptor. Now,
let’s use this new descriptor artifact:
<project>
  (...)
  <artifactId>my-web-fragment</artifactId>
  (...)
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>2.2-beta-2</version>
        <dependencies>
          <dependency>
            <groupId>org.sonatype.mavenbook.assemblies</groupId>
            <artifactId>web-fragment-descriptor</artifactId>
            <version>1.0-SNAPSHOT</version>
          </dependency>
        </dependencies>
        <executions>
          <execution>
            <id>assemble</id>
            <phase>package</phase>
            <goals>
               <goal>single</goal>
            </goals>
            <configuration>
               <descriptorRefs>

                                                                                  330
Maven Assemblies

                 <descriptorRef>web-fragment</descriptorRef>
               </descriptorRefs>
             </configuration>
           </execution>
        </executions>
      </plugin>
      (...)
    </plugins>
  </build>
  (...)
</project>


Two things are special about this Assembly plugin configuration:

   • We have to include a plugin-level dependency declaration on our new
     web-fragment-descriptor artifact in order to have access to the assembly
     descriptor via the plugin’s classpath.

    • Since we’re using a classpath reference instead of a file in the local project
       directory structure, we must use the descriptorRefs section instead of the
       descriptor section. Also, notice that, while the assembly descriptor is
       actually in the assemblies/web-fragment.xml location within the plugin’s
       classpath, we reference it without the assemblies/ prefix. This is because
       the Assembly plugin assumes that built-in assembly descriptors will always
       reside in the classpath under this path prefix.
Now, you’re free to reuse the POM configuration above in as many projects as you
like, with the assurance that all of their web-fragment assemblies will turn out the
same. As you need to make adjustments to the assembly format - maybe to include
other resources, or to fine-tune the dependency and file sets - you can simply
increment the version of the assembly descriptor’s project, and release it again.
POMs referencing the assembly-descriptor artifact can then adopt this new version
of the descriptor as they are able.
One final point about assembly-descriptor reuse: you may want to consider sharing
the plugin configuration itself as well as publishing the descriptor as an artifact.
This is a fairly simple step; you simply add the configuration listed above to the
pluginManagement section of your parent POM, then reference the managed plugin
configuration from your module POM like this:
(...)

                                                                                331
Maven Assemblies

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
      </plugin>
(...)


If you’ve added the rest of the plugin’s configuration - listed in the previous
example - to the pluginManagement section of the project’s parent POM, then each
project inheriting from that parent POM can add a minimal entry like the one
above and take advantage of an advanced assembly format in their own builds.


12.6.2. Distribution (Aggregating) Assemblies
As mentioned above, the Assembly plugin provides multiple ways of creating
many archive formats. Distribution archives are typically very good examples of
this, since they often combine modules from a multi-module build, along with their
dependencies and possibly, other files and artifacts besides these. The distribution
aims to include all these different sources into a single archive that the user can
download, unpack, and run with convenience. However, we also examined some of
the potential drawbacks of using the moduleSets section of the assembly descriptor
- namely, that the parent-child relationships between POMs in a build can prevent
the availability of module artifacts in some cases.
Specifically, if module POMs reference as their parent the POM that contains the
Assembly-plugin configuration, that parent project will be built ahead of the
module projects when the multi-module build executes. The parent’s assembly
expects to find artifacts in place for its modules, but these module projects are
waiting on the parent itself to finish building, a gridlock situation is reached and
the parent build cannot succeed (since it’s unable to find artifacts for its module
projects). In other words, the child project depends on the parent project which in
turn depends on the child project.
As an example, consider the assembly descriptor below, designed to be used from
the top-level project of a multi-module hierarchy:
<assembly>
  <id>distribution</id>
  <formats>

                                                                                  332
Maven Assemblies

    <format>zip</format>
    <format>tar.gz</format>
    <format>tar.bz2</format>
  </formats>

  <moduleSets>
    <moduleSet>
      <includes>
        <include>*-web</include>
      </includes>
      <binaries>
        <outputDirectory>/</outputDirectory>
        <unpack>true</unpack>
        <includeDependencies>true</includeDependencies>
        <dependencySets>
          <dependencySet>
            <outputDirectory>/WEB-INF/lib</outputDirectory>
          </dependencySet>
        </dependencySets>
      </binaries>
    </moduleSet>
    <moduleSet>
      <includes>
        <include>*-addons</include>
      </includes>
      <binaries>
        <outputDirectory>/WEB-INF/lib</outputDirectory>
        <includeDependencies>true</includeDependencies>
        <dependencySets>
          <dependencySet/>
        </dependencySets>
      </binaries>
    </moduleSet>
  </moduleSets>
</assembly>


Given a parent project - called app-parent - with three modules called app-core,
app-web, and app-addons, notice what happens when we try to execute this
multi-module build:
$ mvn package
[INFO] Reactor build order:
[INFO]   app-parent <----- PARENT BUILDS FIRST
[INFO]   app-core
[INFO]   app-web
[INFO]   app-addons
[INFO] ---------------------------------------------------------------
[INFO] Building app-parent
[INFO]    task-segment: [package]
[INFO] ---------------------------------------------------------------
[INFO] [site:attach-descriptor]


                                                                               333
Maven Assemblies

[INFO] [assembly:single {execution: distro}]
[INFO] Reading assembly descriptor: src/main/assembly/distro.xml
[INFO] ---------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ---------------------------------------------------------------
[INFO] Failed to create assembly: Artifact:
org.sonatype.mavenbook.assemblies:app-web:jar:1.0-SNAPSHOT (included by module)
does not have an artifact with a file. Please ensure the package phase is
run before the assembly is generated.
...


The parent project - app-parent - builds first. This is because each of the other
projects lists that POM as its parent, which causes it to be forced to the front of the
build order. The app-web module, which is the first module to be processed in the
assembly descriptor, hasn’t been built yet. Therefore, it has no artifact associated
with it, and the assembly cannot succeed.
One workaround for this is to remove the executions section of the
Assembly-plugin declaration, that binds the plugin to the package lifecycle phase
in the parent POM, keeping the configuration section intact. Then, execute Maven
with two command-line tasks: the first, package, to build the multi-module project
graph, and a second, assembly:assembly, as a direct invocation of the assembly
plugin to consume the artifacts built on the previous run, and create the distribution
assembly. The command line for such a build might look like this:
$ mvn package assembly:assembly


However, this approach has several drawbacks. First, it makes the
distribution-assembly process more of a manual task that can increase the
complexity and potential for error in the overall build process significantly.
Additionally, it could mean that attached artifacts - which are associated in
memory as the project build executes - are not reachable on the second pass
without resorting to file-system references.
Instead of using a moduleSet to collect the artifacts from your multi-module build,
it often makes more sense to employ a low-tech approach: using a dedicated
distribution project module and inter-project dependencies. In this approach, you
create a new module in your build whose sole purpose is to assemble the
distribution. This module POM contains dependency references to all the other
modules in the project hierarchy, and it configures the Assembly plugin to be

                                                                                   334
Maven Assemblies

bound the package phase of its build lifecycle. The assembly descriptor itself uses
the dependencySets section instead of the moduleSets section to collect module
artifacts and determine where to include them in the resulting assembly archive.
This approach escapes the pitfalls associated with the parent-child relationship
discussed above, and has the additional advantage of using a simpler configuration
section within the assembly descriptor itself to do the job.
To do this, we can create a new project structure that’s very similar to the one used
for the module-set approach above, with the addition of a new distribution project,
we might end up with five POMs in total: app-parent, app-core, app-web,
app-addons, and app-distribution. The new app-distribution POM looks
similar to the following:
<project>
  <parent>
    <artifactId>app-parent</artifactId>
    <groupId>org.sonatype.mavenbook.assemblies</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>app-distribution</artifactId>
  <name>app-distribution</name>

  <dependencies>
    <dependency>
      <artifactId>app-web</artifactId>
      <groupId>org.sonatype.mavenbook.assemblies</groupId>
      <version>1.0-SNAPSHOT</version>
      <type>war</type>
    </dependency>
    <dependency>
      <artifactId>app-addons</artifactId>
      <groupId>org.sonatype.mavenbook.assemblies</groupId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- Not necessary since it's brought in via app-web.
    <dependency> [2]
      <artifactId>app-core</artifactId>
      <groupId>org.sonatype.mavenbook.assemblies</groupId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    -->
  </dependencies>
</project>


Notice that we have to include dependencies for the other modules in the project

                                                                                 335
Maven Assemblies

structure, since we don’t have a modules section to rely on in this POM. Also,
notice that we’re not using an explicit dependency on app-core. Since it’s also a
dependency of app-web, we don’t need to process it (or, avoid processing it) twice.
Next, when we move the distro.xml assembly descriptor into the
app-distribution project, we must also change it to use a dependencySets
section, like this:
<assembly>
  ...
  <dependencySets>
    <dependencySet>
      <includes>
        <include>*-web</include>
      </includes>
      <useTransitiveDependencies>false</useTransitiveDependencies>
      <outputDirectory>/</outputDirectory>
      <unpack>true</unpack>
    </dependencySet>
    <dependencySet>
      <excludes>
        <exclude>*-web</exclude>
      </excludes>
      <useProjectArtifact>false</useProjectArtifact>
      <outputDirectory>/WEB-INF/lib</outputDirectory>
    </dependencySet>
  </dependencySets>
  ...
</assembly>


This time, if we run the build from the top-level project directory, we get better
news:
$ mvn package
(...)
[INFO] ---------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ---------------------------------------------------------------
[INFO] module-set-distro-parent ...............SUCCESS [3.070s]
[INFO] app-core .............................. SUCCESS [2.970s]
[INFO] app-web ............................... SUCCESS [1.424s]
[INFO] app-addons ............................ SUCCESS [0.543s]
[INFO] app-distribution ...................... SUCCESS [2.603s]
[INFO] ---------------------------------------------------------------
[INFO] ---------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ---------------------------------------------------------------
[INFO] Total time: 10 seconds
[INFO] Finished at: Thu May 01 18:00:09 EDT 2008


                                                                                     336
Maven Assemblies

[INFO] Final Memory: 16M/29M
[INFO] ---------------------------------------------------------------


As you can see, the dependency-set approach is much more stable and - at least
until Maven’s internal project-sorting logic catches up with the Assembly plugin’s
capabilities, - involves less opportunity to get things wrong when running a build.



12.7. Summary
As we’ve seen in this chapter, the Maven Assembly plugin offers quite a bit of
potential for creating custom archive formats. While the details of these assembly
archives can be complex, they certainly don’t have to be in all cases - as we saw
with built-in assembly descriptors. Even if your aim is to include your project’s
dependencies and selected project files in some unique, archived directory
structure, writing a custom assembly descriptor doesn’t have to be an arduous task.
Assemblies are useful for a wide array of applications, but are most commonly
used as application distributions of various sorts. And, while there are many
different ways to use the Assembly plugin, using standardized assembly-descriptor
artifacts and avoiding moduleSets when creating distributions containing binaries
are two sure ways to avoid problems.




                                                                                337
Chapter 13. Properties and Ressource
Filterung

13.1. Einleitung
In diesem Buch finden Sie durchgängig Hinweise auf Properties (Eigenschaften)
welche Sie in POM-Dateien referenzieren können. Auf geerbte Abhängigkeiten
eines multi-projekt Builds kann mittels der Properties ${project.groupId} sowie
${project.version} zugegriffen werden und jeder Abschnitt eines POM kann
referenziert werden indem man einer Variablen die Vorsilbe „project.“ Anfügt.
Umgebungsvariablen und Java System Einstellungen können referenziert werden,
wie auch alle Einstellungen aus Ihrer ~/.m2/settings.xml Datei. Was Ihnen noch
nicht auf den Weg gegeben wurde ist eine Aufstellung der möglichen Property
Variablen, und eine Beschreibung wie diese zum Einsatz kommen um einen
portablen Build zu erstellen. Dieses Kapitel nun liefert Ihnen diese Aufstellung.
Sollten Sie bereits Property Referenzen in Ihrem POM einsetzen, so sollte Ihnen
ebenfalls bewusst sein, dass Maven eine Funktionalität mit dem Namen Ressource
Filter anbietet, welche Ihnen erlaubt, jegliche Property Referenz zu ändern, welche
in einer Ressource Datei unter src/main/resources abgelegt ist. Standardmässig
ist diese Funktion deaktiviert, um unbeabsichtigte Veränderung von Property
Referenzen zu verhindern. Diese Funktionalität kann jedoch Einsatz finden, um
einen Build auf eine bestimmte Plattform zu richten, oder um wichtige Property
Einstellungen in Ressource Dateien, POMs oder Profilen zu externalisieren. Dieses
Kapitel hat zum Ziel, die Funktionalität des Ressource Filtering darzustellen und
gibt eine kurze Einführung wie diese zum Einsatz kommt um einen portablen
Enterprise Build herzustellen.



13.2. Maven Properties
Sie können Maven Properties in einer pom.xml Datei, oder irgendeiner anderen

                                                                               338
Properties and Ressource Filterung

Ressource ablegen, welche vom Maven Ressource Plugin und dessen Filter
Funktion verarbeitet wird. Ein Property ist immer umschlossen von ${ und } . Um
auf das Property project.version zu referenzieren, würden Sie folgende Variable
benutzen:
${project.version}


Es bestehen einige implizite Maven Properties, diese sind:

   project.*
   Maven Project Objekt Modell (POM). Benutzen Sie das Präfix project.* um
   Werte des Maven POM zu referenzieren.

   settings.*
   Maven Settings; Benutzen Sie das Präfix settings.* um Werte aus der lokalen
   Maven Settings Datei (gewöhnlich unter ~/.m2/settings.xml zu referenzieren.

   env.*
   Umgebungsvariables wie z.B. PATH sowie M2_HOME können mittels dem Präfix
   env.* benannte werden.

    System Properties
   Jeglicher Wert welcher durch die System.getProperty() Methode abgefragt
   werden kann, kann als Maven Property referenziert werden.
Zusätzlich zu den impliziten Properties wie oben dargestellt, können das Maven
POM, die Maven Settings Datei sowie das Maven Profil weitere freidefinierte
Werte enthalten. Der folgende Abschnitt wird Ihnen einigen Einblick in die
verschiedenen in einem Maven Projekt verfügbaren Werte geben.


13.2.1. Maven Projekt Einstellungen
Immer wenn ein Maven Projekt Property referenziert wird, zeigt der Name des
Property auf einen Wert des Projekt Objekt Modell (POM). Genauer gesagt, Sie
setzen eine Referenz auf ein Property der Klasse org.apache.maven.model.Model
welche als implizite Projektvariable dargestellt wird. Wollen Sie sich auf ein


                                                                             339
Properties and Ressource Filterung

Property dieser impliziten Variablen beziehen, so wenden Sie die einfache
Punkt-Notation an, um auf ein Bean-Property des Modell-Objektes zu zeigen.
Referenzieren Sie zum Beispiel ${project.version}, so rufen Sie tatsächlich die
Methode getVersion() der Instanz des Modell Objektes auf, als welches das
Projekt offengelegt wird.
Das POM wird ebenfalls durch die Datei pom.xml dargestellt, welche in allen
Maven Projekten präsent ist. Alle Bestandteile des Maven POM können als
Property angesprochen werden. Eine komplette Referenz der POM Struktur finden
Sie unter http://maven.apache.org/ref/2.0.9/maven-model/maven.html. In der
folgenden Liste werden wir einige der häufiger gebrauchten Referenzen auf Maven
Projekt Properties ausführen:

   project.groupId, project.version
   Projekte die Bestandteil eines grossen multi-modularen Build sind, benutzen
   häufig die selbe groupId und version Bezeichner. Sollten Sie Abhängigkeiten
   zwischen verschiedenen Modulen deklarieren welche die gleiche groupId und
   version tragen, so tun Sie gut daran, für beide eine Property Referenz zu
   definieren:
<dependencies>
  <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>sibling-project</artifactId>
    <version>${project.version}</version>
  </dependency>
</dependencies>



   project.artifactId
   Die artifactId eines Projektes wird oft als Name eines Paketes eingesetzt. So
   möchten Sie, im Falle eines Projektes das als WAR paketiert wird, ein WAR
   ohne Versionsbezug im Namen generieren. Um dies zu bewerkstelligen würden
   Sie project.artifactId wie folgt referenzieren:
<build>
  <finalName>${project.artifactId}</finalName>
</build>




                                                                            340
Properties and Ressource Filterung


   project.name, project.description
   Der Name und die Projektbeschreibung sind ofmals nützliche Referenzen in
   einer Dokumentation. Anstatt sich damit abzumüchen, dass der gesamte Site die
   gleiche Kurzbeschreibung benutzt, ist es viel einfacher diese Werte zu
   referenzieren.

   project.build.*
   Sollten Sie sich auf Ausgabeverzeichnisse berufen müssen, so vermeiden Sie
   den Einsatz von direkten Bezügen zu den Verzeichnissen wie z.B.
   target/classes. Statt dessen sollten Sie Property Referenzen auf die
   Verzeichnisse setzen.

   •   project.build.sourceDirectory

   •   project.build.scriptSourceDirectory

   •   project.build.testSourceDirectory

   •   project.build.outputDirectory

   •   project.build.testOutputDirectory

   •   project.build.directory
   sourceDirectory, scriptSourceDirectory,        und testSourceDirectory geben
   Ihnen Zugriff auf die Quellverzeichnisse des Projektes. outputDirectory sowie
   testOutputDirectory geben Ihnen Zugriff auf die Verzeichnisse, welche
   Maven nutzt um den Byte Code oder Build Output abzulegen. directory
   bezieht sich auf das Verzeichnis, das alle derartigen Ausgabeverzeichnisse
   umfasst.

    Weitere Projekt Property Referenzes
   Darüber hinaus gibt es hunderte Properties welche man innerhalb eines POM
   referenzieren kann. Eine vollständige Referenz des POM finden Sie unter
   http://maven.apache.org/ref/2.0.9/maven-model/maven.html.
Sollten Sie eine vollständige Dokumentation der Maven Modell Objekt Properties

                                                                            341
Properties and Ressource Filterung

benötigen, so bitte wir Sie auf die JavaDoc des Projektes maven-model
zurückzugreifen, diese finden Sie unter:
http://maven.apache.org/ref/2.0.9/maven-model/apidocs/index.html. Nach dem
Laden der JavaDoc, sehen Sie sich die Klasse Model an. Von dieser Klasse aus
sollten Sie alle von Ihnen gesuchten POM Properties ansteuern können. Sollten Sie
das Ausgabeverzeichnis des Builds referenzieren wollen, so sehen Sie in der
Maven Model JavaDoc, dass dieses mittels
model.getBuild().getOutputDirectory(); erfolgt. Diesen Methodenaufruf
würden Sie in die Maven Property Referenz:
${project.build.outputDirectory} wiederfinden.

Bezüglich weiterer Informationen zum Maven Model Module, dem Modul,
welches die Struktur des POM definiert, möchten wir auf die Maven Model Seite
unter http://maven.apache.org/ref/2.0.9/maven-model verweisen.


13.2.2. Properties der Maven Einstellungen
(settings.xml)
Sie können auf jedes Property der lokalen Einstellungsdatei zugreifen, welche
normalerweise unter ~/.m2/settings.xml abgelegt ist. Diese Datei enthält
benutzerspezifische Einstellungen bezüglich der Maven Installation wie z.B. dem
Ort des lokalen Repository, der lokalen Server Infrastruktur, Profilen sowie
Spiegel-Repositorien wie diese von einem Benutzer definiert wurden.
Eine ausführliche Referenz der lokalen Einstellungen sowie der entsprechenden
Properties finden Sie unter
http://maven.apache.org/ref/2.0.9/maven-settings/settings.html.


13.2.3. Properties der Umgebungsvariablen
Umgebungsvariablen werden mit einem vorangestellten env.* eingeleitet. Einige
der interessanteren Umgebungsvariablen sind in der folgenden Liste aufgeführt:

   env.PATH
   Enthält den aktuellen Pfad (PATH)in welchem Maven läuft. PATH enthält eine

                                                                                342
Properties and Ressource Filterung

   Liste von Verzeichnissen, in welchen ausführbare Skripte und Programme
   gesucht werden sollen.

   env.HOME
   (Unter *nix Systemen) zeigt diese Variable auf das “Heimatverzeichnis” des
   Benutzers. Besser und portabler ist es statt dessen ${user.home} einzusetzen.

   env.JAVA_HOME
   Enthält das Java Installationsverzeichnis. Dieses wird entweder auf einen Java
   Development Kit (JDK) oder auf eine Java Laufzeitumgebung (JRE) zeigen. Es
   empfiehlt sich statt dessen ${java.home} zu benutzen.

   env.M2_HOME
   Beinhaltet das Maven 2 Installationsverzeichnis
Solange die Java System Einstellungen verfügbar sind, sollten Sie diese einsetzen.
Um auf das Heimatverzeichnis eines Benutzers zuzugreifen benutzen Sie
${user.home} statt ${env.HOME}. Sie erzeugen damit einen portablerern Build
welcher dem Versprechen von Write-One-Run-Anywhere (WORA) der Java
Plattform näherkommt.


13.2.4. Java System Properties
Maven legt alle Properties von java.lang.System offen. Alle Daten, welche Sie
mittels System.getProperty() beziehen können, sind auch für Maven angreifbar.
Die nachfolgende Tabelle führt die verfügbaren Properties auf:

Table 13.1. Java System Properties

System Property                             Beschreibung
java.version                                Java Laufzeitumgebung Version
java.vendor                                 Java Laufzeitumgebung Hersteller
java.vendor.url                             Java Hersteller URL
java.home                                   Java Installationsverzeichnis

                                                                               343
Properties and Ressource Filterung

System Property                         Beschreibung
java.vm.specification.version           Java Virtual Machine Spezifikation
                                        Version
java.vm.specification.vendor            Java Virtual Machine Spezifikation
                                        Hersteller
java.vm.specification.name              Java Virtual Machine Spezifikation Name
java.vm.version                         Java Virtual Machine Implementierung
                                        Version
java.vm.vendor                          Java Virtual Machine Implementierung
                                        Hersteller
java.vm.name                            Java Virtual Machine Implementierung
                                        Name
java.specification.version              Java Laufzeitumgebung Spezifikation
                                        Version
java.specification.vendor               Java Laufzeitumgebung Spezifikation
                                        Hersteller
java.specification.name                 Java Laufzeitumgebung Spezifikation
                                        Name
java.class.version                      Java Klassenformat Versionsnummber
java.class.path                         Java Klassenpfad
java.ext.dirs                           Pfad zum Verzeichnis der Erweiterungen
os.name                                 Betriebssystem Name
os.arch                                 Betriebssystem System Architekture
os.version                              Betriebssystem System Version
file.separator                          Datei Trennzeichen ("/" auf UNIX, "" auf
                                        Windows)


                                                                         344
Properties and Ressource Filterung

System Property                             Beschreibung
path.separator                              Pfad Trennzeichen (":" auf UNIX, ";" auf
                                            Windows)
line.separator                              Zeilenumbruch ("n" auf UNIX und
                                            Windows)
user.name                                   Benutzerkonto Name
user.home                                   Benutzerverzeichnis (home)
user.dir                                    Aktuelles Arbeitsverzeichnis (des
                                            Benutzers)



13.2.5. Benuzerdefinierte Properties
Zusätzlich zu den implizit von POM, den Maven Einstellungen,
Umgebungsvariablen und Java System Einstellungen bereitgestellten Properties
haben Sie die Möglichkeit Ihre eigenen beliebigen Properties zu setzen. Properties
können als Teil eines POM oder Profil definiert werden. Die Menge der Properties
eines POMs oder Maven Profils kann wie alle anderen Properties in Maven
angesprochen werden. Benutzerdefinierte Properties können innerhalb einer POM
Datei referenziert werden, oder Sie können eingesetzt werden um Ressourcen
mittels dem Maven Resource Plugin zu filtern. Hier ein Beispiel wie man ein
beliebiges Property innnerhalb einer POM Datei definiert:

Example 13.1. Definition eines benutzerdefinierten Property im POM
<project>
  ...
  <properties>
    <arbitrary.property.a>This is some text</arbitrary.property.a>
    <hibernate.version>3.3.0.ga</hibernate.version>
  </properties>
  ...
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>


                                                                                345
Properties and Ressource Filterung

      <version>${hibernate.version}</version>
    </dependency>
  </dependencies>
  ...
</project>



Das vorgehende Beispiel definiert zwei Properties: arbitrary.property.a und
hibernate.version. hibernate.version wird innerhalb einer
Abhängigkeitsdefinition eingesetzt. Der Einsatz des Punkt-Zeichens (.) als
Trennzeichen in Propertynamen hat sich als gängige Schreibweise für Properties
durchgesetzt; Für Maven ist „hibernate.version“ lediglich ein Schlüssel um den
Wert „3.3.0.ga“ aufzufinden.

Example 13.2. Definition eines benutzerdefinierten Property innerhalb einer
Profildefinition im POM
<project>
  ...
  <profiles>
    <profile>
      <id>some-profile</id>
      <properties>
        <arbitrary.property>This is some text</arbitrary.property>
      </properties>
    </profile>
  </profiles>
  ...
</project>



Das vorangegangene Beispiel verdeutlicht wie man ein benutzerdefiniertes
Property innerhalb einer Profildefinition eines Maven POM definiert. Weitere
Details und Informationen bezüglich benutzerdefinierter Properties und Profilen
finden sie im Chapter 11, Build Profiles (Kapitel 11: Build Profile).



13.3. Ressource Filterung
Sie können Maven auch dazu einsetzen, Variablen innerhalb von Projektressourcen
zu ersetzen. Sobald Ressourcen Filterung aktiviert ist, durchsucht Maven

                                                                              346
Properties and Ressource Filterung

Ressourcen nach Property Referenzen eingeschlossen in ${ und}. Sobald es eine
derartige Referenz gefunden hat, ersetzt es diese mit dem entsprechenden Wert
ganz ähnlich wie dies auch mit den Property Referenzen eines POM geschieht und
im letzen Abschnitt beschrieben wurde. Diese Möglichkeit ist insbesondere dann
hilfreich, wenn es notwendig wird einen Build zu parametrisieren um verschiede
Konfigurationen zu übergeben, abhängig von der entsprechenden Zielplattform.
Oftmals wird eine Datei .properties oder ein XML Dokument unter
/src/main/resources die Definition externer Ressource wie
Datenbankverbindungen oder Netzwerkparameter enthalten, welche abhängig von
der Zielplatform unterschiedlich konfiguriert werden müssen. So hat z.B. ein
System welches von einer Datenbank lesen muss eine Datei welche die JDBC
URL zusammen mit den entsprechenden Parametern für den Datenbankzugriff
angibt. Benutzen Sie eine andere Datenbank während der Entwicklung als in der
Produktion (was zu erwarten ist), so können Sie entweder diese
Ressourcedefinition mittels einer Technologie wie z.B. JNDI externalisieren, oder
aber Sie generieren einen Build welcher es versteht, die Zielplattform abhängigen
Werte mit den entsprechenden Werten zu setzen.
Unter Einsatz von Maven Ressource Filterung können Sie Maven Properties setzen
und diese dann mittels Maven Profilen entsprechend der benötigten Zielplattform
definieren. Um diese Möglichkeit zu illustrieren, hier ein Beispiel: gehen Sie
davon aus, dass Ihr Projekt auf dem Spring Framework beruht, welches eine
BasicDataSource aus dem CommonsDBCP Projekt konfiguriert. Ihr Projekt wird also
unter src/main/resources eine Datei mit dem Namen applicationContext.xml
ablegen, welche die in Example 13.3, “Maven Properties aus einer Ressource Datei
referenzieren” (Beispiel 13.3: Maven Properties aus einer Ressource Datei
referenzieren) ausgeführten Daten enthält.

Example 13.3. Maven Properties aus einer Ressource Datei referenzieren
<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.5.xsd">

    <bean id="someDao" class="com.example.SomeDao">
        <property name="dataSource" ref="dataSource"/>


                                                                              347
Properties and Ressource Filterung

    </bean>

    <bean id="dataSource" destroy-method="close"
              class="org.apache.commons.dbcp.BasicDataSource">
         <property name="driverClassName" value="${jdbc.driverClassName}"/>
         <property name="url" value="${jdbc.url}"/>
         <property name="username" value="${jdbc.username}"/>
         <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>



Ihr Programm würde diese Datei zur Laufzeit lesen, und Ihr Build wird Referenzen
auf Properties wie z.B. jdbc.url oder jdbc.username mit den Werten welche Sie
in Ihrer pom.xml-Datei setzen ersetzen. Ressource Filterung ist standardmässig
ausgeschaltet, um die unbewusste und ungewollte Filterung von Werten zu
verhindern. Um den Ressource Filter einzustellen, müssen Sie das abgeleitete
Element resource innerhalb ihrer POM Datei setzen. Example 13.4, “Definieren
von Variablen und Aktivierung von Ressource Filterung” (Beispiel 13.4:
Definieren von Variablen und Aktivierung von Ressource Filterung) illustriert ein
POM welches die Variablen aus Example 13.3, “Maven Properties aus einer
Ressource Datei referenzieren” (Beispiel 13.3: Maven Properties aus einer
Ressource Datei referenzieren) definiert und Ressource Filterung für alle
Ressourcen unter src/main/resources aktiviert

Example 13.4. Definieren von Variablen und Aktivierung von Ressource
Filterung
<project>
  ...
  <properties>
    <jdbc.driverClassName>
      com.mysql.jdbc.Driver</jdbc.driverClassName>
    <jdbc.url>jdbc:mysql://localhost:3306/development_db</jdbc.url>
    <jdbc.username>dev_user</jdbc.username>
    <jdbc.password>s3cr3tw0rd</jdbc.password>
  </properties>
  ...
  <build>
    <resources>
      <resource>src/main/resources</resource>
      <filtering>true</filtering>
    </resources>
  </build>

                                                                              348
Properties and Ressource Filterung

  ...
  <profiles>
    <profile>
      <id>production</id>
      <properties>
        <jdbc.driverClassName>oracle.jdbc.driver.OracleDriver</jdbc.driverClassName>
        <jdbc.url>jdbc:oracle:thin:@proddb01:1521:PROD</jdbc.url>
        <jdbc.username>prod_user</jdbc.username>
        <jdbc.password>s00p3rs3cr3t</jdbc.password>
      </properties>
    </profile>
  </profiles>
</project>



Die vier entsprechenden Variablen werden jeweils innerhalb des Elements
properties definiert und Ressource Filterung wird für Ressourcen unter
src/main/ressources gesetzt. Da Ressource Filterung standardmässig deaktiviert
ist, ist es notwendig für Ihr Projekt explizit den Wert des Elements filtering auf
true zu setzen. Wenn Sie nun ein Projekt mit der Ressource aus Example 13.3,
“Maven Properties aus einer Ressource Datei referenzieren” (Beispiel 13.3: Maven
Properties aus einer Ressource Datei referenzieren) und den POM Einträgen aus
Example 13.4, “Definieren von Variablen und Aktivierung von Ressource
Filterung” (Beispiel 13.4: Definieren von Variablen und Aktivierung von
Ressource Filterung) builden und Sie dann den Inhalt der Ressource unter
target/classes ansehen, so sollten Sie dort die folgende, gefilterte Ressource
wiederfinden:
$ mvn install
...
$ cat target/classes/applicationContext.xml
...
    <bean id="dataSource" destroy-method="close"
              class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/development_db"/>
        <property name="username" value="dev_user"/>
        <property name="password" value="s3cr3tw0rd"/>
    </bean>
...


Das POM aus Example 13.4, “Definieren von Variablen und Aktivierung von
Ressource Filterung” (Beispiel 13.4: Definieren von Variablen und Aktivierung
von Ressource Filterung) definiert ebenfalls unter dem Element profiles/profile

                                                                              349
Properties and Ressource Filterung

ein Profile production, welches das Standardprofil mit Werten welche für eine
Produktionsumgebung sinnvoll sind, übersteuert. In diesem ausgeführten Beispiel
definiert das POM Standardwerte für die Datenbankverbindung auf eine lokal
installierte MySQL™ Instanz. Sollte der Build mit aktiviertem Profil production
erstellt werden, so wird Maven das System derart konfigurieren, dass fortan das
System gegen eine Oracle Datenbank mit anderen Treibern, URL, Benutzername
und Passwort zum Zuge kommt. Wenn sie also den Build mit den oben
ausgeführten Beispielen und aktivierten Profil production ausgeführt haben, so
sollten Sie unter target/classes eine Ressource mit den unten aufgeführten
produktiven Werten auffinden.
$ mvn -Pproduction install
...
$ cat target/classes/applicationContext.xml
...
    <bean id="dataSource" destroy-method="close"
             class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName"
                     value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@proddb01:1521:PROD"/>
        <property name="username" value="prod_user"/>
        <property name="password" value="s00p3rs3cr3t"/>
    </bean>
...




                                                                              350
Chapter 14. Maven in Eclipse: m2eclipse

14.1. Einführung
Die Eclipse IDE ist heutzutage wohl die Entwicklungsumgebung mit der grössten
Verbreitung. Es gibt eine riesige Anzahl verfügbarer Plugins
(http://www.eclipseplugincentral.com/) und eine unzählbar grosse Anzahl
Organisationen welche deren Software auf Eclipse entwickeln. Einfach
ausgedrückt: Eclipse ist allgegenwärtig. Das m2eclipse Projekt bietet eine Maven
Unterstützung von innerhalb der Entwicklungsumgebung. In diesem Kapitel
werden wir die Möglichkeiten welche uns dieses Werkzeug bietet erkunden.



14.2. m2eclipse
Das m2eclipse Plugin (http://m2eclipse.codehaus.org/) bietet eine
Mavenintegration für Eclipse. M2eclipse bietet ebenfalls Möglichkeiten die
Funktionen von zwei weiteren Plugins, dem Subclipse Plugin
(http://subclipse.tigris.org) sowie dem Mylin Plugin
(http://www.eclipse.org/mylyn) zu nutzen. Das Subclipse Plugin bietet dem
m2eclipse Plugin die Möglichkeit direkt mit einem Subversion Repositorien zu
interagieren und das Mylyn Plugin erweitert das m2eclipse Plugin um mit dem
weit verbreiteten Mylyn Interface zu zu interagieren. Dieses hilft die Übersicht im
Entwicklungszusammenhang zu behalten. Hier eine Reihe Funktionalitäten welche
m2eclipse bietet:

   • Erstellen sowie importieren von Maven Projekten

   • Abhängigkeitsverwaltung und Integration mit dem Eclipse Klassenpfad

   • Automatischer Download von Abhängigkeiten und deren Update

   • Artefakt JavaDoc sowie Quellen Auflösung

                                                                               351
Maven in Eclipse: m2eclipse


   • Erstellen von Projekten basierend auf Maven Artefakten

   • Durchsuchen und Suchen in entfernten Maven Repositorien

   • POM Verwaltung mit automatischem Update der Abhängigkeitsliste

   • Materialisierung ausgehend von einem Maven POM

   • Auschecken eines Maven Projektes von mehreren verteilten SCM
     Repositorien

   • Anpassen von verschachtelten multi-modularen Maven Projekten auf die
     Entwicklungsumgebung Eclipse

   • Integration mit dem Web Tools Projekt (WTP)

   • Integration mit AspectJ Development Tools (AJDT)

   • Integration mit Subclipse

   • Integration mit Mylyn

   • Formularbasierter POM Editor

   • Graphische Darstellung des Abhängigkeitsgraphen

    • GUI Darstellung des Abhängigkeitsbaumes und der aufgelösten
      Abhängigkeiten
Es gibt über diese Liste hinaus gibt es noch viele weitere Funktionalitäten welche
m2eclipse bietet. Dieses Kapitel wird Sie in einige der eindrucksvolleren
Fähigkeiten einführen, welche derzeit verfügbar sind. Lassen Sie uns damit
beginnen, das m2eclipse Plugin zu installieren.



14.3. Installation des m2eclipse Plugins

                                                                                352
Maven in Eclipse: m2eclipse

Um das m2eclipse Plugin lauffähig zu installieren, müssen etliche
Voraussetzungen erfüllt sein. Sie müssen mindestens auf Eclipse 3.2 aufsetzen,
mindestens JDK 1.4 benutzen und sicherstellen, dass Sie auf einem JDK und nicht
nur einer Java Runtime (JRE) basieren. Sobald Sie Eclipse sowie einen
entsprechenden JDK am Laufen haben, müssen Sie zunächst noch zwei Plugins
installieren: Subclipse und Mylyn.


14.3.1. Installieren der Voraussetzungen
Sie können die Voraussetzungen zusammen mit m2eclipse installieren: fügen Sie
einfach die den Komponennten entsprechenden Update Sites in Eclipse an. Um
diese Komponenten dann zu installieren, wählen Sie Hilfe >Software Updates
>Suchen und Installieren... Es wird die Update-/Installationsschaltfläche anzeigen.
Wählen Sie „nach neuen zu installierenden Features suchen“ und wählen Sei
„weiter“. Die Anzeige wird auf „xxx“ wechseln. Wählen Sie „Neuer Remote Site“
und fügen Sie für jede Komponente die entsprechende Site hinzu, stellen Sie sicher
dass der entsprechende Eintrag ausgewählt ist. Zum Abschluss wird Eclipse ihnen
anbieten die ausgewählten Komponenten zu installieren. Eclipse wird diese dann
herunterladen, installieren und konfigurieren.
Sollten Sie die neuere Version Eclipse 3.4 (Ganymede) benutzen, so wird die
Plugin-Installation leicht von dieser Beschreibung abweichen: unter Ganymede
wählen Sie Hilfe > Software Updates und öffnen hiermit den Dialog: „xxx“.
Innerhalb dieses Fensters wählen Sie die „Verfügbare Software“ und dann „Site
hinzufügen“ welches den Dialog „Site hinzufügen" öffnen wird. Geben Sie die
URL des Sites welchen Sie hinzufügen möchten ein und bestätigen mit "OK". Im
Dialogfenster „Software Updates and Add-Ons" werden die entsprechend
verfügbaren Plugins der hinzugefügten Sites erscheinen. Sie können nun die
entsprechenden Module auswählen und installieren. Eclipse wird die
entsprechenden Abhängigkeiten auflösen und gegebenenfalls darum bitten die
Software Lizenzbedingungen zu akzeptieren. Nach der Installation wird Eclipse
Sie wahrscheinlich aufrufen Eclipse einem Neustart zu unterziehen.


14.3.1.1. Installation von Subclipse

                                                                               353
Maven in Eclipse: m2eclipse

Um Subclipse zu installieren, benutzen Sie den unten aufgeführten Eclipse Plugin
Update Site:

   • Subclipse 1.2: http://subclipse.tigris.org/update_1.2.x
Um andere Versionen von Subclipse zu installieren, oder um weitere
Informationen bezüglich des Subclipse Plugin zu erlagen, wenden Sie sich and den
Subclipse Projekt Website: http://subclipse.tigris.org/.


14.3.1.2. Installation von Mylyn
To install JIRA integration with Mylyn, add the Mylyn extras Eclipse update URL,
you'll want to do this if your organization uses Atlassian's JIRA for issue tracking.
To install Mylyn use the following update sites:
Um die JIRA Integration mittels Mylyn zu installieren, fügen Sie die Update Site
der Eclipse Mylyn Extras hinzu. Dies werden Sie insbesondere tun wollen, wenn
Ihre Organisation Atlassian JIRA Issue Tracking einsetzt. Um Mylyn zu
installieren, benutzen Sie einen der folgenden Update Sites:

   • Mylyn (Eclipse 3.3): http://download.eclipse.org/tools/mylyn/update/e3.3

   • Mylyn (Eclipse 3.4): http://download.eclipse.org/tools/mylyn/update/e3.4

   • Mylyn Extras (JIRA Support):
     http://download.eclipse.org/tools/mylyn/update/extras
Um weitere Informationen bezüglich des Mylyn Plugin zu erlagen, wenden Sie
sich and den Mylin Projekt Website: http://www.eclipse.org/mylyn/.


14.3.1.3. Installation der AspectJ Development Tools (AJDT)
Sollten Sie die Version 0.9.4 von m2eclipse installieren, so werden Sie eventuell
auch die beiden Erweiterungen: Web Tools Platform (WTP) sowie AspectJ
Development Tools (AJDT) installieren wollen. Um AJDT zu installieren,
benutzen Sie:



                                                                                 354
Maven in Eclipse: m2eclipse


   • AJDT (Eclipse 3.3): http://download.eclipse.org/tools/ajdt/33/update

   • AJDT (Eclipse 3.4): http://download.eclipse.org/tools/ajdt/34/dev/update
Um weitere Informationen bezüglich des AspectJ Development Tools zu erlagen,
wenden Sie sich and den AJDT Projekt Website: http://www.eclipse.org/ajdt/.


14.3.1.4. Installation der Web Tools Platform (WTP)
To install the Web Tools Platform (WTP). Use one of the following update URLs
in Eclipse, or just look for the Web Tools Project in the Discovery Site which
should already be in your Eclipse remote update sites list.
Um die Web Tools Platform (WTP) zu installieren, benutzen Sie die unten
stehende Update URL in Eclipse oder suchen Sie das Web Tools Project innerhalb
des Discovery Site welcher bereits Teil der Eclipse Remote Update Site Liste sein
sollte.

   • WTP: http://download.eclipse.org/webtools/updates/
Um weitere Informationen bezüglich der Web Tools Platform zu erlagen, wenden
Sie sich and den WTP Projekt Website http://www.eclipse.org/webtools/.


14.3.2. Installation von m2eclipse
Sobald Sie diese Voraussetzungen erfüllt haben und die oben aufgeführten
Komponenten installiert haben, können Sie mit der Installation von m2Eclipse
fortfahren. Benutzen Sie hierzu die folgende Update URL:

    • m2eclipse Plugin: http://m2eclipse.sonatype.org/update/
Sollten Sie den neusten Entwickler Snapshot Release des Plugins bevorzugen,
wählen Sie stattdessen die Update Development URL:

   • m2eclipse Plugin (Development Snapshot):
     http://m2eclipse.sonatype.org/update-dev/


                                                                                355
Maven in Eclipse: m2eclipse

Um das m2eclipse Plugin zu installieren, wählen Sie Hilfe > Software Update >
Suchen und Installieren … Es wird die Update-/Installationsschaltfläche angezeigt.
Wählen Sie „nach neuen zu installierenden Features suchen“ und wählen Sei
„weiter“. Die Anzeige wird auf „xxx“ wechseln. Wählen Sie „Neuer Remote Site“
und fügen Sie die oben angegebene Site hinzu. Stellen Sie sicher, dass der
entsprechende Eintrag ausgewählt ist. Zum Abschluss wird Eclipse ihnen anbieten
m2eclipse zu installieren. Eclipse wird die Komponente dann herunterladen,
installieren und konfigurieren.
Mit der erfolgreichen Installation des Plugins sollten Sie unter Windows >
Preferences in der Liste der Optionen einen Eintrag "Maven" finden.



14.4. Aufschalten der Maven Konsole
Vor wir nun tatsächlich anfangen die Möglichkeiten des m2eclipse Plugins
auszuleuchten, lassen Sie uns zunächst die Maven Konsole aufschalten. Öffnen Sie
die Konsolenansicht (Windows > Ansichten > Konsole). Wählen Sie den kleinen
Pfeil rechts vom Symbol Konsole öffnen und dort "Maven Konsole"; wie unten
dargestellt:




Figure 14.1. Aufschalten der Maven Konsole in Eclipse

Die Maven Konsole gibt alle Ausgaben wieder, welche normalerweise auf dem
Konsolenfenster ausgegeben werden, wenn Maven von der Befehlszeile gestartet
wird. Dies ist sehr nützlich um zu sehen was Maven gerade macht, oder um
Debugausgaben zu analysieren und Probleme anzusehen.



                                                                              356
Maven in Eclipse: m2eclipse


14.5. Erstellen eines Maven Projekts
Beim Einsatz von Maven werden Projekte gewöhlich durch den Einsatz von
Maven Archetypen erstellt. Innerhalb von Eclipse werden neue Projekte durch den
„Neue Projekte Wizard“ erstellt. Der Eclipse "Neues Projekt"-Wizard bietet eine
Fülle verschiedener Vorlagen um neue Projekte zu erstellen! Das m2eclipse Plugin
baut auf diesen Wizard auf, und bietet die folgenden zusätzlichen Möglichkeiten:

   • Auschecken eines Maven Projekts aus einem SCM Repository

   • Erstellen eines Maven Projekts auf der Basis eines Maven Archetyps

   • Erstellen einer Maven POM Datei
Wie in Figure 14.2, “Erstellen eines Neuen Projekts mit Hilfe des m2eclipse
Wizards” (Abbildung 14.2: "Erstellen eines Neuen Projekts mit Hilfe des
m2eclipse Wizards") dargestellt, sind alle drei Möglichkeiten für einen Entwickler
von Interesse. Lassen Sie uns diese Möglichkeiten im Einzelnen genauer ansehen.




                                                                               357
Maven in Eclipse: m2eclipse




Figure 14.2. Erstellen eines Neuen Projekts mit Hilfe des m2eclipse Wizards



14.5.1. Auschecken eines Maven Projektes von einem
SCM Repository
m2eclipse bietet die Möglichkeit Projekte direkt aus einem SCM Repository
auszuchecken. Geben Sie einfach die SCM Daten des Projektes ein und m2eclipse
wird das Projekt an den von Ihnen angegebenen Ort auschecken. Dies ist in
Figure 14.3, “Auschecken eines Projekts aus Subversion” (Abbildung 14.3:

                                                                          358
Maven in Eclipse: m2eclipse

"Auschecken eines Projekts aus Subversion" beispielhaft dargestellt:




Figure 14.3. Auschecken eines Projekts aus Subversion

Es bestehen zusätzlichen Optionen als Teil dieses Dialogs. Diese Ermöglichen
Ihnen eine bestimmte Version auszuwählen, entweder indem Sie im Katalog des
Subversion Repository auswählen, oder einfach durch die manuelle Eingabe


                                                                           359
Maven in Eclipse: m2eclipse

Revisionsnummer. Diese Funktionalitäten bauen auf Funktionen des Subclipse
Plugins auf, um mit dem Subclipse Repository zu interagieren. Darüber hinaus
unterstützt das m2eclipse Plugin die folgenden SCM Anbieter:

   • Bazaar

   • Clearcase

   • CVS

   • git

   • hg

   • Perforce

   • Starteam

   • Subversion

   • Synergy

   • Visual SourceSafe


14.5.2. Erstellen eines Maven Projekts auf der Basis
eines Maven Archetyps
m2eclipse ermöglicht ein Maven Projekt auf der Basis eines Maven Archetyps zu
erstellen. Es bestehen sehr viele verschiedene Maven Archetypen, diese werden
bereits in der Auswahlliste des m2eclipse aufgeführt werden, wie aus der
Figure 14.4, “Erstellen eines Neuen Projektes auf der Basis eines Maven
Archetyps” (Abbildung 14.4: "Erstellen eines Neuen Projektes auf der Basis eines
Maven Archetyps" ersichtlich wird:




                                                                               360
Maven in Eclipse: m2eclipse




Figure 14.4. Erstellen eines Neuen Projektes auf der Basis eines Maven
Archetyps

Die Liste der aufgeführten Muster aus Figure 14.4, “Erstellen eines Neuen
Projektes auf der Basis eines Maven Archetyps” (Abbildung 14.4: "Erstellen eines
Neuen Projektes auf der Basis eines Maven Archetyps") ist eine Aufzählung
welche von einem Werkzeug genannt Nexus Indexer erzeugt wurde. Nexus ist ein
Repository Manager auf welchen in Chapter 16, Repository Management with
Nexus (Kapitel 16: "Repository Manager") näher eingegangen wird. Der Nexus
Indexer ist eine Datei die einen Index über das gesamte betrachtete Maven

                                                                             361
Maven in Eclipse: m2eclipse

Repository darstellt. m2eclipse benutzt diese um alle bereitstehenden Archetypen
des gesamten Maven Repository darzustellen. Als dieses Kapitel das letzte Mal
angepasst wurde, wurden bereits ca. 90 verschiedene Muster im Archetypdialog
dargestellt. Darunter besonders erwähnenswert sind:

   • Die Standardarchetypen von Maven zur Erstellung von

      • Maven Plugins

      • Einfache Web Anwendungen

      • Einfache Projekte

      • Neue Maven Archetypen

   • Databinder Archetypen (datengetriebene Wicket Anwendungen) unter
      net.databinder

   • Apache Cocoon Archetypen unter org.apache.cocoon

   • Apache Directory Server Archetypen unter org.apache.directory.server

   • Apache Geronimo Archetypen unter org.apache.geronimo.buildsupport

   • Apache MyFaces Archetypen unter org.apache.myfaces.buildtools

   • Apache Tapestry Archetypen unter org.apache.tapestry

   • Apache Wicket Archetypen unter org.apache.wicket

   • AppFuse Archetypen unter org.appfuse.archetypes

   • Codehaus Cargo Archetypen unter org.codehaus.cargo

   • Codehaus Castor Archetypen unter org.codehaus.castor

   • Groovy-based Maven Plugin Archetypen (deprecated)24 unter

                                                                              362
Maven in Eclipse: m2eclipse


       org.codehaus.mojo.groovy

     • Jini Archetypen

     • Mule Archetypen unter org.mule.tools

     • Objectweb Fractal Archetypen unter org.objectweb.fractal

     • Objectweb Petals Archetypen unter org.objectweb.petals

     • ops4j Archetypen unter org.ops4j

     • Parancoe unter org.parancoe

     • slf4j Archetypen unter org.slf4j

     • Springframework OSGI sowie Web Services Archetypen unter
       org.springframework

     • Trails Framework Archetypen unter org.trailsframework
24
  And these were just the archetypes that were listed under the Nexus Indexer
Catalog, if you switch Catalogs you'll see other archetypes. While your results may
vary, the following additional archetypes were available in the Internal Catalog:
Und dies sind nur die Archetypen welche vom Nexus Indexer Katalog afugeführt
werden, sollten Sie einen anderen Katalog auswählen, so werden Sie weitere
Archetypen kennen lernen. Die Resultate werden unterschiedlich von Fall zu Fall
unterschiedlich sein, im internen Katalog waren zum Beispiel folgende
zusätzlichen Muster verfügbar:

     • Atlassian Confluence Plugin Archetype unter
       com.atlassian.maven.archetypes



24
  [4] Verzichten Sie auf den Einsatz des Groovy Maven Plugin des Codehaus Mojo Projekt. Jason
Dillon hat inzwischen die Groovy Maven Integrationsbemühungen in das Groovy Projekt von Codehaus
verschoben. Für weitere Informationen wenden Sie sich an http://groovy.codehaus.org/GMaven.

                                                                                           363
Maven in Eclipse: m2eclipse


   • Apache Struts Archetypen unter org.apache.struts

   • Apache Shale Archetypen unter org.apache.shale
Ein Katalog ist einfach eine Referenz auf einen Repository Index. Sie können die
Menge aller Kataloge welche vom m2eclipse Plugin berücksichtigt werden
verwalten, indem Sie neben der Drop Down Box des Kataloges den Knopf
Configure… auswählen. Sollten Sie Ihren eigenen Archetyp einem Katalog
zufügen wollen, so wählen Sie Add Archetype …
Sobald Sie einen Archetypen ausgewählt haben, wird Maven den entsprechenden
Artefakten aus dem Maven Repository holen und ein entsprechendes Eclipse
Projekt erstellen.


14.5.3. Erstellen eines Maven Moduls
m2eclipse bietet auch die Möglichkeit ein Maven Modul zu erstellen. Dies ist
beinahe identisch zur Erstellung eines Maven Projektes, da es ebenfalls ein Maven
Projekt auf der Basis eines Archetypen erstellt. Jedoch ist ein Maven Modul ein
Unterprojekt zu einem anderen Maven Projekt, das gemeinhin als übergeordnetes
oder elterliches Projekt bezeichnet wird.




                                                                              364
Maven in Eclipse: m2eclipse




Figure 14.5. Erstellen eines neuen Maven Modules

Beim Erstellen eines neuen Maven Moduls müssen Sie ein übergeordnetes Projekt
auswählen, welches bereits innerhalb der Entwicklungsumgebung existiert. Die
Auswahl des Buttons Browse zeigt eine Liste der bereits bestehenden Projekte wie
in Figure 14.6, “Auswahl eines übergeordneten Projektes eines neues Maven
Modul” (Abbildung 14.6: "Auswahl eines übergeordneten Projektes eines neues


                                                                             365
Maven in Eclipse: m2eclipse

Maven Modul") dargestellt.




Figure 14.6. Auswahl eines übergeordneten Projektes eines neues Maven

                                                                        366
Maven in Eclipse: m2eclipse

Modul

Nach der Auswahl eines übergeordneten Projektes aus der Liste werden Sie auf das
"Neues Maven Modul"-Fenster zurückgeleitet, dort ist nun das Feld
Übergeordnetes Projekt ausgefüllt wie in Figure 14.5, “Erstellen eines neuen
Maven Modules” (Abbildung 14.5: "Erstellen eines neuen Maven Moduls")
dargestellt. Mit der Auswahl von Weiter werden Sie zur Liste der Maven
Archetypen wie in Section 14.5.2, “Erstellen eines Maven Projekts auf der Basis
eines Maven Archetyps” (14.5.2: "Erstellen eines Maven Projekts auf der Basis
eines Maven Archetyp") ausgeführt. Sie können dabei auswählen auf der Basis
welchen Musters das neue Maven Modul erstellt werden soll.



14.6. Erstellen einer Maven POM Datei
Eine weitere wichtige Funktionalität des m2eclipse-Plugins ist es POM-Dateien zu
erstellen. m2eclipse stellt einen Wizard bereit, um für ein bestehendes Projekt eine
POM Datei zu erstellen. Dieser POM Wizard wird in Figure 14.7, “Abbildung
14.7: Erstellen eines neuen POMs” (Abbildung 14.7: Erstellen eines neuen POMs)
dargestellt:




                                                                                367
Maven in Eclipse: m2eclipse




Figure 14.7. Abbildung 14.7: Erstellen eines neuen POMs

Zu einem bestehenden Projekt ein neues Maven POM zu erstellen ist einfach:
Auswählen des Projektes, Eingabe der groupID, artefactID sowie version,
Auswahl des Paketierungstyps und Vergabe eines Namens in den dafür
vorgesehenen Feldern des m2eclipse Wizards. Wählen Sie weiter um
Abhängigkeiten zuzufügen.


                                                                             368
Maven in Eclipse: m2eclipse




Figure 14.8. Hinzufügen von Abhängigkeiten zu einer neuen POM Datei

Wie Sie leicht aus Figure 14.8, “Hinzufügen von Abhängigkeiten zu einer neuen
POM Datei” (Abbildung 14.8: "Hinzufügen von Abhängigkeiten zu einer neuen
POM Datei") ersehen, sind zunächst keine Abhängigkeiten enthalten. Wählen Sie
zufügen um das zentrale Maven Repository nach Abhängigkeiten zu durchsuchen.
Dies ist in der nächsten Abbildung, Figure 14.9, “Durchsuchen des Zentralen
Repositories nach Abhängigkeiten” (Abbildung 14.9: "Durchsuchen des Zentralen

                                                                          369
Maven in Eclipse: m2eclipse

Repositories nach Abhängigkeiten") dargestellt:




Figure 14.9. Durchsuchen des Zentralen Repositories nach Abhängigkeiten

Nach Abhängigkeiten zu suchen ist einfach; geben Sie die groupId des benötigten
Artefakten ein. Figure 14.9, “Durchsuchen des Zentralen Repositories nach


                                                                            370
Maven in Eclipse: m2eclipse

Abhängigkeiten” (Abbildung 14.9: "Durchsuchen des Zentralen Repositories nach
Abhängigkeiten") stellt die Abfrage nach org.apache.commons dar, wobei
commons-vfs geöffnet ist, um zu sehen welche Versionen verfügbar sind. Die
Auswahl der 1.1-SNAPSHOT Version von commons-vfs und nachfolgende Auswahl
von Ok führt Sie zurück in den Dialog zur Auswahl der Abhängigkeiten, wo Sie
dann weitere Abhängigkeiten hinzufügen können, oder via Abschliessen das POM
erstellen können. Bei der Suche nach Abhängigkeiten macht m2eclipse Gebrauch
von dem gleichen Nexus Repository Index, welcher auch von Nexus Index
Manager aus Chapter 16, Repository Management with Nexus (Kapitel 16:
"Repository Manager") benutzt wird.
Jetzt, da Sie die Funktionalität welche m2eclipse zur Erstellung eines neuen
Projektes bietet gesehen haben, lassen Sie uns die verwandten Funktionen zum
Import von Projekten ansehen:



14.7. Importieren von Maven Projekten
m2eclipse bietet dreierlei Möglichkeiten Maven Projekte in Eclipse zu importieren:

   • Importieren eines existierenden Maven Projekts

   • Auschecken eines Maven Projektes aus einem SCM Repository

   • Materialisieren eines Maven Projektes
Figure 14.10, “Importieren eines Maven Projektes” (Abbildung 14.10:
"Importieren eines Maven Projektes") stellt den Wizard zum Import von Projekten
mit den zugehörigen Optionen, welche Maven durch das m2eclipse Plugin
bereitstellt, dar.




                                                                               371
Maven in Eclipse: m2eclipse




Figure 14.10. Importieren eines Maven Projektes

Die Dialogbox aus Figure 14.10, “Importieren eines Maven Projektes” (Abbildung
14.10: "Importieren eines Maven Projektes") kann über Datei > Importieren und
anschliessendem Filtern mit dem Schlüsselbegriff maven, innerhalb von Eclipse
aufgerufen werden. Wie oben erwähnt bieten sich drei Möglichkeiten ein Maven
Projekt zu in Eclipse zu importieren: Maven Projekte Importieren, Auschecken
eines Maven Projektes aus einem Repository und Materialisieren von Maven
Projekten.

                                                                           372
Maven in Eclipse: m2eclipse

Importieren eines Maven Projektes aus einem SCM Repository ist identisch zur
Funktionalität "Erstellen eines Maven Projektes aus einem SCM Repository"
welche vorgängig Diskutiert wurde; eine weitere Behandlung ist überflüssig.
Lassen Sie uns weitergehen zu den zwei verbleibenden Möglichkeiten ein Maven
Projekt zu importieren.


14.7.1. Importiren eines Maven Projektes
m2eclipse kann ein Projekt mit bestehender pom.xml-Datei importieren. Durch
Angabe des Verzeichnisses in welchem das Maven Projekt abgelegt wurde, ist
m2eclipse in der Lage alle Maven POMs des Projekts zu finden und eine
hierarchische Liste dieser aufzustellen. Diese ist in Figure 14.11, “Importieren
eines multi-modularen Maven Projektes ” (Abbildung 14.11: "Importieren eines
multi-modularen Maven Projektes") dargestellt:




                                                                               373
Maven in Eclipse: m2eclipse




Figure 14.11. Importieren eines multi-modularen Maven Projektes

Figure 14.11, “Importieren eines multi-modularen Maven Projektes ” (Abbildung
14.11: "Importieren eines multi-modularen Maven Projektes") zeigt die
Darstellung des zu importierenden Projektes. Bitte beachten Sie, dass alle POMs
des Projektes in hierarchischer Darstellung wiedergegeben werden. Das erlaubt
Ihnen auf einfache Art und Weise genau auszuwählen, welche POMs (und damit
Projekte) Sie in Eclipse importieren möchten. Haben Sie erst einmal die Projekte
welche Sie importieren möchten ausgewählt, nimmt Ihnen m2eclipse den Import

                                                                              374
Maven in Eclipse: m2eclipse

und das Builden der Projekte mit der Unterstützung von Maven ab.


14.7.2. Materialisieren eines Maven Projektes
Eine weitere Funktionalität welche m2eclipse bietet ist die Materialisierung eines
Projektes. Materialisierung ist ähnlich dem Auschecken eines Projektes aus einem
SCM Repository, jedoch, anstatt die URL des Repository manuell einzugeben,
wird diese aus der obersten POM Datei des Projektes gelesen. Sie können diese
Funktionalität dazu benutzen, Projekte aus nichts weiter als einer pom.xml Datei,
welche die entsprechenden Elemente enthält, zu erstellen. Mit dieser Möglichkeit
ist es Ihnen gegeben, das Maven Repository zu durchstreifen und die enthaltenen
Projekte in Eclipse zu materialisieren. Dies ist insbesondere nützlich, sollte Ihr
Projekt von einer Open Source Library abhängig ist, und Sie versuchen an dessen
Quellen zu gelangen. Statt mühsam den Projekt-Website herauszufinden und
festzustellen, wie Sie diese aus dem Repository auschecken können, benutzen Sie
einfach m2eclipse um das Projekt zu materialisieren.
Figure 14.12, “Materialisieren eines Maven Projektes” (Abbildung 14.12:
"Materialisieren eines Maven Projektes") stellt den Wizard nach der Auswahl des
zu materialisierenden Projekts dar:




                                                                               375
Maven in Eclipse: m2eclipse




Figure 14.12. Materialisieren eines Maven Projektes

Beachten Sie, dass die Dialogbox der Maven Artefakte in Figure 14.12,
“Materialisieren eines Maven Projektes” (Abbildung 14.12: "Materialisieren eines
Maven Projektes") leer ist. Dies ist so, da noch keine Projekte ausgewählt wurden.
Um ein Projekt hinzuzufügen müssen Sie auf der rechten Seite den Button

                                                                               376
Maven in Eclipse: m2eclipse

"Hinzufügen" auswählen und einen Abhängigkeit aus dem zentralen Maven
Repository auswählen. Dies ist in Figure 14.13, “Auswahl eines Artefakten zum
Materialisieren” (Abbildung 14.13: "Auswahl eines Artefakten zum
Materialisieren"), dargestellt.




Figure 14.13. Auswahl eines Artefakten zum Materialisieren


                                                                            377
Maven in Eclipse: m2eclipse

Auf die Eingabe einer Auswahl hin werden die möglichen Abhängigkeiten im
Maven Repository gesucht. Nach wenigen Momenten Indexierung des lokalen
Repositories wird eine Liste der möglichen Artefakte wiedergegeben. Wählen Sie
den entsprechendne Kandidaten eines Projektes aus, so dass dieser zur Liste
hinzugefügt werden kann, wie dies in Figure 14.14, “Materialisieren von Apache
Camel” (Abbildung 14.14: "Materialisieren von Apache Camel") gezeigt wird.




                                                                            378
Maven in Eclipse: m2eclipse

Figure 14.14. Materialisieren von Apache Camel

Nach dem hinzufügen der Abhängigkeit bietet Ihnen das m2eclipse die
Möglichkeit alle Projekte des Artefaktes auszuchecken.



14.8. Starten von Maven Builds
m2eclipse wird auch die Menüs „Run as …“ sowie „Debug as … “ anpassen, um
Ihnen zu ermöglichen Maven Builds zu starten. Figure 14.15, “Starten eines
Eclipse Builds durch „Run as…“ ” (Abbildung 14.15: "Starten eines Eclipse Builds
durch „Run as…“ ") stellt das „Run as…“ Menü eines m2eclispse Projektes dar.
Ausgehend von diesem Menü können Sie eine Anzahl Maven Lebenszyklusphasen
wie z. B. clean, install und package direkt starten. Sie können auch den
Run-Konfigurationsdialog aufrufen und dort spezifische Maven Buildparameter
und/oder weitere Optionen setzen.




                                                                            379
Maven in Eclipse: m2eclipse




Figure 14.15. Starten eines Eclipse Builds durch „Run as…“

Beabsichtigen Sie einen Maven Build mit erweiterter Konfiguration zu erstellen, so
können Sie Run Configuration… auswählen und einen neuen Maven Build
erstellen. Figure 14.16, “Konfiguration eines Maven Builds mittels "Run
Configurations..." ” (Abbildung 14.16: "Konfiguration eines Maven Builds mittels
"Run Configurations..." ") stellt den Run Dialog vor




                                                                              380
Maven in Eclipse: m2eclipse




Figure 14.16. Konfiguration eines Maven Builds mittels "Run
Configurations..."

Der “Run Configuration ...” Dialog erlaubt es, mehrere Goals sowie Profile
darzustellen, es werden Auswahlmöglichkeiten wie „Test auslassen (skip test)“
oder „Update Snapshots“ und erlaubt Ihnen Parameter ausgehend vom denen des
Projektes bis hin zur unterliegenden Java Runtime Umgebung (JRE) oder den
Umgebungsvariablen zu setzen. Sie können diese Dialogseite benutzen um jeden
beliebigen massgeschneiderten Maven Build mittels m2eclipse zu starten.



                                                                           381
Maven in Eclipse: m2eclipse


14.9. Mit Maven Projekten arbeiten
Das m2eclipse bietet auch eine Reihe Funktionalitäten um mit Maven Projekten zu
arbeiten sobald diese in Eclipse geladen sind. Viele dieser Funktionen erleichtern
das Arbeiten mit Maven, lassen Sie uns diese näher ansehen: Im vorhergehenden
Abschnitt materialisierte ich ein Projekt und wählte ein Unterprojekt von Apache
Camel aus; camel-core. Wie werden die verschiedenen Möglichkeiten anhand
dieses Projektes verdeutlichen.
Wählen Sie mit der rechten Maustaste den Menüpunkt Maven auf dem camel-core
Projekt, so können Sie die verfügbaren Maven Funktionen sehen. Figure 14.17,
“Verfügbare Maven Funktionalitäten” (Abbildung 14.17: "Verfügbare Maven
Funktionen") stellt dies dar:




                                                                              382
Maven in Eclipse: m2eclipse




Figure 14.17. Verfügbare Maven Funktionalitäten

Notice in Figure 14.17, “Verfügbare Maven Funktionalitäten” the available Maven
features for the camel-core project, including:
Beachten Sie in Figure 14.17, “Verfügbare Maven Funktionalitäten” (Abbildung
14.17: "Verfügbare Maven Funktionalitäten") die verfügbaren Möglichkeiten des
camel-core Projektes, diese umfassen




                                                                            383
Maven in Eclipse: m2eclipse


   • Hinzufügen von Abhängigkeiten sowie Plugins

   • Updaten von Abhängigkeiten, Snapshots sowie Quellverzeichnissen

   • Erstellen eines Maven Moduls

   • Herunterladen von Quelldateien

   • Öffnen von Projekt URLs wie z. B. der Projekt Web Page, dem Issue
     Tracker, SCM oder Continuous Integration Werkzeugen.

   • Zuschalten/Abschalten der Workspace Auflösung, verschachteltem Modul-
     und Abhängigkeitsmanagement
Diese Funktionalitäten können uns Zeit sparen, und so lassen Sie uns diese genauer
ansehen.


14.9.1. Zufügen und Updaten von Abhängigkeiten und
Plugins
Nehmen wir einmal an, wir wollen zum camel-core POM eine Abhängigkeit oder
ein Plugin hinzufügen. Der Demonstration halber fügen wir commons-lang als
Abhängigkeit hinzu. (Da die Funktionalität zum Hinzufügen von Abhängigkeiten
und Plugins genau dieselbe ist, werden wir beispielhaft lediglich eine Abhängigkeit
hinzufügen).
m2eclipse bietet zwei Möglichkeiten eine Abhängigkeit dem Projekt zuzufügen.
Die erste ist, die pom.xml-Datei direkt durch hinzufügen des entsprechenden XML
Elements abzuändern. Der Nachteil dieses Ansatzes ist, dass Sie bereits die
Informationen bezüglich der Artefakte im Detail wissen müssen, oder alternativ die
Funktionen welche im nächsten Abschnitt dargestellt werden benutzen müssten um
diese in den Repository Indexen zu finden. Der Vorteil ist, dass nach einer
manuellen Änderung und Speicherung des Projekt POM der Maven
Abhängigkeits-Container automatisch die Neuerungen reflektiert. Figure 14.18,
“Manuelles Zufügen einer Abhängigkeit zum Projekt POM” (Abbildung 14.18:

                                                                               384
Maven in Eclipse: m2eclipse

"Manuelles Zufügen einer Abhängigkeit zum Projekt POM") zeigt, wie ich eine
Abhängigkeit zu commons-lang zum POM der camel-console zugefügt habe und
der Maven Abhängigkeits-Container dies automatisch reflektiert und somit
darstellt:




Figure 14.18. Manuelles Zufügen einer Abhängigkeit zum Projekt POM

Nach dem manuelen Zufügen des Elements dependency an das POM, zeigt die
Statusanzeige in der rechten unteren Ecke die Hintergrundaktion wie in
Figure 14.19, “Updaten von Maven Abhängigkeiten” (Abbildung 14.19: "Updaten
von Maven Abhängigkeiten") dargestellt, an:




Figure 14.19. Updaten von Maven Abhängigkeiten

Manuelles Zufügen von Abhängigkeiten funktioniert gut, aber benötigt mehr
Arbeit als dies die zweite Möglichkeit erfordert. Diese Möglichkeit ist viel
einfacher, sie setzt nicht voraus, dass Sie irgendwelche Informationen über die der
groupId der Artefakte hinaus haben. Figure 14.20, “Suchen einer Abhängigkeit”
(Abbildung 14.20: "Suchen einer Abhängigkeit") stellt dies dar:

                                                                                385
Maven in Eclipse: m2eclipse




Figure 14.20. Suchen einer Abhängigkeit

Durch eintragen einer groupId in das Abfragefeld durchsucht m2eclipse die Indexe
der Repositorien sowie die Versionen der Artefakte welche sich bereits im lokalen
Maven Repository befinden. Dies ist die bevorzugte Möglichkeit, da es enorme
Zeitersparnisse birgt. Mit m2eclipse gehört die langwierige Suche durch die
zentralen Maven Repositorien der Vergangenheit an.

                                                                             386
Maven in Eclipse: m2eclipse


14.9.2. Erstellen eines Maven Modules
m2eclipse bringt eine enorme Erleichterung zur Erstellung eines verschachtelten
Unterprojektes innerhalb eines multi-modularen Maven Projektes. Sollten Sie ein
übergeordnetes Maven Projekt bearbeiten und Sie wollen ein Unterprojekt
erstellen, so machen Sie einen Rechts-Click auf dem Projekt, gehen Sie zum
Maven Menü und wählen „Neues Maven Modul Projekt". m2eclipse wird Sie
durch die Schritte zum Erstellen eines Untermoduls führen, das POM erstellen, das
elterliche POM anpassen und in den Bezügen reflektieren. Vor es m2eclipse gab
war es sehr schwierig, innerhalb von Eclipse mit hierarchisch geschachtelten
Projektbäumen zu arbeiten. Mittels m2eclipse wurden die darunterliegenden
Beziehungen zwischen Über und Unterprojekten in die Entwicklungsumgebung
eingebunden.


14.9.3. Herunterladen der Quelldatei(en)
Sollte das zentrale Maven Repository eine Quelldatei der Artefakten beinhalten, so
können Sie diese mittels Maven herunterladen und in Eclipse bereitstellen. Beim
Versuch eine komplexe Anwendung zu Debuggen, ist es manchmal äusserst
nützlich, kann man den zu einem bestehenden externen Quellcode einer
Abhängigkeit durch einen einfachen Rechts-Click herunterladen und zu debuggen.
Wählen Sie diese Funktion, so wird Maven für Sie versuchen dies zu tun. Ist es
nicht möglich, so sollten Sie den Verantwortlichen des Projektes darauf
aufmerksam machen, dass dieser im zentralen Maven Repository die
entsprechenden Quelldateien hinterlegen.


14.9.4. Öffnen von Projektseiten
Das Maven POM enthält einige sehr nützliche URLs welche einen Entwickler
interesieren mögen: Diese sind die Projekt Webseite, der Link auf das SCM
Repository, eine URL auf eine Continuous Integrations Instanz (Dienst wie z.B.
Hudson) sowie eine URL, welche auf das entsprechende Issue Tracking System
zeigt. Sind diese Variablen im POM gesetzt, so öffnet m2eclipse die
entsprechenden Seiten innerhalb eines Browserfensters.

                                                                              387
Maven in Eclipse: m2eclipse


14.9.5. Auflösen von Abhängigkeiten
Sie können ein Projekt so konfigurieren, dass dieses die Abhängigkeiten innerhalb
Ihres Workspace auflöst. Das hat den Nebeneffekt, dass es unter Umständen das
Verhalten ändert wie Maven einen Artefakten findet. Sollte ein Projekt so
konfiguriert sein, dass es Abhängigkeiten innerhalb eines Workspaces auflöst,
müssen diese Artefakte nicht in Ihrem lokalen Repository vorhanden sein. Nehmen
wir einmal an, Projekt A und Projekt B sind beide im gleichen Eclipse Workspace
vorhanden. Ist Workspace Auflösung ausgestellt, so wird die Abhängigkeit nur
aufgelöst, wenn die entsprechenden Artefakte in Ihrem lokalen Repository
aufzufinden sind. Ist Workspace Auflösung hingegen aktiviert, so wird m2eclipse
die Abhängigkeiten innerhalb Ihres Workspaces auflösen. In anderen Worten, ist
Workspace Auflösung aktiviert, so müssen lokale Projekte nicht in Ihrem lokalen
Repository vorhanden sein um sich aufeinander zu beziehen.
Sie können auch das Abhängigkeitsmanagement ausschalten. Das hat dann den
Nebeneffekt, dass es m2eclipse anweist, aufzugeben zu versuchen, den
Projektklassenpfad zu verwalten, und wird den Maven Abhängigkeits Container
aus Ihrem Projekt entfernen. Sollten Sie diese Option wählen, so sind Sie
letztendlich mit der Verwaltung des Projektklassenpfades auf sich gestellt.



14.10. Arbeiten mit den Maven Repositorien
m2eclipse bietet auch einige Werkzeuge um die Arbeit mit Maven Repositorien zu
erleichtern. Insbesondere stehen Werkzeuge mit folgender Funktionalität bereit:

   • Suchen von Artefakten

   • Suchen von Java Klassen

   • Indexierung von Maven Repositorien


14.10.1. Suchen von Maven Artefakten sowie Java

                                                                             388
Maven in Eclipse: m2eclipse


Klassen
m2eclipse fügt dem Navigationsmenü von Eclipse weitere Einträge hinzu, welche
die Suche nach Maven Artefakten sowie Java Klassen erleichtern. Diese
Funktionalität wird durch einen Aufruf innerhalb des Menüs ausgelöst, und ist in
Figure 14.21, “Suchen von Artefakten und Klassen” (Abbildung 14.21: "Suchen
von Artefakten und Klassen") dargestellt:




                                                                             389
Maven in Eclipse: m2eclipse




Figure 14.21. Suchen von Artefakten und Klassen

Beachten Sie in Figure 14.21, “Suchen von Artefakten und Klassen” (Abbildung
14.21: "Suchen von Artefakten und Klassen") im Navigationsmenü von Eclipse die
Einträge mit dem Titel: Öffnen eines Maven POM und Maven Typauswahl. Der


                                                                          390
Maven in Eclipse: m2eclipse

Eintrag Öffnen eines Maven POM erlaubt Ihnen das Maven Repository nach einem
bestimmten POM zu durchsuchen, wie in Abbildung Figure 14.22, “Suche eines
POM” (14.22: "Suche eines POM") ersichtlich ist:




                                                                        391
Maven in Eclipse: m2eclipse

Figure 14.22. Suche eines POM

Nach der Auswahl eines Artefakts und der Bestätigung mit OK wird innerhalb von
Eclipse das POM zur Ansicht und Modifikation geöffnet. Dies ist sehr hilfreich,
wenn Sie das POM eines Artefakten zur schnellen Einsicht ansehen wollen.
Der zweite Eintrag im Navigationsmenü von m2eclipse ist „Open Type from
Maven“. Diese Funktion erlaubt es Ihnen in einem entfernten Repository nach Java
Klassen zu suchen. Beim Öffnen des Dialogfensters geben Sie versuchsweise
einmal ‚factory bean’ ein, und Sie werden sehen wie viele Klassen mit diesem
Namen gefunden werden! Dies ist in Figure 14.23, “Durchsuchen eines Repository
nach einer Java Klasse” (Abbildung 14.23: "Durchsuchen eines Repository nach
einer Java Klasse") dargestellt.




                                                                            392
Maven in Eclipse: m2eclipse




Figure 14.23. Durchsuchen eines Repository nach einer Java Klasse

Dies ist eine grosse zeitliche Entlastung, denn bislang bedeutete die Suche nach

                                                                                   393
Maven in Eclipse: m2eclipse

einer Java Klasse eine zeitraubende Suche innerhalb der einzelnen Artefakte eines
Repositories. Wenn Sie heute eine bestimmt Klasse suchen, starten Sie einfach
Eclipse, gehen zum Navigationsmenü und suchen nach der entsprechenden Klasse.
m2eclipse wird Ihnen eine Liste der Artefakte zurückgeben in welchem diese
Klasse erscheint.


14.10.2. Indizierung von Maven Repositorien
Die Maven Index Anzeige erlaubt es Ihnen POMs in entfernten Repositorien
anzuwählen und in Eclipse auszuwählen. Um dies zu demonstrieren, wählen Sie
View > Show View > Other. Geben sie den Begriff „maven“ in die Suchmaske ein.
Sie sollten wie in Figure 14.24, “Anzeigen von Maven Indexen im Eclipse View”
(Abbildung 14.24: Anzeigen von Maven Indexen im Eclipse View") dargestellt ein
View der bestehenden Artefakte zur Auswahl gestellt bekommen:




                                                                             394
Maven in Eclipse: m2eclipse




Figure 14.24. Anzeigen von Maven Indexen im Eclipse View

Wählen Sie das entsprechende View aus und bestätigen Sie mit OK. Es wird sich
das Maven Index View wie in Figure 14.25, “Maven Index View” (Abbildung
14.25: "Maven Index View") dargestellt öffnen.




                                                                           395
Maven in Eclipse: m2eclipse




Figure 14.25. Maven Index View

Zusätzlich in Figure 14.26, “Auswahl eines POM innerhalb des Index Views”
(Abbildung 14.26: "Auswahl eines POM innerhalb des Index Views") dargestellt
nach dem manuellen Suchen eines POM:




                                                                          396
Maven in Eclipse: m2eclipse




Figure 14.26. Auswahl eines POM innerhalb des Index Views

Nach der Auswahl des apache-camel Artefakte wird ein Doppelclick dieses in
Eclipse zur Bearbeitung öffnen.
Diese Funktion erleichtert die Arbeit mit einem entfernten Repository wesentlich
und spart darüber hinaus viel Zeit. Nach all der Zeit welche Sie in den vergangenen
Jahren mit solcherlei Beschäftigungen zugebracht haben – Ansehen des Inhaltes
eines Repositories in einem Webbrowser, Herunterladen des Artefaktes und
anschiessenden Greppens nach Klassen oder POM – sie werden von m2eclipse
angenehm überrascht sein.




                                                                               397
Maven in Eclipse: m2eclipse


14.11. Der neue graphische POM Editor
Die neuste Inkarnation des m2eclipse Plugin beinhaltet einen graphischen POM
Editor, welcher es erlaubt, alle Aspekte eines Projekt POMs in einer einfachen
GUI Schnittstelle zu bearbeiten. Um den POM Editor zu öffnen, Klicken Sie auf
eine pom.xml Datei. Sollten Sie eine benutzerdefinierte Einstellung eines Editors
gewählt haben, und der graphische POM Editor nicht als Standardeditor bestimmt
haben, so können Sie die Alternative „Open with … /Maven POM Editor“ wählen
um diesen zu starten. Der POM Editor wird Ihnen dann die in Figure 14.27,
“Übersichtsanzeige des POM Editors für das Projekt idiom-core” (Abbildung
14.27: "Übersichtsanzeige des POM Editors für das Projekt idiom-core")
dargestellte Gesamtübersicht anzeigen.
Eine häufige Klage bezüglich Maven ist, dass Maven den Entwickler dazu zwingt
sich des öfteren mit langen, unübesichtlichen und hoch komplexen XML
Dokumenten zu befassen, wie diese in einem multi modularen Build vorkommen.
Wärend wir, die Autoren davon überzeugt sind, dass dies ein kleiner Preis für all
die Möglichkeiten und Flexibilität welche Maven bietet ist, so erlaubt der
graphische POM Editor die Arbeit mit Maven ohne die das POM untermauernde
XML Struktur überhaupt zu kennen.




                                                                              398
Maven in Eclipse: m2eclipse




Figure 14.27. Übersichtsanzeige des POM Editors für das Projekt idiom-core

Das in Figure 14.27, “Übersichtsanzeige des POM Editors für das Projekt
idiom-core” (Abbildung 14.27: "Übersichtsanzeige des POM Editors für das
Projekt idiom-core") dargestellte Projekt besitzt einen Artefakten: idiom-core. Es
besteht keine groupId oder version, und Angaben zur SCM Verwaltung sind
ebenfalls nicht gesetzt. Dies rührt daher, dass idiom-core die allermeisten
Informationen vom übergeordneten Projekt idom erbt. Öffnen wir das pom.xml des
elterlichen Projekts im graphischen Editor, so würden wir die Anzeige wie in
Figure 14.28, “Übersichtsansicht des graphischen Editors des übergeordneten
idiom Projekts” (Abbildung 14.28: "Übersichtsansicht des graphischen Editors des
übergeordneten idiom Projekts") sehen.
Das Symbol des "geöffneten Ordners" an zahlreichen Einträgen des POM Editors

                                                                              399
Maven in Eclipse: m2eclipse

zeigt an, dass der entsprechende Eintrag ebenfalls Teil des Eclipse-Workspace ist,
und das Jar-Symbol deutet auf Artefakte welche sich auf ein Maven Repository
beziehen. Sie können einen solche Eintrag doppelklicken um sich dessen POM
Datei im POM Editor anzuzeigen. Dies funktioniert für Module, Abhängigkeiten,
Plugins sowie weiteren Artefakten, welche entsprechende Maven Artefakte
aufweisen. Unterstrichene Titel in den Seiten des POM Editors weisen auf
Hyperlinks hin, welche benutzt werden können um Maven Artefakte ebenfalls im
POM Editor zu öffnen.




Figure 14.28. Übersichtsansicht des graphischen Editors des übergeordneten
idiom Projekts


In diesem übergeordneten POM ersichtlich die groupId und version. Beachten Sie
bitte, dass ein Grossteil der Detailinformationen welche im POM von idiom-core
fehlten, hier gesetzt werden. Die Anzeige des POM Editors beschränkt sich auf
Angaben welche im jeweiligen POM gemacht wurden und zeigt keine geerbten

                                                                               400
Maven in Eclipse: m2eclipse

Werte. Sollten Sie dennoch das tatsächliche POM des Projektes ansehen wollen, so
nutzen Sie die Möglichkeit „tatsächliches POM anzeigen“ von der Toolbar an der
oberen rechten Ecke des POM Editors (linke Klammer und Gleichzeichen über
dem blauen Maven Projekt M). Es wird Ihnen das tatsächliche POM wie in
Figure 14.29, “Tatsächliches POM von idiom-core” (Abbildung 14.29:
"Tatsächliches POM von idiom-core") dargestellt, angezeigt.




Figure 14.29. Tatsächliches POM von idiom-core

In der Ansicht „Tatsächliches POM“ von idiom-core wird das ursprüngliche POM
mit den POM der ‚Vorfahren’ (übergeordnet (Eltern), darüber (Grosseltern), etc.)
ähnlich zum Aufruf ‚mvn help:effective-pom’ verschmolzen und dargestellt. Da
diese Ansicht nun die Kombination vieler verschiedener abhängiger POM ist, ist
diese Darstellung schreibgeschützt und Sie werden in dieser Ansicht keine
Änderungen vornehmen können.
In der Ansicht des POM Editors wie in Figure 14.27, “Übersichtsanzeige des POM
Editors für das Projekt idiom-core” (Abbildung 14.27: "Übersichtsanzeige des

                                                                            401
Maven in Eclipse: m2eclipse

POM Editors für das Projekt idiom-core") dargestellt, können Sie ebenfalls die
Ansicht des übergeordneten POM aufrufen. Wählen Sie „Übergeordnetes POM
öffnen" von der Toolbar an der oberen rechten Ecke des POM Editors (gelber Pfeil
nach oben).
Der POM Editor bietet eine Anzahl verschieden Ansichten der Informationen des
POM, im letzten Tab des Editors können Sie die darunterliegende XML-Datei
ansehen. Ebenfalls Vorhanden ist ein Tab bezüglich Abhängigkeiten, dargestellt in
Figure 14.30, “Abhängigkeitsansicht des POM Editors” (Abbildung 14.30:
"Abhängigkeitsansicht des POM Editors"). Dieses gibt Ihnen eine einfache
Möglichkeit Abhängigkeiten zu verwalten, sowie den Abschnitt
dependencyManagement des POM zu editieren. Die Abhängigkeitsverwaltung
integriert ebenfalls die Artefakt Suchfunktion des m2eclipse Plugins. Beim
Arbeiten in den Feldern der Abhängigkeiten können Sie auf Aktionen aus den
Editorfeldern oder auch Ctrl + Leertaste Hilfestellungen zurückgreifen.
Müssen Sie einmal weitere Details über bestimmte Artefakte in Erfahrung bringen,
so können Sie die Funktion „Webseite öffnen von innerhalb des Abhängigkeiten
aufrufen, um eine entsprechenden Webseite aufzurufen.




                                                                              402
Maven in Eclipse: m2eclipse




Figure 14.30. Abhängigkeitsansicht des POM Editors

Der Build Tab, dargestellt in Figure 14.31, “Build Tab des POM Editors”
(Abbildung 14.31: "Build Tab des POM Editors") gibt Ihnen Zugriff auf den
Abschnitt der Build Elemente. Aus diesem Tab heraus können Sie die benutzten
Verzeichnisse, Erweiterungen sowie Standard Goal-Namen sowie
Ressource-Verzeichnisse bestimmen.




                                                                           403
Maven in Eclipse: m2eclipse




Figure 14.31. Build Tab des POM Editors

Wir haben hier nur eine Untermenge der Funktionalität des POM Editors
aufgezeigt. Sollten Sie an weiteren Informationen interessiert sein, so laden Sie
sich doch bitte das m2eclipse Plugin herunter und installieren Sie es.



14.12. Projektabhängigkeiten mit m2eclipse
analysieren
Die neueste Version des m2eclipse Plugin umfasst unter anderem auch eine Editor
Version mit zusätzlichen Abhängigkeitsanalyse-Werkzeugen. Diese werden die
Art, wie Abhängigkeiten verwaltet werden, grundsätzlich ändern. Eine der

                                                                                    404
Maven in Eclipse: m2eclipse

grundlegenden Eigenschaften warum Maven zum Einsatz kommt ist die Art und
Weise wie Abhängigkeiten verwaltet werden. Nehmen Sie an, Sie schreiben an
einer Applikation welche auf der Spring Hibernate3 Integration aufbaut, alles
was Sie tun müssen ist, sich auf den Artefakten spring-hibernate3 des zentralen
Maven Repository abzustützen. Maven wird dieses Artefakt einlesen und alle
weiteren transitiven Abhängigkeiten zufügen. Während dies eine Funktionalität ist,
welche viele Anwender zum Einsatz von Maven lockt, so kann es eben auch
verwirrend werden, sobald ein Projekt auf dutzenden von Abhängigkeiten aufbaut,
und diese jeweils weitere duzende Transitive Abhängigkeiten mit sich bringen.
Probleme treten vor allem dann auf, wenn Sie von einem Projekt abhängen, dessen
POM uvollständig ist, insbesondere wenn Abhängigkeiten nicht ausreichend
definiert und als optional gekennzeichnet sind, oder sobald es Konflikte zwischen
transitiven Abhängigkeiten gibt. Im Falle, dass eine der Voraussetzungen der
Ausschluss von commons-logging oder des Servlet API ist, oder im Falle dass Sie
herausfinden müssen warum in einem bestimmten Gültigkeitsbereich eine
bestimmte Abhängigkeit auftritt, werden Sie regelmässig gezwungen sein, auf die
Kommandozeilen Werkzeuge dependency:tree und dependency:resolve
zurückzugreifen.
Das sind die Momente, in denen Ihnen der neue POM Editor des m2eclipse Plugin
zur Hilfe kommt. Wenn Sie ein Projekt mit vielen Abhängigkeiten öffnen, so
werden Sie die zweispaltige Darstellung der Abhängigkeiten wie in Figure 14.32,
“Baum der Abhängigkeiten dargestellt im POM Editor” (Abbildung 14.32: "Baum
der Abhängigkeiten dargestellt im POM Editor") sehen. Die linke Spalte der
Anzeige veranschaulicht den Baum der Abhängigkeiten. Die erste Ebene stellt
hierbei die direkten Abhängigkeiten zu Ihrem Projekt dar. Jede weitere Ebene ist
dabei die nächste Stufe der Hierarchie. Die linke Spalte hingegen gibt Ihnen ein
einfaches Werkzeug zur Hand um herauszufinden wie eine bestimmte
Abhängigkeit in Ihr Projekt gefunden hat. Die rechte Anzeige Zeigt Ihnen alle
aufgelösten Abhängigkeiten an; das stellt die tatsächliche Liste der Abhängigkeiten
dar, nach der Auflösung aller Konflikte sowie Anwendung aller
Gültigkeitsbereiche und stellt somit die Liste der Abhängigkeiten dar, wie Sie Ihr
Projekt zur Kompilierung, Test und Paketierung benutzen wird.



                                                                               405
Maven in Eclipse: m2eclipse




Figure 14.32. Baum der Abhängigkeiten dargestellt im POM Editor

Die Funktionalität welche den Tab Abhängigkeiten so wertvoll macht ist, dass es
bei Nachforschungen nachvollziebar ist, wie eine bestimmte Abhängigkeit in die
Liste der Abhängigkeiten gekommen ist. Such- und Filter- Funktionalitäten welche
dem Editor zur Verfügung stehen machen es ein leichtes die Projektabhängigkeiten
zu bearbeiten. Sie können das Suche Feld der Eingabefelder sowie die Aktivitäten
Sortieren und Filtern der Abhängigkeits-Hierarchie und der Aufgelösten
Abhängigkeiten einsetzen, um sich durch die Abhängigkeiten zu hangeln.
Figure 14.33, “Lokalisieren von Abhängigkeiten im Abhängigkeitsbaum”
(Abbildung 14.33: "Lokalisieren von Abhängigkeiten im Abhängigkeitsbaum")
stellt dar, was passiert, wenn man die Abhängigkeit commons-logging in der Liste
der Aufgelösten Abhängigkeiten auswählt. Sollte Filterung auf der
Abhängigkeitshierarchie der linken Seite eingestellt sein, so wird die Auswahl
einer Abhängigkeit auf der rechten Seite automatisch alle die Knoten des
Abhängigkeitsbaumes anzeigen, welche zu deren Inklusion geführt haben. Sollte

                                                                            406
Maven in Eclipse: m2eclipse

Sie versuchen eine bestimmte Abhängigkeit loszuwerden, so wird Ihnen dieses
Werkzeug helfen herauszufinden, welche Abhängigkeiten (eben auch transitive
Abhängigkeiten) dazu beitragen, dass eine bestimmte Abhängigkeit aufgelöst
wurde. In anderen Worten, sollten Sie einen Artefakten wie z.B. commons-logging
von Ihrer Liste der Abhängigkeiten löschen wollen, werden Sie hierzu das
Abhängigkeits-Werkzeug benutzen wollen.




Figure 14.33. Lokalisieren von Abhängigkeiten im Abhängigkeitsbaum

Das m2eclipse Plugin bietet Ihnen des Weiteren auch die Möglichkeit den Baum
der Abhängigkeiten graphisch darzustellen. Figure 14.34, “Darstellung eines
Projektes als Graphen” (Abbildung 14.34: "Darstellung eines Projektes als
Graphen"), stellt die Abhängigkeiten des idiom-core Projekts dar. Die aller
oberste Box stellt das idiom-core Projekt dar, alle darunterliegenden
Abhängigkeiten sind sinngemäss darunter dargestellt. Direkte Abhängigkeiten sind
durch direkte Verbindungen gekennzeichnet, transitive Abhängigkeiten gehen von


                                                                            407
Maven in Eclipse: m2eclipse

andren Abhängigkeiten aus. Sie können jede beliebige Abhängigkeit auswählen,
um die Verbindungen hervorzuheben, oder sie können mit dem Suche Werkzeug
im oberen Bereich der Seite die entsprechenden Knoten finden.
Beachten Sie bitte auch das Symbol des “offenen Ordners” für diejenigen
Artefakte welche lokal vorliegen, und das Symbol der Jar Datei für Artefakte
welche auf ein Maven-Repository zeigen.




Figure 14.34. Darstellung eines Projektes als Graphen

Der dargestellt Graph kann durch Rechtsklicken der Maustaste geändert werden.
Sie können ebefalls Einstellen, ob Sie die artifactId, groupId, version sowie
Gültigkeitsbereich dargestellt haben möchten, und ob der Text in der Zeile
umgebrochen werden soll.Figure 14.35, “Radiale Darstellung des
Abhängigkeitsgraphen” (Abbildung 14:35 "Radiale Darstellung des
Abhängigkeitsgraphen") stellt den gleichen Graphen aus Figure 14.34,
“Darstellung eines Projektes als Graphen” (Abbildung 14.34) in einem anderen
Layout dar.




                                                                               408
Maven in Eclipse: m2eclipse




Figure 14.35. Radiale Darstellung des Abhängigkeitsgraphen




14.13. Maven Einstellungen
Die Möglichkeit, Maven Grundeinstellungen sowie einige Optionen zu setzen, ist
ein wichtiger Aspekt an der Arbeit mit Maven. m2eclipse bietet die Möglichkeit
diese Einstellungen in der Maven Einstellungen Seite innerhalb von Eclipse zu
setzen. Typischerweise, beim Einsatz von Maven von der Befehlszeile, werden
diese Einstellungen als Optionen übergeben oder aus dem Verzeichnis ~/.m2
ausgelesen. m2eclipse bietet Zugriff auf die wichtigsten Einstellungen von
innerhalb der IDE. Figure 14.36, “Maven Einstellungen in Eclipse” (Abbildung
14.36 "Maven Einstellungen in Eclipse") stellt die Einstellungsseite innerhalb von

                                                                               409
Maven in Eclipse: m2eclipse

Eclipse dar:




Figure 14.36. Maven Einstellungen in Eclipse

Die Tick-Boxen im oberen Bereich erlauben:

   • Maven im Offline Modus zu betreiben, d.h. ohne Zugriff auf entfernte
     Repositorien

   • Debug Ausgaben auf die Maven Konsole umleiten

   • Quell-Jar Dateien von Artefakten aus entfernten Repositorien herunterladen

   • JavaDoc von Artefakten aus entfernten Repositorien herunterladen

   • Indexe von entfernten Repositorien beim Start herunterladen und
     aufzufrischen.
Der nächste Abschnitt erlaubt in einem Fenster die Auswahl der auszuführenden

                                                                            410
Maven in Eclipse: m2eclipse

Goals wann immer ein Projekt importiert wird und auch sobald Quellverzeichnisse
eines gegebenen Projektes geändert werden. Das Standard Goal ist
process-ressources, welches die Ressourcen eines Projektes in das
Zielverzeichnis kopiert und das Projekt zur Paketierung vorbereitet. Diese
Auswahl anzupassen ist hilfreich, sollten sie benutzerdefinierte Goals aufrufen um
Ressourcen zu verarbeiten oder unterstützende Konfigurationen zu generieren.
Sollten Sie Hilfe brauchen, ein bestimmtes Goal auszuwählen, so drücken sie den
Select … Auswahlknopf um den „Goals“ Dialog aufzurufen. Das linke Fenster aus
Figure 14.37, “Maven Goal Dialogfenster” (Abbildung 14.37: "Maven Goal
Dialogfenster") gibt die Auflistung aller Maven Standardlebenszyklen wieder.




                                                                              411
Maven in Eclipse: m2eclipse




Figure 14.37. Maven Goal Dialogfenster

Es ist gut möglich, dass Sie beim Anblick der vielen verfügbaren Goals überwältigt
sind. Es bestehen buchstäblich hunderte Maven Plugins für alles Mögliche, von der
Generierung der Datenbank Struktur, über das Durchführen von Integrationstests,


                                                                              412
Maven in Eclipse: m2eclipse

das Durchführen von statischen Analysen bis hin zur Generierung von Web
Services mittels XFire. Der Goals-Dialog bietet mehr als 200 Plugins mit
auswählbaren Goals. Das Fenster der rechten Seite der Figure 14.37, “Maven Goal
Dialogfenster” (Abbildung 14.37: "Maven Goal Dialogfenster") stellt z.B. die
angebotenen Goals des Tomcat Maven Plugin dar. Mittels der Maske Suchen
können Sie wie gewohnt die Liste reduzieren. m2eclipse wird während Sie die
Eingabe vornehmen die Liste gemäss Ihrer Auswahl anpassen.
Eine weitere Option ist die Anzeige der Maven Installationen Konfiguration wie
diese in Figure 14.38, “Maven Installationen Auswahl Anzeige” (Abbildung 14.38:
"Maven Installationen Auswahl Anzeige") dargestellt ist:




Figure 14.38. Maven Installationen Auswahl Anzeige



                                                                            413
Maven in Eclipse: m2eclipse

Auf dieser Seite können Sie weitere Maven Installationen Ihrer Eclipse Umgebung
zufügen. Sollten Sie eine von der mitgelieferten verschiedene Maven Version
benutzen wollen, so können Sie auf dieser Seite verschiedene Versionen
konfigurieren. Die Funktionalität ist ähnlich der, der Java Virtuellen Maschine
(JVM) Konfiguration innerhalb von Eclipse. Eine eingebettete Version von Maven,
bekannt unter dem Titel Maven Embedder ist bereits gesetzt. Diese wird benutzt
um Maven innerhalb von Eclipse auszuführen. Sollten Sie eine andere Installation
neben der von Embedder angebotenen einsetzen wollen, können Sie diese mittels
des Add Buttons zufügen. Abbildung Figure 14.38, “Maven Installationen
Auswahl Anzeige” (14.38: "Maven Installationen Auswahl Anzeige") zeigt die
Konfiguration mit Maven Embedder, Maven 2.0.9 sowie Maven 2.1-SNAPSHOT.
Die Maven Installationen Auswahl Anzeige erlaubt Ihnen ebenfalls die Dateien der
lokalen sowie globalen Einstellungen, der settings.xml Dateien zu setzen.
Sollten Sie den Ort dieser Dateien nicht spezifizieren, so geht Maven davon aus,
das es die globalen Einstellungen unter conf/settings.xml der ausgewählten
Maven Installation findet. Sie können ebenfalls den Ort der Datei der
benutzerdefinierten Einstellungen von ~/.m2/settings.xml umdefinieren, sowie
den Ort des lokalen Maven Repository unterschiedlich von ~/.m2/repositroy
definieren.
Ebenfalls zur Auswahl in den Einstellungen von Eclipse steht die Möglichkeit
Maven Version Dekoratoren einzustellen. Diese Einstellung gibt die Möglichkeit
die aktuelle Version eines Artefakten im Eclipse Package Explorers dazustellen
und wird in Figure 14.39, “Einstellen der Maven Versions-Dekoratoren”
(Abbildung 14.39 "Einstellen der Maven Versions-Dekoratoren") dargestellt.




                                                                            414
Maven in Eclipse: m2eclipse




Figure 14.39. Einstellen der Maven Versions-Dekoratoren

Um diese Einstellung zu aktivieren, wählen Sie einfach den Maven Version
Dekorator Tick wie in Figure 14.39, “Einstellen der Maven Versions-Dekoratoren”
(Abbildung 14.39: "Einstellen der Maven Versions-Dekoratoren") dargestellt aus.

                                                                           415
Maven in Eclipse: m2eclipse

Sollte dies nicht ausgewählt sein, so wird das Projekt lediglich mit dem Namen
sowie dem relativen Pfad innerhalb des Package Explorers dargestellt, wie in
Figure 14.40, “Package Explorer ohne Maven Versions-Dekoratoren” (Abbildung
14.40 "Package Explorer ohne Maven Versions-Dekoratoren") zu sehen:




Figure 14.40. Package Explorer ohne Maven Versions-Dekoratoren

Sobald die Maven Versions-Dekoratoren zugeschaltet sind, wird dem
Projektnamen die aktuelle Projektversion zugefügt wie dies in Figure 14.41,
“Package Explorer mit zugeschalteten Maven Versions-Dekoratoren” (Abbildung
14.41: "Package Explorer mit zugeschalteten Maven Versions-Dekoratoren"):




                                                                           416
Maven in Eclipse: m2eclipse




Figure 14.41. Package Explorer mit zugeschalteten Maven
Versions-Dekoratoren

Diese nützliche Funktion erlaubt doch auf einen Blick die eingebundenen Projekt
Versionen zu überblicken, ohne den Umweg über das POM einzuschlagen.



14.14. Zusammenfassung
m2eclipse ist mehr als nur ein Plugin welches die Unterstützung für Maven
innerhalb von Eclipse bereitstellt. Vielmehr ist es eine umfassende Integration
welche den Umgang beginnend mit dem Erstellen eines Projektes bis hin zum
Herausfinden von impliziten Abhängigkeiten um Längen vereinfacht. m2eclipse ist
ein erster Schritt in die Richtung einer Entwicklungsumgebung, welche den
                                                                             417
Maven in Eclipse: m2eclipse

reichen semantischen Fundus des zentralen Maven Repositorien nutzt. Mit der
Verbreitung des m2eclipse Plugin werden viele weitere Archetypen veröffentlicht
werden, und weitere Projekte werden Wert darin sehen, deren Artefakte ins
zentrale Maven Repository zu publizieren. Sollten Sie bereits zuvor versucht haben
Eclipse und Maven - ohne die Hilfe eines Werkzeuges welches versteht mit den
hierarchischen Abhängigkeiten umzugehen – zu benutzen, so können Sie
einschätzen wie wichtig und zentral die saubere Integration ist, welche ein
Arbeiten mit verschachtelten Projekten ermöglicht, welche multi-modulare
Projekte mit sich bringen.




                                                                              418
Chapter 15. Site Generation

15.1. Introduction
Successful software applications are rarely produced by a team of one. When we're
talking about any software worth writing, we're usually dealing teams of
collaborating developers ranging anywhere in size from a handful of programmers
working in a small team to hundreds or thousands of programmers working in
large distributed environment. Most open source projects (such as Maven) succeed
or fail based on the presence or absence of well written documentation for a
widely-distributed, ad-hoc collection of users and developers. In all environments
it is important for projects to have an easy way to publish and maintain online
documentation. Software development is primarily an exercise in collaboration and
communication, and publishing a Maven site is one way to make sure that your
project is communicating with your end-users.
A web site for an open source project is often the foundation for both the end-user
and developer communities alike. End-users look to a project's web site for
tutorials, user guides, API documentation, and mailing list archives, and
developers look to a project's web site for design documents, code reports, issue
tracking, and release plans. Large open-sources projects may be integrated with
wikis, issue trackers, and continuous integration systems which help to augment a
project's online documentation with material that reflects the current status of
ongoing development. If a new open source project has an inadequate web site
which fails to convey basic information to prospective users, if often is a sign that
the project in question will fail to be adopted. In other words, for an open source
project, the site and the documentation are as important to the formation of a
community as the code itself.
Maven can be used to create a project web site to capture information which is
relevant to both the end-user and the developer audience. Out of the box, Maven
can generate reports on everything from unit test failures to package coupling to
reports on code quality. Maven provides you with the ability to write simple web

                                                                                  419
Site Generation

pages and render those pages against a consistent project template. Maven can
publish site content in multiple formats including XHTML and PDF. Maven can be
used to generate API document and can also be used to embedded Javadoc and
source code in your project's binary release archive. Once you've used Maven to
generate all of your project's end-user and developer documentation, you can then
use Maven to publish your web site to a remote server.



15.2. Building a Project Site with Maven
To illustrate the process of building a project website, create a sample Maven
project with the archetype plugin:
$ mvn archetype:create -DgroupId=org.sonatype.mavenbook -DartifactId=sample-project


This creates the simplest possible Maven project with a one Java class in
src/main/java and a simple POM. You can then build a Maven site by simply
running mvn site. To build the site and preview the result in a browser, you can run
mvn site:run, this will build the site and start an embedded instance of Jetty.
$ cd sample-project
$ mvn site:run
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'site'.
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-project
[INFO]    task-segment: [site:run] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] Setting property: classpath.resource.loader.class =>
       'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] [site:run]
2008-04-26 11:52:26.981::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Starting Jetty on http://localhost:8080/
2008-04-26 11:52:26.046::INFO: jetty-6.1.5
2008-04-26 11:52:26.156::INFO: NO JSP Support for /, did not find
         org.apache.jasper.servlet.JspServlet
2008-04-26 11:52:26.244::INFO: Started SelectChannelConnector@0.0.0.0:8080


Once Jetty starts and is listening to port 8080, you can see the project's site when
you go to http://localhost:8080/ in a web browser. You can see the results in

                                                                                  420
Site Generation

Figure 15.1, “Simple Generated Maven Site”.




Figure 15.1. Simple Generated Maven Site

If you click around on this simple site, you'll see that it isn't very helpful as a real
project site. There's just nothing there (and it doesn't look very good). Since the
sample-project hasn't configured any developers, mailing lists, issue tracking
providers, or source code repositories, all of these pages on the project site will
have no information. Even the index page of the site states, "There is currently no
description associated with this project". To customize the site, you'll have to start
add content to the project and to the project's POM.
If you are going to use the Maven Site plugin to build your project's site, you'll
want to customize it. You will want to populate some of the important fields in the
POM that tell Maven about the people participating in the project, and you'll want
to customize the left-hand navigation menu and the links visible in the header of
the page. To customize the contents of the site and affect the contents of the
left-hand navigation menu, you will need to edit the site descriptor.


                                                                                     421
Site Generation


15.3. Customizing the Site Descriptor
When you add content to the site, you are going to want to modify the left-hand
navigation menu that is generated with your site. The following site descriptor
customizes the logo in the upper left-hand corner of the site. In addition to
customizing the header of the site, this descriptor adds a menu section to the
left-hand navigation menu under the heading "Sample Project". This menu
contains a single link to an overview page.

Example 15.1. An Initial Site Descriptor
<project name="Sample Project">
  <bannerLeft>
    <name>Sonatype</name>
    <src>images/logo.png</src>
    <href>http://www.sonatype.com</href>
  </bannerLeft>
  <body>
    <menu name="Sample Project">
      <item name="Overview" href="index.html"/>
    </menu>
    <menu ref="reports"/>
  </body>
</project>




This site descriptor references one image. This logo.png image should be placed
in ${basedir}/src/site/resources/images. In addition to the change to the site
descriptor, you'll want to create a simple index.apt page in
${basedir}/src/site/apt. Put the following content in index.apt, it will be
transformed to the index.html and serve as the first page a user sees when they
come to your project's Maven-generated web site.
  Welcome to the Sample Project, we hope you enjoy your time
  on this project site. We've tried to assemble some
  great user documentation and developer information, and
  we're really excited that you've taken the time to visit
  this site.

What is Sample Project

  Well, it's easy enough to explain.     This sample project is


                                                                                  422
Site Generation

  a sample of a project with a Maven-generated site from
  Maven: The Definitive Guide.   A dedicated team of volunteers
  help maintain this sample site, and so on and so forth.


To preview the the site, run mvn clean site followed by mvn site:run:
$ mvn clean site
$ mvn site:run


Once you do this, load the page in a browser by going to http://localhost:8080. You
should see something similar to the screenshot in Figure 15.2, “Customized
Sample Project Web Site”.




Figure 15.2. Customized Sample Project Web Site



15.3.1. Customizing the Header Graphics
To customize the graphics which appear in the upper left-hand and right-hand

                                                                               423
Site Generation

corners of the page, you can use the bannerLeft and bannerRight elements in a
site descriptor.

Example 15.2. Adding a Banner Left and Banner Right to Site Descriptor
<project name="Sample Project">

  <bannerLeft>
    <name>Left Banner</name>
    <src>images/banner-left.png</src>
    <href>http://www.google.com</href>
  </bannerLeft>

  <bannerRight>
    <name>Right Banner</name>
    <src>images/banner-right.png</src>
    <href>http://www.yahoo.com</href>
  </bannerRight>
  ...
</project>




Both the bannerLeft and bannerRight elements take name, src, and href child
elements. In the site descriptor shown above, the Maven Site plugin will generate a
site with banner-left.png in the left-hand corner of the page and banner-right in
the right-hand corner of the page. Maven is going to look in
${basedir}/src/site/resources/images for these images.



15.3.2. Customizing the Navigation Menu
To customize the contents of the navigation menu, use the menu element with item
child elements. The menu element adds a section to the left-hand navigation menu.
Each item is rendered as a link in that menu.

Example 15.3. Creating Menu Items in a Site Descriptor
<project name="Sample Project">
  ...
  <body>

    <menu name="Sample Project">
      <item name="Introduction" href="index.html"/>


                                                                               424
Site Generation

      <item   name="News" href="news.html"/>
      <item   name="Features" href="features.html"/>
      <item   name="Installation" href="installation.html"/>
      <item   name="Configuration" href="configuration.html"/>
      <item   name="FAQ" href="faq.html"/>
    </menu>
    ...
  </body>
</project>




Menu items can also be nested. If you nest items, you will be creating a collapsible
menu in the left-hand navigation menu. The following example adds a link
"Developer Resources" which links to /developer/index.html. When a user is
looking at the Developer Resources page, the menu items below the Developer
Resources menu item will be expanded.

Example 15.4. Adding a Link to the Site Menu
<project name="Sample Project">
  ...
  <body>
    ...
    <menu name="Sample Project">
      ...
      <item name="Developer Resources" href="/developer/index.html"
                collapse="true">
         <item name="System Architecture" href="/developer/architecture.html"/>
         <item name="Embedder's Guide" href="/developer/embedding.html"/>
      </item>
    </menu>
    ...
  </body>
</project>




When an item has the collapse attribute set to true, Maven will collapse the item
until a user is viewing that specific page. In the previous example, when the user is
not looking at the Developer Resources page, Maven will not display the System
Architecture and Embedder's Guide links; instead, it will display an arrow pointing
to the Developer Resources link. When the user is viewing the Developer
Resources page it will show these links with an arrow pointing down.


                                                                                 425
Site Generation


15.4. Site Directory Structure
Maven places all site document under src/site. Documents of similar format are
placed in subdirectories of src/site. All APT documents should be in
src/site/apt, all FML documents should be in src/site/fml, and XDoc
documents should be in src/site/xdoc. The site descriptor should be in
src/site/site.xml, and all resources should be stored under
src/site/resources. When the Maven Site plugin builds a web site, it will copy
everything in the resources directory to the root of the site. If you store an image in
src/site/resources/images/test.png, they you would refer to the image from
your site documentation using the relative path images/test.png.
The following examples shows the location of all files in a project which contains
APT, FML, HTML, XHTML, and some XDoc. Note that the XHTML content is
simply stored in the resources directory. The architecture.html file will not be
processed by Doxia, it will simply be copied to the output directory. You can use
this approach if you want to include unprocessed HTML content and you don't
want to take advantage of the templating and formatting capabilities of Doxia and
the Maven Site plugin.
sample-project
+- src/
   +- site/
      +- apt/
      | +- index.apt
      | +- about.apt
      | |
      | +- developer/
      |     +- embedding.apt
      |
      +- fml/
      | +- faq.fml
      |
      +- resources/
      | +- images/
      | | +- banner-left.png
      | | +- banner-right.png
      | |
      | +- architecture.html
      | +- jira-roadmap-export-2007-03-26.html
      |
      +- xdoc/
      | +- xml-example.xml

                                                                                   426
Site Generation

       |
       +- site.xml


Note that the developer documentation is stored in
src/site/apt/developer/embedding.apt. This extra directory below the apt
directory will be reflected in the location of the resulting HTML page on the site.
When the Site plugin renders the contents of the src/site/apt directory it will
produce HTML output in directories relative to the site root. If a file is in the apt
directory it will be in the root directory of the generated web site. If a file is in the
apt/developer directory it will be generated in the developer/ directory of the
web site.



15.5. Writing Project Documentation
Maven uses a documentation-processing engine called Doxia which reads multiple
source formats into a common document model. Doxia can then manipulate
documents and render the result into several output formats, such as PDF or
XHTML. To write document for your project, you will need to write your content
in a format which can be parsed by Doxia. Doxia currently has support for Almost
Plain Text (APT), XDoc (a Maven 1.x documentation format), XHTML, and FML
(useful for FAQ documents) formats.
This chapter has a cursory introduction to the APT format. For a deeper understand
of the APT format, or for an in-depth introduction to XDoc or FML, please see the
following resources:

    • APT Reference: http://maven.apache.org/doxia/format.html

    • XDoc Reference: http://jakarta.apache.org/site/jakarta-site2.html

    • FML Reference: http://maven.apache.org/doxia/references/fml-format.html


15.5.1. APT Example
Example 15.5, “APT Document” shows a simple APT document with an

                                                                                      427
Site Generation

introductory paragraph and a simple list. Note that the list is terminated by the
psuedo-element "[]".

Example 15.5. APT Document
---
Introduction to Sample Project
---
Brian Fox
---
26-Mar-2008
---

Welcome to Sample Project

  This is a sample project, welcome!   We're excited that you've decided to
  read the index page of this Sample Project. We hope you enjoy the simple
  sample project we've assembled for you.

  Here are some useful links to get you started:

   * {{{news.html}News}}

   * {{{features.html}Features}}

   * {{{faq.html}FAQ}}

   []



If the APT document from Example 15.5, “APT Document” were placed in
src/site/apt/index.apt, the Maven Site plugin will parse the APT using Doxia
and produce XHTML content in index.html.


15.5.2. FML Example
Many projects maintain a Frequently Asked Questions (FAQ) page. Example 15.6,
“FAQ Markup Language Document” shows an example of an FML document.

Example 15.6. FAQ Markup Language Document
<?xml version="1.0" encoding="UTF-8"?>
<faqs title="Frequently Asked Questions">
  <part id="General">
    <faq id="sample-project-sucks">

                                                                                    428
Site Generation

      <question>Sample project doesn't work. Why does sample
        project suck?</question>
      <answer>
        <p>
           We resent that question. Sample wasn't designed to work, it was
           designed to show you how to use Maven.    If you really think
           this project sucks, then keep it to yourself.    We're not
           interested in your pestering questions.
        </p>
      </answer>
    </faq>
    <faq id="sample-project-source">
      <question>I want to put some code in Sample Project,
        how do I do this?</question>
      <answer>
        <p>
           If you want to add code to this project, just start putting
           Java source in src/main/java.   If you want to put some source
           code in this FAQ, use the source element:
        </p>
        <source>
           for( int i = 0; i < 1234; i++ ) {
             // do something brilliant
           }
        </source>
      </answer>
    </faq>
  </part>
</faqs>




15.6. Deploying Your Project Website
Once your project's documentation has been written and you've creates a site to be
proud of, you will want to deploy it a server. To deploy your site you'll use the
Maven Site plugin which can take care of deploying your project's site to a remote
server using a number of methods including FTP, SCP, and DAV. To deploy the
site using DAV, configure the site entry of the distributionManagement section
in the POM, like this:

Example 15.7. Configuring Site Deployment
<project>
  ...

                                                                               429
Site Generation

  <distributionManagement>
    <site>
      <id>sample-project.website</id>
      <url>dav:https://dav.sample.com/sites/sample-project</url>
    </site>
  </distributionManagement>
  ...
</project>




The url in distribution management has a leading indicator dav which tells the
Maven Site plugin to deploy the site to a URL that is able to understand WebDAV.
Once you have added the distributionManagement section to our
sample-project POM, we can try deploying the site:

$ mvn clean site-deploy


If you have a server configured properly that can understand WebDAV, Maven
will deploy your project's web site to the remote server. If you are deploying this
project to a site and server visible to the public, you are going to want to configure
your web server to access for credentials. If your web server asks for a username
and password (or other credentials, you can configure this values in your
~/.m2/settings.xml).



15.6.1. Configuring Server Authentication
To configure a username/password combination for use during the site
deployment, we'll include the following in $HOME/.m2/settings.xml:

Example 15.8. Storing Server Authentication in User-specific Settings
<settings>
  ...
  <servers>
    <server>
      <id>sample-project.website</id>
      <username>jdcasey</username>
      <password>b@dp@ssw0rd</password>
    </server>
    ...
  </servers>
  ...

                                                                                   430
Site Generation

</settings>




The server authentication section can contain a number of authentication elements.
In the event you're using SCP for deployment, you may wish to use public-key
authentication. To do this, specify the publicKey and passphrase elements,
instead of the password element. You may still want to configure the username
element, depending on your server's configuration.


15.6.2. Configuring File and Directory Modes
If you are working in a large group of developers, you'll want to make sure that
your web site's files end up with the proper user and group permissions after they
are published to the remote server. To configure specific file and directory modes
for use during the site deployment, include the following in
$HOME/.m2/settings.xml:


Example 15.9. Configuring File and Directory Modes on Remote Servers
<settings>
  ...
  <servers>
    ...
    <server>
      <id>hello-world.website</id>
      ...
      <directoryPermissions>0775</directoryPermissions>
      <filePermissions>0664</filePermissions>
    </server>
  </servers>
  ...
</settings>




The above settings will make any directories readable and writable by either the
owner or members of the owner's primary group; the anonymous users will only
have access to read and list the directory. Similarly, the owner or members of the
owner's primary group will have access to read and write any files, with the rest of
the world restricted to read-only access.

                                                                                 431
Site Generation


15.7. Customizing Site Appearance
The default Maven template leaves much to be desired. If you wish to customize
your project's website beyond simply adding content, navigational elements, and
custom logos. Maven offers several mechanisms for customizing your website that
offer successively deeper access to content decoration and website structure. For
small, per-project tweaks, providing a custom site.css is often enough. However,
if you want your customizations to be reusable across multiple projects, or if your
customizations involve changing the XHTML that Maven generates, you should
consider creating your own Maven website skin.


15.7.1. Customizing the Site CSS
The easiest way to affect the look and feel of your project's web site is through the
project's site.css. Just like any images or XHTML content you provide for the
website, the site.css file goes in the src/site/resources directory. Maven
expects this file to be in the src/site/resources/css subdirectory. With CSS it is
possible to change text styling properties, layout properties, and even add
background images and custom bullet graphics. For example, if we decided that to
make the menu heading stand out a little more, we might try the following style in
src/site/resources/css/site.css:

#navcolumn h5 {
  font-size: smaller;
  border: 1px solid #aaaaaa;
  background-color: #bbb;
  margin-top: 7px;
  margin-bottom: 2px;
  padding-top: 2px;
  padding-left: 2px;
  color: #000;
}


When you regenerate the website, the menu headers should be framed by a gray
background and separated from the rest of the menu by some extra margin space.
Using this file, any structure in the Maven-generated website can be decorated with
custom CSS. When you change site.css in a specific Maven project, the changes


                                                                                 432
Site Generation

will apply to that specific project. If you are interested in making changes that will
apply to more than one Maven project, you can create a custom skin for the Maven
Site plugin.

         Tip
         There is no good reference for the structure of the default Maven site
         template. If you are attempting to customize the style of your Maven
         project, you should use a Firefox extension like Firebug as a tool to
         explore the DOM for your project's pages.



15.7.2. Create a Custom Site Template
If the default Maven Site structure just doesn't do it for you, you can always
customize the Maven site template. Customizing the Maven Site template gives
you complete control over the ultimate output of the Maven plugin, and it is
possible to customize your project's site template to the point where it hardly
resembles the structure of a default Maven site template.
The Site plugin uses a rendering engine called Doxia, which in turn uses a Velocity
template to render the XHTML for each page. To change the page structure that is
rendered by default, we can configure the site plugin in our POM to use a custom
page template. The site template is fairly complex, and you'll need to have a good
starting point for your customization. Start by copying the default Velocity
template from Doxia's Subversion repository default-site.vm to
src/site/site.vm. This template is written in a templating language called
Velocity. Velocity is a simple templating language which supports simple macro
definition and allows you to access an object's methods and properties using simple
notation. A full introduction is beyond the scope of this book, for more information
about Velocity and a full introduction please go to the Velocity project site at
http://velocity.apache.org.
The default-site.xml template is fairly involved, but the change required to
customize the left-hand menu is relatively straightforward. If you are trying to
change the appearance of a menuItem, locate the menuItem macro. It resides in a

                                                                                  433
Site Generation

section that looks like this:
#macro ( menuItem $item )

  ...

#end


If you replace the macro definition with the macro definition listed below, you will
injects Javascript references into each menu item which will allow the reader to
expand or collapse the menu tree without suffering through a full page reload:
#macro ( menuItem $item $listCount )
  #set ( $collapse = "none" )
  #set ( $currentItemHref = $PathTool.calculateLink( $item.href,
                                                     $relativePath ) )
  #set ( $currentItemHref = $currentItemHref.replaceAll( "", "/" ) )

  #if ( $item && $item.items && $item.items.size() > 0 )
    #if ( $item.collapse == false )
      #set ( $collapse = "collapsed" )
    #else
      ## By default collapsed
      #set ( $collapse = "collapsed" )
    #end

       #set ( $display = false )
       #displayTree( $display $item )

    #if ( $alignedFileName == $currentItemHref || $display )
       #set ( $collapse = "expanded" )
    #end
  #end
  <li class="$collapse">
    #if ( $item.img )
       #if ( ! ( $item.img.toLowerCase().startsWith("http") ||
                 $item.img.toLowerCase().startsWith("https") ) )
         #set ( $src = $PathTool.calculateLink( $item.img, $relativePath ) )
         #set ( $src = $item.img.replaceAll( "", "/" ) )
         <img src="$src"/>
       #else
         <img src="$item.img" align="absbottom" style="border-width: 0"/>
       #end
    #end
    #if ( $alignedFileName == $currentItemHref )
       <strong>$item.name</strong>
    #else
       #if ( $item && $item.items && $item.items.size() > 0 )
       <a onclick="expand('list$listCount')"
             style="cursor:pointer">$item.name</a>
       #else

                                                                                434
Site Generation

       <a href="$currentItemHref">$item.name</a>
       #end
     #end
  #if ( $item && $item.items && $item.items.size() > 0 )
     #if ( $collapse == "expanded" )
     <ul id="list$listCount" style="display:block">
     #else
     <ul id="list$listCount" style="display:none">
       #end
       #foreach( $subitem in $item.items )
          #set ( $listCounter = $listCounter + 1 )
          #menuItem( $subitem $listCounter )
       #end
     </ul>
  #end
  </li>
#end


This change adds a new parameter to the menuItem macro. For the new
functionality to work, you will need to change references to this macro, or the
resulting template may produce unwanted or internally inconsistent XHTML. To
finish changing these references, make a similar replacement in the mainMenu
macro. Find this macro by looking for something similar to the following template
snippet:
#macro ( mainMenu $menus )
  ...
#end


Replace the mainMenu macro with the following implementation:
#macro ( mainMenu $menus )
  #set ( $counter = 0 )
  #set ( $listCounter = 0 )
  #foreach( $menu in $menus )
     #if ( $menu.name )
     <h5 onclick="expand('menu$counter')">$menu.name</h5>
     #end
     <ul id="menu$counter" style="display:block">
       #foreach( $item in $menu.items )
          #menuItem( $item $listCounter )
          #set ( $listCounter = $listCounter + 1 )
       #end
     </ul>
     #set ( $counter = $counter + 1 )
  #end
#end



                                                                              435
Site Generation

This new mainMenu macro is compatible with the new menuItem macro above, and
also provides support for a Javascript-enabled top-level menu. Clicking on a
top-level menu item with children will expand the menu and allow users to see the
entire tree without waiting for a page to load.
The change to the menuItem macro introduced an expand() Javascript function.
This method needs to be added to the main XHTML template at the bottom of this
template file. Find the section that looks similar to the following:
  <head>
    ...
    <meta http-equiv="Content-Type"
             content="text/html; charset=${outputEncoding}" />
    ...
  </head>


and replace it with this:
  <head>
    ...
    <meta http-equiv="Content-Type"
               content="text/html; charset=${outputEncoding}" />
    <script type="text/javascript">
      function expand( item ) {
         var expandIt = document.getElementById( item );
         if( expandIt.style.display == "block" ) {
           expandIt.style.display = "none";
           expandIt.parentNode.className = "collapsed";
         } else {
           expandIt.style.display = "block";
           expandIt.parentNode.className = "expanded";
         }
      }
    </script>
    #if ( $decoration.body.head )
      #foreach( $item in $decoration.body.head.getChildren() )
         #if ( $item.name == "script" )
           $item.toUnescapedString()
         #else
           $item.toString()
         #end
      #end
    #end
  </head>


After modifying the default site template, you'll need to configure your project's
POM to reference this new site template. To customize the site template, you'll

                                                                                 436
Site Generation

need to use the templateDirectory and template configuration properties of the
Maven Site plugin.

Example 15.10. Customizing the Page Template in a Project's POM
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-site-plugin</artifactId>
        <configuration>
           <templateDirectory>src/site</templateDirectory>
           <template>site.vm</template>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>




Now, you should be able to regenerate your project website. When you do so you
may notice that the resources and CSS for the maven site are missing. When a
Maven project customizes the site template, the Site plugin expects the project to
supply all of the default images and CSS. To seed your project's resources, you
may want to copy the resources from the default Doxia site renderer project to your
own project's resources directory by executing the following commands:
$ svn co 
         http://svn.apache.org/repos/asf/maven/doxia/doxia-sitetools/
          trunk/doxia-site-renderer
$ rm 
         doxia-site-renderer/src/main/resources/org/apache/maven/
           doxia/siterenderer/resources/css/maven-theme.css
$ cp -rf 
       doxia-site-renderer/src/main/resources/org/apache/maven/
         doxia/siterenderer/resources/* 
       sample-project/src/site/resources


Check out the doxia-site-renderer project, remove the default maven-theme.css
file and then copy all the resources to your project's src/site/resources
directory.


                                                                               437
Site Generation

When you regenerate the site, you'll notice that a few menu items look like regular
unstyled text. This is caused by a quirky interaction between the site's CSS and our
new custom page template. It can be fixed by modifying our site.css to restore
the proper link color for these menus. Simply add this:
li.collapsed, li.expanded, a:link {
  color:#36a;
}


After regenerating the site, the menu's link color should be corrected. If you
applied the new site template to the same sample-project from this chapter, you'll
notice that the menu now consists of a tree. Clicking on "Developer Resources" no
longer takes you to the "Developer Resources" page; in stead, it expands the
sub-menu. Since you've turned the Developer Resources menu-item into a
dynamically-folding sub-menu, you have lost the ability to reach the
developer/index.apt page. To address this change, you should add an Overview
link to the sub-menu which references the same page:

Example 15.11. Adding a Menu Item to a Site Descriptor
<project name="Hello World">
  ...
  <menu name="Main Menu">
    ...
    <item name="Developer Resources" collapse="true">
      <item name="Overview" href="/developer/index.html"/>
      <item name="System Architecture" href="/developer/architecture.html"/>
      <item name="Embedder's Guide" href="/developer/embedding.html"/>
    </item>
  </menu>
  ...
</project>




15.7.3. Reusable Website Skins
If your organization is created many Maven project sites, you will likely want to
reuse site template and CSS customizations throughout an organization. If you
want thirty projects to share the same CSS and site template, you can use Maven's


                                                                                438
Site Generation

support for skinning. Maven Site skins allow you to package up resources and
templates which can be reused by other projects in lieu of duplicating your site
template for each project which needs to be customized.
While you can define your own skin, you may want to consider using one of
Maven's alternate skins. You can choose from several skins. These each provide
their own layout for navigation, content, logos, and templates:

   • Maven Classic Skin - org.apache.maven.skins:maven-classic-skin:1.0

   • Maven Default Skin - org.apache.maven.skins:maven-default-skin:1.0

    • Maven Stylus Skin - org.apache.maven.skins:maven-stylus-skin:1.0.1
You can find an up-to-date and comprehensive listing in the Maven repository:
http://repo1.maven.org/maven2/org/apache/maven/skins/.
Creating a custom skin is a simple matter of wrapping your customized
maven-theme.css in a Maven project, so that it can be referenced by groupId,
artifactId, and version. It can also include resources such as images, and a
replacement website template (written in Velocity) that can generate a completely
different XHTML page structure. In most cases, custom CSS can manage the
changes you desire. To demonstrate, let's create a designer skin for the
sample-project project, starting with a custom maven-theme.css.
Before we can start writing our custom CSS, we need to create a separate Maven
project to allow the sample-project site descriptor to reference it. First, use
Maven's archetype plugin to create a basic project. Issue the following command
from the directory above the sample-project project's root directory:
$ mvn archetype:create -DartifactId=sample-site-skin
        -DgroupId=org.sonatype.mavenbook


This will create a project (and a directory) called sample-site-skin. Change
directories to the new sample-site-skin directory, remove all of the source code
and tests, and create a directory to store your skin's resources:
$ cd sample-site-skin
$ rm -rf src/main/java src/test
$ mkdir src/main/resources


                                                                                   439
Site Generation


15.7.4. Creating a Custom Theme CSS
Next, write a custom CSS for the custom skin. A Å file in a Maven site skin should
be placed in src/main/resources/css/maven-theme.css. Unlike the site.css
file, which goes in the site-specific source directory for a project, the
maven-theme.css will be bundled in a JAR artifact in your local Maven repository.
In order for the maven-theme.css file to be included in the skin's JAR file, it must
reside in the main project-resources directory, src/main/resources.
As with the default the default site template, you will want to start customizing
your new skin's CSS from a good starting point. Copy the CSS file used by the
default Maven skin to your project's maven-theme.css. To get a copy of this theme
file, save the contents of maven-theme.css from the maven-default-skin project
to src/main/resources/css/maven-theme.css in our new skin project.
Now that we have the base theme file in place, customize it using the CSS from
our old site.css file. Replace the #navcolumn h5 CSS block with the following:
#navcolumn h5 {
  font-size: smaller;
  border: 1px solid #aaaaaa;
  background-color: #bbb;
  margin-top: 7px;
  margin-bottom: 2px;
  padding-top: 2px;
  padding-left: 2px;
  color: #000;
}


Once you've customized the maven-theme.css, build and install the
sample-site-skin JAR artifact to your local Maven repository by running:

$ mvn clean install


Once the installation is complete, switch back to the sample-project project
directory, if you already customized the site.css earlier in this chapter, move
site.css to site.css.bak so it no longer affects the output of the Maven Site
plugin:
$ mv src/site/resources/css/site.css src/site/resources/css/site.css.bak




                                                                                  440
Site Generation

To use the sample-site-skin in the sample-project site, you'll need to add a
reference to the sample-site-skin artifact in the sample-project's site descriptor.
A site references a skin in the site descriptor using the skin element:

Example 15.12. Configuring a Custom Site Skin in Site Descriptor
<project name="Sample Project">
  ...
  <skin>
    <groupId>org.sonatype.mavenbook</groupId>
    <artifactId>sample-site-skin</artifactId>
  </skin>
  ...
</project>




You can think of a Maven Site skin as a site dependency. Site skins are referenced
as artifacts with a groupId and an artifactId. Using a site skin allows you to
consolidate site customizations to a single project, and makes reusing custom CSS
and site templates as easy as reusing build logic through a custom Maven plugin.


15.7.5. Customizing Site Templates in a Skin
Just as you can customize a the site CSS in a Maven Site skin, you can also
customize the site template. Doxia's site-rendering tools will expect to find a file
called META-INF/maven/site.vm inside the skin JAR. To incorporate a custom
page template, copy the template file into the correct location within the
sample-site-skin. Copy the custom site template developed earlier in the chapter
to src/main/resources/META-INF/maven in sample-site-skin:
$ mv sample-project/src/site/site.vm 
    sample-site-skin/src/main/resources/META-INF/maven


If you already customized the site template in sample-project, remove the Site
plugin configuration which pointed to this site template. The Site plugin will
render the site using the site template referenced in the site skin.
<plugin>
  <artifactId>maven-site-plugin</artifactId>
  <configuration>

                                                                                441
Site Generation

    <templateDirectory>src/site</templateDirectory>
    <template>site.vm</template>
  </configuration>
</plugin>


A Maven Site skin is expected to include all of the resources it depends on. This
includes CSS, images, and logos. If you already customized the site template
earlier in the chapter, you've already copied the default doxia-site-renderer
resources to the sample-project's src/site/resources directory. You'll need to
move those files out of the sample-project project and into the new
sample-site-skin project by executing the following commands:

$ cd ..
$ mkdir -p sample-site-skin/src/main/resources/css
$ mv sample-project/src/site/resources/css/maven-base.css 
    sample-site-skin/src/main/resources/css
$ mkdir -p sample-site-skin/src/main/resources/images
$ mv sample-project/src/site/resources/images/logos 
    sample-site-skin/src/main/resources/images
$ mv sample-project/src/site/resources/images/expanded.gif 
    sample-site-skin/src/main/resources/images
$ mv sample/src/site/resources/images/collapsed.gif 
    sample-site-skin/src/main/resources/images


You've changed the sample-site-skin, so you'll need to install this skin into your
local Maven repository. Once you install the skin locally and rebuild the
sample-project web site. You'll see that the skin's custom site template was
applied to the sample-project's web site. You'll notice that the color of the menu
items may be a little off because you haven't added the necessary CSS to the
collapsed and expanded menu items. To do this, modify
src/main/resources/css/maven-theme.css. Change:

a:link {
  ...
}


to this:
li.collapsed, li.expanded, a:link {
  ...
}


Rebuild the skin, then regenerate the website, and you'll see that the menu items

                                                                                442
Site Generation

have returned to normal. You've successfully created a Maven theme which can be
used to apply CSS and templates to a set of projects.



15.8. Tips and Tricks
This section lists some useful tips and tricks you can use when creating a Maven
site.


15.8.1. Inject XHTML into HEAD
To inject XHTML into the HEAD element, add a head element to the body
element in your project's Site descriptor. The following example adds a feed link to
every page in the sample-project web site.

Example 15.13. Injecting HTML into the HEAD element
<project name="Hello World">
  ...
  <body>
    <head>
      <link href="http://sample.com/sites/sample-project/feeds/blog"
            type="application/atom+xml"
            id="auto-discovery"
            rel="alternate"
            title="Sample Project Blog" />
    </head>
    ...
  </body>
</project>




15.8.2. Add Links under Your Site Logo
If you are working on a project which is being developed by an organization, you
may want to add links under your project's logo. Assume that your project is a part
of the Apache Software Foundation, you might want to add a link to the Apache
Software Foundation web site right below your logo, and you might want to add a


                                                                                443
Site Generation

link to a parent project as well. To add links below your site logo, just add a links
element to the body element in the Site descriptor. Each item element in the links
element will be rendered as a link in a bar directly below your project's logo. The
following example would add a link to the Apache Software Foundation followed
by a link to the Apache Maven project.

Example 15.14. Adding Links Under Your Site Logo
<project name="Hello World">
  ...
  <body>
    ...
    <links>
      <item name="Apache" href="http://www.apache.org"/>
      <item name="Maven" href="http://maven.apache.org"/>
    </links>
    ...
  </body>
</project>




15.8.3. Add Breadcrumbs to Your Site
If your hierarchy exists within a logical hierarchy, you may want to place a series
of breadcrumbs to give the user a sense of context and give them a way to navigate
up the tree to projects which might contain the current project as a subproject. To
configure breadcrumbs, add a breadcrumbs element to the body element in the site
descriptor. Each item element will render a link, and the items in the breadcrumbs
element will be rendered in order. The breadcrumb items should be listed from
highest level to lowest level. In the following site descriptor, the Codehaus item
would be seen to contain the Mojo item.

Example 15.15. Configuring the Site's Breadcrumbs
<project name="Sample Project">
  ...
  <body>
    ...
    <breadcrumbs>
      <item name="Codehaus" href="http://www.codehaus.org"/>

                                                                                  444
Site Generation

      <item name="Mojo" href="http://mojo.codehaus.org"/>
    </breadcrumbs>
    ...
  </body>
</project>




15.8.4. Add the Project Version
When you are documenting a project that has multiple versions, it is often very
helpful to list the project's version number on every page. To display your project's
version on the website, simply add the version element to your site descriptor:

Example 15.16. Positioning the Version Information
<project name="Sample Project">
  ...
  <version position="left"/>
  ...
</project>




This will position the version (in the case of the sample-project project, it will say
"Version: 1.0-SNAPSHOT") in the upper left-hand corner of the site, right next to
the default "Last Published" date. Valid positions for the project version are:

   left
   Left side of the bar just below the site logo

   right
   Right side of the bar just below the site logo

   navigation-top
   Top of the menu

   navigation-bottom
   Bottom of the menu


                                                                                   445
Site Generation


   none
   Suppress the version entirely


15.8.5. Modify the Publication Date Format and Location
In some cases, you may wish to reformat or reposition the "Last Published" date
for your project website. Just like the project version tip above, you can specify the
position of the publication date by using one of the following:

   left
   Left side of the bar just below the site logo

   right
   Right side of the bar just below the site logo

   navigation-top
   Top of the menu

   navigation-bottom
   Bottom of the menu

   none
   Suppress the publication entirely

Example 15.17. Positioning the Publish Date
<project name="Sample Project">
  ...
  <publishDate position="navigation-bottom"/>
  ...
</project>




By default, the publication date will be formatted using the date format string
MM/dd/yyyy. You can change this format by using the standard notation found in
the JavaDocs for java.text.SimpleDateFormat (see JavaDoc for
SimpleDateFormat for more information). To reformat the date using yyyy-MM-dd,

                                                                                  446
Site Generation

use the following publishDate element.

Example 15.18. Configuring the Publish Date Format
<project name="Sample Project">
  ...
  <publishDate position="navigation-bottom" format="yyyy-MM-dd"/>
  ...
</project>




15.8.6. Using Doxia Macros
In addition to its advanced document rendering features, Doxia also provides a
macro engine that allows each input format to trigger injection of dynamic content.
An excellent example of this is the snippet macro, which allows a document to pull
a code snippet out of a source file that's available via HTTP. Using this macro, a
small fragment of APT can be rendered into XHTML. The following APT code
calls out to the snippet macro. Please note that this code should be on a single
continuous line, the black slash character is inserted to denote a line break so that
this code will fit on the printed page.
%{snippet|id=modello-model|url=http://svn.apache.org/repos/asf/maven/
archetype/trunk/maven-archetype/maven-archetype-model/src/main/
mdo/archetype.mdo}



Example 15.19. Output of the Snippet Macro in XHTML
<div class="source"><pre>

<model>
  <id>archetype</id>
  <name>Archetype</name>
  <description><![CDATA[Maven's model for the archetype descriptor.
  ]]></description>
  <defaults>
    <default>
      <key>package</key>
      <value>org.apache.maven.archetype.model</value>
    </default>
  </defaults>
  <classes>

                                                                                 447
Site Generation

    <class rootElement="true" xml.tagName="archetype">
      <name>ArchetypeModel</name>
      <description>Describes the assembly layout and packaging.</description>
      <version>1.0.0</version>
      <fields>
         <field>
           <name>id</name>
           <version>1.0.0</version>
           <required>true</required>
           <type>String</type>
         </field>
         ...
      </fields>
    </class>
  </classes>
</model>

</pre></div>




         Warning
         Doxia macros MUST NOT be indented in APT source documents. Doing
         so will result in the APT parser skipping the macro altogether.

For more information about defining snippets in your code for reference by the
snippet macro, see the Guide to the Snippet Macro on the Maven website, at
http://maven.apache.org/guides/mini/guide-snippet-macro.html.




                                                                                 448
Chapter 16. Repository Management with
Nexus
This chapter is deprecated. While the original version of Maven: The Definitive
Guide had an entire chapter dedicated to the Nexus Repository Manager, Sonatype
found that the content was growing fast enough to deserve its own dedicate titled.
We've decided to spin off the content in the Repository Management chapter into a
new book called Repository Management with Nexus. This new book covers both
Nexus Open Source and Nexus Professional, it guides you through the process of
downloading and installing a repository manager, and it provides a detailed survey
of all of the configuration options.
Click here to read Repository Management with Nexus.

         Note
         In the past, a book was a very static object, it was written, edited,
         proofread, and printed. After printing there was little time to reconsider
         the structure or content of a book. While the Internet has offered an
         opportunity for the real-time, constantly evolving on-demand book, the
         publishing industry has yet to come to terms with the idea of a book that
         is "alive" - a book that can change the day after it was sent to the print,
         and a book that can split into two and continue to grow. We decided to
         make this break because it makes sense, if one of our chapters starts to
         grow into a hundred-page beast, we're not just going to throw more pages
         at an already "thick" reference book. (But then again, this book isn't
         "thick" if you are reading it in a web browser, it is all about perspective.)
         What's true about software development is also true about writing. If you
         start out a project with a single, monolithic project, there is going to
         come a time when it makes good sense to refactor a package or collection
         of classes into a separate module. That is exactly what we are doing by
         spinning the Repository Management chapter into Repository
         Management with Nexus. We're "refactoring" the book into two.


                                                                                  449
Repository Management with Nexus


Consider this editor's note to be a deprecation warning. Eventually, we're
going to remove this marker chapter from the book entirely. On the web
it will be a HTTP redirect to the new book, and in the PDF it might be a
place-holder page that references the Nexus book.




                                                                       450
Chapter 17. Writing Plugins

17.1. Introduction
While this chapter covers an advanced topic, don't let the idea of writing a Maven
plugin intimidate. For all of the theory and complexity of this tool, the fundamental
concepts are easy to understand and the mechanics of writing a plugin are
straightforward. After you read this chapter, you will have a better grasp of what is
involved in creating a Maven plugin.



17.2. Programming Maven
Most of this book has dealt with using Maven, and for a book on Maven, you
haven't seen too many code examples dealing with Maven customization. In fact,
you haven't yet seen any. This is by design, 99 out of 100 Maven users will never
need to write a custom plugin to customize Maven; there is an abundance of
configurable plugins, and unless your project has particularly unique requirements,
you will have to work to find a reason to write a new plugin. An even smaller
percentage of people who end up writing custom plugins will ever need to crack
open the source code for Maven and customize a core Maven component. If you
really need to customize the behavior of Maven, then you would write a plugin.
Modifying the core Maven code is as far out of scope for most developers as
modifying the TCP/IP stack on an operating system, it is that abstract for most
Maven users.
On the other hand, if you are going to start writing a custom plugin, you are going
to have to learn a bit about the internals of Maven: How does it manage software
components? What is a Plugin? How can I customize the lifecycle? This section
answers some of those questions, and it introduces a few concepts at the core of
Maven's design. Learning how to write a custom Maven plugin is the gateway to
customizing Maven itself. If you were wondering how to start understanding the
code behind Maven, you've found the proper starting line.

                                                                                 451
Writing Plugins


17.2.1. What is Inversion of Control?
At the heart of Maven is an Inversion of Control (IoC) container named Plexus.
What does it do? It is a system for managing and relating components. While there
is a canonical essay about IoC written by Martin Fowler, the concept and term
have been so heavily overloaded in the past few years it is tough to find a good
definition of the concept that isn't a self-reference (or just a lazy reference to the
aforementioned essay). Instead of resorting to a Wikipedia quote, we'll summarize
Inversion of Control and Dependency Injection with an analogy.
Assume that you have a series of components which need to be wired together.
When you think about components, think stereo components not software
components. Imagine several stereo components hooked up to a Playstation 3 and a
Tivo that have to interface with both an Apple TV box and a 50" flat panel LCD
TV. You bring everything home from the electronics store and you purchase a
series of cables that you are going to use to connect everything to everything else.
You unpack all of these components, put them in the right place and then get to the
job of hooking up fifty thousand coaxial cables and stereo jacks to fifty thousand
digital inputs and network cables. Step back from your home entertainment center
and turn on the TV, you've just performed dependency injection, and you've just
been an inversion of control container.
So what did that have to do with anything? Your Playstation 3 and a Java Bean
both provide an interface. The Playstation 3 has two inputs: power and network,
and one output to the TV. Your JavaBean has three properties: power, network,
and tvOutput. When you open the box of your Playstation 3, it didn't provide you
with detailed pictures and instructions for how to connect it to every different kind
of TV that might be in every different kind of house, and when you look at your
Java Bean it just provides a set of properties, not an explicit recipe for creating and
managing an entire system of components. In an IoC container like Plexus, you are
responsible for declaring the relationships between a set of components which
simply provide an interface of inputs and outputs. You don't instantiate objects,
Plexus does; your application's code isn't responsible for managing the state of
components, Plexus is. Even though it sounds very cheesy, when you start up
Maven, it is starting Plexus and managing a system of related components just like

                                                                                   452
Writing Plugins

your stereo system.
What are the advantages of using an IoC container? What is the advantage of
buying discrete stereo components? If one component breaks, you can drop in a
replacement for your Playstation 3 without having to spend $20,000 on the entire
system. If you are unhappy with your TV, you can swap it out without affecting
your CD player. Most important to you, your stereo components cost less and are
more capable and reliable because manufacturers can build to a set of known
inputs and outputs and focus on building individual components. Inversion of
Control containers and Dependency Injection encourage Disaggregation and the
emergence of standards. The software industry likes to imagine itself as the font of
all new ideas, but dependency injection and inversion of control are really just new
words for the concepts of Disaggregation and interchangeable machinery. If you
really want to know about DI and IoC, learn about the Model T, the Cotton Gin,
and the emergence of a railroad standard in the late 19th century.


17.2.2. Introduction to Plexus
The most important feature of an IoC container implemented in Java is a
mechanism called dependency injection. The basic idea of IoC is that the control of
creating and managing objects is removed from the code itself and placed into the
hands of an IoC framework. Using dependency injection in an application that has
been programmed to interfaces, you can create components which are not bound to
specific implementations of these interfaces. Instead, you program to interfaces
and then configure Plexus to connect the appropriate implementation to the
appropriate component. While your code deals with interfaces, you can capture the
dependencies between classes and components in an XML file that defines
components, implementation classes, and the relationships between your
components. In other words, you can write isolated components, then you can wire
them together using an XML file that defines how the components are wired
together. In the case of Plexus, system components are defined with an XML
document that is found in META-INF/plexus/components.xml.
In a Java IoC container, there are several methods for injecting dependencies
values into a component object: constructor, setter, or field injections. Although

                                                                                 453
Writing Plugins

Plexus is capable of all three dependency injection techniques, Maven only uses
two types: field and setter injection.

    Constructor Injection
   Constructor injection is populating an object's values through its constructor
   when an instance of the object is created. For example, if you had an object of
   type Person which had a constructor Person(String name, Job job), you
   could pass in values for both name and the job via this constructor.

    Setter Injection
   Setter injection is using the setter method of a property on a Java bean to
   populate object dependencies. For example, if you were working with a Person
   object with the properties name and job, an IoC container which uses setter
   injection, would create an instance of Person using a no-arg constructor. Once
   it had an instance of Person, it would proceed to call the setName() and
   setJob() methods.

    Field Injection
   Both Constructor and Setter injection rely on a call to a public method. Using
   Field injection, an IoC container populates a component's dependencies by
   setting an object's fields directly. For example, if you were working with a
   Person object that had two fields name and job, your IoC container would
   populate these dependencies by setting these fields directly (i.e. person.name    =
   "Thomas"; person.job = job;)



17.2.3. Why Plexus?
Spring does happen to be the most popular IoC container at the moment, and
there's a good argument to be made that it has affected the Java "ecosystem" for the
better forcing companies like Sun Microsystems to yield more control to the open
source community and helping to open up standards by providing a pluggable,
component-oriented "bus". But, Spring isn't the only IoC container in open source.
There are many IoC containers (like PicoContainer).
Years and years ago, when Maven was created, Spring wasn't a mature option. The

                                                                                454
Writing Plugins

initial team of committers on Maven were more familiar with Plexus because they
invented it, so they decided to use it as an IoC container. While it might not be as
popular as the Spring Framework, it is no less capable. And, the fact that it was
created by the same person who created Maven makes it a perfect fit. After reading
this chapter you've have an idea of how Plexus works. If you already use an IoC
container you'll notice similarities and differences between Plexus and the
container you currently use.

           Note
           Just because Maven is based on Plexus doesn't mean that the Maven
           community is "anti-Spring" (we've included a whole chapter with a
           Spring example in this book, portions of the Spring project are moving to
           Maven as a build platform). The question, "Why didn't you use Spring?"
           comes up often enough it did make sense to address it here. We know it,
           Spring is a rock star, we don't deny it, and it is on our continuing to-do
           list to introduce people to (and document) Plexus: choice in the software
           industry is always a good thing.



17.2.4. What is a Plugin?
A Maven Plugin is a Maven artifact which contains a plugin descriptor and one or
more Mojos. A Mojo can be thought of as a goal in Maven, and every goal
corresponds to a Mojo. The compiler:compile goal corresponds to the
CompilerMojo class in the Maven Compiler Plugin, and the jar:jar goal
corresponds to the JarMojo class in the Maven Jar Plugin. When you write your
own plugin, you are simply grouping together a set of related Mojos (or goals) in a
single plugin artifact.2

           Note
           Mojo? What is a Mojo? The word mojo2 is defined as "a magic charm or


2
"mojo." The American Heritage® Dictionary of the English Language, Fourth Edition. Houghton
Mifflin Company, 2004. Answers.com 02 Mar. 2008. http://www.answers.com/topic/mojo-1

                                                                                              455
Writing Plugins


         spell", "an amulet, often in a small flannel bag containing one or more
         magic items", and "personal magnetism; charm". Maven uses the term
         Mojo because it is a play on the word Pojo (Plain-old Java Object).

A Mojo is much more than just a goal in Maven, it is a component managed by
Plexus that can include references to other Plexus components.



17.3. Plugin Descriptor
A Maven plugin contains a road-map for Maven that tells Maven about the various
Mojos and plugin configuration. This plugin descriptor is present in the plugin JAR
file in META-INF/maven/plugin.xml. When Maven loads a plugin, it reads this
XML file, instantiates and configures plugin objects to make the Mojos contained
in a plugin available to Maven.
When you are writing custom Maven plugins, you will almost never need to think
about writing a plugin descriptor. In Chapter 10, Der Build Lebenszyklus, the
lifecycle goals bound to the maven-plugin packaging type show that the
plugin:descriptor goal is bound to the generate-resources phase. This goal
generates a plugin descriptor off of the annotations present in a plugin's source
code. Later in this chapter, you will see how Mojos are annotated, and you will
also see how the values in these annotations end up in the
META-INF/maven/plugin.xml file.

Example 17.1, “Plugin Descriptor” shows a plugin descriptor for the Maven Zip
Plugin. This plugin is a contrived plugin that simply zips up the output directory
and produces an archive. Normally, you wouldn't need to write a custom plugin to
create an archive from Maven, you could simply use the Maven Assembly Plugin
which is capable of producing a distribution archive in multiple formats. Read
through the following plugin descriptor to get an idea of the content it contains.

Example 17.1. Plugin Descriptor
<plugin>
  <description></description>

                                                                               456
Writing Plugins

<groupId>com.training.plugins</groupId>
<artifactId>maven-zip-plugin</artifactId>
<version>1-SNAPSHOT</version>
<goalPrefix>zip</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
<mojos>
  <mojo>
    <goal>zip</goal>
    <description>Zips up the output directory.</description>
    <requiresDirectInvocation>false</requiresDirectInvocation>
    <requiresProject>true</requiresProject>
    <requiresReports>false</requiresReports>
    <aggregator>false</aggregator>
    <requiresOnline>false</requiresOnline>
    <inheritedByDefault>true</inheritedByDefault>
    <phase>package</phase>
    <implementation>com.training.plugins.ZipMojo</implementation>
    <language>java</language>
    <instantiationStrategy>per-lookup</instantiationStrategy>
    <executionStrategy>once-per-session</executionStrategy>
    <parameters>
      <parameter>
         <name>baseDirectory</name>
         <type>java.io.File</type>
         <required>false</required>
         <editable>true</editable>
         <description>Base directory of the project.</description>
      </parameter>
      <parameter>
         <name>buildDirectory</name>
         <type>java.io.File</type>
         <required>false</required>
         <editable>true</editable>
         <description>Directory containing the build files.</description>
      </parameter>
    </parameters>
    <configuration>
      <buildDirectory implementation="java.io.File">
         ${project.build.directory}</buildDirectory>
      <baseDirectory implementation="java.io.File">
         ${basedir}</baseDirectory>
    </configuration>
    <requirements>
      <requirement>
         <role>org.codehaus.plexus.archiver.Archiver</role>
         <role-hint>zip</role-hint>
         <field-name>zipArchiver</field-name>
      </requirement>
    </requirements>
  </mojo>
</mojos>
<dependencies>

                                                                            457
Writing Plugins

    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
  </dependencies>
</plugin>



There are three parts to a plugin descriptor: the top-level configuration of the
plugin which contains elements like groupId and artifactId, the declaration of
mojos, and the declaration of dependencies. Let's examine each of these sections in
more detail.


17.3.1. Top-level Plugin Descriptor Elements
The top-level configuration values in the plugin element are:

   description
   This element contains a short description of the plugin. In the case of the Zip
   plugin, this description is empty.

    groupId, artifactId, version
   Just like everything else in Maven, plugins need to have a unique coordinate.
   The groupId, artifactId, and version are used to locate the plugin artifact in a
   Maven repository.

   goalPrefix
   This element controls the prefix used to reference goals in a particular plugin. If
   you were to look at the Compiler plugin's descriptor you would see that
   goalPrefix has a value of compile, and if you look at the descriptor for the Jar
   plugin, it would have a goalPrefix of jar. It is important that you choose a
   distinct goal prefix for your custom plugin.

    isolatedRealm (deprecated)
   This is a legacy property which is no longer used by Maven. It is still present in
   the system to provide for backwards compatibility with older plugins. Earlier
   versions of Maven used to provide a mechanism to load a plugin's dependencies


                                                                                  458
Writing Plugins

   in an isolated ClassLoader. Maven makes extensive use of a project called
   ClassWorlds from the Codehaus community to create hierarchies of
   ClassLoader objects which are modeled by a ClassRealm object. Feel free to
   ignore this property and always set it to false.

    inheritedByDefault
   If inheritedByDefault is set to true, any mojo in this plugin which is configured
   in a parent project will be configured in a child project. If you configure a mojo
   to execute during a specific phase in a parent project and the Plugin has
   inheritedByDefault set to true, this execution will be inherited by the child
   project. If inheritedByDefault is not set to true, then an goal execution defined
   in a parent project will not be inherited by a child project.


17.3.2. Mojo Configuration
Next is the declaration of the each Mojo. The plugin element contains an element
named mojos which contains a mojo element for each mojo present in the Plugin.
Each mojo element contains the following configuration elements:

    goal
   This is the name of the goal. If you were running the compiler:compile goal,
   then compiler is the plugin's goalPrefix and compile would be the name of
   the goal.

    description
   This contains a short description of the goal to display to the use when they use
   the Help plugin to generate plugin documentation.

    requiresDirectInvocation
   If you set this to true, the goal can only be executed if it is explicitly executed
   from the command-line by the user. If someone tries to bind this goal to a
   lifecycle phase in a POM, Maven will print an error message. The default for
   this element is false.

   requiresProject

                                                                                   459
Writing Plugins

Specifies that a given goal cannot be executed outside of a project. The goal
requires a project with a POM. The default value for this requiresProject is
true.

 requiresReports
If you were creating a plugin that relies on the presence of reports, you would
need to set requiresReports to true. For example, if you were writing a
plugin to aggregate information from a number of reports, you would set
requiresReports to true. The default for this element is false.

 aggregator
A Mojo descriptor with aggregator set to true is supposed to only run once
during the execution of Maven, it was created to give plugin developers the
ability to summarize the output of a series of builds; for example, to create a
plugin that summarizes a report across all projects included in a build. A goal
with aggregator set to true should only be run against the top-level project in
a Maven build. The default value of aggregator is false. Aggregator is slated
for deprecation in a future release of Maven.

 requiresOnline
Specifies that a given goal cannot be executed if Maven is running in offline
mode (-o command-line option). If a goal depends on a network resource, you
would specify a value of true for this element and Maven would print an error
if the goal was executed in offline mode. The default for requiresOnline is
false.

 inheritedByDefault
If inheritedByDefault is set to true, a mojo which is configured in a parent
project will be configured in a child project. If you configure a mojo to execute
during a specific phase in a parent project and the Mojo descriptor has
inheritedByDefault set to true, this execution will be inherited by the child
project. If inheritedByDefault is not set to true, then a goal execution defined
in a parent project will not be inherited by a child project.

phase

                                                                             460
Writing Plugins

If you don't bind this goal to a specific phase, this element defines the default
phase for this mojo. If you do not specify a phase element, Maven will require
the user to explicitly specify a phase in a POM.

implementation
This element tells Maven which class to instantiate for this Mojo. This is a
Plexus component property (defined in Plexus ComponentDescriptor).

 language
The default language for a Maven Mojo is java. This controls the Plexus
ComponentFactory used to create instances of this Mojo component. This
chapter focuses on writing Maven plugins in Java, but you can also write
Maven in a number of alternative languages such as Groovy, Beanshell, and
Ruby. If you were writing a plugin in one of these languages you would use a
language element value other than java.

 instantiationStrategy
This property is a Plexus component configuration property, it tells Plexus how
to create and manage instances of the component. In Maven, all mojos are
going to be configured with an instantiationStrategy of per-lookup; a new
instance of the component (mojo) is created every time it is retrieved from
Plexus.

executionStrategy
The execution strategy tells Maven when and how to execute a Mojo. The valid
values are once-per-session and always. Honestly, the valid values are
anything, this particular property doesn't do a thing, it is a hold over from an
early design of Maven. This property is slated for deprecation in a future release
of Maven.

 parameters
This element describes all of the parameters for this Mojo. What's the name of
the parameter What is the type of parameter? Is it required? Each parameter has
the following elements:


                                                                               461
Writing Plugins


   name
   Is the name of the parameter (i.e. baseDirectory)

   type
   This is the type (Java class) of the parameters (i.e. java.io.File)

   required
   Is the parameter required? If true, the parameter must be non-null when the
   goal is executed.

   editable
   If a parameter is not editable (if editable is set to false), then the value of the
   parameter cannot be set in the POM. For example, if the plugin descriptor
   defines the value of buildDirectory to be ${basedir} in the descriptor, a
   POM cannot override this value to be another value in a POM.

   description
   A short description to use when generating plugin documentation (using the
   Help Plugin)

    configuration
   This element provides default values for all of the Mojo's parameters using
   Maven property notation. This example provides a default value for the
   baseDir Mojo parameter and the buildDirectory Mojo parameter. In the
   parameter element, the implementation specifies the type of the parameter (in
   this case java.io.File), the value in the parameter element contains either a
   hard-coded default or a Maven property reference.

  requirements
  This is where the descriptor gets interesting. A Mojo is a component that is
  managed by Plexus, and, because of this, it has the opportunity to reference
  other components managed by Plexus. This element allows you to define
  dependencies on other components in Plexus.
While you should know how to read a Plugin Descriptor, you will almost never


                                                                                   462
Writing Plugins

need to write one of these descriptor files by hand. Plugin Descriptor files are
generated automatically off of a set of annotations in the source for a Mojo.


17.3.3. Plugin Dependencies
Lastly, the plugin descriptor declares a set of dependencies just like a Maven
project. When Maven uses a plugin, it will download any required dependencies
before it attempts to execute a goal from this plugin. In this example, the plugin
depends on Jakarta Commons IO version 1.3.2.



17.4. Writing a Custom Plugin
When you write a custom plugin, you are going to be writing a series of Mojos
(goals). Every Mojo is a single Java class which contains a series of annotations
that tell Maven how to generate the Plugin descriptor described in the previous
section. Before you can start writing Mojo classes, you will need to create Maven
project with the appropriate packaging and POM.


17.4.1. Creating a Plugin Project
To create a plugin project, you should use the Maven Archetype plugin. The
following command-line will create a plugin with a groupId of
org.sonatype.mavenbook.plugins and the artifactId of first-maven-plugin:

$ mvn archetype:create 
  -DgroupId=org.sonatype.mavenbook.plugins 
  -DartifactId=first-maven-plugin 
  -DarchetypeGroupId=org.apache.maven.archetypes 
  -DarchetypeArtifactId=maven-archetype-mojo


The Archetype plugin is going to create a directory named my-first-plugin which
contains the following POM.

Example 17.2. A Plugin Project's POM
<?xml version="1.0" encoding="UTF-8"?><project>
  <modelVersion>4.0.0</modelVersion>

                                                                                   463
Writing Plugins

  <groupId>org.sonatype.mavenbook.plugins</groupId>
  <artifactId>first-maven-plugin</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>maven-plugin</packaging>
  <name>first-maven-plugin Maven Mojo</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>2.0</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>



The most import element in a plugin project's POM is the packaging element
which has a value of maven-plugin. This packaging element customizes the
Maven lifecycle to include the necessary goals to create a plugin descriptor. The
plugin lifecycle was introduce in Section 10.2.3, “ plugin”, it is similar to the Jar
lifecycle with three exceptions: plugin:descriptor is bound to the
generate-resources phase, plugin:addPluginArtifactMetadata is added to the
package phase, and plugin:updateRegistry is added to the install phase.

The other important piece of a plugin project's POM is the dependency on the
Maven Plugin API. This project depends on version 2.0 of the maven-plugin-api
and it also adds in JUnit as a test-scoped dependency.


17.4.2. A Simple Java Mojo
In this chapter, we're going to introduce a Maven Mojo written in Java. Each Mojo
in your project is going to implement the org.apache.maven.plugin.Mojo
interface, the Mojo implementation shown in the following example implements
the Mojo interface by extending the org.apache.maven.plugin.AbstractMojo
class. Before we dive into the code for this Mojo, let's take some time to explore
the methods on the Mojo interface. Mojo provides the following methods:

                                                                                  464
Writing Plugins


   void setLog( org.apache.maven.monitor.logging.Log log )
   Every Mojo implementation has to provide a way for the plugin to communicate
   the progress of a particular goal. Did the goal succeed? Or, was there a problem
   during goal execution? When Maven loads and executes a Mojo, it is going to
   call the setLog() method and supply the Mojo instance with a suitable logging
   destination to be used in your custom plugin.

   protected Log getLog()
   Maven is going to call setLog() before your Mojo is executed, and your Mojo
   can retrieve the logging object by calling getLog(). Instead of printing out
   status to Standard Output or the console, your Mojo is going to invoke methods
   on the Log object.

   void execute() throws
   org.apache.maven.plugin.MojoExecutionException
   This method is called by Maven when it is time to execute your goal.
The Mojo interface is concerned with two things: logging the results of goal
execution and executing a goal. When you are writing a custom plugin, you'll be
extending AbstractMojo. AbstractMojo takes care of handling the setLog() and
getLog() implementations and contains an abstract execute() method. When you
extend AbstractMojo, all you need to do is implement the execute() method.
Example 17.3, “A Simple EchoMojo” shows a trivial Mojo implement which
simply prints out a message to the console.

Example 17.3. A Simple EchoMojo
package org.sonatype.mavenbook.plugins;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;

/**
  * Echos an object string to the output screen.
  * @goal echo
  * @requiresProject false
  */
public class EchoMojo extends AbstractMojo
{

                                                                               465
Writing Plugins

    /**
     * Any Object to print out.
     * @parameter expression="${echo.message}" default-value="Hello World..."
     */
    private Object message;

    public void execute()
        throws MojoExecutionException, MojoFailureException
    {
        getLog().info( message.toString() );
    }
}



If you create this Mojo in ${basedir} under src/main/java in
org/sonatype/mavenbook/mojo/EchoMojo.java in the project created in the
previous section and run mvn install, you should be able to invoke this goal
directly from the command-line with:
$ mvn org.sonatype.mavenbook.plugins:first-maven-plugin:1.0-SNAPSHOT:echo


That large command-line is mvn followed by the
groupId:artifactId:version:goal. When you run this command-line you
should see output that contains the output of the echo goal with the default
message: "Hello Maven World...". If you want to customize the message, you can
pass the value of the message parameter with the following command-line:
$ mvn org.sonatype.mavenbook.plugins:first-maven-plugin:1.0-SNAPSHOT:echo 
             -Decho.message="The Eagle has Landed"


The previous command-line is going to execute the EchoMojo and print out the
message "The Eagle has Landed".


17.4.3. Configuring a Plugin Prefix
Specifying the groupId, artifactId, version, and goal on the command-line is
cumbersome. To address this, Maven assigns a plugin a prefix. Instead of typing:
$ mvn org.apache.maven.plugins:maven-jar-plugin:2.2:jar


You can use the plugin prefix jar and turn that command-line into mvn jar:jar.


                                                                               466
Writing Plugins

How does Maven resolve something like jar:jar to
org.apache.mven.plugins:maven-jar:2.3? Maven looks at a file in the Maven
repository to obtain a list of plugins for a specific groupId. By default, Maven is
configured to look for plugins in two groups: org.apache.maven.plugins and
org.codehaus.mojo. When you specify a new plugin prefix like mvn
hibernate3:hbm2ddl, Maven is going to scan the repository metadata for the
appropriate plugin prefix. First, Maven is going to scan the
org.apache.maven.plugins group for the plugin prefix hibernate3. If it doesn't
find the plugin prefix hibernate3 in the org.apache.maven.plugins group it will
scan the metadata for the org.codehaus.mojo group.
When Maven scans the metadata for a particular groupId, it is retrieving an XML
file from the Maven repository which captures metadata about the artifacts
contained in a group. This XML file is specific for each repository referenced, if
you are not using a custom Maven repository, you will be able to see the Maven
metadata for the org.apache.maven.plugins group in your local Maven
repository (~/.m2/repository) under
org/apache/maven/plugins/maven-metadata-central.xml. Example 17.4,
“Maven Metadata for the Maven Plugin Group” shows a snippet of the
maven-metadata-central.xml file from the org.apache.maven.plugin group.


Example 17.4. Maven Metadata for the Maven Plugin Group
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
  <plugins>
    <plugin>
      <name>Maven Clean Plugin</name>
      <prefix>clean</prefix>
      <artifactId>maven-clean-plugin</artifactId>
    </plugin>
    <plugin>
      <name>Maven Compiler Plugin</name>
      <prefix>compiler</prefix>
      <artifactId>maven-compiler-plugin</artifactId>
    </plugin>
    <plugin>
      <name>Maven Surefire Plugin</name>
      <prefix>surefire</prefix>
      <artifactId>maven-surefire-plugin</artifactId>
    </plugin>
    ...

                                                                                467
Writing Plugins

  </plugins>
</metadata>



As you can see in Example 17.4, “Maven Metadata for the Maven Plugin Group”,
this maven-metadata-central.xml file in your local repository is what makes it
possible for your to execute mvn surefire:test. Maven scans
org.apache.maven.plugins and org.codehaus.mojo: plugins from
org.apache.maven.plugins are considered core Maven plugins and plugins from
org.codehaus.mojo are considered extra plugins. The Apache Maven project
manages the org.apache.maven.plugins group, and a separate independent open
source community manages the Codehaus Mojo project. If you would like to start
publishing plugins to your own groupId, and you would like Maven to
automatically scan your own groupId for plugin prefixes, you can customize the
groups that Maven scans for plugins in your Maven Settings.
If you wanted to be able to run the first-maven-plugin's echo goal by running
first:echo, add the org.sonatype.mavenbook.plugins groupId to your
~/.m2/settings.xml as shown in Example 17.5, “Customizing the Plugin Groups
in Maven Settings”. This will prepend the org.sonatype.mavenbook.plugins to
the list of groups which Maven scans for Maven plugins.

Example 17.5. Customizing the Plugin Groups in Maven Settings
<settings>
  ...
  <pluginGroups>
    <pluginGroup>org.sonatype.mavenbook.plugins</pluginGroup>
  </pluginGroups>
</settings>



You can now run mvn first:echo from any directory and see that Maven will
properly resolve the goal prefix to the appropriate plugin identifiers. This worked
because our project adhered to a naming convention for Maven plugins. If your
plugin project has an artifactId which follows the pattern maven-first-plugin
or first-maven-plugin. Maven will automatically assign a plugin goal prefix of
first to your plugin. In other words, when the Maven Plugin Plugin is generating


                                                                                468
Writing Plugins

the Plugin descriptor for your plugin and you have not explicitly set the
goalPrefix in your project, the plugin:descriptor goal will extract the prefix
from your plugin's artifactId when it matches the following patterns:

   •   ${prefix}-maven-plugin,    OR

    • maven-${prefix}-plugin
If you would like to set an explicit plugin prefix, you'll need to configure the
Maven Plugin Plugin. The Maven Plugin Plugin is a plugin that is responsible for
building the Plugin descriptor and performing plugin specific tasks during the
package and load phases. The Maven Plugin Plugin can be configured just like any
other plugin in the build element. To set the plugin prefix for your plugin, add the
following build element to the first-maven-plugin project's pom.xml.

Example 17.6. Configuring a Plugin Prefix
<?xml version="1.0" encoding="UTF-8"?><project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook.plugins</groupId>
  <artifactId>first-maven-plugin</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>maven-plugin</packaging>
  <name>first-maven-plugin Maven Mojo</name>
  <url>http://maven.apache.org</url>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>2.3</version>
        <configuration>
           <goalPrefix>blah</goalPrefix>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>2.0</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>

                                                                                469
Writing Plugins

      <scope>test</scope>
    </dependency>
  </dependencies>
</project>



Example 17.6, “Configuring a Plugin Prefix” sets the plugin prefix to blah. If
you've added the org.sonatype.mavenbook.plugins to the pluginGroups in your
~/.m2/settings.xml, you should be able to execute the EchoMojo by running mvn
echo:blah from any directory.


17.4.4. Logging from a Plugin
Maven takes care of connecting your Mojo to a logging provider by calling
setLog() prior to the execution of your Mojo. It supplies an implementation of
org.apache.maven.monitor.logging.Log. This class exposes methods that you
can use to communicate information back to the user. This Log class provides
multiple levels of logging similar to that API provided by Log4J. Those levels are
captured by a series of methods available for each level: debug, info, error and
warn. To save trees, we've only listed the methods for a single logging level:
debug.

   void debug( CharSequence message )
   Prints a message to the debug logging level.

   void debug( CharSequence message, Throwable t )
   Prints a message to the debug logging level which includes the stack trace from
   the Throwable (either Exception or Error)

   void debug( Throwable t )
   Prints out the stack trace of the Throwable (either Exception or Error)
Each of the four levels exposes the same three methods. The four logging levels
serve different purposes. The debug level exists for debugging purposes and for
people who want to see a very detailed picture of the execution of a Mojo. You
should use the debug logging level to provide as much detail on the execution of a
Mojo, but you should never assume that a user is going to see the debug level. The

                                                                               470
Writing Plugins

info level is for general informational messages that should be printed as a normal
course of operation. If you were building a plugin that compiled code using a
compiler, you might want to print the output of the compiler to the screen.
The warn logging level is used for messages about unexpected events and errors
that your Mojo can cope with. If you were trying to run a plugin that compiled
Ruby source code, and there was no Ruby source code available, you might want
to just print a warning message and move on. Warnings are not fatal, but errors are
usually build-stopping conditions. For the completely unexpected error condition,
there is the error logging level. You would use error if you couldn't continue
executing a Mojo. If you were writing a Mojo to compile some Java code and the
compiler wasn't available, you'd print a message to the error level and possibly
pass along an Exception that Maven could print out for the user. You should
assume that a user is going to see most of the messages in info and all of the
messages in error.


17.4.5. Mojo Class Annotations
In first-maven-plugin, you didn't write the plugin descriptor yourself, you relied
on Maven to generate the plugin descriptor from your source code. The descriptor
was generated using your plugin project's POM information and a set of
annotations on your EchoMojo class. EchoMojo only specifies the @goal annotation,
here is a list of other annotations you can place on your Mojo implementation.

    @goal <goalName>
   This is the only required annotation which gives a name to this goal unique to
   this plugin.

    @requiresDependencyResolution <requireScope>
   Flags this mojo as requiring the dependencies in the specified scope (or an
   implied scope) to be resolved before it can execute. Supports compile, runtime,
   and test. If this annotation had a value of test, it would tell Maven that the
   Mojo cannot be executed until the dependencies in the test scope had been
   resolved.


                                                                                471
Writing Plugins


@requiresProject (true|false)
Marks that this goal must be run inside of a project, default is true. This is
opposed to plugins like archetypes, which do not.

 @requiresReports (true|false)
If you were creating a plugin that relies on the presence of reports, you would
need to set requiresReports to true. The default value of this annotation is
false.

 @aggregator (true|false)
A Mojo with aggregator set to true is supposed to only run once during the
execution of Maven, it was created to give plugin developers the ability to
summarize the output of a series of builds; for example, to create a plugin that
summarizes a report across all projects included in a build. A goal with
aggregator set to true should only be run against the top-level project in a
Maven build. The default value of aggregator is false.

@requiresOnline (true|false)
When set to true, Maven must not be running in offline mode when this goal is
executed. Maven will throw an error if one attempts to execute this goal offline.
Default: false.

 @requiresDirectInvocation
When set to true, the goal can only be executed if it is explicitly executed from
the command-line by the user. Maven will throw an error if someone tries to
bind this goal to a lifecycle phase. The default for this annotation is false.

 @phase <phaseName>
This annotation specifies the default phase for this goal. If you add an execution
for this goal to a pom.xml and do not specify the phase, Maven will bind the
goal to the phase specified in this annotation by default.

@execute [goal=goalName|phase=phaseName [lifecycle=lifecycleId]]
This annotation can be used in a number of ways. If a phase is supplied, Maven


                                                                                 472
Writing Plugins

   will execute a parallel lifecycle ending in the specified phase. The results of this
   separate execution will be made available in the Maven property
   ${executedProperty}.
   The second way of using this annotation is to specify an explicit goal using the
   prefix:goal notation. When you specify just a goal, Maven will execute this
   goal in a parallel environment that will not affect the current Maven build.
   The third way of using this annotation would be to specify a phase in an
   alternate lifecycle using the identifier of a lifecycle.
@execute phase="package" lifecycle="zip"
@execute phase="compile"
@execute goal="zip:zip"


If you look at the source for EchoMojo, you'll notice that Maven is not using the
standard annotations available in Java 5. Instead, it is using Commons Attributes.
Commons Attributes provided a way for Java programmers to use annotations
before annotations were a part of the Java language specification. Why doesn't
Maven use Java 5 annotations? Maven doesn't use Java 5 annotations because it is
designed to target pre-Java 5 JVMs. Because Maven has to support older versions
of Java, it cannot use any of the newer features available in Java 5.


17.4.6. When a Mojo Fails
The execute() method in Mojo throws two exceptions MojoExecutionException
and MojoFailureException. The difference between these two exception is both
subtle and important, and it relates to what happens when a goal execution "fails".
A MojoExecutionException is a fatal exception, something unrecoverable
happened. You would throw a MojoExecutionException if something happens
that warrants a complete stop in a build; you re trying to write to disk, but there is
no space left, or you were trying to publish to a remote repository, but you can't
connect to it. Throw a MojoExecutionException if there is no chance of a build
continuing; something terrible has happened and you want the build to stop and the
user to see a "BUILD ERROR" message.
A MojoFailureException is something less catastrophic, a goal can fail, but it


                                                                                   473
Writing Plugins

might not be the end of the world for your Maven build. A unit test can fail, or a
MD5 checksum can fail; both of these are potential problems, but you don't want to
return an exception that is going to kill the entire build. In this situation you would
throw a MojoFailureException. Maven provides for different "resiliency" settings
when it comes to project failure. Which are described below.
When you run a Maven build, it could involve a series of projects each of which
can succeed or fail. You have the option of running Maven in three failure modes:

   mvn -ff
   Fail-fast mode: Maven will fail (stop) at the first build failure.

    mvn -fae
   Fail-at-end: Maven will fail at the end of the build. If a project in the Maven
   reactor fails, Maven will continue to build the rest of the builds and report a
   failure at the end of the build.

    mvn -fn
   Fail never: Maven won't stop for a failure and it won't report a failure.
You might want to ignore failure if you are running a continuous integration build
and you want to attempt a build regardless of the success of failure of an individual
project build. As a plugin developer, you'll have to make a call as to whether a
particular failure condition is a MojoExecutionException or a
MojoFailureExeception.




17.5. Mojo Parameters
Just as important as the execute() method and the Mojo annotations, a Mojo is
configured via parameters. This section deals with some configuration and topics
surrounding Mojo parameters.


17.5.1. Supplying Values for Mojo Parameters
In EchoMojo we declared the message parameter with the following annotations:

                                                                                     474
Writing Plugins

/**
 * Any Object to print out.
 * @parameter
 *       expression="${echo.message}"
 *       default-value="Hello Maven World"
 */
private Object message;


The default expression for this parameter is ${echo.message}, this means that
Maven will try to use the value of the echo.message property to set the value for
message. If the value of the echo.message property is null, the default-value
attribute of the @parameter annotation will be used instead. Instead of using the
echo.message property, we can configure a value for the message parameter of the
EchoMojo directly in a project's POM.
There are a few ways to populate the message parameter in the EchoMojo. First we
can pass in a value from the command-line like this (assuming that you've added
org.sonatype.mavenbook.plugins to your pluginGroups):

$ mvn first:echo -Decho.message="Hello Everybody"


We could also specify the value of this message parameter, by setting a property in
our POM or in our settings.xml.
<project>
  ...
  <properties>
    <echo.message>Hello Everybody</echo.message>
  </properties>
</project>


This parameter could also be configured directly as a configuration value for the
plugin. If we wanted to customize the message parameter directly, we could use
the following build configuration. The following configuration bypasses the
echo.message property and populates the Mojo parameter in plugin configuration.
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.sonatype.mavenbook.plugins</groupId>
        <artifactId>first-maven-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>


                                                                               475
Writing Plugins

        <configuration>
           <message>Hello Everybody!</message>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>


If we wanted to run the EchoMojo twice at difference phases in a lifecycle, and we
wanted to customize the message parameter for each execution separately, we
could configure the parameter value at the execution level in a POM like this:
<build>
  <build>
    <plugins>
      <plugin>
         <groupId>org.sonatype.mavenbook.plugins</groupId>
         <artifactId>first-maven-plugin</artifactId>
         <version>1.0-SNAPSHOT</version>
         <executions>
           <execution>
             <id>first-execution</id>
             <phase>generate-resources</phase>
             <goals>
               <goal>echo</goal>
             </goals>
             <configuration>
               <message>The Eagle has Landed!</message>
             </configuration>
           </execution>
           <execution>
             <id>second-execution</id>
             <phase>validate</phase>
             <goals>
               <goal>echo</goal>
             </goals>
             <configuration>
               <message>${project.version}</message>
             </configuration>
           </execution>
         </executions>
      </plugin>
    </plugins>
  </build>
</build>


While this last configuration example seems very verbose, it illustrates the
flexibility of Maven. In the previous configuration example, you've bound the
EchoMojo to both the validate and generate-resources phases in the default

                                                                                476
Writing Plugins

Maven lifecycle. The first execution is bound to generate-resources, it supplies a
string value to the message parameter of "The Eagle has Landed!". The second
execution is bound to the validate phase, it supplies a property reference to
${project.version}. When you run mvn install for his project, you'll see that the
first:echo goal executes twice and prints out two different messages.



17.5.2. Multi-valued Mojo Parameters
Plugins can have parameters which accept more than one value. Take a look at the
ZipMojo shown in Example 17.7, “A Plugin with Multi-valued Parameters”. Both
the includes and excludes parameters are multivalued String arrays which
specify the inclusion and exclusion patterns for a component that creates a ZIP file.

Example 17.7. A Plugin with Multi-valued Parameters
package org.sonatype.mavenbook.plugins

/**
  * Zips up the output directory.
  * @goal zip
  * @phase package
  */
public class ZipMojo extends AbstractMojo
{
     /**
      * The Zip archiver.
      * @parameter 
          expression="${component.org.codehaus.plexus.archiver.Archiver#zip}"
      */
     private ZipArchiver zipArchiver;

    /**
     * Directory containing the build files.
     * @parameter expression="${project.build.directory}"
     */
    private File buildDirectory;

    /**
     * Base directory of the project.
     * @parameter expression="${basedir}"
     */
    private File baseDirectory;

    /**
     * A set of file patterns to include in the zip.


                                                                                 477
Writing Plugins

     * @parameter alias="includes"
     */
    private String[] mIncludes;

    /**
     * A set of file patterns to exclude from the zip.
     * @parameter alias="excludes"
     */
    private String[] mExcludes;

    public void setExcludes( String[] excludes ) { mExcludes = excludes; }

    public void setIncludes( String[] includes ) { mIncludes = includes; }

    public void execute()
        throws MojoExecutionException
    {
        try {
            zipArchiver.addDirectory( buildDirectory, includes, excludes );
            zipArchiver.setDestFile( new File( baseDirectory, "output.zip" ) );
            zipArchiver.createArchive();
        } catch( Exception e ) {
            throw new MojoExecutionException( "Could not zip", e );
        }
    }
}


To configure a multi-valued Mojo parameter, you use a series of elements for each
value. If the name of the multi-valued parameter is includes, you would use an
element includes with child elements include. If the multi-valued parameter is
excludes, you would use an element excludes with child elements exclude. To
configure the ZipMojo to ignore all files ending in .txt and all files ending in a
tilde, you would use the following plugin configuration.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.sonatype.mavenbook.plugins</groupId>
        <artifactId>zip-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>**/*.txt</exclude>
            <exclude>**/*~</exclude>
          </excludes>
        </configuration>
      </plugin>


                                                                               478
Writing Plugins

    </plugins>
  </build>
</project>




17.5.3. Depending on Plexus Components
A Mojo is a component managed by an IoC container called Plexus. A Mojo can
depend on other components managed by Plexus by declaring a Mojo parameter
and using the @parameter or the @component annotation. Example 17.7, “A Plugin
with Multi-valued Parameters” shows a ZipMojo which depends on a Plexus
component using the @parameter annotation, this dependency could be declared
using the @component annotation.

Example 17.8. Depending on a Plexus Component
/**
 * The Zip archiver.
 * @component role="org.codehaus.plexus.archiver.Archiver" roleHint="zip"
 */
private ZipArchiver zipArchiver;



When Maven instantiates this Mojo, it will then attempt to retrieve the Plexus
component with the specified role and role hint. In this example, the Mojo will be
related to a ZipArchiver component which will allow the ZipMojo to create a ZIP
file.


17.5.4. Mojo Parameter Annotations
Unless you insist on writing your Plugin descriptors by hand, you'll never have to
write that XML. Instead, the Maven Plugin Plugin has a plugin:descriptor goal
bound to the generate-resources phase. This goal generates the plugin descriptor
from annotations on your Mojo. To configure a Mojo parameter, you should use
the following annotations on either the private member variables for each of your
Mojo's parameters. You can also use these annotations on public setter methods,
but the most common convention for Maven plugins is to annotate private member


                                                                                479
Writing Plugins

variables directly.

    @parameter [alias="someAlias"] [expression="${someExpression}"]
    [default-value="value"]
   Marks a private field (or a setter method) as a parameter. The alias provides
   the name of the parameter. If alias is omitted, Maven will use the name of the
   variable as the parameter name. The expression is an expression that Maven
   will evaluate to obtain a value. Usually the expression is a property reference
   like ${echo.message}. default-value is the value that this Mojo will use if no
   value can be derived from the expression or if a value was not explicitly
   supplied via plugin configuration in a POM.

    @required
   If this annotation is present, a valid value for this parameter is required prior to
   Mojo execution. If Maven tries to execute this Mojo and the parameter has a
   null value, Maven will throw and error when it tries to execute this goal.

    @readonly
   If this annotation is present, the user cannot directly configuration this
   parameter in the POM. You would use this annotation with the expression
   attribute of the parameter annotation. For example, if you wanted to make sure
   that a particular parameter always had the value of the finalName POM
   property, you would list an expression of ${build.finalName} and then add the
   @readOnly annotation. If this were the case, the user could only change the
   value of this parameter by changing the value of finalName in the POM.

   @component
   Tells Maven to populate a field with a Plexus Component. A valid value for the
   @component annotation would be:
@component role="org.codehaus.plexus.archiver.Archiver" roleHint="zip"


   This would have the effect of retrieving the ZipArchiver from Plexus. The
   ZipArchiver is the Archiver which corresponds to the role hint zip. Instead of
   component, you could also use the @parameter annotation with an expression
   attribute of:

                                                                                    480
Writing Plugins

@parameter expression="${component.org.codehaus.plexus.archiver.Archiver#zip}"


   While the two annotations are effectively the same, the @component annotation
   is the preferred way to configure dependencies on Plexus components.

   @deprecated
   The parameter will be deprecated. Users can continue configuring this
   parameter, but a warning message will be displayed.


17.6. Plugins and the Maven Lifecycle
In the Chapter 10, Der Build Lebenszyklus chapter, you learned that lifecycles can
be customized by packaging types. A plugin can both introduce a new packaging
type and customize the lifecycle. In this section, you are going to learn how you
can customize the lifecycle from a custom Maven plugin. You are also some to see
how you can tell a Mojo to execute a parallel lifecycle.


17.6.1. Executing a Parallel Lifecycle
Let's assume you write some goal that depends on the output from a previous build.
Maybe the ZipMojo goal can only run if there is output to include in an archive.
You can specify something like a prerequisite goal by using the @execute
annotation on a Mojo class. This annotation will cause Maven to spawn a parallel
build and execute a goal or a lifecycle in a parallel instance of Maven that isn't
going to affect the current build. Maybe you wrote some Mojo that you can to run
once a day that runs mvn install and then packages up all of the output in some
sort of customized distribution format. Your Mojo descriptor could tell Maven that
before you execute your CustomMojo, you'd like it to execute the default lifecycle
up to the install phase and then expose the results of that project to your Mojo as
the property ${executedProject}. You could then reference properties in that
project to before some sort of post processing.
Another possibility is that you have a goal that does something completely
unrelated to the default lifecycle. Let's consider something completely unexpected,

                                                                               481
Writing Plugins

maybe you have a goal that turns a WAV file into an MP3 using something like
LAME, but before you do that, you want to step through a lifecycle that turns a
MIDI file to a WAV. (You can use Maven for anything, this isn't that "far out".)
You've created a "midi-sound" lifecycle, and you want to include the output of the
midi-sound lifecycle's install phase in your web application project which has a
war packaging type. Since your project is running in the war packaging lifecycle,
you'll need to have goal that effectively forks off an isolated build and runs through
the midi-source lifecycle. You would do this by annotating your mojo with
@execute lifecycle="midi-source" phase="install".


    @execute goal="<goal>"
   This will execute the given goal before execution of this one. The goal name is
   specified using the prefix:goal notation.

   @execute phase="<phase>"
   This will fork an alternate build lifecycle up to the specified phase before
   continuing to execute the current one. If no lifecycle is specified, Maven will
   use the lifecycle of the current build.

   @execute lifecycle="<lifecycle>" phase="<phase>"
   This will execute the given alternate lifecycle. A custom lifecycle can be
   defined in META-INF/maven/lifecycle.xml.


17.6.2. Creating a Custom Lifecycle
A custom lifecycle must be packaged in the plugin under the
META-INF/maven/lifecycle.xml file. You can include a lifecycle under
src/main/resources in META-INF/maven/lifecycle.xml. The following
lifecycle.xml declares a lifecycle named zipcycle that contains only the zip
goal in a package phase.

Example 17.9. Define a Custom Lifecycle in lifecycle.xml
<lifecycles>
  <lifecycle>
    <id>zipcycle</id>


                                                                                  482
Writing Plugins

    <phases>
      <phase>
        <id>package</id>
        <executions>
          <execution>
            <goals>
               <goal>zip</goal>
            </goals>
          </execution>
        </executions>
      </phase>
    </phases>
  </lifecycle>
</lifecycles>




If you wanted to execute the zipcycle phase within another build, you could then
create a ZipForkMojo which uses the @execute annotation to tell Maven to step
through the zipcycle phase to package when the ZipForkMojo is executed.

Example 17.10. Forking a Customer Lifecycle from a Mojo
/**
  * Forks a zip lifecycle.
  * @goal zip-fork
  * @execute lifecycle="zipcycle" phase="package"
  */
public class ZipForkMojo extends AbstractMojo
{
   public void execute()
     throws MojoExecutionException
   {
     getLog().info( "doing nothing here" );
   }
}




Running the ZipForkMojo will fork the lifecycle. If you've configured your plugin
to execute with the goal prefix zip, running zip-fork should produce something
similar to the following output.
$ mvn zip:zip-fork
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'zip'.
[INFO] ----------------------------------------------------------------------
[INFO] Building Maven Zip Forked Lifecycle Test


                                                                              483
Writing Plugins

[INFO]    task-segment: [zip:zip-fork]
[INFO] ----------------------------------------------------------------------
[INFO] Preparing zip:zip-fork
[INFO] [site:attach-descriptor]
[INFO] [zip:zip]
[INFO] Building zip: 
   ~/maven-zip-plugin/src/projects/zip-lifecycle-test/target/output.zip
[INFO] [zip:zip-fork]
[INFO] doing nothing here
[INFO] ---------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ---------------------------------------------------------------------
[INFO] Total time: 1 second
[INFO] Finished at: Sun Apr 29 16:10:06 CDT 2007
[INFO] Final Memory: 3M/7M
[INFO] ---------------------------------------------------------------------


Calling zip-fork spawned another lifecycle, Maven executed the zipcycle
lifecycle then it printed out the message from ZipFormMojo's execute method.


17.6.3. Overriding the Default Lifecycle
Once you've created your own lifecycle and spawned it from a Mojo. The next
question you might have is how do you override the default lifecycle. How do you
create custom lifecycles and attach them to projects? In Chapter 10, Der Build
Lebenszyklus, we saw that the packaging of a project defines the lifecycle of a
project. There's something different about almost every packaging type; war
attached different goals to package, custom lifecycles like swf from the Israfil Flex
3 plugin attach different goals to the compile phase. When you create a custom
lifecycle, you can attach that lifecycle to a packaging type by supplying some
Plexus configuration in your plugin's archive.
To define a new lifecycle for a new packaging type, you'll need to configure a
LifecycleMapping component in Plexus. In your plugin project, create a
META-INF/plexus/components.xml under src/main/resources. In components.xml
add the content from Example 17.11, “Overriding the Default Lifecycle”. Set the
name of the packaging type under role-hint, and the set of phases containing the
coordinates of the goals to bind (omit the version). Multiple goals can be
associated with a phase using a comma delimited list.



                                                                                 484
Writing Plugins


Example 17.11. Overriding the Default Lifecycle
<component-set>
  <components>
    <component>
      <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
      <role-hint>zip</role-hint>
      <implementation>
        org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping
      </implementation>
      <configuration>
        <phases>
          <process-resources>
            org.apache.maven.plugins:maven-resources-plugin:resources
          </process-resources>
          <compile>
            org.apache.maven.plugins:maven-compiler-plugin:compile
          </compile>
          <package>org.sonatype.mavenbook.plugins:maven-zip-plugin:zip</package>
        </phases>
      </configuration>
    </component>
  </components>
</component-set>




If you create a plugin which defines a new packaging type and a customized
lifecycle, Maven won't know anything it until you add the plugin to your project's
POM and set the extensions element to true. Once you do this, Maven will scan
your plugin for more than just Mojos to execute, it will look for the
components.xml under META-INF/plexus, and it will make the packaging type
available to your project.

Example 17.12. Configuring a Plugin as an Extension
<project>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <groupId>com.training.plugins</groupId>
        <artifactId>maven-zip-plugin</artifactId>
        <extensions>true</extensions>
      </plugin>
    </plugins>

                                                                                485
Writing Plugins

  </build>
</project>




Once you add the plugin with the extensions element set to true, you can use the
custom packaging type and your project will be able to execute the custom
lifecycle associated with that packaging type.




                                                                               486
Chapter 18. Writing Plugins in Alternative
Languages
You can write a Mojo in Java, or you can write a Mojo in an alternative language.
Maven has support for a number of implementation languages, and this chapter is
going to show you how to create plugins in three languages: Groovy, Ant, and
Ruby plugins.



18.1. Writing Plugins in Ant
Ant isn't a language as much as it is a build tool which allows you to describe a
build as a set of tasks grouped into build targets. Ant then allows you to declare
dependencies between build targets; for example, in Ant you are essentially
creating your own lifecycle. An Ant build.xml might have an install target which
depends on a test target which depends on a compile target. Ant is something of a
ancestor to Maven, it was the ubiquitous procedural build tool that almost every
project used before Maven introduced the concept of wide-scale reusability of
common build plugins and the concept of a universal lifecycle.
While Maven is an improvement on Ant, Ant can still be useful when describing
parts of the build process. Ant provides a set of tasks which can come in handy
when you need to perform file operations or XSLT transformations or any other
operation you could think of. There is a large library of available Ant tasks for
everything from running JUnit tests to transforming XML to copying files to a
remote server using SCP. An overview of available Ant tasks can be found online
in the Apache Ant Manual. You can use these tasks as a low-level build
customization language, and you can also write a Maven plugin where, instead of a
Mojo written in Java, you can pass parameters to a Mojo which is an Ant build
target.



18.2. Creating an Ant Plugin
                                                                               487
Writing Plugins in Alternative Languages

To create a Maven plugin using Ant, you will need to have a pom.xml and a single
Mojo implemented in Ant. To get started, create a project directory named
firstant-maven-plugin. Place the following pom.xml in this directory.

Example 18.1. POM for an Ant Maven Plugin
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook.plugins</groupId>
  <artifactId>firstant-maven-plugin</artifactId>
  <name>Example Ant Mojo - firstant-maven-plugin</name>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-script-ant</artifactId>
      <version>2.0.9</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>2.4</version>
        <dependencies>
           <dependency>
             <groupId>org.apache.maven.plugin-tools</groupId>
             <artifactId>maven-plugin-tools-ant</artifactId>
             <version>2.4</version>
           </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>



Next, you will need to create your Ant Mojo. An Ant mojo consists of two parts:
the Ant tasks in an XML file, and a file which supplies Mojo descriptor
information. The Ant plugin tools are going to look for both of these files in
${basedir}/src/main/scripts. One file will be named echo.build.xml and it
will contain the Ant XML.

Example 18.2. Echo Ant Mojo

                                                                              488
Writing Plugins in Alternative Languages

<project>
  <target name="echotarget">
    <echo>${message}</echo>
  </target>
</project>



The other file will describe the Echo Ant Mojo and will be in the echo.mojos.xml
file also in ${basedir}/src/main/scripts.

Example 18.3. Echo Ant Mojo Descriptor
<pluginMetadata>
  <mojos>
    <mojo>
      <goal>echo</goal>
      <call>echotarget</call>
      <description>Echos a Message</description>
      <parameters>
        <parameter>
           <name>message</name>
           <property>message</property>
           <required>false</required>
           <expression>${message}</expression>
           <type>java.lang.Object</type>
           <defaultValue>Hello Maven World</defaultValue>
           <description>Prints a message</description>
        </parameter>
      </parameters>
    </mojo>
  </mojos>
</pluginMetadata>



This echo.mojos.xml file configures the Mojo descriptor for this plugin. It
supplies the goal name "echo", and it tells Maven what Ant task to call in the call
element. In addition to configuring the description, this XML file configures the
message parameter to use the expression ${message} and to have a default value of
"Hello Maven World."
If you've configured your plugin groups in ~/.m2/settings.xml to include
org.sonatype.mavenbook.plugins, you can install this Ant plugin by executing
the following command at the command-line:
$ mvn install
[INFO] ------------------------------------------------------------------------

                                                                               489
Writing Plugins in Alternative Languages

[INFO]   Building Example Ant Mojo - firstant-maven-plugin
[INFO]      task-segment: [install]
[INFO]   ------------------------------------------------------------------------
[INFO]   [plugin:descriptor]
[INFO]   Using 3 extractors.
[INFO]   Applying extractor for language: java
[INFO]   Extractor for language: java found 0 mojo descriptors.
[INFO]   Applying extractor for language: bsh
[INFO]   Extractor for language: bsh found 0 mojo descriptors.
[INFO]   Applying extractor for language: ant
[INFO]   Extractor for language: ant found 1 mojo descriptors.
...
[INFO]   ------------------------------------------------------------------------
[INFO]   BUILD SUCCESSFUL
[INFO]   ------------------------------------------------------------------------


Note that the plugin:descriptor goal found a single Ant mojo descriptor. To run
this goal, you would execute the following command-line:
$ mvn firstant:echo
...
[INFO] [firstant:echo]

echotarget:
     [echo] Hello Maven World
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------


The echo goal executed and printed out the default value of the message parameter.
If you are used to Apache Ant build scripts, you will notice that Ant prints out the
name of the target executed and then adds a logging prefix to the output of the
echo Ant task.



18.3. Writing Plugins in JRuby
Ruby is an object-oriented scripting language which provides a rich set of facilities
for meta-programming and reflection. Ruby's reliance on closures and blocks make
for a programming style that is both compact and powerful. Although Ruby has
been around since 1993, most people came to know Ruby after it was made
popular by a Ruby-based web framework known as Ruby on Rails. JRuby is a
Ruby interpreter written in Java. For more information about the Ruby language,


                                                                                 490
Writing Plugins in Alternative Languages

see: http://www.ruby-lang.org/, and for more information about JRuby, see:
http://jruby.codehaus.org/.


18.3.1. Creating a JRuby Plugin
To create a Maven plugin using JRuby, you will need to have a pom.xml and a
single Mojo implemented in Ruby. To get started, create a project directory named
firstruby-maven-plugin. Place the following pom.xml in this directory.


Example 18.4. POM for a JRuby Maven Plugin
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook.plugins</groupId>
  <artifactId>firstruby-maven-plugin</artifactId>
  <name>Example Ruby Mojo - firstruby-maven-plugin</name>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>jruby-maven-plugin</artifactId>
      <version>1.0-beta-4</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>2.4</version>
        <dependencies>
           <dependency>
             <groupId>org.codehaus.mojo</groupId>
             <artifactId>jruby-maven-plugin</artifactId>
             <version>1.0-beta-4</version>
           </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>



Next, you will need to create a Mojo implemented in Ruby. Maven is going to look
for a Ruby Mojo in ${basedir}/src/main/scripts. Put the following Ruby class

                                                                              491
Writing Plugins in Alternative Languages

in ${basedir}/src/main/scripts/echo.rb.

Example 18.5. The Echo Ruby Mojo
# Prints a message
# @goal "echo"
# @phase "validate"
class Echo < Mojo

  # @parameter type="java.lang.String" default-value="Hello Maven World" 
    expression="${message}"
  def message
  end

  def execute
    info $message
  end

end

run_mojo Echo



The Echo class must extend Mojo, and it must override the execute() method. At
the end of the echo.rb file, you will need to run the mojo with "run_mojo Echo".
To install this plugin, run mvn install:
$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Example Ruby Mojo - firstruby-maven-plugin
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
...
[INFO] [plugin:descriptor]
...
[INFO] Applying extractor for language: jruby
[INFO] Ruby Mojo File: /echo.rb
[INFO] Extractor for language: jruby found 1 mojo descriptors.
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------


During the build, you should see that the Maven Plugin Plugin's descriptor goal
applies the JRuby extractor to create a plugin.xml which captures the annotations
in the Echo class. If you've configured your default plugin groups to include

                                                                              492
Writing Plugins in Alternative Languages

org.sonatype.mavenbook.plugins,        you should be able to run this echo goal with
the following command-line:
$ mvn firstruby:echo
...
[INFO] [firstruby:echo]
[INFO] Hello Maven World
...




18.3.2. Ruby Mojo Implementations
Ruby Mojos are annotated using comments in Ruby source files. A single
annotation like @parameter takes a number of attributes, and each of these
attributes must be specified on the same line. There can be no line-breaks between
an annotations attribute in the Ruby source. Both classes and parameters are
annotated. Parameters are annotated with four annotations: @parameter,
@required, @readonly, and @deprecated. The @parameter attribute takes the
following attributes:

    alias
   An alias for the parameter. An alternate name which can be used to populate the
   same parameter.

   default-value
   Provides a default value to the parameter if the supplied value or the parameter
   expression produces a null result. In echo.rb, we specify the default as "Hello
   Maven World".

   expression
   Contains an expression which can resolve to a Maven property or a System
   property.

    type
   The fully qualified Java type of the parameter. If the type is not specified it will
   default to java.lang.String.
In addition to the @parameter annotation, a parameter can take the following

                                                                                   493
Writing Plugins in Alternative Languages

annotations:

   @required "<true|false>"
   Marks the parameter as being required. The default value is false.

   @readonly "<true|false>"
   Marks the parameter as read-only. If this is true, you may not override the
   default value or the value from the expression from the command line. The
   default value is false.

    @deprecated "<true|false>"
   Marks the parameter as deprecated. The default value is false.
Putting this altogether, a fully annotated message parameter from echo.rb would
look like the following code:
# @parameter type="java.lang.String" default-value="Hello Maven World" 
  expression="${message}"
# @readonly true
# @required false
# @deprecated false
def message
end


Ruby Mojo classes are annotated with the following attributes:

   @goal
   Specifies the name of the goal.

   @phase
   The default phase to bind this goal to.

   @requiresDependencyResolution
   True if the Mojo requires that dependencies be resolved before execution.

   @aggregator
   Marks this mojo as an aggregator.

   @execute

                                                                                 494
Writing Plugins in Alternative Languages

   Provides the opportunity to execute a goal or lifecycle phase before executing
   this Mojo. The @execute annotation takes the following attributes:

   goal
   Name of the goal to execute

   phase
   Name of the lifecycle phase to execute

   lifecycle
   Name of the lifecycle (if other than default)
For an example of an annotated Mojo class, consider the following code example:
# Completes some build task
# @goal custom-goal
# @phase install
# @requiresDependencyResolution false
# @execute phase=compile
class CustomMojo < Mojo
    ...
end


Mojo parameters can reference Java classes and Maven properties. The following
example shows you how to get access to the Maven Project object from a Ruby
Mojo.

Example 18.6. Referencing a Maven Project from a Ruby Mojo
# This is a mojo description
# @goal test
# @phase validate
class Test < Mojo
  # @parameter type="java.lang.String" default-value="nothing" alias="a_string"
  def prop
  end

  # @parameter type="org.apache.maven.project.MavenProject" 
    expression="${project}"
  # @required true
  def project
  end

  def execute
    info "The following String was passed to prop: '#{$prop}'"
    info "My project artifact is: #{$project.artifactId}"

                                                                               495
Writing Plugins in Alternative Languages

  end
end

run_mojo Test


In the previous example, we can access properties on the Project class using
standard Ruby syntax. If you put test.rb in firstruby-maven-plugin's
src/main/scripts directory, install the plugin, and then run it, you will see the
following output:
$ mvn install
...
[INFO] [plugin:descriptor]
[INFO] Using 3 extractors.
[INFO] Applying extractor for language: java
...
[INFO] Applying extractor for language: jruby
[INFO] Ruby Mojo File: /echo.rb
[INFO] Ruby Mojo File: /test.rb
[INFO] Extractor for language: jruby found 2 mojo descriptors.
...
$ mvn firstruby:test
...
[INFO] [firstruby:test]
[INFO] The following String was passed to prop: 'nothing'
[INFO] My project artifact is: firstruby-maven-plugin




18.3.3. Logging from a Ruby Mojo
To log from a Ruby Mojo, call the info(), debug(), and error() methods with a
message.
# Tests Logging
# @goal logtest
# @phase validate
class LogTest < Mojo

  def execute
    info "Prints an INFO message"
    error "Prints an ERROR message"
    debug "Prints to the Console"
  end

end

run_mojo LogTest

                                                                                    496
Writing Plugins in Alternative Languages




18.3.4. Raising a MojoError
If there is an unrecoverable error in a Ruby Mojo, you will need to raise a
MojoError. Example 18.7, “Raising a MojoError from a Ruby Mojo” shows you
how to raise a MojoError. This example mojo prints out a message and then raises
a MojoError.

Example 18.7. Raising a MojoError from a Ruby Mojo
# Prints a Message
# @goal error
# @phase validate
class Error < Mojo

  # @parameter type="java.lang.String" default-value="Hello Maven World" 
    expression="${message}"
  # @required true
  # @readonly false
  # @deprecated false
  def message
  end

  def execute
    info $message
    raise MojoError.new( "This Mojo Raised a MojoError" )
  end

end

run_mojo Error



Running this Mojo, produces the following output:
$ mvn firstruby:error
...
INFO] [firstruby:error]
[INFO] Hello Maven World
[ERROR] This Mojo Raised a MojoError




18.3.5. Referencing Plexus Components from JRuby

                                                                             497
Writing Plugins in Alternative Languages

A Ruby Mojo can depend on a Plexus component. To do this, you would use the
expression attribute of the @parameter annotation to specify a role and a hint for
Plexus. The following example Ruby Mojo, depends upon an Archiver component
which Maven will retrieve from Plexus.

Example 18.8. Depending on a Plexus Component from a Ruby Mojo
# This mojo tests plexus integration
# @goal testplexus
# @phase validate
class TestPlexus < Mojo

  # @parameter type="org.codehaus.plexus.archiver.Archiver" 
expression="${component.org.codehaus.plexus.archiver.Archiver#zip}"
  def archiver
  end

  def execute
    info $archiver
  end
end

run_mojo TestPlexus



Please note that the attributes for an annotation in a Ruby Mojo cannot span
multiple lines. If you were to run this goal, you would see Maven attempt to
retrieve a component from Plexus with a role of
org.codehaus.plexus.arhiver.Archiver and a hint of zip.




18.4. Writing Plugins in Groovy
Groovy is a dynamic language based on the Java Virtual Machine which compiles
to Java bytecode. Groovy is a project in the Codehaus community. If you are fluent
in Java, Groovy will seem like a natural choice for a scripting language. Groovy
takes the features of Java, pares down the syntax a bit, and adds features like
closures, duck-typing, and regular expressions. For more information about
Groovy, please see the Groovy web site at http://groovy.codehaus.org.



                                                                               498
Writing Plugins in Alternative Languages


18.4.1. Creating a Groovy Plugin
To create a Maven Plugin using Groovy, you only need two files: a pom.xml and a
single Mojo implemented in Groovy. To get started, create a project directory
named firstgroovy-maven-plugin. Place the following pom.xml in this directory.

Example 18.9. POM for a Groovy Maven Plugin
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook.plugins</groupId>
  <artifactId>firstgroovy-maven-plugin</artifactId>
  <name>Example Groovy Mojo - firstgroovy-maven-plugin</name>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.mojo.groovy</groupId>
      <artifactId>groovy-mojo-support</artifactId>
      <version>1.0-beta-3</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>2.4</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo.groovy</groupId>
        <artifactId>groovy-maven-plugin</artifactId>
        <version>1.0-beta-3</version>
        <extensions>true</extensions>
        <executions>
           <execution>
             <goals>
               <goal>generateStubs</goal>
               <goal>compile</goal>
               <goal>generateTestStubs</goal>
               <goal>testCompile</goal>
             </goals>
           </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>



                                                                            499
Writing Plugins in Alternative Languages

What's going on in this POM? First, notice that the packaging of the POM is
maven-plugin because we are creating a project that will package a Maven plugin.
Next, note that the project depends on the groovy-mojo-support artifact in the
org.codehaus.mojo.groovy group.

Then under src/main/groovy in a directory org/sonatype/mavenbook/plugins,
create a file named EchoMojo.groovy which contains the EchoMojo class.

Example 18.10.
package org.sonatype.mavenbook.plugins

import org.codehaus.mojo.groovy.GroovyMojo

/**
 * Example goal which echos a message
 *
 * @goal echo
 */
class EchoMojo extends GroovyMojo {

    /**
     * Message to print
     *
     * @parameter expression="${echo.message}"
     *             default-value="Hello Maven World"
     */
    String message

    void execute() {
      log.info( message )
    }
}




                                                                             500
Chapter 19. Using Maven Archetypes
         Warning
         This chapter hasn't reached Draft status yet, it is in a pre-alpha stage. I'm
         publishing works in progress because I believe that transparency in
         writing benefits both the author and the community. A book is much
         more than the pages (or web pages) it is printed on, and the true meaning
         of a book is captured in both the content and conversation it provokes.
         As this is a pre-alpha release of a chapter, don't worry about reporting
         typos. Expect them until a quality beta version of this chapter is released.
         If you do care to provide any feedback, tell me what you want to read. If,
         after reading this pre-alpha chapter you are longing to know how to X, Y,
         or Z. Go over to our Get Satisfaction page and file a suggestion or an
         idea. We're very interested in the feedback.
         Don't expect this chapter to be in pre-alpha for weeks and weeks, one
         thing I'm particularly disinterested in is leaving readers with cliffhanger
         endings - sections that provide 95% of the essential information only to
         leave them with a table that hasn't been completed or a section that was
         written in a hurry. This is a new practice of "Agile Writing", and I've
         taken care to publish complete sections. While the enumeration of
         third-party plugins isn't complete and this chapter lacks a section on
         generating artifacts, the paragraphs and third-level sections that have
         been published are in this version because I didn't want to sit on the
         content for weeks and weeks.
         Xpect ah lott of tipos inh this chapther(, but don't report 'em yet).
         Monday, October 13, 2008 - Tim O'Brien




19.1. Introduction to Maven Archetypes
An archetype is a template for a Maven project which is used by the Maven

                                                                                   501
Using Maven Archetypes

Archetype plugin to create new projects. Archetypes are useful for open source
projects such as Apache Wicket or Apache Cocoon which want to present
end-users with a set of baseline projects that can be used as a foundation for new
applications. Archetypes can also be useful within an organization that wants to
encourage standards across a series of similar and related projects. If you work in
an organization with a large team of developers who all need to create projects
which follow a similar structure, you can publish an archetype that can be used by
all other members of the development team. You can create a new product from an
archetype using the Maven Archetype plugin from the command line or by using
the project creation wizard in the m2eclipse plugin introduced in Chapter 14,
Maven in Eclipse: m2eclipse.



19.2. Using Archetypes
You can use an archetype by invoking the generate goal of the Archetype plugin
via the command-line or with m2eclipse.


19.2.1. Using an Archetype from the Command Line
The following command line can be used to generate a project from the quickstart
archetype.
mvn archetype:generate 
  -DgroupId=org.sonatype.mavenbook 
  -DartifactId=quickstart 
  -Dversion=1.0-SNAPSHOT 
  -DpackageName=org.sonatype.mavenbook 
  -DarchetypeGroupId=org.apache.maven.archetypes 
  -DarchetypeArtifactId=maven-archetype-quickstart 
  -DarchetypeVersion=1.0 
  -DinteractiveMode=false


The generate goal accepts the following parameters:

   groupId
   The groupId for the project you are creating.


                                                                               502
Using Maven Archetypes


   artifactId
   The artifactId for the project you are creating.

   version
   The version for the project you are creating (defaults to 1.0-SNAPSHOT).

   packageName
   The default package for the project you are creating (defaults to groupId).

   archetypeGroupId
   The groupId of the archetype you wish to use for project generation.

   archetypeArtifactId
   The artifactId of the archetype you wish to use for project generation.

   archetypeVersion
   The version of the archetype you wish to use for project generation.

   interactiveMode
    When the generate goal is executed in interactive mode, it will prompt the user
    for all the previously listed parameters. When interactiveMode is false, the
    generate goal will use the values passed in from the command line.
Once you run the generate goal using the previously listed command line, you
will have a directory named quickstart which contains a new Maven project. The
command line you had to suffer through in this section is difficult to manage. In
the next section we generate the same project running the generate goal in an
interactive mode.


19.2.2. Using the Interactive generate Goal
The simplest way to use the Maven Archetype plugin to generate a new Maven
project from an archetype is to run the archetype:generate goal in interactive
mode. When interactiveMode is set to true, the generate goal will present you
with a list of archetypes and prompt you to select an archetype and supply the


                                                                                 503
Using Maven Archetypes

necessary identifiers. Since the default value of the parameter interactiveMode is
true, all you have to do to generate a new Maven project is run mvn
archetype:generate.
$ mvn archetype:generate
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:generate] (aggregator-style)
[INFO] [archetype:generate]
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart
Choose archetype:
1: internal -> appfuse-basic-jsf
2: internal -> appfuse-basic-spring
3: internal -> appfuse-basic-struts
4: internal -> appfuse-basic-tapestry
5: internal -> appfuse-core
6: internal -> appfuse-modular-jsf
7: internal -> appfuse-modular-spring
8: internal -> appfuse-modular-struts
9: internal -> appfuse-modular-tapestry
10: internal -> maven-archetype-j2ee-simple
11: internal -> maven-archetype-marmalade-mojo
12: internal -> maven-archetype-mojo
13: internal -> maven-archetype-portlet
14: internal -> maven-archetype-profiles
15: internal -> maven-archetype-quickstart
16: internal -> maven-archetype-site-simple
17: internal -> maven-archetype-site
18: internal -> maven-archetype-webapp
19: internal -> jini-service-archetype
20: internal -> softeu-archetype-seam
21: internal -> softeu-archetype-seam-simple
22: internal -> softeu-archetype-jsf
23: internal -> jpa-maven-archetype
24: internal -> spring-osgi-bundle-archetype
25: internal -> confluence-plugin-archetype
26: internal -> jira-plugin-archetype
27: internal -> maven-archetype-har
28: internal -> maven-archetype-sar
29: internal -> wicket-archetype-quickstart
30: internal -> scala-archetype-simple
31: internal -> lift-archetype-blank
32: internal -> lift-archetype-basic
33: internal -> cocoon-22-archetype-block-plain
34: internal -> cocoon-22-archetype-block
35: internal -> cocoon-22-archetype-webapp
36: internal -> myfaces-archetype-helloworld
37: internal -> myfaces-archetype-helloworld-facelets
38: internal -> myfaces-archetype-trinidad
39: internal -> myfaces-archetype-jsfcomponents
40: internal -> gmaven-archetype-basic

                                                                               504
Using Maven Archetypes

41: internal -> gmaven-archetype-mojo
Choose a number: 15


The first thing that the archetype:generate goal does in interactive mode is to
print out a list of archetypes that it is aware of. The Maven Archetype plugin ships
with an archetype catalog which includes a reference to all of the standard, simple
Maven archetypes (10-18). The plugin's archetype catalog also contains a number
of references to compelling third party archetypes such as archetypes which can be
used to create AppFuse projects, Confluence and JIRA plugins, Wicket
applications, Scala applications, and Groovy projects. For a brief overview of these
third-party archetypes, see Section 19.3.2, “Notable Third-Party Archetypes”.
Once you select an archetype, the Maven Archetype plugin downloads the
archetype, and then asks you to supply the following values for your new project:

   •   groupId

   •   artifactId

   •   version

   •   package
[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates
Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetypes/maven-archetype-quick
4K downloaded
Define value for groupId: : org.sonatype.mavenbook
Define value for artifactId: : quickstart
Define value for version: 1.0-SNAPSHOT: : 1.0-SNAPSHOT
Define value for package: org.sonatype.mavenbook: : org.sonatype.mavenbook
Confirm properties configuration:
groupId: org.sonatype.mavenbook
artifactId: quickstart
version: 1.0-SNAPSHOT
package: org.sonatype.mavenbook
 Y: : Y


Once this interactive portion of the archetype:generate goal execution is
finished, the Maven Archetype plugin will generate the project in a directory
named after the artifactId you supplied.
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating OldArchetype: maven-archetype-quickstart:RELE

                                                                                505
Using Maven Archetypes

[INFO]   ----------------------------------------------------------------------------
[INFO]   Parameter: groupId, Value: org.sonatype.mavenbook
[INFO]   Parameter: packageName, Value: org.sonatype.mavenbook
[INFO]   Parameter: basedir, Value: /Users/tobrien/tmp
[INFO]   Parameter: package, Value: org.sonatype.mavenbook
[INFO]   Parameter: version, Value: 1.0-SNAPSHOT
[INFO]   Parameter: artifactId, Value: quickstart
[INFO]   ********************* End of debug info from resources from generated POM ***********
[INFO]   OldArchetype created in dir: /Users/tobrien/tmp/quickstart
[INFO]   ------------------------------------------------------------------------
[INFO]   BUILD SUCCESSFUL
[INFO]   ------------------------------------------------------------------------
[INFO]   Total time: 1 minute 57 seconds
[INFO]   Finished at: Sun Oct 12 15:39:14 CDT 2008
[INFO]   Final Memory: 8M/15M
[INFO]   ------------------------------------------------------------------------




19.2.3. Using an Archetype from m2eclipse
m2eclipse makes creating a new Maven project from a Maven Archetype very easy
by providing an intuitive wizard for searching for, selecting, and configuring a
Maven Archetype. For more information about generating a Maven project form a
Maven Archetype using m2eclipse, see Section 14.5.2, “Erstellen eines Maven
Projekts auf der Basis eines Maven Archetyps”.



19.3. Available Archetypes
As more and more projects adopt Maven, more and more artifacts are being
published by projects as a way to provide users with a quick way of creating
projects from existing templates. This section discusses some of the simple core
archetypes from the Apache Maven as well as providing a survey of some
interesting third-party archetypes.


19.3.1. Common Maven Archetypes
Some of the most straightforward Maven archetypes are contained in the
org.apache.maven.archetypes groupId. Most of the basic archetypes under
org.apache.maven.archetypes are very basic templates that include few options.

                                                                                 506
Using Maven Archetypes

You'll use them only to provide the most basic features that distinguish a Maven
project from a non-Maven project. For example, the webapp archetype plugin
described in this section just includes a stub of a web.xml file in
${basedir}/src/main/webapp/WEB-INF, and it doesn't even go as far as providing
a Servlet for you to customize. In Section 19.3.2, “Notable Third-Party
Archetypes” you'll see a quick survey of some of the more notable third-party
archetype such as the AppFuse and Cocoon artifacts.
The following archetypes can be found in the groupId
org.apache.maven.archetypes:


19.3.1.1. maven-archetype-quickstart
The quickstart archetype is a simple project with JAR packaging and a single
dependency on JUnit. After generating a project with the quickstart archetype, you
will have a single class named App in the default package with a main() method
that prints "Hello World!" to standard output. You will also have a single JUnit test
class named AppTest with a testApp() method with a trivial unit test.


19.3.1.2. maven-archetype-webapp
This archetype creates a simple project with WAR packaging and a single
dependency on JUnit. ${basedir}/src/main/webapp contains a simple shell of a
web application: an index.jsp page and the simplest possible web.xml file. Even
though the archetype includes a dependency on JUnit, this archetype does not
create any unit tests. If you were looking for a functional web application, this
archetype is going to disappoint you. For more relevant web archetypes, see
Section 19.3.2, “Notable Third-Party Archetypes”.


19.3.1.3. maven-archetype-mojo
This archetype creates a simple project with maven-plugin packaging and a single
mojo class named MyMojo in the project's default package. The MyMojo class
contains a touch goal which is bound to the process-resources phase, it creates a
file named touch.txt in the target/ directory of the new project when it is
executed. The new project will have a dependency on maven-plugin-api and JUnit.

                                                                                 507
Using Maven Archetypes


19.3.2. Notable Third-Party Archetypes
This section is going to give you a brief overview of some of the archetypes
available from third-parties not associated with the Apache Maven project. If you
are looking for a more comprehensive list of available archetypes, take a look at
the list of archetypes in m2eclipse. m2eclipse allows you to create a new Maven
project from an ever growing list of approximately 80 archetypes which span an
amazing number of projects and technologies. Section 14.5.2, “Erstellen eines
Maven Projekts auf der Basis eines Maven Archetyps” contains a list of archetypes
which are immediately available to you when you use m2eclipse. The archetypes
listed in this section are available on the default list of archetypes generated by the
interactive execution of the generate goal.


19.3.2.1. AppFuse
AppFuse is an application framework developed by Matt Raible. You can think of
AppFuse as something of a Rosetta Stone for a few very popular Java technologies
like the Spring Framework, Hibernate, and iBatis. Using AppFuse you can very
quickly create an end-to-end multi-tiered application that can plugin into several
front-end web frameworks like Java Server Faces, Struts, and Tapestry. Starting
with AppFuse 2.0, Matt Raible has been transitioning the framework to Maven 2 to
take advantage of the dependency management and archetype capabilities.
AppFuse 2 provides the following archetypes all in the groupId
org.appfuse.archetypes:


   appfuse-basic-jsf    and appfuse-modular-jsf
   End-to-end application using Java Server Faces in the presentation layer

   appfuse-basic-spring    and appfuse-modular-spring
   End-to-end application using Spring MVC in the presentation layer

   appfuse-basic-struts    and appfuse-modular-struts
   End-to-end application using Struts 2 in the presentation layer

   appfuse-basic-tapestry       and appfuse-modular-tapestry

                                                                                   508
Using Maven Archetypes

   End-to-end application using Tapestry in the presentation layer

   appfuse-core
   Persistence and object model without the presentation layer
Archetypes following the appfuse-basic-* pattern are entire end-to-end
applications in a single Maven project, and archetypes following the
appfuse-modular-* pattern are end-to-end applications in a multimodule Maven
project which separates the core model objects and persistence logic from the web
front-end. Here's an example from generating a project to running a web
application for the modular Spring MVC application:
$ mvn archetype:generate 
      -DarchetypeArtifactId=appfuse-modular-spring 
      -DarchetypeGroupId=org.appfuse.archetypes 
      -DgroupId=org.sonatype.mavenbook 
      -DartifactId=mod-spring 
      -Dversion=1.0-SNAPSHOT 
      -DinteractiveMode=false[INFO] Scanning for projects...
...
[INFO] [archetype:generate]
[INFO] Generating project in Batch mode
[INFO] Archetype [org.appfuse.archetypes:appfuse-modular-spring:RELEASE] found in catalog in
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating OldArchetype: appfuse-modular-spring:RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.sonatype.mavenbook
[INFO] Parameter: packageName, Value: org.sonatype.mavenbook
[INFO] Parameter: basedir, Value: /Users/tobrien/tmp
[INFO] Parameter: package, Value: org.sonatype.mavenbook
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: mod-spring
...
[INFO] OldArchetype created in dir: /Users/tobrien/tmp/mod-spring
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
$ cd mod-spring
$ mvn
... (an overwhelming amount of activity ~5 minutes)
$ cd web
$ mvn jetty:run-war
... (Maven Jetty plugin starts a Servlet Container on port 8080)


From generating a project with the AppFuse archetype to running a web
application with a authentication and user-management system takes all of 5
minutes. This is the real power of using a Maven Archetype as a foundation for a


                                                                              509
Using Maven Archetypes

new application. We oversimplified the AppFuse installation process a bit and left
out the important part where you download and install a MySQL database, but
that's easy enough to figure out by reading the AppFuse Quickstart Documentation.


19.3.2.2. Confluence and JIRA plugins
Atlassian has created some archetypes for people interested in developing plugins
for both Confluence and JIRA. Confluence and JIRA are, respectively, a Wiki and
an issue tracker both of which have gained a large open source user base through
granting free licenses for open source projects. Both the jira-plugin-archetype
and the confluence-maven-archetype artifacts are under the
com.atlassian.maven.archetypes groupId. When you generate a Confluence plugin,
the archetype will generate a pom.xml which contains the necessary references to
the Atlassian repositories and a dependency on the confluence artifact. The
resulting Confluence plugin project will have a single example macro class and an
atlassian-plugin.xml descriptor. Generating a project from the Jira archetype
creates a project with a single, blank MyPlugin class and an atlassian-plugin.xml
descriptor in ${basedir}/src/main/resources.
fr more information about developing Confluence plugins with Maven 2, see
Developing Confluence Plugins with Maven 2 on the Confluence project's Wiki.
For more information about developing Jira plugins with Maven 2, see How to
Build and Atlassian Plugin on the Atlassian Developer Network.


19.3.2.3. Wicket
Apache Wicket is a component-oriented web framework which focused on
managing the server-side state of a number of components written in Java and
simple HTML. Where a framework like Spring MVC or Ruby on Rails focuses on
merging objects within a request with a series of page templates, Wicket is very
strongly focused on capturing interactions and page structure in a series of POJO
Java classes. In an age where hype-driven tech media outlets are proclaiming the
"Death of Java", Wicket is a contrarian approach to the design and assembly of
web applications. To generate a Wicket project with the Maven Archetype plugin:
$ mvn archetype:generate
... (select the "wicket-archetype-quickstart" artifact from the interactive menu) ...

                                                                              510
Using Maven Archetypes

... (supply a groupId, artifactId, version, package) ...
... (assuming the artifactId is "ex-wicket") ...
$ cd ex-wicket
$ mvn install
... (a lot of Maven activity) ...
$ mvn jetty:run
... (Jetty will start listening on port 8080) ...


Just like the AppFuse archetype, this archetype creates a shell web application
which can be immediately executed with the Maven Jetty plugin. If you hit
http://localhost:8080/ex-wicket, you be able to see the newly created web
application in a servlet container.

         Note
         Think about the power of Maven Archetypes versus the copy and paste
         approach that has characterized the last few years of web development.
         Six years ago, without the benefit of something like the Maven
         Archetype plugin, you would have had to slog through a book about
         AppFuse or a book about Wicket and followed circuitous pedagogy about
         the framework before you could actually fire it up in servlet container. It
         was either that or just copying an existing project and customizing it for
         your needs. With the Maven Archetype plugin, framework developers
         can now give you a working, customized shell for an application in a
         matter of minutes. This is a sea change that has yet to hit the enterprise
         development space, and you can expect that this handful of available
         third-party artifacts will balloon to hundreds within the next few years.




19.4. Publishing Archetypes
Once you've generated a good set of artifacts, you will probably want to share them
with the world. To do this, you'll need to create something called an Archetype
catalog. An Archetype catalog is an XML file which the Maven Archetype plugin
can consult to locate archetypes in a repository. Example 19.1, “Archetype Catalog
for the Apache Cocoon Project” shows the contents of the Archetype catalog for
the Apache Cocoon project which can be found at

                                                                                  511
Using Maven Archetypes

http://cocoon.apache.org/archetype-catalog.xml.

Example 19.1. Archetype Catalog for the Apache Cocoon Project
<archetype-catalog>
  <archetypes>
    <archetype>
      <groupId>org.apache.cocoon</groupId>
      <artifactId>cocoon-22-archetype-block-plain</artifactId>
      <version>1.0.0</version>
      <description>Creates an empty Cocoon block; useful if you want to add another block to

    </archetype>
    <archetype>
      <groupId>org.apache.cocoon</groupId>
      <artifactId>cocoon-22-archetype-block</artifactId>
      <version>1.0.0</version>
      <description>Creates a Cocoon block containing some small samples</description>
    </archetype>

    <archetype>
      <groupId>org.apache.cocoon</groupId>
      <artifactId>cocoon-22-archetype-webapp</artifactId>
      <version>1.0.0</version>
      <description>Creates a web application configured to host Cocoon blocks. Just add the
    </archetype>
  </archetypes>

</archetype-catalog>


To generate such a catalog, you'll need crawl a Maven repository and generate this
catalog XML file. The Archetype plugin has a goal named crawl which does just
this, and it assumes that it has access to the file system that hosts a repository. If
you run archetype:crawl from the command line with no arguments, the Archetype
plugin will crawl your local repository searching for Archetypes and it will create
an archetype-catalog.xml in ~/.m2/repository.
[tobrien@MACBOOK repository]$ mvn archetype:crawl
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:crawl] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] [archetype:crawl]
repository /Users/tobrien/.m2/repository
catalogFile null
[INFO] Scanning /Users/tobrien/.m2/repository/ant/ant/1.5/ant-1.5.jar

                                                                                  512
Using Maven Archetypes

[INFO]   Scanning /Users/tobrien/.m2/repository/ant/ant/1.5.1/ant-1.5.1.jar
[INFO]   Scanning /Users/tobrien/.m2/repository/ant/ant/1.6/ant-1.6.jar
[INFO]   Scanning /Users/tobrien/.m2/repository/ant/ant/1.6.5/ant-1.6.5.jar
...
[INFO]   Scanning /Users/tobrien/.m2/repository/xmlrpc/xmlrpc/1.2-b1/xmlrpc-1.2-b1.jar
[INFO]   Scanning /Users/tobrien/.m2/repository/xom/xom/1.0/xom-1.0.jar
[INFO]   Scanning /Users/tobrien/.m2/repository/xom/xom/1.0b3/xom-1.0b3.jar
[INFO]   Scanning /Users/tobrien/.m2/repository/xpp3/xpp3_min/1.1.3.4.O/xpp3_min-1.1.3.4.O.jar
[INFO]   ------------------------------------------------------------------------
[INFO]   BUILD SUCCESSFUL
[INFO]   ------------------------------------------------------------------------
[INFO]   Total time: 31 seconds
[INFO]   Finished at: Sun Oct 12 16:06:07 CDT 2008
[INFO]   Final Memory: 6M/12M
[INFO]   ------------------------------------------------------------------------


If you are interested in creating an Archetype catalog it is usually because you are
an open source project or organization which has a set of archetypes to share.
These archetypes are likely already available in a repository, and you need to crawl
this repository and generate a catalog in a file system. In other words, you'll
probably want to scan a directory on an existing Maven repository and generate an
Archetype plugin at the root of the repository. To do this, you'll need to pass in the
catalog and repository parameters to the archetype:crawl goal.
The following command line assumes that you are trying to generate a catalog file
in /var/www/html/archetype-catalog.xml for a repository hosted in
/var/www/html/maven2.
$ mvn archetype:crawl -Dcatalog=/var/www/html/archetype-catalog.xml 
                      [INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:crawl] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] [archetype:crawl]
repository /Users/tobrien/tmp/maven2
catalogFile /Users/tobrien/tmp/blah.xml
[INFO] Scanning /Users/tobrien/tmp/maven2/com/discursive/cas/extend/cas-extend-client-java/2
[INFO] Scanning /Users/tobrien/tmp/maven2/com/discursive/cas/extend/cas-extend-client-java/2
-Drepository=/var/www/html/maven2
...




                                                                                  513
Appendix A. Appendix: Detailinformationen
zur settings.xml-Datei

A.1. Übersicht
Das Element settings der setting.xml Datei beinhaltet die Elemente welche
benutzt werden, die Laufzeitumgebung von Maven zu konfigurieren. Einstellungen
dieser Datei sind von solcher Natur, dass diese über Projektgrenzen hinweg zum
Einsatz kommen und daher nicht an ein spezifisches Projekt gebunden sein sollten
oder an eine bestimmte Kundschaft verbreitet werden. Werte, welche darin
enthalten sind, sind z.B. die Einstellung des lokalen Repository, alternative Remote
Repositorien und Authentifizierungsdaten. Die settings.xml kann bei einer
Standardinstallation an zweierlei Orten aufgefunden werden:

   • Im Maven Installationsverzeichnis: $M2_HOME/conf/settings.xml

   • Als benutzerspezifische Datei des Profils: ~/.m2/settings.xml
Hier eine Übersicht der Elemente der settings.xml-Datei:

Example A.1. Übersicht der Elemente einer settings.xml-Datei
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                       http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository/>
  <interactiveMode/>
  <usePluginRegistry/>
  <offline/>
  <pluginGroups/>
  <servers/>
  <mirrors/>
  <proxies/>
  <profiles/>
  <activeProfiles/>
</settings>




                                                                                514
Appendix: Detailinformationen zur


A.2. Die Details der settings.xml Datei

A.2.1. Einfache Wertangaben
Die Hälfte der Elemente auf oberster Ebene innerhalb des Elementes settings sind
einfache Wertangaben welche benutzt werden um das Verhalten von Maven zu
steuern:

Example A.2. Einfache Wertangaben im top level Element der settings.xml
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository>${user.dir}/.m2/repository</localRepository>
  <interactiveMode>true</interactiveMode>
  <usePluginRegistry>false</usePluginRegistry>
  <offline>false</offline>
  <pluginGroups>
    <pluginGroup>org.codehaus.mojo</pluginGroup>
  </pluginGroups>
  ...
</settings>




The simple top-level elements are:
Diese top-level Wertangaben sind:

    localRepository
   This value is the path of this build system's local repository. The default value
   is
   Dieser Wert bestimmt den Pfad des lokalen Repository des Build Systems. Der
   Standardwert ist ${user.dir}/.m2/repository

   InteractiveMode
   true sollte Maven auf Benutzereingaben warten, ansonsten false.
   Standardwert true.

                                                                                 515
settings.xml-Datei


    usePluginRegistry
   true wenn Maven auf ${user.dir}/.m2/plugin-registry.xml zurückgreifen
   soll um die Plugin Versionen zu verwalten, Standardwert ist false.

   offline
   true sollte das Build-System im offline Modus arbeiten, Standardwert ist
   false. Dieser Wert ist insbesondere wichtig für serverseitige Build-Syteme,
   welche nicht auf ein externes Repository zugreifen dürfen, sei dies aufgrund des
   Netzwerkes oder auch wegen Sicherheitsbestimmungen.

    pluginGroups
   Dieses Element enthält eine Liste von Elementen pluginGroup, jedes enthält
   eine groupId. Die Liste wird durchsucht, sollte ein Plugin von der
   Kommandozeile angezogen werden und der Parameter der groupId nicht gesetzt
   sein. Standardmässig enthält diese Liste die org.apache.maven.plugins.


A.2.2. Servers
Das Element distributionManagement eines POM definiert die Repositorien für
die Verteilung. Gewisse Angaben, wie z.B. die Security Credentials sollten nicht
mit der pom.xml Datei verteilt werden. Diese Art der Angaben sollte auf einem
Build Server in der settings.xml Datei im Element servers abgelegt werden:

Example A.3. Server Konfiguration der settings.xml
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  ...
  <servers>
    <server>
      <id>server001</id>
      <username>my_login</username>
      <password>my_password</password>
      <privateKey>${user.home}/.ssh/id_dsa</privateKey>
      <passphrase>some_passphrase</passphrase>
      <filePermissions>664</filePermissions>
      <directoryPermissions>775</directoryPermissions>
      <configuration></configuration>

                                                                               516
Appendix: Detailinformationen zur

    </server>
  </servers>
  ...
</settings>




Die Elemente unter server sind:

   id
   Die eindeutige Kennung - id - des Servers (nicht des Benutzers welcher beim
   Einloggen zum Einsatz kommt) welche mit der id des Elementes
   distributionManagement des Repositories übereinstimmt.

   username, password
   Diese Elemente bilden ein Paar, sie bezeichnen das Login und Passwort
   welches notwendig ist, um sich gegenüber dem Server auszuweisen.

   privateKey, passphrase
   Wie vorangehend ebenfalls zwei Elemente, dieses Paar beschreibt den Pfad zu
   einem Privaten Schlüssel (Standardwert ist ${user.home}/.ssh/id_dsa) und
   einer Passphrase, sollte diese notwendig sein. Hinweis: Zukünftig sollte es
   möglich sein die Elemente passphrase und password zu externalisieren, zur
   Zeit jedoch müssen diese in Klartext in der settings.xml enthalten sein.

   filePermissions, directoryPermissions
   Sollte während des Deployments eine Repository Datei oder ein Verzeichnis
   angelegt werden, so sind dies die Rechte welche dort gesetzt werden. Die
   möglichen Werte beider Parameter sind eine dreistellige *nix artige Angabe
   entsprechend der Dateirechte, z.B. 664, oder 775.


A.2.3. Spiegelrepositorien

Example A.4. Konfiguration eines Spiegelrepositories in der settings.xml
<settings 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

                                                                            517
settings.xml-Datei

                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  ...
  <mirrors>
    <mirror>
      <id>planetmirror.com</id>
      <name>PlanetMirror Australia</name>
      <url>http://downloads.planetmirror.com/pub/maven2</url>
      <mirrorOf>central</mirrorOf>
    </mirror>
  </mirrors>
  ...
</settings>




  id, name
  Die eindeutige Kennung, id, des Spiegelrepositories. Diese id wird benutzt, um
  zwischen verschiedenen Elementen mirror zu unterscheiden.

  url
  Die Basis URL des Spiegelrepositories. Das Build System wird diese URL
  einem Repository Aufruf voransetzen, satt diese an die URL des Standard
  Server Repostory zu senden.

   mirrorOf
  Die id des Servers, welcher von diesem Server gespiegelt wird. Ein Beispiel,
  um auf eine Spiegelrepository des Maven central Server zu verweisen
  (http://repo1.maven.org/maven2), setzen Sie diesen Wert auf central. Der
  Wert muss nicht mit der id des Spiegelrepositories übereinstimmen.


A.2.4. Proxies

Example A.5. Konfiguration eines Proxy in der settings.xml
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  ...
  <proxies>
    <proxy>

                                                                             518
Appendix: Detailinformationen zur

      <id>myproxy</id>
      <active>true</active>
      <protocol>http</protocol>
      <host>proxy.somewhere.com</host>
      <port>8080</port>
      <username>proxyuser</username>
      <password>somepassword</password>
      <nonProxyHosts>*.google.com|ibiblio.org</nonProxyHosts>
    </proxy>
  </proxies>
  ...
</settings>




  id
  Die eindeutige Kennung, id, des Proxy. Diese id wird benutzt, um zwischen
  verschiedenen Elementen proxy zu unterscheiden

  active
  true, wenn dieser Proxy aktiviert ist. Insbesondere nützlich wenn mehrere
  Proxy konfiguriert sind, aber nur einer tatsächlich zum Einsatz kommen darf.

  protocol, host, port
  The protocol://host:port of the proxy, separated into discrete elements.
  Die Bestandteile der URL des Proxy: protocol://host:port;die Elemente
  werden getrennt geführt.

  username, password
  Diese Elemente bilden ein Paar, sie bezeichnen das Login und Passwort
  welches notwendig ist, um sich gegenüber dem Proxy auszuweisen.

  nonProxyHosts
  Unter diesem Element befindet sich eine Liste der Ressourcen welche nicht
  durch den Proxy abgebildet werden sollten. Das Trennsymbol der Liste ist das
  vom Proxy Server erwartete Zeichen; in unserem Beispiel das ‚Pipe’-Symbol.
  Ebenfalls häufig angewandt ist die Komma getrennte Liste.



                                                                             519
settings.xml-Datei


A.2.5. Profiles
Das Element profile der settings.xml-Datei ist ein verkürzter Auszug des
Elements profile der pom.xml-Datei. Es besteht aus den Elementen activation,
repositories, pluginRepositories und properties. Das Element beinhaltet nur
diese vier Elemente, da diese das Build System als ganzes betreffen (welches der
Rolle der settings.xml entspricht) und nicht einzelne Aspekte der Projekt Modell
Einstellungen.
Wird ein Profil innerhalb einer settings.xml aktiviert, so übersteuert dies jedes
entsprechende Profil einer pom.xml oder profiles.xml Datei.


A.2.6. Activation
Aktivierungen sind Schlüsselemente eines Profiles. Wie Profile eines POM rührt
die Mächtigkeit der Profile der settings.xml von der Möglichkeit her, Werte nur
unter bestimmten Umständen/Bedingungen zu beeinflussen. Diese Bedingungen
werden im Element activation bestimmt.

Example A.6. Definition eines Activation Parameters in der settings.xml
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                       http://maven.apache.org/xsd/settings-1.0.0.xsd">
  ...
  <profiles>
    <profile>
      <id>test</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.3</value>
        </property>
        <file>

                                                                                520
Appendix: Detailinformationen zur

          <exists>${basedir}/file2.properties</exists>
          <missing>${basedir}/file1.properties</missing>
        </file>
      </activation>
      ...
    </profile>
  </profiles>
  ...
</settings>




Die Aktivierung tritt ein, wenn alle definierten Kriterien erfüllt sind, jedoch nicht
notwendigerweise alle zur gleichen Zeit.

   jdk
   activation  beinhaltet eine eingebaute Java spezifische Prüfung des Elements
   jdk. Dieses Element wird aktiviert, sobald ein Test unter einer bestimmten
   Version eines JDK (angegeben durch ein entsprechendes Kürzel) läuft. Im oben
   angegebenen Beispiel wäre dies für 1.5.0_06 der Fall.

    os
   Mittels des Elements os können, wie oben ausgeführt, Betriebssystem
   spezifische Eigenschaften geprüft werden.

    property
   Das Profil wird aktiviert, sollte Maven ein Property (ein Wert, welcher
   innerhalb des POM mittels ${name} referenziert werden kann) mit dem
   entsprechenden Wertepaar finden.

   file
   Schliesslich kann ein Profil durch die Existenz oder das Fehlen einer
   bestimmten Datei aktiviert werden.
Das Element activation ist nicht der einzige Weg, wie ein Profil aktiviert werden
kann. Die Profil id kann ebenfalls innerhalb der Datei settings.xml im Element
activeProfile bestimmt werden. Profile können auch explizit von der
Befehlszeile mittels einer Komma getrennten Liste einem P-flag folgend,
aufgerufen werden (z.B. –p test).


                                                                                   521
settings.xml-Datei

Um festzustellen, welche Profile von einem bestimmten Build aktiviert werden,
benutzen Sie das Maven Help Plugin.
mvn help:active-profiles




A.2.7. Properties
Maven Properties sind ein Platzhalter für Werte - wie auch Properties unter Ant.
Auf deren Werte ist mittels der Notation ${x}, x ist der Name des Properties, von
überall innerhalb eines POM zuzugreifen. Properties kommen in fünf
verschiedenen Varianten, alle zugreifbar von innerhalb der settings.xml:

   env.X
   Kennzeichnet man eine Variable mit vorangestelltem ‘env.’ so wird der Wert
   der entsprechende Shell Variablen zurückgegeben. Ein Beispiel: ${env.PATH}
   enthält den Wert der Umgebungsvariablen $PATH (%PATH% unter Windows).

   project.x
   Ein punktnotierter (.) Pfad innerhalb der pom.xml Datei wird den
   entsprechenden Wert enthalten.

   settings.x
   Ein punktnotierter (.) Pfad innerhalb der settings.xml Datei wird den
   entsprechenden Wert enthalten.

   Java system properties
   Alle Properties welche durch java.lang.System.getProperties() offengelegt
   werden sind ebenfalls als POM Properties zugänglich, so zum Beispiel das
   Property ${java.home}.

   x
   Innerhalb eines Elements properties oder innerhalb einer externen Datei
   gesetzt, ist der Wert durch ${einWert} zugänglich.

Example A.7. Setzen des Properties ${user.install} property in der

                                                                                522
Appendix: Detailinformationen zur


settings.xml
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  ...
  <profiles>
    <profile>
      ...
      <properties>
        <user.install>${user.dir}/our-project</user.install>
      </properties>
      ...
    </profile>
  </profiles>
  ...
</settings>




Das Property ${user.install} ist für dieses POM zugänglich, wenn das Profil
aktiv ist.


A.2.8. Repositories
Repositorien sind entfernte Objektsammlungen, von welchen Maven das lokale
Repository bestückt. Von diesem lokalen Repository ruft Maven die
entsprechenden Abhängigkeiten oder Plugins ab. Verschiedene ferne Repositorien
können verschiedene Projekte enthalten, und mittels aktiver Profile können diese
nach entsprechenden Releases und Snapshot Artefakten durchsucht werden.

Example A.8. Repository Konfiguration in der settings.xml
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  ...
  <profiles>
    <profile>
      ...
      <repositories>
        <repository>
          <id>codehausSnapshots</id>

                                                                              523
settings.xml-Datei

          <name>Codehaus Snapshots</name>
          <releases>
            <enabled>false</enabled>
            <updatePolicy>always</updatePolicy>
            <checksumPolicy>warn</checksumPolicy>
          </releases>
          <snapshots>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
            <checksumPolicy>fail</checksumPolicy>
          </snapshots>
          <url>http://snapshots.maven.codehaus.org/maven2</url>
          <layout>default</layout>
        </repository>
      </repositories>
      <pluginRepositories>
        ...
      </pluginRepositories>
      ...
    </profile>
  </profiles>
  ...
</settings>




   releases, snapshots
  Zu jedem Artefakttyp release und snapshot bestehen Regeln. Mit diesen zwei
  Gruppen hat ein POM die Möglichkeit die Regeln innerhalb eines Repositories
  typspezifisch und unabhängig von den anderen Typen anzupassen. Zum
  Beispiel kann man eine Einschränkung erstellen welche es nur erlaubt in der
  Entwicklung snapshot Artefakte herunterzuladen.

  enabled
  true oder false entsprechend ob ein Repository für den entsprechenden Typ
  zugelassen ist oder nicht (release oder snapshot)

  updatePolicy
  Dieses Element bestimmt wie oft ein Update Versuch stattfinden soll. Maven
  wird den Timestamp des lokalen POM mit dem des fernen Artefakten
  vergleichen. Die möglichen Werte sind: always, daily (Standardeinstellung),
  interval:X (wobei X eine ganze Zahl in Minuten ist) oder never



                                                                           524
Appendix: Detailinformationen zur


   checksumPolicy
  Wann immer Maven eine Datei in ein Repository einstellt, wird ebenfalls eine
  Quersummendatei eingesetellt. Sie haben hier die Wahlmöglichkeit bezüglich
  ignore, fail, oder warn sollte eine Quersummendatei fehlen oder fehlerhaft
  sein.

   layout
  In der vorangegangenen Beschreibung der Repositorien wurde erwähnt, dass
  alle Repositorien einem gemeinsamen Layout entsprechen. Das stimmt
  meistens. Maven 2 besitzt ein bestimmtes Standardlayout; jedoch baut Maven 1
  auf ein anderes Format auf. Benutzen Sie diesen Parameter, um zu bestimmen,
  ob es default oder legacy ist. Sollten Sie von Maven 1 auf Maven 2 migrieren,
  und Sie möchten das bisherige Repository , welches unter Maven 1 benutzt
  wurde weiterverwenden, so setzen Sie layout auf legacy.


A.2.9. Plugin Repositories
Die Struktur des Elementblocks pluginRepositories ist ähnlich dem des
Elementblocks repositories. Das Element pluginRepository spezifiziert jeweils
eine entfernte Lokation, in welcher Maven Plugin Artefakte finden kann.

Example A.9. Plugin Repositories in der settings.xml
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                       http://maven.apache.org/xsd/settings-1.0.0.xsd">
  ...
  <profiles>
    <profile>
      ...
      <repositories>
        ...
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>codehausSnapshots</id>
          <name>Codehaus Snapshots</name>
          <releases>
             <enabled>false</enabled>

                                                                            525
settings.xml-Datei

            <updatePolicy>always</updatePolicy>
            <checksumPolicy>warn</checksumPolicy>
          </releases>
          <snapshots>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
            <checksumPolicy>fail</checksumPolicy>
          </snapshots>
          <url>http://snapshots.maven.codehaus.org/maven2</url>
          <layout>default</layout>
        </pluginRepository>
      </pluginRepositories>
      ...
    </profile>
  </profiles>
  ...
</settings>




A.2.10. Aktive Profile

Example A.10. Setzen von aktiven Profiles in der settings.xml
<settings xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  ...
  <activeProfiles>
    <activeProfile>env-test</activeProfile>
  </activeProfiles>
</settings>




Der letzte Baustein des settings.xml Puzzles ist das Element activeProfiles.
Dieses bestimmt die Menge der Elemente activeProfile, welche alle einen
eigenen Wert id haben. Jede Profile-id, welche als activeProfile definiert wurde
wird immer aktiv sein, ungeachtet jeglicher Umgebungseinflüsse. Sollte kein
entsprechendes Profil gefunden werden, so wird nichts passieren. So wird zum
Beispiel, sollte env-test ein aktives Profil in activeProfile sein, ein Profil einer
pom.xml (oder auch profile.xml) mit einer entsprechenden id aktiviert. Sollte
kein solches Profil gefunden werden, so wird die Verarbeitung unberührt

                                                                                526
Appendix: Detailinformationen zur

fortfahren.




                                                  527
Appendix B. Appendix: Alternativen zu den
Sun Spezifikationen
Das Apache Projekt Geronimo unterhält eine ganze Anzahl der Java Enterprise
Spezifikationen. Table B.1, “Tabelle A.1: „Alternative
Implementierungs-Artefakte der Spezifikationen“” (Tabelle A.1: „Alternative
Implementierungs-Artefakte der Spezifikationen“) führt alle vom Geronimo
Projekt bereitgestellten Artefakte mit deren artifactId sowie Artifact version
auf. Um eine dieser Abhängigkeiten zu benutzen setzen Sie die groupId auf
org.apache.geronimo.specs, gehen zu der Version der
Spezifikationsimplementierung welche Sie benutzen möchten, und definieren Sie
die Abhängigkeit indem Sie artifactId und artifact version wie in der Tabelle
angegeben einsetzen.

         Note
         Alle Artefakte der Table B.1, “Tabelle A.1: „Alternative
         Implementierungs-Artefakte der Spezifikationen“” (Tabelle A.1:
         „Alternative Implementierungs-Artefakte der Spezifikationen“) benutzen
         eine groupId von org.apache.geronimo.specs.


Table B.1. Tabelle A.1: „Alternative Implementierungs-Artefakte der
Spezifikationen“

Spezifikation          Spec Artifact Id                                      Artifact
                       Version                                               Version
Activation             1.0.2   geronimo-activation_1.0.2_spec                1.2
Activation             1.1     geronimo-activation_1.1_spec                  1.0.1
Activation             1.0     geronimo-activation_1.0_spec                  1.1
CommonJ                1.1     geronimo-commonj_1.1_spec                     1.0
Corba                  2.3     geronimo-corba_2.3_spec                       1.1

                                                                            528
Appendix: Alternativen zu den Sun Spezifikationen

Spezifikation           Spec Artifact Id                               Artifact
                        Version                                        Version
Corba                   3.0     geronimo-corba_3.0_spec                1.2
EJB                     2.1     geronimo-ejb_2.1_spec                  1.1
EJB                     3.0     geronimo-ejb_3.0_spec                  1.0
EL                      1.0     geronimo-el_1.0_spec                   1.0
Interceptor             3.0     geronimo-interceptor_3.0_spec          1.0
J2EE Connector          1.5     geronimo-j2ee-connector_1.5_spec       1.1.1
J2EE Deployment         1.1     geronimo-j2ee-deployment_1.1_spec      1.1
J2EE JACC               1.0     geronimo-j2ee-jacc_1.0_spec            1.1.1
J2EE Management         1.0     geronimo-j2ee-management_1.0_spec      1.1
J2EE Management         1.1     geronimo-j2ee-management_1.1_spec      1.0
J2EE                    1.4     geronimo-j2ee_1.4_spec                 1.1
JACC                    1.1     geronimo-jacc_1.1_spec                 1.0
JEE Deployment          1.1MR3geronimo-javaee-deployment_1.1MR3_spec   1.0
JavaMail                1.3.1   geronimo-javamail_1.3.1_spec           1.3
JavaMail                1.4     geronimo-javamail_1.4_spec             1.2
JAXR                    1.0     geronimo-jaxr_1.0_spec                 1.1
JAXRPC                  1.1     geronimo-jaxrpc_1.1_spec               1.1
JMS                     1.1     geronimo-jms_1.1_spec                  1.1
JPA                     3.0     geronimo-jpa_3.0_spec                  1.1
JSP                     2.0     geronimo-jsp_2.0_spec                  1.1
JSP                     2.1     geronimo-jsp_2.1_spec                  1.0
JTA                     1.0.1B geronimo-jta_1.0.1B_spec                1.1.1

                                                                       529
Appendix: Alternativen zu den Sun Spezifikationen

Spezifikation           Spec Artifact Id                                      Artifact
                        Version                                               Version
JTA                     1.1     geronimo-jta_1.1_spec                         1.1
QName                   1.1     geronimo-qname_1.1_spec                       1.1
SAAJ                    1.1     geronimo-saaj_1.1_spec                        1.1
Servlet                 2.4     geronimo-servlet_2.4_spec                     1.1.1
Servlet                 2.5     geronimo-servlet_2.5_spec                     1.1.1
STaX API                1.0     geronimo-stax-api_1.0_spec                    1.0.1
WS Metadata             2.0     geronimo-ws-metadata_2.0_spec                 1.1.1


          Note
          Zum Zeitpunkt, zu dem Sie das Buch lesen, kann die in der Spalte
          "Artifact Version" aufgeführte Version bereits veraltet sein. Um die
          aktuelle Versionsnummer zu überprüfen, gehen Sie mit Ihrem Browser
          auf http://repo1.maven.org/maven2/org/apache/geronimo/specs/ und
          wählen die artifactId welche Sie benutzen wollen, durch anklicken,
          aus. Wählen Sie die höchste Version der Spezifikation von welcher Sie
          abhängig sind.

Hier ein Beispiel wie Sie die Table B.1, “Tabelle A.1: „Alternative
Implementierungs-Artefakte der Spezifikationen“” (Tabelle A.1: „Alternative
Implementierungs-Artefakte der Spezifikationen“) benutzen würden: Sollten Sie
eine Implementierung vornehmen, welche von der Implementierung der JTA
1.0.1B Spezifikation abhängt, so würden Sie die folgende Abhängigkeitsdefinition
in ihres Projektes pom.xml-Datei einfügen:

Example B.1. Hinzufügen von JTA 1.0.1B zu einem Maven Projekt
<dependency>
  <groupId>org.apache.geronimo.specs</groupId>
  <artifactId>geronimo-jta_1.0.1B_spec</artifactId>
  <version>1.1.1</version>

                                                                              530
Appendix: Alternativen zu den Sun Spezifikationen

</dependency>




Beachten Sie wie die Version des Artifaktes sich nicht mit der Version der
Spezifikation deckt – die vorgehende Abhängigkeitdefinition fügt Version 1.0.1B
der JTA Spezifikation ein, benutzt aber eine Artifaktkennung von 1.1.1 (Geronimo
Implementierung). Seien Sie auf der Hut, wenn Sie alternative Implementierungen
einsetzen und stellen Sie jeweils sicher, dass Sie die neuste und höchste
Artifaktversion der entsprechenden Spezifikationsversion einsetzen.




                                                                             531

Weitere ähnliche Inhalte

PDF
Creation ii v0.93 vorläufig
PDF
Test Management Software Benutzerhandbuch
PDF
B8 Handbuch
PDF
Diplomarbeit Sicherheitsbank
PDF
LeitlinienküHlwasser
PDF
Handbuch de
PDF
Agiles Projektmanagement – Projektentwicklung mit Scrum, Kanb an & Co.
PDF
Hackers Undergroundbook
Creation ii v0.93 vorläufig
Test Management Software Benutzerhandbuch
B8 Handbuch
Diplomarbeit Sicherheitsbank
LeitlinienküHlwasser
Handbuch de
Agiles Projektmanagement – Projektentwicklung mit Scrum, Kanb an & Co.
Hackers Undergroundbook

Was ist angesagt? (19)

PDF
Agorum core-entwickler-dokumentation-6 4-0
PDF
Mocek Thesis
PDF
Masterarbeit / Fakultät für Mathematik und Informatik / Lehrgebiet Datenverar...
PDF
User manual
PDF
Team Oldenburger Robo-Fußball – Abschlussbericht der Projektgruppe 2010
PDF
Homematic
PDF
Homematic
PDF
Bedienungsanleitung Schlüsselverwaltung TSObjektkey 2008
PDF
Agorum core-administrations-handbuch-6 4-0a
PDF
Hb Autopilot
PDF
Das "Privacy Handbuch"
PDF
Technikerarbeit: Technische Dokumentation in der Ersatzteillogistik
PDF
Privacy handbuch win
PDF
Homepage erstellen Benutzerhandbuch
PDF
Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
PDF
Dfs manual g
PDF
agorum core-benutzer-handbuch-6 4-0
PDF
Bedienungsanleitung Schlüsselverwaltung TSKey 2006
PDF
Handbuch
Agorum core-entwickler-dokumentation-6 4-0
Mocek Thesis
Masterarbeit / Fakultät für Mathematik und Informatik / Lehrgebiet Datenverar...
User manual
Team Oldenburger Robo-Fußball – Abschlussbericht der Projektgruppe 2010
Homematic
Homematic
Bedienungsanleitung Schlüsselverwaltung TSObjektkey 2008
Agorum core-administrations-handbuch-6 4-0a
Hb Autopilot
Das "Privacy Handbuch"
Technikerarbeit: Technische Dokumentation in der Ersatzteillogistik
Privacy handbuch win
Homepage erstellen Benutzerhandbuch
Stack- und Heap-Overflow-Schutz bei Windows XP und Windows Vista
Dfs manual g
agorum core-benutzer-handbuch-6 4-0
Bedienungsanleitung Schlüsselverwaltung TSKey 2006
Handbuch
Anzeige

Andere mochten auch (20)

PDF
Vorlesung "Online PR" an der BAW
PDF
The Ultimate Guide to Creating Visually Appealing Content
PDF
Dear NSA, let me take care of your slides.
PPTX
What I Carry: 10 Tools for Success
PDF
What Makes Great Infographics
PPT
Social Media Strategie
PDF
Masters of SlideShare
PDF
STOP! VIEW THIS! 10-Step Checklist When Uploading to Slideshare
PDF
Von grauen Zellen zu bunten Ideen.
PDF
You Suck At PowerPoint!
PDF
10 Ways to Win at SlideShare SEO & Presentation Optimization
PDF
How To Get More From SlideShare - Super-Simple Tips For Content Marketing
PDF
2015 Upload Campaigns Calendar - SlideShare
PDF
Estrutura do livro
PPTX
What to Upload to SlideShare
PDF
HISTÓRIAS BÍBLICAS PARA CRIANÇAS - O LIVRO DE GÊNESISDisponível em: www.porta...
PDF
Manual patronaje femenino
PDF
Gestão por Processo
PDF
Teclado curso completo - como tocar teclado - rafael harduim
PDF
How to Make Awesome SlideShares: Tips & Tricks
Vorlesung "Online PR" an der BAW
The Ultimate Guide to Creating Visually Appealing Content
Dear NSA, let me take care of your slides.
What I Carry: 10 Tools for Success
What Makes Great Infographics
Social Media Strategie
Masters of SlideShare
STOP! VIEW THIS! 10-Step Checklist When Uploading to Slideshare
Von grauen Zellen zu bunten Ideen.
You Suck At PowerPoint!
10 Ways to Win at SlideShare SEO & Presentation Optimization
How To Get More From SlideShare - Super-Simple Tips For Content Marketing
2015 Upload Campaigns Calendar - SlideShare
Estrutura do livro
What to Upload to SlideShare
HISTÓRIAS BÍBLICAS PARA CRIANÇAS - O LIVRO DE GÊNESISDisponível em: www.porta...
Manual patronaje femenino
Gestão por Processo
Teclado curso completo - como tocar teclado - rafael harduim
How to Make Awesome SlideShares: Tips & Tricks
Anzeige

Ähnlich wie Maven Definitive Guide De (17)

PDF
Ant Maven
PPS
Einführung Maven
PDF
Maven2 - Die nächste Generation des Buildmanagements?
PDF
Maven Intro
PPTX
Automatischer Build mit Maven - OPITZ CONSULTING - Stefan Scheidt
PPTX
Introduction to Apache Maven 3 (German)
PPTX
Automatischer Build mit Maven
PDF
Continuous Integration mit Hudson (JUG Stuttgart, 11.02.2010)
PDF
Continuous Integration mit Hudson
PDF
Vortragsreihe Dortmund: Unified Development Environments
PDF
Continuous Integration mit Hudson (JUG Mannheim, 27.01.2010)
PDF
JFS 2011 - Top 10 der Tools & Methoden - Baumgartner, Oehmichen
PDF
JFS 2011 - Top 10 Tools & Methoden - Baumgartner, Oehmichen
PPT
Einsteiger Workshop
PDF
Continuous Delivery - Development Tool Chain - Virtualisierung, Packer, Vagra...
KEY
iOS: Continuous Integration and One Click Deployments
PPT
Automatisierte Linux installation 0603_version2012_inoxt_rz
Ant Maven
Einführung Maven
Maven2 - Die nächste Generation des Buildmanagements?
Maven Intro
Automatischer Build mit Maven - OPITZ CONSULTING - Stefan Scheidt
Introduction to Apache Maven 3 (German)
Automatischer Build mit Maven
Continuous Integration mit Hudson (JUG Stuttgart, 11.02.2010)
Continuous Integration mit Hudson
Vortragsreihe Dortmund: Unified Development Environments
Continuous Integration mit Hudson (JUG Mannheim, 27.01.2010)
JFS 2011 - Top 10 der Tools & Methoden - Baumgartner, Oehmichen
JFS 2011 - Top 10 Tools & Methoden - Baumgartner, Oehmichen
Einsteiger Workshop
Continuous Delivery - Development Tool Chain - Virtualisierung, Packer, Vagra...
iOS: Continuous Integration and One Click Deployments
Automatisierte Linux installation 0603_version2012_inoxt_rz

Maven Definitive Guide De

  • 1. 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
  • 2. 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
  • 3. 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
  • 4. 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
  • 5. 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
  • 6. 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
  • 7. 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
  • 8. 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
  • 9. 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
  • 10. 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
  • 11. 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
  • 12. 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
  • 13. 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
  • 14. 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
  • 15. 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
  • 16. 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
  • 17. 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
  • 18. 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
  • 19. 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
  • 20. 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
  • 21. 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
  • 22. 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
  • 23. 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
  • 24. 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
  • 25. 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
  • 26. 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
  • 27. 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
  • 28. 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
  • 29. 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
  • 30. 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
  • 31. 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
  • 32. 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
  • 33. 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
  • 34. 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
  • 35. 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
  • 36. 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
  • 37. 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
  • 38. 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
  • 39. 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
  • 40. 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
  • 41. 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
  • 42. 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
  • 43. 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
  • 44. 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
  • 45. 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
  • 46. 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
  • 47. 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
  • 48. 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
  • 49. 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
  • 50. 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
  • 51. 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
  • 52. 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
  • 53. 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
  • 54. 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
  • 55. 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
  • 56. 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
  • 57. 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
  • 58. 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
  • 59. 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
  • 60. 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
  • 61. 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
  • 62. 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
  • 63. 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
  • 64. Ein einfaches Maven Projekt 45
  • 65. 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
  • 66. 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
  • 67. 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
  • 68. 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
  • 69. 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
  • 70. 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
  • 71. 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
  • 72. 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
  • 73. 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
  • 74. 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
  • 75. 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
  • 76. 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
  • 77. 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
  • 78. 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
  • 79. 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
  • 80. 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
  • 81. 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
  • 82. 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
  • 83. 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
  • 84. 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
  • 85. 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
  • 86. 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
  • 87. 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
  • 88. 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
  • 89. 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
  • 90. 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
  • 91. 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
  • 92. 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
  • 93. 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
  • 94. 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
  • 95. 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
  • 96. 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
  • 97. 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
  • 98. 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
  • 99. 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
  • 100. 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
  • 101. 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
  • 102. 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
  • 103. 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
  • 104. 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
  • 105. 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
  • 106. 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
  • 107. 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
  • 108. 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
  • 109. 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
  • 110. 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
  • 111. 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
  • 112. 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
  • 113. 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
  • 114. 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
  • 115. 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
  • 116. 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
  • 117. 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
  • 118. 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
  • 119. 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
  • 120. 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
  • 121. 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
  • 122. 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
  • 123. 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
  • 124. 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
  • 125. 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
  • 126. 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
  • 127. 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
  • 128. 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
  • 129. 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
  • 130. 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
  • 131. 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
  • 132. 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
  • 133. 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
  • 134. 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
  • 135. 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
  • 136. 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
  • 137. 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
  • 138. 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
  • 139. 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
  • 140. 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
  • 141. 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
  • 142. 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
  • 143. 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
  • 144. 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
  • 145. 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
  • 146. 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
  • 147. 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
  • 148. 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
  • 149. 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
  • 150. 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
  • 151. 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
  • 152. 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
  • 153. 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
  • 154. 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
  • 155. 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
  • 156. 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
  • 157. 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
  • 158. 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
  • 159. 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
  • 160. 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
  • 161. 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
  • 162. 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
  • 163. 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
  • 164. 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
  • 165. 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
  • 166. 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
  • 167. 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
  • 168. 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
  • 169. 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
  • 170. 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
  • 171. 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
  • 172. 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
  • 173. 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
  • 174. 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
  • 175. 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
  • 176. 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
  • 177. 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
  • 178. 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
  • 179. 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
  • 180. 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
  • 181. 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
  • 182. 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
  • 183. 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
  • 184. 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
  • 185. 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
  • 186. 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
  • 187. 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
  • 188. 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
  • 189. 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
  • 190. 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
  • 191. 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
  • 192. 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
  • 193. 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
  • 194. 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
  • 195. 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
  • 196. 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
  • 197. 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
  • 198. 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
  • 199. 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
  • 200. 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
  • 201. 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
  • 202. 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
  • 203. 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
  • 204. 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
  • 205. 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
  • 206. 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
  • 207. 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
  • 208. 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
  • 209. 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
  • 210. 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
  • 211. Part II. Maven Reference 192
  • 212. 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
  • 213. 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
  • 214. 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
  • 215. 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
  • 216. 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
  • 217. 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
  • 218. 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
  • 219. 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
  • 220. 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
  • 221. 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
  • 222. 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
  • 223. 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
  • 224. 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
  • 225. 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
  • 226. 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
  • 227. 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
  • 228. 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
  • 229. 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
  • 230. 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
  • 231. 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
  • 232. 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
  • 233. 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
  • 234. 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
  • 235. 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
  • 236. 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
  • 237. 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
  • 238. 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
  • 239. 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
  • 240. 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
  • 241. 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
  • 242. 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
  • 243. 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
  • 244. 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
  • 245. 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
  • 246. 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
  • 247. 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
  • 248. 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
  • 249. 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
  • 250. 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
  • 251. 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
  • 252. 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
  • 253. 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
  • 254. 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
  • 255. 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
  • 256. 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
  • 257. 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
  • 258. 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
  • 259. 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
  • 260. 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
  • 261. 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
  • 262. 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
  • 263. 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
  • 264. 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
  • 265. 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
  • 266. 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
  • 267. 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
  • 268. 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
  • 269. 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
  • 270. 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
  • 271. 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
  • 272. 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
  • 273. 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
  • 274. 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
  • 275. 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
  • 276. 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
  • 277. 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
  • 278. 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
  • 279. 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
  • 280. Der Build Lebenszyklus betroffen sein könnten, annimmt. 261
  • 281. 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
  • 282. 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
  • 283. 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
  • 284. 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
  • 285. 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
  • 286. 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
  • 287. 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
  • 288. 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
  • 289. 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
  • 290. 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
  • 291. 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
  • 292. 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
  • 293. 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
  • 294. 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
  • 295. 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
  • 296. 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
  • 297. 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
  • 298. 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
  • 299. 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
  • 300. 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
  • 301. 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
  • 302. 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
  • 303. 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
  • 304. 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
  • 305. 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
  • 306. 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
  • 307. 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
  • 308. 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
  • 309. 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
  • 310. 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
  • 311. 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
  • 312. 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
  • 313. 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
  • 314. 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
  • 315. 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
  • 316. 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
  • 317. 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
  • 318. 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
  • 319. 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
  • 320. 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
  • 321. 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
  • 322. 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
  • 323. 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
  • 324. 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
  • 325. 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
  • 326. 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
  • 327. 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
  • 328. 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
  • 329. 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
  • 330. 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
  • 331. 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
  • 332. 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
  • 333. 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
  • 334. 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
  • 335. 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
  • 336. 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
  • 337. 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
  • 338. 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
  • 339. 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
  • 340. 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
  • 341. 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
  • 342. 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
  • 343. 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. However, as of Maven 2.0.9 this can cause big problems if that parent is a higher-level POM in the same build, particularly if that parent POM expects to build an assembly using its modules’ binaries. Maven 2.0.9 sorts projects in a multi-module build according to their dependencies, with a given project’s dependencies being built ahead of itself. The problem is the parent element is considered a dependency, which means the parent project’s build must complete before the child project is built. If part of that parent’s build process includes the creation of an assembly that uses module binaries, those binaries will not exist yet, and therefore cannot be included, causing the assembly to fail. This is a complex and subtle issue, which severely limits the usefulness of the module binaries section of the assembly descriptor. In fact, it has been filed in the bug tracker for the Assembly plugin at: http://jira.codehaus.org/browse/MASSEMBLY-97. Hopefully, future versions of Maven will find a way to restore this functionality, since the parent-first requirement may not be completely necessary. 324
  • 344. Maven Assemblies 12.5.6. Repositories Section The repositories section represents a slightly more exotic feature in the assembly descriptor, since few applications other than Maven can take full advantage of a Maven-repository directory structure. For this reason, and because many of its features closely resemble those in the dependencySets section, we won’t spend too much time on the repositories section of the assembly descriptor. In most cases, users who understand dependency sets should have no trouble constructing repositories via the Assembly plugin. We're not going to motivate the repositories section; we're not going to go through a the business of setting up a use case and walking you through the process. We're just going to bring up a few caveats for those of you who find the need to use the repostiories section. Having said that, there are a two features particular to the repositories section that deserve some mention. The first is the includeMetadata flag. When set to true it includes metadata such as the list of real versions that correspond to -SNAPSHOT virtual versions, and by default it’s set to false. At present, the only metadata included when this flag is true is the information downloaded from Maven’s central repository. The second feature is called groupVersionAlignments. Again, this section is a list of individual groupVersionAlignment configurations, whose purpose is to normalize all included artifacts for a particular groupId to use a single version. Each alignment entry consists of two mandatory elements - id and version - along with an optional section called excludes that supplies a list of artifactId string values which are to be excluded from this realignment. Unfortunately, this realignment doesn’t seem to modify the POMs involved in the repository, neither those related to realigned artifacts nor those that depend on realigned artifacts, so it’s difficult to imagine what the practical application for this sort of realignment would be. In general, it’s simplest to apply the same principles you would use in dependency sets to repositories when adding them to your assembly descriptor. While the repositories section does support the above extra options, they are mainly provided for backward compatibility, and will probably be deprecated in future releases. 325
  • 345. Maven Assemblies 12.5.7. Managing the Assembly’s Root Directory Now that we’ve made it through the main body of the assembly descriptor, we can close the discussion of content-related descriptor sections with something lighter: root-directory naming and site-directory handling. Some may consider it a stylistic concern, but it’s often important to have control over the name of the root directory for your assembly, or whether the root directory is there at all. Fortunately, two configuration options in the root of the assembly descriptor make managing the archive root directory simple: includeBaseDirectory and baseDirectory. In cases like executable jar files, you probably don’t want a root directory at all. To skip it, simply set the includeBaseDirectory flag to false (it’s true by default). This will result in an archive that, when unpacked, may create more than one directory in the unpack target directory. While this is considered bad form for archives that are meant to be unpacked before use, it’s not so bad for archives that are consumable as-is. In other cases, you may want to guarantee the name of the archive root directory regardless of the POM’s version or other information. By default, the baseDirectory element has a value equal to ${project.artifactId}-${project.version}. However, we can easily set this element to any value that consists of literal strings and expressions which can be interpolated from the current POM, such as ${project.groupId}-${project.artifactId}. This could be very good news for your documentation team! (We all have those, right?) Another configuration available is the includeSiteDirectory flag, whose default value is false. If your project build has also constructed a website document root using the site lifecycle or the Site plugin goals, that output can be included by setting this flag to true. However, this feature is a bit limited, since it only includes the outputDirectory from the reporting section of the current POM (by default, target/site) and doesn’t take into consideration any site directories that may be available in module projects. Use it if you want, but a good fileSet specification or moduleSet specification with sources configured could serve equally well, if not better. This is yet another example of legacy configuration currently supported by the Assembly plugin for the purpose of backward 326
  • 346. Maven Assemblies compatibility. Your mileage may vary. If you really want to include a site that is aggregated from many modules, you'll want to consider using a fileSet or moduleSet instead of setting includeSiteDirectory to true. 12.5.8. componentDescriptors and containerDescriptorHandlers To round out our exploration of the assembly descriptor, we should touch briefly on two other sections: containerDescriptorHandlers and componentDescriptors. The containerDescriptorHandlers section refers to custom components that you use to extend the capabilities of the Assembly plugin. Specifically, these custom components allow you to define and handle special files which may need to be merged from the multiple constituents used to create your assembly. A good example of this might be a custom container-descriptor handler that merged web.xml files from constituent war or war-fragment files included in your assembly, in order to create the single web-application descriptor required for you to use the resulting assembly archive as a war file. The componentDescriptors section allows you to reference external assembly-descriptor fragments and include them in the current descriptor. Component references can be any of the following: 1. Relative file paths: src/main/assembly/component.xml 2. Artifact references: groupId:artifactId:version[:type[:classifier]] 3. Classpath resources: /assemblies/component.xml 4. URLs: http://www.sonatype.com/component.xml Incidentally, when resolving a component descriptor, the Assembly plugin tries those different strategies in that exact order. The first one to succeed is used. Component descriptors can contain many of the same content-oriented sections available in the assembly descriptor itself, with the exception of moduleSets, which is considered so specific to each project that it’s not a good candidate for 327
  • 347. Maven Assemblies reuse. Also included in a component descriptor is the containerDescriptorHandlers section, which we briefly discussed above. Component descriptors cannot contain formats, assembly id’s, or any configuration related to the base directory of the assembly archive, all of which are also considered unique to a particular assembly descriptor. While it may make sense to allow sharing of the formats section, this has not been implemented as of the 2.2-beta-2 Assembly-plugin release. 12.6. Best Practices The Assembly plugin provides enough flexibility to solve many problems in a number of different ways. If you have a unique requirement for your project, there's a good chance that you can use the methods documented in this chapter to achieve almost any assembly structure. This section of the chapter details some common best practices which, if adhered to, will make your experiences with the assembly plugin more productive and less painful. 12.6.1. Standard, Reusable Assembly Descriptors Up to now, we’ve been talking mainly about one-off solutions for building a particular type of assembly. But what do you do if you have dozens of projects that all need a particular type of assembly? In short, how can we reuse the effort we’ve invested to get our assemblies just the way we like them across more than one project without copying and pasting our assembly descriptor? The simplest answer is to create a standardized, versioned artifact out of the assembly descriptor, and deploy it. Once that’s done, you can specify that the Assembly plugin section of your project’s POM include the assembly-descriptor artifact as a plugin-level dependency, which will prompt Maven to resolve and include that artifact in the plugin’s classpath. At that point, you can use the assembly descriptor via the descriptorRefs configuration section in the Assembly plugin declaration. To illustrate, consider this example assembly descriptor: <assembly> <id>war-fragment</id> <formats> 328
  • 348. Maven Assemblies <format>zip</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <dependencySets> <dependencySet> <outputDirectory>WEB-INF/lib</outputDirectory> </dependencySet> </dependencySets> <fileSets> <fileSet> <directory>src/main/webapp</directory> <outputDirectory>/</outputDirectory> <excludes> <exclude>**/web.xml</exclude> </excludes> </fileSet> </fileSets> </assembly> Included in your project, this descriptor would be a useful way to bundle the project contents so that it could be unpacked directly into an existing web application in order to add to it (for adding an extending feature, say). However, if your team builds more than one of these web-fragment projects, it will likely want to reuse this descriptor rather than duplicating it. To deploy this descriptor as its own artifact, we’re going to put it in its own project, under the src/main/resources/assemblies directory. The project structure for this assembly-descriptor artifact will look similar to the following: |-- pom.xml `-- src `-- main `-- resources `-- assemblies `-- web-fragment.xml Notice the path of our web-fragment descriptor file. By default, Maven includes the files from the src/main/resources directory structure in the final jar, which means our assembly descriptor will be included with no extra configuration on our part. Also, notice the assemblies/ path prefix, the Assembly plugin expects this path prefix on all descriptors provided in the plugin classpath. It’s important that we put our descriptor in the appropriate relative location, so it will be picked up by the Assembly plugin as it executes. 329
  • 349. Maven Assemblies Remember, this project is separate from your actual web-fragment project now; the assembly descriptor has become its own artifact with its own version and, possibly, its own release cycle. Once you install this new project using Maven, you’ll be able to reference it in your web-fragment projects. For clarity, the build process should look something like this: $ mvn install (...) [INFO] [install:install] [INFO] Installing (...)/web-fragment-descriptor/target/ web-fragment-descriptor-1.0-SNAPSHOT.jar to /Users/~/.m2/repository/org/sonatype/mavenbook/assemblies/ web-fragment-descriptor/1.0-SNAPSHOT/ web-fragment-descriptor-1.0-SNAPSHOT.jar [INFO] --------------------------------------------------------------- [INFO] BUILD SUCCESSFUL [INFO] --------------------------------------------------------------- [INFO] Total time: 5 seconds (...) Since there are no sources for the web-fragment-descriptor project, the resulting jar artifact will include nothing but our web-fragment assembly descriptor. Now, let’s use this new descriptor artifact: <project> (...) <artifactId>my-web-fragment</artifactId> (...) <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-2</version> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.assemblies</groupId> <artifactId>web-fragment-descriptor</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> <executions> <execution> <id>assemble</id> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <descriptorRefs> 330
  • 350. Maven Assemblies <descriptorRef>web-fragment</descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> (...) </plugins> </build> (...) </project> Two things are special about this Assembly plugin configuration: • We have to include a plugin-level dependency declaration on our new web-fragment-descriptor artifact in order to have access to the assembly descriptor via the plugin’s classpath. • Since we’re using a classpath reference instead of a file in the local project directory structure, we must use the descriptorRefs section instead of the descriptor section. Also, notice that, while the assembly descriptor is actually in the assemblies/web-fragment.xml location within the plugin’s classpath, we reference it without the assemblies/ prefix. This is because the Assembly plugin assumes that built-in assembly descriptors will always reside in the classpath under this path prefix. Now, you’re free to reuse the POM configuration above in as many projects as you like, with the assurance that all of their web-fragment assemblies will turn out the same. As you need to make adjustments to the assembly format - maybe to include other resources, or to fine-tune the dependency and file sets - you can simply increment the version of the assembly descriptor’s project, and release it again. POMs referencing the assembly-descriptor artifact can then adopt this new version of the descriptor as they are able. One final point about assembly-descriptor reuse: you may want to consider sharing the plugin configuration itself as well as publishing the descriptor as an artifact. This is a fairly simple step; you simply add the configuration listed above to the pluginManagement section of your parent POM, then reference the managed plugin configuration from your module POM like this: (...) 331
  • 351. Maven Assemblies <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> </plugin> (...) If you’ve added the rest of the plugin’s configuration - listed in the previous example - to the pluginManagement section of the project’s parent POM, then each project inheriting from that parent POM can add a minimal entry like the one above and take advantage of an advanced assembly format in their own builds. 12.6.2. Distribution (Aggregating) Assemblies As mentioned above, the Assembly plugin provides multiple ways of creating many archive formats. Distribution archives are typically very good examples of this, since they often combine modules from a multi-module build, along with their dependencies and possibly, other files and artifacts besides these. The distribution aims to include all these different sources into a single archive that the user can download, unpack, and run with convenience. However, we also examined some of the potential drawbacks of using the moduleSets section of the assembly descriptor - namely, that the parent-child relationships between POMs in a build can prevent the availability of module artifacts in some cases. Specifically, if module POMs reference as their parent the POM that contains the Assembly-plugin configuration, that parent project will be built ahead of the module projects when the multi-module build executes. The parent’s assembly expects to find artifacts in place for its modules, but these module projects are waiting on the parent itself to finish building, a gridlock situation is reached and the parent build cannot succeed (since it’s unable to find artifacts for its module projects). In other words, the child project depends on the parent project which in turn depends on the child project. As an example, consider the assembly descriptor below, designed to be used from the top-level project of a multi-module hierarchy: <assembly> <id>distribution</id> <formats> 332
  • 352. Maven Assemblies <format>zip</format> <format>tar.gz</format> <format>tar.bz2</format> </formats> <moduleSets> <moduleSet> <includes> <include>*-web</include> </includes> <binaries> <outputDirectory>/</outputDirectory> <unpack>true</unpack> <includeDependencies>true</includeDependencies> <dependencySets> <dependencySet> <outputDirectory>/WEB-INF/lib</outputDirectory> </dependencySet> </dependencySets> </binaries> </moduleSet> <moduleSet> <includes> <include>*-addons</include> </includes> <binaries> <outputDirectory>/WEB-INF/lib</outputDirectory> <includeDependencies>true</includeDependencies> <dependencySets> <dependencySet/> </dependencySets> </binaries> </moduleSet> </moduleSets> </assembly> Given a parent project - called app-parent - with three modules called app-core, app-web, and app-addons, notice what happens when we try to execute this multi-module build: $ mvn package [INFO] Reactor build order: [INFO] app-parent <----- PARENT BUILDS FIRST [INFO] app-core [INFO] app-web [INFO] app-addons [INFO] --------------------------------------------------------------- [INFO] Building app-parent [INFO] task-segment: [package] [INFO] --------------------------------------------------------------- [INFO] [site:attach-descriptor] 333
  • 353. Maven Assemblies [INFO] [assembly:single {execution: distro}] [INFO] Reading assembly descriptor: src/main/assembly/distro.xml [INFO] --------------------------------------------------------------- [ERROR] BUILD ERROR [INFO] --------------------------------------------------------------- [INFO] Failed to create assembly: Artifact: org.sonatype.mavenbook.assemblies:app-web:jar:1.0-SNAPSHOT (included by module) does not have an artifact with a file. Please ensure the package phase is run before the assembly is generated. ... The parent project - app-parent - builds first. This is because each of the other projects lists that POM as its parent, which causes it to be forced to the front of the build order. The app-web module, which is the first module to be processed in the assembly descriptor, hasn’t been built yet. Therefore, it has no artifact associated with it, and the assembly cannot succeed. One workaround for this is to remove the executions section of the Assembly-plugin declaration, that binds the plugin to the package lifecycle phase in the parent POM, keeping the configuration section intact. Then, execute Maven with two command-line tasks: the first, package, to build the multi-module project graph, and a second, assembly:assembly, as a direct invocation of the assembly plugin to consume the artifacts built on the previous run, and create the distribution assembly. The command line for such a build might look like this: $ mvn package assembly:assembly However, this approach has several drawbacks. First, it makes the distribution-assembly process more of a manual task that can increase the complexity and potential for error in the overall build process significantly. Additionally, it could mean that attached artifacts - which are associated in memory as the project build executes - are not reachable on the second pass without resorting to file-system references. Instead of using a moduleSet to collect the artifacts from your multi-module build, it often makes more sense to employ a low-tech approach: using a dedicated distribution project module and inter-project dependencies. In this approach, you create a new module in your build whose sole purpose is to assemble the distribution. This module POM contains dependency references to all the other modules in the project hierarchy, and it configures the Assembly plugin to be 334
  • 354. Maven Assemblies bound the package phase of its build lifecycle. The assembly descriptor itself uses the dependencySets section instead of the moduleSets section to collect module artifacts and determine where to include them in the resulting assembly archive. This approach escapes the pitfalls associated with the parent-child relationship discussed above, and has the additional advantage of using a simpler configuration section within the assembly descriptor itself to do the job. To do this, we can create a new project structure that’s very similar to the one used for the module-set approach above, with the addition of a new distribution project, we might end up with five POMs in total: app-parent, app-core, app-web, app-addons, and app-distribution. The new app-distribution POM looks similar to the following: <project> <parent> <artifactId>app-parent</artifactId> <groupId>org.sonatype.mavenbook.assemblies</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>app-distribution</artifactId> <name>app-distribution</name> <dependencies> <dependency> <artifactId>app-web</artifactId> <groupId>org.sonatype.mavenbook.assemblies</groupId> <version>1.0-SNAPSHOT</version> <type>war</type> </dependency> <dependency> <artifactId>app-addons</artifactId> <groupId>org.sonatype.mavenbook.assemblies</groupId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Not necessary since it's brought in via app-web. <dependency> [2] <artifactId>app-core</artifactId> <groupId>org.sonatype.mavenbook.assemblies</groupId> <version>1.0-SNAPSHOT</version> </dependency> --> </dependencies> </project> Notice that we have to include dependencies for the other modules in the project 335
  • 355. Maven Assemblies structure, since we don’t have a modules section to rely on in this POM. Also, notice that we’re not using an explicit dependency on app-core. Since it’s also a dependency of app-web, we don’t need to process it (or, avoid processing it) twice. Next, when we move the distro.xml assembly descriptor into the app-distribution project, we must also change it to use a dependencySets section, like this: <assembly> ... <dependencySets> <dependencySet> <includes> <include>*-web</include> </includes> <useTransitiveDependencies>false</useTransitiveDependencies> <outputDirectory>/</outputDirectory> <unpack>true</unpack> </dependencySet> <dependencySet> <excludes> <exclude>*-web</exclude> </excludes> <useProjectArtifact>false</useProjectArtifact> <outputDirectory>/WEB-INF/lib</outputDirectory> </dependencySet> </dependencySets> ... </assembly> This time, if we run the build from the top-level project directory, we get better news: $ mvn package (...) [INFO] --------------------------------------------------------------- [INFO] Reactor Summary: [INFO] --------------------------------------------------------------- [INFO] module-set-distro-parent ...............SUCCESS [3.070s] [INFO] app-core .............................. SUCCESS [2.970s] [INFO] app-web ............................... SUCCESS [1.424s] [INFO] app-addons ............................ SUCCESS [0.543s] [INFO] app-distribution ...................... SUCCESS [2.603s] [INFO] --------------------------------------------------------------- [INFO] --------------------------------------------------------------- [INFO] BUILD SUCCESSFUL [INFO] --------------------------------------------------------------- [INFO] Total time: 10 seconds [INFO] Finished at: Thu May 01 18:00:09 EDT 2008 336
  • 356. Maven Assemblies [INFO] Final Memory: 16M/29M [INFO] --------------------------------------------------------------- As you can see, the dependency-set approach is much more stable and - at least until Maven’s internal project-sorting logic catches up with the Assembly plugin’s capabilities, - involves less opportunity to get things wrong when running a build. 12.7. Summary As we’ve seen in this chapter, the Maven Assembly plugin offers quite a bit of potential for creating custom archive formats. While the details of these assembly archives can be complex, they certainly don’t have to be in all cases - as we saw with built-in assembly descriptors. Even if your aim is to include your project’s dependencies and selected project files in some unique, archived directory structure, writing a custom assembly descriptor doesn’t have to be an arduous task. Assemblies are useful for a wide array of applications, but are most commonly used as application distributions of various sorts. And, while there are many different ways to use the Assembly plugin, using standardized assembly-descriptor artifacts and avoiding moduleSets when creating distributions containing binaries are two sure ways to avoid problems. 337
  • 357. Chapter 13. Properties and Ressource Filterung 13.1. Einleitung In diesem Buch finden Sie durchgängig Hinweise auf Properties (Eigenschaften) welche Sie in POM-Dateien referenzieren können. Auf geerbte Abhängigkeiten eines multi-projekt Builds kann mittels der Properties ${project.groupId} sowie ${project.version} zugegriffen werden und jeder Abschnitt eines POM kann referenziert werden indem man einer Variablen die Vorsilbe „project.“ Anfügt. Umgebungsvariablen und Java System Einstellungen können referenziert werden, wie auch alle Einstellungen aus Ihrer ~/.m2/settings.xml Datei. Was Ihnen noch nicht auf den Weg gegeben wurde ist eine Aufstellung der möglichen Property Variablen, und eine Beschreibung wie diese zum Einsatz kommen um einen portablen Build zu erstellen. Dieses Kapitel nun liefert Ihnen diese Aufstellung. Sollten Sie bereits Property Referenzen in Ihrem POM einsetzen, so sollte Ihnen ebenfalls bewusst sein, dass Maven eine Funktionalität mit dem Namen Ressource Filter anbietet, welche Ihnen erlaubt, jegliche Property Referenz zu ändern, welche in einer Ressource Datei unter src/main/resources abgelegt ist. Standardmässig ist diese Funktion deaktiviert, um unbeabsichtigte Veränderung von Property Referenzen zu verhindern. Diese Funktionalität kann jedoch Einsatz finden, um einen Build auf eine bestimmte Plattform zu richten, oder um wichtige Property Einstellungen in Ressource Dateien, POMs oder Profilen zu externalisieren. Dieses Kapitel hat zum Ziel, die Funktionalität des Ressource Filtering darzustellen und gibt eine kurze Einführung wie diese zum Einsatz kommt um einen portablen Enterprise Build herzustellen. 13.2. Maven Properties Sie können Maven Properties in einer pom.xml Datei, oder irgendeiner anderen 338
  • 358. Properties and Ressource Filterung Ressource ablegen, welche vom Maven Ressource Plugin und dessen Filter Funktion verarbeitet wird. Ein Property ist immer umschlossen von ${ und } . Um auf das Property project.version zu referenzieren, würden Sie folgende Variable benutzen: ${project.version} Es bestehen einige implizite Maven Properties, diese sind: project.* Maven Project Objekt Modell (POM). Benutzen Sie das Präfix project.* um Werte des Maven POM zu referenzieren. settings.* Maven Settings; Benutzen Sie das Präfix settings.* um Werte aus der lokalen Maven Settings Datei (gewöhnlich unter ~/.m2/settings.xml zu referenzieren. env.* Umgebungsvariables wie z.B. PATH sowie M2_HOME können mittels dem Präfix env.* benannte werden. System Properties Jeglicher Wert welcher durch die System.getProperty() Methode abgefragt werden kann, kann als Maven Property referenziert werden. Zusätzlich zu den impliziten Properties wie oben dargestellt, können das Maven POM, die Maven Settings Datei sowie das Maven Profil weitere freidefinierte Werte enthalten. Der folgende Abschnitt wird Ihnen einigen Einblick in die verschiedenen in einem Maven Projekt verfügbaren Werte geben. 13.2.1. Maven Projekt Einstellungen Immer wenn ein Maven Projekt Property referenziert wird, zeigt der Name des Property auf einen Wert des Projekt Objekt Modell (POM). Genauer gesagt, Sie setzen eine Referenz auf ein Property der Klasse org.apache.maven.model.Model welche als implizite Projektvariable dargestellt wird. Wollen Sie sich auf ein 339
  • 359. Properties and Ressource Filterung Property dieser impliziten Variablen beziehen, so wenden Sie die einfache Punkt-Notation an, um auf ein Bean-Property des Modell-Objektes zu zeigen. Referenzieren Sie zum Beispiel ${project.version}, so rufen Sie tatsächlich die Methode getVersion() der Instanz des Modell Objektes auf, als welches das Projekt offengelegt wird. Das POM wird ebenfalls durch die Datei pom.xml dargestellt, welche in allen Maven Projekten präsent ist. Alle Bestandteile des Maven POM können als Property angesprochen werden. Eine komplette Referenz der POM Struktur finden Sie unter http://maven.apache.org/ref/2.0.9/maven-model/maven.html. In der folgenden Liste werden wir einige der häufiger gebrauchten Referenzen auf Maven Projekt Properties ausführen: project.groupId, project.version Projekte die Bestandteil eines grossen multi-modularen Build sind, benutzen häufig die selbe groupId und version Bezeichner. Sollten Sie Abhängigkeiten zwischen verschiedenen Modulen deklarieren welche die gleiche groupId und version tragen, so tun Sie gut daran, für beide eine Property Referenz zu definieren: <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>sibling-project</artifactId> <version>${project.version}</version> </dependency> </dependencies> project.artifactId Die artifactId eines Projektes wird oft als Name eines Paketes eingesetzt. So möchten Sie, im Falle eines Projektes das als WAR paketiert wird, ein WAR ohne Versionsbezug im Namen generieren. Um dies zu bewerkstelligen würden Sie project.artifactId wie folgt referenzieren: <build> <finalName>${project.artifactId}</finalName> </build> 340
  • 360. Properties and Ressource Filterung project.name, project.description Der Name und die Projektbeschreibung sind ofmals nützliche Referenzen in einer Dokumentation. Anstatt sich damit abzumüchen, dass der gesamte Site die gleiche Kurzbeschreibung benutzt, ist es viel einfacher diese Werte zu referenzieren. project.build.* Sollten Sie sich auf Ausgabeverzeichnisse berufen müssen, so vermeiden Sie den Einsatz von direkten Bezügen zu den Verzeichnissen wie z.B. target/classes. Statt dessen sollten Sie Property Referenzen auf die Verzeichnisse setzen. • project.build.sourceDirectory • project.build.scriptSourceDirectory • project.build.testSourceDirectory • project.build.outputDirectory • project.build.testOutputDirectory • project.build.directory sourceDirectory, scriptSourceDirectory, und testSourceDirectory geben Ihnen Zugriff auf die Quellverzeichnisse des Projektes. outputDirectory sowie testOutputDirectory geben Ihnen Zugriff auf die Verzeichnisse, welche Maven nutzt um den Byte Code oder Build Output abzulegen. directory bezieht sich auf das Verzeichnis, das alle derartigen Ausgabeverzeichnisse umfasst. Weitere Projekt Property Referenzes Darüber hinaus gibt es hunderte Properties welche man innerhalb eines POM referenzieren kann. Eine vollständige Referenz des POM finden Sie unter http://maven.apache.org/ref/2.0.9/maven-model/maven.html. Sollten Sie eine vollständige Dokumentation der Maven Modell Objekt Properties 341
  • 361. Properties and Ressource Filterung benötigen, so bitte wir Sie auf die JavaDoc des Projektes maven-model zurückzugreifen, diese finden Sie unter: http://maven.apache.org/ref/2.0.9/maven-model/apidocs/index.html. Nach dem Laden der JavaDoc, sehen Sie sich die Klasse Model an. Von dieser Klasse aus sollten Sie alle von Ihnen gesuchten POM Properties ansteuern können. Sollten Sie das Ausgabeverzeichnis des Builds referenzieren wollen, so sehen Sie in der Maven Model JavaDoc, dass dieses mittels model.getBuild().getOutputDirectory(); erfolgt. Diesen Methodenaufruf würden Sie in die Maven Property Referenz: ${project.build.outputDirectory} wiederfinden. Bezüglich weiterer Informationen zum Maven Model Module, dem Modul, welches die Struktur des POM definiert, möchten wir auf die Maven Model Seite unter http://maven.apache.org/ref/2.0.9/maven-model verweisen. 13.2.2. Properties der Maven Einstellungen (settings.xml) Sie können auf jedes Property der lokalen Einstellungsdatei zugreifen, welche normalerweise unter ~/.m2/settings.xml abgelegt ist. Diese Datei enthält benutzerspezifische Einstellungen bezüglich der Maven Installation wie z.B. dem Ort des lokalen Repository, der lokalen Server Infrastruktur, Profilen sowie Spiegel-Repositorien wie diese von einem Benutzer definiert wurden. Eine ausführliche Referenz der lokalen Einstellungen sowie der entsprechenden Properties finden Sie unter http://maven.apache.org/ref/2.0.9/maven-settings/settings.html. 13.2.3. Properties der Umgebungsvariablen Umgebungsvariablen werden mit einem vorangestellten env.* eingeleitet. Einige der interessanteren Umgebungsvariablen sind in der folgenden Liste aufgeführt: env.PATH Enthält den aktuellen Pfad (PATH)in welchem Maven läuft. PATH enthält eine 342
  • 362. Properties and Ressource Filterung Liste von Verzeichnissen, in welchen ausführbare Skripte und Programme gesucht werden sollen. env.HOME (Unter *nix Systemen) zeigt diese Variable auf das “Heimatverzeichnis” des Benutzers. Besser und portabler ist es statt dessen ${user.home} einzusetzen. env.JAVA_HOME Enthält das Java Installationsverzeichnis. Dieses wird entweder auf einen Java Development Kit (JDK) oder auf eine Java Laufzeitumgebung (JRE) zeigen. Es empfiehlt sich statt dessen ${java.home} zu benutzen. env.M2_HOME Beinhaltet das Maven 2 Installationsverzeichnis Solange die Java System Einstellungen verfügbar sind, sollten Sie diese einsetzen. Um auf das Heimatverzeichnis eines Benutzers zuzugreifen benutzen Sie ${user.home} statt ${env.HOME}. Sie erzeugen damit einen portablerern Build welcher dem Versprechen von Write-One-Run-Anywhere (WORA) der Java Plattform näherkommt. 13.2.4. Java System Properties Maven legt alle Properties von java.lang.System offen. Alle Daten, welche Sie mittels System.getProperty() beziehen können, sind auch für Maven angreifbar. Die nachfolgende Tabelle führt die verfügbaren Properties auf: Table 13.1. Java System Properties System Property Beschreibung java.version Java Laufzeitumgebung Version java.vendor Java Laufzeitumgebung Hersteller java.vendor.url Java Hersteller URL java.home Java Installationsverzeichnis 343
  • 363. Properties and Ressource Filterung System Property Beschreibung java.vm.specification.version Java Virtual Machine Spezifikation Version java.vm.specification.vendor Java Virtual Machine Spezifikation Hersteller java.vm.specification.name Java Virtual Machine Spezifikation Name java.vm.version Java Virtual Machine Implementierung Version java.vm.vendor Java Virtual Machine Implementierung Hersteller java.vm.name Java Virtual Machine Implementierung Name java.specification.version Java Laufzeitumgebung Spezifikation Version java.specification.vendor Java Laufzeitumgebung Spezifikation Hersteller java.specification.name Java Laufzeitumgebung Spezifikation Name java.class.version Java Klassenformat Versionsnummber java.class.path Java Klassenpfad java.ext.dirs Pfad zum Verzeichnis der Erweiterungen os.name Betriebssystem Name os.arch Betriebssystem System Architekture os.version Betriebssystem System Version file.separator Datei Trennzeichen ("/" auf UNIX, "" auf Windows) 344
  • 364. Properties and Ressource Filterung System Property Beschreibung path.separator Pfad Trennzeichen (":" auf UNIX, ";" auf Windows) line.separator Zeilenumbruch ("n" auf UNIX und Windows) user.name Benutzerkonto Name user.home Benutzerverzeichnis (home) user.dir Aktuelles Arbeitsverzeichnis (des Benutzers) 13.2.5. Benuzerdefinierte Properties Zusätzlich zu den implizit von POM, den Maven Einstellungen, Umgebungsvariablen und Java System Einstellungen bereitgestellten Properties haben Sie die Möglichkeit Ihre eigenen beliebigen Properties zu setzen. Properties können als Teil eines POM oder Profil definiert werden. Die Menge der Properties eines POMs oder Maven Profils kann wie alle anderen Properties in Maven angesprochen werden. Benutzerdefinierte Properties können innerhalb einer POM Datei referenziert werden, oder Sie können eingesetzt werden um Ressourcen mittels dem Maven Resource Plugin zu filtern. Hier ein Beispiel wie man ein beliebiges Property innnerhalb einer POM Datei definiert: Example 13.1. Definition eines benutzerdefinierten Property im POM <project> ... <properties> <arbitrary.property.a>This is some text</arbitrary.property.a> <hibernate.version>3.3.0.ga</hibernate.version> </properties> ... <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> 345
  • 365. Properties and Ressource Filterung <version>${hibernate.version}</version> </dependency> </dependencies> ... </project> Das vorgehende Beispiel definiert zwei Properties: arbitrary.property.a und hibernate.version. hibernate.version wird innerhalb einer Abhängigkeitsdefinition eingesetzt. Der Einsatz des Punkt-Zeichens (.) als Trennzeichen in Propertynamen hat sich als gängige Schreibweise für Properties durchgesetzt; Für Maven ist „hibernate.version“ lediglich ein Schlüssel um den Wert „3.3.0.ga“ aufzufinden. Example 13.2. Definition eines benutzerdefinierten Property innerhalb einer Profildefinition im POM <project> ... <profiles> <profile> <id>some-profile</id> <properties> <arbitrary.property>This is some text</arbitrary.property> </properties> </profile> </profiles> ... </project> Das vorangegangene Beispiel verdeutlicht wie man ein benutzerdefiniertes Property innerhalb einer Profildefinition eines Maven POM definiert. Weitere Details und Informationen bezüglich benutzerdefinierter Properties und Profilen finden sie im Chapter 11, Build Profiles (Kapitel 11: Build Profile). 13.3. Ressource Filterung Sie können Maven auch dazu einsetzen, Variablen innerhalb von Projektressourcen zu ersetzen. Sobald Ressourcen Filterung aktiviert ist, durchsucht Maven 346
  • 366. Properties and Ressource Filterung Ressourcen nach Property Referenzen eingeschlossen in ${ und}. Sobald es eine derartige Referenz gefunden hat, ersetzt es diese mit dem entsprechenden Wert ganz ähnlich wie dies auch mit den Property Referenzen eines POM geschieht und im letzen Abschnitt beschrieben wurde. Diese Möglichkeit ist insbesondere dann hilfreich, wenn es notwendig wird einen Build zu parametrisieren um verschiede Konfigurationen zu übergeben, abhängig von der entsprechenden Zielplattform. Oftmals wird eine Datei .properties oder ein XML Dokument unter /src/main/resources die Definition externer Ressource wie Datenbankverbindungen oder Netzwerkparameter enthalten, welche abhängig von der Zielplatform unterschiedlich konfiguriert werden müssen. So hat z.B. ein System welches von einer Datenbank lesen muss eine Datei welche die JDBC URL zusammen mit den entsprechenden Parametern für den Datenbankzugriff angibt. Benutzen Sie eine andere Datenbank während der Entwicklung als in der Produktion (was zu erwarten ist), so können Sie entweder diese Ressourcedefinition mittels einer Technologie wie z.B. JNDI externalisieren, oder aber Sie generieren einen Build welcher es versteht, die Zielplattform abhängigen Werte mit den entsprechenden Werten zu setzen. Unter Einsatz von Maven Ressource Filterung können Sie Maven Properties setzen und diese dann mittels Maven Profilen entsprechend der benötigten Zielplattform definieren. Um diese Möglichkeit zu illustrieren, hier ein Beispiel: gehen Sie davon aus, dass Ihr Projekt auf dem Spring Framework beruht, welches eine BasicDataSource aus dem CommonsDBCP Projekt konfiguriert. Ihr Projekt wird also unter src/main/resources eine Datei mit dem Namen applicationContext.xml ablegen, welche die in Example 13.3, “Maven Properties aus einer Ressource Datei referenzieren” (Beispiel 13.3: Maven Properties aus einer Ressource Datei referenzieren) ausgeführten Daten enthält. Example 13.3. Maven Properties aus einer Ressource Datei referenzieren <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.5.xsd"> <bean id="someDao" class="com.example.SomeDao"> <property name="dataSource" ref="dataSource"/> 347
  • 367. Properties and Ressource Filterung </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> Ihr Programm würde diese Datei zur Laufzeit lesen, und Ihr Build wird Referenzen auf Properties wie z.B. jdbc.url oder jdbc.username mit den Werten welche Sie in Ihrer pom.xml-Datei setzen ersetzen. Ressource Filterung ist standardmässig ausgeschaltet, um die unbewusste und ungewollte Filterung von Werten zu verhindern. Um den Ressource Filter einzustellen, müssen Sie das abgeleitete Element resource innerhalb ihrer POM Datei setzen. Example 13.4, “Definieren von Variablen und Aktivierung von Ressource Filterung” (Beispiel 13.4: Definieren von Variablen und Aktivierung von Ressource Filterung) illustriert ein POM welches die Variablen aus Example 13.3, “Maven Properties aus einer Ressource Datei referenzieren” (Beispiel 13.3: Maven Properties aus einer Ressource Datei referenzieren) definiert und Ressource Filterung für alle Ressourcen unter src/main/resources aktiviert Example 13.4. Definieren von Variablen und Aktivierung von Ressource Filterung <project> ... <properties> <jdbc.driverClassName> com.mysql.jdbc.Driver</jdbc.driverClassName> <jdbc.url>jdbc:mysql://localhost:3306/development_db</jdbc.url> <jdbc.username>dev_user</jdbc.username> <jdbc.password>s3cr3tw0rd</jdbc.password> </properties> ... <build> <resources> <resource>src/main/resources</resource> <filtering>true</filtering> </resources> </build> 348
  • 368. Properties and Ressource Filterung ... <profiles> <profile> <id>production</id> <properties> <jdbc.driverClassName>oracle.jdbc.driver.OracleDriver</jdbc.driverClassName> <jdbc.url>jdbc:oracle:thin:@proddb01:1521:PROD</jdbc.url> <jdbc.username>prod_user</jdbc.username> <jdbc.password>s00p3rs3cr3t</jdbc.password> </properties> </profile> </profiles> </project> Die vier entsprechenden Variablen werden jeweils innerhalb des Elements properties definiert und Ressource Filterung wird für Ressourcen unter src/main/ressources gesetzt. Da Ressource Filterung standardmässig deaktiviert ist, ist es notwendig für Ihr Projekt explizit den Wert des Elements filtering auf true zu setzen. Wenn Sie nun ein Projekt mit der Ressource aus Example 13.3, “Maven Properties aus einer Ressource Datei referenzieren” (Beispiel 13.3: Maven Properties aus einer Ressource Datei referenzieren) und den POM Einträgen aus Example 13.4, “Definieren von Variablen und Aktivierung von Ressource Filterung” (Beispiel 13.4: Definieren von Variablen und Aktivierung von Ressource Filterung) builden und Sie dann den Inhalt der Ressource unter target/classes ansehen, so sollten Sie dort die folgende, gefilterte Ressource wiederfinden: $ mvn install ... $ cat target/classes/applicationContext.xml ... <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/development_db"/> <property name="username" value="dev_user"/> <property name="password" value="s3cr3tw0rd"/> </bean> ... Das POM aus Example 13.4, “Definieren von Variablen und Aktivierung von Ressource Filterung” (Beispiel 13.4: Definieren von Variablen und Aktivierung von Ressource Filterung) definiert ebenfalls unter dem Element profiles/profile 349
  • 369. Properties and Ressource Filterung ein Profile production, welches das Standardprofil mit Werten welche für eine Produktionsumgebung sinnvoll sind, übersteuert. In diesem ausgeführten Beispiel definiert das POM Standardwerte für die Datenbankverbindung auf eine lokal installierte MySQL™ Instanz. Sollte der Build mit aktiviertem Profil production erstellt werden, so wird Maven das System derart konfigurieren, dass fortan das System gegen eine Oracle Datenbank mit anderen Treibern, URL, Benutzername und Passwort zum Zuge kommt. Wenn sie also den Build mit den oben ausgeführten Beispielen und aktivierten Profil production ausgeführt haben, so sollten Sie unter target/classes eine Ressource mit den unten aufgeführten produktiven Werten auffinden. $ mvn -Pproduction install ... $ cat target/classes/applicationContext.xml ... <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@proddb01:1521:PROD"/> <property name="username" value="prod_user"/> <property name="password" value="s00p3rs3cr3t"/> </bean> ... 350
  • 370. Chapter 14. Maven in Eclipse: m2eclipse 14.1. Einführung Die Eclipse IDE ist heutzutage wohl die Entwicklungsumgebung mit der grössten Verbreitung. Es gibt eine riesige Anzahl verfügbarer Plugins (http://www.eclipseplugincentral.com/) und eine unzählbar grosse Anzahl Organisationen welche deren Software auf Eclipse entwickeln. Einfach ausgedrückt: Eclipse ist allgegenwärtig. Das m2eclipse Projekt bietet eine Maven Unterstützung von innerhalb der Entwicklungsumgebung. In diesem Kapitel werden wir die Möglichkeiten welche uns dieses Werkzeug bietet erkunden. 14.2. m2eclipse Das m2eclipse Plugin (http://m2eclipse.codehaus.org/) bietet eine Mavenintegration für Eclipse. M2eclipse bietet ebenfalls Möglichkeiten die Funktionen von zwei weiteren Plugins, dem Subclipse Plugin (http://subclipse.tigris.org) sowie dem Mylin Plugin (http://www.eclipse.org/mylyn) zu nutzen. Das Subclipse Plugin bietet dem m2eclipse Plugin die Möglichkeit direkt mit einem Subversion Repositorien zu interagieren und das Mylyn Plugin erweitert das m2eclipse Plugin um mit dem weit verbreiteten Mylyn Interface zu zu interagieren. Dieses hilft die Übersicht im Entwicklungszusammenhang zu behalten. Hier eine Reihe Funktionalitäten welche m2eclipse bietet: • Erstellen sowie importieren von Maven Projekten • Abhängigkeitsverwaltung und Integration mit dem Eclipse Klassenpfad • Automatischer Download von Abhängigkeiten und deren Update • Artefakt JavaDoc sowie Quellen Auflösung 351
  • 371. Maven in Eclipse: m2eclipse • Erstellen von Projekten basierend auf Maven Artefakten • Durchsuchen und Suchen in entfernten Maven Repositorien • POM Verwaltung mit automatischem Update der Abhängigkeitsliste • Materialisierung ausgehend von einem Maven POM • Auschecken eines Maven Projektes von mehreren verteilten SCM Repositorien • Anpassen von verschachtelten multi-modularen Maven Projekten auf die Entwicklungsumgebung Eclipse • Integration mit dem Web Tools Projekt (WTP) • Integration mit AspectJ Development Tools (AJDT) • Integration mit Subclipse • Integration mit Mylyn • Formularbasierter POM Editor • Graphische Darstellung des Abhängigkeitsgraphen • GUI Darstellung des Abhängigkeitsbaumes und der aufgelösten Abhängigkeiten Es gibt über diese Liste hinaus gibt es noch viele weitere Funktionalitäten welche m2eclipse bietet. Dieses Kapitel wird Sie in einige der eindrucksvolleren Fähigkeiten einführen, welche derzeit verfügbar sind. Lassen Sie uns damit beginnen, das m2eclipse Plugin zu installieren. 14.3. Installation des m2eclipse Plugins 352
  • 372. Maven in Eclipse: m2eclipse Um das m2eclipse Plugin lauffähig zu installieren, müssen etliche Voraussetzungen erfüllt sein. Sie müssen mindestens auf Eclipse 3.2 aufsetzen, mindestens JDK 1.4 benutzen und sicherstellen, dass Sie auf einem JDK und nicht nur einer Java Runtime (JRE) basieren. Sobald Sie Eclipse sowie einen entsprechenden JDK am Laufen haben, müssen Sie zunächst noch zwei Plugins installieren: Subclipse und Mylyn. 14.3.1. Installieren der Voraussetzungen Sie können die Voraussetzungen zusammen mit m2eclipse installieren: fügen Sie einfach die den Komponennten entsprechenden Update Sites in Eclipse an. Um diese Komponenten dann zu installieren, wählen Sie Hilfe >Software Updates >Suchen und Installieren... Es wird die Update-/Installationsschaltfläche anzeigen. Wählen Sie „nach neuen zu installierenden Features suchen“ und wählen Sei „weiter“. Die Anzeige wird auf „xxx“ wechseln. Wählen Sie „Neuer Remote Site“ und fügen Sie für jede Komponente die entsprechende Site hinzu, stellen Sie sicher dass der entsprechende Eintrag ausgewählt ist. Zum Abschluss wird Eclipse ihnen anbieten die ausgewählten Komponenten zu installieren. Eclipse wird diese dann herunterladen, installieren und konfigurieren. Sollten Sie die neuere Version Eclipse 3.4 (Ganymede) benutzen, so wird die Plugin-Installation leicht von dieser Beschreibung abweichen: unter Ganymede wählen Sie Hilfe > Software Updates und öffnen hiermit den Dialog: „xxx“. Innerhalb dieses Fensters wählen Sie die „Verfügbare Software“ und dann „Site hinzufügen“ welches den Dialog „Site hinzufügen" öffnen wird. Geben Sie die URL des Sites welchen Sie hinzufügen möchten ein und bestätigen mit "OK". Im Dialogfenster „Software Updates and Add-Ons" werden die entsprechend verfügbaren Plugins der hinzugefügten Sites erscheinen. Sie können nun die entsprechenden Module auswählen und installieren. Eclipse wird die entsprechenden Abhängigkeiten auflösen und gegebenenfalls darum bitten die Software Lizenzbedingungen zu akzeptieren. Nach der Installation wird Eclipse Sie wahrscheinlich aufrufen Eclipse einem Neustart zu unterziehen. 14.3.1.1. Installation von Subclipse 353
  • 373. Maven in Eclipse: m2eclipse Um Subclipse zu installieren, benutzen Sie den unten aufgeführten Eclipse Plugin Update Site: • Subclipse 1.2: http://subclipse.tigris.org/update_1.2.x Um andere Versionen von Subclipse zu installieren, oder um weitere Informationen bezüglich des Subclipse Plugin zu erlagen, wenden Sie sich and den Subclipse Projekt Website: http://subclipse.tigris.org/. 14.3.1.2. Installation von Mylyn To install JIRA integration with Mylyn, add the Mylyn extras Eclipse update URL, you'll want to do this if your organization uses Atlassian's JIRA for issue tracking. To install Mylyn use the following update sites: Um die JIRA Integration mittels Mylyn zu installieren, fügen Sie die Update Site der Eclipse Mylyn Extras hinzu. Dies werden Sie insbesondere tun wollen, wenn Ihre Organisation Atlassian JIRA Issue Tracking einsetzt. Um Mylyn zu installieren, benutzen Sie einen der folgenden Update Sites: • Mylyn (Eclipse 3.3): http://download.eclipse.org/tools/mylyn/update/e3.3 • Mylyn (Eclipse 3.4): http://download.eclipse.org/tools/mylyn/update/e3.4 • Mylyn Extras (JIRA Support): http://download.eclipse.org/tools/mylyn/update/extras Um weitere Informationen bezüglich des Mylyn Plugin zu erlagen, wenden Sie sich and den Mylin Projekt Website: http://www.eclipse.org/mylyn/. 14.3.1.3. Installation der AspectJ Development Tools (AJDT) Sollten Sie die Version 0.9.4 von m2eclipse installieren, so werden Sie eventuell auch die beiden Erweiterungen: Web Tools Platform (WTP) sowie AspectJ Development Tools (AJDT) installieren wollen. Um AJDT zu installieren, benutzen Sie: 354
  • 374. Maven in Eclipse: m2eclipse • AJDT (Eclipse 3.3): http://download.eclipse.org/tools/ajdt/33/update • AJDT (Eclipse 3.4): http://download.eclipse.org/tools/ajdt/34/dev/update Um weitere Informationen bezüglich des AspectJ Development Tools zu erlagen, wenden Sie sich and den AJDT Projekt Website: http://www.eclipse.org/ajdt/. 14.3.1.4. Installation der Web Tools Platform (WTP) To install the Web Tools Platform (WTP). Use one of the following update URLs in Eclipse, or just look for the Web Tools Project in the Discovery Site which should already be in your Eclipse remote update sites list. Um die Web Tools Platform (WTP) zu installieren, benutzen Sie die unten stehende Update URL in Eclipse oder suchen Sie das Web Tools Project innerhalb des Discovery Site welcher bereits Teil der Eclipse Remote Update Site Liste sein sollte. • WTP: http://download.eclipse.org/webtools/updates/ Um weitere Informationen bezüglich der Web Tools Platform zu erlagen, wenden Sie sich and den WTP Projekt Website http://www.eclipse.org/webtools/. 14.3.2. Installation von m2eclipse Sobald Sie diese Voraussetzungen erfüllt haben und die oben aufgeführten Komponenten installiert haben, können Sie mit der Installation von m2Eclipse fortfahren. Benutzen Sie hierzu die folgende Update URL: • m2eclipse Plugin: http://m2eclipse.sonatype.org/update/ Sollten Sie den neusten Entwickler Snapshot Release des Plugins bevorzugen, wählen Sie stattdessen die Update Development URL: • m2eclipse Plugin (Development Snapshot): http://m2eclipse.sonatype.org/update-dev/ 355
  • 375. Maven in Eclipse: m2eclipse Um das m2eclipse Plugin zu installieren, wählen Sie Hilfe > Software Update > Suchen und Installieren … Es wird die Update-/Installationsschaltfläche angezeigt. Wählen Sie „nach neuen zu installierenden Features suchen“ und wählen Sei „weiter“. Die Anzeige wird auf „xxx“ wechseln. Wählen Sie „Neuer Remote Site“ und fügen Sie die oben angegebene Site hinzu. Stellen Sie sicher, dass der entsprechende Eintrag ausgewählt ist. Zum Abschluss wird Eclipse ihnen anbieten m2eclipse zu installieren. Eclipse wird die Komponente dann herunterladen, installieren und konfigurieren. Mit der erfolgreichen Installation des Plugins sollten Sie unter Windows > Preferences in der Liste der Optionen einen Eintrag "Maven" finden. 14.4. Aufschalten der Maven Konsole Vor wir nun tatsächlich anfangen die Möglichkeiten des m2eclipse Plugins auszuleuchten, lassen Sie uns zunächst die Maven Konsole aufschalten. Öffnen Sie die Konsolenansicht (Windows > Ansichten > Konsole). Wählen Sie den kleinen Pfeil rechts vom Symbol Konsole öffnen und dort "Maven Konsole"; wie unten dargestellt: Figure 14.1. Aufschalten der Maven Konsole in Eclipse Die Maven Konsole gibt alle Ausgaben wieder, welche normalerweise auf dem Konsolenfenster ausgegeben werden, wenn Maven von der Befehlszeile gestartet wird. Dies ist sehr nützlich um zu sehen was Maven gerade macht, oder um Debugausgaben zu analysieren und Probleme anzusehen. 356
  • 376. Maven in Eclipse: m2eclipse 14.5. Erstellen eines Maven Projekts Beim Einsatz von Maven werden Projekte gewöhlich durch den Einsatz von Maven Archetypen erstellt. Innerhalb von Eclipse werden neue Projekte durch den „Neue Projekte Wizard“ erstellt. Der Eclipse "Neues Projekt"-Wizard bietet eine Fülle verschiedener Vorlagen um neue Projekte zu erstellen! Das m2eclipse Plugin baut auf diesen Wizard auf, und bietet die folgenden zusätzlichen Möglichkeiten: • Auschecken eines Maven Projekts aus einem SCM Repository • Erstellen eines Maven Projekts auf der Basis eines Maven Archetyps • Erstellen einer Maven POM Datei Wie in Figure 14.2, “Erstellen eines Neuen Projekts mit Hilfe des m2eclipse Wizards” (Abbildung 14.2: "Erstellen eines Neuen Projekts mit Hilfe des m2eclipse Wizards") dargestellt, sind alle drei Möglichkeiten für einen Entwickler von Interesse. Lassen Sie uns diese Möglichkeiten im Einzelnen genauer ansehen. 357
  • 377. Maven in Eclipse: m2eclipse Figure 14.2. Erstellen eines Neuen Projekts mit Hilfe des m2eclipse Wizards 14.5.1. Auschecken eines Maven Projektes von einem SCM Repository m2eclipse bietet die Möglichkeit Projekte direkt aus einem SCM Repository auszuchecken. Geben Sie einfach die SCM Daten des Projektes ein und m2eclipse wird das Projekt an den von Ihnen angegebenen Ort auschecken. Dies ist in Figure 14.3, “Auschecken eines Projekts aus Subversion” (Abbildung 14.3: 358
  • 378. Maven in Eclipse: m2eclipse "Auschecken eines Projekts aus Subversion" beispielhaft dargestellt: Figure 14.3. Auschecken eines Projekts aus Subversion Es bestehen zusätzlichen Optionen als Teil dieses Dialogs. Diese Ermöglichen Ihnen eine bestimmte Version auszuwählen, entweder indem Sie im Katalog des Subversion Repository auswählen, oder einfach durch die manuelle Eingabe 359
  • 379. Maven in Eclipse: m2eclipse Revisionsnummer. Diese Funktionalitäten bauen auf Funktionen des Subclipse Plugins auf, um mit dem Subclipse Repository zu interagieren. Darüber hinaus unterstützt das m2eclipse Plugin die folgenden SCM Anbieter: • Bazaar • Clearcase • CVS • git • hg • Perforce • Starteam • Subversion • Synergy • Visual SourceSafe 14.5.2. Erstellen eines Maven Projekts auf der Basis eines Maven Archetyps m2eclipse ermöglicht ein Maven Projekt auf der Basis eines Maven Archetyps zu erstellen. Es bestehen sehr viele verschiedene Maven Archetypen, diese werden bereits in der Auswahlliste des m2eclipse aufgeführt werden, wie aus der Figure 14.4, “Erstellen eines Neuen Projektes auf der Basis eines Maven Archetyps” (Abbildung 14.4: "Erstellen eines Neuen Projektes auf der Basis eines Maven Archetyps" ersichtlich wird: 360
  • 380. Maven in Eclipse: m2eclipse Figure 14.4. Erstellen eines Neuen Projektes auf der Basis eines Maven Archetyps Die Liste der aufgeführten Muster aus Figure 14.4, “Erstellen eines Neuen Projektes auf der Basis eines Maven Archetyps” (Abbildung 14.4: "Erstellen eines Neuen Projektes auf der Basis eines Maven Archetyps") ist eine Aufzählung welche von einem Werkzeug genannt Nexus Indexer erzeugt wurde. Nexus ist ein Repository Manager auf welchen in Chapter 16, Repository Management with Nexus (Kapitel 16: "Repository Manager") näher eingegangen wird. Der Nexus Indexer ist eine Datei die einen Index über das gesamte betrachtete Maven 361
  • 381. Maven in Eclipse: m2eclipse Repository darstellt. m2eclipse benutzt diese um alle bereitstehenden Archetypen des gesamten Maven Repository darzustellen. Als dieses Kapitel das letzte Mal angepasst wurde, wurden bereits ca. 90 verschiedene Muster im Archetypdialog dargestellt. Darunter besonders erwähnenswert sind: • Die Standardarchetypen von Maven zur Erstellung von • Maven Plugins • Einfache Web Anwendungen • Einfache Projekte • Neue Maven Archetypen • Databinder Archetypen (datengetriebene Wicket Anwendungen) unter net.databinder • Apache Cocoon Archetypen unter org.apache.cocoon • Apache Directory Server Archetypen unter org.apache.directory.server • Apache Geronimo Archetypen unter org.apache.geronimo.buildsupport • Apache MyFaces Archetypen unter org.apache.myfaces.buildtools • Apache Tapestry Archetypen unter org.apache.tapestry • Apache Wicket Archetypen unter org.apache.wicket • AppFuse Archetypen unter org.appfuse.archetypes • Codehaus Cargo Archetypen unter org.codehaus.cargo • Codehaus Castor Archetypen unter org.codehaus.castor • Groovy-based Maven Plugin Archetypen (deprecated)24 unter 362
  • 382. Maven in Eclipse: m2eclipse org.codehaus.mojo.groovy • Jini Archetypen • Mule Archetypen unter org.mule.tools • Objectweb Fractal Archetypen unter org.objectweb.fractal • Objectweb Petals Archetypen unter org.objectweb.petals • ops4j Archetypen unter org.ops4j • Parancoe unter org.parancoe • slf4j Archetypen unter org.slf4j • Springframework OSGI sowie Web Services Archetypen unter org.springframework • Trails Framework Archetypen unter org.trailsframework 24 And these were just the archetypes that were listed under the Nexus Indexer Catalog, if you switch Catalogs you'll see other archetypes. While your results may vary, the following additional archetypes were available in the Internal Catalog: Und dies sind nur die Archetypen welche vom Nexus Indexer Katalog afugeführt werden, sollten Sie einen anderen Katalog auswählen, so werden Sie weitere Archetypen kennen lernen. Die Resultate werden unterschiedlich von Fall zu Fall unterschiedlich sein, im internen Katalog waren zum Beispiel folgende zusätzlichen Muster verfügbar: • Atlassian Confluence Plugin Archetype unter com.atlassian.maven.archetypes 24 [4] Verzichten Sie auf den Einsatz des Groovy Maven Plugin des Codehaus Mojo Projekt. Jason Dillon hat inzwischen die Groovy Maven Integrationsbemühungen in das Groovy Projekt von Codehaus verschoben. Für weitere Informationen wenden Sie sich an http://groovy.codehaus.org/GMaven. 363
  • 383. Maven in Eclipse: m2eclipse • Apache Struts Archetypen unter org.apache.struts • Apache Shale Archetypen unter org.apache.shale Ein Katalog ist einfach eine Referenz auf einen Repository Index. Sie können die Menge aller Kataloge welche vom m2eclipse Plugin berücksichtigt werden verwalten, indem Sie neben der Drop Down Box des Kataloges den Knopf Configure… auswählen. Sollten Sie Ihren eigenen Archetyp einem Katalog zufügen wollen, so wählen Sie Add Archetype … Sobald Sie einen Archetypen ausgewählt haben, wird Maven den entsprechenden Artefakten aus dem Maven Repository holen und ein entsprechendes Eclipse Projekt erstellen. 14.5.3. Erstellen eines Maven Moduls m2eclipse bietet auch die Möglichkeit ein Maven Modul zu erstellen. Dies ist beinahe identisch zur Erstellung eines Maven Projektes, da es ebenfalls ein Maven Projekt auf der Basis eines Archetypen erstellt. Jedoch ist ein Maven Modul ein Unterprojekt zu einem anderen Maven Projekt, das gemeinhin als übergeordnetes oder elterliches Projekt bezeichnet wird. 364
  • 384. Maven in Eclipse: m2eclipse Figure 14.5. Erstellen eines neuen Maven Modules Beim Erstellen eines neuen Maven Moduls müssen Sie ein übergeordnetes Projekt auswählen, welches bereits innerhalb der Entwicklungsumgebung existiert. Die Auswahl des Buttons Browse zeigt eine Liste der bereits bestehenden Projekte wie in Figure 14.6, “Auswahl eines übergeordneten Projektes eines neues Maven Modul” (Abbildung 14.6: "Auswahl eines übergeordneten Projektes eines neues 365
  • 385. Maven in Eclipse: m2eclipse Maven Modul") dargestellt. Figure 14.6. Auswahl eines übergeordneten Projektes eines neues Maven 366
  • 386. Maven in Eclipse: m2eclipse Modul Nach der Auswahl eines übergeordneten Projektes aus der Liste werden Sie auf das "Neues Maven Modul"-Fenster zurückgeleitet, dort ist nun das Feld Übergeordnetes Projekt ausgefüllt wie in Figure 14.5, “Erstellen eines neuen Maven Modules” (Abbildung 14.5: "Erstellen eines neuen Maven Moduls") dargestellt. Mit der Auswahl von Weiter werden Sie zur Liste der Maven Archetypen wie in Section 14.5.2, “Erstellen eines Maven Projekts auf der Basis eines Maven Archetyps” (14.5.2: "Erstellen eines Maven Projekts auf der Basis eines Maven Archetyp") ausgeführt. Sie können dabei auswählen auf der Basis welchen Musters das neue Maven Modul erstellt werden soll. 14.6. Erstellen einer Maven POM Datei Eine weitere wichtige Funktionalität des m2eclipse-Plugins ist es POM-Dateien zu erstellen. m2eclipse stellt einen Wizard bereit, um für ein bestehendes Projekt eine POM Datei zu erstellen. Dieser POM Wizard wird in Figure 14.7, “Abbildung 14.7: Erstellen eines neuen POMs” (Abbildung 14.7: Erstellen eines neuen POMs) dargestellt: 367
  • 387. Maven in Eclipse: m2eclipse Figure 14.7. Abbildung 14.7: Erstellen eines neuen POMs Zu einem bestehenden Projekt ein neues Maven POM zu erstellen ist einfach: Auswählen des Projektes, Eingabe der groupID, artefactID sowie version, Auswahl des Paketierungstyps und Vergabe eines Namens in den dafür vorgesehenen Feldern des m2eclipse Wizards. Wählen Sie weiter um Abhängigkeiten zuzufügen. 368
  • 388. Maven in Eclipse: m2eclipse Figure 14.8. Hinzufügen von Abhängigkeiten zu einer neuen POM Datei Wie Sie leicht aus Figure 14.8, “Hinzufügen von Abhängigkeiten zu einer neuen POM Datei” (Abbildung 14.8: "Hinzufügen von Abhängigkeiten zu einer neuen POM Datei") ersehen, sind zunächst keine Abhängigkeiten enthalten. Wählen Sie zufügen um das zentrale Maven Repository nach Abhängigkeiten zu durchsuchen. Dies ist in der nächsten Abbildung, Figure 14.9, “Durchsuchen des Zentralen Repositories nach Abhängigkeiten” (Abbildung 14.9: "Durchsuchen des Zentralen 369
  • 389. Maven in Eclipse: m2eclipse Repositories nach Abhängigkeiten") dargestellt: Figure 14.9. Durchsuchen des Zentralen Repositories nach Abhängigkeiten Nach Abhängigkeiten zu suchen ist einfach; geben Sie die groupId des benötigten Artefakten ein. Figure 14.9, “Durchsuchen des Zentralen Repositories nach 370
  • 390. Maven in Eclipse: m2eclipse Abhängigkeiten” (Abbildung 14.9: "Durchsuchen des Zentralen Repositories nach Abhängigkeiten") stellt die Abfrage nach org.apache.commons dar, wobei commons-vfs geöffnet ist, um zu sehen welche Versionen verfügbar sind. Die Auswahl der 1.1-SNAPSHOT Version von commons-vfs und nachfolgende Auswahl von Ok führt Sie zurück in den Dialog zur Auswahl der Abhängigkeiten, wo Sie dann weitere Abhängigkeiten hinzufügen können, oder via Abschliessen das POM erstellen können. Bei der Suche nach Abhängigkeiten macht m2eclipse Gebrauch von dem gleichen Nexus Repository Index, welcher auch von Nexus Index Manager aus Chapter 16, Repository Management with Nexus (Kapitel 16: "Repository Manager") benutzt wird. Jetzt, da Sie die Funktionalität welche m2eclipse zur Erstellung eines neuen Projektes bietet gesehen haben, lassen Sie uns die verwandten Funktionen zum Import von Projekten ansehen: 14.7. Importieren von Maven Projekten m2eclipse bietet dreierlei Möglichkeiten Maven Projekte in Eclipse zu importieren: • Importieren eines existierenden Maven Projekts • Auschecken eines Maven Projektes aus einem SCM Repository • Materialisieren eines Maven Projektes Figure 14.10, “Importieren eines Maven Projektes” (Abbildung 14.10: "Importieren eines Maven Projektes") stellt den Wizard zum Import von Projekten mit den zugehörigen Optionen, welche Maven durch das m2eclipse Plugin bereitstellt, dar. 371
  • 391. Maven in Eclipse: m2eclipse Figure 14.10. Importieren eines Maven Projektes Die Dialogbox aus Figure 14.10, “Importieren eines Maven Projektes” (Abbildung 14.10: "Importieren eines Maven Projektes") kann über Datei > Importieren und anschliessendem Filtern mit dem Schlüsselbegriff maven, innerhalb von Eclipse aufgerufen werden. Wie oben erwähnt bieten sich drei Möglichkeiten ein Maven Projekt zu in Eclipse zu importieren: Maven Projekte Importieren, Auschecken eines Maven Projektes aus einem Repository und Materialisieren von Maven Projekten. 372
  • 392. Maven in Eclipse: m2eclipse Importieren eines Maven Projektes aus einem SCM Repository ist identisch zur Funktionalität "Erstellen eines Maven Projektes aus einem SCM Repository" welche vorgängig Diskutiert wurde; eine weitere Behandlung ist überflüssig. Lassen Sie uns weitergehen zu den zwei verbleibenden Möglichkeiten ein Maven Projekt zu importieren. 14.7.1. Importiren eines Maven Projektes m2eclipse kann ein Projekt mit bestehender pom.xml-Datei importieren. Durch Angabe des Verzeichnisses in welchem das Maven Projekt abgelegt wurde, ist m2eclipse in der Lage alle Maven POMs des Projekts zu finden und eine hierarchische Liste dieser aufzustellen. Diese ist in Figure 14.11, “Importieren eines multi-modularen Maven Projektes ” (Abbildung 14.11: "Importieren eines multi-modularen Maven Projektes") dargestellt: 373
  • 393. Maven in Eclipse: m2eclipse Figure 14.11. Importieren eines multi-modularen Maven Projektes Figure 14.11, “Importieren eines multi-modularen Maven Projektes ” (Abbildung 14.11: "Importieren eines multi-modularen Maven Projektes") zeigt die Darstellung des zu importierenden Projektes. Bitte beachten Sie, dass alle POMs des Projektes in hierarchischer Darstellung wiedergegeben werden. Das erlaubt Ihnen auf einfache Art und Weise genau auszuwählen, welche POMs (und damit Projekte) Sie in Eclipse importieren möchten. Haben Sie erst einmal die Projekte welche Sie importieren möchten ausgewählt, nimmt Ihnen m2eclipse den Import 374
  • 394. Maven in Eclipse: m2eclipse und das Builden der Projekte mit der Unterstützung von Maven ab. 14.7.2. Materialisieren eines Maven Projektes Eine weitere Funktionalität welche m2eclipse bietet ist die Materialisierung eines Projektes. Materialisierung ist ähnlich dem Auschecken eines Projektes aus einem SCM Repository, jedoch, anstatt die URL des Repository manuell einzugeben, wird diese aus der obersten POM Datei des Projektes gelesen. Sie können diese Funktionalität dazu benutzen, Projekte aus nichts weiter als einer pom.xml Datei, welche die entsprechenden Elemente enthält, zu erstellen. Mit dieser Möglichkeit ist es Ihnen gegeben, das Maven Repository zu durchstreifen und die enthaltenen Projekte in Eclipse zu materialisieren. Dies ist insbesondere nützlich, sollte Ihr Projekt von einer Open Source Library abhängig ist, und Sie versuchen an dessen Quellen zu gelangen. Statt mühsam den Projekt-Website herauszufinden und festzustellen, wie Sie diese aus dem Repository auschecken können, benutzen Sie einfach m2eclipse um das Projekt zu materialisieren. Figure 14.12, “Materialisieren eines Maven Projektes” (Abbildung 14.12: "Materialisieren eines Maven Projektes") stellt den Wizard nach der Auswahl des zu materialisierenden Projekts dar: 375
  • 395. Maven in Eclipse: m2eclipse Figure 14.12. Materialisieren eines Maven Projektes Beachten Sie, dass die Dialogbox der Maven Artefakte in Figure 14.12, “Materialisieren eines Maven Projektes” (Abbildung 14.12: "Materialisieren eines Maven Projektes") leer ist. Dies ist so, da noch keine Projekte ausgewählt wurden. Um ein Projekt hinzuzufügen müssen Sie auf der rechten Seite den Button 376
  • 396. Maven in Eclipse: m2eclipse "Hinzufügen" auswählen und einen Abhängigkeit aus dem zentralen Maven Repository auswählen. Dies ist in Figure 14.13, “Auswahl eines Artefakten zum Materialisieren” (Abbildung 14.13: "Auswahl eines Artefakten zum Materialisieren"), dargestellt. Figure 14.13. Auswahl eines Artefakten zum Materialisieren 377
  • 397. Maven in Eclipse: m2eclipse Auf die Eingabe einer Auswahl hin werden die möglichen Abhängigkeiten im Maven Repository gesucht. Nach wenigen Momenten Indexierung des lokalen Repositories wird eine Liste der möglichen Artefakte wiedergegeben. Wählen Sie den entsprechendne Kandidaten eines Projektes aus, so dass dieser zur Liste hinzugefügt werden kann, wie dies in Figure 14.14, “Materialisieren von Apache Camel” (Abbildung 14.14: "Materialisieren von Apache Camel") gezeigt wird. 378
  • 398. Maven in Eclipse: m2eclipse Figure 14.14. Materialisieren von Apache Camel Nach dem hinzufügen der Abhängigkeit bietet Ihnen das m2eclipse die Möglichkeit alle Projekte des Artefaktes auszuchecken. 14.8. Starten von Maven Builds m2eclipse wird auch die Menüs „Run as …“ sowie „Debug as … “ anpassen, um Ihnen zu ermöglichen Maven Builds zu starten. Figure 14.15, “Starten eines Eclipse Builds durch „Run as…“ ” (Abbildung 14.15: "Starten eines Eclipse Builds durch „Run as…“ ") stellt das „Run as…“ Menü eines m2eclispse Projektes dar. Ausgehend von diesem Menü können Sie eine Anzahl Maven Lebenszyklusphasen wie z. B. clean, install und package direkt starten. Sie können auch den Run-Konfigurationsdialog aufrufen und dort spezifische Maven Buildparameter und/oder weitere Optionen setzen. 379
  • 399. Maven in Eclipse: m2eclipse Figure 14.15. Starten eines Eclipse Builds durch „Run as…“ Beabsichtigen Sie einen Maven Build mit erweiterter Konfiguration zu erstellen, so können Sie Run Configuration… auswählen und einen neuen Maven Build erstellen. Figure 14.16, “Konfiguration eines Maven Builds mittels "Run Configurations..." ” (Abbildung 14.16: "Konfiguration eines Maven Builds mittels "Run Configurations..." ") stellt den Run Dialog vor 380
  • 400. Maven in Eclipse: m2eclipse Figure 14.16. Konfiguration eines Maven Builds mittels "Run Configurations..." Der “Run Configuration ...” Dialog erlaubt es, mehrere Goals sowie Profile darzustellen, es werden Auswahlmöglichkeiten wie „Test auslassen (skip test)“ oder „Update Snapshots“ und erlaubt Ihnen Parameter ausgehend vom denen des Projektes bis hin zur unterliegenden Java Runtime Umgebung (JRE) oder den Umgebungsvariablen zu setzen. Sie können diese Dialogseite benutzen um jeden beliebigen massgeschneiderten Maven Build mittels m2eclipse zu starten. 381
  • 401. Maven in Eclipse: m2eclipse 14.9. Mit Maven Projekten arbeiten Das m2eclipse bietet auch eine Reihe Funktionalitäten um mit Maven Projekten zu arbeiten sobald diese in Eclipse geladen sind. Viele dieser Funktionen erleichtern das Arbeiten mit Maven, lassen Sie uns diese näher ansehen: Im vorhergehenden Abschnitt materialisierte ich ein Projekt und wählte ein Unterprojekt von Apache Camel aus; camel-core. Wie werden die verschiedenen Möglichkeiten anhand dieses Projektes verdeutlichen. Wählen Sie mit der rechten Maustaste den Menüpunkt Maven auf dem camel-core Projekt, so können Sie die verfügbaren Maven Funktionen sehen. Figure 14.17, “Verfügbare Maven Funktionalitäten” (Abbildung 14.17: "Verfügbare Maven Funktionen") stellt dies dar: 382
  • 402. Maven in Eclipse: m2eclipse Figure 14.17. Verfügbare Maven Funktionalitäten Notice in Figure 14.17, “Verfügbare Maven Funktionalitäten” the available Maven features for the camel-core project, including: Beachten Sie in Figure 14.17, “Verfügbare Maven Funktionalitäten” (Abbildung 14.17: "Verfügbare Maven Funktionalitäten") die verfügbaren Möglichkeiten des camel-core Projektes, diese umfassen 383
  • 403. Maven in Eclipse: m2eclipse • Hinzufügen von Abhängigkeiten sowie Plugins • Updaten von Abhängigkeiten, Snapshots sowie Quellverzeichnissen • Erstellen eines Maven Moduls • Herunterladen von Quelldateien • Öffnen von Projekt URLs wie z. B. der Projekt Web Page, dem Issue Tracker, SCM oder Continuous Integration Werkzeugen. • Zuschalten/Abschalten der Workspace Auflösung, verschachteltem Modul- und Abhängigkeitsmanagement Diese Funktionalitäten können uns Zeit sparen, und so lassen Sie uns diese genauer ansehen. 14.9.1. Zufügen und Updaten von Abhängigkeiten und Plugins Nehmen wir einmal an, wir wollen zum camel-core POM eine Abhängigkeit oder ein Plugin hinzufügen. Der Demonstration halber fügen wir commons-lang als Abhängigkeit hinzu. (Da die Funktionalität zum Hinzufügen von Abhängigkeiten und Plugins genau dieselbe ist, werden wir beispielhaft lediglich eine Abhängigkeit hinzufügen). m2eclipse bietet zwei Möglichkeiten eine Abhängigkeit dem Projekt zuzufügen. Die erste ist, die pom.xml-Datei direkt durch hinzufügen des entsprechenden XML Elements abzuändern. Der Nachteil dieses Ansatzes ist, dass Sie bereits die Informationen bezüglich der Artefakte im Detail wissen müssen, oder alternativ die Funktionen welche im nächsten Abschnitt dargestellt werden benutzen müssten um diese in den Repository Indexen zu finden. Der Vorteil ist, dass nach einer manuellen Änderung und Speicherung des Projekt POM der Maven Abhängigkeits-Container automatisch die Neuerungen reflektiert. Figure 14.18, “Manuelles Zufügen einer Abhängigkeit zum Projekt POM” (Abbildung 14.18: 384
  • 404. Maven in Eclipse: m2eclipse "Manuelles Zufügen einer Abhängigkeit zum Projekt POM") zeigt, wie ich eine Abhängigkeit zu commons-lang zum POM der camel-console zugefügt habe und der Maven Abhängigkeits-Container dies automatisch reflektiert und somit darstellt: Figure 14.18. Manuelles Zufügen einer Abhängigkeit zum Projekt POM Nach dem manuelen Zufügen des Elements dependency an das POM, zeigt die Statusanzeige in der rechten unteren Ecke die Hintergrundaktion wie in Figure 14.19, “Updaten von Maven Abhängigkeiten” (Abbildung 14.19: "Updaten von Maven Abhängigkeiten") dargestellt, an: Figure 14.19. Updaten von Maven Abhängigkeiten Manuelles Zufügen von Abhängigkeiten funktioniert gut, aber benötigt mehr Arbeit als dies die zweite Möglichkeit erfordert. Diese Möglichkeit ist viel einfacher, sie setzt nicht voraus, dass Sie irgendwelche Informationen über die der groupId der Artefakte hinaus haben. Figure 14.20, “Suchen einer Abhängigkeit” (Abbildung 14.20: "Suchen einer Abhängigkeit") stellt dies dar: 385
  • 405. Maven in Eclipse: m2eclipse Figure 14.20. Suchen einer Abhängigkeit Durch eintragen einer groupId in das Abfragefeld durchsucht m2eclipse die Indexe der Repositorien sowie die Versionen der Artefakte welche sich bereits im lokalen Maven Repository befinden. Dies ist die bevorzugte Möglichkeit, da es enorme Zeitersparnisse birgt. Mit m2eclipse gehört die langwierige Suche durch die zentralen Maven Repositorien der Vergangenheit an. 386
  • 406. Maven in Eclipse: m2eclipse 14.9.2. Erstellen eines Maven Modules m2eclipse bringt eine enorme Erleichterung zur Erstellung eines verschachtelten Unterprojektes innerhalb eines multi-modularen Maven Projektes. Sollten Sie ein übergeordnetes Maven Projekt bearbeiten und Sie wollen ein Unterprojekt erstellen, so machen Sie einen Rechts-Click auf dem Projekt, gehen Sie zum Maven Menü und wählen „Neues Maven Modul Projekt". m2eclipse wird Sie durch die Schritte zum Erstellen eines Untermoduls führen, das POM erstellen, das elterliche POM anpassen und in den Bezügen reflektieren. Vor es m2eclipse gab war es sehr schwierig, innerhalb von Eclipse mit hierarchisch geschachtelten Projektbäumen zu arbeiten. Mittels m2eclipse wurden die darunterliegenden Beziehungen zwischen Über und Unterprojekten in die Entwicklungsumgebung eingebunden. 14.9.3. Herunterladen der Quelldatei(en) Sollte das zentrale Maven Repository eine Quelldatei der Artefakten beinhalten, so können Sie diese mittels Maven herunterladen und in Eclipse bereitstellen. Beim Versuch eine komplexe Anwendung zu Debuggen, ist es manchmal äusserst nützlich, kann man den zu einem bestehenden externen Quellcode einer Abhängigkeit durch einen einfachen Rechts-Click herunterladen und zu debuggen. Wählen Sie diese Funktion, so wird Maven für Sie versuchen dies zu tun. Ist es nicht möglich, so sollten Sie den Verantwortlichen des Projektes darauf aufmerksam machen, dass dieser im zentralen Maven Repository die entsprechenden Quelldateien hinterlegen. 14.9.4. Öffnen von Projektseiten Das Maven POM enthält einige sehr nützliche URLs welche einen Entwickler interesieren mögen: Diese sind die Projekt Webseite, der Link auf das SCM Repository, eine URL auf eine Continuous Integrations Instanz (Dienst wie z.B. Hudson) sowie eine URL, welche auf das entsprechende Issue Tracking System zeigt. Sind diese Variablen im POM gesetzt, so öffnet m2eclipse die entsprechenden Seiten innerhalb eines Browserfensters. 387
  • 407. Maven in Eclipse: m2eclipse 14.9.5. Auflösen von Abhängigkeiten Sie können ein Projekt so konfigurieren, dass dieses die Abhängigkeiten innerhalb Ihres Workspace auflöst. Das hat den Nebeneffekt, dass es unter Umständen das Verhalten ändert wie Maven einen Artefakten findet. Sollte ein Projekt so konfiguriert sein, dass es Abhängigkeiten innerhalb eines Workspaces auflöst, müssen diese Artefakte nicht in Ihrem lokalen Repository vorhanden sein. Nehmen wir einmal an, Projekt A und Projekt B sind beide im gleichen Eclipse Workspace vorhanden. Ist Workspace Auflösung ausgestellt, so wird die Abhängigkeit nur aufgelöst, wenn die entsprechenden Artefakte in Ihrem lokalen Repository aufzufinden sind. Ist Workspace Auflösung hingegen aktiviert, so wird m2eclipse die Abhängigkeiten innerhalb Ihres Workspaces auflösen. In anderen Worten, ist Workspace Auflösung aktiviert, so müssen lokale Projekte nicht in Ihrem lokalen Repository vorhanden sein um sich aufeinander zu beziehen. Sie können auch das Abhängigkeitsmanagement ausschalten. Das hat dann den Nebeneffekt, dass es m2eclipse anweist, aufzugeben zu versuchen, den Projektklassenpfad zu verwalten, und wird den Maven Abhängigkeits Container aus Ihrem Projekt entfernen. Sollten Sie diese Option wählen, so sind Sie letztendlich mit der Verwaltung des Projektklassenpfades auf sich gestellt. 14.10. Arbeiten mit den Maven Repositorien m2eclipse bietet auch einige Werkzeuge um die Arbeit mit Maven Repositorien zu erleichtern. Insbesondere stehen Werkzeuge mit folgender Funktionalität bereit: • Suchen von Artefakten • Suchen von Java Klassen • Indexierung von Maven Repositorien 14.10.1. Suchen von Maven Artefakten sowie Java 388
  • 408. Maven in Eclipse: m2eclipse Klassen m2eclipse fügt dem Navigationsmenü von Eclipse weitere Einträge hinzu, welche die Suche nach Maven Artefakten sowie Java Klassen erleichtern. Diese Funktionalität wird durch einen Aufruf innerhalb des Menüs ausgelöst, und ist in Figure 14.21, “Suchen von Artefakten und Klassen” (Abbildung 14.21: "Suchen von Artefakten und Klassen") dargestellt: 389
  • 409. Maven in Eclipse: m2eclipse Figure 14.21. Suchen von Artefakten und Klassen Beachten Sie in Figure 14.21, “Suchen von Artefakten und Klassen” (Abbildung 14.21: "Suchen von Artefakten und Klassen") im Navigationsmenü von Eclipse die Einträge mit dem Titel: Öffnen eines Maven POM und Maven Typauswahl. Der 390
  • 410. Maven in Eclipse: m2eclipse Eintrag Öffnen eines Maven POM erlaubt Ihnen das Maven Repository nach einem bestimmten POM zu durchsuchen, wie in Abbildung Figure 14.22, “Suche eines POM” (14.22: "Suche eines POM") ersichtlich ist: 391
  • 411. Maven in Eclipse: m2eclipse Figure 14.22. Suche eines POM Nach der Auswahl eines Artefakts und der Bestätigung mit OK wird innerhalb von Eclipse das POM zur Ansicht und Modifikation geöffnet. Dies ist sehr hilfreich, wenn Sie das POM eines Artefakten zur schnellen Einsicht ansehen wollen. Der zweite Eintrag im Navigationsmenü von m2eclipse ist „Open Type from Maven“. Diese Funktion erlaubt es Ihnen in einem entfernten Repository nach Java Klassen zu suchen. Beim Öffnen des Dialogfensters geben Sie versuchsweise einmal ‚factory bean’ ein, und Sie werden sehen wie viele Klassen mit diesem Namen gefunden werden! Dies ist in Figure 14.23, “Durchsuchen eines Repository nach einer Java Klasse” (Abbildung 14.23: "Durchsuchen eines Repository nach einer Java Klasse") dargestellt. 392
  • 412. Maven in Eclipse: m2eclipse Figure 14.23. Durchsuchen eines Repository nach einer Java Klasse Dies ist eine grosse zeitliche Entlastung, denn bislang bedeutete die Suche nach 393
  • 413. Maven in Eclipse: m2eclipse einer Java Klasse eine zeitraubende Suche innerhalb der einzelnen Artefakte eines Repositories. Wenn Sie heute eine bestimmt Klasse suchen, starten Sie einfach Eclipse, gehen zum Navigationsmenü und suchen nach der entsprechenden Klasse. m2eclipse wird Ihnen eine Liste der Artefakte zurückgeben in welchem diese Klasse erscheint. 14.10.2. Indizierung von Maven Repositorien Die Maven Index Anzeige erlaubt es Ihnen POMs in entfernten Repositorien anzuwählen und in Eclipse auszuwählen. Um dies zu demonstrieren, wählen Sie View > Show View > Other. Geben sie den Begriff „maven“ in die Suchmaske ein. Sie sollten wie in Figure 14.24, “Anzeigen von Maven Indexen im Eclipse View” (Abbildung 14.24: Anzeigen von Maven Indexen im Eclipse View") dargestellt ein View der bestehenden Artefakte zur Auswahl gestellt bekommen: 394
  • 414. Maven in Eclipse: m2eclipse Figure 14.24. Anzeigen von Maven Indexen im Eclipse View Wählen Sie das entsprechende View aus und bestätigen Sie mit OK. Es wird sich das Maven Index View wie in Figure 14.25, “Maven Index View” (Abbildung 14.25: "Maven Index View") dargestellt öffnen. 395
  • 415. Maven in Eclipse: m2eclipse Figure 14.25. Maven Index View Zusätzlich in Figure 14.26, “Auswahl eines POM innerhalb des Index Views” (Abbildung 14.26: "Auswahl eines POM innerhalb des Index Views") dargestellt nach dem manuellen Suchen eines POM: 396
  • 416. Maven in Eclipse: m2eclipse Figure 14.26. Auswahl eines POM innerhalb des Index Views Nach der Auswahl des apache-camel Artefakte wird ein Doppelclick dieses in Eclipse zur Bearbeitung öffnen. Diese Funktion erleichtert die Arbeit mit einem entfernten Repository wesentlich und spart darüber hinaus viel Zeit. Nach all der Zeit welche Sie in den vergangenen Jahren mit solcherlei Beschäftigungen zugebracht haben – Ansehen des Inhaltes eines Repositories in einem Webbrowser, Herunterladen des Artefaktes und anschiessenden Greppens nach Klassen oder POM – sie werden von m2eclipse angenehm überrascht sein. 397
  • 417. Maven in Eclipse: m2eclipse 14.11. Der neue graphische POM Editor Die neuste Inkarnation des m2eclipse Plugin beinhaltet einen graphischen POM Editor, welcher es erlaubt, alle Aspekte eines Projekt POMs in einer einfachen GUI Schnittstelle zu bearbeiten. Um den POM Editor zu öffnen, Klicken Sie auf eine pom.xml Datei. Sollten Sie eine benutzerdefinierte Einstellung eines Editors gewählt haben, und der graphische POM Editor nicht als Standardeditor bestimmt haben, so können Sie die Alternative „Open with … /Maven POM Editor“ wählen um diesen zu starten. Der POM Editor wird Ihnen dann die in Figure 14.27, “Übersichtsanzeige des POM Editors für das Projekt idiom-core” (Abbildung 14.27: "Übersichtsanzeige des POM Editors für das Projekt idiom-core") dargestellte Gesamtübersicht anzeigen. Eine häufige Klage bezüglich Maven ist, dass Maven den Entwickler dazu zwingt sich des öfteren mit langen, unübesichtlichen und hoch komplexen XML Dokumenten zu befassen, wie diese in einem multi modularen Build vorkommen. Wärend wir, die Autoren davon überzeugt sind, dass dies ein kleiner Preis für all die Möglichkeiten und Flexibilität welche Maven bietet ist, so erlaubt der graphische POM Editor die Arbeit mit Maven ohne die das POM untermauernde XML Struktur überhaupt zu kennen. 398
  • 418. Maven in Eclipse: m2eclipse Figure 14.27. Übersichtsanzeige des POM Editors für das Projekt idiom-core Das in Figure 14.27, “Übersichtsanzeige des POM Editors für das Projekt idiom-core” (Abbildung 14.27: "Übersichtsanzeige des POM Editors für das Projekt idiom-core") dargestellte Projekt besitzt einen Artefakten: idiom-core. Es besteht keine groupId oder version, und Angaben zur SCM Verwaltung sind ebenfalls nicht gesetzt. Dies rührt daher, dass idiom-core die allermeisten Informationen vom übergeordneten Projekt idom erbt. Öffnen wir das pom.xml des elterlichen Projekts im graphischen Editor, so würden wir die Anzeige wie in Figure 14.28, “Übersichtsansicht des graphischen Editors des übergeordneten idiom Projekts” (Abbildung 14.28: "Übersichtsansicht des graphischen Editors des übergeordneten idiom Projekts") sehen. Das Symbol des "geöffneten Ordners" an zahlreichen Einträgen des POM Editors 399
  • 419. Maven in Eclipse: m2eclipse zeigt an, dass der entsprechende Eintrag ebenfalls Teil des Eclipse-Workspace ist, und das Jar-Symbol deutet auf Artefakte welche sich auf ein Maven Repository beziehen. Sie können einen solche Eintrag doppelklicken um sich dessen POM Datei im POM Editor anzuzeigen. Dies funktioniert für Module, Abhängigkeiten, Plugins sowie weiteren Artefakten, welche entsprechende Maven Artefakte aufweisen. Unterstrichene Titel in den Seiten des POM Editors weisen auf Hyperlinks hin, welche benutzt werden können um Maven Artefakte ebenfalls im POM Editor zu öffnen. Figure 14.28. Übersichtsansicht des graphischen Editors des übergeordneten idiom Projekts In diesem übergeordneten POM ersichtlich die groupId und version. Beachten Sie bitte, dass ein Grossteil der Detailinformationen welche im POM von idiom-core fehlten, hier gesetzt werden. Die Anzeige des POM Editors beschränkt sich auf Angaben welche im jeweiligen POM gemacht wurden und zeigt keine geerbten 400
  • 420. Maven in Eclipse: m2eclipse Werte. Sollten Sie dennoch das tatsächliche POM des Projektes ansehen wollen, so nutzen Sie die Möglichkeit „tatsächliches POM anzeigen“ von der Toolbar an der oberen rechten Ecke des POM Editors (linke Klammer und Gleichzeichen über dem blauen Maven Projekt M). Es wird Ihnen das tatsächliche POM wie in Figure 14.29, “Tatsächliches POM von idiom-core” (Abbildung 14.29: "Tatsächliches POM von idiom-core") dargestellt, angezeigt. Figure 14.29. Tatsächliches POM von idiom-core In der Ansicht „Tatsächliches POM“ von idiom-core wird das ursprüngliche POM mit den POM der ‚Vorfahren’ (übergeordnet (Eltern), darüber (Grosseltern), etc.) ähnlich zum Aufruf ‚mvn help:effective-pom’ verschmolzen und dargestellt. Da diese Ansicht nun die Kombination vieler verschiedener abhängiger POM ist, ist diese Darstellung schreibgeschützt und Sie werden in dieser Ansicht keine Änderungen vornehmen können. In der Ansicht des POM Editors wie in Figure 14.27, “Übersichtsanzeige des POM Editors für das Projekt idiom-core” (Abbildung 14.27: "Übersichtsanzeige des 401
  • 421. Maven in Eclipse: m2eclipse POM Editors für das Projekt idiom-core") dargestellt, können Sie ebenfalls die Ansicht des übergeordneten POM aufrufen. Wählen Sie „Übergeordnetes POM öffnen" von der Toolbar an der oberen rechten Ecke des POM Editors (gelber Pfeil nach oben). Der POM Editor bietet eine Anzahl verschieden Ansichten der Informationen des POM, im letzten Tab des Editors können Sie die darunterliegende XML-Datei ansehen. Ebenfalls Vorhanden ist ein Tab bezüglich Abhängigkeiten, dargestellt in Figure 14.30, “Abhängigkeitsansicht des POM Editors” (Abbildung 14.30: "Abhängigkeitsansicht des POM Editors"). Dieses gibt Ihnen eine einfache Möglichkeit Abhängigkeiten zu verwalten, sowie den Abschnitt dependencyManagement des POM zu editieren. Die Abhängigkeitsverwaltung integriert ebenfalls die Artefakt Suchfunktion des m2eclipse Plugins. Beim Arbeiten in den Feldern der Abhängigkeiten können Sie auf Aktionen aus den Editorfeldern oder auch Ctrl + Leertaste Hilfestellungen zurückgreifen. Müssen Sie einmal weitere Details über bestimmte Artefakte in Erfahrung bringen, so können Sie die Funktion „Webseite öffnen von innerhalb des Abhängigkeiten aufrufen, um eine entsprechenden Webseite aufzurufen. 402
  • 422. Maven in Eclipse: m2eclipse Figure 14.30. Abhängigkeitsansicht des POM Editors Der Build Tab, dargestellt in Figure 14.31, “Build Tab des POM Editors” (Abbildung 14.31: "Build Tab des POM Editors") gibt Ihnen Zugriff auf den Abschnitt der Build Elemente. Aus diesem Tab heraus können Sie die benutzten Verzeichnisse, Erweiterungen sowie Standard Goal-Namen sowie Ressource-Verzeichnisse bestimmen. 403
  • 423. Maven in Eclipse: m2eclipse Figure 14.31. Build Tab des POM Editors Wir haben hier nur eine Untermenge der Funktionalität des POM Editors aufgezeigt. Sollten Sie an weiteren Informationen interessiert sein, so laden Sie sich doch bitte das m2eclipse Plugin herunter und installieren Sie es. 14.12. Projektabhängigkeiten mit m2eclipse analysieren Die neueste Version des m2eclipse Plugin umfasst unter anderem auch eine Editor Version mit zusätzlichen Abhängigkeitsanalyse-Werkzeugen. Diese werden die Art, wie Abhängigkeiten verwaltet werden, grundsätzlich ändern. Eine der 404
  • 424. Maven in Eclipse: m2eclipse grundlegenden Eigenschaften warum Maven zum Einsatz kommt ist die Art und Weise wie Abhängigkeiten verwaltet werden. Nehmen Sie an, Sie schreiben an einer Applikation welche auf der Spring Hibernate3 Integration aufbaut, alles was Sie tun müssen ist, sich auf den Artefakten spring-hibernate3 des zentralen Maven Repository abzustützen. Maven wird dieses Artefakt einlesen und alle weiteren transitiven Abhängigkeiten zufügen. Während dies eine Funktionalität ist, welche viele Anwender zum Einsatz von Maven lockt, so kann es eben auch verwirrend werden, sobald ein Projekt auf dutzenden von Abhängigkeiten aufbaut, und diese jeweils weitere duzende Transitive Abhängigkeiten mit sich bringen. Probleme treten vor allem dann auf, wenn Sie von einem Projekt abhängen, dessen POM uvollständig ist, insbesondere wenn Abhängigkeiten nicht ausreichend definiert und als optional gekennzeichnet sind, oder sobald es Konflikte zwischen transitiven Abhängigkeiten gibt. Im Falle, dass eine der Voraussetzungen der Ausschluss von commons-logging oder des Servlet API ist, oder im Falle dass Sie herausfinden müssen warum in einem bestimmten Gültigkeitsbereich eine bestimmte Abhängigkeit auftritt, werden Sie regelmässig gezwungen sein, auf die Kommandozeilen Werkzeuge dependency:tree und dependency:resolve zurückzugreifen. Das sind die Momente, in denen Ihnen der neue POM Editor des m2eclipse Plugin zur Hilfe kommt. Wenn Sie ein Projekt mit vielen Abhängigkeiten öffnen, so werden Sie die zweispaltige Darstellung der Abhängigkeiten wie in Figure 14.32, “Baum der Abhängigkeiten dargestellt im POM Editor” (Abbildung 14.32: "Baum der Abhängigkeiten dargestellt im POM Editor") sehen. Die linke Spalte der Anzeige veranschaulicht den Baum der Abhängigkeiten. Die erste Ebene stellt hierbei die direkten Abhängigkeiten zu Ihrem Projekt dar. Jede weitere Ebene ist dabei die nächste Stufe der Hierarchie. Die linke Spalte hingegen gibt Ihnen ein einfaches Werkzeug zur Hand um herauszufinden wie eine bestimmte Abhängigkeit in Ihr Projekt gefunden hat. Die rechte Anzeige Zeigt Ihnen alle aufgelösten Abhängigkeiten an; das stellt die tatsächliche Liste der Abhängigkeiten dar, nach der Auflösung aller Konflikte sowie Anwendung aller Gültigkeitsbereiche und stellt somit die Liste der Abhängigkeiten dar, wie Sie Ihr Projekt zur Kompilierung, Test und Paketierung benutzen wird. 405
  • 425. Maven in Eclipse: m2eclipse Figure 14.32. Baum der Abhängigkeiten dargestellt im POM Editor Die Funktionalität welche den Tab Abhängigkeiten so wertvoll macht ist, dass es bei Nachforschungen nachvollziebar ist, wie eine bestimmte Abhängigkeit in die Liste der Abhängigkeiten gekommen ist. Such- und Filter- Funktionalitäten welche dem Editor zur Verfügung stehen machen es ein leichtes die Projektabhängigkeiten zu bearbeiten. Sie können das Suche Feld der Eingabefelder sowie die Aktivitäten Sortieren und Filtern der Abhängigkeits-Hierarchie und der Aufgelösten Abhängigkeiten einsetzen, um sich durch die Abhängigkeiten zu hangeln. Figure 14.33, “Lokalisieren von Abhängigkeiten im Abhängigkeitsbaum” (Abbildung 14.33: "Lokalisieren von Abhängigkeiten im Abhängigkeitsbaum") stellt dar, was passiert, wenn man die Abhängigkeit commons-logging in der Liste der Aufgelösten Abhängigkeiten auswählt. Sollte Filterung auf der Abhängigkeitshierarchie der linken Seite eingestellt sein, so wird die Auswahl einer Abhängigkeit auf der rechten Seite automatisch alle die Knoten des Abhängigkeitsbaumes anzeigen, welche zu deren Inklusion geführt haben. Sollte 406
  • 426. Maven in Eclipse: m2eclipse Sie versuchen eine bestimmte Abhängigkeit loszuwerden, so wird Ihnen dieses Werkzeug helfen herauszufinden, welche Abhängigkeiten (eben auch transitive Abhängigkeiten) dazu beitragen, dass eine bestimmte Abhängigkeit aufgelöst wurde. In anderen Worten, sollten Sie einen Artefakten wie z.B. commons-logging von Ihrer Liste der Abhängigkeiten löschen wollen, werden Sie hierzu das Abhängigkeits-Werkzeug benutzen wollen. Figure 14.33. Lokalisieren von Abhängigkeiten im Abhängigkeitsbaum Das m2eclipse Plugin bietet Ihnen des Weiteren auch die Möglichkeit den Baum der Abhängigkeiten graphisch darzustellen. Figure 14.34, “Darstellung eines Projektes als Graphen” (Abbildung 14.34: "Darstellung eines Projektes als Graphen"), stellt die Abhängigkeiten des idiom-core Projekts dar. Die aller oberste Box stellt das idiom-core Projekt dar, alle darunterliegenden Abhängigkeiten sind sinngemäss darunter dargestellt. Direkte Abhängigkeiten sind durch direkte Verbindungen gekennzeichnet, transitive Abhängigkeiten gehen von 407
  • 427. Maven in Eclipse: m2eclipse andren Abhängigkeiten aus. Sie können jede beliebige Abhängigkeit auswählen, um die Verbindungen hervorzuheben, oder sie können mit dem Suche Werkzeug im oberen Bereich der Seite die entsprechenden Knoten finden. Beachten Sie bitte auch das Symbol des “offenen Ordners” für diejenigen Artefakte welche lokal vorliegen, und das Symbol der Jar Datei für Artefakte welche auf ein Maven-Repository zeigen. Figure 14.34. Darstellung eines Projektes als Graphen Der dargestellt Graph kann durch Rechtsklicken der Maustaste geändert werden. Sie können ebefalls Einstellen, ob Sie die artifactId, groupId, version sowie Gültigkeitsbereich dargestellt haben möchten, und ob der Text in der Zeile umgebrochen werden soll.Figure 14.35, “Radiale Darstellung des Abhängigkeitsgraphen” (Abbildung 14:35 "Radiale Darstellung des Abhängigkeitsgraphen") stellt den gleichen Graphen aus Figure 14.34, “Darstellung eines Projektes als Graphen” (Abbildung 14.34) in einem anderen Layout dar. 408
  • 428. Maven in Eclipse: m2eclipse Figure 14.35. Radiale Darstellung des Abhängigkeitsgraphen 14.13. Maven Einstellungen Die Möglichkeit, Maven Grundeinstellungen sowie einige Optionen zu setzen, ist ein wichtiger Aspekt an der Arbeit mit Maven. m2eclipse bietet die Möglichkeit diese Einstellungen in der Maven Einstellungen Seite innerhalb von Eclipse zu setzen. Typischerweise, beim Einsatz von Maven von der Befehlszeile, werden diese Einstellungen als Optionen übergeben oder aus dem Verzeichnis ~/.m2 ausgelesen. m2eclipse bietet Zugriff auf die wichtigsten Einstellungen von innerhalb der IDE. Figure 14.36, “Maven Einstellungen in Eclipse” (Abbildung 14.36 "Maven Einstellungen in Eclipse") stellt die Einstellungsseite innerhalb von 409
  • 429. Maven in Eclipse: m2eclipse Eclipse dar: Figure 14.36. Maven Einstellungen in Eclipse Die Tick-Boxen im oberen Bereich erlauben: • Maven im Offline Modus zu betreiben, d.h. ohne Zugriff auf entfernte Repositorien • Debug Ausgaben auf die Maven Konsole umleiten • Quell-Jar Dateien von Artefakten aus entfernten Repositorien herunterladen • JavaDoc von Artefakten aus entfernten Repositorien herunterladen • Indexe von entfernten Repositorien beim Start herunterladen und aufzufrischen. Der nächste Abschnitt erlaubt in einem Fenster die Auswahl der auszuführenden 410
  • 430. Maven in Eclipse: m2eclipse Goals wann immer ein Projekt importiert wird und auch sobald Quellverzeichnisse eines gegebenen Projektes geändert werden. Das Standard Goal ist process-ressources, welches die Ressourcen eines Projektes in das Zielverzeichnis kopiert und das Projekt zur Paketierung vorbereitet. Diese Auswahl anzupassen ist hilfreich, sollten sie benutzerdefinierte Goals aufrufen um Ressourcen zu verarbeiten oder unterstützende Konfigurationen zu generieren. Sollten Sie Hilfe brauchen, ein bestimmtes Goal auszuwählen, so drücken sie den Select … Auswahlknopf um den „Goals“ Dialog aufzurufen. Das linke Fenster aus Figure 14.37, “Maven Goal Dialogfenster” (Abbildung 14.37: "Maven Goal Dialogfenster") gibt die Auflistung aller Maven Standardlebenszyklen wieder. 411
  • 431. Maven in Eclipse: m2eclipse Figure 14.37. Maven Goal Dialogfenster Es ist gut möglich, dass Sie beim Anblick der vielen verfügbaren Goals überwältigt sind. Es bestehen buchstäblich hunderte Maven Plugins für alles Mögliche, von der Generierung der Datenbank Struktur, über das Durchführen von Integrationstests, 412
  • 432. Maven in Eclipse: m2eclipse das Durchführen von statischen Analysen bis hin zur Generierung von Web Services mittels XFire. Der Goals-Dialog bietet mehr als 200 Plugins mit auswählbaren Goals. Das Fenster der rechten Seite der Figure 14.37, “Maven Goal Dialogfenster” (Abbildung 14.37: "Maven Goal Dialogfenster") stellt z.B. die angebotenen Goals des Tomcat Maven Plugin dar. Mittels der Maske Suchen können Sie wie gewohnt die Liste reduzieren. m2eclipse wird während Sie die Eingabe vornehmen die Liste gemäss Ihrer Auswahl anpassen. Eine weitere Option ist die Anzeige der Maven Installationen Konfiguration wie diese in Figure 14.38, “Maven Installationen Auswahl Anzeige” (Abbildung 14.38: "Maven Installationen Auswahl Anzeige") dargestellt ist: Figure 14.38. Maven Installationen Auswahl Anzeige 413
  • 433. Maven in Eclipse: m2eclipse Auf dieser Seite können Sie weitere Maven Installationen Ihrer Eclipse Umgebung zufügen. Sollten Sie eine von der mitgelieferten verschiedene Maven Version benutzen wollen, so können Sie auf dieser Seite verschiedene Versionen konfigurieren. Die Funktionalität ist ähnlich der, der Java Virtuellen Maschine (JVM) Konfiguration innerhalb von Eclipse. Eine eingebettete Version von Maven, bekannt unter dem Titel Maven Embedder ist bereits gesetzt. Diese wird benutzt um Maven innerhalb von Eclipse auszuführen. Sollten Sie eine andere Installation neben der von Embedder angebotenen einsetzen wollen, können Sie diese mittels des Add Buttons zufügen. Abbildung Figure 14.38, “Maven Installationen Auswahl Anzeige” (14.38: "Maven Installationen Auswahl Anzeige") zeigt die Konfiguration mit Maven Embedder, Maven 2.0.9 sowie Maven 2.1-SNAPSHOT. Die Maven Installationen Auswahl Anzeige erlaubt Ihnen ebenfalls die Dateien der lokalen sowie globalen Einstellungen, der settings.xml Dateien zu setzen. Sollten Sie den Ort dieser Dateien nicht spezifizieren, so geht Maven davon aus, das es die globalen Einstellungen unter conf/settings.xml der ausgewählten Maven Installation findet. Sie können ebenfalls den Ort der Datei der benutzerdefinierten Einstellungen von ~/.m2/settings.xml umdefinieren, sowie den Ort des lokalen Maven Repository unterschiedlich von ~/.m2/repositroy definieren. Ebenfalls zur Auswahl in den Einstellungen von Eclipse steht die Möglichkeit Maven Version Dekoratoren einzustellen. Diese Einstellung gibt die Möglichkeit die aktuelle Version eines Artefakten im Eclipse Package Explorers dazustellen und wird in Figure 14.39, “Einstellen der Maven Versions-Dekoratoren” (Abbildung 14.39 "Einstellen der Maven Versions-Dekoratoren") dargestellt. 414
  • 434. Maven in Eclipse: m2eclipse Figure 14.39. Einstellen der Maven Versions-Dekoratoren Um diese Einstellung zu aktivieren, wählen Sie einfach den Maven Version Dekorator Tick wie in Figure 14.39, “Einstellen der Maven Versions-Dekoratoren” (Abbildung 14.39: "Einstellen der Maven Versions-Dekoratoren") dargestellt aus. 415
  • 435. Maven in Eclipse: m2eclipse Sollte dies nicht ausgewählt sein, so wird das Projekt lediglich mit dem Namen sowie dem relativen Pfad innerhalb des Package Explorers dargestellt, wie in Figure 14.40, “Package Explorer ohne Maven Versions-Dekoratoren” (Abbildung 14.40 "Package Explorer ohne Maven Versions-Dekoratoren") zu sehen: Figure 14.40. Package Explorer ohne Maven Versions-Dekoratoren Sobald die Maven Versions-Dekoratoren zugeschaltet sind, wird dem Projektnamen die aktuelle Projektversion zugefügt wie dies in Figure 14.41, “Package Explorer mit zugeschalteten Maven Versions-Dekoratoren” (Abbildung 14.41: "Package Explorer mit zugeschalteten Maven Versions-Dekoratoren"): 416
  • 436. Maven in Eclipse: m2eclipse Figure 14.41. Package Explorer mit zugeschalteten Maven Versions-Dekoratoren Diese nützliche Funktion erlaubt doch auf einen Blick die eingebundenen Projekt Versionen zu überblicken, ohne den Umweg über das POM einzuschlagen. 14.14. Zusammenfassung m2eclipse ist mehr als nur ein Plugin welches die Unterstützung für Maven innerhalb von Eclipse bereitstellt. Vielmehr ist es eine umfassende Integration welche den Umgang beginnend mit dem Erstellen eines Projektes bis hin zum Herausfinden von impliziten Abhängigkeiten um Längen vereinfacht. m2eclipse ist ein erster Schritt in die Richtung einer Entwicklungsumgebung, welche den 417
  • 437. Maven in Eclipse: m2eclipse reichen semantischen Fundus des zentralen Maven Repositorien nutzt. Mit der Verbreitung des m2eclipse Plugin werden viele weitere Archetypen veröffentlicht werden, und weitere Projekte werden Wert darin sehen, deren Artefakte ins zentrale Maven Repository zu publizieren. Sollten Sie bereits zuvor versucht haben Eclipse und Maven - ohne die Hilfe eines Werkzeuges welches versteht mit den hierarchischen Abhängigkeiten umzugehen – zu benutzen, so können Sie einschätzen wie wichtig und zentral die saubere Integration ist, welche ein Arbeiten mit verschachtelten Projekten ermöglicht, welche multi-modulare Projekte mit sich bringen. 418
  • 438. Chapter 15. Site Generation 15.1. Introduction Successful software applications are rarely produced by a team of one. When we're talking about any software worth writing, we're usually dealing teams of collaborating developers ranging anywhere in size from a handful of programmers working in a small team to hundreds or thousands of programmers working in large distributed environment. Most open source projects (such as Maven) succeed or fail based on the presence or absence of well written documentation for a widely-distributed, ad-hoc collection of users and developers. In all environments it is important for projects to have an easy way to publish and maintain online documentation. Software development is primarily an exercise in collaboration and communication, and publishing a Maven site is one way to make sure that your project is communicating with your end-users. A web site for an open source project is often the foundation for both the end-user and developer communities alike. End-users look to a project's web site for tutorials, user guides, API documentation, and mailing list archives, and developers look to a project's web site for design documents, code reports, issue tracking, and release plans. Large open-sources projects may be integrated with wikis, issue trackers, and continuous integration systems which help to augment a project's online documentation with material that reflects the current status of ongoing development. If a new open source project has an inadequate web site which fails to convey basic information to prospective users, if often is a sign that the project in question will fail to be adopted. In other words, for an open source project, the site and the documentation are as important to the formation of a community as the code itself. Maven can be used to create a project web site to capture information which is relevant to both the end-user and the developer audience. Out of the box, Maven can generate reports on everything from unit test failures to package coupling to reports on code quality. Maven provides you with the ability to write simple web 419
  • 439. Site Generation pages and render those pages against a consistent project template. Maven can publish site content in multiple formats including XHTML and PDF. Maven can be used to generate API document and can also be used to embedded Javadoc and source code in your project's binary release archive. Once you've used Maven to generate all of your project's end-user and developer documentation, you can then use Maven to publish your web site to a remote server. 15.2. Building a Project Site with Maven To illustrate the process of building a project website, create a sample Maven project with the archetype plugin: $ mvn archetype:create -DgroupId=org.sonatype.mavenbook -DartifactId=sample-project This creates the simplest possible Maven project with a one Java class in src/main/java and a simple POM. You can then build a Maven site by simply running mvn site. To build the site and preview the result in a browser, you can run mvn site:run, this will build the site and start an embedded instance of Jetty. $ cd sample-project $ mvn site:run [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'site'. [INFO] ------------------------------------------------------------------------ [INFO] Building sample-project [INFO] task-segment: [site:run] (aggregator-style) [INFO] ------------------------------------------------------------------------ [INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'. [INFO] Setting property: velocimacro.messages.on => 'false'. [INFO] Setting property: resource.loader => 'classpath'. [INFO] Setting property: resource.manager.logwhenfound => 'false'. [INFO] [site:run] 2008-04-26 11:52:26.981::INFO: Logging to STDERR via org.mortbay.log.StdErrLog [INFO] Starting Jetty on http://localhost:8080/ 2008-04-26 11:52:26.046::INFO: jetty-6.1.5 2008-04-26 11:52:26.156::INFO: NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet 2008-04-26 11:52:26.244::INFO: Started SelectChannelConnector@0.0.0.0:8080 Once Jetty starts and is listening to port 8080, you can see the project's site when you go to http://localhost:8080/ in a web browser. You can see the results in 420
  • 440. Site Generation Figure 15.1, “Simple Generated Maven Site”. Figure 15.1. Simple Generated Maven Site If you click around on this simple site, you'll see that it isn't very helpful as a real project site. There's just nothing there (and it doesn't look very good). Since the sample-project hasn't configured any developers, mailing lists, issue tracking providers, or source code repositories, all of these pages on the project site will have no information. Even the index page of the site states, "There is currently no description associated with this project". To customize the site, you'll have to start add content to the project and to the project's POM. If you are going to use the Maven Site plugin to build your project's site, you'll want to customize it. You will want to populate some of the important fields in the POM that tell Maven about the people participating in the project, and you'll want to customize the left-hand navigation menu and the links visible in the header of the page. To customize the contents of the site and affect the contents of the left-hand navigation menu, you will need to edit the site descriptor. 421
  • 441. Site Generation 15.3. Customizing the Site Descriptor When you add content to the site, you are going to want to modify the left-hand navigation menu that is generated with your site. The following site descriptor customizes the logo in the upper left-hand corner of the site. In addition to customizing the header of the site, this descriptor adds a menu section to the left-hand navigation menu under the heading "Sample Project". This menu contains a single link to an overview page. Example 15.1. An Initial Site Descriptor <project name="Sample Project"> <bannerLeft> <name>Sonatype</name> <src>images/logo.png</src> <href>http://www.sonatype.com</href> </bannerLeft> <body> <menu name="Sample Project"> <item name="Overview" href="index.html"/> </menu> <menu ref="reports"/> </body> </project> This site descriptor references one image. This logo.png image should be placed in ${basedir}/src/site/resources/images. In addition to the change to the site descriptor, you'll want to create a simple index.apt page in ${basedir}/src/site/apt. Put the following content in index.apt, it will be transformed to the index.html and serve as the first page a user sees when they come to your project's Maven-generated web site. Welcome to the Sample Project, we hope you enjoy your time on this project site. We've tried to assemble some great user documentation and developer information, and we're really excited that you've taken the time to visit this site. What is Sample Project Well, it's easy enough to explain. This sample project is 422
  • 442. Site Generation a sample of a project with a Maven-generated site from Maven: The Definitive Guide. A dedicated team of volunteers help maintain this sample site, and so on and so forth. To preview the the site, run mvn clean site followed by mvn site:run: $ mvn clean site $ mvn site:run Once you do this, load the page in a browser by going to http://localhost:8080. You should see something similar to the screenshot in Figure 15.2, “Customized Sample Project Web Site”. Figure 15.2. Customized Sample Project Web Site 15.3.1. Customizing the Header Graphics To customize the graphics which appear in the upper left-hand and right-hand 423
  • 443. Site Generation corners of the page, you can use the bannerLeft and bannerRight elements in a site descriptor. Example 15.2. Adding a Banner Left and Banner Right to Site Descriptor <project name="Sample Project"> <bannerLeft> <name>Left Banner</name> <src>images/banner-left.png</src> <href>http://www.google.com</href> </bannerLeft> <bannerRight> <name>Right Banner</name> <src>images/banner-right.png</src> <href>http://www.yahoo.com</href> </bannerRight> ... </project> Both the bannerLeft and bannerRight elements take name, src, and href child elements. In the site descriptor shown above, the Maven Site plugin will generate a site with banner-left.png in the left-hand corner of the page and banner-right in the right-hand corner of the page. Maven is going to look in ${basedir}/src/site/resources/images for these images. 15.3.2. Customizing the Navigation Menu To customize the contents of the navigation menu, use the menu element with item child elements. The menu element adds a section to the left-hand navigation menu. Each item is rendered as a link in that menu. Example 15.3. Creating Menu Items in a Site Descriptor <project name="Sample Project"> ... <body> <menu name="Sample Project"> <item name="Introduction" href="index.html"/> 424
  • 444. Site Generation <item name="News" href="news.html"/> <item name="Features" href="features.html"/> <item name="Installation" href="installation.html"/> <item name="Configuration" href="configuration.html"/> <item name="FAQ" href="faq.html"/> </menu> ... </body> </project> Menu items can also be nested. If you nest items, you will be creating a collapsible menu in the left-hand navigation menu. The following example adds a link "Developer Resources" which links to /developer/index.html. When a user is looking at the Developer Resources page, the menu items below the Developer Resources menu item will be expanded. Example 15.4. Adding a Link to the Site Menu <project name="Sample Project"> ... <body> ... <menu name="Sample Project"> ... <item name="Developer Resources" href="/developer/index.html" collapse="true"> <item name="System Architecture" href="/developer/architecture.html"/> <item name="Embedder's Guide" href="/developer/embedding.html"/> </item> </menu> ... </body> </project> When an item has the collapse attribute set to true, Maven will collapse the item until a user is viewing that specific page. In the previous example, when the user is not looking at the Developer Resources page, Maven will not display the System Architecture and Embedder's Guide links; instead, it will display an arrow pointing to the Developer Resources link. When the user is viewing the Developer Resources page it will show these links with an arrow pointing down. 425
  • 445. Site Generation 15.4. Site Directory Structure Maven places all site document under src/site. Documents of similar format are placed in subdirectories of src/site. All APT documents should be in src/site/apt, all FML documents should be in src/site/fml, and XDoc documents should be in src/site/xdoc. The site descriptor should be in src/site/site.xml, and all resources should be stored under src/site/resources. When the Maven Site plugin builds a web site, it will copy everything in the resources directory to the root of the site. If you store an image in src/site/resources/images/test.png, they you would refer to the image from your site documentation using the relative path images/test.png. The following examples shows the location of all files in a project which contains APT, FML, HTML, XHTML, and some XDoc. Note that the XHTML content is simply stored in the resources directory. The architecture.html file will not be processed by Doxia, it will simply be copied to the output directory. You can use this approach if you want to include unprocessed HTML content and you don't want to take advantage of the templating and formatting capabilities of Doxia and the Maven Site plugin. sample-project +- src/ +- site/ +- apt/ | +- index.apt | +- about.apt | | | +- developer/ | +- embedding.apt | +- fml/ | +- faq.fml | +- resources/ | +- images/ | | +- banner-left.png | | +- banner-right.png | | | +- architecture.html | +- jira-roadmap-export-2007-03-26.html | +- xdoc/ | +- xml-example.xml 426
  • 446. Site Generation | +- site.xml Note that the developer documentation is stored in src/site/apt/developer/embedding.apt. This extra directory below the apt directory will be reflected in the location of the resulting HTML page on the site. When the Site plugin renders the contents of the src/site/apt directory it will produce HTML output in directories relative to the site root. If a file is in the apt directory it will be in the root directory of the generated web site. If a file is in the apt/developer directory it will be generated in the developer/ directory of the web site. 15.5. Writing Project Documentation Maven uses a documentation-processing engine called Doxia which reads multiple source formats into a common document model. Doxia can then manipulate documents and render the result into several output formats, such as PDF or XHTML. To write document for your project, you will need to write your content in a format which can be parsed by Doxia. Doxia currently has support for Almost Plain Text (APT), XDoc (a Maven 1.x documentation format), XHTML, and FML (useful for FAQ documents) formats. This chapter has a cursory introduction to the APT format. For a deeper understand of the APT format, or for an in-depth introduction to XDoc or FML, please see the following resources: • APT Reference: http://maven.apache.org/doxia/format.html • XDoc Reference: http://jakarta.apache.org/site/jakarta-site2.html • FML Reference: http://maven.apache.org/doxia/references/fml-format.html 15.5.1. APT Example Example 15.5, “APT Document” shows a simple APT document with an 427
  • 447. Site Generation introductory paragraph and a simple list. Note that the list is terminated by the psuedo-element "[]". Example 15.5. APT Document --- Introduction to Sample Project --- Brian Fox --- 26-Mar-2008 --- Welcome to Sample Project This is a sample project, welcome! We're excited that you've decided to read the index page of this Sample Project. We hope you enjoy the simple sample project we've assembled for you. Here are some useful links to get you started: * {{{news.html}News}} * {{{features.html}Features}} * {{{faq.html}FAQ}} [] If the APT document from Example 15.5, “APT Document” were placed in src/site/apt/index.apt, the Maven Site plugin will parse the APT using Doxia and produce XHTML content in index.html. 15.5.2. FML Example Many projects maintain a Frequently Asked Questions (FAQ) page. Example 15.6, “FAQ Markup Language Document” shows an example of an FML document. Example 15.6. FAQ Markup Language Document <?xml version="1.0" encoding="UTF-8"?> <faqs title="Frequently Asked Questions"> <part id="General"> <faq id="sample-project-sucks"> 428
  • 448. Site Generation <question>Sample project doesn't work. Why does sample project suck?</question> <answer> <p> We resent that question. Sample wasn't designed to work, it was designed to show you how to use Maven. If you really think this project sucks, then keep it to yourself. We're not interested in your pestering questions. </p> </answer> </faq> <faq id="sample-project-source"> <question>I want to put some code in Sample Project, how do I do this?</question> <answer> <p> If you want to add code to this project, just start putting Java source in src/main/java. If you want to put some source code in this FAQ, use the source element: </p> <source> for( int i = 0; i < 1234; i++ ) { // do something brilliant } </source> </answer> </faq> </part> </faqs> 15.6. Deploying Your Project Website Once your project's documentation has been written and you've creates a site to be proud of, you will want to deploy it a server. To deploy your site you'll use the Maven Site plugin which can take care of deploying your project's site to a remote server using a number of methods including FTP, SCP, and DAV. To deploy the site using DAV, configure the site entry of the distributionManagement section in the POM, like this: Example 15.7. Configuring Site Deployment <project> ... 429
  • 449. Site Generation <distributionManagement> <site> <id>sample-project.website</id> <url>dav:https://dav.sample.com/sites/sample-project</url> </site> </distributionManagement> ... </project> The url in distribution management has a leading indicator dav which tells the Maven Site plugin to deploy the site to a URL that is able to understand WebDAV. Once you have added the distributionManagement section to our sample-project POM, we can try deploying the site: $ mvn clean site-deploy If you have a server configured properly that can understand WebDAV, Maven will deploy your project's web site to the remote server. If you are deploying this project to a site and server visible to the public, you are going to want to configure your web server to access for credentials. If your web server asks for a username and password (or other credentials, you can configure this values in your ~/.m2/settings.xml). 15.6.1. Configuring Server Authentication To configure a username/password combination for use during the site deployment, we'll include the following in $HOME/.m2/settings.xml: Example 15.8. Storing Server Authentication in User-specific Settings <settings> ... <servers> <server> <id>sample-project.website</id> <username>jdcasey</username> <password>b@dp@ssw0rd</password> </server> ... </servers> ... 430
  • 450. Site Generation </settings> The server authentication section can contain a number of authentication elements. In the event you're using SCP for deployment, you may wish to use public-key authentication. To do this, specify the publicKey and passphrase elements, instead of the password element. You may still want to configure the username element, depending on your server's configuration. 15.6.2. Configuring File and Directory Modes If you are working in a large group of developers, you'll want to make sure that your web site's files end up with the proper user and group permissions after they are published to the remote server. To configure specific file and directory modes for use during the site deployment, include the following in $HOME/.m2/settings.xml: Example 15.9. Configuring File and Directory Modes on Remote Servers <settings> ... <servers> ... <server> <id>hello-world.website</id> ... <directoryPermissions>0775</directoryPermissions> <filePermissions>0664</filePermissions> </server> </servers> ... </settings> The above settings will make any directories readable and writable by either the owner or members of the owner's primary group; the anonymous users will only have access to read and list the directory. Similarly, the owner or members of the owner's primary group will have access to read and write any files, with the rest of the world restricted to read-only access. 431
  • 451. Site Generation 15.7. Customizing Site Appearance The default Maven template leaves much to be desired. If you wish to customize your project's website beyond simply adding content, navigational elements, and custom logos. Maven offers several mechanisms for customizing your website that offer successively deeper access to content decoration and website structure. For small, per-project tweaks, providing a custom site.css is often enough. However, if you want your customizations to be reusable across multiple projects, or if your customizations involve changing the XHTML that Maven generates, you should consider creating your own Maven website skin. 15.7.1. Customizing the Site CSS The easiest way to affect the look and feel of your project's web site is through the project's site.css. Just like any images or XHTML content you provide for the website, the site.css file goes in the src/site/resources directory. Maven expects this file to be in the src/site/resources/css subdirectory. With CSS it is possible to change text styling properties, layout properties, and even add background images and custom bullet graphics. For example, if we decided that to make the menu heading stand out a little more, we might try the following style in src/site/resources/css/site.css: #navcolumn h5 { font-size: smaller; border: 1px solid #aaaaaa; background-color: #bbb; margin-top: 7px; margin-bottom: 2px; padding-top: 2px; padding-left: 2px; color: #000; } When you regenerate the website, the menu headers should be framed by a gray background and separated from the rest of the menu by some extra margin space. Using this file, any structure in the Maven-generated website can be decorated with custom CSS. When you change site.css in a specific Maven project, the changes 432
  • 452. Site Generation will apply to that specific project. If you are interested in making changes that will apply to more than one Maven project, you can create a custom skin for the Maven Site plugin. Tip There is no good reference for the structure of the default Maven site template. If you are attempting to customize the style of your Maven project, you should use a Firefox extension like Firebug as a tool to explore the DOM for your project's pages. 15.7.2. Create a Custom Site Template If the default Maven Site structure just doesn't do it for you, you can always customize the Maven site template. Customizing the Maven Site template gives you complete control over the ultimate output of the Maven plugin, and it is possible to customize your project's site template to the point where it hardly resembles the structure of a default Maven site template. The Site plugin uses a rendering engine called Doxia, which in turn uses a Velocity template to render the XHTML for each page. To change the page structure that is rendered by default, we can configure the site plugin in our POM to use a custom page template. The site template is fairly complex, and you'll need to have a good starting point for your customization. Start by copying the default Velocity template from Doxia's Subversion repository default-site.vm to src/site/site.vm. This template is written in a templating language called Velocity. Velocity is a simple templating language which supports simple macro definition and allows you to access an object's methods and properties using simple notation. A full introduction is beyond the scope of this book, for more information about Velocity and a full introduction please go to the Velocity project site at http://velocity.apache.org. The default-site.xml template is fairly involved, but the change required to customize the left-hand menu is relatively straightforward. If you are trying to change the appearance of a menuItem, locate the menuItem macro. It resides in a 433
  • 453. Site Generation section that looks like this: #macro ( menuItem $item ) ... #end If you replace the macro definition with the macro definition listed below, you will injects Javascript references into each menu item which will allow the reader to expand or collapse the menu tree without suffering through a full page reload: #macro ( menuItem $item $listCount ) #set ( $collapse = "none" ) #set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) ) #set ( $currentItemHref = $currentItemHref.replaceAll( "", "/" ) ) #if ( $item && $item.items && $item.items.size() > 0 ) #if ( $item.collapse == false ) #set ( $collapse = "collapsed" ) #else ## By default collapsed #set ( $collapse = "collapsed" ) #end #set ( $display = false ) #displayTree( $display $item ) #if ( $alignedFileName == $currentItemHref || $display ) #set ( $collapse = "expanded" ) #end #end <li class="$collapse"> #if ( $item.img ) #if ( ! ( $item.img.toLowerCase().startsWith("http") || $item.img.toLowerCase().startsWith("https") ) ) #set ( $src = $PathTool.calculateLink( $item.img, $relativePath ) ) #set ( $src = $item.img.replaceAll( "", "/" ) ) <img src="$src"/> #else <img src="$item.img" align="absbottom" style="border-width: 0"/> #end #end #if ( $alignedFileName == $currentItemHref ) <strong>$item.name</strong> #else #if ( $item && $item.items && $item.items.size() > 0 ) <a onclick="expand('list$listCount')" style="cursor:pointer">$item.name</a> #else 434
  • 454. Site Generation <a href="$currentItemHref">$item.name</a> #end #end #if ( $item && $item.items && $item.items.size() > 0 ) #if ( $collapse == "expanded" ) <ul id="list$listCount" style="display:block"> #else <ul id="list$listCount" style="display:none"> #end #foreach( $subitem in $item.items ) #set ( $listCounter = $listCounter + 1 ) #menuItem( $subitem $listCounter ) #end </ul> #end </li> #end This change adds a new parameter to the menuItem macro. For the new functionality to work, you will need to change references to this macro, or the resulting template may produce unwanted or internally inconsistent XHTML. To finish changing these references, make a similar replacement in the mainMenu macro. Find this macro by looking for something similar to the following template snippet: #macro ( mainMenu $menus ) ... #end Replace the mainMenu macro with the following implementation: #macro ( mainMenu $menus ) #set ( $counter = 0 ) #set ( $listCounter = 0 ) #foreach( $menu in $menus ) #if ( $menu.name ) <h5 onclick="expand('menu$counter')">$menu.name</h5> #end <ul id="menu$counter" style="display:block"> #foreach( $item in $menu.items ) #menuItem( $item $listCounter ) #set ( $listCounter = $listCounter + 1 ) #end </ul> #set ( $counter = $counter + 1 ) #end #end 435
  • 455. Site Generation This new mainMenu macro is compatible with the new menuItem macro above, and also provides support for a Javascript-enabled top-level menu. Clicking on a top-level menu item with children will expand the menu and allow users to see the entire tree without waiting for a page to load. The change to the menuItem macro introduced an expand() Javascript function. This method needs to be added to the main XHTML template at the bottom of this template file. Find the section that looks similar to the following: <head> ... <meta http-equiv="Content-Type" content="text/html; charset=${outputEncoding}" /> ... </head> and replace it with this: <head> ... <meta http-equiv="Content-Type" content="text/html; charset=${outputEncoding}" /> <script type="text/javascript"> function expand( item ) { var expandIt = document.getElementById( item ); if( expandIt.style.display == "block" ) { expandIt.style.display = "none"; expandIt.parentNode.className = "collapsed"; } else { expandIt.style.display = "block"; expandIt.parentNode.className = "expanded"; } } </script> #if ( $decoration.body.head ) #foreach( $item in $decoration.body.head.getChildren() ) #if ( $item.name == "script" ) $item.toUnescapedString() #else $item.toString() #end #end #end </head> After modifying the default site template, you'll need to configure your project's POM to reference this new site template. To customize the site template, you'll 436
  • 456. Site Generation need to use the templateDirectory and template configuration properties of the Maven Site plugin. Example 15.10. Customizing the Page Template in a Project's POM <project> ... <build> <plugins> <plugin> <artifactId>maven-site-plugin</artifactId> <configuration> <templateDirectory>src/site</templateDirectory> <template>site.vm</template> </configuration> </plugin> </plugins> </build> ... </project> Now, you should be able to regenerate your project website. When you do so you may notice that the resources and CSS for the maven site are missing. When a Maven project customizes the site template, the Site plugin expects the project to supply all of the default images and CSS. To seed your project's resources, you may want to copy the resources from the default Doxia site renderer project to your own project's resources directory by executing the following commands: $ svn co http://svn.apache.org/repos/asf/maven/doxia/doxia-sitetools/ trunk/doxia-site-renderer $ rm doxia-site-renderer/src/main/resources/org/apache/maven/ doxia/siterenderer/resources/css/maven-theme.css $ cp -rf doxia-site-renderer/src/main/resources/org/apache/maven/ doxia/siterenderer/resources/* sample-project/src/site/resources Check out the doxia-site-renderer project, remove the default maven-theme.css file and then copy all the resources to your project's src/site/resources directory. 437
  • 457. Site Generation When you regenerate the site, you'll notice that a few menu items look like regular unstyled text. This is caused by a quirky interaction between the site's CSS and our new custom page template. It can be fixed by modifying our site.css to restore the proper link color for these menus. Simply add this: li.collapsed, li.expanded, a:link { color:#36a; } After regenerating the site, the menu's link color should be corrected. If you applied the new site template to the same sample-project from this chapter, you'll notice that the menu now consists of a tree. Clicking on "Developer Resources" no longer takes you to the "Developer Resources" page; in stead, it expands the sub-menu. Since you've turned the Developer Resources menu-item into a dynamically-folding sub-menu, you have lost the ability to reach the developer/index.apt page. To address this change, you should add an Overview link to the sub-menu which references the same page: Example 15.11. Adding a Menu Item to a Site Descriptor <project name="Hello World"> ... <menu name="Main Menu"> ... <item name="Developer Resources" collapse="true"> <item name="Overview" href="/developer/index.html"/> <item name="System Architecture" href="/developer/architecture.html"/> <item name="Embedder's Guide" href="/developer/embedding.html"/> </item> </menu> ... </project> 15.7.3. Reusable Website Skins If your organization is created many Maven project sites, you will likely want to reuse site template and CSS customizations throughout an organization. If you want thirty projects to share the same CSS and site template, you can use Maven's 438
  • 458. Site Generation support for skinning. Maven Site skins allow you to package up resources and templates which can be reused by other projects in lieu of duplicating your site template for each project which needs to be customized. While you can define your own skin, you may want to consider using one of Maven's alternate skins. You can choose from several skins. These each provide their own layout for navigation, content, logos, and templates: • Maven Classic Skin - org.apache.maven.skins:maven-classic-skin:1.0 • Maven Default Skin - org.apache.maven.skins:maven-default-skin:1.0 • Maven Stylus Skin - org.apache.maven.skins:maven-stylus-skin:1.0.1 You can find an up-to-date and comprehensive listing in the Maven repository: http://repo1.maven.org/maven2/org/apache/maven/skins/. Creating a custom skin is a simple matter of wrapping your customized maven-theme.css in a Maven project, so that it can be referenced by groupId, artifactId, and version. It can also include resources such as images, and a replacement website template (written in Velocity) that can generate a completely different XHTML page structure. In most cases, custom CSS can manage the changes you desire. To demonstrate, let's create a designer skin for the sample-project project, starting with a custom maven-theme.css. Before we can start writing our custom CSS, we need to create a separate Maven project to allow the sample-project site descriptor to reference it. First, use Maven's archetype plugin to create a basic project. Issue the following command from the directory above the sample-project project's root directory: $ mvn archetype:create -DartifactId=sample-site-skin -DgroupId=org.sonatype.mavenbook This will create a project (and a directory) called sample-site-skin. Change directories to the new sample-site-skin directory, remove all of the source code and tests, and create a directory to store your skin's resources: $ cd sample-site-skin $ rm -rf src/main/java src/test $ mkdir src/main/resources 439
  • 459. Site Generation 15.7.4. Creating a Custom Theme CSS Next, write a custom CSS for the custom skin. A Å file in a Maven site skin should be placed in src/main/resources/css/maven-theme.css. Unlike the site.css file, which goes in the site-specific source directory for a project, the maven-theme.css will be bundled in a JAR artifact in your local Maven repository. In order for the maven-theme.css file to be included in the skin's JAR file, it must reside in the main project-resources directory, src/main/resources. As with the default the default site template, you will want to start customizing your new skin's CSS from a good starting point. Copy the CSS file used by the default Maven skin to your project's maven-theme.css. To get a copy of this theme file, save the contents of maven-theme.css from the maven-default-skin project to src/main/resources/css/maven-theme.css in our new skin project. Now that we have the base theme file in place, customize it using the CSS from our old site.css file. Replace the #navcolumn h5 CSS block with the following: #navcolumn h5 { font-size: smaller; border: 1px solid #aaaaaa; background-color: #bbb; margin-top: 7px; margin-bottom: 2px; padding-top: 2px; padding-left: 2px; color: #000; } Once you've customized the maven-theme.css, build and install the sample-site-skin JAR artifact to your local Maven repository by running: $ mvn clean install Once the installation is complete, switch back to the sample-project project directory, if you already customized the site.css earlier in this chapter, move site.css to site.css.bak so it no longer affects the output of the Maven Site plugin: $ mv src/site/resources/css/site.css src/site/resources/css/site.css.bak 440
  • 460. Site Generation To use the sample-site-skin in the sample-project site, you'll need to add a reference to the sample-site-skin artifact in the sample-project's site descriptor. A site references a skin in the site descriptor using the skin element: Example 15.12. Configuring a Custom Site Skin in Site Descriptor <project name="Sample Project"> ... <skin> <groupId>org.sonatype.mavenbook</groupId> <artifactId>sample-site-skin</artifactId> </skin> ... </project> You can think of a Maven Site skin as a site dependency. Site skins are referenced as artifacts with a groupId and an artifactId. Using a site skin allows you to consolidate site customizations to a single project, and makes reusing custom CSS and site templates as easy as reusing build logic through a custom Maven plugin. 15.7.5. Customizing Site Templates in a Skin Just as you can customize a the site CSS in a Maven Site skin, you can also customize the site template. Doxia's site-rendering tools will expect to find a file called META-INF/maven/site.vm inside the skin JAR. To incorporate a custom page template, copy the template file into the correct location within the sample-site-skin. Copy the custom site template developed earlier in the chapter to src/main/resources/META-INF/maven in sample-site-skin: $ mv sample-project/src/site/site.vm sample-site-skin/src/main/resources/META-INF/maven If you already customized the site template in sample-project, remove the Site plugin configuration which pointed to this site template. The Site plugin will render the site using the site template referenced in the site skin. <plugin> <artifactId>maven-site-plugin</artifactId> <configuration> 441
  • 461. Site Generation <templateDirectory>src/site</templateDirectory> <template>site.vm</template> </configuration> </plugin> A Maven Site skin is expected to include all of the resources it depends on. This includes CSS, images, and logos. If you already customized the site template earlier in the chapter, you've already copied the default doxia-site-renderer resources to the sample-project's src/site/resources directory. You'll need to move those files out of the sample-project project and into the new sample-site-skin project by executing the following commands: $ cd .. $ mkdir -p sample-site-skin/src/main/resources/css $ mv sample-project/src/site/resources/css/maven-base.css sample-site-skin/src/main/resources/css $ mkdir -p sample-site-skin/src/main/resources/images $ mv sample-project/src/site/resources/images/logos sample-site-skin/src/main/resources/images $ mv sample-project/src/site/resources/images/expanded.gif sample-site-skin/src/main/resources/images $ mv sample/src/site/resources/images/collapsed.gif sample-site-skin/src/main/resources/images You've changed the sample-site-skin, so you'll need to install this skin into your local Maven repository. Once you install the skin locally and rebuild the sample-project web site. You'll see that the skin's custom site template was applied to the sample-project's web site. You'll notice that the color of the menu items may be a little off because you haven't added the necessary CSS to the collapsed and expanded menu items. To do this, modify src/main/resources/css/maven-theme.css. Change: a:link { ... } to this: li.collapsed, li.expanded, a:link { ... } Rebuild the skin, then regenerate the website, and you'll see that the menu items 442
  • 462. Site Generation have returned to normal. You've successfully created a Maven theme which can be used to apply CSS and templates to a set of projects. 15.8. Tips and Tricks This section lists some useful tips and tricks you can use when creating a Maven site. 15.8.1. Inject XHTML into HEAD To inject XHTML into the HEAD element, add a head element to the body element in your project's Site descriptor. The following example adds a feed link to every page in the sample-project web site. Example 15.13. Injecting HTML into the HEAD element <project name="Hello World"> ... <body> <head> <link href="http://sample.com/sites/sample-project/feeds/blog" type="application/atom+xml" id="auto-discovery" rel="alternate" title="Sample Project Blog" /> </head> ... </body> </project> 15.8.2. Add Links under Your Site Logo If you are working on a project which is being developed by an organization, you may want to add links under your project's logo. Assume that your project is a part of the Apache Software Foundation, you might want to add a link to the Apache Software Foundation web site right below your logo, and you might want to add a 443
  • 463. Site Generation link to a parent project as well. To add links below your site logo, just add a links element to the body element in the Site descriptor. Each item element in the links element will be rendered as a link in a bar directly below your project's logo. The following example would add a link to the Apache Software Foundation followed by a link to the Apache Maven project. Example 15.14. Adding Links Under Your Site Logo <project name="Hello World"> ... <body> ... <links> <item name="Apache" href="http://www.apache.org"/> <item name="Maven" href="http://maven.apache.org"/> </links> ... </body> </project> 15.8.3. Add Breadcrumbs to Your Site If your hierarchy exists within a logical hierarchy, you may want to place a series of breadcrumbs to give the user a sense of context and give them a way to navigate up the tree to projects which might contain the current project as a subproject. To configure breadcrumbs, add a breadcrumbs element to the body element in the site descriptor. Each item element will render a link, and the items in the breadcrumbs element will be rendered in order. The breadcrumb items should be listed from highest level to lowest level. In the following site descriptor, the Codehaus item would be seen to contain the Mojo item. Example 15.15. Configuring the Site's Breadcrumbs <project name="Sample Project"> ... <body> ... <breadcrumbs> <item name="Codehaus" href="http://www.codehaus.org"/> 444
  • 464. Site Generation <item name="Mojo" href="http://mojo.codehaus.org"/> </breadcrumbs> ... </body> </project> 15.8.4. Add the Project Version When you are documenting a project that has multiple versions, it is often very helpful to list the project's version number on every page. To display your project's version on the website, simply add the version element to your site descriptor: Example 15.16. Positioning the Version Information <project name="Sample Project"> ... <version position="left"/> ... </project> This will position the version (in the case of the sample-project project, it will say "Version: 1.0-SNAPSHOT") in the upper left-hand corner of the site, right next to the default "Last Published" date. Valid positions for the project version are: left Left side of the bar just below the site logo right Right side of the bar just below the site logo navigation-top Top of the menu navigation-bottom Bottom of the menu 445
  • 465. Site Generation none Suppress the version entirely 15.8.5. Modify the Publication Date Format and Location In some cases, you may wish to reformat or reposition the "Last Published" date for your project website. Just like the project version tip above, you can specify the position of the publication date by using one of the following: left Left side of the bar just below the site logo right Right side of the bar just below the site logo navigation-top Top of the menu navigation-bottom Bottom of the menu none Suppress the publication entirely Example 15.17. Positioning the Publish Date <project name="Sample Project"> ... <publishDate position="navigation-bottom"/> ... </project> By default, the publication date will be formatted using the date format string MM/dd/yyyy. You can change this format by using the standard notation found in the JavaDocs for java.text.SimpleDateFormat (see JavaDoc for SimpleDateFormat for more information). To reformat the date using yyyy-MM-dd, 446
  • 466. Site Generation use the following publishDate element. Example 15.18. Configuring the Publish Date Format <project name="Sample Project"> ... <publishDate position="navigation-bottom" format="yyyy-MM-dd"/> ... </project> 15.8.6. Using Doxia Macros In addition to its advanced document rendering features, Doxia also provides a macro engine that allows each input format to trigger injection of dynamic content. An excellent example of this is the snippet macro, which allows a document to pull a code snippet out of a source file that's available via HTTP. Using this macro, a small fragment of APT can be rendered into XHTML. The following APT code calls out to the snippet macro. Please note that this code should be on a single continuous line, the black slash character is inserted to denote a line break so that this code will fit on the printed page. %{snippet|id=modello-model|url=http://svn.apache.org/repos/asf/maven/ archetype/trunk/maven-archetype/maven-archetype-model/src/main/ mdo/archetype.mdo} Example 15.19. Output of the Snippet Macro in XHTML <div class="source"><pre> <model> <id>archetype</id> <name>Archetype</name> <description><![CDATA[Maven's model for the archetype descriptor. ]]></description> <defaults> <default> <key>package</key> <value>org.apache.maven.archetype.model</value> </default> </defaults> <classes> 447
  • 467. Site Generation <class rootElement="true" xml.tagName="archetype"> <name>ArchetypeModel</name> <description>Describes the assembly layout and packaging.</description> <version>1.0.0</version> <fields> <field> <name>id</name> <version>1.0.0</version> <required>true</required> <type>String</type> </field> ... </fields> </class> </classes> </model> </pre></div> Warning Doxia macros MUST NOT be indented in APT source documents. Doing so will result in the APT parser skipping the macro altogether. For more information about defining snippets in your code for reference by the snippet macro, see the Guide to the Snippet Macro on the Maven website, at http://maven.apache.org/guides/mini/guide-snippet-macro.html. 448
  • 468. Chapter 16. Repository Management with Nexus This chapter is deprecated. While the original version of Maven: The Definitive Guide had an entire chapter dedicated to the Nexus Repository Manager, Sonatype found that the content was growing fast enough to deserve its own dedicate titled. We've decided to spin off the content in the Repository Management chapter into a new book called Repository Management with Nexus. This new book covers both Nexus Open Source and Nexus Professional, it guides you through the process of downloading and installing a repository manager, and it provides a detailed survey of all of the configuration options. Click here to read Repository Management with Nexus. Note In the past, a book was a very static object, it was written, edited, proofread, and printed. After printing there was little time to reconsider the structure or content of a book. While the Internet has offered an opportunity for the real-time, constantly evolving on-demand book, the publishing industry has yet to come to terms with the idea of a book that is "alive" - a book that can change the day after it was sent to the print, and a book that can split into two and continue to grow. We decided to make this break because it makes sense, if one of our chapters starts to grow into a hundred-page beast, we're not just going to throw more pages at an already "thick" reference book. (But then again, this book isn't "thick" if you are reading it in a web browser, it is all about perspective.) What's true about software development is also true about writing. If you start out a project with a single, monolithic project, there is going to come a time when it makes good sense to refactor a package or collection of classes into a separate module. That is exactly what we are doing by spinning the Repository Management chapter into Repository Management with Nexus. We're "refactoring" the book into two. 449
  • 469. Repository Management with Nexus Consider this editor's note to be a deprecation warning. Eventually, we're going to remove this marker chapter from the book entirely. On the web it will be a HTTP redirect to the new book, and in the PDF it might be a place-holder page that references the Nexus book. 450
  • 470. Chapter 17. Writing Plugins 17.1. Introduction While this chapter covers an advanced topic, don't let the idea of writing a Maven plugin intimidate. For all of the theory and complexity of this tool, the fundamental concepts are easy to understand and the mechanics of writing a plugin are straightforward. After you read this chapter, you will have a better grasp of what is involved in creating a Maven plugin. 17.2. Programming Maven Most of this book has dealt with using Maven, and for a book on Maven, you haven't seen too many code examples dealing with Maven customization. In fact, you haven't yet seen any. This is by design, 99 out of 100 Maven users will never need to write a custom plugin to customize Maven; there is an abundance of configurable plugins, and unless your project has particularly unique requirements, you will have to work to find a reason to write a new plugin. An even smaller percentage of people who end up writing custom plugins will ever need to crack open the source code for Maven and customize a core Maven component. If you really need to customize the behavior of Maven, then you would write a plugin. Modifying the core Maven code is as far out of scope for most developers as modifying the TCP/IP stack on an operating system, it is that abstract for most Maven users. On the other hand, if you are going to start writing a custom plugin, you are going to have to learn a bit about the internals of Maven: How does it manage software components? What is a Plugin? How can I customize the lifecycle? This section answers some of those questions, and it introduces a few concepts at the core of Maven's design. Learning how to write a custom Maven plugin is the gateway to customizing Maven itself. If you were wondering how to start understanding the code behind Maven, you've found the proper starting line. 451
  • 471. Writing Plugins 17.2.1. What is Inversion of Control? At the heart of Maven is an Inversion of Control (IoC) container named Plexus. What does it do? It is a system for managing and relating components. While there is a canonical essay about IoC written by Martin Fowler, the concept and term have been so heavily overloaded in the past few years it is tough to find a good definition of the concept that isn't a self-reference (or just a lazy reference to the aforementioned essay). Instead of resorting to a Wikipedia quote, we'll summarize Inversion of Control and Dependency Injection with an analogy. Assume that you have a series of components which need to be wired together. When you think about components, think stereo components not software components. Imagine several stereo components hooked up to a Playstation 3 and a Tivo that have to interface with both an Apple TV box and a 50" flat panel LCD TV. You bring everything home from the electronics store and you purchase a series of cables that you are going to use to connect everything to everything else. You unpack all of these components, put them in the right place and then get to the job of hooking up fifty thousand coaxial cables and stereo jacks to fifty thousand digital inputs and network cables. Step back from your home entertainment center and turn on the TV, you've just performed dependency injection, and you've just been an inversion of control container. So what did that have to do with anything? Your Playstation 3 and a Java Bean both provide an interface. The Playstation 3 has two inputs: power and network, and one output to the TV. Your JavaBean has three properties: power, network, and tvOutput. When you open the box of your Playstation 3, it didn't provide you with detailed pictures and instructions for how to connect it to every different kind of TV that might be in every different kind of house, and when you look at your Java Bean it just provides a set of properties, not an explicit recipe for creating and managing an entire system of components. In an IoC container like Plexus, you are responsible for declaring the relationships between a set of components which simply provide an interface of inputs and outputs. You don't instantiate objects, Plexus does; your application's code isn't responsible for managing the state of components, Plexus is. Even though it sounds very cheesy, when you start up Maven, it is starting Plexus and managing a system of related components just like 452
  • 472. Writing Plugins your stereo system. What are the advantages of using an IoC container? What is the advantage of buying discrete stereo components? If one component breaks, you can drop in a replacement for your Playstation 3 without having to spend $20,000 on the entire system. If you are unhappy with your TV, you can swap it out without affecting your CD player. Most important to you, your stereo components cost less and are more capable and reliable because manufacturers can build to a set of known inputs and outputs and focus on building individual components. Inversion of Control containers and Dependency Injection encourage Disaggregation and the emergence of standards. The software industry likes to imagine itself as the font of all new ideas, but dependency injection and inversion of control are really just new words for the concepts of Disaggregation and interchangeable machinery. If you really want to know about DI and IoC, learn about the Model T, the Cotton Gin, and the emergence of a railroad standard in the late 19th century. 17.2.2. Introduction to Plexus The most important feature of an IoC container implemented in Java is a mechanism called dependency injection. The basic idea of IoC is that the control of creating and managing objects is removed from the code itself and placed into the hands of an IoC framework. Using dependency injection in an application that has been programmed to interfaces, you can create components which are not bound to specific implementations of these interfaces. Instead, you program to interfaces and then configure Plexus to connect the appropriate implementation to the appropriate component. While your code deals with interfaces, you can capture the dependencies between classes and components in an XML file that defines components, implementation classes, and the relationships between your components. In other words, you can write isolated components, then you can wire them together using an XML file that defines how the components are wired together. In the case of Plexus, system components are defined with an XML document that is found in META-INF/plexus/components.xml. In a Java IoC container, there are several methods for injecting dependencies values into a component object: constructor, setter, or field injections. Although 453
  • 473. Writing Plugins Plexus is capable of all three dependency injection techniques, Maven only uses two types: field and setter injection. Constructor Injection Constructor injection is populating an object's values through its constructor when an instance of the object is created. For example, if you had an object of type Person which had a constructor Person(String name, Job job), you could pass in values for both name and the job via this constructor. Setter Injection Setter injection is using the setter method of a property on a Java bean to populate object dependencies. For example, if you were working with a Person object with the properties name and job, an IoC container which uses setter injection, would create an instance of Person using a no-arg constructor. Once it had an instance of Person, it would proceed to call the setName() and setJob() methods. Field Injection Both Constructor and Setter injection rely on a call to a public method. Using Field injection, an IoC container populates a component's dependencies by setting an object's fields directly. For example, if you were working with a Person object that had two fields name and job, your IoC container would populate these dependencies by setting these fields directly (i.e. person.name = "Thomas"; person.job = job;) 17.2.3. Why Plexus? Spring does happen to be the most popular IoC container at the moment, and there's a good argument to be made that it has affected the Java "ecosystem" for the better forcing companies like Sun Microsystems to yield more control to the open source community and helping to open up standards by providing a pluggable, component-oriented "bus". But, Spring isn't the only IoC container in open source. There are many IoC containers (like PicoContainer). Years and years ago, when Maven was created, Spring wasn't a mature option. The 454
  • 474. Writing Plugins initial team of committers on Maven were more familiar with Plexus because they invented it, so they decided to use it as an IoC container. While it might not be as popular as the Spring Framework, it is no less capable. And, the fact that it was created by the same person who created Maven makes it a perfect fit. After reading this chapter you've have an idea of how Plexus works. If you already use an IoC container you'll notice similarities and differences between Plexus and the container you currently use. Note Just because Maven is based on Plexus doesn't mean that the Maven community is "anti-Spring" (we've included a whole chapter with a Spring example in this book, portions of the Spring project are moving to Maven as a build platform). The question, "Why didn't you use Spring?" comes up often enough it did make sense to address it here. We know it, Spring is a rock star, we don't deny it, and it is on our continuing to-do list to introduce people to (and document) Plexus: choice in the software industry is always a good thing. 17.2.4. What is a Plugin? A Maven Plugin is a Maven artifact which contains a plugin descriptor and one or more Mojos. A Mojo can be thought of as a goal in Maven, and every goal corresponds to a Mojo. The compiler:compile goal corresponds to the CompilerMojo class in the Maven Compiler Plugin, and the jar:jar goal corresponds to the JarMojo class in the Maven Jar Plugin. When you write your own plugin, you are simply grouping together a set of related Mojos (or goals) in a single plugin artifact.2 Note Mojo? What is a Mojo? The word mojo2 is defined as "a magic charm or 2 "mojo." The American Heritage® Dictionary of the English Language, Fourth Edition. Houghton Mifflin Company, 2004. Answers.com 02 Mar. 2008. http://www.answers.com/topic/mojo-1 455
  • 475. Writing Plugins spell", "an amulet, often in a small flannel bag containing one or more magic items", and "personal magnetism; charm". Maven uses the term Mojo because it is a play on the word Pojo (Plain-old Java Object). A Mojo is much more than just a goal in Maven, it is a component managed by Plexus that can include references to other Plexus components. 17.3. Plugin Descriptor A Maven plugin contains a road-map for Maven that tells Maven about the various Mojos and plugin configuration. This plugin descriptor is present in the plugin JAR file in META-INF/maven/plugin.xml. When Maven loads a plugin, it reads this XML file, instantiates and configures plugin objects to make the Mojos contained in a plugin available to Maven. When you are writing custom Maven plugins, you will almost never need to think about writing a plugin descriptor. In Chapter 10, Der Build Lebenszyklus, the lifecycle goals bound to the maven-plugin packaging type show that the plugin:descriptor goal is bound to the generate-resources phase. This goal generates a plugin descriptor off of the annotations present in a plugin's source code. Later in this chapter, you will see how Mojos are annotated, and you will also see how the values in these annotations end up in the META-INF/maven/plugin.xml file. Example 17.1, “Plugin Descriptor” shows a plugin descriptor for the Maven Zip Plugin. This plugin is a contrived plugin that simply zips up the output directory and produces an archive. Normally, you wouldn't need to write a custom plugin to create an archive from Maven, you could simply use the Maven Assembly Plugin which is capable of producing a distribution archive in multiple formats. Read through the following plugin descriptor to get an idea of the content it contains. Example 17.1. Plugin Descriptor <plugin> <description></description> 456
  • 476. Writing Plugins <groupId>com.training.plugins</groupId> <artifactId>maven-zip-plugin</artifactId> <version>1-SNAPSHOT</version> <goalPrefix>zip</goalPrefix> <isolatedRealm>false</isolatedRealm> <inheritedByDefault>true</inheritedByDefault> <mojos> <mojo> <goal>zip</goal> <description>Zips up the output directory.</description> <requiresDirectInvocation>false</requiresDirectInvocation> <requiresProject>true</requiresProject> <requiresReports>false</requiresReports> <aggregator>false</aggregator> <requiresOnline>false</requiresOnline> <inheritedByDefault>true</inheritedByDefault> <phase>package</phase> <implementation>com.training.plugins.ZipMojo</implementation> <language>java</language> <instantiationStrategy>per-lookup</instantiationStrategy> <executionStrategy>once-per-session</executionStrategy> <parameters> <parameter> <name>baseDirectory</name> <type>java.io.File</type> <required>false</required> <editable>true</editable> <description>Base directory of the project.</description> </parameter> <parameter> <name>buildDirectory</name> <type>java.io.File</type> <required>false</required> <editable>true</editable> <description>Directory containing the build files.</description> </parameter> </parameters> <configuration> <buildDirectory implementation="java.io.File"> ${project.build.directory}</buildDirectory> <baseDirectory implementation="java.io.File"> ${basedir}</baseDirectory> </configuration> <requirements> <requirement> <role>org.codehaus.plexus.archiver.Archiver</role> <role-hint>zip</role-hint> <field-name>zipArchiver</field-name> </requirement> </requirements> </mojo> </mojos> <dependencies> 457
  • 477. Writing Plugins <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependencies> </plugin> There are three parts to a plugin descriptor: the top-level configuration of the plugin which contains elements like groupId and artifactId, the declaration of mojos, and the declaration of dependencies. Let's examine each of these sections in more detail. 17.3.1. Top-level Plugin Descriptor Elements The top-level configuration values in the plugin element are: description This element contains a short description of the plugin. In the case of the Zip plugin, this description is empty. groupId, artifactId, version Just like everything else in Maven, plugins need to have a unique coordinate. The groupId, artifactId, and version are used to locate the plugin artifact in a Maven repository. goalPrefix This element controls the prefix used to reference goals in a particular plugin. If you were to look at the Compiler plugin's descriptor you would see that goalPrefix has a value of compile, and if you look at the descriptor for the Jar plugin, it would have a goalPrefix of jar. It is important that you choose a distinct goal prefix for your custom plugin. isolatedRealm (deprecated) This is a legacy property which is no longer used by Maven. It is still present in the system to provide for backwards compatibility with older plugins. Earlier versions of Maven used to provide a mechanism to load a plugin's dependencies 458
  • 478. Writing Plugins in an isolated ClassLoader. Maven makes extensive use of a project called ClassWorlds from the Codehaus community to create hierarchies of ClassLoader objects which are modeled by a ClassRealm object. Feel free to ignore this property and always set it to false. inheritedByDefault If inheritedByDefault is set to true, any mojo in this plugin which is configured in a parent project will be configured in a child project. If you configure a mojo to execute during a specific phase in a parent project and the Plugin has inheritedByDefault set to true, this execution will be inherited by the child project. If inheritedByDefault is not set to true, then an goal execution defined in a parent project will not be inherited by a child project. 17.3.2. Mojo Configuration Next is the declaration of the each Mojo. The plugin element contains an element named mojos which contains a mojo element for each mojo present in the Plugin. Each mojo element contains the following configuration elements: goal This is the name of the goal. If you were running the compiler:compile goal, then compiler is the plugin's goalPrefix and compile would be the name of the goal. description This contains a short description of the goal to display to the use when they use the Help plugin to generate plugin documentation. requiresDirectInvocation If you set this to true, the goal can only be executed if it is explicitly executed from the command-line by the user. If someone tries to bind this goal to a lifecycle phase in a POM, Maven will print an error message. The default for this element is false. requiresProject 459
  • 479. Writing Plugins Specifies that a given goal cannot be executed outside of a project. The goal requires a project with a POM. The default value for this requiresProject is true. requiresReports If you were creating a plugin that relies on the presence of reports, you would need to set requiresReports to true. For example, if you were writing a plugin to aggregate information from a number of reports, you would set requiresReports to true. The default for this element is false. aggregator A Mojo descriptor with aggregator set to true is supposed to only run once during the execution of Maven, it was created to give plugin developers the ability to summarize the output of a series of builds; for example, to create a plugin that summarizes a report across all projects included in a build. A goal with aggregator set to true should only be run against the top-level project in a Maven build. The default value of aggregator is false. Aggregator is slated for deprecation in a future release of Maven. requiresOnline Specifies that a given goal cannot be executed if Maven is running in offline mode (-o command-line option). If a goal depends on a network resource, you would specify a value of true for this element and Maven would print an error if the goal was executed in offline mode. The default for requiresOnline is false. inheritedByDefault If inheritedByDefault is set to true, a mojo which is configured in a parent project will be configured in a child project. If you configure a mojo to execute during a specific phase in a parent project and the Mojo descriptor has inheritedByDefault set to true, this execution will be inherited by the child project. If inheritedByDefault is not set to true, then a goal execution defined in a parent project will not be inherited by a child project. phase 460
  • 480. Writing Plugins If you don't bind this goal to a specific phase, this element defines the default phase for this mojo. If you do not specify a phase element, Maven will require the user to explicitly specify a phase in a POM. implementation This element tells Maven which class to instantiate for this Mojo. This is a Plexus component property (defined in Plexus ComponentDescriptor). language The default language for a Maven Mojo is java. This controls the Plexus ComponentFactory used to create instances of this Mojo component. This chapter focuses on writing Maven plugins in Java, but you can also write Maven in a number of alternative languages such as Groovy, Beanshell, and Ruby. If you were writing a plugin in one of these languages you would use a language element value other than java. instantiationStrategy This property is a Plexus component configuration property, it tells Plexus how to create and manage instances of the component. In Maven, all mojos are going to be configured with an instantiationStrategy of per-lookup; a new instance of the component (mojo) is created every time it is retrieved from Plexus. executionStrategy The execution strategy tells Maven when and how to execute a Mojo. The valid values are once-per-session and always. Honestly, the valid values are anything, this particular property doesn't do a thing, it is a hold over from an early design of Maven. This property is slated for deprecation in a future release of Maven. parameters This element describes all of the parameters for this Mojo. What's the name of the parameter What is the type of parameter? Is it required? Each parameter has the following elements: 461
  • 481. Writing Plugins name Is the name of the parameter (i.e. baseDirectory) type This is the type (Java class) of the parameters (i.e. java.io.File) required Is the parameter required? If true, the parameter must be non-null when the goal is executed. editable If a parameter is not editable (if editable is set to false), then the value of the parameter cannot be set in the POM. For example, if the plugin descriptor defines the value of buildDirectory to be ${basedir} in the descriptor, a POM cannot override this value to be another value in a POM. description A short description to use when generating plugin documentation (using the Help Plugin) configuration This element provides default values for all of the Mojo's parameters using Maven property notation. This example provides a default value for the baseDir Mojo parameter and the buildDirectory Mojo parameter. In the parameter element, the implementation specifies the type of the parameter (in this case java.io.File), the value in the parameter element contains either a hard-coded default or a Maven property reference. requirements This is where the descriptor gets interesting. A Mojo is a component that is managed by Plexus, and, because of this, it has the opportunity to reference other components managed by Plexus. This element allows you to define dependencies on other components in Plexus. While you should know how to read a Plugin Descriptor, you will almost never 462
  • 482. Writing Plugins need to write one of these descriptor files by hand. Plugin Descriptor files are generated automatically off of a set of annotations in the source for a Mojo. 17.3.3. Plugin Dependencies Lastly, the plugin descriptor declares a set of dependencies just like a Maven project. When Maven uses a plugin, it will download any required dependencies before it attempts to execute a goal from this plugin. In this example, the plugin depends on Jakarta Commons IO version 1.3.2. 17.4. Writing a Custom Plugin When you write a custom plugin, you are going to be writing a series of Mojos (goals). Every Mojo is a single Java class which contains a series of annotations that tell Maven how to generate the Plugin descriptor described in the previous section. Before you can start writing Mojo classes, you will need to create Maven project with the appropriate packaging and POM. 17.4.1. Creating a Plugin Project To create a plugin project, you should use the Maven Archetype plugin. The following command-line will create a plugin with a groupId of org.sonatype.mavenbook.plugins and the artifactId of first-maven-plugin: $ mvn archetype:create -DgroupId=org.sonatype.mavenbook.plugins -DartifactId=first-maven-plugin -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-mojo The Archetype plugin is going to create a directory named my-first-plugin which contains the following POM. Example 17.2. A Plugin Project's POM <?xml version="1.0" encoding="UTF-8"?><project> <modelVersion>4.0.0</modelVersion> 463
  • 483. Writing Plugins <groupId>org.sonatype.mavenbook.plugins</groupId> <artifactId>first-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <packaging>maven-plugin</packaging> <name>first-maven-plugin Maven Mojo</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project> The most import element in a plugin project's POM is the packaging element which has a value of maven-plugin. This packaging element customizes the Maven lifecycle to include the necessary goals to create a plugin descriptor. The plugin lifecycle was introduce in Section 10.2.3, “ plugin”, it is similar to the Jar lifecycle with three exceptions: plugin:descriptor is bound to the generate-resources phase, plugin:addPluginArtifactMetadata is added to the package phase, and plugin:updateRegistry is added to the install phase. The other important piece of a plugin project's POM is the dependency on the Maven Plugin API. This project depends on version 2.0 of the maven-plugin-api and it also adds in JUnit as a test-scoped dependency. 17.4.2. A Simple Java Mojo In this chapter, we're going to introduce a Maven Mojo written in Java. Each Mojo in your project is going to implement the org.apache.maven.plugin.Mojo interface, the Mojo implementation shown in the following example implements the Mojo interface by extending the org.apache.maven.plugin.AbstractMojo class. Before we dive into the code for this Mojo, let's take some time to explore the methods on the Mojo interface. Mojo provides the following methods: 464
  • 484. Writing Plugins void setLog( org.apache.maven.monitor.logging.Log log ) Every Mojo implementation has to provide a way for the plugin to communicate the progress of a particular goal. Did the goal succeed? Or, was there a problem during goal execution? When Maven loads and executes a Mojo, it is going to call the setLog() method and supply the Mojo instance with a suitable logging destination to be used in your custom plugin. protected Log getLog() Maven is going to call setLog() before your Mojo is executed, and your Mojo can retrieve the logging object by calling getLog(). Instead of printing out status to Standard Output or the console, your Mojo is going to invoke methods on the Log object. void execute() throws org.apache.maven.plugin.MojoExecutionException This method is called by Maven when it is time to execute your goal. The Mojo interface is concerned with two things: logging the results of goal execution and executing a goal. When you are writing a custom plugin, you'll be extending AbstractMojo. AbstractMojo takes care of handling the setLog() and getLog() implementations and contains an abstract execute() method. When you extend AbstractMojo, all you need to do is implement the execute() method. Example 17.3, “A Simple EchoMojo” shows a trivial Mojo implement which simply prints out a message to the console. Example 17.3. A Simple EchoMojo package org.sonatype.mavenbook.plugins; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; /** * Echos an object string to the output screen. * @goal echo * @requiresProject false */ public class EchoMojo extends AbstractMojo { 465
  • 485. Writing Plugins /** * Any Object to print out. * @parameter expression="${echo.message}" default-value="Hello World..." */ private Object message; public void execute() throws MojoExecutionException, MojoFailureException { getLog().info( message.toString() ); } } If you create this Mojo in ${basedir} under src/main/java in org/sonatype/mavenbook/mojo/EchoMojo.java in the project created in the previous section and run mvn install, you should be able to invoke this goal directly from the command-line with: $ mvn org.sonatype.mavenbook.plugins:first-maven-plugin:1.0-SNAPSHOT:echo That large command-line is mvn followed by the groupId:artifactId:version:goal. When you run this command-line you should see output that contains the output of the echo goal with the default message: "Hello Maven World...". If you want to customize the message, you can pass the value of the message parameter with the following command-line: $ mvn org.sonatype.mavenbook.plugins:first-maven-plugin:1.0-SNAPSHOT:echo -Decho.message="The Eagle has Landed" The previous command-line is going to execute the EchoMojo and print out the message "The Eagle has Landed". 17.4.3. Configuring a Plugin Prefix Specifying the groupId, artifactId, version, and goal on the command-line is cumbersome. To address this, Maven assigns a plugin a prefix. Instead of typing: $ mvn org.apache.maven.plugins:maven-jar-plugin:2.2:jar You can use the plugin prefix jar and turn that command-line into mvn jar:jar. 466
  • 486. Writing Plugins How does Maven resolve something like jar:jar to org.apache.mven.plugins:maven-jar:2.3? Maven looks at a file in the Maven repository to obtain a list of plugins for a specific groupId. By default, Maven is configured to look for plugins in two groups: org.apache.maven.plugins and org.codehaus.mojo. When you specify a new plugin prefix like mvn hibernate3:hbm2ddl, Maven is going to scan the repository metadata for the appropriate plugin prefix. First, Maven is going to scan the org.apache.maven.plugins group for the plugin prefix hibernate3. If it doesn't find the plugin prefix hibernate3 in the org.apache.maven.plugins group it will scan the metadata for the org.codehaus.mojo group. When Maven scans the metadata for a particular groupId, it is retrieving an XML file from the Maven repository which captures metadata about the artifacts contained in a group. This XML file is specific for each repository referenced, if you are not using a custom Maven repository, you will be able to see the Maven metadata for the org.apache.maven.plugins group in your local Maven repository (~/.m2/repository) under org/apache/maven/plugins/maven-metadata-central.xml. Example 17.4, “Maven Metadata for the Maven Plugin Group” shows a snippet of the maven-metadata-central.xml file from the org.apache.maven.plugin group. Example 17.4. Maven Metadata for the Maven Plugin Group <?xml version="1.0" encoding="UTF-8"?> <metadata> <plugins> <plugin> <name>Maven Clean Plugin</name> <prefix>clean</prefix> <artifactId>maven-clean-plugin</artifactId> </plugin> <plugin> <name>Maven Compiler Plugin</name> <prefix>compiler</prefix> <artifactId>maven-compiler-plugin</artifactId> </plugin> <plugin> <name>Maven Surefire Plugin</name> <prefix>surefire</prefix> <artifactId>maven-surefire-plugin</artifactId> </plugin> ... 467
  • 487. Writing Plugins </plugins> </metadata> As you can see in Example 17.4, “Maven Metadata for the Maven Plugin Group”, this maven-metadata-central.xml file in your local repository is what makes it possible for your to execute mvn surefire:test. Maven scans org.apache.maven.plugins and org.codehaus.mojo: plugins from org.apache.maven.plugins are considered core Maven plugins and plugins from org.codehaus.mojo are considered extra plugins. The Apache Maven project manages the org.apache.maven.plugins group, and a separate independent open source community manages the Codehaus Mojo project. If you would like to start publishing plugins to your own groupId, and you would like Maven to automatically scan your own groupId for plugin prefixes, you can customize the groups that Maven scans for plugins in your Maven Settings. If you wanted to be able to run the first-maven-plugin's echo goal by running first:echo, add the org.sonatype.mavenbook.plugins groupId to your ~/.m2/settings.xml as shown in Example 17.5, “Customizing the Plugin Groups in Maven Settings”. This will prepend the org.sonatype.mavenbook.plugins to the list of groups which Maven scans for Maven plugins. Example 17.5. Customizing the Plugin Groups in Maven Settings <settings> ... <pluginGroups> <pluginGroup>org.sonatype.mavenbook.plugins</pluginGroup> </pluginGroups> </settings> You can now run mvn first:echo from any directory and see that Maven will properly resolve the goal prefix to the appropriate plugin identifiers. This worked because our project adhered to a naming convention for Maven plugins. If your plugin project has an artifactId which follows the pattern maven-first-plugin or first-maven-plugin. Maven will automatically assign a plugin goal prefix of first to your plugin. In other words, when the Maven Plugin Plugin is generating 468
  • 488. Writing Plugins the Plugin descriptor for your plugin and you have not explicitly set the goalPrefix in your project, the plugin:descriptor goal will extract the prefix from your plugin's artifactId when it matches the following patterns: • ${prefix}-maven-plugin, OR • maven-${prefix}-plugin If you would like to set an explicit plugin prefix, you'll need to configure the Maven Plugin Plugin. The Maven Plugin Plugin is a plugin that is responsible for building the Plugin descriptor and performing plugin specific tasks during the package and load phases. The Maven Plugin Plugin can be configured just like any other plugin in the build element. To set the plugin prefix for your plugin, add the following build element to the first-maven-plugin project's pom.xml. Example 17.6. Configuring a Plugin Prefix <?xml version="1.0" encoding="UTF-8"?><project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.plugins</groupId> <artifactId>first-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <packaging>maven-plugin</packaging> <name>first-maven-plugin Maven Mojo</name> <url>http://maven.apache.org</url> <build> <plugins> <plugin> <artifactId>maven-plugin-plugin</artifactId> <version>2.3</version> <configuration> <goalPrefix>blah</goalPrefix> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> 469
  • 489. Writing Plugins <scope>test</scope> </dependency> </dependencies> </project> Example 17.6, “Configuring a Plugin Prefix” sets the plugin prefix to blah. If you've added the org.sonatype.mavenbook.plugins to the pluginGroups in your ~/.m2/settings.xml, you should be able to execute the EchoMojo by running mvn echo:blah from any directory. 17.4.4. Logging from a Plugin Maven takes care of connecting your Mojo to a logging provider by calling setLog() prior to the execution of your Mojo. It supplies an implementation of org.apache.maven.monitor.logging.Log. This class exposes methods that you can use to communicate information back to the user. This Log class provides multiple levels of logging similar to that API provided by Log4J. Those levels are captured by a series of methods available for each level: debug, info, error and warn. To save trees, we've only listed the methods for a single logging level: debug. void debug( CharSequence message ) Prints a message to the debug logging level. void debug( CharSequence message, Throwable t ) Prints a message to the debug logging level which includes the stack trace from the Throwable (either Exception or Error) void debug( Throwable t ) Prints out the stack trace of the Throwable (either Exception or Error) Each of the four levels exposes the same three methods. The four logging levels serve different purposes. The debug level exists for debugging purposes and for people who want to see a very detailed picture of the execution of a Mojo. You should use the debug logging level to provide as much detail on the execution of a Mojo, but you should never assume that a user is going to see the debug level. The 470
  • 490. Writing Plugins info level is for general informational messages that should be printed as a normal course of operation. If you were building a plugin that compiled code using a compiler, you might want to print the output of the compiler to the screen. The warn logging level is used for messages about unexpected events and errors that your Mojo can cope with. If you were trying to run a plugin that compiled Ruby source code, and there was no Ruby source code available, you might want to just print a warning message and move on. Warnings are not fatal, but errors are usually build-stopping conditions. For the completely unexpected error condition, there is the error logging level. You would use error if you couldn't continue executing a Mojo. If you were writing a Mojo to compile some Java code and the compiler wasn't available, you'd print a message to the error level and possibly pass along an Exception that Maven could print out for the user. You should assume that a user is going to see most of the messages in info and all of the messages in error. 17.4.5. Mojo Class Annotations In first-maven-plugin, you didn't write the plugin descriptor yourself, you relied on Maven to generate the plugin descriptor from your source code. The descriptor was generated using your plugin project's POM information and a set of annotations on your EchoMojo class. EchoMojo only specifies the @goal annotation, here is a list of other annotations you can place on your Mojo implementation. @goal <goalName> This is the only required annotation which gives a name to this goal unique to this plugin. @requiresDependencyResolution <requireScope> Flags this mojo as requiring the dependencies in the specified scope (or an implied scope) to be resolved before it can execute. Supports compile, runtime, and test. If this annotation had a value of test, it would tell Maven that the Mojo cannot be executed until the dependencies in the test scope had been resolved. 471
  • 491. Writing Plugins @requiresProject (true|false) Marks that this goal must be run inside of a project, default is true. This is opposed to plugins like archetypes, which do not. @requiresReports (true|false) If you were creating a plugin that relies on the presence of reports, you would need to set requiresReports to true. The default value of this annotation is false. @aggregator (true|false) A Mojo with aggregator set to true is supposed to only run once during the execution of Maven, it was created to give plugin developers the ability to summarize the output of a series of builds; for example, to create a plugin that summarizes a report across all projects included in a build. A goal with aggregator set to true should only be run against the top-level project in a Maven build. The default value of aggregator is false. @requiresOnline (true|false) When set to true, Maven must not be running in offline mode when this goal is executed. Maven will throw an error if one attempts to execute this goal offline. Default: false. @requiresDirectInvocation When set to true, the goal can only be executed if it is explicitly executed from the command-line by the user. Maven will throw an error if someone tries to bind this goal to a lifecycle phase. The default for this annotation is false. @phase <phaseName> This annotation specifies the default phase for this goal. If you add an execution for this goal to a pom.xml and do not specify the phase, Maven will bind the goal to the phase specified in this annotation by default. @execute [goal=goalName|phase=phaseName [lifecycle=lifecycleId]] This annotation can be used in a number of ways. If a phase is supplied, Maven 472
  • 492. Writing Plugins will execute a parallel lifecycle ending in the specified phase. The results of this separate execution will be made available in the Maven property ${executedProperty}. The second way of using this annotation is to specify an explicit goal using the prefix:goal notation. When you specify just a goal, Maven will execute this goal in a parallel environment that will not affect the current Maven build. The third way of using this annotation would be to specify a phase in an alternate lifecycle using the identifier of a lifecycle. @execute phase="package" lifecycle="zip" @execute phase="compile" @execute goal="zip:zip" If you look at the source for EchoMojo, you'll notice that Maven is not using the standard annotations available in Java 5. Instead, it is using Commons Attributes. Commons Attributes provided a way for Java programmers to use annotations before annotations were a part of the Java language specification. Why doesn't Maven use Java 5 annotations? Maven doesn't use Java 5 annotations because it is designed to target pre-Java 5 JVMs. Because Maven has to support older versions of Java, it cannot use any of the newer features available in Java 5. 17.4.6. When a Mojo Fails The execute() method in Mojo throws two exceptions MojoExecutionException and MojoFailureException. The difference between these two exception is both subtle and important, and it relates to what happens when a goal execution "fails". A MojoExecutionException is a fatal exception, something unrecoverable happened. You would throw a MojoExecutionException if something happens that warrants a complete stop in a build; you re trying to write to disk, but there is no space left, or you were trying to publish to a remote repository, but you can't connect to it. Throw a MojoExecutionException if there is no chance of a build continuing; something terrible has happened and you want the build to stop and the user to see a "BUILD ERROR" message. A MojoFailureException is something less catastrophic, a goal can fail, but it 473
  • 493. Writing Plugins might not be the end of the world for your Maven build. A unit test can fail, or a MD5 checksum can fail; both of these are potential problems, but you don't want to return an exception that is going to kill the entire build. In this situation you would throw a MojoFailureException. Maven provides for different "resiliency" settings when it comes to project failure. Which are described below. When you run a Maven build, it could involve a series of projects each of which can succeed or fail. You have the option of running Maven in three failure modes: mvn -ff Fail-fast mode: Maven will fail (stop) at the first build failure. mvn -fae Fail-at-end: Maven will fail at the end of the build. If a project in the Maven reactor fails, Maven will continue to build the rest of the builds and report a failure at the end of the build. mvn -fn Fail never: Maven won't stop for a failure and it won't report a failure. You might want to ignore failure if you are running a continuous integration build and you want to attempt a build regardless of the success of failure of an individual project build. As a plugin developer, you'll have to make a call as to whether a particular failure condition is a MojoExecutionException or a MojoFailureExeception. 17.5. Mojo Parameters Just as important as the execute() method and the Mojo annotations, a Mojo is configured via parameters. This section deals with some configuration and topics surrounding Mojo parameters. 17.5.1. Supplying Values for Mojo Parameters In EchoMojo we declared the message parameter with the following annotations: 474
  • 494. Writing Plugins /** * Any Object to print out. * @parameter * expression="${echo.message}" * default-value="Hello Maven World" */ private Object message; The default expression for this parameter is ${echo.message}, this means that Maven will try to use the value of the echo.message property to set the value for message. If the value of the echo.message property is null, the default-value attribute of the @parameter annotation will be used instead. Instead of using the echo.message property, we can configure a value for the message parameter of the EchoMojo directly in a project's POM. There are a few ways to populate the message parameter in the EchoMojo. First we can pass in a value from the command-line like this (assuming that you've added org.sonatype.mavenbook.plugins to your pluginGroups): $ mvn first:echo -Decho.message="Hello Everybody" We could also specify the value of this message parameter, by setting a property in our POM or in our settings.xml. <project> ... <properties> <echo.message>Hello Everybody</echo.message> </properties> </project> This parameter could also be configured directly as a configuration value for the plugin. If we wanted to customize the message parameter directly, we could use the following build configuration. The following configuration bypasses the echo.message property and populates the Mojo parameter in plugin configuration. <project> ... <build> <plugins> <plugin> <groupId>org.sonatype.mavenbook.plugins</groupId> <artifactId>first-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> 475
  • 495. Writing Plugins <configuration> <message>Hello Everybody!</message> </configuration> </plugin> </plugins> </build> </project> If we wanted to run the EchoMojo twice at difference phases in a lifecycle, and we wanted to customize the message parameter for each execution separately, we could configure the parameter value at the execution level in a POM like this: <build> <build> <plugins> <plugin> <groupId>org.sonatype.mavenbook.plugins</groupId> <artifactId>first-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <executions> <execution> <id>first-execution</id> <phase>generate-resources</phase> <goals> <goal>echo</goal> </goals> <configuration> <message>The Eagle has Landed!</message> </configuration> </execution> <execution> <id>second-execution</id> <phase>validate</phase> <goals> <goal>echo</goal> </goals> <configuration> <message>${project.version}</message> </configuration> </execution> </executions> </plugin> </plugins> </build> </build> While this last configuration example seems very verbose, it illustrates the flexibility of Maven. In the previous configuration example, you've bound the EchoMojo to both the validate and generate-resources phases in the default 476
  • 496. Writing Plugins Maven lifecycle. The first execution is bound to generate-resources, it supplies a string value to the message parameter of "The Eagle has Landed!". The second execution is bound to the validate phase, it supplies a property reference to ${project.version}. When you run mvn install for his project, you'll see that the first:echo goal executes twice and prints out two different messages. 17.5.2. Multi-valued Mojo Parameters Plugins can have parameters which accept more than one value. Take a look at the ZipMojo shown in Example 17.7, “A Plugin with Multi-valued Parameters”. Both the includes and excludes parameters are multivalued String arrays which specify the inclusion and exclusion patterns for a component that creates a ZIP file. Example 17.7. A Plugin with Multi-valued Parameters package org.sonatype.mavenbook.plugins /** * Zips up the output directory. * @goal zip * @phase package */ public class ZipMojo extends AbstractMojo { /** * The Zip archiver. * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#zip}" */ private ZipArchiver zipArchiver; /** * Directory containing the build files. * @parameter expression="${project.build.directory}" */ private File buildDirectory; /** * Base directory of the project. * @parameter expression="${basedir}" */ private File baseDirectory; /** * A set of file patterns to include in the zip. 477
  • 497. Writing Plugins * @parameter alias="includes" */ private String[] mIncludes; /** * A set of file patterns to exclude from the zip. * @parameter alias="excludes" */ private String[] mExcludes; public void setExcludes( String[] excludes ) { mExcludes = excludes; } public void setIncludes( String[] includes ) { mIncludes = includes; } public void execute() throws MojoExecutionException { try { zipArchiver.addDirectory( buildDirectory, includes, excludes ); zipArchiver.setDestFile( new File( baseDirectory, "output.zip" ) ); zipArchiver.createArchive(); } catch( Exception e ) { throw new MojoExecutionException( "Could not zip", e ); } } } To configure a multi-valued Mojo parameter, you use a series of elements for each value. If the name of the multi-valued parameter is includes, you would use an element includes with child elements include. If the multi-valued parameter is excludes, you would use an element excludes with child elements exclude. To configure the ZipMojo to ignore all files ending in .txt and all files ending in a tilde, you would use the following plugin configuration. <project> ... <build> <plugins> <plugin> <groupId>org.sonatype.mavenbook.plugins</groupId> <artifactId>zip-maven-plugin</artifactId> <configuration> <excludes> <exclude>**/*.txt</exclude> <exclude>**/*~</exclude> </excludes> </configuration> </plugin> 478
  • 498. Writing Plugins </plugins> </build> </project> 17.5.3. Depending on Plexus Components A Mojo is a component managed by an IoC container called Plexus. A Mojo can depend on other components managed by Plexus by declaring a Mojo parameter and using the @parameter or the @component annotation. Example 17.7, “A Plugin with Multi-valued Parameters” shows a ZipMojo which depends on a Plexus component using the @parameter annotation, this dependency could be declared using the @component annotation. Example 17.8. Depending on a Plexus Component /** * The Zip archiver. * @component role="org.codehaus.plexus.archiver.Archiver" roleHint="zip" */ private ZipArchiver zipArchiver; When Maven instantiates this Mojo, it will then attempt to retrieve the Plexus component with the specified role and role hint. In this example, the Mojo will be related to a ZipArchiver component which will allow the ZipMojo to create a ZIP file. 17.5.4. Mojo Parameter Annotations Unless you insist on writing your Plugin descriptors by hand, you'll never have to write that XML. Instead, the Maven Plugin Plugin has a plugin:descriptor goal bound to the generate-resources phase. This goal generates the plugin descriptor from annotations on your Mojo. To configure a Mojo parameter, you should use the following annotations on either the private member variables for each of your Mojo's parameters. You can also use these annotations on public setter methods, but the most common convention for Maven plugins is to annotate private member 479
  • 499. Writing Plugins variables directly. @parameter [alias="someAlias"] [expression="${someExpression}"] [default-value="value"] Marks a private field (or a setter method) as a parameter. The alias provides the name of the parameter. If alias is omitted, Maven will use the name of the variable as the parameter name. The expression is an expression that Maven will evaluate to obtain a value. Usually the expression is a property reference like ${echo.message}. default-value is the value that this Mojo will use if no value can be derived from the expression or if a value was not explicitly supplied via plugin configuration in a POM. @required If this annotation is present, a valid value for this parameter is required prior to Mojo execution. If Maven tries to execute this Mojo and the parameter has a null value, Maven will throw and error when it tries to execute this goal. @readonly If this annotation is present, the user cannot directly configuration this parameter in the POM. You would use this annotation with the expression attribute of the parameter annotation. For example, if you wanted to make sure that a particular parameter always had the value of the finalName POM property, you would list an expression of ${build.finalName} and then add the @readOnly annotation. If this were the case, the user could only change the value of this parameter by changing the value of finalName in the POM. @component Tells Maven to populate a field with a Plexus Component. A valid value for the @component annotation would be: @component role="org.codehaus.plexus.archiver.Archiver" roleHint="zip" This would have the effect of retrieving the ZipArchiver from Plexus. The ZipArchiver is the Archiver which corresponds to the role hint zip. Instead of component, you could also use the @parameter annotation with an expression attribute of: 480
  • 500. Writing Plugins @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#zip}" While the two annotations are effectively the same, the @component annotation is the preferred way to configure dependencies on Plexus components. @deprecated The parameter will be deprecated. Users can continue configuring this parameter, but a warning message will be displayed. 17.6. Plugins and the Maven Lifecycle In the Chapter 10, Der Build Lebenszyklus chapter, you learned that lifecycles can be customized by packaging types. A plugin can both introduce a new packaging type and customize the lifecycle. In this section, you are going to learn how you can customize the lifecycle from a custom Maven plugin. You are also some to see how you can tell a Mojo to execute a parallel lifecycle. 17.6.1. Executing a Parallel Lifecycle Let's assume you write some goal that depends on the output from a previous build. Maybe the ZipMojo goal can only run if there is output to include in an archive. You can specify something like a prerequisite goal by using the @execute annotation on a Mojo class. This annotation will cause Maven to spawn a parallel build and execute a goal or a lifecycle in a parallel instance of Maven that isn't going to affect the current build. Maybe you wrote some Mojo that you can to run once a day that runs mvn install and then packages up all of the output in some sort of customized distribution format. Your Mojo descriptor could tell Maven that before you execute your CustomMojo, you'd like it to execute the default lifecycle up to the install phase and then expose the results of that project to your Mojo as the property ${executedProject}. You could then reference properties in that project to before some sort of post processing. Another possibility is that you have a goal that does something completely unrelated to the default lifecycle. Let's consider something completely unexpected, 481
  • 501. Writing Plugins maybe you have a goal that turns a WAV file into an MP3 using something like LAME, but before you do that, you want to step through a lifecycle that turns a MIDI file to a WAV. (You can use Maven for anything, this isn't that "far out".) You've created a "midi-sound" lifecycle, and you want to include the output of the midi-sound lifecycle's install phase in your web application project which has a war packaging type. Since your project is running in the war packaging lifecycle, you'll need to have goal that effectively forks off an isolated build and runs through the midi-source lifecycle. You would do this by annotating your mojo with @execute lifecycle="midi-source" phase="install". @execute goal="<goal>" This will execute the given goal before execution of this one. The goal name is specified using the prefix:goal notation. @execute phase="<phase>" This will fork an alternate build lifecycle up to the specified phase before continuing to execute the current one. If no lifecycle is specified, Maven will use the lifecycle of the current build. @execute lifecycle="<lifecycle>" phase="<phase>" This will execute the given alternate lifecycle. A custom lifecycle can be defined in META-INF/maven/lifecycle.xml. 17.6.2. Creating a Custom Lifecycle A custom lifecycle must be packaged in the plugin under the META-INF/maven/lifecycle.xml file. You can include a lifecycle under src/main/resources in META-INF/maven/lifecycle.xml. The following lifecycle.xml declares a lifecycle named zipcycle that contains only the zip goal in a package phase. Example 17.9. Define a Custom Lifecycle in lifecycle.xml <lifecycles> <lifecycle> <id>zipcycle</id> 482
  • 502. Writing Plugins <phases> <phase> <id>package</id> <executions> <execution> <goals> <goal>zip</goal> </goals> </execution> </executions> </phase> </phases> </lifecycle> </lifecycles> If you wanted to execute the zipcycle phase within another build, you could then create a ZipForkMojo which uses the @execute annotation to tell Maven to step through the zipcycle phase to package when the ZipForkMojo is executed. Example 17.10. Forking a Customer Lifecycle from a Mojo /** * Forks a zip lifecycle. * @goal zip-fork * @execute lifecycle="zipcycle" phase="package" */ public class ZipForkMojo extends AbstractMojo { public void execute() throws MojoExecutionException { getLog().info( "doing nothing here" ); } } Running the ZipForkMojo will fork the lifecycle. If you've configured your plugin to execute with the goal prefix zip, running zip-fork should produce something similar to the following output. $ mvn zip:zip-fork [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'zip'. [INFO] ---------------------------------------------------------------------- [INFO] Building Maven Zip Forked Lifecycle Test 483
  • 503. Writing Plugins [INFO] task-segment: [zip:zip-fork] [INFO] ---------------------------------------------------------------------- [INFO] Preparing zip:zip-fork [INFO] [site:attach-descriptor] [INFO] [zip:zip] [INFO] Building zip: ~/maven-zip-plugin/src/projects/zip-lifecycle-test/target/output.zip [INFO] [zip:zip-fork] [INFO] doing nothing here [INFO] --------------------------------------------------------------------- [INFO] BUILD SUCCESSFUL [INFO] --------------------------------------------------------------------- [INFO] Total time: 1 second [INFO] Finished at: Sun Apr 29 16:10:06 CDT 2007 [INFO] Final Memory: 3M/7M [INFO] --------------------------------------------------------------------- Calling zip-fork spawned another lifecycle, Maven executed the zipcycle lifecycle then it printed out the message from ZipFormMojo's execute method. 17.6.3. Overriding the Default Lifecycle Once you've created your own lifecycle and spawned it from a Mojo. The next question you might have is how do you override the default lifecycle. How do you create custom lifecycles and attach them to projects? In Chapter 10, Der Build Lebenszyklus, we saw that the packaging of a project defines the lifecycle of a project. There's something different about almost every packaging type; war attached different goals to package, custom lifecycles like swf from the Israfil Flex 3 plugin attach different goals to the compile phase. When you create a custom lifecycle, you can attach that lifecycle to a packaging type by supplying some Plexus configuration in your plugin's archive. To define a new lifecycle for a new packaging type, you'll need to configure a LifecycleMapping component in Plexus. In your plugin project, create a META-INF/plexus/components.xml under src/main/resources. In components.xml add the content from Example 17.11, “Overriding the Default Lifecycle”. Set the name of the packaging type under role-hint, and the set of phases containing the coordinates of the goals to bind (omit the version). Multiple goals can be associated with a phase using a comma delimited list. 484
  • 504. Writing Plugins Example 17.11. Overriding the Default Lifecycle <component-set> <components> <component> <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role> <role-hint>zip</role-hint> <implementation> org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping </implementation> <configuration> <phases> <process-resources> org.apache.maven.plugins:maven-resources-plugin:resources </process-resources> <compile> org.apache.maven.plugins:maven-compiler-plugin:compile </compile> <package>org.sonatype.mavenbook.plugins:maven-zip-plugin:zip</package> </phases> </configuration> </component> </components> </component-set> If you create a plugin which defines a new packaging type and a customized lifecycle, Maven won't know anything it until you add the plugin to your project's POM and set the extensions element to true. Once you do this, Maven will scan your plugin for more than just Mojos to execute, it will look for the components.xml under META-INF/plexus, and it will make the packaging type available to your project. Example 17.12. Configuring a Plugin as an Extension <project> ... <build> ... <plugins> <plugin> <groupId>com.training.plugins</groupId> <artifactId>maven-zip-plugin</artifactId> <extensions>true</extensions> </plugin> </plugins> 485
  • 505. Writing Plugins </build> </project> Once you add the plugin with the extensions element set to true, you can use the custom packaging type and your project will be able to execute the custom lifecycle associated with that packaging type. 486
  • 506. Chapter 18. Writing Plugins in Alternative Languages You can write a Mojo in Java, or you can write a Mojo in an alternative language. Maven has support for a number of implementation languages, and this chapter is going to show you how to create plugins in three languages: Groovy, Ant, and Ruby plugins. 18.1. Writing Plugins in Ant Ant isn't a language as much as it is a build tool which allows you to describe a build as a set of tasks grouped into build targets. Ant then allows you to declare dependencies between build targets; for example, in Ant you are essentially creating your own lifecycle. An Ant build.xml might have an install target which depends on a test target which depends on a compile target. Ant is something of a ancestor to Maven, it was the ubiquitous procedural build tool that almost every project used before Maven introduced the concept of wide-scale reusability of common build plugins and the concept of a universal lifecycle. While Maven is an improvement on Ant, Ant can still be useful when describing parts of the build process. Ant provides a set of tasks which can come in handy when you need to perform file operations or XSLT transformations or any other operation you could think of. There is a large library of available Ant tasks for everything from running JUnit tests to transforming XML to copying files to a remote server using SCP. An overview of available Ant tasks can be found online in the Apache Ant Manual. You can use these tasks as a low-level build customization language, and you can also write a Maven plugin where, instead of a Mojo written in Java, you can pass parameters to a Mojo which is an Ant build target. 18.2. Creating an Ant Plugin 487
  • 507. Writing Plugins in Alternative Languages To create a Maven plugin using Ant, you will need to have a pom.xml and a single Mojo implemented in Ant. To get started, create a project directory named firstant-maven-plugin. Place the following pom.xml in this directory. Example 18.1. POM for an Ant Maven Plugin <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.plugins</groupId> <artifactId>firstant-maven-plugin</artifactId> <name>Example Ant Mojo - firstant-maven-plugin</name> <packaging>maven-plugin</packaging> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-script-ant</artifactId> <version>2.0.9</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-plugin-plugin</artifactId> <version>2.4</version> <dependencies> <dependency> <groupId>org.apache.maven.plugin-tools</groupId> <artifactId>maven-plugin-tools-ant</artifactId> <version>2.4</version> </dependency> </dependencies> </plugin> </plugins> </build> </project> Next, you will need to create your Ant Mojo. An Ant mojo consists of two parts: the Ant tasks in an XML file, and a file which supplies Mojo descriptor information. The Ant plugin tools are going to look for both of these files in ${basedir}/src/main/scripts. One file will be named echo.build.xml and it will contain the Ant XML. Example 18.2. Echo Ant Mojo 488
  • 508. Writing Plugins in Alternative Languages <project> <target name="echotarget"> <echo>${message}</echo> </target> </project> The other file will describe the Echo Ant Mojo and will be in the echo.mojos.xml file also in ${basedir}/src/main/scripts. Example 18.3. Echo Ant Mojo Descriptor <pluginMetadata> <mojos> <mojo> <goal>echo</goal> <call>echotarget</call> <description>Echos a Message</description> <parameters> <parameter> <name>message</name> <property>message</property> <required>false</required> <expression>${message}</expression> <type>java.lang.Object</type> <defaultValue>Hello Maven World</defaultValue> <description>Prints a message</description> </parameter> </parameters> </mojo> </mojos> </pluginMetadata> This echo.mojos.xml file configures the Mojo descriptor for this plugin. It supplies the goal name "echo", and it tells Maven what Ant task to call in the call element. In addition to configuring the description, this XML file configures the message parameter to use the expression ${message} and to have a default value of "Hello Maven World." If you've configured your plugin groups in ~/.m2/settings.xml to include org.sonatype.mavenbook.plugins, you can install this Ant plugin by executing the following command at the command-line: $ mvn install [INFO] ------------------------------------------------------------------------ 489
  • 509. Writing Plugins in Alternative Languages [INFO] Building Example Ant Mojo - firstant-maven-plugin [INFO] task-segment: [install] [INFO] ------------------------------------------------------------------------ [INFO] [plugin:descriptor] [INFO] Using 3 extractors. [INFO] Applying extractor for language: java [INFO] Extractor for language: java found 0 mojo descriptors. [INFO] Applying extractor for language: bsh [INFO] Extractor for language: bsh found 0 mojo descriptors. [INFO] Applying extractor for language: ant [INFO] Extractor for language: ant found 1 mojo descriptors. ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ Note that the plugin:descriptor goal found a single Ant mojo descriptor. To run this goal, you would execute the following command-line: $ mvn firstant:echo ... [INFO] [firstant:echo] echotarget: [echo] Hello Maven World [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ The echo goal executed and printed out the default value of the message parameter. If you are used to Apache Ant build scripts, you will notice that Ant prints out the name of the target executed and then adds a logging prefix to the output of the echo Ant task. 18.3. Writing Plugins in JRuby Ruby is an object-oriented scripting language which provides a rich set of facilities for meta-programming and reflection. Ruby's reliance on closures and blocks make for a programming style that is both compact and powerful. Although Ruby has been around since 1993, most people came to know Ruby after it was made popular by a Ruby-based web framework known as Ruby on Rails. JRuby is a Ruby interpreter written in Java. For more information about the Ruby language, 490
  • 510. Writing Plugins in Alternative Languages see: http://www.ruby-lang.org/, and for more information about JRuby, see: http://jruby.codehaus.org/. 18.3.1. Creating a JRuby Plugin To create a Maven plugin using JRuby, you will need to have a pom.xml and a single Mojo implemented in Ruby. To get started, create a project directory named firstruby-maven-plugin. Place the following pom.xml in this directory. Example 18.4. POM for a JRuby Maven Plugin <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.plugins</groupId> <artifactId>firstruby-maven-plugin</artifactId> <name>Example Ruby Mojo - firstruby-maven-plugin</name> <packaging>maven-plugin</packaging> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.codehaus.mojo</groupId> <artifactId>jruby-maven-plugin</artifactId> <version>1.0-beta-4</version> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-plugin-plugin</artifactId> <version>2.4</version> <dependencies> <dependency> <groupId>org.codehaus.mojo</groupId> <artifactId>jruby-maven-plugin</artifactId> <version>1.0-beta-4</version> </dependency> </dependencies> </plugin> </plugins> </build> </project> Next, you will need to create a Mojo implemented in Ruby. Maven is going to look for a Ruby Mojo in ${basedir}/src/main/scripts. Put the following Ruby class 491
  • 511. Writing Plugins in Alternative Languages in ${basedir}/src/main/scripts/echo.rb. Example 18.5. The Echo Ruby Mojo # Prints a message # @goal "echo" # @phase "validate" class Echo < Mojo # @parameter type="java.lang.String" default-value="Hello Maven World" expression="${message}" def message end def execute info $message end end run_mojo Echo The Echo class must extend Mojo, and it must override the execute() method. At the end of the echo.rb file, you will need to run the mojo with "run_mojo Echo". To install this plugin, run mvn install: $ mvn install [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Building Example Ruby Mojo - firstruby-maven-plugin [INFO] task-segment: [install] [INFO] ------------------------------------------------------------------------ ... [INFO] [plugin:descriptor] ... [INFO] Applying extractor for language: jruby [INFO] Ruby Mojo File: /echo.rb [INFO] Extractor for language: jruby found 1 mojo descriptors. ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ During the build, you should see that the Maven Plugin Plugin's descriptor goal applies the JRuby extractor to create a plugin.xml which captures the annotations in the Echo class. If you've configured your default plugin groups to include 492
  • 512. Writing Plugins in Alternative Languages org.sonatype.mavenbook.plugins, you should be able to run this echo goal with the following command-line: $ mvn firstruby:echo ... [INFO] [firstruby:echo] [INFO] Hello Maven World ... 18.3.2. Ruby Mojo Implementations Ruby Mojos are annotated using comments in Ruby source files. A single annotation like @parameter takes a number of attributes, and each of these attributes must be specified on the same line. There can be no line-breaks between an annotations attribute in the Ruby source. Both classes and parameters are annotated. Parameters are annotated with four annotations: @parameter, @required, @readonly, and @deprecated. The @parameter attribute takes the following attributes: alias An alias for the parameter. An alternate name which can be used to populate the same parameter. default-value Provides a default value to the parameter if the supplied value or the parameter expression produces a null result. In echo.rb, we specify the default as "Hello Maven World". expression Contains an expression which can resolve to a Maven property or a System property. type The fully qualified Java type of the parameter. If the type is not specified it will default to java.lang.String. In addition to the @parameter annotation, a parameter can take the following 493
  • 513. Writing Plugins in Alternative Languages annotations: @required "<true|false>" Marks the parameter as being required. The default value is false. @readonly "<true|false>" Marks the parameter as read-only. If this is true, you may not override the default value or the value from the expression from the command line. The default value is false. @deprecated "<true|false>" Marks the parameter as deprecated. The default value is false. Putting this altogether, a fully annotated message parameter from echo.rb would look like the following code: # @parameter type="java.lang.String" default-value="Hello Maven World" expression="${message}" # @readonly true # @required false # @deprecated false def message end Ruby Mojo classes are annotated with the following attributes: @goal Specifies the name of the goal. @phase The default phase to bind this goal to. @requiresDependencyResolution True if the Mojo requires that dependencies be resolved before execution. @aggregator Marks this mojo as an aggregator. @execute 494
  • 514. Writing Plugins in Alternative Languages Provides the opportunity to execute a goal or lifecycle phase before executing this Mojo. The @execute annotation takes the following attributes: goal Name of the goal to execute phase Name of the lifecycle phase to execute lifecycle Name of the lifecycle (if other than default) For an example of an annotated Mojo class, consider the following code example: # Completes some build task # @goal custom-goal # @phase install # @requiresDependencyResolution false # @execute phase=compile class CustomMojo < Mojo ... end Mojo parameters can reference Java classes and Maven properties. The following example shows you how to get access to the Maven Project object from a Ruby Mojo. Example 18.6. Referencing a Maven Project from a Ruby Mojo # This is a mojo description # @goal test # @phase validate class Test < Mojo # @parameter type="java.lang.String" default-value="nothing" alias="a_string" def prop end # @parameter type="org.apache.maven.project.MavenProject" expression="${project}" # @required true def project end def execute info "The following String was passed to prop: '#{$prop}'" info "My project artifact is: #{$project.artifactId}" 495
  • 515. Writing Plugins in Alternative Languages end end run_mojo Test In the previous example, we can access properties on the Project class using standard Ruby syntax. If you put test.rb in firstruby-maven-plugin's src/main/scripts directory, install the plugin, and then run it, you will see the following output: $ mvn install ... [INFO] [plugin:descriptor] [INFO] Using 3 extractors. [INFO] Applying extractor for language: java ... [INFO] Applying extractor for language: jruby [INFO] Ruby Mojo File: /echo.rb [INFO] Ruby Mojo File: /test.rb [INFO] Extractor for language: jruby found 2 mojo descriptors. ... $ mvn firstruby:test ... [INFO] [firstruby:test] [INFO] The following String was passed to prop: 'nothing' [INFO] My project artifact is: firstruby-maven-plugin 18.3.3. Logging from a Ruby Mojo To log from a Ruby Mojo, call the info(), debug(), and error() methods with a message. # Tests Logging # @goal logtest # @phase validate class LogTest < Mojo def execute info "Prints an INFO message" error "Prints an ERROR message" debug "Prints to the Console" end end run_mojo LogTest 496
  • 516. Writing Plugins in Alternative Languages 18.3.4. Raising a MojoError If there is an unrecoverable error in a Ruby Mojo, you will need to raise a MojoError. Example 18.7, “Raising a MojoError from a Ruby Mojo” shows you how to raise a MojoError. This example mojo prints out a message and then raises a MojoError. Example 18.7. Raising a MojoError from a Ruby Mojo # Prints a Message # @goal error # @phase validate class Error < Mojo # @parameter type="java.lang.String" default-value="Hello Maven World" expression="${message}" # @required true # @readonly false # @deprecated false def message end def execute info $message raise MojoError.new( "This Mojo Raised a MojoError" ) end end run_mojo Error Running this Mojo, produces the following output: $ mvn firstruby:error ... INFO] [firstruby:error] [INFO] Hello Maven World [ERROR] This Mojo Raised a MojoError 18.3.5. Referencing Plexus Components from JRuby 497
  • 517. Writing Plugins in Alternative Languages A Ruby Mojo can depend on a Plexus component. To do this, you would use the expression attribute of the @parameter annotation to specify a role and a hint for Plexus. The following example Ruby Mojo, depends upon an Archiver component which Maven will retrieve from Plexus. Example 18.8. Depending on a Plexus Component from a Ruby Mojo # This mojo tests plexus integration # @goal testplexus # @phase validate class TestPlexus < Mojo # @parameter type="org.codehaus.plexus.archiver.Archiver" expression="${component.org.codehaus.plexus.archiver.Archiver#zip}" def archiver end def execute info $archiver end end run_mojo TestPlexus Please note that the attributes for an annotation in a Ruby Mojo cannot span multiple lines. If you were to run this goal, you would see Maven attempt to retrieve a component from Plexus with a role of org.codehaus.plexus.arhiver.Archiver and a hint of zip. 18.4. Writing Plugins in Groovy Groovy is a dynamic language based on the Java Virtual Machine which compiles to Java bytecode. Groovy is a project in the Codehaus community. If you are fluent in Java, Groovy will seem like a natural choice for a scripting language. Groovy takes the features of Java, pares down the syntax a bit, and adds features like closures, duck-typing, and regular expressions. For more information about Groovy, please see the Groovy web site at http://groovy.codehaus.org. 498
  • 518. Writing Plugins in Alternative Languages 18.4.1. Creating a Groovy Plugin To create a Maven Plugin using Groovy, you only need two files: a pom.xml and a single Mojo implemented in Groovy. To get started, create a project directory named firstgroovy-maven-plugin. Place the following pom.xml in this directory. Example 18.9. POM for a Groovy Maven Plugin <?xml version="1.0" encoding="UTF-8"?> <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.plugins</groupId> <artifactId>firstgroovy-maven-plugin</artifactId> <name>Example Groovy Mojo - firstgroovy-maven-plugin</name> <packaging>maven-plugin</packaging> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.codehaus.mojo.groovy</groupId> <artifactId>groovy-mojo-support</artifactId> <version>1.0-beta-3</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-plugin-plugin</artifactId> <version>2.4</version> </plugin> <plugin> <groupId>org.codehaus.mojo.groovy</groupId> <artifactId>groovy-maven-plugin</artifactId> <version>1.0-beta-3</version> <extensions>true</extensions> <executions> <execution> <goals> <goal>generateStubs</goal> <goal>compile</goal> <goal>generateTestStubs</goal> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> 499
  • 519. Writing Plugins in Alternative Languages What's going on in this POM? First, notice that the packaging of the POM is maven-plugin because we are creating a project that will package a Maven plugin. Next, note that the project depends on the groovy-mojo-support artifact in the org.codehaus.mojo.groovy group. Then under src/main/groovy in a directory org/sonatype/mavenbook/plugins, create a file named EchoMojo.groovy which contains the EchoMojo class. Example 18.10. package org.sonatype.mavenbook.plugins import org.codehaus.mojo.groovy.GroovyMojo /** * Example goal which echos a message * * @goal echo */ class EchoMojo extends GroovyMojo { /** * Message to print * * @parameter expression="${echo.message}" * default-value="Hello Maven World" */ String message void execute() { log.info( message ) } } 500
  • 520. Chapter 19. Using Maven Archetypes Warning This chapter hasn't reached Draft status yet, it is in a pre-alpha stage. I'm publishing works in progress because I believe that transparency in writing benefits both the author and the community. A book is much more than the pages (or web pages) it is printed on, and the true meaning of a book is captured in both the content and conversation it provokes. As this is a pre-alpha release of a chapter, don't worry about reporting typos. Expect them until a quality beta version of this chapter is released. If you do care to provide any feedback, tell me what you want to read. If, after reading this pre-alpha chapter you are longing to know how to X, Y, or Z. Go over to our Get Satisfaction page and file a suggestion or an idea. We're very interested in the feedback. Don't expect this chapter to be in pre-alpha for weeks and weeks, one thing I'm particularly disinterested in is leaving readers with cliffhanger endings - sections that provide 95% of the essential information only to leave them with a table that hasn't been completed or a section that was written in a hurry. This is a new practice of "Agile Writing", and I've taken care to publish complete sections. While the enumeration of third-party plugins isn't complete and this chapter lacks a section on generating artifacts, the paragraphs and third-level sections that have been published are in this version because I didn't want to sit on the content for weeks and weeks. Xpect ah lott of tipos inh this chapther(, but don't report 'em yet). Monday, October 13, 2008 - Tim O'Brien 19.1. Introduction to Maven Archetypes An archetype is a template for a Maven project which is used by the Maven 501
  • 521. Using Maven Archetypes Archetype plugin to create new projects. Archetypes are useful for open source projects such as Apache Wicket or Apache Cocoon which want to present end-users with a set of baseline projects that can be used as a foundation for new applications. Archetypes can also be useful within an organization that wants to encourage standards across a series of similar and related projects. If you work in an organization with a large team of developers who all need to create projects which follow a similar structure, you can publish an archetype that can be used by all other members of the development team. You can create a new product from an archetype using the Maven Archetype plugin from the command line or by using the project creation wizard in the m2eclipse plugin introduced in Chapter 14, Maven in Eclipse: m2eclipse. 19.2. Using Archetypes You can use an archetype by invoking the generate goal of the Archetype plugin via the command-line or with m2eclipse. 19.2.1. Using an Archetype from the Command Line The following command line can be used to generate a project from the quickstart archetype. mvn archetype:generate -DgroupId=org.sonatype.mavenbook -DartifactId=quickstart -Dversion=1.0-SNAPSHOT -DpackageName=org.sonatype.mavenbook -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.0 -DinteractiveMode=false The generate goal accepts the following parameters: groupId The groupId for the project you are creating. 502
  • 522. Using Maven Archetypes artifactId The artifactId for the project you are creating. version The version for the project you are creating (defaults to 1.0-SNAPSHOT). packageName The default package for the project you are creating (defaults to groupId). archetypeGroupId The groupId of the archetype you wish to use for project generation. archetypeArtifactId The artifactId of the archetype you wish to use for project generation. archetypeVersion The version of the archetype you wish to use for project generation. interactiveMode When the generate goal is executed in interactive mode, it will prompt the user for all the previously listed parameters. When interactiveMode is false, the generate goal will use the values passed in from the command line. Once you run the generate goal using the previously listed command line, you will have a directory named quickstart which contains a new Maven project. The command line you had to suffer through in this section is difficult to manage. In the next section we generate the same project running the generate goal in an interactive mode. 19.2.2. Using the Interactive generate Goal The simplest way to use the Maven Archetype plugin to generate a new Maven project from an archetype is to run the archetype:generate goal in interactive mode. When interactiveMode is set to true, the generate goal will present you with a list of archetypes and prompt you to select an archetype and supply the 503
  • 523. Using Maven Archetypes necessary identifiers. Since the default value of the parameter interactiveMode is true, all you have to do to generate a new Maven project is run mvn archetype:generate. $ mvn archetype:generate [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Default Project [INFO] task-segment: [archetype:generate] (aggregator-style) [INFO] [archetype:generate] [INFO] Generating project in Interactive mode [INFO] No archetype defined. Using maven-archetype-quickstart Choose archetype: 1: internal -> appfuse-basic-jsf 2: internal -> appfuse-basic-spring 3: internal -> appfuse-basic-struts 4: internal -> appfuse-basic-tapestry 5: internal -> appfuse-core 6: internal -> appfuse-modular-jsf 7: internal -> appfuse-modular-spring 8: internal -> appfuse-modular-struts 9: internal -> appfuse-modular-tapestry 10: internal -> maven-archetype-j2ee-simple 11: internal -> maven-archetype-marmalade-mojo 12: internal -> maven-archetype-mojo 13: internal -> maven-archetype-portlet 14: internal -> maven-archetype-profiles 15: internal -> maven-archetype-quickstart 16: internal -> maven-archetype-site-simple 17: internal -> maven-archetype-site 18: internal -> maven-archetype-webapp 19: internal -> jini-service-archetype 20: internal -> softeu-archetype-seam 21: internal -> softeu-archetype-seam-simple 22: internal -> softeu-archetype-jsf 23: internal -> jpa-maven-archetype 24: internal -> spring-osgi-bundle-archetype 25: internal -> confluence-plugin-archetype 26: internal -> jira-plugin-archetype 27: internal -> maven-archetype-har 28: internal -> maven-archetype-sar 29: internal -> wicket-archetype-quickstart 30: internal -> scala-archetype-simple 31: internal -> lift-archetype-blank 32: internal -> lift-archetype-basic 33: internal -> cocoon-22-archetype-block-plain 34: internal -> cocoon-22-archetype-block 35: internal -> cocoon-22-archetype-webapp 36: internal -> myfaces-archetype-helloworld 37: internal -> myfaces-archetype-helloworld-facelets 38: internal -> myfaces-archetype-trinidad 39: internal -> myfaces-archetype-jsfcomponents 40: internal -> gmaven-archetype-basic 504
  • 524. Using Maven Archetypes 41: internal -> gmaven-archetype-mojo Choose a number: 15 The first thing that the archetype:generate goal does in interactive mode is to print out a list of archetypes that it is aware of. The Maven Archetype plugin ships with an archetype catalog which includes a reference to all of the standard, simple Maven archetypes (10-18). The plugin's archetype catalog also contains a number of references to compelling third party archetypes such as archetypes which can be used to create AppFuse projects, Confluence and JIRA plugins, Wicket applications, Scala applications, and Groovy projects. For a brief overview of these third-party archetypes, see Section 19.3.2, “Notable Third-Party Archetypes”. Once you select an archetype, the Maven Archetype plugin downloads the archetype, and then asks you to supply the following values for your new project: • groupId • artifactId • version • package [INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates Downloading: http://repo1.maven.org/maven2/org/apache/maven/archetypes/maven-archetype-quick 4K downloaded Define value for groupId: : org.sonatype.mavenbook Define value for artifactId: : quickstart Define value for version: 1.0-SNAPSHOT: : 1.0-SNAPSHOT Define value for package: org.sonatype.mavenbook: : org.sonatype.mavenbook Confirm properties configuration: groupId: org.sonatype.mavenbook artifactId: quickstart version: 1.0-SNAPSHOT package: org.sonatype.mavenbook Y: : Y Once this interactive portion of the archetype:generate goal execution is finished, the Maven Archetype plugin will generate the project in a directory named after the artifactId you supplied. [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating OldArchetype: maven-archetype-quickstart:RELE 505
  • 525. Using Maven Archetypes [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: groupId, Value: org.sonatype.mavenbook [INFO] Parameter: packageName, Value: org.sonatype.mavenbook [INFO] Parameter: basedir, Value: /Users/tobrien/tmp [INFO] Parameter: package, Value: org.sonatype.mavenbook [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: artifactId, Value: quickstart [INFO] ********************* End of debug info from resources from generated POM *********** [INFO] OldArchetype created in dir: /Users/tobrien/tmp/quickstart [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1 minute 57 seconds [INFO] Finished at: Sun Oct 12 15:39:14 CDT 2008 [INFO] Final Memory: 8M/15M [INFO] ------------------------------------------------------------------------ 19.2.3. Using an Archetype from m2eclipse m2eclipse makes creating a new Maven project from a Maven Archetype very easy by providing an intuitive wizard for searching for, selecting, and configuring a Maven Archetype. For more information about generating a Maven project form a Maven Archetype using m2eclipse, see Section 14.5.2, “Erstellen eines Maven Projekts auf der Basis eines Maven Archetyps”. 19.3. Available Archetypes As more and more projects adopt Maven, more and more artifacts are being published by projects as a way to provide users with a quick way of creating projects from existing templates. This section discusses some of the simple core archetypes from the Apache Maven as well as providing a survey of some interesting third-party archetypes. 19.3.1. Common Maven Archetypes Some of the most straightforward Maven archetypes are contained in the org.apache.maven.archetypes groupId. Most of the basic archetypes under org.apache.maven.archetypes are very basic templates that include few options. 506
  • 526. Using Maven Archetypes You'll use them only to provide the most basic features that distinguish a Maven project from a non-Maven project. For example, the webapp archetype plugin described in this section just includes a stub of a web.xml file in ${basedir}/src/main/webapp/WEB-INF, and it doesn't even go as far as providing a Servlet for you to customize. In Section 19.3.2, “Notable Third-Party Archetypes” you'll see a quick survey of some of the more notable third-party archetype such as the AppFuse and Cocoon artifacts. The following archetypes can be found in the groupId org.apache.maven.archetypes: 19.3.1.1. maven-archetype-quickstart The quickstart archetype is a simple project with JAR packaging and a single dependency on JUnit. After generating a project with the quickstart archetype, you will have a single class named App in the default package with a main() method that prints "Hello World!" to standard output. You will also have a single JUnit test class named AppTest with a testApp() method with a trivial unit test. 19.3.1.2. maven-archetype-webapp This archetype creates a simple project with WAR packaging and a single dependency on JUnit. ${basedir}/src/main/webapp contains a simple shell of a web application: an index.jsp page and the simplest possible web.xml file. Even though the archetype includes a dependency on JUnit, this archetype does not create any unit tests. If you were looking for a functional web application, this archetype is going to disappoint you. For more relevant web archetypes, see Section 19.3.2, “Notable Third-Party Archetypes”. 19.3.1.3. maven-archetype-mojo This archetype creates a simple project with maven-plugin packaging and a single mojo class named MyMojo in the project's default package. The MyMojo class contains a touch goal which is bound to the process-resources phase, it creates a file named touch.txt in the target/ directory of the new project when it is executed. The new project will have a dependency on maven-plugin-api and JUnit. 507
  • 527. Using Maven Archetypes 19.3.2. Notable Third-Party Archetypes This section is going to give you a brief overview of some of the archetypes available from third-parties not associated with the Apache Maven project. If you are looking for a more comprehensive list of available archetypes, take a look at the list of archetypes in m2eclipse. m2eclipse allows you to create a new Maven project from an ever growing list of approximately 80 archetypes which span an amazing number of projects and technologies. Section 14.5.2, “Erstellen eines Maven Projekts auf der Basis eines Maven Archetyps” contains a list of archetypes which are immediately available to you when you use m2eclipse. The archetypes listed in this section are available on the default list of archetypes generated by the interactive execution of the generate goal. 19.3.2.1. AppFuse AppFuse is an application framework developed by Matt Raible. You can think of AppFuse as something of a Rosetta Stone for a few very popular Java technologies like the Spring Framework, Hibernate, and iBatis. Using AppFuse you can very quickly create an end-to-end multi-tiered application that can plugin into several front-end web frameworks like Java Server Faces, Struts, and Tapestry. Starting with AppFuse 2.0, Matt Raible has been transitioning the framework to Maven 2 to take advantage of the dependency management and archetype capabilities. AppFuse 2 provides the following archetypes all in the groupId org.appfuse.archetypes: appfuse-basic-jsf and appfuse-modular-jsf End-to-end application using Java Server Faces in the presentation layer appfuse-basic-spring and appfuse-modular-spring End-to-end application using Spring MVC in the presentation layer appfuse-basic-struts and appfuse-modular-struts End-to-end application using Struts 2 in the presentation layer appfuse-basic-tapestry and appfuse-modular-tapestry 508
  • 528. Using Maven Archetypes End-to-end application using Tapestry in the presentation layer appfuse-core Persistence and object model without the presentation layer Archetypes following the appfuse-basic-* pattern are entire end-to-end applications in a single Maven project, and archetypes following the appfuse-modular-* pattern are end-to-end applications in a multimodule Maven project which separates the core model objects and persistence logic from the web front-end. Here's an example from generating a project to running a web application for the modular Spring MVC application: $ mvn archetype:generate -DarchetypeArtifactId=appfuse-modular-spring -DarchetypeGroupId=org.appfuse.archetypes -DgroupId=org.sonatype.mavenbook -DartifactId=mod-spring -Dversion=1.0-SNAPSHOT -DinteractiveMode=false[INFO] Scanning for projects... ... [INFO] [archetype:generate] [INFO] Generating project in Batch mode [INFO] Archetype [org.appfuse.archetypes:appfuse-modular-spring:RELEASE] found in catalog in [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating OldArchetype: appfuse-modular-spring:RELEASE [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: groupId, Value: org.sonatype.mavenbook [INFO] Parameter: packageName, Value: org.sonatype.mavenbook [INFO] Parameter: basedir, Value: /Users/tobrien/tmp [INFO] Parameter: package, Value: org.sonatype.mavenbook [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: artifactId, Value: mod-spring ... [INFO] OldArchetype created in dir: /Users/tobrien/tmp/mod-spring [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL $ cd mod-spring $ mvn ... (an overwhelming amount of activity ~5 minutes) $ cd web $ mvn jetty:run-war ... (Maven Jetty plugin starts a Servlet Container on port 8080) From generating a project with the AppFuse archetype to running a web application with a authentication and user-management system takes all of 5 minutes. This is the real power of using a Maven Archetype as a foundation for a 509
  • 529. Using Maven Archetypes new application. We oversimplified the AppFuse installation process a bit and left out the important part where you download and install a MySQL database, but that's easy enough to figure out by reading the AppFuse Quickstart Documentation. 19.3.2.2. Confluence and JIRA plugins Atlassian has created some archetypes for people interested in developing plugins for both Confluence and JIRA. Confluence and JIRA are, respectively, a Wiki and an issue tracker both of which have gained a large open source user base through granting free licenses for open source projects. Both the jira-plugin-archetype and the confluence-maven-archetype artifacts are under the com.atlassian.maven.archetypes groupId. When you generate a Confluence plugin, the archetype will generate a pom.xml which contains the necessary references to the Atlassian repositories and a dependency on the confluence artifact. The resulting Confluence plugin project will have a single example macro class and an atlassian-plugin.xml descriptor. Generating a project from the Jira archetype creates a project with a single, blank MyPlugin class and an atlassian-plugin.xml descriptor in ${basedir}/src/main/resources. fr more information about developing Confluence plugins with Maven 2, see Developing Confluence Plugins with Maven 2 on the Confluence project's Wiki. For more information about developing Jira plugins with Maven 2, see How to Build and Atlassian Plugin on the Atlassian Developer Network. 19.3.2.3. Wicket Apache Wicket is a component-oriented web framework which focused on managing the server-side state of a number of components written in Java and simple HTML. Where a framework like Spring MVC or Ruby on Rails focuses on merging objects within a request with a series of page templates, Wicket is very strongly focused on capturing interactions and page structure in a series of POJO Java classes. In an age where hype-driven tech media outlets are proclaiming the "Death of Java", Wicket is a contrarian approach to the design and assembly of web applications. To generate a Wicket project with the Maven Archetype plugin: $ mvn archetype:generate ... (select the "wicket-archetype-quickstart" artifact from the interactive menu) ... 510
  • 530. Using Maven Archetypes ... (supply a groupId, artifactId, version, package) ... ... (assuming the artifactId is "ex-wicket") ... $ cd ex-wicket $ mvn install ... (a lot of Maven activity) ... $ mvn jetty:run ... (Jetty will start listening on port 8080) ... Just like the AppFuse archetype, this archetype creates a shell web application which can be immediately executed with the Maven Jetty plugin. If you hit http://localhost:8080/ex-wicket, you be able to see the newly created web application in a servlet container. Note Think about the power of Maven Archetypes versus the copy and paste approach that has characterized the last few years of web development. Six years ago, without the benefit of something like the Maven Archetype plugin, you would have had to slog through a book about AppFuse or a book about Wicket and followed circuitous pedagogy about the framework before you could actually fire it up in servlet container. It was either that or just copying an existing project and customizing it for your needs. With the Maven Archetype plugin, framework developers can now give you a working, customized shell for an application in a matter of minutes. This is a sea change that has yet to hit the enterprise development space, and you can expect that this handful of available third-party artifacts will balloon to hundreds within the next few years. 19.4. Publishing Archetypes Once you've generated a good set of artifacts, you will probably want to share them with the world. To do this, you'll need to create something called an Archetype catalog. An Archetype catalog is an XML file which the Maven Archetype plugin can consult to locate archetypes in a repository. Example 19.1, “Archetype Catalog for the Apache Cocoon Project” shows the contents of the Archetype catalog for the Apache Cocoon project which can be found at 511
  • 531. Using Maven Archetypes http://cocoon.apache.org/archetype-catalog.xml. Example 19.1. Archetype Catalog for the Apache Cocoon Project <archetype-catalog> <archetypes> <archetype> <groupId>org.apache.cocoon</groupId> <artifactId>cocoon-22-archetype-block-plain</artifactId> <version>1.0.0</version> <description>Creates an empty Cocoon block; useful if you want to add another block to </archetype> <archetype> <groupId>org.apache.cocoon</groupId> <artifactId>cocoon-22-archetype-block</artifactId> <version>1.0.0</version> <description>Creates a Cocoon block containing some small samples</description> </archetype> <archetype> <groupId>org.apache.cocoon</groupId> <artifactId>cocoon-22-archetype-webapp</artifactId> <version>1.0.0</version> <description>Creates a web application configured to host Cocoon blocks. Just add the </archetype> </archetypes> </archetype-catalog> To generate such a catalog, you'll need crawl a Maven repository and generate this catalog XML file. The Archetype plugin has a goal named crawl which does just this, and it assumes that it has access to the file system that hosts a repository. If you run archetype:crawl from the command line with no arguments, the Archetype plugin will crawl your local repository searching for Archetypes and it will create an archetype-catalog.xml in ~/.m2/repository. [tobrien@MACBOOK repository]$ mvn archetype:crawl [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'archetype'. [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Default Project [INFO] task-segment: [archetype:crawl] (aggregator-style) [INFO] ------------------------------------------------------------------------ [INFO] [archetype:crawl] repository /Users/tobrien/.m2/repository catalogFile null [INFO] Scanning /Users/tobrien/.m2/repository/ant/ant/1.5/ant-1.5.jar 512
  • 532. Using Maven Archetypes [INFO] Scanning /Users/tobrien/.m2/repository/ant/ant/1.5.1/ant-1.5.1.jar [INFO] Scanning /Users/tobrien/.m2/repository/ant/ant/1.6/ant-1.6.jar [INFO] Scanning /Users/tobrien/.m2/repository/ant/ant/1.6.5/ant-1.6.5.jar ... [INFO] Scanning /Users/tobrien/.m2/repository/xmlrpc/xmlrpc/1.2-b1/xmlrpc-1.2-b1.jar [INFO] Scanning /Users/tobrien/.m2/repository/xom/xom/1.0/xom-1.0.jar [INFO] Scanning /Users/tobrien/.m2/repository/xom/xom/1.0b3/xom-1.0b3.jar [INFO] Scanning /Users/tobrien/.m2/repository/xpp3/xpp3_min/1.1.3.4.O/xpp3_min-1.1.3.4.O.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 31 seconds [INFO] Finished at: Sun Oct 12 16:06:07 CDT 2008 [INFO] Final Memory: 6M/12M [INFO] ------------------------------------------------------------------------ If you are interested in creating an Archetype catalog it is usually because you are an open source project or organization which has a set of archetypes to share. These archetypes are likely already available in a repository, and you need to crawl this repository and generate a catalog in a file system. In other words, you'll probably want to scan a directory on an existing Maven repository and generate an Archetype plugin at the root of the repository. To do this, you'll need to pass in the catalog and repository parameters to the archetype:crawl goal. The following command line assumes that you are trying to generate a catalog file in /var/www/html/archetype-catalog.xml for a repository hosted in /var/www/html/maven2. $ mvn archetype:crawl -Dcatalog=/var/www/html/archetype-catalog.xml [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'archetype'. [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Default Project [INFO] task-segment: [archetype:crawl] (aggregator-style) [INFO] ------------------------------------------------------------------------ [INFO] [archetype:crawl] repository /Users/tobrien/tmp/maven2 catalogFile /Users/tobrien/tmp/blah.xml [INFO] Scanning /Users/tobrien/tmp/maven2/com/discursive/cas/extend/cas-extend-client-java/2 [INFO] Scanning /Users/tobrien/tmp/maven2/com/discursive/cas/extend/cas-extend-client-java/2 -Drepository=/var/www/html/maven2 ... 513
  • 533. Appendix A. Appendix: Detailinformationen zur settings.xml-Datei A.1. Übersicht Das Element settings der setting.xml Datei beinhaltet die Elemente welche benutzt werden, die Laufzeitumgebung von Maven zu konfigurieren. Einstellungen dieser Datei sind von solcher Natur, dass diese über Projektgrenzen hinweg zum Einsatz kommen und daher nicht an ein spezifisches Projekt gebunden sein sollten oder an eine bestimmte Kundschaft verbreitet werden. Werte, welche darin enthalten sind, sind z.B. die Einstellung des lokalen Repository, alternative Remote Repositorien und Authentifizierungsdaten. Die settings.xml kann bei einer Standardinstallation an zweierlei Orten aufgefunden werden: • Im Maven Installationsverzeichnis: $M2_HOME/conf/settings.xml • Als benutzerspezifische Datei des Profils: ~/.m2/settings.xml Hier eine Übersicht der Elemente der settings.xml-Datei: Example A.1. Übersicht der Elemente einer settings.xml-Datei <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <localRepository/> <interactiveMode/> <usePluginRegistry/> <offline/> <pluginGroups/> <servers/> <mirrors/> <proxies/> <profiles/> <activeProfiles/> </settings> 514
  • 534. Appendix: Detailinformationen zur A.2. Die Details der settings.xml Datei A.2.1. Einfache Wertangaben Die Hälfte der Elemente auf oberster Ebene innerhalb des Elementes settings sind einfache Wertangaben welche benutzt werden um das Verhalten von Maven zu steuern: Example A.2. Einfache Wertangaben im top level Element der settings.xml <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <localRepository>${user.dir}/.m2/repository</localRepository> <interactiveMode>true</interactiveMode> <usePluginRegistry>false</usePluginRegistry> <offline>false</offline> <pluginGroups> <pluginGroup>org.codehaus.mojo</pluginGroup> </pluginGroups> ... </settings> The simple top-level elements are: Diese top-level Wertangaben sind: localRepository This value is the path of this build system's local repository. The default value is Dieser Wert bestimmt den Pfad des lokalen Repository des Build Systems. Der Standardwert ist ${user.dir}/.m2/repository InteractiveMode true sollte Maven auf Benutzereingaben warten, ansonsten false. Standardwert true. 515
  • 535. settings.xml-Datei usePluginRegistry true wenn Maven auf ${user.dir}/.m2/plugin-registry.xml zurückgreifen soll um die Plugin Versionen zu verwalten, Standardwert ist false. offline true sollte das Build-System im offline Modus arbeiten, Standardwert ist false. Dieser Wert ist insbesondere wichtig für serverseitige Build-Syteme, welche nicht auf ein externes Repository zugreifen dürfen, sei dies aufgrund des Netzwerkes oder auch wegen Sicherheitsbestimmungen. pluginGroups Dieses Element enthält eine Liste von Elementen pluginGroup, jedes enthält eine groupId. Die Liste wird durchsucht, sollte ein Plugin von der Kommandozeile angezogen werden und der Parameter der groupId nicht gesetzt sein. Standardmässig enthält diese Liste die org.apache.maven.plugins. A.2.2. Servers Das Element distributionManagement eines POM definiert die Repositorien für die Verteilung. Gewisse Angaben, wie z.B. die Security Credentials sollten nicht mit der pom.xml Datei verteilt werden. Diese Art der Angaben sollte auf einem Build Server in der settings.xml Datei im Element servers abgelegt werden: Example A.3. Server Konfiguration der settings.xml <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <servers> <server> <id>server001</id> <username>my_login</username> <password>my_password</password> <privateKey>${user.home}/.ssh/id_dsa</privateKey> <passphrase>some_passphrase</passphrase> <filePermissions>664</filePermissions> <directoryPermissions>775</directoryPermissions> <configuration></configuration> 516
  • 536. Appendix: Detailinformationen zur </server> </servers> ... </settings> Die Elemente unter server sind: id Die eindeutige Kennung - id - des Servers (nicht des Benutzers welcher beim Einloggen zum Einsatz kommt) welche mit der id des Elementes distributionManagement des Repositories übereinstimmt. username, password Diese Elemente bilden ein Paar, sie bezeichnen das Login und Passwort welches notwendig ist, um sich gegenüber dem Server auszuweisen. privateKey, passphrase Wie vorangehend ebenfalls zwei Elemente, dieses Paar beschreibt den Pfad zu einem Privaten Schlüssel (Standardwert ist ${user.home}/.ssh/id_dsa) und einer Passphrase, sollte diese notwendig sein. Hinweis: Zukünftig sollte es möglich sein die Elemente passphrase und password zu externalisieren, zur Zeit jedoch müssen diese in Klartext in der settings.xml enthalten sein. filePermissions, directoryPermissions Sollte während des Deployments eine Repository Datei oder ein Verzeichnis angelegt werden, so sind dies die Rechte welche dort gesetzt werden. Die möglichen Werte beider Parameter sind eine dreistellige *nix artige Angabe entsprechend der Dateirechte, z.B. 664, oder 775. A.2.3. Spiegelrepositorien Example A.4. Konfiguration eines Spiegelrepositories in der settings.xml <settings 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 517
  • 537. settings.xml-Datei http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <mirrors> <mirror> <id>planetmirror.com</id> <name>PlanetMirror Australia</name> <url>http://downloads.planetmirror.com/pub/maven2</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> ... </settings> id, name Die eindeutige Kennung, id, des Spiegelrepositories. Diese id wird benutzt, um zwischen verschiedenen Elementen mirror zu unterscheiden. url Die Basis URL des Spiegelrepositories. Das Build System wird diese URL einem Repository Aufruf voransetzen, satt diese an die URL des Standard Server Repostory zu senden. mirrorOf Die id des Servers, welcher von diesem Server gespiegelt wird. Ein Beispiel, um auf eine Spiegelrepository des Maven central Server zu verweisen (http://repo1.maven.org/maven2), setzen Sie diesen Wert auf central. Der Wert muss nicht mit der id des Spiegelrepositories übereinstimmen. A.2.4. Proxies Example A.5. Konfiguration eines Proxy in der settings.xml <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <proxies> <proxy> 518
  • 538. Appendix: Detailinformationen zur <id>myproxy</id> <active>true</active> <protocol>http</protocol> <host>proxy.somewhere.com</host> <port>8080</port> <username>proxyuser</username> <password>somepassword</password> <nonProxyHosts>*.google.com|ibiblio.org</nonProxyHosts> </proxy> </proxies> ... </settings> id Die eindeutige Kennung, id, des Proxy. Diese id wird benutzt, um zwischen verschiedenen Elementen proxy zu unterscheiden active true, wenn dieser Proxy aktiviert ist. Insbesondere nützlich wenn mehrere Proxy konfiguriert sind, aber nur einer tatsächlich zum Einsatz kommen darf. protocol, host, port The protocol://host:port of the proxy, separated into discrete elements. Die Bestandteile der URL des Proxy: protocol://host:port;die Elemente werden getrennt geführt. username, password Diese Elemente bilden ein Paar, sie bezeichnen das Login und Passwort welches notwendig ist, um sich gegenüber dem Proxy auszuweisen. nonProxyHosts Unter diesem Element befindet sich eine Liste der Ressourcen welche nicht durch den Proxy abgebildet werden sollten. Das Trennsymbol der Liste ist das vom Proxy Server erwartete Zeichen; in unserem Beispiel das ‚Pipe’-Symbol. Ebenfalls häufig angewandt ist die Komma getrennte Liste. 519
  • 539. settings.xml-Datei A.2.5. Profiles Das Element profile der settings.xml-Datei ist ein verkürzter Auszug des Elements profile der pom.xml-Datei. Es besteht aus den Elementen activation, repositories, pluginRepositories und properties. Das Element beinhaltet nur diese vier Elemente, da diese das Build System als ganzes betreffen (welches der Rolle der settings.xml entspricht) und nicht einzelne Aspekte der Projekt Modell Einstellungen. Wird ein Profil innerhalb einer settings.xml aktiviert, so übersteuert dies jedes entsprechende Profil einer pom.xml oder profiles.xml Datei. A.2.6. Activation Aktivierungen sind Schlüsselemente eines Profiles. Wie Profile eines POM rührt die Mächtigkeit der Profile der settings.xml von der Möglichkeit her, Werte nur unter bestimmten Umständen/Bedingungen zu beeinflussen. Diese Bedingungen werden im Element activation bestimmt. Example A.6. Definition eines Activation Parameters in der settings.xml <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <profiles> <profile> <id>test</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.3</value> </property> <file> 520
  • 540. Appendix: Detailinformationen zur <exists>${basedir}/file2.properties</exists> <missing>${basedir}/file1.properties</missing> </file> </activation> ... </profile> </profiles> ... </settings> Die Aktivierung tritt ein, wenn alle definierten Kriterien erfüllt sind, jedoch nicht notwendigerweise alle zur gleichen Zeit. jdk activation beinhaltet eine eingebaute Java spezifische Prüfung des Elements jdk. Dieses Element wird aktiviert, sobald ein Test unter einer bestimmten Version eines JDK (angegeben durch ein entsprechendes Kürzel) läuft. Im oben angegebenen Beispiel wäre dies für 1.5.0_06 der Fall. os Mittels des Elements os können, wie oben ausgeführt, Betriebssystem spezifische Eigenschaften geprüft werden. property Das Profil wird aktiviert, sollte Maven ein Property (ein Wert, welcher innerhalb des POM mittels ${name} referenziert werden kann) mit dem entsprechenden Wertepaar finden. file Schliesslich kann ein Profil durch die Existenz oder das Fehlen einer bestimmten Datei aktiviert werden. Das Element activation ist nicht der einzige Weg, wie ein Profil aktiviert werden kann. Die Profil id kann ebenfalls innerhalb der Datei settings.xml im Element activeProfile bestimmt werden. Profile können auch explizit von der Befehlszeile mittels einer Komma getrennten Liste einem P-flag folgend, aufgerufen werden (z.B. –p test). 521
  • 541. settings.xml-Datei Um festzustellen, welche Profile von einem bestimmten Build aktiviert werden, benutzen Sie das Maven Help Plugin. mvn help:active-profiles A.2.7. Properties Maven Properties sind ein Platzhalter für Werte - wie auch Properties unter Ant. Auf deren Werte ist mittels der Notation ${x}, x ist der Name des Properties, von überall innerhalb eines POM zuzugreifen. Properties kommen in fünf verschiedenen Varianten, alle zugreifbar von innerhalb der settings.xml: env.X Kennzeichnet man eine Variable mit vorangestelltem ‘env.’ so wird der Wert der entsprechende Shell Variablen zurückgegeben. Ein Beispiel: ${env.PATH} enthält den Wert der Umgebungsvariablen $PATH (%PATH% unter Windows). project.x Ein punktnotierter (.) Pfad innerhalb der pom.xml Datei wird den entsprechenden Wert enthalten. settings.x Ein punktnotierter (.) Pfad innerhalb der settings.xml Datei wird den entsprechenden Wert enthalten. Java system properties Alle Properties welche durch java.lang.System.getProperties() offengelegt werden sind ebenfalls als POM Properties zugänglich, so zum Beispiel das Property ${java.home}. x Innerhalb eines Elements properties oder innerhalb einer externen Datei gesetzt, ist der Wert durch ${einWert} zugänglich. Example A.7. Setzen des Properties ${user.install} property in der 522
  • 542. Appendix: Detailinformationen zur settings.xml <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <profiles> <profile> ... <properties> <user.install>${user.dir}/our-project</user.install> </properties> ... </profile> </profiles> ... </settings> Das Property ${user.install} ist für dieses POM zugänglich, wenn das Profil aktiv ist. A.2.8. Repositories Repositorien sind entfernte Objektsammlungen, von welchen Maven das lokale Repository bestückt. Von diesem lokalen Repository ruft Maven die entsprechenden Abhängigkeiten oder Plugins ab. Verschiedene ferne Repositorien können verschiedene Projekte enthalten, und mittels aktiver Profile können diese nach entsprechenden Releases und Snapshot Artefakten durchsucht werden. Example A.8. Repository Konfiguration in der settings.xml <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <profiles> <profile> ... <repositories> <repository> <id>codehausSnapshots</id> 523
  • 543. settings.xml-Datei <name>Codehaus Snapshots</name> <releases> <enabled>false</enabled> <updatePolicy>always</updatePolicy> <checksumPolicy>warn</checksumPolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> <checksumPolicy>fail</checksumPolicy> </snapshots> <url>http://snapshots.maven.codehaus.org/maven2</url> <layout>default</layout> </repository> </repositories> <pluginRepositories> ... </pluginRepositories> ... </profile> </profiles> ... </settings> releases, snapshots Zu jedem Artefakttyp release und snapshot bestehen Regeln. Mit diesen zwei Gruppen hat ein POM die Möglichkeit die Regeln innerhalb eines Repositories typspezifisch und unabhängig von den anderen Typen anzupassen. Zum Beispiel kann man eine Einschränkung erstellen welche es nur erlaubt in der Entwicklung snapshot Artefakte herunterzuladen. enabled true oder false entsprechend ob ein Repository für den entsprechenden Typ zugelassen ist oder nicht (release oder snapshot) updatePolicy Dieses Element bestimmt wie oft ein Update Versuch stattfinden soll. Maven wird den Timestamp des lokalen POM mit dem des fernen Artefakten vergleichen. Die möglichen Werte sind: always, daily (Standardeinstellung), interval:X (wobei X eine ganze Zahl in Minuten ist) oder never 524
  • 544. Appendix: Detailinformationen zur checksumPolicy Wann immer Maven eine Datei in ein Repository einstellt, wird ebenfalls eine Quersummendatei eingesetellt. Sie haben hier die Wahlmöglichkeit bezüglich ignore, fail, oder warn sollte eine Quersummendatei fehlen oder fehlerhaft sein. layout In der vorangegangenen Beschreibung der Repositorien wurde erwähnt, dass alle Repositorien einem gemeinsamen Layout entsprechen. Das stimmt meistens. Maven 2 besitzt ein bestimmtes Standardlayout; jedoch baut Maven 1 auf ein anderes Format auf. Benutzen Sie diesen Parameter, um zu bestimmen, ob es default oder legacy ist. Sollten Sie von Maven 1 auf Maven 2 migrieren, und Sie möchten das bisherige Repository , welches unter Maven 1 benutzt wurde weiterverwenden, so setzen Sie layout auf legacy. A.2.9. Plugin Repositories Die Struktur des Elementblocks pluginRepositories ist ähnlich dem des Elementblocks repositories. Das Element pluginRepository spezifiziert jeweils eine entfernte Lokation, in welcher Maven Plugin Artefakte finden kann. Example A.9. Plugin Repositories in der settings.xml <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <profiles> <profile> ... <repositories> ... </repositories> <pluginRepositories> <pluginRepository> <id>codehausSnapshots</id> <name>Codehaus Snapshots</name> <releases> <enabled>false</enabled> 525
  • 545. settings.xml-Datei <updatePolicy>always</updatePolicy> <checksumPolicy>warn</checksumPolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> <checksumPolicy>fail</checksumPolicy> </snapshots> <url>http://snapshots.maven.codehaus.org/maven2</url> <layout>default</layout> </pluginRepository> </pluginRepositories> ... </profile> </profiles> ... </settings> A.2.10. Aktive Profile Example A.10. Setzen von aktiven Profiles in der settings.xml <settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <activeProfiles> <activeProfile>env-test</activeProfile> </activeProfiles> </settings> Der letzte Baustein des settings.xml Puzzles ist das Element activeProfiles. Dieses bestimmt die Menge der Elemente activeProfile, welche alle einen eigenen Wert id haben. Jede Profile-id, welche als activeProfile definiert wurde wird immer aktiv sein, ungeachtet jeglicher Umgebungseinflüsse. Sollte kein entsprechendes Profil gefunden werden, so wird nichts passieren. So wird zum Beispiel, sollte env-test ein aktives Profil in activeProfile sein, ein Profil einer pom.xml (oder auch profile.xml) mit einer entsprechenden id aktiviert. Sollte kein solches Profil gefunden werden, so wird die Verarbeitung unberührt 526
  • 547. Appendix B. Appendix: Alternativen zu den Sun Spezifikationen Das Apache Projekt Geronimo unterhält eine ganze Anzahl der Java Enterprise Spezifikationen. Table B.1, “Tabelle A.1: „Alternative Implementierungs-Artefakte der Spezifikationen“” (Tabelle A.1: „Alternative Implementierungs-Artefakte der Spezifikationen“) führt alle vom Geronimo Projekt bereitgestellten Artefakte mit deren artifactId sowie Artifact version auf. Um eine dieser Abhängigkeiten zu benutzen setzen Sie die groupId auf org.apache.geronimo.specs, gehen zu der Version der Spezifikationsimplementierung welche Sie benutzen möchten, und definieren Sie die Abhängigkeit indem Sie artifactId und artifact version wie in der Tabelle angegeben einsetzen. Note Alle Artefakte der Table B.1, “Tabelle A.1: „Alternative Implementierungs-Artefakte der Spezifikationen“” (Tabelle A.1: „Alternative Implementierungs-Artefakte der Spezifikationen“) benutzen eine groupId von org.apache.geronimo.specs. Table B.1. Tabelle A.1: „Alternative Implementierungs-Artefakte der Spezifikationen“ Spezifikation Spec Artifact Id Artifact Version Version Activation 1.0.2 geronimo-activation_1.0.2_spec 1.2 Activation 1.1 geronimo-activation_1.1_spec 1.0.1 Activation 1.0 geronimo-activation_1.0_spec 1.1 CommonJ 1.1 geronimo-commonj_1.1_spec 1.0 Corba 2.3 geronimo-corba_2.3_spec 1.1 528
  • 548. Appendix: Alternativen zu den Sun Spezifikationen Spezifikation Spec Artifact Id Artifact Version Version Corba 3.0 geronimo-corba_3.0_spec 1.2 EJB 2.1 geronimo-ejb_2.1_spec 1.1 EJB 3.0 geronimo-ejb_3.0_spec 1.0 EL 1.0 geronimo-el_1.0_spec 1.0 Interceptor 3.0 geronimo-interceptor_3.0_spec 1.0 J2EE Connector 1.5 geronimo-j2ee-connector_1.5_spec 1.1.1 J2EE Deployment 1.1 geronimo-j2ee-deployment_1.1_spec 1.1 J2EE JACC 1.0 geronimo-j2ee-jacc_1.0_spec 1.1.1 J2EE Management 1.0 geronimo-j2ee-management_1.0_spec 1.1 J2EE Management 1.1 geronimo-j2ee-management_1.1_spec 1.0 J2EE 1.4 geronimo-j2ee_1.4_spec 1.1 JACC 1.1 geronimo-jacc_1.1_spec 1.0 JEE Deployment 1.1MR3geronimo-javaee-deployment_1.1MR3_spec 1.0 JavaMail 1.3.1 geronimo-javamail_1.3.1_spec 1.3 JavaMail 1.4 geronimo-javamail_1.4_spec 1.2 JAXR 1.0 geronimo-jaxr_1.0_spec 1.1 JAXRPC 1.1 geronimo-jaxrpc_1.1_spec 1.1 JMS 1.1 geronimo-jms_1.1_spec 1.1 JPA 3.0 geronimo-jpa_3.0_spec 1.1 JSP 2.0 geronimo-jsp_2.0_spec 1.1 JSP 2.1 geronimo-jsp_2.1_spec 1.0 JTA 1.0.1B geronimo-jta_1.0.1B_spec 1.1.1 529
  • 549. Appendix: Alternativen zu den Sun Spezifikationen Spezifikation Spec Artifact Id Artifact Version Version JTA 1.1 geronimo-jta_1.1_spec 1.1 QName 1.1 geronimo-qname_1.1_spec 1.1 SAAJ 1.1 geronimo-saaj_1.1_spec 1.1 Servlet 2.4 geronimo-servlet_2.4_spec 1.1.1 Servlet 2.5 geronimo-servlet_2.5_spec 1.1.1 STaX API 1.0 geronimo-stax-api_1.0_spec 1.0.1 WS Metadata 2.0 geronimo-ws-metadata_2.0_spec 1.1.1 Note Zum Zeitpunkt, zu dem Sie das Buch lesen, kann die in der Spalte "Artifact Version" aufgeführte Version bereits veraltet sein. Um die aktuelle Versionsnummer zu überprüfen, gehen Sie mit Ihrem Browser auf http://repo1.maven.org/maven2/org/apache/geronimo/specs/ und wählen die artifactId welche Sie benutzen wollen, durch anklicken, aus. Wählen Sie die höchste Version der Spezifikation von welcher Sie abhängig sind. Hier ein Beispiel wie Sie die Table B.1, “Tabelle A.1: „Alternative Implementierungs-Artefakte der Spezifikationen“” (Tabelle A.1: „Alternative Implementierungs-Artefakte der Spezifikationen“) benutzen würden: Sollten Sie eine Implementierung vornehmen, welche von der Implementierung der JTA 1.0.1B Spezifikation abhängt, so würden Sie die folgende Abhängigkeitsdefinition in ihres Projektes pom.xml-Datei einfügen: Example B.1. Hinzufügen von JTA 1.0.1B zu einem Maven Projekt <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-jta_1.0.1B_spec</artifactId> <version>1.1.1</version> 530
  • 550. Appendix: Alternativen zu den Sun Spezifikationen </dependency> Beachten Sie wie die Version des Artifaktes sich nicht mit der Version der Spezifikation deckt – die vorgehende Abhängigkeitdefinition fügt Version 1.0.1B der JTA Spezifikation ein, benutzt aber eine Artifaktkennung von 1.1.1 (Geronimo Implementierung). Seien Sie auf der Hut, wenn Sie alternative Implementierungen einsetzen und stellen Sie jeweils sicher, dass Sie die neuste und höchste Artifaktversion der entsprechenden Spezifikationsversion einsetzen. 531