MediaHandlerImpl.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.impl;

  21. import java.util.ArrayList;
  22. import java.util.List;

  23. import org.apache.sling.api.SlingHttpServletRequest;
  24. import org.apache.sling.api.adapter.Adaptable;
  25. import org.apache.sling.api.resource.Resource;
  26. import org.apache.sling.models.annotations.Model;
  27. import org.apache.sling.models.annotations.injectorspecific.OSGiService;
  28. import org.apache.sling.models.annotations.injectorspecific.Self;
  29. import org.jetbrains.annotations.NotNull;
  30. import org.jetbrains.annotations.Nullable;
  31. import org.slf4j.Logger;
  32. import org.slf4j.LoggerFactory;

  33. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  34. import io.wcm.handler.commons.dom.HtmlElement;
  35. import io.wcm.handler.media.Media;
  36. import io.wcm.handler.media.MediaArgs;
  37. import io.wcm.handler.media.MediaBuilder;
  38. import io.wcm.handler.media.MediaHandler;
  39. import io.wcm.handler.media.MediaInvalidReason;
  40. import io.wcm.handler.media.MediaRequest;
  41. import io.wcm.handler.media.format.MediaFormat;
  42. import io.wcm.handler.media.format.MediaFormatHandler;
  43. import io.wcm.handler.media.spi.MediaHandlerConfig;
  44. import io.wcm.handler.media.spi.MediaMarkupBuilder;
  45. import io.wcm.handler.media.spi.MediaProcessor;
  46. import io.wcm.handler.media.spi.MediaSource;
  47. import io.wcm.sling.commons.adapter.AdaptTo;
  48. import io.wcm.wcm.commons.component.ComponentPropertyResolverFactory;

  49. /**
  50.  * Default Implementation of a {@link MediaHandler}.
  51.  */
  52. @Model(adaptables = {
  53.     SlingHttpServletRequest.class, Resource.class
  54. }, adapters = MediaHandler.class)
  55. public final class MediaHandlerImpl implements MediaHandler {

  56.   @Self
  57.   private Adaptable adaptable;
  58.   @Self
  59.   private MediaHandlerConfig mediaHandlerConfig;
  60.   @Self
  61.   private MediaFormatHandler mediaFormatHandler;
  62.   @OSGiService
  63.   private ComponentPropertyResolverFactory componentPropertyResolverFactory;

  64.   private static final Logger log = LoggerFactory.getLogger(MediaHandlerImpl.class);

  65.   @Override
  66.   public @NotNull MediaBuilder get(@Nullable Resource resource) {
  67.     return new MediaBuilderImpl(resource, this, componentPropertyResolverFactory);
  68.   }

  69.   @Override
  70.   public @NotNull MediaBuilder get(@Nullable Resource resource, @NotNull MediaArgs mediaArgs) {
  71.     return get(resource).args(mediaArgs);
  72.   }

  73.   @Override
  74.   public @NotNull MediaBuilder get(@Nullable Resource resource, MediaFormat @NotNull... mediaFormats) {
  75.     return get(resource).mediaFormats(mediaFormats);
  76.   }

  77.   @Override
  78.   public @NotNull MediaBuilder get(@Nullable String mediaRef) {
  79.     return new MediaBuilderImpl(mediaRef, null, this, componentPropertyResolverFactory);
  80.   }

  81.   @Override
  82.   public @NotNull MediaBuilder get(@Nullable String mediaRef, @Nullable Resource contextResource) {
  83.     return new MediaBuilderImpl(mediaRef, contextResource, this, componentPropertyResolverFactory);
  84.   }

  85.   @Override
  86.   public @NotNull MediaBuilder get(@Nullable String mediaRef, @NotNull MediaArgs mediaArgs) {
  87.     return get(mediaRef).args(mediaArgs);
  88.   }

  89.   @Override
  90.   public @NotNull MediaBuilder get(@Nullable String mediaRef, MediaFormat @NotNull... mediaFormats) {
  91.     return get(mediaRef).mediaFormats(mediaFormats);
  92.   }

  93.   @Override
  94.   public @NotNull MediaBuilder get(@NotNull MediaRequest mediaRequest) {
  95.     return new MediaBuilderImpl(mediaRequest, this);
  96.   }

  97.   /**
  98.    * Resolves the media request
  99.    * @param mediaRequest Media request
  100.    * @return Media metadata (never null)
  101.    */
  102.   @NotNull
  103.   @SuppressWarnings({
  104.       "null", "unused", "java:S2589",
  105.       "java:S3776", "java:S6541", // ignore complexity
  106.       "java:S112", // allow runtime exception
  107.       "java:S1192" // multiple strings
  108.   })
  109.   @SuppressFBWarnings({ "CORRECTNESS", "STYLE" })
  110.   Media processRequest(@NotNull final MediaRequest mediaRequest) {

  111.     // detect media source
  112.     MediaSource mediaSource = null;
  113.     List<Class<? extends MediaSource>> mediaSources = mediaHandlerConfig.getSources();
  114.     if (mediaSources == null || mediaSources.isEmpty()) {
  115.       throw new RuntimeException("No media sources defined.");
  116.     }
  117.     MediaSource firstMediaSource = null;
  118.     for (Class<? extends MediaSource> candidateMediaSourceClass : mediaSources) {
  119.       MediaSource candidateMediaSource = AdaptTo.notNull(adaptable, candidateMediaSourceClass);
  120.       if (candidateMediaSource.accepts(mediaRequest)) {
  121.         mediaSource = candidateMediaSource;
  122.         break;
  123.       }
  124.       else if (firstMediaSource == null) {
  125.         firstMediaSource = candidateMediaSource;
  126.       }
  127.     }
  128.     // if no media source was detected use first media resource defined
  129.     if (mediaSource == null) {
  130.       mediaSource = firstMediaSource;
  131.     }
  132.     Media media = new Media(mediaSource, mediaRequest);

  133.     // resolve media format names to media formats
  134.     MediaFormatResolver mediaFormatResolver = new MediaFormatResolver(mediaFormatHandler);
  135.     if (!mediaFormatResolver.resolve(mediaRequest.getMediaArgs())) {
  136.       media.setMediaInvalidReason(MediaInvalidReason.INVALID_MEDIA_FORMAT);
  137.       return media;
  138.     }

  139.     // if only downloads are accepted prepare media format filter set which only contains download media formats
  140.     if (!resolveDownloadMediaFormats(mediaRequest.getMediaArgs())) {
  141.       media.setMediaInvalidReason(MediaInvalidReason.INVALID_MEDIA_FORMAT);
  142.       return media;
  143.     }

  144.     // apply defaults to media args
  145.     if (mediaRequest.getMediaArgs().getIncludeAssetAemRenditions() == null) {
  146.       mediaRequest.getMediaArgs().includeAssetAemRenditions(mediaHandlerConfig.getIncludeAssetAemRenditionsByDefault());
  147.     }

  148.     if (log.isTraceEnabled()) {
  149.       log.trace("Start processing media request (mediaSource={}): {}", mediaSource.getId(), mediaRequest);
  150.     }

  151.     // preprocess media request before resolving
  152.     List<Class<? extends MediaProcessor>> mediaPreProcessors = mediaHandlerConfig.getPreProcessors();
  153.     if (mediaPreProcessors != null) {
  154.       for (Class<? extends MediaProcessor> processorClass : mediaPreProcessors) {
  155.         log.trace("Apply pre processor ({}): {}", processorClass, mediaRequest);
  156.         MediaProcessor processor = AdaptTo.notNull(adaptable, processorClass);
  157.         media = processor.process(media);
  158.         if (media == null) {
  159.           throw new RuntimeException("MediaPreProcessor '" + processor + "' returned null, request: " + mediaRequest);
  160.         }
  161.       }
  162.     }

  163.     if (media.getMediaInvalidReason() == null) {

  164.       // resolve media request
  165.       media = mediaSource.resolveMedia(media);
  166.       if (media == null) {
  167.         throw new RuntimeException("MediaType '" + mediaSource + "' returned null, request: " + mediaRequest);
  168.       }

  169.       // generate markup (if markup builder is available) - first accepting wins
  170.       List<Class<? extends MediaMarkupBuilder>> mediaMarkupBuilders = mediaHandlerConfig.getMarkupBuilders();
  171.       if (mediaMarkupBuilders != null) {
  172.         media.setElementBuilder(m -> {
  173.           for (Class<? extends MediaMarkupBuilder> mediaMarkupBuilderClass : mediaMarkupBuilders) {
  174.             MediaMarkupBuilder mediaMarkupBuilder = AdaptTo.notNull(adaptable, mediaMarkupBuilderClass);
  175.             if (mediaMarkupBuilder.accepts(m)) {
  176.               log.trace("Apply media markup builder ({}): {}", mediaMarkupBuilderClass, mediaRequest);
  177.               return mediaMarkupBuilder.build(m);
  178.             }
  179.           }
  180.           return null;
  181.         });
  182.       }

  183.       // postprocess media request after resolving
  184.       List<Class<? extends MediaProcessor>> mediaPostProcessors = mediaHandlerConfig.getPostProcessors();
  185.       if (mediaPostProcessors != null) {
  186.         for (Class<? extends MediaProcessor> processorClass : mediaPostProcessors) {
  187.           log.trace("Apply post processor ({}): {}", processorClass, mediaRequest);
  188.           MediaProcessor processor = AdaptTo.notNull(adaptable, processorClass);
  189.           media = processor.process(media);
  190.           if (media == null) {
  191.             throw new RuntimeException("MediaPostProcessor '" + processor + "' returned null, request: " + mediaRequest);
  192.           }
  193.         }
  194.       }

  195.     }
  196.     else {
  197.       log.trace("Skip media resolving because media was set to invalid by prepocessor. reason={}, message={}",
  198.           media.getMediaInvalidReason(), media.getMediaInvalidReasonCustomMessage());
  199.     }

  200.     log.debug("Finished media processing: {}", media);

  201.     return media;
  202.   }

  203.   @Override
  204.   @SuppressWarnings({ "null", "java:S2589" })
  205.   public boolean isValidElement(HtmlElement element) {

  206.     // if it is null it is always invalid
  207.     if (element == null) {
  208.       return false;
  209.     }

  210.     // otherwise check if any media markup builder is available that rates this html element valid
  211.     List<Class<? extends MediaMarkupBuilder>> mediaMarkupBuilders = mediaHandlerConfig.getMarkupBuilders();
  212.     if (mediaMarkupBuilders != null) {
  213.       for (Class<? extends MediaMarkupBuilder> mediaMarkupBuilderClass : mediaMarkupBuilders) {
  214.         MediaMarkupBuilder mediaMarkupBuilder = AdaptTo.notNull(adaptable, mediaMarkupBuilderClass);
  215.         if (mediaMarkupBuilder.isValidMedia(element)) {
  216.           return true;
  217.         }
  218.       }
  219.     }

  220.     return false;
  221.   }

  222.   /**
  223.    * If a set of media formats is given it is filtered to contain only download media formats.
  224.    * If no is given a new set of allowed media formats is created by getting from all media formats those marked as
  225.    * "download".
  226.    * If the result is an empty set of media formats (but downloads are requested) resolution is not successful.
  227.    * If the result is an empty set because no media format requests and no download format at all defined, it is
  228.    * successful.
  229.    * @param mediaArgs Media args
  230.    * @return true if resolving was successful
  231.    */
  232.   private boolean resolveDownloadMediaFormats(MediaArgs mediaArgs) {
  233.     if (!mediaArgs.isDownload()) {
  234.       // not filtering for downloads
  235.       return true;
  236.     }
  237.     List<MediaFormat> candidates = new ArrayList<>();
  238.     boolean fallbackToAllMediaFormats = false;
  239.     if (mediaArgs.getMediaFormats() != null) {
  240.       candidates.addAll(List.of(mediaArgs.getMediaFormats()));
  241.     }
  242.     else {
  243.       candidates.addAll(mediaFormatHandler.getMediaFormats());
  244.       fallbackToAllMediaFormats = true;
  245.     }
  246.     MediaFormat[] result = candidates.stream()
  247.         .filter(MediaFormat::isDownload)
  248.         .toArray(size -> new MediaFormat[size]);
  249.     if (result.length > 0) {
  250.       mediaArgs.mediaFormats(result);
  251.       return true;
  252.     }
  253.     else {
  254.       // not successful when an explicit list of media formats was given, and this did not contain any download format
  255.       // successful when no media format was given, and the global list of all formats does not contain any download format
  256.       return fallbackToAllMediaFormats;
  257.     }
  258.   }

  259.   @Override
  260.   @SuppressWarnings("java:S112") // allow runtime exception
  261.   public Media invalid() {
  262.     // build invalid media with first media source
  263.     Class<? extends MediaSource> mediaSourceClass = mediaHandlerConfig.getSources().stream().findFirst().orElse(null);
  264.     if (mediaSourceClass == null) {
  265.       throw new RuntimeException("No media sources defined.");
  266.     }
  267.     MediaSource mediaSource = AdaptTo.notNull(adaptable, mediaSourceClass);
  268.     Media media = new Media(mediaSource, new MediaRequest((String)null, null));
  269.     media.setMediaInvalidReason(MediaInvalidReason.MEDIA_REFERENCE_MISSING);
  270.     return media;
  271.   }

  272. }