AEM Resource Resolver Commit/Save – Best Practices

Problem Statement:

When to commit or save node (Resource) using resource resolver? Is it good save node inside a loop?

Requirement:

Save 50 nodes with some properties

Introduction:

The ResourceResolver defines the API which may be used to resolve Resource objects and work with such resources as creating, editing or updating them. The resource resolver is available to the request processing servlet through the SlingHttpServletRequest.getResourceResolver() method. A resource resolver can also be created through the ResourceResolverFactory service.

A ResourceResolver is generally not thread safe! As a consequence, an application that uses the resolver, its returned resources and/or objects resulting from adapting either the resolver or a resource, must provide proper synchronization to ensure no more than one thread concurrently operates against a single resolver, resource or resulting objects.

An algorithm is used to resolve and getResource and provide various methods to manage resources like:

OperationDescription
Create(Resource, String, Map)for creating a new resource.
Delete(Resource)to delete a resource.
Adaptable.adaptTo(Class)allows to adapt a resource to a ModifiableValueMap to update a resource.
Move(String, String)to move resources.
Copy(String, String)to copy resources.
Commit()commits all staged changes.
Revert()reverts all staged changes.

All changes are transient and require committing them at the end

Hence as per API documentation, it’s better to stage all the changes before calling commit or revert.

But please make sure we are not trying to save more millions of nodes at a time and also updating nodes takes more time compared to creating a new one as per the adapto conference showcase.

Hence check whether the node already has the property and value before you save it.

Resolution:

Saving resolver inside for loop

For our use case, I am using ResourceUtil.getOrCreateResource() for creating or getting the exiting node and if it creating then it will be saving the node with default properties like jcr:primaryType = un:unstructured

Using ResourceUtil increases code readability and maintainability

Parameters:
resolver – The resource resolver to use for the creation
path – The full path to be created
resourceProperties – The optional resource properties of the final resource to create
intermediateResourceType – THe optional resource type of all intermediate resources
autoCommit – If set to true, a commit is performed after each resource creation.

In the below example I am creating a for loop, and I am creating the node (resource) with default properties, and I am setting auto commit has true. After creating the resource, I am adapting it to ModifiableValueMap and I will be adding a new property name and value as “property+index” and committing the resolver.

public void saveNodes() {
  try (ResourceResolver resourceResolver = resolverFactory.getServiceResourceResolver(
    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SERVICE_USER))) {
    for (int index = 0; index <= 50; index++) {
      @NotNull
      Resource savedResource = ResourceUtil.getOrCreateResource(resourceResolver, "/content/" + index,
        defualtNodeProperties, StringUtils.EMPTY, true);
      ModifiableValueMap map = savedResource.adaptTo(ModifiableValueMap.class);
      map.put("name", "property" + index);
      resourceResolver.commit();
    }
  } catch (LoginException | PersistenceException e) {
    LOGGER.error("Error Occured during Login", e.getMessage());
  }
}

Saving resolver outside for loop

In the below example I have to remove auto save as false and the rest of the code remains the same, but I am committing resolver outside for loop. By doing so, I can stage the resource resolver and commit it at last.

public void saveNodes() {
  try (ResourceResolver resourceResolver = resolverFactory.getServiceResourceResolver(
    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SERVICE_USER))) {
    for (int index = 0; index <= 50; index++) {
      @NotNull
      Resource savedResource = ResourceUtil.getOrCreateResource(resourceResolver, "/content/" + index,
        defualtNodeProperties, StringUtils.EMPTY, false);
      ModifiableValueMap map = savedResource.adaptTo(ModifiableValueMap.class);
      map.put("name", "property" + index);
    }
    resourceResolver.commit();
  } catch (LoginException | PersistenceException e) {
    LOGGER.error("Error Occured during Login", e.getMessage());
  }
}

What will happen if I rerun the same code?

ResourceUtil would handle getting the existing resource instead of recreating, but I would still be updating the resource and committing the changes, which is a costly process.

Better implementation with Validation

In order to avoid the updating of the node we could validate the property exists and check the value and if and only if the resolver has changes, will commit.

public void saveNodes() {
  try (ResourceResolver resourceResolver = resolverFactory.getServiceResourceResolver(
    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SERVICE_USER))) {
    for (int index = 0; index <= 50; index++) {
      @NotNull
      Resource savedResource = ResourceUtil.getOrCreateResource(resourceResolver, "/content/" + index,
        defualtNodeProperties, StringUtils.EMPTY, false);
      ModifiableValueMap map = savedResource.adaptTo(ModifiableValueMap.class);
      if (!map.containsKey("name") || !StringUtils.equals(map.get("name", StringUtils.EMPTY), "property" + index)) {
        map.put("name", index);
      }
    }
    if (resourceResolver.hasChanges()) {
      resourceResolver.commit();
    }
  } catch (LoginException | PersistenceException e) {
    LOGGER.error("Error Occured during Login", e.getMessage());
  }
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s