How to build Apache Sling projects using Maven

In the previous post, we had a look at some of the samples included with the Sling framework.

In this post, I'll walk you through the steps I followed when setting up an Apache Sling project that uses Maven.

Note: Apache Sling is part of the Adobe Experience Manager (AEM) stack.

Prerequisites

  • OpenJDK for Java 1.8
  • Git
  • Maven (I'm using 3.3.9)
  • The Eclipse IDE for Java EE Developers (I'm using Neon)
  • The Sling Launchpad

Note: This post will walk you through the steps required to install the OpenJDK for Java 1.8. This post will walk you through the steps required to install Git, Maven and the Eclipse IDE. And, this post introduces the Apache Sling Launchpad.

Getting Started

When developing a Sling project you usually create both user interface components and OSGi services. And, these are typically divided into seperate modules.

A multi-module Maven project

A multi-module Maven project is defined by a parent POM referencing one or more modules. We can create a parent POM (and a directory for our new project) by using the pom-root Maven archetype as follows:

mvn archetype:generate \
    -DarchetypeGroupId=org.codehaus.mojo.archetypes \
    -DarchetypeArtifactId=pom-root \
    -DarchetypeVersion=RELEASE \
    -DgroupId=org.robferguson.sling.project \
    -DartifactId=sling-multi-module-maven-project \
    -Dversion=1.0.0-SNAPSHOT \
    -DinteractiveMode=false

The pom-root archetype will create a directory for our new project (i.e., /sling-multi-module-maven-project) and a (very basic) parent POM:

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>org.robferguson.sling.project</groupId>
    <artifactId>sling-multi-module-maven-project</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

</project>

The parent POM is the ideal place to store shared configuration information, so let's update it as follows:

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">

    ...

    <properties>
        <sling.username>admin</sling.username>
        <sling.password>admin</sling.password>
    </properties>

    <modules>
    </modules>

</project>

Apache Sling Maven Archetypes

Apache Sling includes several Maven archetypes designed to help kickstart Sling projects.

The Sling Initial Content Archetype

Now that we have a parent POM, we can use the sling-initial-content-archetype to create a "ui" module (where our initial content and scripts are located):

mvn archetype:generate \
    -DarchetypeGroupId=org.apache.sling \
    -DarchetypeArtifactId=sling-initial-content-archetype \
    -DgroupId=org.robferguson.sling.project \
    -DartifactId=ui \
    -Dversion=1.0.0-SNAPSHOT \
    -Dpackage=org.robferguson.sling.project.ui \
    -DappsFolderName=project \
    -DartifactName="ui" \
    -DpackageGroup="ui" \
    -DinteractiveMode=false

This is what the sling-initial-content-archetype's generated project structure looks like:

├── /ui
    └── /src
        └── /main
            └── /resources
                └── /SLING-INF
                    └── /content (place your initial content here)
                        ├── my.first.node.xml
                    └── /nodetypes
                        ├── nodetypes.cnd
                    └── /scripts (place your scripts here)
                        ├── html.esp
    └── /target
    ├── pom.xml
Initial Content Loading

Like me you probably want to provide your application with some initial content, that's why Apache Sling provides support for both registering node types and loading initial content into a repository.

The scaffolding generated by the sling-initial-content-archetype includes a Node Type Definition File (nodetypes.cnd):

[my:node] > nt:unstructured
    - title (string)
    - description (string)

And, a XML Descriptor File (my.first.node.xml):

<node>
    <primaryNodeType>my:node</primaryNodeType>

    <property>
        <name>title</name>
        <type>String</type>
        <value>My first node</value>
    </property>

    <property>
        <name>description</name>
        <type>String</type>
        <value>This node has been created by a sling bundle.</value>
    </property>
</node>

Note: Apache Sling also provides support for JSON Descriptor Files.

If you take a look at the "ui" module's POM:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 

    ...

    <parent>
        <artifactId>sling-multi-module-maven-project</artifactId>
        <groupId>org.robferguson.sling.project</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <version>${maven-bundle-plugin.version}</version>
                <configuration>
                    <instructions>
  <Sling-Nodetypes>SLING-INF/nodetypes/nodetypes.cnd</Sling-Nodetypes>
  <Sling-Initial-Content>
  SLING-INF/scripts;overwrite:=true;uninstall:=true;path:=/apps/my/node,
  SLING-INF/content;overwrite:=true;uninstall:=true;path:=/content
  </Sling-Initial-Content>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>

    ...

</project>

You'll notice that the configuration section of the maven-bundle-plugin references the node type definition file (SLING-INF/nodetypes/nodetypes.cnd) and the initial content path (SLING-INF/scripts and SLING-INF/content).

You can of course create content using tools like curl:

curl -u admin:admin -F":operation=import" -F":contentType=json" \
    -F":contentFile=@posts.json" http://localhost:8080/content

To retrieve the posts.json file:

curl http://localhost:8080/content/posts.json

To delete the posts.json file:

curl -u admin:admin -F":operation=delete" \
    http://localhost:8080/content/posts

The Sling Bundle Archetype

Now, lets use the sling-bundle-archetype to create a "core" module (where Java files that are used in OSGi services and Sling servlets are located):

mvn archetype:generate \
    -DarchetypeGroupId=org.apache.sling \
    -DarchetypeArtifactId=sling-bundle-archetype \
    -DgroupId=org.robferguson.sling.project \
    -DartifactId=core \
    -Dversion=1.0.0-SNAPSHOT \
    -Dpackage=org.robferguson.sling.project.core \
    -DappsFolderName=project \
    -DartifactName="core" \
    -DpackageGroup="core" \
    -DinteractiveMode=false

This is what the sling-bundle-archetype's generated project structure looks like:

├── /core
    └── /src
        └── /main
            └── /java
                └── /org
                    └── /robferguson
                        └── /sling
                            └── /project
                                └── /core
                                    ├── SimpleDSComponent.java
    └── /target
    ├── pom.xml

The Sling archetypes will also update the parent POM:

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">

    ...

    <properties>
        <sling.username>admin</sling.username>
        <sling.password>admin</sling.password>
    </properties>

    <modules>
        <module>ui</module>
        <module>core</module>
    </modules>

</project>

Depending on the complexity of your project, you may want to refine this setup and create separate modules for individual areas of your project. For example, it is common practice to have separate bundles for infrastrutural components (e.g., loggers) and business components (e.g., workflow steps).

References: