1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.handler.mediasource.dam.impl.dynamicmedia;
21
22 import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT;
23
24 import java.util.Map;
25 import java.util.regex.Pattern;
26
27 import javax.jcr.RepositoryException;
28
29 import org.apache.commons.lang3.StringUtils;
30 import org.apache.sling.api.adapter.Adaptable;
31 import org.apache.sling.api.resource.LoginException;
32 import org.apache.sling.api.resource.Resource;
33 import org.apache.sling.api.resource.ResourceResolver;
34 import org.apache.sling.api.resource.ResourceResolverFactory;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37 import org.osgi.service.component.annotations.Activate;
38 import org.osgi.service.component.annotations.Component;
39 import org.osgi.service.component.annotations.Reference;
40 import org.osgi.service.metatype.annotations.AttributeDefinition;
41 import org.osgi.service.metatype.annotations.Designate;
42 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import com.day.cq.dam.api.Asset;
47 import com.day.cq.dam.api.DamConstants;
48 import com.day.cq.dam.api.s7dam.utils.PublishUtils;
49
50 import io.wcm.handler.media.Dimension;
51 import io.wcm.handler.url.SiteConfig;
52 import io.wcm.handler.url.UrlHandler;
53 import io.wcm.handler.url.UrlMode;
54 import io.wcm.handler.url.UrlModes;
55 import io.wcm.sling.commons.adapter.AdaptTo;
56
57
58
59
60 @Component(service = DynamicMediaSupportService.class, immediate = true)
61 @Designate(ocd = DynamicMediaSupportServiceImpl.Config.class)
62 public class DynamicMediaSupportServiceImpl implements DynamicMediaSupportService {
63
64 @ObjectClassDefinition(
65 name = "wcm.io Media Handler Dynamic Media Support",
66 description = "Configures dynamic media support in media handling.")
67 @interface Config {
68
69 @AttributeDefinition(
70 name = "Enabled",
71 description = "Enable support for dynamic media. "
72 + "Only gets active when dynamic media is actually enabled for the instance.")
73 boolean enabled() default true;
74
75 @AttributeDefinition(
76 name = "Dynamic Media Capability",
77 description = "Whether to detect automatically if Dynamic Media is actually for a given asset by looking for existing DM metadata. "
78 + "Setting to ON disables the auto-detection and forces it to enabled for all asssets, setting to OFF forced it to disabled.")
79 DynamicMediaCapabilityDetection dmCapabilityDetection() default DynamicMediaCapabilityDetection.AUTO;
80
81 @AttributeDefinition(
82 name = "Author Preview Mode",
83 description = "Loads dynamic media images via author instance - to allow previewing unpublished images. "
84 + "Must not be enabled on publish instances.")
85 boolean authorPreviewMode() default false;
86
87 @AttributeDefinition(
88 name = "Disable AEM Fallback",
89 description = "Disable the automatic fallback to AEM-based rendering of renditions (via Media Handler) "
90 + "if Dynamic Media is enabled, but the asset has not the appropriate Dynamic Media metadata. "
91 + "Please note that AEM is still used to deliver binaries for downloads even if this is activated, "
92 + "unless 'Enable Downloads' is activated as well (which is not recommended).")
93 boolean disableAemFallback() default false;
94
95 @AttributeDefinition(
96 name = "Enable Downloads",
97 description = "Use Dynamic Media for downloads (for both image and non-image binaries). "
98 + "It is NOT recommended to enable this setting. Dynamic Media does provides reliable downloads only for non-image files, not to original binaries of images files "
99 + "(although this did work for older setups). Enable this option only if you know what you are doing (backward-compatibility mode).")
100 boolean enableDownloads() default false;
101
102 @AttributeDefinition(
103 name = "Validate Smart Crop Rendition Sizes",
104 description = "Validates that the renditions defined via smart cropping fulfill the requested image width/height to avoid upscaling or white borders.")
105 boolean validateSmartCropRenditionSizes() default true;
106
107 @AttributeDefinition(
108 name = "Image width limit",
109 description = "The configured width value for 'Reply Image Size Limit'.")
110 long imageSizeLimitWidth() default 2000;
111
112 @AttributeDefinition(
113 name = "Image height limit",
114 description = "The configured height value for 'Reply Image Size Limit'.")
115 long imageSizeLimitHeight() default 2000;
116
117 @AttributeDefinition(
118 name = "Set Image Quality",
119 description = "Control image quality for lossy output formats for each media request via 'qlt' URL parameter (instead of relying on default setting within Dynamic Media).")
120 boolean setImageQuality() default true;
121
122 @AttributeDefinition(
123 name = "Default Format",
124 description = "Default response image format. "
125 + "If empty, the default setting that is configured on the Dynamic Media server environment is used. "
126 + "Accepts the same values as the 'fmt' parameter from the Dynamic Media Image Service API.")
127 String defaultFmt() default "";
128
129 @AttributeDefinition(
130 name = "Default Format Alpha Channel",
131 description = "Default response image format for source images that may have an alpha channel (e.g. for PNG). "
132 + "Accepts the same values as the 'fmt' parameter from the Dynamic Media Image Service API.")
133 String defaultFmtAlpha() default "webp-alpha";
134
135 }
136
137 @Reference
138 private PublishUtils dynamicMediaPublishUtils;
139 @Reference
140 private ResourceResolverFactory resourceResolverFactory;
141
142 private boolean enabled;
143 private DynamicMediaCapabilityDetection dmCapabilityDetection;
144 private boolean authorPreviewMode;
145 private boolean disableAemFallback;
146 private boolean enableDownloads;
147 private boolean validateSmartCropRenditionSizes;
148 private Dimension imageSizeLimit;
149 private boolean setImageQuality;
150 private String defaultFmt;
151 private String defaultFmtAlpha;
152
153 private static final String SERVICEUSER_SUBSERVICE = "dynamic-media-support";
154 private static final Pattern DAM_PATH_PATTERN = Pattern.compile("^/content/dam(/.*)?$");
155
156 private static final Logger log = LoggerFactory.getLogger(DynamicMediaSupportServiceImpl.class);
157
158 @Activate
159 private void activate(Config config) {
160 this.enabled = config.enabled();
161 this.dmCapabilityDetection = config.dmCapabilityDetection();
162 this.authorPreviewMode = config.authorPreviewMode();
163 this.disableAemFallback = config.disableAemFallback();
164 this.enableDownloads = config.enableDownloads();
165 this.validateSmartCropRenditionSizes = config.validateSmartCropRenditionSizes();
166 this.imageSizeLimit = new Dimension(config.imageSizeLimitWidth(), config.imageSizeLimitHeight());
167 this.setImageQuality = config.setImageQuality();
168 this.defaultFmt = StringUtils.trim(config.defaultFmt());
169 this.defaultFmtAlpha = StringUtils.trim(config.defaultFmtAlpha());
170
171 if (this.enabled) {
172 log.info("DynamicMediaSupport: enabled={}, capabilityEnabled={}, capabilityDetection={}, "
173 + "authorPreviewMode={}, disableAemFallback={}, imageSizeLimit={}",
174 this.enabled, this.dmCapabilityDetection, this.dmCapabilityDetection,
175 this.authorPreviewMode, this.disableAemFallback, this.imageSizeLimit);
176 }
177 }
178
179 @Override
180 public boolean isDynamicMediaEnabled() {
181 return this.enabled;
182 }
183
184 @Override
185 public boolean isDynamicMediaCapabilityEnabled(boolean isDynamicMediaAsset) {
186 switch (dmCapabilityDetection) {
187 case AUTO:
188 return isDynamicMediaAsset;
189 case ON:
190 return true;
191 case OFF:
192 default:
193 return false;
194 }
195 }
196
197 @Override
198 public boolean isAemFallbackDisabled() {
199 return disableAemFallback;
200 }
201
202 @Override
203 public boolean isEnableDownloads() {
204 return enableDownloads;
205 }
206
207 @Override
208 public boolean isValidateSmartCropRenditionSizes() {
209 return validateSmartCropRenditionSizes;
210 }
211
212 @Override
213 public @NotNull Dimension getImageSizeLimit() {
214 return this.imageSizeLimit;
215 }
216
217 @Override
218 public boolean isSetImageQuality() {
219 return this.setImageQuality;
220 }
221
222
223 @Override
224 public @NotNull String getDefaultFmt() {
225 return this.defaultFmt;
226 }
227
228 @Override
229 public @NotNull String getDefaultFmtAlpha() {
230 return this.defaultFmtAlpha;
231 }
232
233 @Override
234 public @Nullable ImageProfile getImageProfile(@NotNull String profilePath) {
235 try (ResourceResolver resourceResolver = resourceResolverFactory
236 .getServiceResourceResolver(Map.of(ResourceResolverFactory.SUBSERVICE, SERVICEUSER_SUBSERVICE))) {
237 Resource profileResource = resourceResolver.getResource(profilePath);
238 if (profileResource != null) {
239 log.debug("Loaded image profile: {}", profilePath);
240 return new ImageProfileImpl(profileResource);
241 }
242 }
243 catch (LoginException ex) {
244 log.error("Missing service user mapping for 'io.wcm.handler.media:dynamic-media-support' - see https://wcm.io/handler/media/configuration.html", ex);
245 }
246 log.debug("Image profile not found: {}", profilePath);
247 return null;
248 }
249
250 @Override
251 public @Nullable ImageProfile getImageProfileForAsset(@NotNull Asset asset) {
252 Resource assetResource = AdaptTo.notNull(asset, Resource.class);
253 Resource folderResource = assetResource.getParent();
254 if (folderResource != null) {
255 return getImageProfileForAssetFolder(folderResource);
256 }
257 return null;
258 }
259
260 private @Nullable ImageProfile getImageProfileForAssetFolder(@NotNull Resource folderResource) {
261 if (!DAM_PATH_PATTERN.matcher(folderResource.getPath()).matches()) {
262 return null;
263 }
264 Resource folderContentResource = folderResource.getChild(JCR_CONTENT);
265 if (folderContentResource != null) {
266 String imageProfilePath = folderContentResource.getValueMap().get(DamConstants.IMAGE_PROFILE, String.class);
267 if (imageProfilePath != null) {
268 return getImageProfile(imageProfilePath);
269 }
270 }
271 Resource parentFolderResource = folderResource.getParent();
272 if (parentFolderResource != null) {
273 return getImageProfileForAssetFolder(parentFolderResource);
274 }
275 else {
276 return null;
277 }
278 }
279
280 @Override
281 public @Nullable String getDynamicMediaServerUrl(@NotNull Asset asset, @Nullable UrlMode urlMode, @NotNull Adaptable adaptable) {
282 Resource assetResource = AdaptTo.notNull(asset, Resource.class);
283 if (authorPreviewMode && !forcePublishMode(urlMode)) {
284
285
286 SiteConfig siteConfig = AdaptTo.notNull(adaptable, SiteConfig.class);
287 String siteUrlAUthor = StringUtils.defaultString(siteConfig.siteUrlAuthor());
288 UrlHandler urlHandler = AdaptTo.notNull(adaptable, UrlHandler.class);
289 return urlHandler.applySiteUrlAutoDetection(siteUrlAUthor);
290 }
291 try {
292 String[] productionAssetUrls = dynamicMediaPublishUtils.externalizeImageDeliveryAsset(assetResource);
293 if (productionAssetUrls != null && productionAssetUrls.length > 0) {
294 return productionAssetUrls[0];
295 }
296 }
297 catch (RepositoryException ex) {
298 log.warn("Unable to get dynamic media production asset URLs for {}", assetResource.getPath(), ex);
299 }
300 log.warn("Unable to get dynamic media production asset URLs for {}", assetResource.getPath());
301 return null;
302 }
303
304
305
306
307
308
309 private boolean forcePublishMode(@Nullable UrlMode urlMode) {
310 return urlMode != null && (urlMode.equals(UrlModes.FULL_URL_PUBLISH)
311 || urlMode.equals(UrlModes.FULL_URL_PUBLISH_FORCENONSECURE)
312 || urlMode.equals(UrlModes.FULL_URL_PUBLISH_FORCESECURE)
313 || urlMode.equals(UrlModes.FULL_URL_PUBLISH_PROTOCOLRELATIVE));
314 }
315
316 }