DamUriTemplate.java

  1. /*
  2.  * #%L
  3.  * wcm.io
  4.  * %%
  5.  * Copyright (C) 2021 wcm.io
  6.  * %%
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *      http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  * #L%
  19.  */
  20. package io.wcm.handler.mediasource.dam.impl;

  21. import static io.wcm.handler.media.MediaNameConstants.URI_TEMPLATE_PLACEHOLDER_HEIGHT;
  22. import static io.wcm.handler.media.MediaNameConstants.URI_TEMPLATE_PLACEHOLDER_WIDTH;

  23. import org.apache.commons.lang3.StringUtils;
  24. import org.apache.sling.api.resource.Resource;
  25. import org.jetbrains.annotations.NotNull;
  26. import org.jetbrains.annotations.Nullable;

  27. import com.day.cq.dam.api.Rendition;

  28. import io.wcm.handler.media.CropDimension;
  29. import io.wcm.handler.media.Dimension;
  30. import io.wcm.handler.media.MediaArgs;
  31. import io.wcm.handler.media.UriTemplate;
  32. import io.wcm.handler.media.UriTemplateType;
  33. import io.wcm.handler.media.impl.ImageFileServlet;
  34. import io.wcm.handler.media.impl.ImageFileServletSelector;
  35. import io.wcm.handler.media.impl.MediaFileServletConstants;
  36. import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaPath;
  37. import io.wcm.handler.mediasource.dam.impl.dynamicmedia.NamedDimension;
  38. import io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop;
  39. import io.wcm.handler.mediasource.dam.impl.weboptimized.WebOptimizedImageDeliveryParams;
  40. import io.wcm.handler.url.UrlHandler;
  41. import io.wcm.sling.commons.adapter.AdaptTo;

  42. /**
  43.  * Generates URI templates for asset renditions - with or without Dynamic Media.
  44.  */
  45. final class DamUriTemplate implements UriTemplate {

  46.   private static final long DUMMY_WIDTH = 999991;
  47.   private static final long DUMMY_HEIGHT = 999992;

  48.   private final UriTemplateType type;
  49.   private final String uriTemplate;
  50.   private final Dimension dimension;

  51.   DamUriTemplate(@NotNull UriTemplateType type, @NotNull Dimension dimension,
  52.       @NotNull Rendition rendition, @Nullable CropDimension cropDimension, @Nullable Integer rotation,
  53.       @Nullable Double ratio, @NotNull DamContext damContext) {
  54.     this.type = type;

  55.     String url = null;
  56.     Dimension validatedDimension = null;
  57.     if (damContext.isDynamicMediaEnabled() && damContext.isDynamicMediaAsset()) {
  58.       // if DM is enabled: try to get rendition URL from dynamic media
  59.       NamedDimension smartCropDef = getDynamicMediaSmartCropDef(cropDimension, rotation, ratio, damContext);
  60.       url = buildUriTemplateDynamicMedia(type, cropDimension, rotation, smartCropDef, damContext);
  61.       // get actual max. dimension from smart crop rendition
  62.       if (url != null && smartCropDef != null) {
  63.         validatedDimension = SmartCrop.getCropDimensionForAsset(damContext.getAsset(), damContext.getResourceResolver(), smartCropDef);
  64.       }
  65.     }
  66.     if (url == null && (!damContext.isDynamicMediaEnabled() || !damContext.isDynamicMediaAemFallbackDisabled())) {
  67.       if (damContext.isWebOptimizedImageDeliveryEnabled()) {
  68.         // Render renditions via web-optimized image delivery: build externalized URL
  69.         url = buildUriTemplateWebOptimizedImageDelivery(type, cropDimension, rotation, damContext);
  70.       }
  71.       if (url == null) {
  72.         // Render renditions in AEM: build externalized URL
  73.         url = buildUriTemplateDam(type, rendition, cropDimension, rotation,
  74.             damContext.getMediaArgs().getImageQualityPercentage(), damContext);
  75.       }
  76.     }
  77.     this.uriTemplate = url;

  78.     if (validatedDimension == null) {
  79.       validatedDimension = dimension;
  80.     }
  81.     this.dimension = validatedDimension;
  82.   }

  83.   private static String buildUriTemplateDam(@NotNull UriTemplateType type, @NotNull Rendition rendition,
  84.       @Nullable CropDimension cropDimension, @Nullable Integer rotation, @Nullable Double imageQualityPercentage,
  85.       @NotNull DamContext damContext) {

  86.     // build rendition URL with dummy width/height parameters (otherwise externalization will fail)
  87.     MediaArgs mediaArgs = damContext.getMediaArgs();
  88.     String mediaPath = RenditionMetadata.buildMediaPath(rendition.getPath()
  89.         + "." + ImageFileServletSelector.build(DUMMY_WIDTH, DUMMY_HEIGHT, cropDimension, rotation, imageQualityPercentage, false)
  90.         + "." + MediaFileServletConstants.EXTENSION,
  91.         ImageFileServlet.getImageFileName(damContext.getAsset().getName(), mediaArgs.getEnforceOutputFileExtension()));
  92.     UrlHandler urlHandler = AdaptTo.notNull(damContext, UrlHandler.class);
  93.     String url = urlHandler.get(mediaPath).urlMode(mediaArgs.getUrlMode())
  94.         .buildExternalResourceUrl(damContext.getAsset().adaptTo(Resource.class));

  95.     // replace dummy width/height parameters with actual placeholders
  96.     switch (type) {
  97.       case CROP_CENTER:
  98.         url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH);
  99.         url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT);
  100.         break;
  101.       case SCALE_WIDTH:
  102.         url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH);
  103.         url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), "0");
  104.         break;
  105.       case SCALE_HEIGHT:
  106.         url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), "0");
  107.         url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT);
  108.         break;
  109.       default:
  110.         throw new IllegalArgumentException("Unsupported type: " + type);
  111.     }
  112.     return url;
  113.   }

  114.   private static String buildUriTemplateWebOptimizedImageDelivery(@NotNull UriTemplateType type,
  115.       @Nullable CropDimension cropDimension, @Nullable Integer rotation, @NotNull DamContext damContext) {
  116.     // scale by height is not supported by Web-Optimized Image Delivery
  117.     if (type == UriTemplateType.SCALE_HEIGHT) {
  118.       return null;
  119.     }

  120.     // build rendition URL with dummy width/height parameters (otherwise API call will fail)
  121.     String url = damContext.getWebOptimizedImageDeliveryUrl(new WebOptimizedImageDeliveryParams()
  122.         .width(DUMMY_WIDTH).cropDimension(cropDimension).rotation(rotation));
  123.     if (url == null) {
  124.       return null;
  125.     }

  126.     // replace dummy width/height parameters with actual placeholders
  127.     switch (type) {
  128.       case CROP_CENTER:
  129.         url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH);
  130.         break;
  131.       case SCALE_WIDTH:
  132.         url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH);
  133.         break;
  134.       default:
  135.         throw new IllegalArgumentException("Unsupported type for Web-optimized image delivery: " + type);
  136.     }
  137.     return url;
  138.   }

  139.   private static @Nullable String buildUriTemplateDynamicMedia(@NotNull UriTemplateType type,
  140.       @Nullable CropDimension cropDimension, @Nullable Integer rotation, @Nullable NamedDimension smartCropDef,
  141.       @NotNull DamContext damContext) {
  142.     String productionAssetUrl = damContext.getDynamicMediaServerUrl();
  143.     if (productionAssetUrl == null) {
  144.       return null;
  145.     }
  146.     StringBuilder result = new StringBuilder();
  147.     result.append(productionAssetUrl).append(DynamicMediaPath.buildImage(damContext));

  148.     // build DM URL with smart cropping
  149.     if (smartCropDef != null) {
  150.       result.append("%3A").append(smartCropDef.getName()).append("?")
  151.           .append(getDynamicMediaWidthHeightParameters(type))
  152.           .append("&fit=constrain");
  153.       return result.toString();
  154.     }

  155.     // build DM URL without smart cropping
  156.     result.append("?");
  157.     if (cropDimension != null) {
  158.       result.append("crop=").append(cropDimension.getCropStringWidthHeight()).append("&");
  159.     }
  160.     if (rotation != null) {
  161.       result.append("rotate=").append(rotation).append("&");
  162.     }
  163.     result.append(getDynamicMediaWidthHeightParameters(type));
  164.     return result.toString();
  165.   }

  166.   private static String getDynamicMediaWidthHeightParameters(UriTemplateType type) {
  167.     switch (type) {
  168.       case CROP_CENTER:
  169.         return "wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH + "&hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT + "&fit=crop";
  170.       case SCALE_WIDTH:
  171.         return "wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH;
  172.       case SCALE_HEIGHT:
  173.         return "hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT;
  174.       default:
  175.         throw new IllegalArgumentException("Unsupported type for Dynamic Media: " + type);
  176.     }
  177.   }

  178.   private static NamedDimension getDynamicMediaSmartCropDef(@Nullable CropDimension cropDimension, @Nullable Integer rotation,
  179.       @Nullable Double ratio, @NotNull DamContext damContext) {
  180.     if (SmartCrop.canApply(cropDimension, rotation) && ratio != null) {
  181.       // check for matching image profile and use predefined cropping preset if match found
  182.       return SmartCrop.getDimensionForRatio(damContext.getImageProfile(), ratio);
  183.     }
  184.     return null;
  185.   }

  186.   @Override
  187.   public @NotNull UriTemplateType getType() {
  188.     return type;
  189.   }

  190.   @Override
  191.   public @NotNull String getUriTemplate() {
  192.     return uriTemplate;
  193.   }

  194.   @Override
  195.   public long getMaxWidth() {
  196.     return dimension.getWidth();
  197.   }

  198.   @Override
  199.   public long getMaxHeight() {
  200.     return dimension.getHeight();
  201.   }

  202.   @Override
  203.   public String toString() {
  204.     return uriTemplate;
  205.   }

  206. }