IPEConfigResourceProvider.java

  1. /*
  2.  * #%L
  3.  * wcm.io
  4.  * %%
  5.  * Copyright (C) 2019 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.media.impl.ipeconfig;

  21. import static io.wcm.handler.media.impl.ipeconfig.CroppingRatios.MEDIAFORMAT_FREE_CROP;
  22. import static io.wcm.handler.media.impl.ipeconfig.PathParser.NN_ASPECT_RATIOS;
  23. import static io.wcm.handler.media.impl.ipeconfig.PathParser.NN_CONFIG;
  24. import static io.wcm.handler.media.impl.ipeconfig.PathParser.NN_MEDIA_FORMAT;

  25. import java.util.Iterator;
  26. import java.util.LinkedHashMap;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import java.util.SortedSet;
  30. import java.util.TreeSet;

  31. import org.apache.commons.lang3.StringUtils;
  32. import org.apache.sling.api.resource.Resource;
  33. import org.apache.sling.api.resource.ResourceResolver;
  34. import org.apache.sling.api.resource.SyntheticResource;
  35. import org.apache.sling.spi.resource.provider.ResolveContext;
  36. import org.apache.sling.spi.resource.provider.ResourceContext;
  37. import org.apache.sling.spi.resource.provider.ResourceProvider;
  38. import org.jetbrains.annotations.NotNull;
  39. import org.jetbrains.annotations.Nullable;
  40. import org.osgi.service.component.annotations.Component;

  41. import com.day.cq.wcm.api.components.ComponentManager;

  42. import io.wcm.handler.media.format.MediaFormat;
  43. import io.wcm.handler.media.format.MediaFormatHandler;
  44. import io.wcm.sling.commons.adapter.AdaptTo;

  45. /**
  46.  * Resource provider that overlays a IPE config resource with a dynamically generated
  47.  * set of cropping aspect ratios derived from given set of media formats.
  48.  *
  49.  * <p>
  50.  * URL pattern for resource access:<br>
  51.  * <code>/wcmio:mediaHandler/ipeConfig/{componentContentPath}/wcmio:mediaFormat/{mf1}/{mf2}/.../wcmio:config/{relativeConfigPath}</code>
  52.  * </p>
  53.  */
  54. @Component(service = ResourceProvider.class, property = {
  55.     ResourceProvider.PROPERTY_NAME + "=wcmioHandlerIPEConfig",
  56.     ResourceProvider.PROPERTY_ROOT + "=" + IPEConfigResourceProvider.IPECONFIG_OVERLAY_ROOTPATH
  57. })
  58. public class IPEConfigResourceProvider extends ResourceProvider<Void> {

  59.   /**
  60.    * Root path for IPE config overlay resources.
  61.    */
  62.   @SuppressWarnings("java:S1075") // no file path
  63.   public static final String IPECONFIG_OVERLAY_ROOTPATH = "/wcmio:mediaHandler/ipeConfig";

  64.   @Override
  65.   public @Nullable Resource getResource(@NotNull ResolveContext resolveContext, @NotNull String path,
  66.       @NotNull ResourceContext resourceContext, @Nullable Resource parent) {

  67.     PathParser parser = new PathParser(path);
  68.     if (!parser.isValid()) {
  69.       return null;
  70.     }

  71.     ResourceResolver resolver = resolveContext.getResourceResolver();
  72.     if (parser.isAspectRatiosNode()) {
  73.       // simulate 'aspectRatios' node
  74.       return buildAspectRatiosResource(resolver, path);
  75.     }
  76.     else if (parser.isAspectRatioItem()) {
  77.       // simulate 'aspectRatios/xxx' node
  78.       String mediaFormatName = parser.getAspectRatioItemName();
  79.       if (parser.getMediaFormatNames().contains(mediaFormatName)) {
  80.         return buildAspectRatioItemResource(resolver, path, mediaFormatName, parser);
  81.       }
  82.     }
  83.     else {
  84.       // return wrapped overlaid resource
  85.       String overlayResourcePath = getIpeConfigPath(resolver, parser);
  86.       if (StringUtils.isNotEmpty(overlayResourcePath)) {
  87.         Resource overlayResource = resolver.getResource(overlayResourcePath);
  88.         if (overlayResource != null) {
  89.           return new OverlayResource(overlayResource, path);
  90.         }
  91.       }
  92.     }
  93.     return null;
  94.   }

  95.   @Override
  96.   public @Nullable Iterator<Resource> listChildren(@NotNull ResolveContext resolveContext, @NotNull Resource resource) {
  97.     Map<String, Resource> childMap = getOverlayedResourceChilden(resource);

  98.     String path = resource.getPath();
  99.     PathParser parser = new PathParser(path);
  100.     if (!parser.isValid()) {
  101.       return null;
  102.     }

  103.     ResourceResolver resolver = resolveContext.getResourceResolver();
  104.     if (parser.isPluginsCropNode()) {
  105.       // add simulated 'aspectRatios' node
  106.       childMap.put(NN_ASPECT_RATIOS, buildAspectRatiosResource(resolver, path + "/" + NN_ASPECT_RATIOS));
  107.     }
  108.     else if (parser.isAspectRatiosNode()) {
  109.       // add simulated 'aspectRatios/xxx' nodes
  110.       childMap.clear();
  111.       for (String mediaFormatName : parser.getMediaFormatNames()) {
  112.         Resource item = buildAspectRatioItemResource(resolver, path + "/" + mediaFormatName, mediaFormatName, parser);
  113.         if (item != null) {
  114.           childMap.put(mediaFormatName, item);
  115.         }
  116.       }
  117.     }

  118.     if (childMap.isEmpty()) {
  119.       return null;
  120.     }
  121.     else {
  122.       return childMap.values().iterator();
  123.     }
  124.   }

  125.   /**
  126.    * Gets children of overlaid resource and converts children to {@link OverlayResource}.
  127.    * @param resource Requested resources
  128.    * @return Map with all children
  129.    */
  130.   private Map<String, Resource> getOverlayedResourceChilden(Resource resource) {
  131.     Map<String, Resource> childMap = new LinkedHashMap<>();
  132.     if (resource instanceof OverlayResource) {
  133.       Resource overlayResource = ((OverlayResource)resource).getOverlayedResource();
  134.       Iterator<Resource> childrenIterator = overlayResource.listChildren();
  135.       while (childrenIterator.hasNext()) {
  136.         Resource child = childrenIterator.next();
  137.         childMap.put(child.getName(), new OverlayResource(child,
  138.             resource.getPath() + "/" + child.getName()));
  139.       }
  140.     }
  141.     return childMap;
  142.   }

  143.   /**
  144.    * Build resource for /aspectRatios node
  145.    * @param resolver Resource resolver
  146.    * @param path Path
  147.    * @return Resource
  148.    */
  149.   private Resource buildAspectRatiosResource(ResourceResolver resolver, String path) {
  150.     return new SyntheticResource(resolver, path, null);
  151.   }

  152.   /**
  153.    * Build virtual resource with name and aspect ratio of given media format.
  154.    * @param resolver Resource resolver
  155.    * @param path Path
  156.    * @param mediaFormatName Media format name
  157.    * @param parser Path parser
  158.    * @return Resource or null if media format not found or has no valid ratio
  159.    */
  160.   private Resource buildAspectRatioItemResource(ResourceResolver resolver, String path, String mediaFormatName,
  161.       PathParser parser) {
  162.     Resource componentContent = resolver.getResource(parser.getComponentContentPath());
  163.     if (componentContent != null) {
  164.       MediaFormatHandler mediaFormatHandler = AdaptTo.notNull(componentContent, MediaFormatHandler.class);
  165.       MediaFormat mediaFormat = getMediaFormat(mediaFormatName, mediaFormatHandler);
  166.       if (mediaFormat != null) {
  167.         return new AspectRatioResource(resolver, mediaFormat, path);
  168.       }
  169.     }
  170.     return null;
  171.   }

  172.   private MediaFormat getMediaFormat(String mediaFormatName, MediaFormatHandler mediaFormatHandler) {
  173.     if (StringUtils.equals(mediaFormatName, MEDIAFORMAT_FREE_CROP.getName())) {
  174.       return MEDIAFORMAT_FREE_CROP;
  175.     }
  176.     else {
  177.       return mediaFormatHandler.getMediaFormat(mediaFormatName);
  178.     }
  179.   }

  180.   /**
  181.    * Get IPE config path from component associated with given resource and append the relative
  182.    * config path from current resource request.
  183.    * @param resolver Resource resolver
  184.    * @param parser Path parser
  185.    * @return Path or null
  186.    */
  187.   private String getIpeConfigPath(ResourceResolver resolver, PathParser parser) {
  188.     Resource componentContent = resolver.getResource(parser.getComponentContentPath());
  189.     if (componentContent != null) {
  190.       ComponentManager componentManager = AdaptTo.notNull(resolver, ComponentManager.class);
  191.       com.day.cq.wcm.api.components.Component component = componentManager.getComponentOfResource(componentContent);
  192.       if (component != null
  193.           && component.getEditConfig() != null
  194.           && component.getEditConfig().getInplaceEditingConfig() != null) {
  195.         String ipeConfigPath = component.getEditConfig().getInplaceEditingConfig().getConfigPath();
  196.         if (StringUtils.isNotEmpty(ipeConfigPath)) {
  197.           return ipeConfigPath + StringUtils.defaultString(parser.getRelativeConfigPath());
  198.         }
  199.       }
  200.     }
  201.     return null;
  202.   }

  203.   /**
  204.    * Build path to overlaid IPE configuration services by this resource provider.
  205.    * @param componentContentPath Content resource path containing reference component with image IPE enabled
  206.    * @param mediaFormatNames Media format names
  207.    * @return Path
  208.    */
  209.   public static String buildPath(String componentContentPath, Set<String> mediaFormatNames) {
  210.     SortedSet<String> sortedMediaFormatNames = new TreeSet<>(mediaFormatNames);
  211.     return IPECONFIG_OVERLAY_ROOTPATH + componentContentPath
  212.         + "/" + NN_MEDIA_FORMAT + "/" + StringUtils.join(sortedMediaFormatNames, "/")
  213.         + "/" + NN_CONFIG;
  214.   }

  215. }