AEM 6.4: Challenges with Dynamic Media Integration | Perficient Digital

AEM 6.4: Challenges with Dynamic Media Integration

Since the release of AEM 6.4, Adobe has changed the way Dynamic Media is integrated with AEM.  Configuring the integration is now a little different than what customers are used to in 6.3 and below. Adobe has provided an architecture diagram of Dynamic Media – Scene7 mode, which describes how it works.

In this post, I’m going to share some challenges that I encountered on a project and possible solutions.

Serving Images With Transparent Background

This was a shocker to me. If you use the Dynamic Media component and drag a PNG image that has a transparent background, the image is served with a white background from Scene7. I had assumed that the Dynamic Media component or the AEM integration would detect the extension or mime type, then ask Scene7 to serve the image with a correct type to ensure that transparency is not lost. But that was not the case at all. All images served from Scene7, by default, come back in the JPEG format. This means losing transparency.

Of course, that is not to say that Scene7 does not support transparency. In fact, it does!. You just have to tell it the type you want it to return via the fmt request parameter or by means of an image preset.

Pro tip: when working with Scene7 integration with AEM. Always refer to the Image Serving Context documentation and NOT the Image Rendering Context documentation

The difference between the two can be confusing. For our purposes, it’s enough to know where the request param docs are and that Image Serving URL starts with is/image and Image Rendering URL starts with ir/render

I’ve already opened a ticket with Adobe in hopes that there is a manual way to map image type/extension to a format. But unfortunately, it does not seem to be available at this time.

I’d like to also add that this impacts all of the image formats that support transparency, namely: GIF, PNG, BMP, TIFF, and JPEG 2000.

Possible Solutions to the Transparency Problem

It’s no secret that support for images from Dynamic Media is solely done via the Dynamic Media components. And that additional support, say in custom components or using a style attribute, is none existent out of the box. So many implementors opt for using the Sling Output Rewriter, akin link rewriter, to serve images from Dynamic Media by means of calling an OOTB service to do the transformation and return a URL.

Transparency When Using Dynamic Media Components

For this, we can just create a new image preset, call it “transparent” and use it:

 

Then when authoring a Dynamic Media component, select “transparent” as the preset:

 

 

Transparency for Images Outside the Dynamic Media Component  (Using Image Rewriter)

Before using this approach, I highly recommend you find a way to incorporate the Dynamic Media component into your existing component. This has its place, but it’s much better to use the Dynamic Media component for the authoring experience.

I won’t get into how to create a rewriter, there are many posts on that and the sling docs do a pretty good job! It’s the same process as a link rewriter, just rewrites the src attribute of an img tag. But, I can show you how to get a full Scene7 URL as if you’ve done it via the Dynamic Media component.

I have written a generic OSGi service that you can use for this purpose. This service will append fmt=png-alpha if the asset ends with a .png extension. It uses a service user that has access to read from the dam.

Unless you are intimidated by lambdas, the code should, hopefully, be easy to follow. But I’ve added as much documentation as possible.

You can then use this service in your rewriter, pass it a dam path and it will spit out a Scene7 URL!

import com.day.cq.dam.api.s7dam.utils.PublishUtils;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import javax.jcr.RepositoryException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Dynamic Media Service. */
@Component(
    immediate = true,
    property = {"label=Dynamic Media Service", "description=a service that can return the full Dynamic Media URL for a dam image"},
    service = DynamicMediaService.class)
public class DynamicMediaService {

  private ResourceResolverFactory resourceResolverFactory;
  private PublishUtils publishUtils;

  @Reference
  public void setPublishUtils(PublishUtils publishUtils) {
    this.publishUtils = publishUtils;
  }

  @Reference
  public void setResourceResolverFactory(ResourceResolverFactory resourceResolverFactory) {
    this.resourceResolverFactory = resourceResolverFactory;
  }

  private static final String DAM_PREFIX = "/content/dam";
  private static final String IS_IMAGE = "is/image";
  private static final String SLASH = "/";
  private static final Logger logger = LoggerFactory.getLogger(DynamicMediaService.class);

