Friday, October 15, 2010

Maven, RAD and WebSphere: how to generate a set of RAD 7.5 projects for a J2EE application

This post continues the series on building RAD/WebSphere applications with Maven2.

 What we are trying to do here:
  • For a typical J2EE application, generate a set of RAD 7.5 projects using Maven's Eclipse plug-in (not to be confused with M2Eclipse, the Eclipse plug-in for Maven
  • Make sure the projects are usable "both ways": deploy on a local WebSphere server in the "normal" RAD way, and also build and deploy with Maven
  • Use Maven WAS6 plug-in to handle container-specific tasks: EJB stubs generation, bindings generation, application deployment

The main goal is to create a set-up that would not put any Eclipse/RAD-specific artefact (like .project, .classpath, .settings, etc.) in source control. Instead, the project in source control would be completely defined by the POM. The reason one would want to do that is that RAD projects -- in my humble opinion and experience -- are brittle, redundant and likely to contain settings that only make sense on a given workstation; this makes it hard to share. Another reason is that RAD is slow, rather bloated and an overkill for most tasks; if we don't standardize on RAD, people that don't use the advanced features would be able to use "vanilla" Eclipse, Net Beans, or any other IDE. Finally, making Maven POMs the single source of information about the project makes sure that this information is shared between the build script and the IDE.

Running Maven to generate RAD projects:

D:\apache-maven-2.2.1\bin\mvn.bat clean eclipse:clean eclipse:configure-workspace eclipse:eclipse -Declipse.workspace="${workspace_loc}


What follows is the Maven Eclipse plug-in configuration for the main POMs:

  • Common definitions factored out to parent 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>com.rbc.eca</groupId>
  <artifactId>J2EEProto</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>

  <modules>
    <module>../J2EEProto-EAR</module>
    <module>../J2EEProto-lib</module>
    <module>../J2EEProto-ejb</module>
    <module>../J2EEProto-web</module>
  </modules>
...
    <pluginManagement>
...
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-eclipse-plugin</artifactId>
          <version>2.8</version>
          <inherited>true</inherited>
          <configuration>

            <wtpdefaultserver>WebSphere Application Server v6.1</wtpdefaultserver>
            <wtpmanifest>false</wtpmanifest>
            <wtpapplicationxml>true</wtpapplicationxml>
            <wtpversion>1.5</wtpversion>

            <downloadSources>true</downloadSources>
            <downloadJavadocs>true</downloadJavadocs>

            <projectNameTemplate>[artifactId]</projectNameTemplate>
            <useProjectReferences>true</useProjectReferences>

            <additionalBuildcommands>
              <buildcommand>org.eclipse.wst.validation.validationbuilder</buildcommand>
              <buildcommand>org.eclipse.wst.common.project.facet.core.builder</buildcommand>
            </additionalBuildcommands>

            <additionalProjectnatures>
              <projectnature>org.eclipse.wst.common.project.facet.core.nature</projectnature>
              <projectnature>org.eclipse.wst.common.modulecore.ModuleCoreNature</projectnature>
            </additionalProjectnatures>

            <classpathContainers>
              <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/WebSphere v6.1 JRE</classpathContainer>
              <classpathContainer>org.eclipse.jst.server.core.container/com.ibm.ws.ast.st.runtime.runtimeTarget.v61/was.base.v61</classpathContainer>
              <classpathContainer>org.eclipse.jst.j2ee.internal.module.container</classpathContainer>
            </classpathContainers>

          </configuration>
        </plugin>
    </pluginManagement>
...
  </project>

  • EJB project

This project uses Eclipse plug-in definitions from the parent POM, and
also calls Maven's WAS6 plug-in to prepare the EJB for deployment
outside of RAD.

<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>com.rbc.eca</groupId>
    <artifactId>J2EEProto</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../J2EEProto/pom.xml</relativePath>
  </parent>
  <artifactId>J2EEProto-ejb</artifactId>
  <packaging>ejb</packaging>

...

  <properties>
    <was6-home>D:\IBM\SDP75\runtimes\base_v61</was6-home>
  </properties>


  <build>
