How to Add Functionality to your AEM Site When the Standard Features aren't Adequate
Oct 29, 2024
Adobe Experience Manager (AEM) is one of the leading digital experience platforms (DXPs). It offers a variety of out-of-the-box features that help marketing teams create engaging digital experiences and websites.
However, while AEM includes content management, digital asset management, personalization, and more, enterprises may need to extend or customize AEM to fit their particular use case.
Unfortunately, customizing AEM presents significant challenges due to its complex architecture. Custom components or workflows can lead to dependency conflicts with AEM’s core libraries, and maintaining compatibility through upgrades adds ongoing difficulty.
One of the most frequently asked questions in the Adobe Community concerns embedding an external JAR into AEM, which enables developers to customize AEM or integrate with other tools. In this article, we’ll explain what happens when we embed a JAR or external dependencies into AEM and provide solutions to some of the problems that might occur.
The Basics
To embed dependencies, you must be aware of three concepts and how they relate: the OSGi framework, bundles, and JARs.
AEM is constructed using the OSGi (Open Service Gateway Initiative) framework, which facilitates the creation of software applications in a modular and interoperable fashion. At its core, OSGi manages the lifecycle of different types of OSGi components and their interoperability. One OSGi component type is the bundle.
A bundle is a JAR file with extra instructions on operating within the OSGi environment. These instructions are detailed in the MANIFEST.MF
file. Bundles are the standard way to package and understand Java code in an OSGi environment. It's important to note that regular JAR files can't just be plopped into OSGi environments— they must be properly bundled first. That's why, when dealing with Java in AEM, we encapsulate and transform our code into a "bundle" format before deploying it into AEM.
The Issue
A "dependency" means that one piece of code relies on another software component to function correctly. In AEM, this frequently means depending on JAR files that must be accessible within the OSGi environment. We often use terms like "external dependency," "3rd party dependency," or "external JAR" to describe this reliance. However, issues occur when these dependencies are unavailable in the OSGi environment at runtime. This typically results in our bundle remaining inactive and encountering unresolved dependencies, leading to an error similar to the following:
Note: For the code examples of this blog, I will be using the JWT dependency available in the Maven Central repository: io.jsonwebtoken
The Solution
No one solution will work for all scenarios. However, various solutions will work for different use cases.
Your Dependency (JAR) Is an OSGi Bundle.
If you have a dependency that turns out to be an OSGi bundle, or if you decide to convert the JAR to an OSGi bundle yourself, you have a few options.
- Install the bundle manually through the OSGi console. Of course, this is not the recommended approach, but it is still an option, especially if you are doing local development, a proof of concept (POC), testing, etc., and aren’t concerned about how to handle this.
To do this, open the OSGi console in AEM and manually install the bundle through the UI.
-
Use the recommended plugin to install the bundle during code deployment. Using this approach, you can leverage the
filevault-package-maven-plugin
to "drop" your bundle into your code repository's "install" folder. To use this option, you only need to add the dependency as an embed entry as part of thefilevault-package-maven-plugin
configuration. You can do something like below:<!-- ====================================================================== --> <!-- V A U L T P A C K A G E P L U G I N S --> <!-- ====================================================================== --> <plugin> <groupId>org.apache.jackrabbit</groupId> <artifactId>filevault-package-maven-plugin</artifactId> <extensions>true</extensions> <configuration> <group>com.esteban.site</group> <packageType>container</packageType> <!-- skip sub package validation for now as some vendor packages like CIF apps will not pass --> <skipSubPackageValidation>true</skipSubPackageValidation> <embeddeds> <!-- Add your Bundle here --> <embedded> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <type>jar</type> <target>/apps/estebanSite-vendor-packages/application/install</target> </embedded> </embeddeds> </configuration> </plugin>
-
Perform manually what the plugin automates for you. Essentially, the bundle is installed into AEM when the JAR reaches the "install" directory under
apps
. By definition, if any valid bundle can reach such locations, it will be installed in the OSGi environment. You could even install the bundle by manually adding an "install" folder under your code project and adjusting thefilter.xml
rules for it to be installed in a location similar to this:ui.apps/src/main/content/jcr_root/apps/myProject/install
. While not recommended, this approach would still function. It could be a valid option, particularly if you are working on an older project where thefilevault-package-maven-plugin
is unavailable.
Your Dependency (JAR) Is NOT an OSGi Bundle
It’s not possible to directly use a JAR (a non-OSGi bundle) in AEM. Before installing any JAR dependency, you must "convert" this into a Bundle. For this task, and since most AEM projects use Maven to build their projects, two well-known Maven plugins can accomplish this: maven-bundle-plugin
and bnd-maven-plugin, which is the preferred option.
But what should you do if your dependency is not already a bundle? There are a few options.
Manually Convert the JAR to a Bundle
Manually converting a JAR to a bundle is a straightforward approach documented in various sources. There are a couple of options to do it:
-
Create a manual
Manifest.txt
file and wrap it along with your JAR. This can be achieved using a Java command such as:jar cvfm <bundle-name> myManualManifest.txt <jarfile-name>
-
Utilizing the Plug-in Development options available in the Eclipse IDE.
-
Leveraging the
bnd
tool
Maven Bundle Plugin
Although this is no longer the preferred plugin for building an OSGi bundle, it was adopted in the early AEM Archetypes. If you are working on an AEM project based on an old AEM Project Archetype (<20), it's likely that you are dealing with this plugin.
The good news is that this plugin can smoothly handle the embedding of dependencies. To include the dependency JAR, you can use the embed-dependency
tag and configure what to embed as part of your bundle. One great feature of this plugin is that it manages transitive dependencies for you. A "transitive dependency" is a "dependency of your dependency," which can become a headache when unaware. However, the Embed-Transitive
tag will also instruct the plugin to include all the transitive dependencies in your bundle. To embed a dependency as part of your bundle, you will have to use something like the below:
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>*;resolution:=optional</Import-Package>
<Export-Package>
io.jsonwebtoken.*;version=0.9.1
</Export-Package>
<Embed-Dependency>*</Embed-Dependency>
<Embed-Transitive>true</Embed-Transitive>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
Bnd Maven Plugin
This is now the preferred plugin for building OSGi bundles and has been included in the AEM Archetypes since 2019. Unlike the Maven Bundle Plugin, this plugin does not have a direct counterpart for embedding a JAR into your bundle. However, this task can be narrowed down to either using -conditionalpackage
or -includeresource
instructions with varying configuration options.
You can read more about these options here: Instruction Index. The preferred method is to use -includeresource
along with lib=true
. At a high level, this configuration adds the entire dependency JAR within the bundle and handles classpath inclusion.
Below is an example of how to embed a dependency and explicitly avoid transitive dependencies that are not necessary for the original dependency to work correctly:
<build>
<plugins>
<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-maven-plugin</artifactId>
<executions>
<execution>
<id>bnd-process</id>
<goals>
<goal>bnd-process</goal>
</goals>
<configuration>
<bnd>
<![CDATA[
Import-Package: android.util;resolution:=optional,org.bouncycastle.jce;resolution:=optional;version="[1.56,2)",org.bouncycastle.jce.spec;resolution:=optional;version="[1.56,2)",javax.annotation;version=0.0.0,*
-includeresource: jjwt-0.9.1.jar;lib:=true
]]>
</bnd>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Common Issue
The archetype designed for AEMaaCS ships and enables the aemanalyser-maven-plugin
, which is responsible, among other things, for checking that your bundles successfully satisfy dependencies between each other. You may encounter an error similar to the one below:
[ERROR] org.acme:mybundle:0.0.1-SNAPSHOT: Bundle xxx.yyyy:mybundle:0.0.1-SNAPSHOT is
importing package(s) org.acme.foo in start level 20
but no bundle is exporting these for that start level.
This issue likely occurred because your dependencies were not correctly embedded. Consequently, the aemanalyser
plugin could not verify that your dependencies would be available at runtime. To resolve this, you can utilize the bnd-maven-plugin
to embed the dependency (including its transitive dependencies) into your bundle, as previously explained.
Another slightly more complex alternative would involve wrapping all dependencies into a separate bundle. This approach allows the aemanalyser
plugin to recognize that your bundles are exporting what they are importing. Essentially, this strategy involves creating two bundles: one dedicated to exporting third-party dependencies and another responsible for consuming them.
Wrapping Up
Managing third-party dependencies and embedding external JARs in AEM can be complex. Ensuring compatibility, avoiding conflicts, and adhering to OSGi standards are crucial for maintaining the stability and performance of your AEM environment. This is where partnering with experts like Oshyn, a certified AEM partner, can make a significant difference.
With deep expertise in AEM architecture, Oshyn can streamline the integration of external JARs, optimize your development processes, and provide the technical support needed to keep your projects on track. This allows your team to focus on innovation and growth.
Read our Adobe Experience Manager Implementation Best Practices ebook to learn more about getting the maximum out of AEM.
Related Insights
-
Esteban Bustamante
-
Francisco Cornejo
-
Rafael Maldonado
-
Jonathan Saurez
AEM Dispatcher Cache Basics
What You Need to Know
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.