Java™ API Best Practices
Adobe Experience Manager (AEM) is built on a rich open-source software stack that exposes many Java™ APIs for use during development. This article explores the major APIs and when and why they should be used.
AEM is built on four primary Java™ API sets.
-
Adobe Experience Manager (AEM)
- Product abstractions such as pages, assets, workflows, etc.
-
Apache Sling Web Framework
- REST and resource-based abstractions such as resources, value maps, and HTTP requests.
-
JCR (Apache Jackrabbit Oak)
- Data and content abstractions such as node, properties, and sessions.
-
OSGi (Apache Felix)
- OSGi application container abstractions such as services and (OSGi) components.
Java™ API preference “rule of thumb”
The general rule is to prefer the APIs/abstractions the following order:
- AEM
- Sling
- JCR
- OSGi
If an API is provided by AEM, prefer it over Sling, JCR, and OSGi. If AEM doesn’t provide an API, then prefer Sling over JCR and OSGi.
This order is a general rule, meaning exceptions exist. Acceptable reasons to break from this rule are:
-
Well-known exceptions, as described below.
-
Required functionality is not available in a higher-level API.
-
Operating in the context of existing code (custom or AEM product code) which itself uses a less-preferred API, and the cost to move to the new API is unjustifiable.
- It is better to consistently use the lower level API than create a mix.
AEM APIs
AEM APIs provide abstractions and functionality specific to productized use cases.
For example, AEM’s PageManager and Page APIs provide abstractions for cq:Page
nodes in AEM that represent web pages.
While these nodes are available via Sling APIs as Resources, and JCR APIs as Nodes, AEM’s APIs provide abstractions for common use cases. Using the AEM APIs ensures consistent behavior between AEM the product, and customizations and extensions to AEM.
com.adobe.* vs com.day.* APIs
AEM APIs have an intra-package preference, identified by the following Java™ packages, in order of preference:
com.adobe.cq
com.adobe.granite
com.day.cq
The com.adobe.cq
package supports product use cases whereas com.adobe.granite
supports cross-product platform use-cases, such as workflow or tasks (which are used across products: AEM Assets, Sites, and so on).
The com.day.cq
package contains “original” APIs. These APIs address core abstractions and functionalities that existed before and/or around Adobe’s acquisition of Day CQ. These APIs are supported and should be avoided, unless com.adobe.cq
or com.adobe.granite
packages does NOT provide a (newer) alternative.
New abstractions such as Content Fragments and Experience Fragments are built out in the com.adobe.cq
space rather than com.day.cq
described below.
Query APIs
AEM supports multiple query languages. The three main languages are JCR-SQL2, XPath, and AEM Query Builder.
The most important concern is maintaining a consistent query language across the code base, to reduce complexity and cost to understand.
All the query languages have effectively the same performance profiles, as Apache Oak trans-piles them to JCR-SQL2 for final query execution, and the conversion time to JCR-SQL2 is negligible compared to the query time itself.
The preferred API is AEM Query Builder, which is the highest level abstraction and provides a robust API for constructing, executing, and retrieving results for queries, and provides the following:
-
Simple, parameterized query construction (query params modeled as a Map)
-
Native Java™ API and HTTP APIs
-
AEM predicates supporting common query requirements
-
Extensible API, allowing for the development of custom query predicates
-
JCR-SQL2 and XPath can be executed directly via Sling and JCR APIs, returning results a Sling Resources or JCR Nodes, respectively.
Sling APIs
Apache Sling is the RESTful web framework that underpins AEM. Sling provides HTTP request routing, models JCR nodes as resources, provides security context, and much more.
Sling APIs have the added benefit of being built for extension, which means it is often easier and safer to augment behavior of applications built using Sling APIs than the less extensible JCR APIs.
Common uses of Sling APIs
-
Accessing JCR nodes as Sling Resources and accessing their data via ValueMaps.
-
Providing security context via the ResourceResolver.
-
Creating and removing resources via ResourceResolver’s create/move/copy/delete methods.
-
Updating properties via the ModifiableValueMap.
-
Building request processing building blocks
-
Asynchronous work processing building blocks
JCR APIs
The JCR (Java™ Content Repository) 2.0 APIs is part of a specification for JCR implementations (in the case of AEM, Apache Jackrabbit Oak). All JCR implementation must conform to and implement these APIs, and thus, is the lowest level API for interacting with AEM’s content.
The JCR itself is a hierarchical/tree-based NoSQL datastore AEM uses as its content repository. The JCR has a vast array of supported APIs, ranging from content CRUD to querying content. Despite this robust API, it is rare they’re preferred over the higher-level AEM and Sling abstractions.
Always prefer the JCR APIs over the Apache Jackrabbit Oak APIs. The JCR APIs are for interacting with a JCR repository, whereas the Oak APIs are for implementing a JCR repository.
Common misconceptions about JCR APIs
While the JCR is AEM’s content repository, its APIs are NOT the preferred method for interacting with the content. Instead prefer the AEM APIs (Page, Assets, Tag, and so on) or Sling Resource APIs as they provide better abstractions.
Common uses of JCR APIs
-
JCR observation (listening for JCR events)
-
Creating deep node structures
OSGi APIs
There is little overlap between the OSGi APIs and the higher-level APIs (AEM, Sling, and JCR), and the need to use OSGi APIs is rare and requires a high level of AEM development expertise.
OSGi vs Apache Felix APIs
OSGi defines a specification all OSGi containers must implement and conform to. AEM’s OSGi implementation, Apache Felix, provides several of its own APIs as well.
- Prefer OSGi APIs (
org.osgi
) over Apache Felix APIs (org.apache.felix
).
Common uses of OSGi APIs
-
OSGi annotations for declaring OSGi services and components.
- Prefer OSGi Declarative Services (DS) 1.2 Annotations over Felix SCR Annotations for declaring OSGi services and components
-
OSGi APIs for dynamically in-code un/registering OSGi services/components.
- Prefer the use of OSGi DS 1.2 annotations when conditional OSGi Service/Component management is not needed (which is most often the case).
Exceptions to the rule
The following are common exceptions to the rules defined above.
OSGi APIs
When dealing with low-level OSGi abstractions, such as defining or reading in OSGi component properties, the newer abstractions provided by org.osgi
are preferred over higher level Sling abstractions. The competing Sling abstractions have not been marked as @Deprecated
and suggest the org.osgi
alternative.
Also note the OSGi configuration node definition prefer cfg.json
over the sling:OsgiConfig
format.
AEM Asset APIs
-
Prefer
com.day.cq.dam.api
overcom.adobe.granite.asset.api
.- While the
com.day.cq
Assets API’s provide more complimentary tooling to AEM’s asset management use-cases. - The Granite Assets APIs support low-level asset management use-cases (version, relations).
- While the
Query APIs
- AEM QueryBuilder does not support certain query functions such as suggestions, spellcheck, and index hints among other less common functions. To query with these functions JCR-SQL2 is preferred.
Sling Servlet Registration sling-servlet-registration
- Sling servlet registration, prefer OSGi DS 1.2 annotations w/ @SlingServletResourceTypes over
@SlingServlet
Sling Filter Registration sling-filter-registration
- Sling filter registration, prefer OSGi DS 1.2 annotations w/ @SlingServletFilter over
@SlingFilter
Helpful code snippets
The following are helpful Java™ code snippets that illustrate best practices for common use cases using discussed APIs. These snippets also illustrate how to move from less preferred APIs to more preferred APIs.
JCR Session to Sling ResourceResolver
Auto-closing Sling ResourceResolver
Since AEM 6.2, the Sling ResourceResolver is AutoClosable
in a try-with-resources statement. Using this syntax, an explicit call to resourceResolver .close()
is not needed.
@Reference
ResourceResolverFactory rrf;
...
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, jcrSession);
try (ResourceResolver resourceResolver = rrf.getResourceResolver(authInfo)) {
// Do work with the resourceResolver
} catch (LoginException e) { .. }
Manually closed Sling ResourceResolver
ResourceResolvers can be must be manually closed in a finally
block, if the auto-closing technique shown above cannot be used.
@Reference
ResourceResolverFactory rrf;
...
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, jcrSession);
ResourceResolver resourceResolver = null;
try {
resourceResolver = rrf.getResourceResolver(authInfo);
// Do work with the resourceResolver
} catch (LoginException e) {
...
} finally {
if (resourceResolver != null) { resourceResolver.close(); }
}
JCR Path to Sling Resource
Resource resource = ResourceResolver.getResource("/path/to/the/resource");
JCR Node to Sling Resource
Resource resource = resourceResolver.getResource(node.getPath());
Sling Resource to AEM Asset
Recommended approach
The DamUtil.resolveToAsset(..)
function resolves any resource under the dam:Asset
to the Asset object by walking up the tree as needed.
Asset asset = DamUtil.resolveToAsset(resource);
Alternative approach
Adapting a resource to an Asset requires the resource itself to be the dam:Asset
node.
Asset asset = resource.adaptTo(Asset.class);
Sling Resource to AEM Page
Recommended approach
pageManager.getContainingPage(..)
resolves any resource under the cq:Page
to the Page object by walking up the tree as needed.
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
Page page = pageManager.getContainingPage(resource);
Page page2 = pageManager.getContainingPage("/content/path/to/page/jcr:content/or/component");
Alternative approach alternative-approach-1
Adapting a resource to a Page requires the resource itself to be the cq:Page
node.
Page page = resource.adaptTo(Page.class);
Read AEM Page properties
Use the Page object’s getters to get well-known properties (getTitle()
, getDescription()
, and so on) and page.getProperties()
to obtain the [cq:Page]/jcr:content
ValueMap for retrieving other properties.
Page page = resource.adaptTo(Page.class);
String title = page.getTitle();
Calendar value = page.getProperties().get("cq:lastModified", Calendar.getInstance());
Read AEM Asset metadata properties
The Asset API provides convenient methods for reading properties from the [dam:Asset]/jcr:content/metadata
node. This is not a ValueMap, the second parameter (default value, and auto-type casting) is not supported.
Asset asset = resource.adaptTo(Asset.class);
String title = asset.getMetadataValue("dc:title");
Calendar lastModified = (Calendar) asset.getMetadata("cq:lastModified");
Read Sling Resource properties read-sling-resource-properties
When properties are stored in locations (properties or relative resources) where the AEM APIs (Page, Asset) cannot directly access, the Sling Resources, and ValueMaps can be used to obtain the data.
ValueMap properties = resource.getValueMap();
String value = properties.get("jcr:title", "Default title");
String relativeResourceValue = properties.get("relative/propertyName", "Default value");
In this case, the AEM object may have to be converted into a Sling Resource to efficiently locate the desired property or sub-resource.
AEM Page to Sling Resource
Resource resource = page.adaptTo(Resource.class);
AEM Asset to Sling Resource
Resource resource = asset.adaptTo(Resource.class);
Write properties using Sling’s ModifiableValueMap
Use Sling’s ModifiableValueMap to write properties to nodes. This can only write to the immediate node (relative property paths are not supported).
Note the call to .adaptTo(ModifiableValueMap.class)
requires write permissions to the resource, else it returns null.
ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);
properties.put("newPropertyName", "new value");
properties.put("propertyNameToUpdate", "updated value");
properties.remove("propertyToRemove");
resource.getResourceResolver().commit();
Create an AEM Page
Always use PageManager to create pages as it takes a Page Template, is required to properly define and initialize Pages in AEM.
String templatePath = "/conf/my-app/settings/wcm/templates/content-page";
boolean autoSave = true;
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
pageManager.create("/content/parent/path", "my-new-page", templatePath, "My New Page Title", autoSave);
if (!autoSave) { resourceResolver.commit(); }
Create a Sling Resource
ResourceResolver supports basic operations for creating resources. When creating higher-level abstractions (AEM Pages, Assets, Tags, and so on) use the methods provided by their respective Managers.
resourceResolver.create(parentResource, "my-node-name", new ImmutableMap.Builder<String, Object>()
.put("jcr:primaryType", "nt:unstructured")
.put("jcr:title", "Hello world")
.put("propertyName", "Other initial properties")
.build());
resourceResolver.commit();
Delete a Sling Resource
ResourceResolver supports removing a resource. When creating higher-level abstractions (AEM Pages, Assets, Tags, and so on) use the methods provided by their respective Managers.
resourceResolver.delete(resource);
resourceResolver.commit();