Sling Resource Decoration, Use Cases & Benefits

Apache Sling provides a way to “decorate” resources before returning them through the Sling API. Here are some benefits and use cases for using Sling Resource Decoration.

By implementing a ResourceDecorator, you are able to use this functionality in your Sling application.

See Sling documentation on wrapping/decorating resources

Why Decorate Resources?

There are a few cases where using a Resource Decorator seems pretty useful.

Adding Resource Metadata

A couple of cases for implementing a Resource Decorator are mentioned in the Sling documentation on decorating resources. Docs say that you might use them to add metadata to a resource.

Here's an example of a simple ResourceDecorator that adds metadata to a resource:

package com.briangoins.example.sling.api.resource;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;

import org.apache.sling.api.resource.ResourceDecorator;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;

/**
* Decorates text resources with related metadata.
*/
@Component
@Service
public class TextResourceDecorator implements ResourceDecorator {
  
  @Override
  public Resource decorate(final Resource resource) {
    
    // Only decorate text resources
    if (!resource.getResourceType().equals("briangoins/text")) {
      return null;
    }
    
    // decorate resource
    ValueMap properties = ResourceUtil.getValueMap(resource);
    String text = properties.get("text", "");
    resource.getResourceMetadata().put("length", text.length());
    return resource;
  }
  
  /* Only called if using older versions of Sling */
  @Override
  public Resource decorate(Resource resource, HttpServletRequest request) {
    return this.decorate(resource);
  }

}
TextResourceDecorator.java

Wrapping Resources

Resource Decorators are good for wrapping resources as well. Implementing Sling's ResourceWrapper interface allows you to override functionality for Resource methods, and sends calls for non-overridden methods to the underlying (or wrapped) Resource. You can use a Resource Decorator to provide a wrapped resource with customized behavior.

Overlaying Properties

This is kind of a combination of the last two cases, but you can use Resource Decorators to overlay the property set for a Resource with other properties.

Adobe Consulting Services has sample code that illustrates this case:

/*
 * #%L
 * ACS AEM Samples
 * %%
 * Copyright (C) 2015 Adobe
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

package com.adobe.acs.samples.resources.impl;

import com.adobe.acs.samples.resources.SampleResourceWrapper;
import com.adobe.cq.commerce.common.ValueMapDecorator;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceDecorator;
import org.apache.sling.api.resource.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;


@Component(
        label = "ACS AEM Samples - Sample Resource Decorator",
        description = "Sample Sling Resource Decorator"
)
@Service
public class SampleResourceDecorator implements ResourceDecorator {
    private final Logger log = LoggerFactory.getLogger(SampleResourceDecorator.class);

    @Override
    public Resource decorate(final Resource resource) {
        // Remember to return early (as seen above) as this decorator is executed on all
        // Resource resolutions (this happens A LOT), especially if the decorator performs
        // any complex/slow running logic.

        if (!this.accepts(resource)) {
            return resource;
        }

        // Any overridden methods (ex. adaptTo) on the wrapper, will be used when invoked on the
        // resultant Resource object (even before casting).

        final ValueMap overlay = new ValueMapDecorator(new HashMap<String, Object>());
        overlay.put("foo", 100);
        overlay.put("cat", "meow");

        // See ACS AEM Samples com.adobe.acs.samples.resources.SampleResourceWrapper for how
        // these above overlay properties are added to the resource's ValueMap
        return new SampleResourceWrapper(resource, overlay);
    }

    @Deprecated
    @Override
    public Resource decorate(final Resource resource, final HttpServletRequest request) {
        // This is deprecated; Use decorate(Resource resource) instead.

        // Note; since ResourceDecorators are not called w Request context (this method being deprecated)
        // To pass in Request Context, use a ThreadLocal Sling Filter.
        // See ACS AEM Samples com.adobe.acs.samples.filters.impl.SampleThreadLocalFilter

        return this.decorate(resource);
    }

    // Check if this resource should be decorated. Return "false" as fast as possible.
    private boolean accepts(final Resource resource) {
        if (resource == null) {
            return false;
        }

        if (StringUtils.equals(resource.getPath(), "/some/path")) {
            return true;
        } else {
            return false;
        }

        // Note: If you are checking if a resource should be decorated based on resource type,
        // Using ResourceUtil.isA(..) will send this into an infinite recursive lookup loop
    }
}

SampleResourceDecorator.java (source)

/*
 * #%L
 * ACS AEM Samples
 * %%
 * Copyright (C) 2015 Adobe
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

package com.adobe.acs.samples.resources;


import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceWrapper;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;

import java.util.HashMap;
import java.util.Map;


public class SampleResourceWrapper extends ResourceWrapper {

    private ValueMap properties;

    // Creates a new wrapper instance delegating all method calls to the given resource,
    // and augments the Resource's properties valueMap with the values in the "overlayProperties" param
    public SampleResourceWrapper(final Resource resource, final ValueMap overlayProperties) {
        super(resource);

        final Map<String, Object> mergedProperties = new HashMap<String, Object>();
        mergedProperties.putAll(super.getValueMap());
        mergedProperties.putAll(overlayProperties);

        this.properties = new ValueMapDecorator(mergedProperties);
    }


    // When modifying the Resource's ValueMap, override .getValueMap()
    public final ValueMap getValueMap() {
        return this.properties;
    }


    // When modifying the Resource's ValueMap, override .adaptTo()
    @Override
    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
        if (type != ValueMap.class) {
            return super.adaptTo(type);
        }

        return (AdapterType) this.getValueMap();
    }
}

SampleResourceWrapper.java (source)

Easier Script Access

This one's more of a benefit than a use case, but if you use a Resource Decorator to add metadata or overlay properties, you can more easily access those properties in scripting languages like JSP or Sightly.

<!--/* Sightly HTML script */-->
<p>${properties.text}</p>
<span class="text-muted">${resource.resourceMetadata.length} characters</span>

text.html

Resource-Centric Architecture

Finally, the last benefit I'll mention of using Resource Decorators for adding metadata and overlaying properties is that it can help keep resource code with the resource, and out of the hands of helper/utility classes.

When you rely on helper/utility classes to provide resource-based operations like deriving custom properties, it can lead to extra resource resolution steps and parameter passing in your bundle code, extra Use API helpers in your Sightly scripts, or scriptlets/helper classes in your JSPs.

Using Resource Decorators can help simplify your Sling application, and help your code stick to common, resource-oriented Sling API operations.