...

   <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-ejb-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <ejbVersion>2.1</ejbVersion>
          <generateClient>true</generateClient>
          <archive>
            <manifest>
              <classpathPrefix>lib/</classpathPrefix>
              <addClasspath>true</addClasspath>
            </manifest>
          </archive>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <configuration>

          <additionalProjectFacets>
            <jst.ejb>2.1</jst.ejb>
            <com.ibm.websphere.extended.ejb>6.1</com.ibm.websphere.extended.ejb>
          </additionalProjectFacets>

        </configuration>
      </plugin>


    </plugins>
  </build>


  <profiles>
    <!-- Profile used for batch build, needs WebSphere's J2EE JARs -->
    <profile>
      <id>batch</id>
      <activation>
        <property>
          <name>batch</name>
          <value>true</value>
        </property>
      </activation>

      <dependencies>
        <dependency>
          <groupId>websphere</groupId>
          <artifactId>j2ee</artifactId>
          <version>6.1</version>
          <scope>system</scope>
          <systemPath>${was6-home}\lib\j2ee.jar</systemPath>
        </dependency>

        <dependency>
          <groupId>websphere</groupId>
          <artifactId>ejbruntime</artifactId>
          <version>6.1</version>
          <scope>system</scope>
          <systemPath>${was6-home}\plugins\com.ibm.ws.runtime_6.1.0.jar</systemPath>
        </dependency>

        <dependency>
          <groupId>websphere</groupId>
          <artifactId>ejbportable</artifactId>
          <version>6.1</version>
          <scope>system</scope>
          <systemPath>${was6-home}\plugins\com.ibm.ws.ejbportable_6.1.0.jar</systemPath>
        </dependency>

        <dependency>
          <groupId>websphere</groupId>
          <artifactId>iwsorbutil</artifactId>
          <version>6.1</version>
          <scope>system</scope>
          <systemPath>${was6-home}\java\jre\lib\ext\iwsorbutil.jar</systemPath>
        </dependency>

      </dependencies>

      <build>

        <plugins>

          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>was6-maven-plugin</artifactId>
            <executions>
              <execution>
                <id>integration-test</id>
                <phase>integration-test</phase>
                <goals>
                  <goal>ejbdeploy</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <legacyMode>true</legacyMode>
            </configuration>
          </plugin>

        </plugins>
      </build>
    </profile>
  </profiles>
