Java EE7 and Maven project for newbies – part 4 – defining the ear module
Resuming from the previous parts
We are resuming for the 4th part, our simple project currently has
- a web maven module (a war)
- an ejb module (ejb) holding our stateless session beans (EJB 3.1)
- and a second (ejb) module holding our entity beans (JPA2)
but we are still missing the one to package them all, archive, which will be of ‘ear’ type (aka Enterprise Archive).
Defining our ear maven module
As you can see in the image below, we create emtpy folder called sample-ear under the sample-parent. This folder needs to have a pom.xml file. Our new module needs to be correctly referenced in the ‘modules‘ section of the sample-parent\pom.xml.
The main purpose of our ear maven module is to ‘configure’ the famous maven-ear-plugin, which is going to be invoked by maven and is going to produce our final deployable application.
There 2 simple things we need to do, add configuration for the maven-ear-plugin, and add our ‘internal‘ application dependencies on the ear module, so that it ‘knows’ which modules should look up. Let’s have a look:
Inside the ear pom.xml
<build> <finalName>sampleapp</finalName> <plugins> <!--Ear plugin -creating the ear - watch out skinny WARS!--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ear-plugin</artifactId> <configuration> <finalName>sampleapp</finalName> <defaultJavaBundleDir>lib/</defaultJavaBundleDir> <skinnyWars>true</skinnyWars> <modules> <webModule> <groupId>gr.javapapo</groupId> <artifactId>sample-web</artifactId> </webModule> <ejbModule> <groupId>gr.javapapo</groupId> <artifactId>sample-services</artifactId> </ejbModule> </modules> </configuration> </plugin> </plugins> </build>
This is the build, section make note on the following things:
- Remember as we did other modules, we have defined some basic common configuration for our plugin, in the ‘parent‘ pom. Go back and have a look what is already there for you.
- Watch out the ‘defaultJavaBundleDir‘ this where we define where all the libraries (apart from the top-level modules that will reside in our ear, usually is a sub-folder in the ear called ‘lib’.
- What is a top level module? It is actually, the jar(s), and wars that are going to be packaged in the ear, and are considered first level citizens,as you can see we define 2, the sample-web and the sample-services.
- Watch out the ‘skinnyWars‘ property. With this switch enabled, we enforce a certain pattern on packaging our third party libs, referenced from our war project. Simply put, our war archives are NOT going to include any external libraries we might define as dependencies under their WEB-INF\lib folder, instead all those libs,they are going to be packaged in the ‘defaultJavaBundleDir‘ path on the ear level.
The above configuration is not going to work, if we dont add the ‘dependencies’ section of our ear-pom.
<!-- our in app dependencies--> <dependencies> <dependency> <groupId>gr.javapapo</groupId> <artifactId>sample-web</artifactId> <version>${project.version}</version> <type>war</type> </dependency> <dependency> <groupId>gr.javapapo</groupId> <artifactId>sample-services</artifactId> <version>${project.version}</version> <type>ejb</type> </dependency> </dependencies>
Make note of the following:
- the dependency element in this pom, needs the ‘type’ attribute.
One good question you may have is, where the sample-domain (jar) module?
Well this module, is not promoted as a top level element in our ear, because we are going to add it as a dependency on the sample-services module. So our services will hold a dependency on the module of the entity beans. (Sounds fair). So we need to update the pom.xml of our sample-services module.
<artifactId>sample-services</artifactId> <name>sample-services</name> <description>EJB service layer</description> <packaging>ejb</packaging> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> </dependency> <dependency> <groupId>gr.javapapo</groupId> <artifactId>sample-domain</artifactId> <version>${project.version}</version> </dependency> </dependencies> </project>
By doing that, the sample-services.jar is going to ‘fetch’ along the sample-domain.jar. By default (remember Maven is all about conventions), when we define a top level module to an ear,l ike the sample-services, it’s dependencies are bundled automatically under the defaultJavaBundleDir lib of the ear! So when we package our ear, we will be expecting to see the sample-domain jar packaged.
One more missing dependency
After our first ‘ in app dependency between the services module and the entities module, we need another one. Our war module, (web layer) is going to use some of our services, but in order to being able to do it needs to have a dependency on the ‘services’ module. So we need to the pom.xml on the sample-web project, accordingly.
<packaging>war</packaging> <build> <finalName>${project.artifactId}</finalName> </build> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>gr.javapapo</groupId> <artifactId>sample-services</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
Let’s package our war.
We are ready for now, our basic dependencies are set, our ear is configured, we just need to package. Under the sample-parent folder level on command line we just need to type:
mvn clean package
We are done, let’s check under the ‘target’ folder of the sample-ear module. Our final ear is ready, maven also creates the ‘exploded‘ version of the ear, (it is, expanded in the image below). Notice our 2 top level ear elements, and how the sample-domain.jar is under the ‘lib’ folder of our ear. Also notice that some basic libraries like the javaee-api.jar are not included in the lib folder. Since we have added the provided in the pom. (see the final version of the xml).
One last thing…skinny war(s) and MANIFEST.MF files
Eventually, we could stop here, our final ear is ok and is going to work, but with all the above configuration, especially with our preference to create, skinny wars , we need to pay attention to a small detail. MANIFEST files are special descriptors within jars and wars, that are used by application servers on locating and class loading ‘dependent’ jars in the class path, within the ear.
Our small problem resides in the MANIFEST.MF file of the sample-web.war. If we unpack the generated war file and we open with a text editor the MANIFEST.MF we will see is something like that.
Manifest-Version: 1.0 Built-By: papo Build-Jdk: 1.7.0_45 Class-Path: lib/sample-services-0.0.1-SNAPSHOT.jar lib/sample-services-0.0 .1-SNAPSHOT.jar lib/sample-domain-0.0.1-SNAPSHOT.jar Created-By: Apache Maven 3.2.1 Archiver-Version: Plexus Archiver
Can you spot the mistake? By default the MANIFEST.MF generated, is indicating a wrong path for one of our top-level ejb jars(sample-services). Our sample-services.jar is not place under the \lib within the ear, but is a top level element. So how are we going to create a correct MANIFEST?
Eventually we need to fine tune a bit the maven-war plugin. We need to overwrite the default behaviour as specified in the parent pom, and specify a correct entry for this particular dependency. If you happen to have more than one, then you need to append all the jars that are top level elements in the configuration (make sure you do it properly, use a space between entries).So in the sample-war pom we need to add some configuration (extra) on top of the one applied. See the image below.
There is an interesting stackoverflow issue, that you can read more about this, little trick or other potential workarounds in case you use skinny-wars.
That’s it, our ear is ready.
Summary
You can find the final version for this post in this Git Tag.With this post, we are completing a first series of posts, starting from scratch, applying basic maven principles and creating some basic maven modules for a java enterprise application. Feel free to re-use this example and extend it in order to meet your own needs. It is by far complete in terms of covering all your needs, but it is a solid example on getting starting, thinking and configuring in Maven.
I am going to expand on this example, adding more modules and using more features of maven in future posts.
Reference: | Java EE7 and Maven project for newbies – part 4 – defining the ear module from our JCG partner Paris Apostolopoulos at the Papo’s log blog. |
The property ‘defaultJavaBundleDir’ has been replaced by ‘defaultLibBundleDir’. http://jira.codehaus.org/browse/MEAR-185.
I was working on these exemple in eclipse luna + jboss 7.1.1 + maven 3.2.2 . your exemple work great but when it come to defaultJavaBundleDir the jar files was not placed in the lib folder, they was deployed in the root of project-ear.ear folder in jboss-as-7.1.1.Final\standalone\deployments.
after changing ‘defaultJavaBundleDir’ to ‘defaultLibBundleDir’ the issue was resolved.
Thanks for posting such a valuable information. Look forward to incorporate in my EAR project.