MediaFormatHandlerImpl.java

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

  21. import java.util.Collections;
  22. import java.util.Comparator;
  23. import java.util.HashMap;
  24. import java.util.Map;
  25. import java.util.SortedSet;
  26. import java.util.TreeSet;

  27. import org.apache.commons.lang3.StringUtils;
  28. import org.apache.sling.api.SlingHttpServletRequest;
  29. import org.apache.sling.api.resource.Resource;
  30. import org.apache.sling.models.annotations.Model;
  31. import org.apache.sling.models.annotations.injectorspecific.OSGiService;
  32. import org.apache.sling.models.annotations.injectorspecific.SlingObject;
  33. import org.jetbrains.annotations.NotNull;
  34. import org.jetbrains.annotations.Nullable;

  35. import io.wcm.handler.media.MediaFileType;
  36. import io.wcm.handler.media.format.MediaFormat;
  37. import io.wcm.handler.media.format.MediaFormatHandler;
  38. import io.wcm.handler.media.format.MediaFormatProviderManager;
  39. import io.wcm.handler.media.format.MediaFormatRankingComparator;
  40. import io.wcm.handler.media.format.MediaFormatSizeRankingComparator;
  41. import io.wcm.handler.media.format.Ratio;

  42. /**
  43.  * Media format handling.
  44.  */
  45. @Model(adaptables = {
  46.     SlingHttpServletRequest.class, Resource.class
  47. }, adapters = MediaFormatHandler.class)
  48. public final class MediaFormatHandlerImpl implements MediaFormatHandler {

  49.   @SlingObject
  50.   private Resource currentResource;
  51.   @OSGiService
  52.   private MediaFormatProviderManager mediaFormatProviderManager;

  53.   // do not access directly - used for caching. use getMediaFormatsForCurrentResource() and getMediaFormatMap() instead
  54.   private SortedSet<MediaFormat> mediaFormats;
  55.   private Map<String, MediaFormat> mediaFormatMap;

  56.   private SortedSet<MediaFormat> getMediaFormatsForCurrentResource() {
  57.     if (this.mediaFormats == null) {
  58.       this.mediaFormats = mediaFormatProviderManager.getMediaFormats(currentResource);
  59.     }
  60.     return this.mediaFormats;
  61.   }

  62.   private Map<String, MediaFormat> getMediaFormatMap() {
  63.     if (this.mediaFormatMap == null) {
  64.       this.mediaFormatMap = new HashMap<>();
  65.       for (MediaFormat mediaFormat : getMediaFormatsForCurrentResource()) {
  66.         this.mediaFormatMap.put(mediaFormat.getName(), mediaFormat);
  67.       }
  68.     }
  69.     return this.mediaFormatMap;
  70.   }

  71.   /**
  72.    * Resolves media format name to media format object.
  73.    * @param mediaFormatName Media format name
  74.    * @return Media format or null if no match found
  75.    */
  76.   @Override
  77.   public MediaFormat getMediaFormat(@NotNull String mediaFormatName) {
  78.     return getMediaFormatMap().get(mediaFormatName);
  79.   }

  80.   /**
  81.    * Get media formats defined by a CMS application that is responsible for the given media library path.
  82.    * @return Media formats sorted by media format name.
  83.    */
  84.   @Override
  85.   public @NotNull SortedSet<MediaFormat> getMediaFormats() {
  86.     return getMediaFormatsForCurrentResource();
  87.   }

  88.   /**
  89.    * Get media formats defined by a CMS application that is responsible for the given media library path.
  90.    * @param comparator Comparator for set
  91.    * @return Media formats
  92.    */
  93.   @Override
  94.   public @NotNull SortedSet<MediaFormat> getMediaFormats(@NotNull Comparator<MediaFormat> comparator) {
  95.     SortedSet<MediaFormat> set = new TreeSet<>(comparator);
  96.     set.addAll(getMediaFormatsForCurrentResource());
  97.     return Collections.unmodifiableSortedSet(set);
  98.   }

  99.   /**
  100.    * Get list of media formats that have the same (or bigger) resolution as the requested media format
  101.    * and (nearly) the same aspect ratio.
  102.    * @param mediaFormatRequested Requested media format
  103.    * @param filterRenditionGroup Only check media formats of the same rendition group.
  104.    * @return Matching media formats, sorted by size (biggest first), ranking, name
  105.    */
  106.   @Override
  107.   @SuppressWarnings({ "java:S3776", "java:S1066" }) //ignore complexity
  108.   public @NotNull SortedSet<MediaFormat> getSameBiggerMediaFormats(@NotNull MediaFormat mediaFormatRequested, boolean filterRenditionGroup) {
  109.     SortedSet<MediaFormat> matchingFormats = new TreeSet<>(new MediaFormatSizeRankingComparator());

  110.     // if filter by rendition group is enabled, but the requested media format does not define one,
  111.     // use only the requested format
  112.     if (filterRenditionGroup && StringUtils.isEmpty(mediaFormatRequested.getRenditionGroup())) {
  113.       matchingFormats.add(mediaFormatRequested);
  114.     }
  115.     else {
  116.       for (MediaFormat mediaFormat : getMediaFormats()) {

  117.         // if filter by rendition group is enabled, check only media formats of same rendition group
  118.         if (!filterRenditionGroup
  119.             || StringUtils.equals(mediaFormat.getRenditionGroup(), mediaFormatRequested.getRenditionGroup())) {

  120.           // check if size matched (image size is same or bigger)
  121.           if (isRenditionMatchSizeSameBigger(mediaFormat, mediaFormatRequested)) { //NOPMD

  122.             // if media formats have ratios, check ratio (with tolerance)
  123.             // otherwise add to list anyway, it *can* contain matching media items
  124.             if (Ratio.matches(mediaFormat, mediaFormatRequested) //NOPMD
  125.                 || !mediaFormat.hasRatio() || !mediaFormatRequested.hasRatio()) {

  126.               // check for supported file extension
  127.               if (isRenditionMatchExtension(mediaFormat)) { //NOPMD
  128.                 matchingFormats.add(mediaFormat);
  129.               }
  130.             }

  131.           }

  132.         }

  133.       }
  134.     }

  135.     return matchingFormats;
  136.   }

  137.   /**
  138.    * Get list of possible media formats that can be rendered from the given media format, i.e. same size or smaller
  139.    * and (nearly) the same aspect ratio.
  140.    * @param mediaFormatRequested Available media format
  141.    * @param filterRenditionGroup Only check media formats of the same rendition group.
  142.    * @return Matching media formats, sorted by size (biggest first), ranking, name
  143.    */
  144.   @Override
  145.   @SuppressWarnings({ "java:S3776", "java:S1066" }) //ignore complexity
  146.   public @NotNull SortedSet<MediaFormat> getSameSmallerMediaFormats(@NotNull MediaFormat mediaFormatRequested, boolean filterRenditionGroup) {
  147.     SortedSet<MediaFormat> matchingFormats = new TreeSet<>(new MediaFormatSizeRankingComparator());

  148.     // if filter by rendition group is enabled, but the requested media format does not define one,
  149.     // use only the requested format
  150.     if (filterRenditionGroup && StringUtils.isEmpty(mediaFormatRequested.getRenditionGroup())) {
  151.       matchingFormats.add(mediaFormatRequested);
  152.     }
  153.     else {
  154.       for (MediaFormat mediaFormat : getMediaFormats()) {

  155.         // if filter by rendition group is enabled, check only media formats of same rendition group
  156.         if (!filterRenditionGroup
  157.             || StringUtils.equals(mediaFormat.getRenditionGroup(), mediaFormatRequested.getRenditionGroup())) {

  158.           // check if size matched (image size is same or smaller)
  159.           if (isRenditionMatchSizeSameSmaller(mediaFormat, mediaFormatRequested)) { //NOPMD

  160.             // if media formats have ratios, check ratio (with tolerance)
  161.             // otherwise add to list anyway, it *can* contain matching media items
  162.             if (Ratio.matches(mediaFormat, mediaFormatRequested) //NOPMD
  163.                 || !mediaFormat.hasRatio() || !mediaFormatRequested.hasRatio()) {

  164.               // check for supported file extension
  165.               if (isRenditionMatchExtension(mediaFormat)) { //NOPMD
  166.                 matchingFormats.add(mediaFormat);
  167.               }
  168.             }

  169.           }

  170.         }

  171.       }
  172.     }

  173.     return matchingFormats;
  174.   }

  175.   /**
  176.    * Checks if the given media format size is same size or bigger than the requested one.
  177.    * @param mediaFormat Media format
  178.    * @param mediaFormatRequested Requested media format
  179.    * @return true if media format is same size or bigger
  180.    */
  181.   private boolean isRenditionMatchSizeSameBigger(MediaFormat mediaFormat, MediaFormat mediaFormatRequested) {
  182.     long widthRequested = getEffectiveMinWidthPreferringMinWidthHeight(mediaFormatRequested);
  183.     long heightRequested = getEffectiveMinHeightPreferringMinWidthHeight(mediaFormatRequested);

  184.     long widthMax = mediaFormat.getEffectiveMaxWidth();
  185.     long heightMax = mediaFormat.getEffectiveMaxHeight();

  186.     return ((widthMax >= widthRequested) || (widthMax == 0))
  187.         && ((heightMax >= heightRequested) || (heightMax == 0));
  188.   }

  189.   /**
  190.    * Checks if the given media format size is same size or smaller than the requested one.
  191.    * @param mediaFormat Media format
  192.    * @param mediaFormatRequested Requested media format
  193.    * @return true if media format is same size or smaller
  194.    */
  195.   private boolean isRenditionMatchSizeSameSmaller(MediaFormat mediaFormat, MediaFormat mediaFormatRequested) {
  196.     long widthRequested = getEffectiveMinWidthPreferringMinWidthHeight(mediaFormatRequested);
  197.     long heightRequested = getEffectiveMinHeightPreferringMinWidthHeight(mediaFormatRequested);

  198.     long widthMin = getEffectiveMinWidthPreferringMinWidthHeight(mediaFormat);
  199.     long heightMin = getEffectiveMinHeightPreferringMinWidthHeight(mediaFormat);

  200.     return widthMin <= widthRequested && heightMin <= heightRequested;
  201.   }

  202.   private long getEffectiveMinWidthPreferringMinWidthHeight(MediaFormat mf) {
  203.     if (mf.getMinWidthHeight() > 0) {
  204.       return mf.getMinWidthHeight();
  205.     }
  206.     else {
  207.       return mf.getEffectiveMinWidth();
  208.     }
  209.   }

  210.   private long getEffectiveMinHeightPreferringMinWidthHeight(MediaFormat mf) {
  211.     if (mf.getMinWidthHeight() > 0) {
  212.       return mf.getMinWidthHeight();
  213.     }
  214.     else {
  215.       return mf.getEffectiveMinHeight();
  216.     }
  217.   }

  218.   /**
  219.    * Checks if one of the extensions of the given media format are supported for renditions.
  220.    * @param mediaFormat Media format
  221.    * @return true if supported extension found
  222.    */
  223.   private boolean isRenditionMatchExtension(MediaFormat mediaFormat) {
  224.     for (String extension : mediaFormat.getExtensions()) {
  225.       if (MediaFileType.isImage(extension)) {
  226.         return true;
  227.       }
  228.     }
  229.     return false;
  230.   }

  231.   /**
  232.    * Detect matching media format.
  233.    * @param extension File extension
  234.    * @param fileSize File size
  235.    * @param width Image width (or 0 if not image)
  236.    * @param height Image height (or 0 if not image)
  237.    * @return Media format or null if no matching media format found
  238.    */
  239.   @Override
  240.   public MediaFormat detectMediaFormat(@Nullable String extension, long fileSize, long width, long height) {
  241.     SortedSet<MediaFormat> matchingFormats = detectMediaFormats(extension, fileSize, width, height);
  242.     return !matchingFormats.isEmpty() ? matchingFormats.first() : null;
  243.   }

  244.   /**
  245.    * Detect all matching media formats.
  246.    * @param extension File extension
  247.    * @param fileSize File size
  248.    * @param width Image width (or 0 if not image)
  249.    * @param height Image height (or 0 if not image)
  250.    * @return Matching media formats sorted by their ranking or an empty list if no matching format was found
  251.    */
  252.   @Override
  253.   @SuppressWarnings("java:S3776") //ignore complexity
  254.   public @NotNull SortedSet<MediaFormat> detectMediaFormats(@Nullable String extension, long fileSize, long width, long height) {

  255.     // sort media formats by ranking
  256.     SortedSet<MediaFormat> matchingFormats = new TreeSet<>(new MediaFormatRankingComparator());

  257.     for (MediaFormat mediaFormat : getMediaFormats()) {

  258.       // skip media formats with negative ranking
  259.       if (mediaFormat.getRanking() < 0) {
  260.         continue;
  261.       }

  262.       // check extension
  263.       boolean extensionMatch = false;
  264.       if (mediaFormat.getExtensions() != null) {
  265.         for (String ext : mediaFormat.getExtensions()) {
  266.           if (StringUtils.equalsIgnoreCase(ext, extension)) {
  267.             extensionMatch = true;
  268.             break;
  269.           }
  270.         }
  271.       }
  272.       else {
  273.         extensionMatch = true;
  274.       }

  275.       // check file size
  276.       boolean fileSizeMatch = false;
  277.       if (mediaFormat.getFileSizeMax() > 0) {
  278.         fileSizeMatch = (fileSize <= mediaFormat.getFileSizeMax());
  279.       }
  280.       else {
  281.         fileSizeMatch = true;
  282.       }

  283.       // width/height match
  284.       boolean dimensionMatch = false;
  285.       if (width > 0 && height > 0) {
  286.         if (mediaFormat.getMinWidthHeight() > 0) {
  287.           dimensionMatch = (width >= mediaFormat.getMinWidthHeight())
  288.               || (height >= mediaFormat.getMinWidthHeight());
  289.         }
  290.         else {
  291.           dimensionMatch = (mediaFormat.getEffectiveMinWidth() == 0 || width >= mediaFormat.getEffectiveMinWidth())
  292.               && (mediaFormat.getEffectiveMaxWidth() == 0 || width <= mediaFormat.getEffectiveMaxWidth())
  293.               && (mediaFormat.getEffectiveMinHeight() == 0 || height >= mediaFormat.getEffectiveMinHeight())
  294.               && (mediaFormat.getEffectiveMaxHeight() == 0 || height <= mediaFormat.getEffectiveMaxHeight());
  295.         }
  296.       }
  297.       else {
  298.         dimensionMatch = true;
  299.       }

  300.       boolean ratioMatch = false;
  301.       if (mediaFormat.hasRatio() && width > 0 && height > 0) {
  302.         double formatRatio = mediaFormat.getRatio();
  303.         double ratio = (double)width / height;
  304.         ratioMatch = Ratio.matches(ratio, formatRatio);
  305.       }
  306.       else {
  307.         ratioMatch = true;
  308.       }

  309.       if (extensionMatch && fileSizeMatch && dimensionMatch && ratioMatch) {
  310.         matchingFormats.add(mediaFormat);
  311.       }
  312.     }

  313.     return matchingFormats;
  314.   }

  315. }