tags:

views:

503

answers:

4

This question is a bit of a "best-practice" question, my team is new to Maven and I am looking for the best way forward on this topic:

When working on a project that has packaging = war, what's the best way to deploy it to your Maven repository (such as a company-wide Nexus installation)?

For example, let's say you have a webapp where the environment-specific properties files (such as database connection strings, logging configuration, etc., which differ based on dev/QA/production environments) are included in the final packaged app based on properties or environment variables. When building this type of app, what makes the most sense to deploy to your repository? Should it be the WAR file with production settings, or a JAR of all of the classes and none of the resources?

The reason I am asking is that when you are building a webapp such as this, you really have a codebase plus a set of environment-specific resource files. The former makes a lot of sense to deploy to your repository so it is available to other projects, but the latter doesn't make much sense to share and make available to others.

+2  A: 

I suggest you build a raw war project with perhaps some default files for development and deploy that to the repository.

Then you can build multiple projects which depend on the war project and provide their own specific resources.


The JavaOne 2009 presentation Getting Serious About Build Automation: Using Maven in the Real World has a good example on how this can be done

  1. Create a 'myapp-deploy' maven project
  2. In the deploy project, reference the war project

    <dependency>
        <groupId>myorg.myapp</groupId>
        <groupId>myapp-web</groupId>
        <version>${webapp.version}</version>
        <type>war</type>
    </dependency>
    
  3. In the deploy project, use an overlay to import the files from the web project

    <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
            <warName>myapp-web</warName>
            <overlays>
                <overlay>
                    <groupId>myorg.myapp</groupId>
                    <artifactId>myapp-web</artifactId>
                    <excludes>
                         <exclude>WEB-INF/classes/config.properties</exclude>
                    </excludes>
                 </overlay>
              </overlays>
              <!-- some config elements not shown here -->
     </plugin>
    
  4. In the deploy project, include the extra resources from the deploy project into the web project

    <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
            <!-- some config elements not shown here -->
            <webResources>
                <resource>
                    <directory>${patch.path}</directory>
                </resource> 
            </webResources>
         </configuration>
     </plugin>
    
Robert Munteanu
I haven't had a chance to use this approach yet, but it sounds pretty reasonable. I also really like how it was outlined in the presentation you linked to (plus it had a lot of other good info), so thanks for that!
matt b
A: 

I'm a big fan of only having one version of each deployable. If you have a production version, a test version etc, this will slow you down with the necessary checks and rebuilds. Build the war once and use the same war for each environment.

This leads to question how do you handle environment specific properties. I usually take a hybrid approach. Where possible all needed properties for all environments are packaged within the war. The war then loads correct properties depending on which environment it is in. The app can determine what environment it is in using a system property.

See here for an example using spring: http://www.developer.com/java/ent/article.php/3811931

This could easily be adapted for use without spring though.

Some properties aren't suitable for packaging up in the war e.g. production passwords. For these the above linked method can easily be used to load the properties from an external file path.

Pablojim
+1  A: 

I think you should build one war. Mucking about with overlays is too much of a temptation for developers to "just drop this fix in for that environment".

Use JNDI resources for environment variables and attach external JNDI resource file(s) (you can attach one for each type of environment if needed).

Of course you then need a process to retrieve the correct resource file and apply it to your environment (tweaking it with passwords and other sensitive variables), but that is a fairly straightforward scripting exercise.

Rich Seller
+1  A: 

I put the properties in my maven pom.xml. First off, I chop up the whole web app so that there are separate jars for the different layers; persistenc/db, service, web (e.g., controllers for spring mvc), and war. The war project has the jsps and properties/config/xml files in src/main/resources.

For example, my parent pom starts like this:

<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"&gt;
<modelVersion>4.0.0</modelVersion>

<groupId>edu.berkeley.ist.waitlist</groupId>

<artifactId>waitlist-parent</artifactId>

<modules>
    <module>../waitlist-core</module>
    <module>../waitlist-db</module>
    <module>../waitlist-web</module>
    <module>../waitlist-war</module>
    <module>../waitlistd</module>
</modules>

and

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>

        <resource>
            <directory>src/main/hbm</directory>
            <filtering>true</filtering>
        </resource>
    </resources>

and

<dependencyManagement>
    <!-- dependencies with exclusions -->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>

            <version>${version.springframework}</version>

            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

Then I have a bunch of profiles, for the various builds:

<profiles>
    <!-- ========================================================== -->
    <!-- local, hsql                                                -->
    <!-- ========================================================== -->
    <profile>
        <id>localhost-hsql</id>

        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>

        <properties>
            <!-- need to specify ${basedir} so that hsql creates its files in the right place -->
            <db2.url>jdbc:hsqldb:file:${basedir}/target/waitlistv2;shutdown=true</db2.url>

            <db2.user>sa</db2.user>
            <db2.password />

            <jdbc-db2.driverClass>org.hsqldb.jdbcDriver</jdbc-db2.driverClass>

            <db2.schema>public</db2.schema>

            <db2.hibernate.default_schema>public</db2.hibernate.default_schema>

            <db2.hibernate.dialect>org.hibernate.dialect.HSQLDialect</db2.hibernate.dialect>

            <dbunit.dataTypeFactoryName>org.dbunit.ext.hsqldb.HsqldbDataTypeFactory</dbunit.dataTypeFactoryName>

            <hbmtool.haltOnError>false</hbmtool.haltOnError>

            <log.filename>/tmp/waitlist.log</log.filename>
        </properties>
    </profile>

For example, in waitlist-war/src/main/resources is the file logback.xml, and it starts with

<configuration debug="true">
<appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="RootFileAppender">
    <!-- set in the pom.xml file -->
    <file>${log.filename}</file>

So you can see how the log.filename property from the pom is used due to maven's filtering.

(I'm specifying all of the versions in the parent's pom and the child projects just specify the dependencies they use, but without the version number. I'm not sure if this is the correct way, best practice, etc.

Also, waitlist-war depends on waitlist-web, waitlist-web depends on waitlist-core (my service layer), and waitlist-core depends on waitlist-db. Waitlistd is a separate app that has no ui to speak of and it depends on waitlist-core.)

lumpynose
I forgot to say that what I like about my method, as opposed to, for example, pablojim's (via the link) is that with mine I only have 1 config/xml file for all layers, and maven generates the real file that is put in the war. For example, I have only one logback.xml, one applicationContext.xml, web.xml, etc.
lumpynose
Confusing wording; the jsps are in src/main/webapp not src/main/resources.
lumpynose
Thanks. Did you know that you can put those properties in an external file? Might reduce the size of your pom.xml. http://maven.apache.org/guides/getting-started/index.html#How_do_I_filter_resource_files Set a filter node under build/filters.
matt b