  private static final Map<String, Object> DYNAMIC_MEDIA_AUTH =
      Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "dynamicMediaService");


  /**
   * Convert dam url to dynamic media url.
   *
   * @param damImagePath the dam image path. must start with "/content/dam".
   * @return the DM url or null if the the image path does not start with "/content/dam" or
   *     dynamic media is not configured.
   */
  public String getDynamicMediaImageUrl(String damImagePath) {

    try (ResourceResolver resourceResolver =
        resourceResolverFactory.getServiceResourceResolver(DYNAMIC_MEDIA_AUTH)) {
      return Optional.ofNullable(resourceResolver)
          .filter(r -> publishUtils != null)
          .filter(r -> this.isDamPath(damImagePath))
          .map(resolver -> resolver.getResource(damImagePath))
          .map(resource -> getDynamicMediaImageUrl(resource, damImagePath))
          .map(s7Uri -> appendQueryParams(s7Uri, damImagePath))
          .orElse(damImagePath);
    } catch (LoginException e) {
      logger.error("Error while getting dynamicMediaService resource resolver,{}", e.getMessage());
      return null;
    }
  }

  /**
   * Get the dynamic media image url for an asset resource.
   * @param assetResource The asset resource to get the URL for.
   * @param defaultUrl the default url to return in case of error/no DM.
   * @return
   */
  public String getDynamicMediaImageUrl(Resource assetResource, String defaultUrl) {
    return Optional.ofNullable(assetResource)
        .filter(resource -> publishUtils != null)
        .map(getDynamicMediaImageArray(publishUtils))
        .filter(ArrayUtils::isNotEmpty)
        .map(this::getDynamicMediaUrlFromArray)
        .orElse(defaultUrl);
  }

  /**
   * Check if a given path is a dam path.
   * @param path the dam path to check.
   * @return
   */
  public boolean isDamPath(String path) {
    return Optional.ofNullable(path)
        .filter(StringUtils::isNoneBlank)
        .filter(str -> StringUtils.startsWith(str, SLASH))
        .filter(str -> StringUtils.startsWith(str, DAM_PREFIX))
        .map(StringUtils::isNoneBlank)
        .orElse(false);
  }

  /** Appends S7 query params based on conditions. */
  private String appendQueryParams(String s7Uri, String damPath) {
    if (!StringUtils.isNoneBlank(s7Uri, damPath)) {
      // either is blank, return the s7Uri as is.
      return s7Uri;
    }

    // check if the dam image ends with .png.
    if (StringUtils.endsWithIgnoreCase(damPath, ".png")) {
      try {
        URIBuilder uriBuilder = new URIBuilder(s7Uri);
        // refer: https://marketing.adobe.com/resources/help/en_US/s7/is_ir_api/is_api/http_ref/r_is_http_fmt.html
        uriBuilder.addParameter("fmt", "png-alpha");
        return uriBuilder.build().toString();
      } catch (URISyntaxException e) {
        logger.error("Unable to append params to s7 url: {}. returning as is", s7Uri);
        return s7Uri;
      }
    }
    return s7Uri;
  }

  private String getDynamicMediaUrlFromArray(String[] dynamicMediaArray) {
    return Optional.ofNullable(dynamicMediaArray)
        .filter(ArrayUtils::isNotEmpty)
        .filter(array -> array.length >= 2)
        .map(array -> ArrayUtils.insert(1, array, IS_IMAGE))
        .map(this::joinPath)
        .orElse(null);
  }

  private String joinPath(String[] array) {
    return Arrays.stream(array)
        .reduce(
            "",
            (acc, next) -> {
              String prefix = StringUtils.stripEnd(acc, SLASH);
              String suffix = StringUtils.stripStart(next, SLASH);
              return StringUtils.stripStart(prefix + SLASH + suffix, SLASH);
            });
  }

  private Function<Resource, String[]> getDynamicMediaImageArray(PublishUtils publishUtils) {
    return resource -> {
      try {
        return publishUtils.externalizeImageDeliveryAsset(resource);
      } catch (RepositoryException e) {
        logger.error("error getting dynamic media url for asset", e);
      }
      return null;
    };
  }
}

 

Support For SVG

SVG support is limited to the SVG 1.1 spec, this means that you have to ensure the validity of your SVG. On top of that, Scene7 will NOT serve raw SVG. Instead, it will rasterize it. This means that it will follow the Image Serving Context described above and will be served as JPEG and, by default, no transparency. You can use the same approach as the above to get transparency, or you could skip serving SVG from Scene7 entirely if you need to serve RAW svg.

 

Ability To Edit Images With the Asset Editor

Typically, you can edit image assets: rotate/crop/flip them by doing the following steps:

  1. Go to assets on the AEM author instance
  2. Pick any image
  3. Select the image click “edit” on the top bar
  4. you can now rotate/crop/flip the image and save

With Dynamic Media Scene7 mode, you cannot edit images as outlined above. Instead, the only thing you can do is author hot spots. Again, I’ve opened a ticket with Adobe on this and their response is that the edit features are not expected to work with Scene7 mode.

That’s all for this one, folks! Comment below and share how this solution worked for you.

2 responses to “AEM 6.4: Challenges with Dynamic Media Integration”

  1. Jeffrey says:

    Hi, I noticed that you say Scene7 won’t serve raw SVG files as SVG files. I’m not sure what your environment / system specifics that may otherwise hinder this, but it is possible. You have to treat it as a static asset (Adobe terminology) , like that of a PDF or animated GIF. By that I mean that you have to start your URL with “://yourcompany.com/is/content <– notice is/content, not is/image with that followed by the folder path in Scene7 to your svg asset such as "/CompanyFolder/folder/subfolder/my-snazzy-svgfile.svg" <– and add the svg extension Hope that helps.

  2. That’s interesting, do you have any documentation on the `/is/content` servlet? I assume this is an AEM servlet since you are referring to the ://yourcompany.com domain and not the scene7 domain?

Leave a Reply