Overview

This document describes how to set up an AEM project based on Apache Maven.

Apache Maven is an open source tool for managing software projects by automating builds and providing quality project information. It is the recommended build management tool for AEM projects.

Building your AEM Project based on Maven offers you several benefits:

An IDE-agnostic development environment

Usage of Maven Archetypes and Artifacts provided by Adobe

Usage of Apache Sling and Apache Felix tool sets for Maven based development setups

Ease of import into an IDE; for example, Eclipse and/or IntelliJ

Easy integration with Continuous Integration Systems

Experience Manager API Dependencies

What is the UberJar?

The "UberJar" is the informal name given to a special Java Archive (JAR) file provided by Adobe. This JAR file contains all of the public Java APIs exposed by Adobe Experience Manager. It includes limited external libraries as well, specifically all public APIs available in AEM which come from the Apache Sling, Apache Jackrabbit, Apache Lucene, Google Guava, and two libraries used for image processing (Werner Randelshofer's CYMK JPEG ImageIO library and the TwelveMonkeys image library). The UberJar only contains API interfaces and classes, meaning that it only contains interfaces and classes which are exported by an OSGi bundle in AEM. It also contained a MANIFEST.MF file containing the correct package export versions for all of these exported packages, thus ensuring that projects built against the UberJar have the correct package import ranges.

Why did Adobe create the UberJar?

In the past, developers had to manage a relatively large number of individual dependencies to different AEM libraries and when each new API was used, one or more individual dependencies had to be added to the project. On one project, the introduction of the UberJar resulted in 30 separate dependencies being removed from the project.

How to I use the UberJar?

If you are using Apache Maven as a build system (which is the case for most AEM Java projects), you will need to add one or two elements to your pom.xml file. The first is a dependency element adding the actual dependency to your project:

If your company is already using a Maven Repository Manager such as Sonatype Nexus, Apache Archiva, or JFrog Artifactory, add the appropriate configuration to your project to reference this repository manager and add Adobe's Maven repository (https://repo.adobe.com/nexus/content/groups/public/) to your repository manager. If you are not using a repository manager, then you will need to add a repository element to your pom.xml file:

It is also possible to configure these repositories in your Maven settings.xml file.

Users of other build systems (for example, Apache Ant, Gradle) should follow similar steps, adapted to the specific syntax of their chosen tool.

What can I do with the UberJar?

With the UberJar, you can compile project code which depends upon AEM APIs (and the APIs used by the projects mentioned above). You can also generate OSGi Service Component Runtime (SCR) and OSGi Metatype information. With some limitations, you can also write and execute unit tests.

What can't I do with the UberJar?

Because the UberJar contains only APIs, it is not executable and cannot be used to run Adobe Experience Manager. To run AEM, you need the AEM Quickstart, either Standalone or Web Application Archive (WAR) form.

You mentioned limitations on unit tests. Please explain further.

Unit tests generally interact with product APIs in three different ways, each of which is impacted slightly differently by the UberJar.

Use Case #1 - Custom Code which calls a API interface

This case, which is the most common, involves some custom code which executes methods on a Java interface defined by the AEM API. The implementation of this interface may either be provided directly or be injected using the Dependency Injection pattern. This use case can be handled with the UberJar.

To unit test either of these methods, a developer would use a mocking framework such as JMockit, Mockito, JMock, or Easymock to create a mock object for the AEM API referenced. These samples use JMockit, but for this particular use case, the difference between these frameworks is largely syntatical.

Use Case #3 - Custom code which extends a base class from the API

As with SCR Generation, if your code extends a base class (abstract or concrete) from the AEM API, you must use the UberJar in order to test it.

Common Development Tasks with Maven

How-To Add Paths to the content Module

The content module contains a file src/main/content/META-INF/vault/filter.xml which defines the filters for the AEM package that is built by Maven. The file that is created by the Maven archetype looks like this:

Adding Paths to the Package Without Syncing Them

If you have files that should be added to the package that is built by the content-package-maven-plugin but that should not be synchronized between the file system and the repository, you can use .vltignore files. These files have the same syntax as .gitignore files.

For example, the archetype uses a .vltignore file to prevent the JAR file that is installed as part of the bundle from being synced back to the file system:

src/main/content/jcr_root/apps/myproject/install/.vltignore

*.jar

Syncing Paths Without Adding Them to the Package

In some cases, you may want to keep particular paths synchronized between the file system and the repository, but not have them included in the package that is built to be installed into AEM.

A typical case is the /libs/foundation path. For development purposes, you may want to have the contents of this path available in your file system, so that e.g. your IDE can resolve JSP inclusions that include JSPs in /libs. However, you don't want to include that part in the package you build, as the /libs part contains product code that must not be modified by custom implementations.

To achieve this, you can provide a file src/main/content/META-INF/vault/filter-vlt.xml. If this file exists, it will be used by the VLT tool, e.g. when you perform vlt up and vlt ci, or when you have set vlt sync set up. The content-package-maven-plugin will continue to use the file src/main/content/META-INF/vault/filter.xml when creating the package.

For example, to make /libs/foundation available locally for development, but only include /apps/myproject in the package, use the following two files.

src/main/content/META-INF/vault/filter-vlt.xml

You will also need to reconfigure the maven-resources-plugin to not include these files in the package: the filter.xml file is not applied when the package is installed but only when the package is built again using package manager.

How-To Work with JSPs

The Maven setup described so far creates a content package that can also include components and their corresponding JSPs. However, Maven treats them as any other file that is part of the content package and does not even recognize them as JSPs.

The resulting components work in AEM all the same, but making Maven aware of the JSPs has two major benefits

it allows Maven to fail if the JSPs contain errors, so that these are surfaced at build time and not when they are first compiled in AEM

For IDEs that can import Maven projects, this also enables code completion and tag library support in the JSPs

Two things are required to enable this setup:

add tag library dependencies

compile the JSPs as part of the Maven compile process

Adding Tag Library Dependencies

Below dependencies need to be added to the content modules's POM.

Note:

Unless you are importing the product dependencies as described in Importing AEM Product Dependencies above, they also need to be added to the parent POM along with the version matching your AEM setup as described in Adding Dependencies above. The comments in each entry below show the package to search for in the Dependency Finder.

Note:

The com.adobe.granite.xssprotection artifact is not included in the cq-quickstart-product-dependencies POM and requires full Maven coordinates as obtained from the Dependency Finder.

Compiling JSPs as part of the Maven Compile Phase

we set up an execution for the jspc goal (which by default binds to the compile phase, so we don't need to specify the phase explicitly)

we tell it to compile any JSPs in ${project.build.directory}/jsps-to-compile

and output the result to ${project.build.directory}/ignoredjspc (which translates to myproject/content/target/ignoredjspc)

we set up maven-resources-plugin to copy the JSPs to ${project.build.directory}/jsps-to-compile in the generate-sources phase and configure it to not copy the libs/ folder (because that is AEM product code and we neither want to incur the dependencies for compilation for our project, nor do we need to validate that it compiles.

Our primary goal, as stated above, is to validate the JSPs and make sure that the build process fails if they contain errors. This is why we compile them to a separate directory that is ignored (and in fact immediately deleted afterwards, as you will see in a minute).

The result of the Maven JspC Plugin can also be bundled and deployed as part of an OSGi Bundle, but this has other implications and side effects and goes beyond our goal of validating the JSPs.

To achieve deletion of the classes compiled from the JSPs, we set up the Maven Clean Plugin as shown below. If you want to inspect the result of the Maven JspC Plugin, run mvn compile in myproject/content -- after that, you will find the result in myproject/content/target/ignoredjspc).

Ignoring SCM control files in VLT

In some cases, you may have SCM control files in the content source tree that you do not want to be checked in to the repository.

Think of the following situation:

The archetype already created a .vltignore file to prevent the installed bundle jar file from being synced back to the file system:

src/main/content/jcr_root/apps/myproject/install/.vltignore

*.jar

Obviously, you do not want this file in your SCM either, so if e.g. you are using git, you would add a corresponding .gitignore file:

src/main/content/jcr_root/apps/myproject/install/.gitignore

*.jar

As the .gitignore file should not go into the repository either, the .vltignore file needs to be extended to include the .gitignore file:

src/main/content/jcr_root/apps/myproject/install/.vltignore

*.jar
.gitignore

How-To Work with Deployment Profiles

If your build process is part of a larger development lifecycle management setup, such as a continous integration process, you often need to deploy to other machines than just the developer's local instance.

The example below adds a profile integrationServer, which redefines the host names and ports for the author and publish instances. You can deploy to these servers by running maven from the project root as shown below.