View Javadoc
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.mediasource.dam.impl;
21  
22  import java.util.Date;
23  import java.util.List;
24  
25  import org.apache.commons.io.FilenameUtils;
26  import org.apache.sling.api.adapter.SlingAdaptable;
27  import org.apache.sling.api.resource.Resource;
28  import org.apache.sling.api.resource.ValueMap;
29  import org.jetbrains.annotations.NotNull;
30  import org.jetbrains.annotations.Nullable;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
35  import io.wcm.handler.media.CropDimension;
36  import io.wcm.handler.media.MediaArgs;
37  import io.wcm.handler.media.MediaFileType;
38  import io.wcm.handler.media.Rendition;
39  import io.wcm.handler.media.UriTemplate;
40  import io.wcm.handler.media.UriTemplateType;
41  import io.wcm.handler.media.format.MediaFormat;
42  import io.wcm.handler.url.UrlHandler;
43  import io.wcm.sling.commons.adapter.AdaptTo;
44  import io.wcm.wcm.commons.caching.ModificationDate;
45  
46  /**
47   * {@link Rendition} implementation for DAM asset renditions.
48   */
49  class DamRendition extends SlingAdaptable implements Rendition {
50  
51    private final DamContext damContext;
52    private final MediaArgs mediaArgs;
53    private final RenditionMetadata rendition;
54    private boolean fallback;
55  
56    private static final Logger log = LoggerFactory.getLogger(DamRendition.class);
57  
58    /**
59     * @param cropDimension Crop dimension
60     * @param mediaArgs Media args
61     * @param damContext DAM context objects
62     */
63    @SuppressWarnings("java:S3776") // ignore complexity
64    DamRendition(CropDimension cropDimension, Integer rotation, MediaArgs mediaArgs, DamContext damContext) {
65      this.damContext = damContext;
66      this.mediaArgs = mediaArgs;
67      RenditionMetadata resolvedRendition = null;
68  
69      // if no transformation parameters are given find non-transformed matching rendition
70      if (cropDimension == null && rotation == null) {
71        RenditionHandler renditionHandler = new DefaultRenditionHandler(damContext);
72        resolvedRendition = renditionHandler.getRendition(mediaArgs);
73      }
74  
75      else {
76        // try to match with all transformations that are configured
77        RenditionHandler renditionHandler = new TransformedRenditionHandler(cropDimension, rotation, damContext);
78        resolvedRendition = renditionHandler.getRendition(mediaArgs);
79  
80        // if no match was found check against renditions without applying the explicit cropping
81        if (resolvedRendition == null && cropDimension != null) {
82          if (rotation != null) {
83            renditionHandler = new TransformedRenditionHandler(null, rotation, damContext);
84            resolvedRendition = renditionHandler.getRendition(mediaArgs);
85          }
86          else {
87            renditionHandler = new DefaultRenditionHandler(damContext);
88            resolvedRendition = renditionHandler.getRendition(mediaArgs);
89          }
90          if (resolvedRendition != null) {
91            fallback = true;
92          }
93        }
94      }
95  
96      // if no match was found and auto-cropping is enabled, try to build a transformed rendition
97      // with automatically devised cropping parameters
98      if (resolvedRendition == null && mediaArgs.isAutoCrop()) {
99        DamAutoCropping autoCropping = new DamAutoCropping(damContext, mediaArgs);
100       List<CropDimension> autoCropDimensions = autoCropping.calculateAutoCropDimensions();
101       for (CropDimension autoCropDimension : autoCropDimensions) {
102         RenditionHandler renditionHandler = new TransformedRenditionHandler(autoCropDimension, rotation, damContext);
103         resolvedRendition = renditionHandler.getRendition(mediaArgs);
104         if (resolvedRendition != null) {
105           break;
106         }
107       }
108     }
109 
110     if (log.isTraceEnabled()) {
111       log.trace("DamRendition: resolvedRendition={}, mediaArgs={}, cropDimension={}, rotation={}",
112           resolvedRendition, mediaArgs, cropDimension, rotation);
113     }
114 
115     this.rendition = resolvedRendition;
116   }
117 
118   @Override
119   public String getUrl() {
120     if (rendition == null) {
121       return null;
122     }
123     String url = null;
124 
125     // check for dynamic media support
126     if (damContext.isDynamicMediaEnabled()) {
127       if (damContext.isDynamicMediaAsset()) {
128         url = buildDynamicMediaUrl();
129         if (url == null) {
130           // asset is valid DM asset, but no valid rendition could be generated
131           // reason might be that the smart-cropped rendition was too small for the requested size
132           return null;
133         }
134       }
135       else {
136         // DM is enabled, but given asset is not a DM asset
137         if (damContext.isDynamicMediaAemFallbackDisabled()) {
138           log.warn("Asset is not a valid DM asset, fallback disabled, rendition invalid: {}", rendition.getRendition().getPath());
139           return null;
140         }
141         else {
142           log.trace("Asset is not a valid DM asset, fallback to AEM-rendered rendition: {}", rendition.getRendition().getPath());
143         }
144       }
145     }
146 
147     // check for web-optimized image delivery
148     if (url == null) {
149       url = buildWebOptimizedImageDeliveryUrl();
150     }
151 
152     // Fallback: Render renditions in AEM - build externalized URL
153     if (url == null) {
154       UrlHandler urlHandler = AdaptTo.notNull(damContext, UrlHandler.class);
155       String mediaPath = rendition.getMediaPath(mediaArgs.isContentDispositionAttachment());
156       url = urlHandler.get(mediaPath).urlMode(mediaArgs.getUrlMode())
157           .buildExternalResourceUrl(rendition.adaptTo(Resource.class));
158     }
159 
160     return url;
161   }
162 
163   /**
164    * Build DM URL for this rendition based on the calculated DM path and the configured DM hostname.
165    * @return DM URL or null if either DM path or configured DM hostname is null
166    */
167   private @Nullable String buildDynamicMediaUrl() {
168     String dynamicMediaPath = rendition.getDynamicMediaPath(mediaArgs.isContentDispositionAttachment(), damContext);
169     String productionAssetUrl = damContext.getDynamicMediaServerUrl();
170     if (dynamicMediaPath != null && productionAssetUrl != null) {
171       return productionAssetUrl + dynamicMediaPath;
172     }
173     else {
174       return null;
175     }
176   }
177 
178   /**
179    * Build web-optimized image delivery URL if this is a raster image.
180    * @return URL or null
181    */
182   private @Nullable String buildWebOptimizedImageDeliveryUrl() {
183     if (MediaFileType.isImage(getFileExtension())
184         && !MediaFileType.isVectorImage(getFileExtension())
185         && !mediaArgs.isContentDispositionAttachment()
186         && !mediaArgs.isWebOptimizedImageDeliveryDisabled()) {
187       return rendition.getWebOptimizedImageDeliveryPath(damContext);
188     }
189     else {
190       return null;
191     }
192   }
193 
194 
195   @Override
196   public String getPath() {
197     if (this.rendition != null) {
198       return this.rendition.getRendition().getPath();
199     }
200     else {
201       return null;
202     }
203   }
204 
205   @Override
206   public String getFileName() {
207     if (this.rendition != null) {
208       return this.rendition.getFileName(this.mediaArgs.isContentDispositionAttachment());
209     }
210     else {
211       return null;
212     }
213   }
214 
215   @Override
216   public String getFileExtension() {
217     return FilenameUtils.getExtension(getFileName());
218   }
219 
220   @Override
221   public long getFileSize() {
222     if (this.rendition != null) {
223       return this.rendition.getFileSize();
224     }
225     else {
226       return 0L;
227     }
228   }
229 
230   @Override
231   public String getMimeType() {
232     if (this.rendition != null) {
233       return this.rendition.getMimeType();
234     }
235     else {
236       return null;
237     }
238   }
239 
240   @Override
241   public Date getModificationDate() {
242     if (this.rendition != null) {
243       return ModificationDate.get(this.rendition.getRendition().adaptTo(Resource.class));
244     }
245     else {
246       return null;
247     }
248   }
249 
250   @Override
251   public MediaFormat getMediaFormat() {
252     if (this.rendition != null) {
253       return this.rendition.getMediaFormat();
254     }
255     else {
256       return null;
257     }
258   }
259 
260   @Override
261   @SuppressWarnings("null")
262   @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
263   public @NotNull ValueMap getProperties() {
264     if (this.rendition != null) {
265       return this.rendition.getRendition().adaptTo(Resource.class).getValueMap();
266     }
267     else {
268       return ValueMap.EMPTY;
269     }
270   }
271 
272   @Override
273   public boolean isImage() {
274     return MediaFileType.isImage(getFileExtension());
275   }
276 
277   @Override
278   public boolean isBrowserImage() {
279     return MediaFileType.isBrowserImage(getFileExtension());
280   }
281 
282   @Override
283   public boolean isVectorImage() {
284     return MediaFileType.isVectorImage(getFileExtension());
285   }
286 
287   @Override
288   public boolean isDownload() {
289     return !isImage();
290   }
291 
292   @Override
293   public long getWidth() {
294     if (this.rendition != null) {
295       return this.rendition.getWidth();
296     }
297     else {
298       return 0;
299     }
300   }
301 
302   @Override
303   public long getHeight() {
304     if (this.rendition != null) {
305       return this.rendition.getHeight();
306     }
307     else {
308       return 0;
309     }
310   }
311 
312   @Override
313   public boolean isFallback() {
314     return fallback;
315   }
316 
317   @Override
318   public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type) {
319     if (this.rendition == null) {
320       throw new IllegalStateException("Rendition is not valid.");
321     }
322     if (type == UriTemplateType.CROP_CENTER) {
323       throw new IllegalArgumentException("CROP_CENTER not supported for rendition URI templates.");
324     }
325     return this.rendition.getUriTemplate(type, damContext);
326   }
327 
328   @Override
329   @SuppressWarnings("null")
330   public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
331     if (this.rendition != null) {
332       AdapterType result = this.rendition.adaptTo(type);
333       if (result != null) {
334         return result;
335       }
336     }
337     return super.adaptTo(type);
338   }
339 
340   @Override
341   public String toString() {
342     if (rendition != null) {
343       return rendition.toString();
344     }
345     return super.toString();
346   }
347 
348 }