</project>

  • EAR 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>
  <parent>
    <groupId>com.rbc.eca</groupId>
    <artifactId>J2EEProto</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../J2EEProto/pom.xml</relativePath>
  </parent>
  <artifactId>J2EEProto-EAR</artifactId>
  <packaging>ear</packaging>

  <properties>
    <was6-home>D:\IBM\SDP75\runtimes\base_v61</was6-home>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-ear-plugin</artifactId>
        <version>2.4.2</version>
        <configuration>
          <filtering>true</filtering>
          <earSourceIncludes>**/ibmconfig/**</earSourceIncludes>
          <earSourceExcludes>**/target/**</earSourceExcludes>
          <version>1.4</version>
<!-- Generating it is the simplest solution if we don't have any extra
     settings; we could use an existing one otherwise, and use Maven
     resource filtering to get correct artifact versions in it -->
          <generateApplicationXml>true</generateApplicationXml>
<!--          <applicationXml>${basedir}/src/main/application/META-INF/application.xml</applicationXml>-->
          <modules>
            <webModule>
              <groupId>${project.groupId}</groupId>
              <artifactId>J2EEProto-web</artifactId>
              <contextRoot>/J2EEProto-web</contextRoot>
            </webModule>

            <jarModule>
              <groupId>${project.groupId}</groupId>
              <artifactId>J2EEProto-lib</artifactId>
            </jarModule>
            <ejbModule>
              <groupId>${project.groupId}</groupId>
              <artifactId>J2EEProto-ejb</artifactId>
            </ejbModule>
            <ejbClientModule>
              <groupId>${project.groupId}</groupId>
              <artifactId>J2EEProto-ejb</artifactId>
            </ejbClientModule>
          </modules>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <configuration>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>was6-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>integration-test</id>
            <phase>integration-test</phase>
            <goals>
              <goal>wsStopServer</goal>
              <goal>wsUninstallApp</goal>
              <goal>wsDefaultBindings</goal>
              <goal>installApp</goal>
              <goal>wsStartServer</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <applicationName>${project.artifactId}</applicationName>
          <updateExisting>false</updateExisting>
          <earFile>${dest-ear}</earFile>
          <verbose>true</verbose>
        </configuration>
      </plugin>

    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>J2EEProto-web</artifactId>
      <version>${project.version}</version>
      <type>war</type>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>J2EEProto-test</artifactId>
      <version>${project.version}</version>
      <type>war</type>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>J2EEProto-ejb</artifactId>
      <version>${project.version}</version>
      <type>ejb</type>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>J2EEProto-ejb</artifactId>
      <version>${project.version}</version>
      <type>ejb-client</type>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>J2EEProto-lib</artifactId>
      <version>${project.version}</version>
    </dependency>

...

  </dependencies>


  <profiles>

    <!-- 
    If building for integration testing, deploy J2EEProto-test.war
     -->
    <profile>
      <id>integration-build</id>
      <activation>
        <property>
          <name>it-build</name>
          <value>true</value>
        </property>
      </activation>
      <properties>
        <exclude-integration>false</exclude-integration>
      </properties>
      <build>
      </build>
    </profile>

  </profiles>

</project>

  • Web 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>

  <parent>
    <groupId>com.rbc.eca</groupId>
    <artifactId>J2EEProto</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../J2EEProto/pom.xml</relativePath>
  </parent>

  <artifactId>J2EEProto-web</artifactId>
  <packaging>war</packaging>

...

  <properties>
    <was6-home>D:\IBM\SDP75\runtimes\base_v61</was6-home>
  </properties>

  <build>

    <plugins>
...
       <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <configuration>

          <warContextRoot>J2EEProto-web</warContextRoot>

          <additionalProjectFacets>
            <jst.web>2.4</jst.web>
            <com.ibm.websphere.extended.web>6.1</com.ibm.websphere.extended.web>
            <com.ibm.websphere.coexistence.web>6.1</com.ibm.websphere.coexistence.web>
          </additionalProjectFacets>

        </configuration>
      </plugin>

    </plugins>
  </build>

  <profiles>
...
</project>

  • Library 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>
  <parent>
    <groupId>com.rbc.eca</groupId>
    <artifactId>J2EEProto</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../J2EEProto/pom.xml</relativePath>
  </parent>
  <artifactId>J2EEProto-lib</artifactId>

...

  <build>
    <plugins>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <configuration>
          <additionalProjectFacets>
            <jst.utility>1.0</jst.utility>
          </additionalProjectFacets>
        </configuration>
      </plugin>


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

Thursday, October 14, 2010

J2EE application on RAD/WebSphere with Maven: Integration testing and other problems

Integration testing of a J2EE application with Maven was unexpectedly hard to get right, although, having got there, the end result seems very logical and almost intuitive.

The reason is probably that (1) there is no single "right" solution, (2) the task is out of the tool's "comfort zone", and (3) doing it requires really wrapping one's head around Maven's life cycle. After having spent several years -- off and on -- using the tool, it turned out I still had some understanding to acquire with (3). But, after the fact, the whole thing seems very straightforward, because it really is.

So. Given: a typical J2EE application being built as an EAR to be deployed on WebSphere, consisting of a library JAR, an EJB JAR, a Web application (WAR), and a test web application with Cactus tests for the EJBs and servlets. The application is built with Maven 2.7 and needs to deploy on WebSphere 6.1.


<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>com.rbc.eca</groupId>
  <artifactId>J2eeProto</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>

  <modules>
    <module>../J2eeProto-EAR</module>
    <module>../J2eeProto-lib</module>
    <module>../J2eeProto-ejb</module>
    <module>../J2eeProto-web</module>
  </modules>
  ...

We need: the level of build automation that would allow for automatically building the application, deploying it on a WebSphere server, and running the integration tests. In real life the sequence would be run by a continuous integration server (e.g. Hudson).

The sequence of steps that need to happen is as follows:

(1) build
(2) run unit tests
(3) deploy on test server
(4) run integration tests

The problem: Maven life cycle is geared towards a project that falls short of J2EE 1.4 (EJB 2.1) and builds a WAR file that can be unit tested, bundled, deployed, integration tested inside a single project. However, building an EAR for WebSphere requires a modular J2EE project; Maven life cycle acts on a single project.


So we need to

(1) separate unit tests in all projects from integration tests

(2) build for integration testing (adding Cactus instrumentation where applicable)

(3) run unit tests in all projects (but not integration tests) when building (up to the "package" phase of Maven's life cycle), and apply coverage reporting and the rest

(4) if packaging succeeds, prepare the resulting artifacts for deployment (this needs running application server-specific tasks -- WebSphere-specific, in our case) and deploy them (since we are doing this for automated testing, we need to stop the server, un-deploy and deploy the EAR, and start the server to diminish the number of things that could break as the application changes)

(5) run the integration tests in all projects, with the appropriate test reporting

To be continued: