diff --git a/.gitattributes b/.gitattributes index bc54a723..ae1669dd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,10 +1,14 @@ # Declare text files with unix file ending +*.any text eol=lf +*.cfg text eol=lf *.conf text eol=lf *.config text eol=lf *.css text eol=lf *.dtd text eol=lf *.esp text eol=lf *.ecma text eol=lf +*.farm text eol=lf +*.gdsl text eol=lf *.groovy text eol=lf *.hbrs text eol=lf *.hbs text eol=lf @@ -15,6 +19,7 @@ *.js text eol=lf *.json text eol=lf *.jsp text eol=lf +*.md text eol=lf *.mustache text eol=lf *.tld text eol=lf *.launch text eol=lf @@ -24,6 +29,8 @@ *.project text eol=lf *.properties text eol=lf *.props text eol=lf +*.py text eol=lf +*.rules text eol=lf *.sass text eol=lf *.scss text eol=lf *.sh text eol=lf @@ -31,7 +38,10 @@ *.shtml text eol=lf *.sql text eol=lf *.svg text eol=lf +*.tf text eol=lf +*.ts text eol=lf *.txt text eol=lf +*.vhost text eol=lf *.vm text eol=lf *.xml text eol=lf *.xsd text eol=lf diff --git a/.gitignore b/.gitignore index e6146058..bcfff4ee 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ npm-debug.log .vlt .vlt-sync* .brackets.json +.aio dependency-reduced-pom.xml diff --git a/changes.xml b/changes.xml index 7546ec62..dd787d99 100644 --- a/changes.xml +++ b/changes.xml @@ -23,6 +23,33 @@ xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd"> + + + Dynamic Media with OpenAPI: Support Dynamic Media with OpenAPI also for local assets. + + + Dynamic Media with OpenAPI: Download original binary when resolving media with download flag. + + + Dynamic Media with OpenAPI: Remove experimental URL parameters from default configuration. + + + Dynamic Media with OpenAPI: Remove experimental HTTP header for metadata request from default configuration. + + + Dynamic Media with OpenAPI: Prefer named smart crops over auto-cropped smart renditions. + + + Dynamic Media with OpenAPI: Enable metadata service by default. + + + Dynamic Media with OpenAPI: Support setting "Content Disposition Header" to attachment for binary URLs + + + Improve trace logging: Make log messages involving value maps and resource/page objects more compact and better readable. + + + Dynamic Media Support: Append fmt=png-alpha for PNG assets to ensure that the alpha channel is preserved in the Dynamic Media rendition. @@ -44,16 +71,16 @@ - Next Generation Dynamic Media: Support non-image assets and SVG assets. + Dynamic Media with OpenAPI: Support non-image assets and SVG assets. - Next Generation Dynamic Media: Use latest NextGen Dynamic Media Asset URLs and make them configurable via OSGi config. + Dynamic Media with OpenAPI: Use latest NextGen Dynamic Media Asset URLs and make them configurable via OSGi config. - Next Generation Dynamic Media: Replace fileupload default pick/remote trigger with customized one to display the customized asset selector dialog. + Dynamic Media with OpenAPI: Replace fileupload default pick/remote trigger with customized one to display the customized asset selector dialog. - Next Generation Dynamic Media: Optionally fetch metadata of NGDM asset reference to check for validity and maximum possible resolution. + Dynamic Media with OpenAPI: Optionally fetch metadata of NGDM asset reference to check for validity and maximum possible resolution. @@ -62,11 +89,11 @@ Version 2.0.0 contains minor breaking API changes, see Migrate from wcm.io Handler 1.x to 2.x for details. ]]> + Add support for Web-Optimized Image Delivery (part of Dynamic Media with OpenAPI) - rendering asset renditions from AEM Sites instance on the edge.
This feature is active by default on AEMaaCS cloud instances, can be disabled via OSGi configuration. ]]>
- Add support for Next Generation Dynamic Media remote assets. This is a first experimental support and will be finalized in release 2.0.2. + Add support for Dynamic Media with OpenAPI (also known as Next Generation Dynamic Media) remote assets. This is a first experimental support and will be finalized in release 2.0.2. Allow to set image quality per media request. diff --git a/pom.xml b/pom.xml index 4bbb9249..051a48ee 100644 --- a/pom.xml +++ b/pom.xml @@ -25,13 +25,13 @@ io.wcm io.wcm.parent_toplevel - 2.3.0 + 2.3.2 io.wcm io.wcm.handler.media - 2.0.8 + 2.1.0 jar Media Handler @@ -49,7 +49,7 @@ handler/media - 2024-05-22T07:37:48Z + 2024-07-08T08:50:40Z @@ -82,7 +82,7 @@ io.wcm io.wcm.wcm.commons - 1.10.0 + 1.11.0 compile diff --git a/src/main/java/io/wcm/handler/media/Media.java b/src/main/java/io/wcm/handler/media/Media.java index 12a72bbe..a44ff33a 100644 --- a/src/main/java/io/wcm/handler/media/Media.java +++ b/src/main/java/io/wcm/handler/media/Media.java @@ -284,6 +284,7 @@ public void setMediaInvalidReasonCustomMessage(@Nullable String mediaInvalidReas @Override public String toString() { ToStringBuilder sb = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + sb.append("valid", isValid()); if (isValid()) { sb.append("url", getUrl()); } diff --git a/src/main/java/io/wcm/handler/media/MediaArgs.java b/src/main/java/io/wcm/handler/media/MediaArgs.java index af283690..33b4f561 100644 --- a/src/main/java/io/wcm/handler/media/MediaArgs.java +++ b/src/main/java/io/wcm/handler/media/MediaArgs.java @@ -42,6 +42,7 @@ import io.wcm.handler.mediasource.dam.AemRenditionType; import io.wcm.handler.url.UrlMode; import io.wcm.wcm.commons.contenttype.FileExtension; +import io.wcm.wcm.commons.util.AemObjectReflectionToStringBuilder; /** * Holds parameters to influence the media resolving process. @@ -843,7 +844,7 @@ public String toString() { sb.append("webOptimizedImageDeliveryDisabled", webOptimizedImageDeliveryDisabled); } if (properties != null && !properties.isEmpty()) { - sb.append("properties", properties); + sb.append("properties", AemObjectReflectionToStringBuilder.filteredValueMap(properties)); } return sb.build(); } @@ -918,14 +919,23 @@ public MediaFormatOption(@NotNull String mediaFormatName, boolean mandatory) { this.mandatory = mandatory; } + /** + * @return Media format + */ public @Nullable MediaFormat getMediaFormat() { return this.mediaFormat; } + /** + * @return Media format name + */ public @Nullable String getMediaFormatName() { return this.mediaFormatName; } + /** + * @return Resolution of this media format is mandatory + */ public boolean isMandatory() { return this.mandatory; } diff --git a/src/main/java/io/wcm/handler/media/MediaFileType.java b/src/main/java/io/wcm/handler/media/MediaFileType.java index e0d0b856..bd2dea51 100644 --- a/src/main/java/io/wcm/handler/media/MediaFileType.java +++ b/src/main/java/io/wcm/handler/media/MediaFileType.java @@ -223,7 +223,7 @@ private static Set getFileExtensions(@NotNull EnumSet fil return null; } String contentTypeLowerCase = StringUtils.toRootLowerCase(contentType); - return Stream.of(MediaFileType.values()) + return Stream.of(values()) .filter(type -> type.getContentTypes().contains(contentTypeLowerCase)) .findFirst() .orElse(null); @@ -240,7 +240,7 @@ private static Set getFileExtensions(@NotNull EnumSet fil return null; } String extensionLowerCase = StringUtils.toRootLowerCase(extension); - return Stream.of(MediaFileType.values()) + return Stream.of(values()) .filter(type -> type.getExtensions().contains(extensionLowerCase)) .findFirst() .orElse(null); diff --git a/src/main/java/io/wcm/handler/media/MediaInvalidReason.java b/src/main/java/io/wcm/handler/media/MediaInvalidReason.java index ea2c450f..26bb96ba 100644 --- a/src/main/java/io/wcm/handler/media/MediaInvalidReason.java +++ b/src/main/java/io/wcm/handler/media/MediaInvalidReason.java @@ -34,6 +34,12 @@ public enum MediaInvalidReason { */ MEDIA_REFERENCE_INVALID, + /** + * Media reference is valid, but the media is not approved. + * This is only relevant for assets served via Dynamic Media with OpenAPI. + */ + NOT_APPROVED, + /** * No matching rendition: The media item exists, but no rendition matches for the requested media args. */ diff --git a/src/main/java/io/wcm/handler/media/MediaRequest.java b/src/main/java/io/wcm/handler/media/MediaRequest.java index 64a71267..3137cd7e 100644 --- a/src/main/java/io/wcm/handler/media/MediaRequest.java +++ b/src/main/java/io/wcm/handler/media/MediaRequest.java @@ -21,9 +21,7 @@ import java.util.HashMap; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.api.wrappers.ValueMapDecorator; @@ -31,6 +29,8 @@ import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ProviderType; +import io.wcm.wcm.commons.util.AemObjectReflectionToStringBuilder; + /** * Holds all properties that are part of a media handling request. */ @@ -119,10 +119,11 @@ public MediaRequest(@Nullable Resource resource, @Nullable String mediaRef, @Nul @Override public String toString() { - ToStringBuilder sb = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + ToStringBuilder sb = new ToStringBuilder(this, + io.wcm.wcm.commons.util.ToStringStyle.SHORT_PREFIX_OMIT_NULL_STYLE); if (resource != null) { sb.append("resource", resource.getPath()); - sb.append("resourceProperties", "[" + StringUtils.join(resource.getValueMap().entrySet(), ",") + "]"); + sb.append("resourceProperties", AemObjectReflectionToStringBuilder.filteredValueMap(resource.getValueMap())); } if (mediaRef != null) { sb.append("mediaRef", mediaRef); diff --git a/src/main/java/io/wcm/handler/media/format/MediaFormat.java b/src/main/java/io/wcm/handler/media/format/MediaFormat.java index 87eb592f..e026193e 100644 --- a/src/main/java/io/wcm/handler/media/format/MediaFormat.java +++ b/src/main/java/io/wcm/handler/media/format/MediaFormat.java @@ -567,9 +567,9 @@ String getCombinedTitle() { // add extended display parts if (!extParts.isEmpty()) { - sb.append(" ("); - sb.append(StringUtils.join(extParts, "; ")); - sb.append(')'); + sb.append(" (") + .append(StringUtils.join(extParts, "; ")) + .append(')'); } combinedTitle = sb.toString(); diff --git a/src/main/java/io/wcm/handler/media/format/Ratio.java b/src/main/java/io/wcm/handler/media/format/Ratio.java index 9df1e511..c69c85cb 100644 --- a/src/main/java/io/wcm/handler/media/format/Ratio.java +++ b/src/main/java/io/wcm/handler/media/format/Ratio.java @@ -84,7 +84,7 @@ public static double get(double width, double height) { * @return Ratio (returns 0 when ratio is invalid) */ public static double get(long width, long height) { - return Ratio.get((double)width, (double)height); + return get((double)width, (double)height); } /** diff --git a/src/main/java/io/wcm/handler/media/impl/AssetRenditionContentDispositionFilter.java b/src/main/java/io/wcm/handler/media/impl/AssetRenditionContentDispositionFilter.java index c3133168..d655c33d 100644 --- a/src/main/java/io/wcm/handler/media/impl/AssetRenditionContentDispositionFilter.java +++ b/src/main/java/io/wcm/handler/media/impl/AssetRenditionContentDispositionFilter.java @@ -126,7 +126,7 @@ private void setContentDisposition(SlingHttpServletRequest request, SlingHttpSer */ @SuppressWarnings("null") private boolean accepts(SlingHttpServletRequest request) { - return request.getMethod().equalsIgnoreCase(METHOD_GET) + return StringUtils.equalsIgnoreCase(request.getMethod(), METHOD_GET) && request.getResource() != null && StringUtils.equals(request.getResource().getValueMap().get(JCR_PRIMARYTYPE, String.class), NT_FILE); } diff --git a/src/main/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilder.java b/src/main/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilder.java index e66f8c3d..e38f9520 100644 --- a/src/main/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilder.java +++ b/src/main/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilder.java @@ -303,6 +303,7 @@ public final HtmlElement build(@NotNull Media media) { * @param media Media * @return Media format or null if none found */ + @SuppressWarnings("null") protected final @Nullable MediaFormat getFirstMediaFormat(@NotNull Media media) { return media.getRenditions().stream() .map(Rendition::getMediaFormat) diff --git a/src/main/java/io/wcm/handler/media/package-info.java b/src/main/java/io/wcm/handler/media/package-info.java index 3dc7b07a..3acdac8c 100644 --- a/src/main/java/io/wcm/handler/media/package-info.java +++ b/src/main/java/io/wcm/handler/media/package-info.java @@ -20,5 +20,5 @@ /** * Media Handler API. */ -@org.osgi.annotation.versioning.Version("2.0.0") +@org.osgi.annotation.versioning.Version("2.1.0") package io.wcm.handler.media; diff --git a/src/main/java/io/wcm/handler/mediasource/dam/AemRenditionType.java b/src/main/java/io/wcm/handler/mediasource/dam/AemRenditionType.java index d88b6c90..204a73ae 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/AemRenditionType.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/AemRenditionType.java @@ -83,7 +83,7 @@ public boolean matches(@NotNull Rendition rendition) { */ @SuppressWarnings("null") public static @Nullable AemRenditionType forRendition(@NotNull String renditionName) { - return Stream.of(AemRenditionType.values()) + return Stream.of(values()) .filter(type -> type.matches(renditionName)) .findFirst() .orElse(null); diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAsset.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAsset.java index 430eb5b8..da11a0b5 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAsset.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAsset.java @@ -21,7 +21,6 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.adapter.SlingAdaptable; import org.apache.sling.api.resource.Resource; @@ -45,7 +44,7 @@ import io.wcm.handler.mediasource.dam.AssetRendition; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportService; import io.wcm.handler.mediasource.dam.impl.weboptimized.WebOptimizedImageDeliveryService; -import io.wcm.wcm.commons.util.ToStringStyle; +import io.wcm.wcm.commons.util.AemObjectReflectionToStringBuilder; /** * {@link Asset} implementation for DAM assets. @@ -225,7 +224,8 @@ public AdapterType adaptTo(Class type) { @Override public String toString() { - return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_OMIT_NULL_STYLE); + return new AemObjectReflectionToStringBuilder(this, + io.wcm.wcm.commons.util.ToStringStyle.SHORT_PREFIX_OMIT_NULL_STYLE).build(); } } diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadata.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadata.java index cbfb609c..e5033f94 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadata.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadata.java @@ -179,22 +179,22 @@ public void setMediaFormat(MediaFormat mediaFormat) { */ public @NotNull String getMediaPath(boolean contentDispositionAttachment) { if (contentDispositionAttachment) { - return RenditionMetadata.buildMediaPath(getRendition().getPath() + "." + MediaFileServletConstants.SELECTOR + return buildMediaPath(getRendition().getPath() + "." + MediaFileServletConstants.SELECTOR + "." + MediaFileServletConstants.SELECTOR_DOWNLOAD + "." + MediaFileServletConstants.EXTENSION, getFileName(contentDispositionAttachment)); } else if (MediaFileType.isVectorImage(getFileExtension())) { - return RenditionMetadata.buildMediaPath(getRendition().getPath() + "." + MediaFileServletConstants.SELECTOR + return buildMediaPath(getRendition().getPath() + "." + MediaFileServletConstants.SELECTOR + "." + MediaFileServletConstants.EXTENSION, getFileName(contentDispositionAttachment)); } else if (MediaFileType.isBrowserImage(getFileExtension()) || !MediaFileType.isImage(getFileExtension())) { // use "deep URL" to reference rendition directly // do not use Asset URL for original rendition because it creates conflicts for dispatcher cache (filename vs. directory for asset resource name) - return RenditionMetadata.buildMediaPath(this.rendition.getPath() + ".", getFileName(contentDispositionAttachment)); + return buildMediaPath(this.rendition.getPath() + ".", getFileName(contentDispositionAttachment)); } else { // image rendition uses a file extension that cannot be displayed in browser directly - render via ImageFileServlet - return RenditionMetadata.buildMediaPath(getRendition().getPath() + "." + ImageFileServlet.SELECTOR + return buildMediaPath(getRendition().getPath() + "." + ImageFileServlet.SELECTOR + "." + getWidth() + "." + getHeight() + "." + MediaFileServletConstants.EXTENSION, getFileName(contentDispositionAttachment)); } diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadata.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadata.java index 5807b2c4..ef991a13 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadata.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadata.java @@ -87,7 +87,7 @@ public long getHeight() { // vector images can be scaled in browser without need of ImageFileServlet return super.getMediaPath(contentDispositionAttachment); } - return RenditionMetadata.buildMediaPath(getRendition().getPath() + return buildMediaPath(getRendition().getPath() + "." + ImageFileServletSelector.build(getWidth(), getHeight(), null, null, this.imageQualityPercentage, contentDispositionAttachment) + "." + MediaFileServletConstants.EXTENSION, getFileName(contentDispositionAttachment)); diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadata.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadata.java index 18e9eb94..28d45ab5 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadata.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadata.java @@ -98,7 +98,7 @@ public Integer getRotation() { @Override public @NotNull String getMediaPath(boolean contentDispositionAttachment) { - return RenditionMetadata.buildMediaPath(getRendition().getPath() + return buildMediaPath(getRendition().getPath() + "." + ImageFileServletSelector.build(getWidth(), getHeight(), this.cropDimension, this.rotation, this.imageQualityPercentage, contentDispositionAttachment) + "." + MediaFileServletConstants.EXTENSION, getFileName(contentDispositionAttachment)); diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/weboptimized/WebOptimizedImageDeliveryService.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/weboptimized/WebOptimizedImageDeliveryService.java index b510a018..049c58c3 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/weboptimized/WebOptimizedImageDeliveryService.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/weboptimized/WebOptimizedImageDeliveryService.java @@ -25,8 +25,8 @@ import com.day.cq.dam.api.Asset; /** - * Supports rendering asset renditions from stored in AEMaaCS sites instance via Next Generation Dynamic Media - * "Web-Optimized Image Delivery", rendering the renditions on the edge. + * Supports rendering asset renditions from stored in AEMaaCS sites instance via "Web-Optimized Image Delivery", + * rendering the renditions on the edge. * This is not available in AEM 6.5 or AEMaaCS SDK. */ public interface WebOptimizedImageDeliveryService { diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/weboptimized/WebOptimizedImageDeliveryServiceImpl.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/weboptimized/WebOptimizedImageDeliveryServiceImpl.java index 9889a2db..42b3a604 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/weboptimized/WebOptimizedImageDeliveryServiceImpl.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/weboptimized/WebOptimizedImageDeliveryServiceImpl.java @@ -47,7 +47,7 @@ public class WebOptimizedImageDeliveryServiceImpl implements WebOptimizedImageDe @ObjectClassDefinition( name = "wcm.io Media Handler Web-Optimized Image Delivery Support", - description = "Support for Next Generation Dynamic Media Web-Optimized Image Delivery capabilites.") + description = "Support for AEMaaCS Web-Optimized Image Delivery capabilites.") @interface Config { @AttributeDefinition( diff --git a/src/main/java/io/wcm/handler/mediasource/inline/InlineAsset.java b/src/main/java/io/wcm/handler/mediasource/inline/InlineAsset.java index 3a8ffa28..284d1d0f 100644 --- a/src/main/java/io/wcm/handler/mediasource/inline/InlineAsset.java +++ b/src/main/java/io/wcm/handler/mediasource/inline/InlineAsset.java @@ -21,7 +21,6 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.adapter.SlingAdaptable; import org.apache.sling.api.resource.Resource; @@ -37,7 +36,7 @@ import io.wcm.handler.media.UriTemplate; import io.wcm.handler.media.UriTemplateType; import io.wcm.handler.media.spi.MediaHandlerConfig; -import io.wcm.wcm.commons.util.ToStringStyle; +import io.wcm.wcm.commons.util.AemObjectReflectionToStringBuilder; /** * {@link Asset} implementation for inline media objects stored in a node in a content page. @@ -169,7 +168,8 @@ public AdapterType adaptTo(Class type) { @Override public String toString() { - return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_OMIT_NULL_STYLE); + return new AemObjectReflectionToStringBuilder(this, + io.wcm.wcm.commons.util.ToStringStyle.SHORT_PREFIX_OMIT_NULL_STYLE).build(); } } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaAsset.java b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaAsset.java index 4330065b..05ce9895 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaAsset.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaAsset.java @@ -19,7 +19,9 @@ */ package io.wcm.handler.mediasource.ngdm; +import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -114,9 +116,30 @@ final class NextGenDynamicMediaAsset implements Asset { } @Override - public @Nullable AdapterType adaptTo(@NotNull Class arg0) { - // not adaption supported + @SuppressWarnings("unchecked") + public @Nullable AdapterType adaptTo(@NotNull Class type) { + com.day.cq.dam.api.Asset asset = context.getReference().getAsset(); + if (asset != null) { + if (type == com.day.cq.dam.api.Asset.class) { + return (AdapterType)asset; + } + if (type == Resource.class) { + return (AdapterType)asset.adaptTo(Resource.class); + } + } return null; } + @Override + public String toString() { + ToStringBuilder sb = new ToStringBuilder(this) + .append("reference", context.getReference()) + .append("metadata", context.getMetadata()); + com.day.cq.dam.api.Asset asset = context.getReference().getAsset(); + if (asset != null) { + sb.append("asset", asset.getPath()); + } + return sb.toString(); + } + } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java index 86049455..24a68b08 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java @@ -26,7 +26,6 @@ import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.models.annotations.Model; -import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; import org.apache.sling.models.annotations.injectorspecific.OSGiService; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,7 +39,7 @@ import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService; /** - * Prepares Next Generation Dynamic Media configuration for GraniteUI components (fileupload, pathfield). + * Prepares Dynamic Media with OpenAPI Remote Assets configuration for GraniteUI components (fileupload, pathfield). */ @Model(adaptables = SlingHttpServletRequest.class) @ProviderType @@ -49,7 +48,7 @@ public final class NextGenDynamicMediaConfigModel { private static final JsonMapper MAPPER = JsonMapper.builder().build(); private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaConfigModel.class); - @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL) + @OSGiService private NextGenDynamicMediaConfigService config; private boolean enabled; @@ -58,29 +57,27 @@ public final class NextGenDynamicMediaConfigModel { @PostConstruct private void activate() { - if (config != null) { - enabled = config.enabled(); - assetSelectorsJsUrl = config.getAssetSelectorsJsUrl(); - configJson = buildConfigJsonString(config); - } + enabled = config.isEnabledRemoteAssets(); + assetSelectorsJsUrl = config.getAssetSelectorsJsUrl(); + configJson = buildConfigJsonString(config); } private static String buildConfigJsonString(@NotNull NextGenDynamicMediaConfigService config) { Map map = new TreeMap<>(); - map.put("repositoryId", config.getRepositoryId()); + map.put("repositoryId", config.getRemoteAssetsRepositoryId()); map.put("apiKey", config.getApiKey()); map.put("env", config.getEnv()); try { return MAPPER.writeValueAsString(map); } catch (JsonProcessingException ex) { - log.warn("Unable to serialize Next Generation Dynamic Media config to JSON.", ex); + log.warn("Unable to serialize Dynamic Media with OpenAPI config to JSON.", ex); return "{}"; } } /** - * @return true if Next Generation Dynamic Media is available and enabled. + * @return true if Dynamic Media with OpenAPI for remote assets is available and enabled. */ public boolean isEnabled() { return this.enabled; diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSource.java b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSource.java index cb6f1491..99a733ea 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSource.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSource.java @@ -19,21 +19,27 @@ */ package io.wcm.handler.mediasource.ngdm; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_APPROVED; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_PROPERTY; + import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.commons.mime.MimeTypeService; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; import org.apache.sling.models.annotations.injectorspecific.OSGiService; import org.apache.sling.models.annotations.injectorspecific.Self; +import org.apache.sling.models.annotations.injectorspecific.SlingObject; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ProviderType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.day.cq.dam.api.Asset; import com.day.cq.wcm.api.WCMMode; import com.day.cq.wcm.api.components.ComponentContext; import com.day.cq.wcm.api.components.EditConfig; @@ -54,7 +60,7 @@ import io.wcm.sling.models.annotations.AemObject; /** - * Handles remote asset referenced via Next Generation Dynamic Media. + * Handles remote asset referenced via Dynamic Media with OpenAPI. */ @Model(adaptables = { SlingHttpServletRequest.class, Resource.class @@ -71,6 +77,8 @@ public final class NextGenDynamicMediaMediaSource extends MediaSource { private Adaptable adaptable; @Self private MediaHandlerConfig mediaHandlerConfig; + @SlingObject + private ResourceResolver resourceResolver; @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL) private NextGenDynamicMediaConfigService nextGenDynamicMediaConfig; @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL) @@ -92,15 +100,15 @@ public final class NextGenDynamicMediaMediaSource extends MediaSource { @Override public boolean accepts(@Nullable String mediaRef) { - return isNextGenDynamicMediaEnabled() && NextGenDynamicMediaReference.isReference(mediaRef); - } - - private boolean isNextGenDynamicMediaEnabled() { if (nextGenDynamicMediaConfig == null) { - log.debug("NGDM media source is disabled: com.adobe.cq.ui.wcm.commons.config.NextGenDynamicMediaConfig is not available."); return false; } - return nextGenDynamicMediaConfig.enabled(); + return (nextGenDynamicMediaConfig.isEnabledRemoteAssets() && NextGenDynamicMediaReference.isReference(mediaRef)) + || (nextGenDynamicMediaConfig.isEnabledLocalAssets() && isDamAssetReference(mediaRef)); + } + + private boolean isDamAssetReference(@Nullable String mediaRef) { + return StringUtils.startsWith(mediaRef, "/content/dam/"); } @Override @@ -109,13 +117,14 @@ private boolean isNextGenDynamicMediaEnabled() { } @Override + @SuppressWarnings("java:S3776") // complexity public @NotNull Media resolveMedia(@NotNull Media media) { String mediaRef = getMediaRef(media.getMediaRequest(), mediaHandlerConfig); MediaArgs mediaArgs = media.getMediaRequest().getMediaArgs(); // check reference and enabled status - NextGenDynamicMediaReference reference = NextGenDynamicMediaReference.fromReference(mediaRef); - if (reference == null || !isNextGenDynamicMediaEnabled()) { + NextGenDynamicMediaReference reference = toNextGenDynamicMediaReference(mediaRef); + if (reference == null || nextGenDynamicMediaConfig == null) { if (StringUtils.isEmpty(mediaRef)) { media.setMediaInvalidReason(MediaInvalidReason.MEDIA_REFERENCE_MISSING); } @@ -127,7 +136,11 @@ private boolean isNextGenDynamicMediaEnabled() { // If enabled: Fetch asset metadata to validate existence and get original dimensions NextGenDynamicMediaMetadata metadata = null; - if (metadataService != null && metadataService.isEnabled()) { + Asset localAsset = reference.getAsset(); + if (localAsset != null) { + metadata = getMetadataFromAsset(localAsset); + } + else if (metadataService != null && metadataService.isEnabled()) { metadata = metadataService.fetchMetadata(reference); if (metadata == null) { media.setMediaInvalidReason(MediaInvalidReason.MEDIA_REFERENCE_INVALID); @@ -135,6 +148,13 @@ private boolean isNextGenDynamicMediaEnabled() { } } + // Do not accept assets that are not approved + if (metadata != null && !StringUtils.equals(metadata.getAssetStatus(), ASSET_STATUS_APPROVED)) { + log.trace("Reject asset with {}={} (expected: {})", ASSET_STATUS_PROPERTY, metadata.getAssetStatus(), ASSET_STATUS_APPROVED); + media.setMediaInvalidReason(MediaInvalidReason.NOT_APPROVED); + return media; + } + // Update media args settings from resource (e.g. alt. text setings) Resource referencedResource = media.getMediaRequest().getResource(); if (referencedResource != null) { @@ -162,6 +182,26 @@ private boolean isNextGenDynamicMediaEnabled() { return media; } + private @Nullable NextGenDynamicMediaReference toNextGenDynamicMediaReference(@Nullable String mediaRef) { + if (nextGenDynamicMediaConfig != null) { + if (nextGenDynamicMediaConfig.isEnabledRemoteAssets() && NextGenDynamicMediaReference.isReference(mediaRef)) { + return NextGenDynamicMediaReference.fromReference(mediaRef); + } + else if (nextGenDynamicMediaConfig.isEnabledLocalAssets() && isDamAssetReference(mediaRef)) { + return NextGenDynamicMediaReference.fromDamAssetReference(mediaRef, resourceResolver); + } + } + return null; + } + + private @Nullable NextGenDynamicMediaMetadata getMetadataFromAsset(@NotNull Asset asset) { + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromAsset(asset); + if (metadata.isValid()) { + return metadata; + } + return null; + } + @Override public void enableMediaDrop(@NotNull HtmlElement element, @NotNull MediaRequest mediaRequest) { if (wcmMode == WCMMode.DISABLED || wcmMode == null) { diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaRendition.java b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaRendition.java index 252228f4..51679687 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaRendition.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaRendition.java @@ -60,6 +60,7 @@ final class NextGenDynamicMediaRendition implements Rendition { private MediaFormat resolvedMediaFormat; private long width; private long height; + private String fileExtension; private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaRendition.class); @@ -87,7 +88,12 @@ final class NextGenDynamicMediaRendition implements Rendition { } } - if (isVectorImage() || !isImage()) { + this.fileExtension = mediaArgs.getEnforceOutputFileExtension(); + if (StringUtils.isEmpty(this.fileExtension)) { + this.fileExtension = FilenameUtils.getExtension(reference.getFileName()); + } + + if (isVectorImage() || !isImage() || mediaArgs.isDownload()) { // deliver as binary this.url = buildBinaryUrl(); } @@ -98,6 +104,7 @@ else if (isRequestedDimensionLargerThanOriginal()) { else { // deliver scaled image rendition this.url = buildImageRenditionUrl(); + this.fileExtension = new NextGenDynamicMediaImageUrlBuilder(context).getFileExtension(); } } @@ -148,7 +155,7 @@ private boolean isRequestedDimensionLargerThanOriginal() { * Build URL which points directly to the binary file. */ private String buildBinaryUrl() { - return new NextGenDynamicMediaBinaryUrlBuilder(context).build(); + return new NextGenDynamicMediaBinaryUrlBuilder(context).build(mediaArgs.isContentDispositionAttachment()); } @Override @@ -169,11 +176,7 @@ private String buildBinaryUrl() { @Override public @Nullable String getFileExtension() { - String extension = mediaArgs.getEnforceOutputFileExtension(); - if (StringUtils.isEmpty(extension)) { - extension = FilenameUtils.getExtension(reference.getFileName()); - } - return extension; + return this.fileExtension; } @Override diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/MediaArgsDimension.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/MediaArgsDimension.java index 98851748..a70a45ce 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/MediaArgsDimension.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/MediaArgsDimension.java @@ -110,7 +110,15 @@ public static double getRequestedRatio(@NotNull MediaArgs mediaArgs) { ratioWidth *= 100000d; ratioHeight *= 100000d; } - return new Dimension(Math.round(ratioWidth), Math.round(ratioHeight)); + long ratioWidthLong = Math.round(ratioWidth); + long ratioHeightLong = Math.round(ratioHeight); + while (ratioWidthLong % 10 == 0 && ratioHeightLong % 10 == 0 + && ratioWidthLong > 0 && ratioHeightLong > 0) { + // de-extrapolate superfluous zeros + ratioWidthLong /= 10; + ratioHeightLong /= 10; + } + return new Dimension(ratioWidthLong, ratioHeightLong); } // no ratio diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilder.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilder.java index 4184ea62..905755d7 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilder.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilder.java @@ -37,6 +37,8 @@ public final class NextGenDynamicMediaBinaryUrlBuilder { private final NextGenDynamicMediaContext context; + static final String PARAM_ATTACHMENT = "attachment"; + /** * @param context Context */ @@ -48,12 +50,18 @@ public NextGenDynamicMediaBinaryUrlBuilder(@NotNull NextGenDynamicMediaContext c * Builds the URL for a binary. * @return URL or null if invalid/not possible */ - public @Nullable String build() { + public @Nullable String build(boolean contentDispositionAttachment) { // get parameters from nextgen dynamic media config for URL parameters - String repositoryId = context.getNextGenDynamicMediaConfig().getRepositoryId(); + String repositoryId; + if (context.getReference().getAsset() != null) { + repositoryId = context.getNextGenDynamicMediaConfig().getLocalAssetsRepositoryId(); + } + else { + repositoryId = context.getNextGenDynamicMediaConfig().getRemoteAssetsRepositoryId(); + } String binaryDeliveryPath = context.getNextGenDynamicMediaConfig().getAssetOriginalBinaryDeliveryPath(); - if (StringUtils.isAnyEmpty(repositoryId, binaryDeliveryPath)) { + if (StringUtils.isAnyBlank(repositoryId, binaryDeliveryPath)) { return null; } @@ -67,6 +75,9 @@ public NextGenDynamicMediaBinaryUrlBuilder(@NotNull NextGenDynamicMediaContext c url.append("https://") .append(repositoryId) .append(binaryDeliveryPath); + if (contentDispositionAttachment) { + url.append("?").append(PARAM_ATTACHMENT).append("=true"); + } return url.toString(); } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigService.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigService.java index 5779fe1a..00c272cf 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigService.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigService.java @@ -19,6 +19,8 @@ */ package io.wcm.handler.mediasource.ngdm.impl; +import org.jetbrains.annotations.Nullable; + /** * Service to access Next Generation Dynamic Media configuration. */ @@ -40,15 +42,22 @@ public interface NextGenDynamicMediaConfigService { String PLACEHOLDER_FORMAT = "{format}"; /** - * Checks if the configuration/feature is enabled. - * @return true if enabled and false otherwise + * Checks if enabled for remote assets, and the appropriate configuration is present. + * @return true if enabled for remote assets + */ + boolean isEnabledRemoteAssets(); + + /** + * Enable Dynamic Media with OpenAPI for local assets in this AEMaaCS instance. + * @return true if enabled for local assets. */ - boolean enabled(); + boolean isEnabledLocalAssets(); /** * Gets the absolute URL for the javascript which contains the microfrontend for the remote asset selector. * @return the absolute URL for the javascript which contains the microfrontend for the remote asset selector */ + @Nullable String getAssetSelectorsJsUrl(); /** @@ -64,6 +73,7 @@ public interface NextGenDynamicMediaConfigService { * * @return the path expression for the image delivery path */ + @Nullable String getImageDeliveryBasePath(); /** @@ -78,6 +88,7 @@ public interface NextGenDynamicMediaConfigService { * * @return the path expression for the video delivery path */ + @Nullable String getVideoDeliveryPath(); /** @@ -91,6 +102,7 @@ public interface NextGenDynamicMediaConfigService { * * @return the path expression for the asset (bitstream) delivery path */ + @Nullable String getAssetOriginalBinaryDeliveryPath(); /** @@ -103,30 +115,48 @@ public interface NextGenDynamicMediaConfigService { * * @return the path expression for the metadata path */ + @Nullable String getAssetMetadataPath(); /** - * Gets the Next Generation Dynamic Media tenant (also known technically as the repository ID). + * Gets the Dynamic Media with OpenAPI tenant (also known technically as the repository ID) for remote assets. * @return the repository ID */ - String getRepositoryId(); + @Nullable + String getRemoteAssetsRepositoryId(); + + /** + * Repository ID for serving local assets. + * @return the repository ID + */ + @Nullable + String getLocalAssetsRepositoryId(); /** * Gets the API key for accessing the asset selectors UI * @return the API key for accessing the asset selectors UI */ + @Nullable String getApiKey(); /** * Gets the environment string which should be 'PROD' or 'STAGE' * @return the environment string */ + @Nullable String getEnv(); /** * Gets the IMS client identifier * @return the IMS client identifier */ + @Nullable String getImsClient(); + /** + * Gets the default image width/height (longest edge) when requesting image renditions without explicit dimension. + * @return default width/height + */ + long getImageWidthHeightDefault(); + } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImpl.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImpl.java index c0543597..be47472f 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImpl.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImpl.java @@ -20,9 +20,11 @@ package io.wcm.handler.mediasource.ngdm.impl; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.AttributeDefinition; @@ -41,17 +43,32 @@ public class NextGenDynamicMediaConfigServiceImpl implements NextGenDynamicMediaConfigService { @ObjectClassDefinition( - name = "wcm.io Media Handler Next Generation Dynamic Media Support", - description = "Support for Next Generation Dynamic Media.") + name = "wcm.io Media Handler Dynamic Media with OpenAPI Support", + description = "Support for Dynamic Media with OpenAPI.") @interface Config { + @AttributeDefinition( + name = "Remote Assets", + description = "Enable Dynamic Media with OpenAPI for remote assets.") + boolean enabledRemoteAssets() default true; + + @AttributeDefinition( + name = "Local Assets", + description = "Enable Next Dynamic Media with OpenAPI for local assets in this AEMaaCS instance.") + boolean enabledLocalAssets() default false; + + @AttributeDefinition( + name = "Repository ID for Local Assets", + description = "Dynamic Media with OpenAPI Delivery host name for local assets. Mandatory if local assets is enabled.") + String localAssetsRepositoryId(); + @AttributeDefinition( name = "Image Delivery Base Path", description = "Base path with placeholders to deliver image renditions. " + "Placeholders: " + PLACEHOLDER_ASSET_ID + ", " + PLACEHOLDER_SEO_NAME + ", " + PLACEHOLDER_FORMAT + ". " + "If not set, the default value from the NextGenDynamicMediaConfig service will be used.") String imageDeliveryBasePath() default ADOBE_ASSETS_PREFIX + PLACEHOLDER_ASSET_ID + "/as/" - + PLACEHOLDER_SEO_NAME + "." + PLACEHOLDER_FORMAT + "?accept-experimental=1"; + + PLACEHOLDER_SEO_NAME + "." + PLACEHOLDER_FORMAT; @AttributeDefinition( name = "Asset Original Binary Delivery Path", @@ -59,7 +76,7 @@ String imageDeliveryBasePath() default ADOBE_ASSETS_PREFIX + PLACEHOLDER_ASSET_I + "Placeholders: " + PLACEHOLDER_ASSET_ID + ", " + PLACEHOLDER_SEO_NAME + ". " + "If not set, the default value from the NextGenDynamicMediaConfig service will be used.") String assetOriginalBinaryDeliveryPath() default ADOBE_ASSETS_PREFIX + PLACEHOLDER_ASSET_ID + "/original/as/" - + PLACEHOLDER_SEO_NAME + "?accept-experimental=1"; + + PLACEHOLDER_SEO_NAME; @AttributeDefinition( name = "Asset Metadata Path", @@ -68,80 +85,122 @@ String assetOriginalBinaryDeliveryPath() default ADOBE_ASSETS_PREFIX + PLACEHOLD + "If not set, the default value from the NextGenDynamicMediaConfig service will be used.") String assetMetadataPath() default ADOBE_ASSETS_PREFIX + PLACEHOLDER_ASSET_ID + "/metadata"; + @AttributeDefinition( + name = "Default image width/height", + description = "Default width/height (longest edge) when requesting image renditions without explicit dimension.") + long imageWidthHeightDefault() default 2048; + } private static final String ADOBE_ASSETS_PREFIX = "/adobe/assets/"; private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaConfigServiceImpl.class); + private boolean enabledRemoteAssets; + private boolean enabledLocalAssets; + private String localAssetsRepositoryId; private String imageDeliveryBasePath; private String assetOriginalBinaryDeliveryPath; private String assetMetadataPath; + private long imageWidthHeightDefault; - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY) + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private NextGenDynamicMediaConfig nextGenDynamicMediaConfig; @Activate private void activate(Config config) { - log.debug("NGDM config: enabled={}, repositoryId={}, apiKey={}, env={}, imsClient={}", - enabled(), getRepositoryId(), getApiKey(), getEnv(), getImsClient()); - - this.imageDeliveryBasePath = StringUtils.defaultIfBlank(config.imageDeliveryBasePath(), - this.nextGenDynamicMediaConfig.getImageDeliveryBasePath()); - this.assetOriginalBinaryDeliveryPath = StringUtils.defaultIfBlank(config.assetOriginalBinaryDeliveryPath(), - this.nextGenDynamicMediaConfig.getAssetOriginalBinaryDeliveryPath()); - this.assetMetadataPath = StringUtils.defaultIfBlank(config.assetMetadataPath(), - this.nextGenDynamicMediaConfig.getAssetMetadataPath()); + enabledRemoteAssets = config.enabledRemoteAssets(); + if (enabledRemoteAssets) { + if (nextGenDynamicMediaConfig == null) { + log.debug("NextGenDynamicMediaConfig service is not available, disable remote assets."); + enabledRemoteAssets = false; + } + else { + log.debug("NextGenDynamicMediaConfig: enabled={}, repositoryId={}, apiKey={}, env={}, imsClient={}", + nextGenDynamicMediaConfig.enabled(), nextGenDynamicMediaConfig.getRepositoryId(), + nextGenDynamicMediaConfig.getApiKey(), nextGenDynamicMediaConfig.getEnv(), nextGenDynamicMediaConfig.getImsClient()); + } + } + + imageDeliveryBasePath = StringUtils.defaultIfBlank(config.imageDeliveryBasePath(), + nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getImageDeliveryBasePath() : null); + assetOriginalBinaryDeliveryPath = StringUtils.defaultIfBlank(config.assetOriginalBinaryDeliveryPath(), + nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getAssetOriginalBinaryDeliveryPath() : null); + assetMetadataPath = StringUtils.defaultIfBlank(config.assetMetadataPath(), + nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getAssetMetadataPath() : null); + + enabledLocalAssets = config.enabledLocalAssets(); + localAssetsRepositoryId = config.localAssetsRepositoryId(); + if (enabledLocalAssets && StringUtils.isBlank(localAssetsRepositoryId)) { + log.debug("localAssetsRepositoryId is not configured, disable local assets."); + enabledLocalAssets = false; + } + + imageWidthHeightDefault = config.imageWidthHeightDefault(); + } + @Override + public boolean isEnabledRemoteAssets() { + return enabledRemoteAssets && nextGenDynamicMediaConfig != null && nextGenDynamicMediaConfig.enabled(); } @Override - public boolean enabled() { - return this.nextGenDynamicMediaConfig.enabled(); + public boolean isEnabledLocalAssets() { + return enabledLocalAssets; } @Override - public String getAssetSelectorsJsUrl() { - return this.nextGenDynamicMediaConfig.getAssetSelectorsJsUrl(); + public @Nullable String getAssetSelectorsJsUrl() { + return nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getAssetSelectorsJsUrl() : null; } @Override - public String getImageDeliveryBasePath() { + public @Nullable String getImageDeliveryBasePath() { return imageDeliveryBasePath; } @Override - public String getVideoDeliveryPath() { - return this.nextGenDynamicMediaConfig.getVideoDeliveryPath(); + public @Nullable String getVideoDeliveryPath() { + return nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getVideoDeliveryPath() : null; } @Override - public String getAssetOriginalBinaryDeliveryPath() { + public @Nullable String getAssetOriginalBinaryDeliveryPath() { return assetOriginalBinaryDeliveryPath; } @Override - public String getAssetMetadataPath() { + public @Nullable String getAssetMetadataPath() { return assetMetadataPath; } @Override - public String getRepositoryId() { - return this.nextGenDynamicMediaConfig.getRepositoryId(); + public @Nullable String getRemoteAssetsRepositoryId() { + return nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getRepositoryId() : null; + } + + @Override + public @Nullable String getLocalAssetsRepositoryId() { + return localAssetsRepositoryId; + } + + @Override + public @Nullable String getApiKey() { + return nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getApiKey() : null; } @Override - public String getApiKey() { - return this.nextGenDynamicMediaConfig.getApiKey(); + public @Nullable String getEnv() { + return nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getEnv() : null; } @Override - public String getEnv() { - return this.nextGenDynamicMediaConfig.getEnv(); + public @Nullable String getImsClient() { + return nextGenDynamicMediaConfig != null ? nextGenDynamicMediaConfig.getImsClient() : null; } @Override - public String getImsClient() { - return this.nextGenDynamicMediaConfig.getImsClient(); + public long getImageWidthHeightDefault() { + return imageWidthHeightDefault; } } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilder.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilder.java index 78d45283..2d5a175e 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilder.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilder.java @@ -36,6 +36,9 @@ import org.jetbrains.annotations.Nullable; import io.wcm.handler.media.Dimension; +import io.wcm.handler.media.format.Ratio; +import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadata; +import io.wcm.handler.mediasource.ngdm.impl.metadata.SmartCrop; import io.wcm.wcm.commons.contenttype.FileExtension; /** @@ -49,7 +52,9 @@ public final class NextGenDynamicMediaImageUrlBuilder { static final String PARAM_PREFER_WEBP = "preferwebp"; static final String PARAM_WIDTH = "width"; + static final String PARAM_HEIGHT = "height"; static final String PARAM_CROP = "crop"; + static final String PARAM_SMARTCROP = "smartcrop"; static final String PARAM_ROTATE = "rotate"; static final String PARAM_QUALITY = "quality"; @@ -76,21 +81,21 @@ public NextGenDynamicMediaImageUrlBuilder(@NotNull NextGenDynamicMediaContext co public @Nullable String build(@NotNull NextGenDynamicMediaImageDeliveryParams params) { // get parameters from nextgen dynamic media config for URL parameters - String repositoryId = context.getNextGenDynamicMediaConfig().getRepositoryId(); + String repositoryId; + if (context.getReference().getAsset() != null) { + repositoryId = context.getNextGenDynamicMediaConfig().getLocalAssetsRepositoryId(); + } + else { + repositoryId = context.getNextGenDynamicMediaConfig().getRemoteAssetsRepositoryId(); + } String imageDeliveryPath = context.getNextGenDynamicMediaConfig().getImageDeliveryBasePath(); - if (StringUtils.isAnyEmpty(repositoryId, imageDeliveryPath)) { + if (repositoryId == null || imageDeliveryPath == null || StringUtils.isAnyBlank(repositoryId, imageDeliveryPath)) { return null; } // replace placeholders in delivery path String seoName = FilenameUtils.getBaseName(context.getReference().getFileName()); - String format = context.getDefaultMediaArgs().getEnforceOutputFileExtension(); - if (StringUtils.isEmpty(format)) { - format = StringUtils.toRootLowerCase(FilenameUtils.getExtension(context.getReference().getFileName())); - } - if (!SUPPORTED_FORMATS.contains(format)) { - format = FileExtension.JPEG; - } + String format = getFileExtension(); imageDeliveryPath = StringUtils.replace(imageDeliveryPath, PLACEHOLDER_ASSET_ID, context.getReference().getAssetId()); imageDeliveryPath = StringUtils.replace(imageDeliveryPath, PLACEHOLDER_SEO_NAME, seoName); imageDeliveryPath = StringUtils.replace(imageDeliveryPath, PLACEHOLDER_FORMAT, format); @@ -111,7 +116,8 @@ else if (width != null) { urlParamMap.put(PARAM_WIDTH, width.toString()); } if (cropSmartRatio != null) { - urlParamMap.put(PARAM_CROP, cropSmartRatio.getWidth() + ":" + cropSmartRatio.getHeight() + ",smart"); + boolean hasWidthDefined = width != null || widthPlaceholder != null; + applyCroppingParams(urlParamMap, cropSmartRatio, hasWidthDefined); } if (rotation != null && rotation != 0) { urlParamMap.put(PARAM_ROTATE, rotation.toString()); @@ -121,6 +127,14 @@ else if (width != null) { } // build URL + return buildImageUrl(repositoryId, imageDeliveryPath, urlParamMap); + } + + /** + * Builds image URL based on URL parameter map. + */ + private static @NotNull String buildImageUrl(@NotNull String repositoryId, @NotNull String imageDeliveryPath, + @NotNull SortedMap urlParamMap) { StringBuilder url = new StringBuilder(); url.append("https://") .append(repositoryId) @@ -140,11 +154,14 @@ else if (width != null) { return url.toString(); } + /** + * Generates URL parameter key/value pair with escaping where appropriate. + */ private static @NotNull String toUrlParam(@NotNull String key, @NotNull String value) { StringBuilder sb = new StringBuilder(); sb.append(key).append("="); // we only need to encode crop, all other parameters are numbers only - if (StringUtils.equals(key, PARAM_CROP)) { + if (StringUtils.equalsAny(key, PARAM_CROP, PARAM_SMARTCROP)) { sb.append(URLEncoder.encode(value, StandardCharsets.UTF_8)); } else { @@ -153,4 +170,59 @@ else if (width != null) { return sb.toString(); } + /** + * Generates cropping/smart cropping URL parameters with or without named smart crop. + */ + private void applyCroppingParams(@NotNull SortedMap urlParamMap, + @NotNull Dimension cropSmartRatio, boolean hasWidthDefined) { + SmartCrop namedSmartCrop = getMatchingNamedSmartCrop(cropSmartRatio); + if (namedSmartCrop != null) { + urlParamMap.put(PARAM_SMARTCROP, namedSmartCrop.getName()); + if (!hasWidthDefined) { + // if no width given apply default width/height to not rely on dimensions defined in AEM image profile + String imageWidthHeightDefault = Long.toString(context.getNextGenDynamicMediaConfig().getImageWidthHeightDefault()); + if (namedSmartCrop.getCropDimension().getWidth() >= namedSmartCrop.getCropDimension().getHeight()) { + urlParamMap.put(PARAM_WIDTH, imageWidthHeightDefault); + } + else { + urlParamMap.put(PARAM_HEIGHT, imageWidthHeightDefault); + } + } + } + else { + urlParamMap.put(PARAM_CROP, cropSmartRatio.getWidth() + ":" + cropSmartRatio.getHeight() + ",smart"); + } + } + + /** + * Looks up named smart crop definition matching the requested ratio. + * @param cropSmartRatio Requested ratio + * @return Matching named smart crop or null if none found + */ + private @Nullable SmartCrop getMatchingNamedSmartCrop(@NotNull Dimension cropSmartRatio) { + NextGenDynamicMediaMetadata metadata = context.getMetadata(); + if (metadata == null) { + return null; + } + double requestedRatio = Ratio.get(cropSmartRatio); + return metadata.getSmartCrops().stream() + .filter(smartCrop -> Ratio.matches(smartCrop.getRatio(), requestedRatio)) + .findFirst() + .orElse(null); + } + + /** + * @return Get file extension used for rendering via DM API. + */ + public @NotNull String getFileExtension() { + String format = context.getDefaultMediaArgs().getEnforceOutputFileExtension(); + if (StringUtils.isEmpty(format)) { + format = StringUtils.toRootLowerCase(FilenameUtils.getExtension(context.getReference().getFileName())); + } + if (format == null || !SUPPORTED_FORMATS.contains(format)) { + format = FileExtension.JPEG; + } + return format; + } + } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReference.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReference.java index 4576698d..13eec568 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReference.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReference.java @@ -23,11 +23,17 @@ import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.day.cq.dam.api.Asset; /** - * Parses and validates Next Generation Dynamic Media references. + * Parses and validates Dynamic Media with OpenAPI asset references. *

* Example: /urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg *

@@ -39,17 +45,29 @@ public final class NextGenDynamicMediaReference { private final String assetId; private final String fileName; + private final Asset asset; + + private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaReference.class); /** * @param assetId Asset ID (has to start with "urn:") * @param fileName File name */ public NextGenDynamicMediaReference(@NotNull String assetId, @NotNull String fileName) { + this(assetId, fileName, null); + } + + /** + * @param assetId Asset ID (has to start with "urn:") + * @param fileName File name + */ + public NextGenDynamicMediaReference(@NotNull String assetId, @NotNull String fileName, @Nullable Asset asset) { if (!StringUtils.startsWith(assetId, ASSET_ID_PREFIX)) { throw new IllegalArgumentException("Asset ID must start with '" + ASSET_ID_PREFIX + "'"); } this.assetId = assetId; this.fileName = fileName; + this.asset = asset; } /** @@ -66,6 +84,13 @@ public NextGenDynamicMediaReference(@NotNull String assetId, @NotNull String fil return fileName; } + /** + * @return Asset (if reference points to local asset) + */ + public @Nullable Asset getAsset() { + return asset; + } + /** * @return Reference */ @@ -91,6 +116,33 @@ public NextGenDynamicMediaReference(@NotNull String assetId, @NotNull String fil return new NextGenDynamicMediaReference(assetId, fileName); } + /** + * Parses a next generation dynamic media reference. + * @param reference Reference + * @return Parsed reference or null if reference is invalid + */ + public static @Nullable NextGenDynamicMediaReference fromDamAssetReference(@Nullable String reference, @NotNull ResourceResolver resourceResolver) { + if (reference == null) { + return null; + } + Resource resource = resourceResolver.getResource(reference); + if (resource == null) { + return null; + } + Asset asset = resource.adaptTo(Asset.class); + if (asset == null) { + return null; + } + String uuid = asset.getID(); + if (StringUtils.isBlank(uuid)) { + log.trace("Ignoring DAM asset without UUID: {}", asset.getPath()); + return null; + } + String assetId = "urn:aaid:aem:" + uuid; + String fileName = asset.getName(); + return new NextGenDynamicMediaReference(assetId, fileName, asset); + } + /** * Checks if given string is a valid next generation dynamic media reference. * @param reference Reference diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataResponse.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataResponse.java index 86719a34..50f8398e 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataResponse.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataResponse.java @@ -19,6 +19,8 @@ */ package io.wcm.handler.mediasource.ngdm.impl.metadata; +import java.util.Map; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -39,10 +41,22 @@ final class MetadataResponse { static final class RepositoryMetadata { @JsonProperty("dc:format") public String dcFormat; + @JsonProperty("smartcrops") + public Map smartCrops; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static final class SmartCrop { + public double left; + public double top; + public double normalizedWidth; + public double normalizedHeight; } @JsonIgnoreProperties(ignoreUnknown = true) static final class AssetMetadata { + @JsonProperty("dam:assetStatus") + public String assetStatus; @JsonProperty("tiff:ImageWidth") public long tiffImageWidth; @JsonProperty("tiff:ImageLength") diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadata.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadata.java index 3d24c2ef..91018228 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadata.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadata.java @@ -19,17 +19,38 @@ */ package io.wcm.handler.mediasource.ngdm.impl.metadata; +import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_PROPERTY; +import static com.day.cq.dam.api.DamConstants.RENDITIONS_FOLDER; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_LEFT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_HEIGHT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_WIDTH; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_TOP; + +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import com.day.cq.dam.api.Asset; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.json.JsonMapper; import io.wcm.handler.media.Dimension; +import io.wcm.handler.mediasource.dam.AssetRendition; +import io.wcm.handler.mediasource.ngdm.impl.metadata.MetadataResponse.AssetMetadata; +import io.wcm.handler.mediasource.ngdm.impl.metadata.MetadataResponse.RepositoryMetadata; import io.wcm.wcm.commons.contenttype.ContentType; /** @@ -39,16 +60,22 @@ public final class NextGenDynamicMediaMetadata { private final String mimeType; private final Dimension dimension; + private final String assetStatus; + private final List smartCrops; private static final JsonMapper OBJECT_MAPPER = new JsonMapper(); + static final String RT_RENDITION_SMARTCROP = "dam/rendition/smartcrop"; - NextGenDynamicMediaMetadata(@Nullable String mimeType, long width, long height) { + NextGenDynamicMediaMetadata(@Nullable String mimeType, @Nullable Dimension dimension, + @Nullable String assetStatus, @Nullable List smartCrops) { this.mimeType = mimeType; - if (width > 0 && height > 0) { - this.dimension = new Dimension(width, height); + this.dimension = dimension; + this.assetStatus = assetStatus; + if (smartCrops != null) { + this.smartCrops = smartCrops; } else { - this.dimension = null; + this.smartCrops = Collections.emptyList(); } } @@ -66,7 +93,24 @@ public final class NextGenDynamicMediaMetadata { return dimension; } - boolean isValid() { + /** + * @return Asset review status + */ + public String getAssetStatus() { + return this.assetStatus; + } + + /** + * @return Named smart crop definitions. + */ + public List getSmartCrops() { + return Collections.unmodifiableList(smartCrops); + } + + /** + * @return true if metadata is valid (has mime type) + */ + public boolean isValid() { return mimeType != null; } @@ -84,20 +128,96 @@ public String toString() { @SuppressWarnings("null") public static @NotNull NextGenDynamicMediaMetadata fromJson(@NotNull String jsonResponse) throws JsonProcessingException { MetadataResponse response = OBJECT_MAPPER.readValue(jsonResponse, MetadataResponse.class); + RepositoryMetadata respositoryMetadata = response.repositoryMetadata; + AssetMetadata assetMetadata = response.assetMetadata; + + long width = 0; + long height = 0; + String assetStatus = null; + if (assetMetadata != null) { + width = assetMetadata.tiffImageWidth; + height = assetMetadata.tiffImageLength; + assetStatus = assetMetadata.assetStatus; + } + Dimension dimension = toDimension(width, height); String mimeType = null; - if (response.repositoryMetadata != null) { - mimeType = response.repositoryMetadata.dcFormat; + List smartCrops = null; + if (respositoryMetadata != null) { + mimeType = respositoryMetadata.dcFormat; + if (respositoryMetadata.smartCrops != null && dimension != null) { + smartCrops = respositoryMetadata.smartCrops.entrySet().stream() + .filter(entry -> isSmartCropDefinitionValid(entry.getKey(), entry.getValue())) + .map(entry -> new SmartCrop(entry.getKey(), entry.getValue(), dimension)) + .collect(Collectors.toList()); + } } - long width = 0; - long height = 0; - if (response.assetMetadata != null) { - width = response.assetMetadata.tiffImageWidth; - height = response.assetMetadata.tiffImageLength; + return new NextGenDynamicMediaMetadata(mimeType, dimension, assetStatus, smartCrops); + } + + private static @Nullable Dimension toDimension(long width, long height) { + if (width > 0 && height > 0) { + return new Dimension(width, height); + } + return null; + } + + /** + * Gets metadata from DAM asset. + * @param asset Asset + * @return Metadata object + */ + @SuppressWarnings("null") + public static @NotNull NextGenDynamicMediaMetadata fromAsset(@NotNull Asset asset) { + String mimeType = asset.getMimeType(); + + Dimension dimension = AssetRendition.getDimension(asset.getOriginal()); + String assetStatus = asset.getMetadataValueFromJcr(ASSET_STATUS_PROPERTY); + List smartCrops = null; + + if (dimension != null) { + smartCrops = getRenditionResources(asset) + .filter(rendition -> rendition.isResourceType(RT_RENDITION_SMARTCROP)) + .map(rendition -> Map.entry(rendition.getName(), renditionToSmartCropDefinition(rendition))) + .filter(entry -> isSmartCropDefinitionValid(entry.getKey(), entry.getValue())) + .map(entry -> new SmartCrop(entry.getKey(), entry.getValue(), dimension)) + .collect(Collectors.toList()); } - return new NextGenDynamicMediaMetadata(mimeType, width, height); + return new NextGenDynamicMediaMetadata(mimeType, dimension, assetStatus, smartCrops); + } + + private static Stream getRenditionResources(@NotNull Asset asset) { + Resource assetResource = asset.adaptTo(Resource.class); + if (assetResource != null) { + Resource renditionsFolder = assetResource.getChild(JCR_CONTENT + "/" + RENDITIONS_FOLDER); + if (renditionsFolder != null) { + return StreamSupport.stream(renditionsFolder.getChildren().spliterator(), false); + } + } + return Stream.empty(); + } + + private static boolean isSmartCropDefinitionValid(@NotNull String name, @NotNull MetadataResponse.SmartCrop smartCop) { + return StringUtils.isNotBlank(name) + && smartCop.normalizedWidth > 0 + && smartCop.normalizedHeight > 0 + && smartCop.left >= 0 + && smartCop.top >= 0; + } + + private static @NotNull MetadataResponse.SmartCrop renditionToSmartCropDefinition(Resource rendition) { + MetadataResponse.SmartCrop result = new MetadataResponse.SmartCrop(); + Resource content = rendition.getChild(JCR_CONTENT); + if (content != null) { + ValueMap props = content.getValueMap(); + result.left = props.get(PN_LEFT, 0d); + result.top = props.get(PN_TOP, 0d); + result.normalizedWidth = props.get(PN_NORMALIZED_WIDTH, 0d); + result.normalizedHeight = props.get(PN_NORMALIZED_HEIGHT, 0d); + } + return result; } } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImpl.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImpl.java index bcaaa71d..111484e9 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImpl.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImpl.java @@ -41,8 +41,6 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.Designate; import org.osgi.service.metatype.annotations.ObjectClassDefinition; @@ -60,21 +58,21 @@ public class NextGenDynamicMediaMetadataServiceImpl implements NextGenDynamicMediaMetadataService { @ObjectClassDefinition( - name = "wcm.io Media Handler Next Generation Dynamic Media Metadata Service", - description = "Fetches metadata for Next Generation Dynamic Media assets.") + name = "wcm.io Media Handler Dynamic Media with OpenAPI Metadata Service", + description = "Fetches metadata for Dynamic Media with OpenAPI remote assets.") @interface Config { @AttributeDefinition( name = "Enabled", - description = "When enabled, metadata is fetched for each resolved asset. This checks for validity/existence of " - + "the asset and for the maximum supported resolution of the original image.") - boolean enabled() default false; + description = "When enabled, metadata is fetched for each resolved remote asset. This checks for validity/existence of " + + "the asset and for the maximum supported resolution of the original image, and allows to fetch Smart Cropping information.") + boolean enabled() default true; @AttributeDefinition( name = "HTTP Headers", description = "HTTP headers to be send with the asset metadata request. " + "Format: 'header1:value1'.") - String[] httpHeaders() default { "X-Adobe-Accept-Experimental:1" }; + String[] httpHeaders(); @AttributeDefinition( name = "Connect Timeout", @@ -103,7 +101,7 @@ public class NextGenDynamicMediaMetadataServiceImpl implements NextGenDynamicMed } - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY) + @Reference private NextGenDynamicMediaConfigService nextGenDynamicMediaConfig; private boolean enabled; diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilder.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilder.java index 44c5726c..2492d4d4 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilder.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilder.java @@ -53,7 +53,7 @@ final class NextGenDynamicMediaMetadataUrlBuilder { public @Nullable String build(@NotNull NextGenDynamicMediaReference reference) { // get parameters from nextgen dynamic media config for URL parameters - String repositoryId = config.getRepositoryId(); + String repositoryId = config.getRemoteAssetsRepositoryId(); String metadataPath = config.getAssetMetadataPath(); if (StringUtils.isAnyEmpty(repositoryId, metadataPath)) { return null; diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/SmartCrop.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/SmartCrop.java new file mode 100644 index 00000000..d1ebc94c --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/SmartCrop.java @@ -0,0 +1,75 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2024 wcm.io + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package io.wcm.handler.mediasource.ngdm.impl.metadata; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.wcm.handler.media.CropDimension; +import io.wcm.handler.media.Dimension; +import io.wcm.handler.media.format.Ratio; + +/** + * Named smart cropping definition. + */ +public class SmartCrop { + private final String name; + private final CropDimension cropDimension; + private final double ratio; + + SmartCrop(String name, + double leftPercentage, double topPercentage, double widthPercentage, double heightPercentage, + Dimension originalDimension) { + + // calculate actual cropping dimension + long originalWidth = originalDimension.getWidth(); + long originalHeight = originalDimension.getHeight(); + long left = Math.round(originalWidth * leftPercentage); + long top = Math.round(originalHeight * topPercentage); + long width = Math.round(originalWidth * widthPercentage); + long height = Math.round(originalHeight * heightPercentage); + + this.name = name; + this.cropDimension = new CropDimension(left, top, width, height, true); + this.ratio = Ratio.get(width, height); + } + + SmartCrop(String name, MetadataResponse.SmartCrop smartCrop, Dimension originalDimension) { + this(name, smartCrop.left, smartCrop.top, smartCrop.normalizedWidth, smartCrop.normalizedHeight, + originalDimension); + } + + public String getName() { + return name; + } + + public CropDimension getCropDimension() { + return this.cropDimension; + } + + public double getRatio() { + return this.ratio; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.NO_CLASS_NAME_STYLE); + } +} \ No newline at end of file diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/package-info.java b/src/main/java/io/wcm/handler/mediasource/ngdm/package-info.java index 7e14a8b2..46b2df12 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/package-info.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/package-info.java @@ -18,7 +18,7 @@ * #L% */ /** - * Media source implementation for Next Generation Dynamic Media. + * Media source implementation for Dynamic Media with OpenAPI. */ @org.osgi.annotation.versioning.Version("1.1.0") package io.wcm.handler.mediasource.ngdm; diff --git a/src/main/resources/i18n/de.json b/src/main/resources/i18n/de.json index de2c6dde..897eb2f4 100644 --- a/src/main/resources/i18n/de.json +++ b/src/main/resources/i18n/de.json @@ -4,6 +4,7 @@ "invalidReason": { "MEDIA_REFERENCE_MISSING": "Keine Referenz", "MEDIA_REFERENCE_INVALID": "Ungültige Referenz", + "NOT_APPROVED": "Asset ist nicht genehmigt", "NO_MATCHING_RENDITION": "Passt nicht zum Medien-Format", "NOT_ENOUGH_MATCHING_RENDITIONS": "Nicht genügend zum Medien-Format passende Renditions", "INVALID_MEDIA_FORMAT": "Ungültiges Medien-Format" diff --git a/src/main/resources/i18n/en.json b/src/main/resources/i18n/en.json index 3d672627..0b8b32ce 100644 --- a/src/main/resources/i18n/en.json +++ b/src/main/resources/i18n/en.json @@ -4,6 +4,7 @@ "invalidReason": { "MEDIA_REFERENCE_MISSING": "No reference", "MEDIA_REFERENCE_INVALID": "Invalid reference", + "NOT_APPROVED": "Asset not approved", "NO_MATCHING_RENDITION": "No matching media format", "NOT_ENOUGH_MATCHING_RENDITIONS": "Not enough renditions matching media formats", "INVALID_MEDIA_FORMAT": "Invalid media format" diff --git a/src/site/markdown/dynamic-media-openapi.md b/src/site/markdown/dynamic-media-openapi.md new file mode 100644 index 00000000..f8d77625 --- /dev/null +++ b/src/site/markdown/dynamic-media-openapi.md @@ -0,0 +1,114 @@ +## Dynamic Media with OpenAPI + +wcm.io Media Handler optionally supports the [Dynamic Media with OpenAPI][aem-nextgen-dm] feature of AEM as a Cloud Service for: + +* Rendering renditions including resizing and cropping +* Delivery via Dynamic Media with OpenAPI CDN +* AI-based Smart Cropping + +Dynamic Media with OpenAPI (formerly known as Next Generation Dynamic Media) has to be licensed separately and is not activated by default for AEMaaCS instances. It can be used for two use cases: + +* Rendering remote assets referenced via the Remote Asset Picker from other assets instances + * Those remote asset references typically start with `/urn:aaid:aem:...`. + * When Dynamic Media with OpenAPI is enabled for an AEMaaCS instance, the asset picker is shown automatically for Media Handler Granite UI widgets. +* Rendering local assets stored in the same AEMaaCS instance. + +The Adobe AEM WCM Core Components currently support only a subset of features, i.e. they only support remote assets but no local assets. When you are using Media Handler directly in your components, or via the [wcm.io WCM Core Components][wcm-core-components] you can leverage all features described in this page. + + +### Media Handler concept + +The integration with Dynamic Media with OpenAPI builds on the [general concepts][general-concepts] of the Media Handler using media formats and a unified media handling API to resolve the renditions for each use case. + +If a rendition is rendered via Dynamic Media with OpenAPI, the media handler returns rendition URLs pointing to the local ore remote asset instance using the [Assets Delivery API (DM API)][aem-dm-api]. From the [supported file formats][file-format-support] Dynamic Media with OpenAPI supports scaling and smart cropping for JPEG, PNG, GIF and TIFF images. All other file formats including SVG images are delivered as original binary via the Dynamic Media with OpenAPI CDN. + +It's not required to configure anything in the component instance edit dialogs or content policies. + + +### Approval State + +Publishing assets to be used via Dynamic Media with OpenAPI works differently than usual: + +* Set the property **Review Status** of the asset to **Approved** + * With this, the asset is accessible for rendering both in author and publish/live environment + * If this status is not set, it's neither visible in Remote Asset Picker, nor can it rendered from local assets +* The Publication status of an asset is not relevant + * However, if you render local assets via Dynamic Media with OpenAPI and the Media Handler, you have to publish the asset after it is set to Approved, so the existence and approval state can be checked on the publish instance when rendering the pages and components. + + +### Smart Cropping + +Smart Cropping is used automatically if a media format with a specific ratio (e.g. 16:9) is used. + +Media formats without any size restrictions, or e.g. only with a width restrictions, can be rendered, but are not cropped. + +If you want to give the business users more control about the actual cropping area of an image, you can create an [image profile][aem-image-profiles] in AEM, enable "Smart Crop" an assign this profile to the asset folders with the assets you want to use (the profile association is inherited to sub folders). Within the image profile, create a cropping entry with a unique name for each rendition you have defined in the media formats, or you are using dynamically for the different breakpoints when using responsive images. If you have already uploaded the assets to this folder you may need to reprocess them. Having this in place, you can use the "Smart Crop" action in the Assets UI to adjust the cropping area for individual assets. + +During the media resolution process, when the media handler has detected the required renditions with their sizes and cropping to fit the output media format/ratio, it checks if named smart crops exist in the asset metadata matching for the requested aspect ratio. If this is the case, the manual adjusted cropping area is used instead of the automatic detected one (if present). To support this for remote assets, the metadata service needs to be enabled (see system configuration). + + +### Validating Assets + +When rendering local assets, the existence and approval state is checked within the local content repository when resolving the media. For this reason, local assets have to be published in AEM after setting the Approval state. + +For remote assets, validating depends on the state of the metadata service (see system configuration). If enabled, the following checks are executed during media resolution: + +* If the remote asset does not exist, or is not approved, the reference is handled as invalid and the component can react to it (e.g. hide the image component). +* If the requested resolution of a rendition is larger than the original resolution of the binary asset, the rendition is handled as invalid. This avoid upscaling, and avoids using an asset in a context which would result in bad image quality for the user. + +If the metadata services is not enabled, the Media Handler assumes by default that a given remote asset reference is always valid, and supports all requested resolutions. + + +### System configuration + +In your project-specific implementation of `io.wcm.handler.media.spi.MediaHandlerConfig` you have to add the media sources implementation `io.wcm.handler.mediasource.ngdm.NextGenDynamicMediaMediaSource` to the list returned by the `getSources()` method (overwrite it from the superclass if required). If you want to use local assets, make sure to put it on top of the list (above the `io.wcm.handler.mediasource.dam.DamMediaSource` media source). + +Example: + +```java +@Component(service = MediaHandlerConfig.class) +public class MediaHandlerConfigImpl extends MediaHandlerConfig { + + private static final List> MEDIA_SOURCES = List.of( + NextGenDynamicMediaMediaSource.class, + DamMediaSource.class, + InlineMediaSource.class); + + public @NotNull List> getSources() { + return MEDIA_SOURCES; + } + + // ... +} +``` + +With this configuration, remote assets should work out-of-the-box, if a remote asset repository is configured for the AEMaaCS instance. + +The "wcm.io Dynamic Media with OpenAPI Support" OSGi configuration allows to reconfigure the actual URLs used for the [Assets Delivery API (DM API)][aem-dm-api]. Usually you can stick with the default values which reflect the latest version of the DM API. Remote assets are supported by default, but can be disabled via this configuration. Local assets are disabled by support, but can be enabled via this configuration. In this case, you also have to configure a repository ID for building the rendition URLs pointing to the AEMaaCS instance. Example: + +```json +{ + "enabledLocalAssets": true, + "localAssetsRepositoryId": "$[env:LOCAL_ASSET_DELIVERY_REPOSITORY_ID;default=]" +} +``` + +With this, you can configure an environment variable `LOCAL_ASSET_DELIVERY_REPOSITORY_ID` pointing to the actual host name which usually has a syntax like `delivery-pXXXXX-eXXXXX.adobeaemcloud.com` with the corresponding program and environment numbers. + +The "wcm.io Dynamic Media with OpenAPI Metadata Service" allows to enable the Asset Metadata support for validation and Smart Cropping. The metadata service is enabled by default. If enabled, for each resolved remote asset a HTTP request is send from the server to the DM API to fetch the asset's metadata. + + +### Known Limitations (as of July 2024) + +* Dynamic Media with OpenAPI is not supported in Media Handler for AEM 6.x, only for AEMaaCS +* Same as with the Adobe AEM WCM Core Components, currently only a single remote AEM Asset instance is supported, which is configured centrally as described in [Support for Remote Assets ][aem-remote-assets]. The media handler uses the same convention for referencing remote assets (using strings starting with `/urn:aaid:aem:...`). This convention also does not support multiple remote AEM Asset instances, as it does not include a pointer to the Repository ID. +* If a component dialog is re-opened with a remote asset references and one of the Media Handler Granite UI widgets (e.g. pathfield), no thumbnail is displayed for the remote asset. But the reference is valid and works. The root cause is a bug/limitation in the underlying AEM pathfield component, which hopefully will be fixed soon by Adobe (SITES-19894). +* The Dynamic Media with OpenAPI remote asset picker currently ignores any folder structures for assets on the remote AEM Asset instance. + + +[aem-remote-assets]: https://experienceleague.adobe.com/en/docs/experience-manager-core-components/using/developing/remote-assets +[aem-dm-api]: https://adobe-aem-assets-delivery.redoc.ly/ +[general-concepts]: general-concepts.html +[file-format-support]: file-format-support.html +[wcm-core-components]: https://wcm.io/wcm/core-components/ +[aem-image-profiles]: https://experienceleague.adobe.com/docs/experience-manager-65/assets/dynamic/image-profiles.html diff --git a/src/site/markdown/graniteui-components.md b/src/site/markdown/graniteui-components.md index d63dcdda..56fb0d07 100644 --- a/src/site/markdown/graniteui-components.md +++ b/src/site/markdown/graniteui-components.md @@ -8,7 +8,7 @@ This is a customized File Upload component that allows to reference AEM assets or upload binary files as inline assets with enhanced support for the wcm.io Media Handler. -```json +```json-jcr "fileReference": { "sling:resourceType": "wcm-io/handler/media/components/granite/form/fileupload", "fieldLabel": "Asset reference" @@ -32,7 +32,7 @@ Enhancements over AEM version: This is a customized Path Field component that allows to reference AEM assets with enhanced support for the wcm.io Media Handler. -```json +```json-jcr "fileReference": { "sling:resourceType": "wcm-io/handler/media/components/granite/form/pathfield", "fieldLabel": "Asset reference" @@ -54,7 +54,7 @@ This component extends the [wcm.io Granite UI components Path Field][wcmio-wcm-u Multi-select field that allows to select one or multiple media formats. -```json +```json-jcr "mediaFormats": { "sling:resourceType": "wcm-io/handler/media/components/granite/form/mediaformatselect", "name": "./mediaFormats", diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index e30aeecc..8164ded3 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -13,8 +13,8 @@ Media resolving, processing and markup generation. * [Component properties][component-properties] * [System configuration][configuration] * [File format support][file-format-support] -* [Next Generation Dynamic Media support][nextgen-dynamic-media] * [Dynamic Media support][dynamic-media] +* [Dynamic Media with OpenAPI support][dynamic-media-openapi] * [API documentation][apidocs] * [Changelog][changelog] @@ -36,7 +36,7 @@ The Media Handler provides: * Generic HTL Placeholder template * Generic [Granite UI components][graniteui-components] that can be used in media/image component dialogs * Support for [Dynamic Media][dynamic-media] -* Support for [Next Generation Dynamic Media][nextgen-dm] on AEMaaCS +* Support for [Dynamic Media with OpenAPI][nextgen-dm] on AEMaaCS * Support for Web-Optimized Image Delivery: On AEMaaCS instances renditions are transparently rendered on the edge Read the [general concepts][general-concepts] to get an overview of the functionality. @@ -82,7 +82,7 @@ Sources: https://github.com/wcm-io/io.wcm.handler.media [file-format-support]: file-format-support.html [nextgen-dm]: https://experienceleague.adobe.com/docs/experience-manager-core-components/using/developing/next-gen-dm.html?lang=en [dynamic-media]: dynamic-media.html -[nextgen-dynamic-media]: nextgen-dynamic-media.html +[dynamic-media-openapi]: dynamic-media-openapi.html [apidocs]: apidocs/ [changelog]: changes-report.html [url-handler]: ../url/ diff --git a/src/site/markdown/nextgen-dynamic-media.md b/src/site/markdown/nextgen-dynamic-media.md deleted file mode 100644 index 8219a2ec..00000000 --- a/src/site/markdown/nextgen-dynamic-media.md +++ /dev/null @@ -1,65 +0,0 @@ -## Next-Generation Dynamic Media - -wcm.io Media Handler optionally supports the [Next Generation Dynamic Media][aem-nextgen-dm] feature of AEM as a Cloud Service for: - -* Rendering renditions including resizing and cropping -* Delivery via Next Generation Dynamic Media CDN -* AI-based Smart Cropping - -Next Generation Dynamic Media support is never applied to assets stored in the AEMaaCS instance itself, but only for remote assets referenced via the Next Generation Dynamic Media asset picker. Those remote asset references typically start with `/urn:aaid:aem:...`. When Next Generation Dynamic Media is enabled for an AEMaaCS instance, the asset picker is shown automatically for Media Handler Granite UI widgets. - -Remote assets are only shown in the asset picker, if they have "approval" state. The publish state of the asset inside AEM is not relevant. - - -### Dynamic Media concept - -The integration with Next Generation Dynamic Media builds on the [general concepts][general-concepts] of the Media Handler using media formats and a unified media handling API to resolve the renditions for each use case. - -If a Next Generation Dynamic Media remote asset is used, the media handler returns rendition URLs pointing to the remote asset instance using the [Assets Delivery API (DM API)][aem-dm-api]. From the [supported file formats][file-format-support] Next Generation Dynamic Media supports scaling and smart cropping for JPEG, PNG, GIF and TIFF images. All other file formats including SVG images are delivered as original binary via the Next Generation Dynamic Media CDN. - -It's not required to configure anything in the component instance edit dialogs or content policies. - - -### Smart Cropping - -Smart Cropping is used automatically if a media format with a specific ratio (e.g. 16:9) is used. - -Media formats without any size restrictions, or e.g. only with a width restrictions, can be rendered, but are not cropped. - - -### Validating Remote Asset Metadata - -By default, the Media Handler assumes that a given remote asset reference is always valid, and supports all requested resolutions. - -Optionally, you can enable the metadata service. If enabled, each time a remote asset reference is resolved, the following checks are executed: - -* If the remote asset does not exist, or is not approved, the reference is handled as invalid and the component can react to it (e.g. hide the image component) -* If the requested resolution of a rendition is larger than the original resolution of the binary asset, the rendition is handled as invalid. This avoid upscaling, and avoids using an asset in a context which would result in bad image quality for the user. - -See system configuration how to enable the metadata service. - - -### System configuration - -If Next Generation Dynamic Media is enabled for a AEMaaCS instance, it will work out-of-the-box with the Media Handler. In your project-specific implementation of `io.wcm.handler.media.spi.MediaHandlerConfig` you have to add the media sources implementation `io.wcm.handler.mediasource.ngdm.NextGenDynamicMediaMediaSource` to the list returned by the `getSources()` method (overwrite it from the superclass if required). - -The "wcm.io Next Generation Dynamic Media Support" OSGi configuration allows to reconfigure the actual URLs used for the [Assets Delivery API (DM API)][aem-dm-api]. Usually you can stick with the default values which reflect the latest version of the DM API. - -The "wcm.io Next Generation Dynamic Media Metadata Service" allows to enable the Asset Metadata support (see above). When this is enabled, for each resolved remote asset, a HTTP request is send from the server to the DM API, so make sure this is allowed in the network infrastructure (should work by default in AEMaaCS instances). Optionally, you can configure an proxy server and timeouts. - - -### Known Limitations (as of March 2024) - -* The DM API URLs still have to contain an `accept-experimental` URL parameter, and the metadata services has to use an `X-Adobe-Accept-Experimental` HTTP header. Both will fade out once Next Generation Dynamic Media reaches full general availability (expected later in 2024). -* Next Generation Dynamic Media is not supported in Media Handler for AEM 6.x, only for AEMaaCS -* Same as the Adobe Core Components, currently only a single remote AEM Asset instance is supported, which is configured centrally as described in [Next Generation Dynamic Media][aem-nextgen-dm]. The media handler is used the same convention for referencing remote assets (using strings starting with `/urn:aaid:aem:...`). This convention also does not support multiple remote AEM Asset instances, as it does not include a pointer to the Repository ID. -* If a component dialog is re-opened with a remote asset references and one of the Media Handler Granite UI widgets (e.g. pathfield), no thumbnail is displayed for the remote asset. But the reference is valid and works. The root cause is a bug/limitation in the underlying AEM pathfield component, which hopefully will be fixed soon by Adobe (SITES-19894). -* The Next Generation Dynamic Media remote asset picker currently ignored any folder structures for assets on the remote AEM Asset instance. -* The DM API currently does not support sending a "Content-Disposition: attachment" HTTP header for downloads. So even, if this is enforced by the Media Handler, it currently does not work for remote assets. - - -[aem-nextgen-dm]: https://experienceleague.adobe.com/docs/experience-manager-core-components/using/developing/next-gen-dm.html?lang=en -[aem-dm-api]: https://adobe-aem-assets-delivery-experimental.redoc.ly/ -[general-concepts]: general-concepts.html -[file-format-support]: file-format-support.html -[configuration]: configuration.html diff --git a/src/test/java/io/wcm/handler/media/MediaComponentPropertyResolverTest.java b/src/test/java/io/wcm/handler/media/MediaComponentPropertyResolverTest.java index 5b5f3a0b..36de7913 100644 --- a/src/test/java/io/wcm/handler/media/MediaComponentPropertyResolverTest.java +++ b/src/test/java/io/wcm/handler/media/MediaComponentPropertyResolverTest.java @@ -124,7 +124,7 @@ void testIsAutoCrop_Component_Policy() throws Exception { } @Test - void testIsAutoCrop_ValueMap() throws Exception { + void testIsAutoCrop_ValueMap() { ValueMap valueMap = new ValueMapDecorator(Map.of(PN_COMPONENT_MEDIA_AUTOCROP, true)); try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(valueMap)) { @@ -249,7 +249,6 @@ void testGetMediaFormatOptions_Multi_Mandatory_Names() throws Exception { } @Test - @SuppressWarnings("unused") void testParseWidths() { assertNull(parseWidths(null)); assertNull(parseWidths("")); diff --git a/src/test/java/io/wcm/handler/media/MediaRequestTest.java b/src/test/java/io/wcm/handler/media/MediaRequestTest.java index 4a9e387b..68d43b82 100644 --- a/src/test/java/io/wcm/handler/media/MediaRequestTest.java +++ b/src/test/java/io/wcm/handler/media/MediaRequestTest.java @@ -23,13 +23,27 @@ import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; + +@ExtendWith(AemContextExtension.class) class MediaRequestTest { + private final AemContext context = AppAemContext.newAemContext(); + @Test void testToString() { MediaRequest request = new MediaRequest("/path", null); assertTrue(StringUtils.contains(request.toString(), "/path")); } + @Test + void testToString_Resource() { + MediaRequest request = new MediaRequest(context.create().resource("/content/test"), null); + assertTrue(StringUtils.contains(request.toString(), "/content/test")); + } + } diff --git a/src/test/java/io/wcm/handler/media/impl/AssetRenditionContentDispositionFilterTest.java b/src/test/java/io/wcm/handler/media/impl/AssetRenditionContentDispositionFilterTest.java index 7a37285f..be2370f5 100644 --- a/src/test/java/io/wcm/handler/media/impl/AssetRenditionContentDispositionFilterTest.java +++ b/src/test/java/io/wcm/handler/media/impl/AssetRenditionContentDispositionFilterTest.java @@ -57,7 +57,7 @@ class AssetRenditionContentDispositionFilterTest { private FilterChain filterChain; @BeforeEach - void setUp() throws Exception { + void setUp() { // setup filter with JPEG on the blacklist underTest = context.registerInjectActivateService(new AssetRenditionContentDispositionFilter(), BLACK_LIST_MIME_TYPE_CONFIG, new String[] { ContentType.JPEG }, diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplAdobeStandardNamingTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplAdobeStandardNamingTest.java index 55d4b010..93831280 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplAdobeStandardNamingTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplAdobeStandardNamingTest.java @@ -94,6 +94,6 @@ public boolean useAdobeStandardNames() { // switch to adobe standard naming return true; } - }; + } } diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest.java index c82e3117..a2b95bac 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest.java @@ -39,7 +39,7 @@ * with rendering via dynamic media. The fallback to AEM-rendered renditions is disabled. */ @ExtendWith(AemContextExtension.class) -@SuppressWarnings("java:S5976") // ignore similar tests +@SuppressWarnings({ "java:S5976", "java:S4144" }) // ignore similar tests class MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest extends MediaHandlerImplImageFileTypesEnd2EndTest { @Override diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaLocalAssetTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaLocalAssetTest.java new file mode 100644 index 00000000..a92d3209 --- /dev/null +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaLocalAssetTest.java @@ -0,0 +1,234 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2024 wcm.io + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package io.wcm.handler.media.impl; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.day.cq.dam.api.Asset; + +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigServiceImpl; +import io.wcm.testing.mock.aem.dam.ngdm.MockNextGenDynamicMediaConfig; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; +import io.wcm.wcm.commons.contenttype.ContentType; + +/** + * This is an "end-to-end" test handling image files with different content types + * from classpath, handles them with and without cropping using media handler + * and renders the result using the ImageFileServlet. + */ +@ExtendWith(AemContextExtension.class) +@SuppressWarnings("java:S2699") // all tests have assertions +class MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaLocalAssetTest extends MediaHandlerImplImageFileTypesEnd2EndTest { + + @BeforeEach + @Override + void setUp() { + MockNextGenDynamicMediaConfig nextGenDynamicMediaConfig = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); + nextGenDynamicMediaConfig.setEnabled(true); + nextGenDynamicMediaConfig.setRepositoryId("repo1"); + context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class, + "enabledLocalAssets", "true", + "localAssetsRepositoryId", "localrepo1"); + super.setUp(); + } + + @Override + @Test + void testAsset_JPEG_Original() { + Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); + buildAssertMedia(asset, 100, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?preferwebp=true&quality=85", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_JPEG_Original_ContentDisposition() { + Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); + buildAssertMedia_ContentDisposition(asset, 100, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?preferwebp=true&quality=85", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_JPEG_Rescale() { + Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); + buildAssertMedia_Rescale(asset, 80, 40, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_JPEG_AutoCrop() { + Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); + buildAssertMedia_AutoCrop(asset, 50, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_JPEG_AutoCrop_ImageQuality() { + Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); + buildAssertMedia_AutoCrop(asset, 50, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=60", + ContentType.JPEG, 0.6d); + } + + @Override + @Test + void testAsset_JPEG_CropWithExplicitRendition() { + Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); + context.create().assetRendition(asset, "square.jpg", 50, 50, ContentType.JPEG); + buildAssertMedia_AutoCrop(asset, 50, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_GIF_Original() { + Asset asset = createSampleAsset("/filetype/sample.gif", ContentType.GIF); + buildAssertMedia(asset, 100, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.gif?preferwebp=true&quality=85", + ContentType.GIF); + } + + @Override + @Test + void testAsset_GIF_Rescale() { + Asset asset = createSampleAsset("/filetype/sample.gif", ContentType.GIF); + buildAssertMedia_Rescale(asset, 80, 40, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.gif?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_GIF_AutoCrop() { + Asset asset = createSampleAsset("/filetype/sample.gif", ContentType.GIF); + buildAssertMedia_AutoCrop(asset, 50, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.gif?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_PNG_Original() { + Asset asset = createSampleAsset("/filetype/sample.png", ContentType.PNG); + buildAssertMedia(asset, 100, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.png?preferwebp=true&quality=85", + ContentType.PNG); + } + + @Override + @Test + void testAsset_PNG_Rescale() { + Asset asset = createSampleAsset("/filetype/sample.png", ContentType.PNG); + buildAssertMedia_Rescale(asset, 80, 40, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.png?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + ContentType.PNG); + } + + @Override + @Test + void testAsset_PNG_AutoCrop() { + Asset asset = createSampleAsset("/filetype/sample.png", ContentType.PNG); + buildAssertMedia_AutoCrop(asset, 50, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.png?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + ContentType.PNG); + } + + @Override + @Test + void testAsset_TIFF_Original() { + Asset asset = createSampleAsset("/filetype/sample.tif", ContentType.TIFF); + buildAssertMedia(asset, 100, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?preferwebp=true&quality=85", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_TIFF_Original_ContentDisposition() { + Asset asset = createSampleAsset("/filetype/sample.tif", ContentType.TIFF); + buildAssertMedia_ContentDisposition(asset, 100, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?preferwebp=true&quality=85", + ContentType.TIFF); + } + + @Override + @Test + void testAsset_TIFF_Rescale() { + Asset asset = createSampleAsset("/filetype/sample.tif", ContentType.TIFF); + buildAssertMedia_Rescale(asset, 80, 40, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_TIFF_AutoCrop() { + Asset asset = createSampleAsset("/filetype/sample.tif", ContentType.TIFF); + buildAssertMedia_AutoCrop(asset, 50, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + ContentType.JPEG); + } + + @Override + @Test + void testAsset_SVG_Original() { + Asset asset = createSampleAsset("/filetype/sample.svg", ContentType.SVG); + buildAssertMedia(asset, 100, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/original/as/sample.svg", + ContentType.SVG); + } + + @Override + @Test + void testAsset_SVG_Original_ContentDisposition() { + Asset asset = createSampleAsset("/filetype/sample.svg", ContentType.SVG); + buildAssertMedia_ContentDisposition(asset, 100, 50, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/original/as/sample.svg?attachment=true", + ContentType.SVG); + } + + @Override + @Test + void testAsset_SVG_Rescale() { + Asset asset = createSampleAsset("/filetype/sample.svg", ContentType.SVG); + buildAssertMedia_Rescale(asset, 80, 40, + "https://localrepo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/original/as/sample.svg", + ContentType.SVG); + } + + @Override + @Test + @Disabled("Not supported with NGDM") + void testAsset_SVG_AutoCrop() { + // disabled + } + +} diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest.java index e46bb970..c0f5e9ef 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest.java @@ -23,6 +23,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,6 +45,7 @@ @SuppressWarnings("java:S2699") // all tests have assertions class MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest extends MediaHandlerImplImageFileTypesEnd2EndTest { + @BeforeEach @Override void setUp() { MockNextGenDynamicMediaConfig nextGenDynamicMediaConfig = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); @@ -57,7 +60,7 @@ void setUp() { void testAsset_JPEG_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia(asset, 100, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?preferwebp=true&quality=85", ContentType.JPEG); } @@ -66,7 +69,7 @@ void testAsset_JPEG_Original() { void testAsset_JPEG_Original_ContentDisposition() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia_ContentDisposition(asset, 100, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?preferwebp=true&quality=85", ContentType.JPEG); } @@ -75,7 +78,7 @@ void testAsset_JPEG_Original_ContentDisposition() { void testAsset_JPEG_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia_Rescale(asset, 80, 40, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", ContentType.JPEG); } @@ -84,7 +87,7 @@ void testAsset_JPEG_Rescale() { void testAsset_JPEG_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=85", ContentType.JPEG); } @@ -93,16 +96,23 @@ void testAsset_JPEG_AutoCrop() { void testAsset_JPEG_AutoCrop_ImageQuality() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=60", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=60", ContentType.JPEG, 0.6d); } + @Override + @Test + @Disabled("Not supported with NGDM") + void testAsset_JPEG_CropWithExplicitRendition() { + // disabled + } + @Override @Test void testAsset_GIF_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.gif"); buildAssertMedia(asset, 100, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?accept-experimental=1&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?preferwebp=true&quality=85", ContentType.GIF); } @@ -111,7 +121,7 @@ void testAsset_GIF_Original() { void testAsset_GIF_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.gif"); buildAssertMedia_Rescale(asset, 80, 40, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?accept-experimental=1&crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", ContentType.GIF); } @@ -120,7 +130,7 @@ void testAsset_GIF_Rescale() { void testAsset_GIF_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.gif"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?crop=1%3A1%2Csmart&preferwebp=true&quality=85", ContentType.GIF); } @@ -129,7 +139,7 @@ void testAsset_GIF_AutoCrop() { void testAsset_PNG_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.png"); buildAssertMedia(asset, 100, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?accept-experimental=1&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?preferwebp=true&quality=85", ContentType.PNG); } @@ -138,7 +148,7 @@ void testAsset_PNG_Original() { void testAsset_PNG_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.png"); buildAssertMedia_Rescale(asset, 80, 40, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?accept-experimental=1&crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", ContentType.PNG); } @@ -147,7 +157,7 @@ void testAsset_PNG_Rescale() { void testAsset_PNG_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.png"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?crop=1%3A1%2Csmart&preferwebp=true&quality=85", ContentType.PNG); } @@ -156,7 +166,7 @@ void testAsset_PNG_AutoCrop() { void testAsset_TIFF_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); buildAssertMedia(asset, 100, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?preferwebp=true&quality=85", ContentType.JPEG); } @@ -165,7 +175,7 @@ void testAsset_TIFF_Original() { void testAsset_TIFF_Original_ContentDisposition() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); buildAssertMedia_ContentDisposition(asset, 100, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?preferwebp=true&quality=85", ContentType.TIFF); } @@ -174,7 +184,7 @@ void testAsset_TIFF_Original_ContentDisposition() { void testAsset_TIFF_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); buildAssertMedia_Rescale(asset, 80, 40, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", ContentType.JPEG); } @@ -183,7 +193,7 @@ void testAsset_TIFF_Rescale() { void testAsset_TIFF_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=85", ContentType.JPEG); } @@ -192,7 +202,7 @@ void testAsset_TIFF_AutoCrop() { void testAsset_SVG_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); buildAssertMedia(asset, 0, 0, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?accept-experimental=1", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg", ContentType.SVG); } @@ -201,7 +211,7 @@ void testAsset_SVG_Original() { void testAsset_SVG_Original_ContentDisposition() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); buildAssertMedia_ContentDisposition(asset, 0, 0, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?accept-experimental=1", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?attachment=true", ContentType.SVG); } @@ -210,7 +220,7 @@ void testAsset_SVG_Original_ContentDisposition() { void testAsset_SVG_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); buildAssertMedia_Rescale(asset, 0, 0, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?accept-experimental=1", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg", ContentType.SVG); } @@ -219,7 +229,7 @@ void testAsset_SVG_Rescale() { void testAsset_SVG_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); buildAssertMedia_AutoCrop(asset, 0, 0, - "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?accept-experimental=1", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg", ContentType.JPEG); } diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java index f2e98bff..12051e51 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java @@ -19,14 +19,17 @@ */ package io.wcm.handler.media.impl; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_APPROVED; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_PROPERTY; import static io.wcm.handler.media.MediaNameConstants.NN_MEDIA_INLINE; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.InputStream; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; @@ -34,13 +37,14 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.ModifiableValueMap; import org.apache.sling.api.resource.Resource; -import org.apache.sling.testing.mock.osgi.MapUtil; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import com.day.cq.commons.jcr.JcrConstants; import com.day.cq.dam.api.Asset; import com.day.cq.dam.scene7.api.constants.Scene7Constants; import com.day.image.Layer; @@ -395,15 +399,15 @@ void testFileUpload_SVG_AutoCrop() { Asset createSampleAsset(String classpathResource, String contentType) { String fileName = FilenameUtils.getName(classpathResource); String fileExtension = FilenameUtils.getExtension(classpathResource); - Map metadata; + Map metadata = new HashMap<>(); + metadata.put(ASSET_STATUS_PROPERTY, ASSET_STATUS_APPROVED); if (isCreateAssetWithDynamicMediaMetadata()) { - metadata = MapUtil.toMap(Scene7Constants.PN_S7_FILE, "DummyFolder/" + fileName); - } - else { - metadata = Collections.emptyMap(); + metadata.put(Scene7Constants.PN_S7_FILE, "DummyFolder/" + fileName); } Asset asset = context.create().asset("/content/dam/" + fileName, classpathResource, contentType, metadata); context.create().assetRendition(asset, "cq5dam.web.sample." + fileExtension, classpathResource, contentType); + ModifiableValueMap props = AdaptTo.notNull(asset, ModifiableValueMap.class); + props.put(JcrConstants.JCR_UUID, SAMPLE_UUID); return asset; } diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndWebOptimizedImageDeliveryTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndWebOptimizedImageDeliveryTest.java index 6920a71c..94aaea5f 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndWebOptimizedImageDeliveryTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndWebOptimizedImageDeliveryTest.java @@ -21,6 +21,7 @@ import static io.wcm.testing.mock.aem.dam.ngdm.MockAssetDelivery.getAssetId; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,6 +39,7 @@ @SuppressWarnings("java:S2699") // all tests have assertions class MediaHandlerImplImageFileTypesEnd2EndWebOptimizedImageDeliveryTest extends MediaHandlerImplImageFileTypesEnd2EndTest { + @BeforeEach @Override void setUp() { context.registerInjectActivateService(MockAssetDelivery.class); diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplTest.java index c307f482..d4a45286 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplTest.java @@ -326,7 +326,7 @@ public List> getPostProcessors() { return List.of(TestPostProcessor.class); } - }; + } @Model(adaptables = { diff --git a/src/test/java/io/wcm/handler/media/impl/ipeconfig/CroppingRatiosTest.java b/src/test/java/io/wcm/handler/media/impl/ipeconfig/CroppingRatiosTest.java index 6440050d..7bb32696 100644 --- a/src/test/java/io/wcm/handler/media/impl/ipeconfig/CroppingRatiosTest.java +++ b/src/test/java/io/wcm/handler/media/impl/ipeconfig/CroppingRatiosTest.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Mock.Strictness; import org.mockito.junit.jupiter.MockitoExtension; import io.wcm.handler.media.MediaArgs; @@ -42,7 +43,7 @@ @ExtendWith(MockitoExtension.class) class CroppingRatiosTest { - @Mock(lenient = true) + @Mock(strictness = Strictness.LENIENT) private MediaFormatHandler mediaFormatHandler; private SortedSet mediaFormats = new TreeSet<>(); diff --git a/src/test/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtilTest.java b/src/test/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtilTest.java index 9b3b6637..12b4c564 100644 --- a/src/test/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtilTest.java +++ b/src/test/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtilTest.java @@ -88,7 +88,7 @@ class MediaMarkupBuilderUtilTest { @BeforeEach @SuppressWarnings("null") - void setUp() throws Exception { + void setUp() { when(request.getResourceResolver()).thenReturn(resolver); when(resolver.adaptTo(PageManager.class)).thenReturn(pageManager); when(request.getResource()).thenReturn(resource); diff --git a/src/test/java/io/wcm/handler/media/testcontext/DummyAppTemplate.java b/src/test/java/io/wcm/handler/media/testcontext/DummyAppTemplate.java index 034d9cd1..69d5e610 100644 --- a/src/test/java/io/wcm/handler/media/testcontext/DummyAppTemplate.java +++ b/src/test/java/io/wcm/handler/media/testcontext/DummyAppTemplate.java @@ -42,8 +42,8 @@ public enum DummyAppTemplate { // build resource type from template path String resourceTypeFromPath = null; - final Pattern TEMPLATE_PATH_PATTERN = Pattern.compile("^/apps/([^/]+)/templates(/.*)?/([^/]+)$"); - Matcher templateParts = TEMPLATE_PATH_PATTERN.matcher(templatePath); + final Pattern templatePathPattern = Pattern.compile("^/apps/([^/]+)/templates(/.*)?/([^/]+)$"); + Matcher templateParts = templatePathPattern.matcher(templatePath); if (templateParts.matches()) { resourceTypeFromPath = templateParts.group(1) + "/components" + StringUtils.defaultString(templateParts.group(2)) + "/page/" + templateParts.group(3); @@ -52,11 +52,6 @@ public enum DummyAppTemplate { this.resourceType = resourceTypeFromPath; } - DummyAppTemplate(String templatePath, String resourceType) { - this.templatePath = templatePath; - this.resourceType = resourceType; - } - /** * Template path * @return Path diff --git a/src/test/java/io/wcm/handler/media/testcontext/DummyMediaHandlerConfig.java b/src/test/java/io/wcm/handler/media/testcontext/DummyMediaHandlerConfig.java index dd4d016e..432a0b5f 100644 --- a/src/test/java/io/wcm/handler/media/testcontext/DummyMediaHandlerConfig.java +++ b/src/test/java/io/wcm/handler/media/testcontext/DummyMediaHandlerConfig.java @@ -35,9 +35,9 @@ public class DummyMediaHandlerConfig extends MediaHandlerConfig { private static final List> MEDIA_SOURCES = List.of( + NextGenDynamicMediaMediaSource.class, DamMediaSource.class, - InlineMediaSource.class, - NextGenDynamicMediaMediaSource.class); + InlineMediaSource.class); private boolean enforceVirtualRenditions; @@ -47,6 +47,7 @@ public class DummyMediaHandlerConfig extends MediaHandlerConfig { } @Override + @Deprecated public boolean includeAssetWebRenditionsByDefault() { // unit tests rely on old behavior return true; diff --git a/src/test/java/io/wcm/handler/mediasource/dam/AbstractDamTest.java b/src/test/java/io/wcm/handler/mediasource/dam/AbstractDamTest.java index 71e7440b..c436da71 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/AbstractDamTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/AbstractDamTest.java @@ -110,7 +110,7 @@ public abstract class AbstractDamTest { private MediaHandler mediaHandler; @BeforeEach - final void setUpDamEnvironment() throws Exception { + final void setUpDamEnvironment() { // simulate HTML requests for integrator mode context.requestPathInfo().setExtension(FileExtension.HTML); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/AssertThumbnailWebRenditionMediaHandlerTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/AssertThumbnailWebRenditionMediaHandlerTest.java index f52d8deb..8f5499f3 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/AssertThumbnailWebRenditionMediaHandlerTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/AssertThumbnailWebRenditionMediaHandlerTest.java @@ -144,6 +144,7 @@ void testWithAllowSpecificRenditions() { void testWithDisallowWebRenditionsViaMediaHandlerConfig() { context.registerService(MediaHandlerConfig.class, new MediaHandlerConfig() { @Override + @Deprecated public boolean includeAssetWebRenditionsByDefault() { return false; } diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/DefaultRenditionHandlerTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/DefaultRenditionHandlerTest.java index d9c5dffa..9077e230 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/DefaultRenditionHandlerTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/DefaultRenditionHandlerTest.java @@ -40,7 +40,7 @@ class DefaultRenditionHandlerTest extends AbstractDamTest { private DefaultRenditionHandler underTest; @BeforeEach - void setUp() throws Exception { + void setUp() { Asset asset = context.resourceResolver().getResource(MEDIAITEM_PATH_16_10).adaptTo(Asset.class); MediaHandlerConfig mediaHandlerConfig = AdaptTo.notNull(context.request(), MediaHandlerConfig.class); @@ -53,14 +53,14 @@ void setUp() throws Exception { } @Test - void testOriginal() throws Exception { + void testOriginal() { RenditionMetadata rendition = underTest.getRendition(new MediaArgs()); assertEquals(1600, rendition.getWidth()); assertEquals(1000, rendition.getHeight()); } @Test - void testInvalidRatio() throws Exception { + void testInvalidRatio() { RenditionMetadata rendition = underTest.getRendition(new MediaArgs() .fixedWidth(100) .fixedHeight(100)); @@ -68,7 +68,7 @@ void testInvalidRatio() throws Exception { } @Test - void testFixedWith() throws Exception { + void testFixedWith() { RenditionMetadata rendition = underTest.getRendition(new MediaArgs() .fixedWidth(160)); assertEquals(160, rendition.getWidth()); @@ -76,7 +76,7 @@ void testFixedWith() throws Exception { } @Test - void testFixedHeight() throws Exception { + void testFixedHeight() { RenditionMetadata rendition = underTest.getRendition(new MediaArgs() .fixedHeight(100)); assertEquals(160, rendition.getWidth()); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadataTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadataTest.java index 34e51ad5..08278183 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadataTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadataTest.java @@ -51,7 +51,7 @@ class RenditionMetadataTest extends AbstractDamTest { private RenditionMetadata biggestRendition; @BeforeEach - void setUp() throws Exception { + void setUp() { Media media = mediaHandler().get(MEDIAITEM_PATH_STANDARD).build(); Asset asset = media.getAsset().adaptTo(Asset.class); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandlerTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandlerTest.java index 6fd047f0..e740c3f1 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandlerTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandlerTest.java @@ -62,7 +62,7 @@ class TransformedRenditionHandlerTest { @BeforeEach @SuppressWarnings("null") - void setUp() throws Exception { + void setUp() { // register RenditionMetadataListenerService to generate rendition metadata context.registerInjectActivateService(new AssetSynchonizationService()); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadataTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadataTest.java index 3f49df80..c175d75c 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadataTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadataTest.java @@ -54,14 +54,14 @@ void setUp() throws Exception { } @Test - void testGetLayer() throws Exception { + void testGetLayer() { Layer layer = underTest.getLayer(); assertEquals(108, layer.getWidth()); assertEquals(51, layer.getHeight()); } @Test - void testGetInputStream() throws Exception { + void testGetInputStream() { InputStream is = underTest.getInputStream(); assertNull(is); } diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadataTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadataTest.java index 67ebd9cb..180eb5e2 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadataTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadataTest.java @@ -55,7 +55,7 @@ void setUp() throws Exception { } @Test - void testGetLayer_cropping() throws Exception { + void testGetLayer_cropping() { VirtualTransformedRenditionMetadata underTest = new VirtualTransformedRenditionMetadata(rendition, 30, 25, null, new CropDimension(5, 5, 30, 25), null, null); @@ -65,7 +65,7 @@ void testGetLayer_cropping() throws Exception { } @Test - void testGetLayer_cropping_resize() throws Exception { + void testGetLayer_cropping_resize() { VirtualTransformedRenditionMetadata underTest = new VirtualTransformedRenditionMetadata(rendition, 30, 25, null, new CropDimension(5, 5, 60, 50), null, null); @@ -75,7 +75,7 @@ void testGetLayer_cropping_resize() throws Exception { } @Test - void testGetLayer_rotation() throws Exception { + void testGetLayer_rotation() { VirtualTransformedRenditionMetadata underTest = new VirtualTransformedRenditionMetadata(rendition, 102, 215, null, null, 90, null); @@ -85,7 +85,7 @@ void testGetLayer_rotation() throws Exception { } @Test - void testGetLayer_cropping_rotation() throws Exception { + void testGetLayer_cropping_rotation() { VirtualTransformedRenditionMetadata underTest = new VirtualTransformedRenditionMetadata(rendition, 25, 30, null, new CropDimension(5, 5, 25, 30), 180, null); @@ -95,7 +95,7 @@ void testGetLayer_cropping_rotation() throws Exception { } @Test - void testGetInputStream() throws Exception { + void testGetInputStream() { VirtualTransformedRenditionMetadata underTest = new VirtualTransformedRenditionMetadata(rendition, 108, 51, null, new CropDimension(5, 5, 30, 25), null, null); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCropTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCropTest.java index 8bbcdcc5..e150ec3c 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCropTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCropTest.java @@ -189,19 +189,19 @@ void testIsMatchingSize_TooSmall_WidthDeviation() { @Test void testIsMatchingSize_MatchesExact_HeightDeviation() { - prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 80x75 cropping area, treated as 80x50 + prepareSmartCropRendition(0, 0, 0.5, 0.75); // results in 80x75 cropping area, treated as 80x50 assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 80, 50)); } @Test void testIsMatchingSize_MatchesSmaller_HeightDeviation() { - prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 80x75 cropping area, treated as 80x50 + prepareSmartCropRendition(0, 0, 0.5, 0.75); // results in 80x75 cropping area, treated as 80x50 assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 40, 25)); } @Test void testIsMatchingSize_TooSmall_HeightDeviation() { - prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 80x75 cropping area, treated as 80x50 + prepareSmartCropRendition(0, 0, 0.5, 0.75); // results in 80x75 cropping area, treated as 80x50 assertFalse(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 120, 75)); } diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataWorkflowProcessTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataWorkflowProcessTest.java index ca690709..79df810d 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataWorkflowProcessTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataWorkflowProcessTest.java @@ -73,7 +73,7 @@ class RenditionMetadataWorkflowProcessTest { @BeforeEach @SuppressWarnings("null") - void setUp() throws Exception { + void setUp() { when(workItem.getWorkflowData()).thenReturn(workflowData); when(workflowData.getPayloadType()).thenReturn(PayloadMap.TYPE_JCR_PATH); when(workflowSession.adaptTo(ResourceResolver.class)).thenReturn(context.resourceResolver()); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/WorkflowProcessUtilTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/WorkflowProcessUtilTest.java index 8971ec45..397c577d 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/WorkflowProcessUtilTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/WorkflowProcessUtilTest.java @@ -66,7 +66,7 @@ class WorkflowProcessUtilTest { private WorkflowData workflowData; @BeforeEach - void setUp() throws Exception { + void setUp() { samplePage1 = context.create().page("/content/mysite/page1"); samplePage2 = context.create().page("/content/mysite/page2"); sampleAsset = context.create().asset("/content/dam/asset1.jpg", 10, 10, "image/jpeg"); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/markup/DamVideoMediaMarkupBuilderTest.java b/src/test/java/io/wcm/handler/mediasource/dam/markup/DamVideoMediaMarkupBuilderTest.java index e82b157a..7dee6443 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/markup/DamVideoMediaMarkupBuilderTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/markup/DamVideoMediaMarkupBuilderTest.java @@ -47,7 +47,7 @@ class DamVideoMediaMarkupBuilderTest extends AbstractDamTest { private DamVideoMediaMarkupBuilder underTest; @BeforeEach - void setUp() throws Exception { + void setUp() { // prepare video profiles context.load().json("/mediasource/dam/dam-video-profiles.json", "/etc/dam/video"); @@ -57,7 +57,7 @@ void setUp() throws Exception { } @Test - void testAccepts() throws Exception { + void testAccepts() { // accepts video assertTrue(underTest.accepts(video)); // does not accept image @@ -68,7 +68,7 @@ void testAccepts() throws Exception { } @Test - void testBuild() throws Exception { + void testBuild() { HtmlElement element = underTest.build(video); assertTrue(element instanceof Video); diff --git a/src/test/java/io/wcm/handler/mediasource/inline/InlineMediaSourceTest.java b/src/test/java/io/wcm/handler/mediasource/inline/InlineMediaSourceTest.java index 7027a880..e12285f5 100644 --- a/src/test/java/io/wcm/handler/mediasource/inline/InlineMediaSourceTest.java +++ b/src/test/java/io/wcm/handler/mediasource/inline/InlineMediaSourceTest.java @@ -24,6 +24,7 @@ import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_CROP; import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_IS_DECORATIVE; import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_ROTATION; +import static io.wcm.handler.media.testcontext.AppAemContext.ROOTPATH_CONTENT; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_1COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_2COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_3COL; @@ -39,7 +40,6 @@ import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_CONTROLS_SCALE1_ONLYWIDTH_RATIO1; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_CONTROLS_SCALE1_ONLYWIDTH_RATIO2; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_FLYOUT_FEATURE; -import static io.wcm.handler.media.testcontext.AppAemContext.ROOTPATH_CONTENT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java index 57c49cff..a8301f45 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java @@ -20,8 +20,6 @@ package io.wcm.handler.mediasource.ngdm; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.json.JSONException; @@ -64,12 +62,4 @@ void testWithConfigService() throws JSONException { underTest.getConfigJson(), true); } - @Test - void testWithoutConfigService() { - NextGenDynamicMediaConfigModel underTest = AdaptTo.notNull(context.request(), NextGenDynamicMediaConfigModel.class); - assertFalse(underTest.isEnabled()); - assertNull(underTest.getAssetSelectorsJsUrl()); - assertNull(underTest.getConfigJson()); - } - } diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSourceTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSourceTest.java index 47bc9dc8..acc48b83 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSourceTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSourceTest.java @@ -52,37 +52,42 @@ class NextGenDynamicMediaMediaSourceTest { @Test void testGetId() { + registerMockNextGenDynamicMediaConfig(false, false); NextGenDynamicMediaMediaSource underTest = AdaptTo.notNull(context.request(), NextGenDynamicMediaMediaSource.class); assertEquals(NextGenDynamicMediaMediaSource.ID, underTest.getId()); } @Test - void testAccepts_withoutNextGenDynamicMediaConfig() { + void testAccepts_withNextGenDynamicMediaConfigDisabled() { + registerMockNextGenDynamicMediaConfig(false, false); NextGenDynamicMediaMediaSource underTest = AdaptTo.notNull(context.request(), NextGenDynamicMediaMediaSource.class); assertFalse(underTest.accepts(SAMPLE_REFERENCE)); + assertFalse(underTest.accepts("/content/dam/sample.jpg")); assertFalse(underTest.accepts("invalid")); assertFalse(underTest.accepts("")); assertFalse(underTest.accepts((String)null)); } @Test - void testAccepts_withNextGenDynamicMediaConfigDisabled() { - registerMockNextGenDynamicMediaConfig(false); + void testAccepts_withNextGenDynamicMediaConfigEnabled() { + registerMockNextGenDynamicMediaConfig(true, true); NextGenDynamicMediaMediaSource underTest = AdaptTo.notNull(context.request(), NextGenDynamicMediaMediaSource.class); - assertFalse(underTest.accepts(SAMPLE_REFERENCE)); + assertTrue(underTest.accepts(SAMPLE_REFERENCE)); + assertTrue(underTest.accepts("/content/dam/sample.jpg")); assertFalse(underTest.accepts("invalid")); assertFalse(underTest.accepts("")); assertFalse(underTest.accepts((String)null)); } @Test - void testAccepts_withNextGenDynamicMediaConfigEnabled() { - registerMockNextGenDynamicMediaConfig(true); + void testAccepts_withNextGenDynamicMediaConfigEnabled_NoLocalAssets() { + registerMockNextGenDynamicMediaConfig(true, false); NextGenDynamicMediaMediaSource underTest = AdaptTo.notNull(context.request(), NextGenDynamicMediaMediaSource.class); assertTrue(underTest.accepts(SAMPLE_REFERENCE)); + assertFalse(underTest.accepts("/content/dam/sample.jpg")); assertFalse(underTest.accepts("invalid")); assertFalse(underTest.accepts("")); assertFalse(underTest.accepts((String)null)); @@ -91,6 +96,7 @@ void testAccepts_withNextGenDynamicMediaConfigEnabled() { @Test @SuppressWarnings("null") void testEnableMediaDrop() { + registerMockNextGenDynamicMediaConfig(false, false); NextGenDynamicMediaMediaSource underTest = AdaptTo.notNull(context.request(), NextGenDynamicMediaMediaSource.class); MediaRequest mediaRequest = new MediaRequest(context.currentResource(), new MediaArgs()); @@ -102,6 +108,8 @@ void testEnableMediaDrop() { @Test @SuppressWarnings("null") void testEnableMediaDrop_Authoring() { + registerMockNextGenDynamicMediaConfig(false, false); + // simulate component context ComponentContext wcmComponentContext = mock(ComponentContext.class); context.request().setAttribute(ComponentContext.CONTEXT_ATTR_NAME, wcmComponentContext); @@ -118,10 +126,12 @@ void testEnableMediaDrop_Authoring() { assertEquals("cq-dd-image", img.getCssClass()); } - void registerMockNextGenDynamicMediaConfig(boolean enabled) { + void registerMockNextGenDynamicMediaConfig(boolean remoteAssets, boolean localAssets) { MockNextGenDynamicMediaConfig nextGenDynamicMediaConfig = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); - nextGenDynamicMediaConfig.setEnabled(enabled); + nextGenDynamicMediaConfig.setEnabled(remoteAssets); nextGenDynamicMediaConfig.setRepositoryId("repo1"); - context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class, + "enabledLocalAssets", localAssets, + "localAssetsRepositoryId", "localrepo1"); } } diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java index 2e4ad3d4..2a58f2a6 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java @@ -19,25 +19,32 @@ */ package io.wcm.handler.mediasource.ngdm; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_APPROVED; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_PROPERTY; import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_ASSET_ID; import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_FILENAME; import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_REFERENCE; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.apache.sling.api.resource.ModifiableValueMap; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import com.day.cq.commons.jcr.JcrConstants; + import io.wcm.handler.media.Asset; import io.wcm.handler.media.Media; import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.MediaHandler; +import io.wcm.handler.media.MediaInvalidReason; import io.wcm.handler.media.MediaNameConstants; import io.wcm.handler.media.Rendition; import io.wcm.handler.media.UriTemplate; @@ -56,25 +63,20 @@ class NextGenDynamicMediaTest { private final AemContext context = AppAemContext.newAemContext(); - private MediaHandler mediaHandler; private Resource resource; @BeforeEach @SuppressWarnings("null") void setUp() { - MockNextGenDynamicMediaConfig nextGenDynamicMediaConfig = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); - nextGenDynamicMediaConfig.setEnabled(true); - nextGenDynamicMediaConfig.setRepositoryId("repo1"); - context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); - resource = context.create().resource(context.currentPage(), "test", MediaNameConstants.PN_MEDIA_REF, SAMPLE_REFERENCE); - - mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); } @Test void testAsset() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + Media media = mediaHandler.get(resource) .build(); assertTrue(media.isValid()); @@ -114,6 +116,9 @@ void testAsset() { @Test void testRendition_16_10() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + Media media = mediaHandler.get(resource) .mediaFormat(DummyMediaFormats.RATIO_16_10) .fixedWidth(2048) @@ -148,6 +153,9 @@ void testRendition_16_10() { @Test void testRendition_16_10_PNG() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + Media media = mediaHandler.get(resource) .mediaFormat(DummyMediaFormats.RATIO_16_10) .enforceOutputFileExtension("png") @@ -163,6 +171,9 @@ void testRendition_16_10_PNG() { @Test void testRendition_FixedDimension() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + Media media = mediaHandler.get(resource) .fixedDimension(100, 50) .build(); @@ -175,6 +186,9 @@ void testRendition_FixedDimension() { @Test void testRendition_FixedMediaFormat() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + Media media = mediaHandler.get(resource) .mediaFormat(DummyMediaFormats.EDITORIAL_1COL) .build(); @@ -187,6 +201,9 @@ void testRendition_FixedMediaFormat() { @Test void testRendition_NonFixedSmallMediaFormat() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + Media media = mediaHandler.get(resource) .mediaFormat(DummyMediaFormats.NONFIXED_SMALL) .build(); @@ -199,6 +216,9 @@ void testRendition_NonFixedSmallMediaFormat() { @Test void testRendition_NonFixedMinWidthHeightMediaFormat() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + Media media = mediaHandler.get(resource) .mediaFormat(DummyMediaFormats.NONFIXED_MINWIDTHHEIGHT) .build(); @@ -212,6 +232,9 @@ void testRendition_NonFixedMinWidthHeightMediaFormat() { @Test @SuppressWarnings("null") void testPDFDownload() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + Resource downloadResource = context.create().resource(context.currentPage(), "download", MediaNameConstants.PN_MEDIA_REF, "/" + SAMPLE_ASSET_ID + "/myfile.pdf"); @@ -223,7 +246,80 @@ void testPDFDownload() { Rendition rendition = media.getRendition(); assertNotNull(rendition); assertEquals(ContentType.PDF, rendition.getMimeType()); - assertEquals("https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/myfile.pdf?accept-experimental=1", rendition.getUrl()); + assertEquals("https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/myfile.pdf", rendition.getUrl()); + } + + @Test + void testImageDownload() { + setupNGDM(false); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + + Media media = mediaHandler.get(resource) + .args(new MediaArgs().download(true)) + .build(); + assertTrue(media.isValid()); + + Rendition rendition = media.getRendition(); + assertNotNull(rendition); + assertEquals(ContentType.JPEG, rendition.getMimeType()); + assertEquals("https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/my-image.jpg", rendition.getUrl()); + } + + @Test + @SuppressWarnings("null") + void testLocalAsset() { + setupNGDM(true); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + + com.day.cq.dam.api.Asset asset = context.create().asset("/content/dam/my-image.jpg", 10, 10, ContentType.JPEG, + ASSET_STATUS_PROPERTY, ASSET_STATUS_APPROVED); + ModifiableValueMap props = AdaptTo.notNull(asset, ModifiableValueMap.class); + props.put(JcrConstants.JCR_UUID, SAMPLE_UUID); + + resource = context.create().resource(context.currentPage(), "local-asset", + MediaNameConstants.PN_MEDIA_REF, asset.getPath()); + + Media media = mediaHandler.get(resource) + .build(); + assertTrue(media.isValid()); + assertUrl(media, "preferwebp=true&quality=85", "jpg"); + } + + @Test + @SuppressWarnings("null") + void testLocalAsset_NotApproved() { + setupNGDM(true); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + + com.day.cq.dam.api.Asset asset = context.create().asset("/content/dam/my-image.jpg", 10, 10, ContentType.JPEG); + ModifiableValueMap props = AdaptTo.notNull(asset, ModifiableValueMap.class); + props.put(JcrConstants.JCR_UUID, SAMPLE_UUID); + + resource = context.create().resource(context.currentPage(), "local-asset", + MediaNameConstants.PN_MEDIA_REF, asset.getPath()); + + Media media = mediaHandler.get(resource) + .build(); + assertFalse(media.isValid()); + assertEquals(MediaInvalidReason.NOT_APPROVED, media.getMediaInvalidReason()); + } + + @Test + @SuppressWarnings("null") + void testLocalAsset_NoUUID() { + setupNGDM(true); + MediaHandler mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + + com.day.cq.dam.api.Asset asset = context.create().asset("/content/dam/my-image.jpg", 10, 10, ContentType.JPEG, + ASSET_STATUS_PROPERTY, ASSET_STATUS_APPROVED); + + resource = context.create().resource(context.currentPage(), "local-asset", + MediaNameConstants.PN_MEDIA_REF, asset.getPath()); + + Media media = mediaHandler.get(resource) + .build(); + assertFalse(media.isValid()); + assertEquals(MediaInvalidReason.MEDIA_REFERENCE_INVALID, media.getMediaInvalidReason()); } private static void assertUrl(Media media, String urlParams, String extension) { @@ -243,7 +339,22 @@ private static void assertUriTemplate(UriTemplate uriTemplate, String urlParams, private static String buildUrl(String urlParams, String extension) { return "https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image." - + extension + "?accept-experimental=1&" + urlParams; + + extension + "?" + urlParams; + } + + private void setupNGDM(boolean localAssets) { + MockNextGenDynamicMediaConfig nextGenDynamicMediaConfig = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); + nextGenDynamicMediaConfig.setEnabled(true); + nextGenDynamicMediaConfig.setRepositoryId("repo1"); + + if (localAssets) { + context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class, + "enabledLocalAssets", true, + "localAssetsRepositoryId", "repo1"); + } + else { + context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + } } } diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaWithMetadataTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaWithMetadataTest.java index 7f38d46a..55377e6b 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaWithMetadataTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaWithMetadataTest.java @@ -149,7 +149,7 @@ void testPDFDownload() { assertNotNull(rendition); assertEquals(ContentType.PDF, rendition.getMimeType()); assertEquals( - "https://" + nextGenDynamicMediaConfig.getRepositoryId() + "/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/myfile.pdf?accept-experimental=1", + "https://" + nextGenDynamicMediaConfig.getRepositoryId() + "/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/myfile.pdf", rendition.getUrl()); } @@ -163,7 +163,7 @@ private void assertUrl(Rendition rendition, String urlParams, String extension) private String buildUrl(String urlParams, String extension) { return "https://" + nextGenDynamicMediaConfig.getRepositoryId() + "/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image." - + extension + "?accept-experimental=1&" + urlParams; + + extension + "?" + urlParams; } } diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/MediaArgsDimensionTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/MediaArgsDimensionTest.java index 3469ac05..e98fd8ce 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/MediaArgsDimensionTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/MediaArgsDimensionTest.java @@ -69,6 +69,22 @@ void testGetRequestedRatioAsWidthHeight() { assertNull(getRequestedRatioAsWidthHeight(new MediaArgs())); } + @Test + @SuppressWarnings("null") + void testGetRequestedRatioAsWidthHeight_Extrapolation() { + Dimension dimension = getRequestedRatioAsWidthHeight(mediaFormat(mf -> mf.ratio(16.125d, 9.2d))); + assertEquals(16125, dimension.getWidth()); + assertEquals(9200, dimension.getHeight()); + } + + @Test + @SuppressWarnings("null") + void testGetRequestedRatioAsWidthHeight_ExtrapolationNotRequired() { + Dimension dimension = getRequestedRatioAsWidthHeight(mediaFormat(mf -> mf.ratio(16.0000001d, 9.00000005d))); + assertEquals(16, dimension.getWidth()); + assertEquals(9, dimension.getHeight()); + } + @Test void testGetFirstMediaFormat() { MediaFormat mf1 = MediaFormatBuilder.create("mf1").build(); diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilderTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilderTest.java index ee17b7df..7e74313c 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilderTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilderTest.java @@ -45,7 +45,7 @@ class NextGenDynamicMediaBinaryUrlBuilderTest { private MimeTypeService mimeTypeService; @BeforeEach - void setUp() throws Exception { + void setUp() { context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class) .setRepositoryId("repo1"); nextGenDynamicMediaConfig = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); @@ -58,8 +58,17 @@ void setUp() throws Exception { void testBuild() { NextGenDynamicMediaBinaryUrlBuilder underTest = getBuilder(new MediaArgs()); - assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/original/as/my-image.jpg?accept-experimental=1", - underTest.build()); + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/original/as/my-image.jpg", + underTest.build(false)); + } + + + @Test + void testBuild_ContentDispositionAttachment() { + NextGenDynamicMediaBinaryUrlBuilder underTest = getBuilder(new MediaArgs()); + + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/original/as/my-image.jpg?attachment=true", + underTest.build(true)); } @SuppressWarnings("null") diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImplTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImplTest.java index 8ed82bbf..8881628b 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImplTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImplTest.java @@ -20,9 +20,10 @@ package io.wcm.handler.mediasource.ngdm.impl; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,31 +37,21 @@ class NextGenDynamicMediaConfigServiceImplTest { private final AemContext context = AppAemContext.newAemContext(); - @BeforeEach - void setUp() { - MockNextGenDynamicMediaConfig config = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); - config.setEnabled(true); - config.setAssetSelectorsJsUrl("/selector1"); - config.setImageDeliveryBasePath("/imagepath1"); - config.setVideoDeliveryPath("/videopath1"); - config.setAssetOriginalBinaryDeliveryPath("/assetpath1"); - config.setAssetMetadataPath("/metadatapath1"); - config.setRepositoryId("repo1"); - config.setApiKey("key1"); - config.setEnv("env1"); - config.setImsClient("client1"); - } - @Test void testPropertiesDefaultConfig() { - NextGenDynamicMediaConfigService underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); - assertTrue(underTest.enabled()); + registerNextGenDynamicMediaConfig(context); + NextGenDynamicMediaConfigService underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class, + "enabledLocalAssets", true, + "localAssetsRepositoryId", "localrepo1"); + assertTrue(underTest.isEnabledRemoteAssets()); + assertTrue(underTest.isEnabledLocalAssets()); assertEquals("/selector1", underTest.getAssetSelectorsJsUrl()); - assertEquals("/adobe/assets/{asset-id}/as/{seo-name}.{format}?accept-experimental=1", underTest.getImageDeliveryBasePath()); + assertEquals("/adobe/assets/{asset-id}/as/{seo-name}.{format}", underTest.getImageDeliveryBasePath()); assertEquals("/videopath1", underTest.getVideoDeliveryPath()); - assertEquals("/adobe/assets/{asset-id}/original/as/{seo-name}?accept-experimental=1", underTest.getAssetOriginalBinaryDeliveryPath()); + assertEquals("/adobe/assets/{asset-id}/original/as/{seo-name}", underTest.getAssetOriginalBinaryDeliveryPath()); assertEquals("/adobe/assets/{asset-id}/metadata", underTest.getAssetMetadataPath()); - assertEquals("repo1", underTest.getRepositoryId()); + assertEquals("repo1", underTest.getRemoteAssetsRepositoryId()); + assertEquals("localrepo1", underTest.getLocalAssetsRepositoryId()); assertEquals("key1", underTest.getApiKey()); assertEquals("env1", underTest.getEnv()); assertEquals("client1", underTest.getImsClient()); @@ -68,20 +59,56 @@ void testPropertiesDefaultConfig() { @Test void testPropertiesEmptyConfig() { + registerNextGenDynamicMediaConfig(context); NextGenDynamicMediaConfigService underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class, + "enabledRemoteAssets", false, + "enabledLocalAssets", false, + "localAssetsRepositoryId", "", "imageDeliveryBasePath", "", "assetOriginalBinaryDeliveryPath", "", "assetMetadataPath", ""); - assertTrue(underTest.enabled()); + assertFalse(underTest.isEnabledRemoteAssets()); + assertFalse(underTest.isEnabledLocalAssets()); assertEquals("/selector1", underTest.getAssetSelectorsJsUrl()); assertEquals("/imagepath1", underTest.getImageDeliveryBasePath()); assertEquals("/videopath1", underTest.getVideoDeliveryPath()); assertEquals("/assetpath1", underTest.getAssetOriginalBinaryDeliveryPath()); assertEquals("/metadatapath1", underTest.getAssetMetadataPath()); - assertEquals("repo1", underTest.getRepositoryId()); + assertEquals("repo1", underTest.getRemoteAssetsRepositoryId()); + assertEquals("", underTest.getLocalAssetsRepositoryId()); assertEquals("key1", underTest.getApiKey()); assertEquals("env1", underTest.getEnv()); assertEquals("client1", underTest.getImsClient()); } + @Test + void testNoNextGenDynamicMediaConfig() { + NextGenDynamicMediaConfigService underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + assertFalse(underTest.isEnabledRemoteAssets()); + assertFalse(underTest.isEnabledLocalAssets()); + assertNull(underTest.getAssetSelectorsJsUrl()); + assertEquals("/adobe/assets/{asset-id}/as/{seo-name}.{format}", underTest.getImageDeliveryBasePath()); + assertNull(underTest.getVideoDeliveryPath()); + assertEquals("/adobe/assets/{asset-id}/original/as/{seo-name}", underTest.getAssetOriginalBinaryDeliveryPath()); + assertEquals("/adobe/assets/{asset-id}/metadata", underTest.getAssetMetadataPath()); + assertNull(underTest.getRemoteAssetsRepositoryId()); + assertNull(underTest.getApiKey()); + assertNull(underTest.getEnv()); + assertNull(underTest.getImsClient()); + } + + private static void registerNextGenDynamicMediaConfig(AemContext context) { + MockNextGenDynamicMediaConfig config = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); + config.setEnabled(true); + config.setAssetSelectorsJsUrl("/selector1"); + config.setImageDeliveryBasePath("/imagepath1"); + config.setVideoDeliveryPath("/videopath1"); + config.setAssetOriginalBinaryDeliveryPath("/assetpath1"); + config.setAssetMetadataPath("/metadatapath1"); + config.setRepositoryId("repo1"); + config.setApiKey("key1"); + config.setEnv("env1"); + config.setImsClient("client1"); + } + } diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilderTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilderTest.java index 595c3a16..c8dd655c 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilderTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilderTest.java @@ -20,9 +20,12 @@ package io.wcm.handler.mediasource.ngdm.impl; import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_REFERENCE; +import static io.wcm.handler.mediasource.ngdm.impl.metadata.MetadataSample.METADATA_JSON_IMAGE; import static org.junit.jupiter.api.Assertions.assertEquals; import org.apache.sling.commons.mime.MimeTypeService; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,6 +34,7 @@ import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.spi.MediaHandlerConfig; import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadata; import io.wcm.sling.commons.adapter.AdaptTo; import io.wcm.testing.mock.aem.dam.ngdm.MockNextGenDynamicMediaConfig; import io.wcm.testing.mock.aem.junit5.AemContext; @@ -46,7 +50,7 @@ class NextGenDynamicMediaImageUrlBuilderTest { private MimeTypeService mimeTypeService; @BeforeEach - void setUp() throws Exception { + void setUp() { context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class) .setRepositoryId("repo1"); nextGenDynamicMediaConfig = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); @@ -61,17 +65,17 @@ void testDefaultParams() { NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams(); assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.jpg" - + "?accept-experimental=1&preferwebp=true", + + "?preferwebp=true", underTest.build(params)); } @Test void testForceOutputExtension() { - NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(new MediaArgs().enforceOutputFileExtension("png")); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(new MediaArgs().enforceOutputFileExtension("png"), null); NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams(); assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.png" - + "?accept-experimental=1&preferwebp=true", + + "?preferwebp=true", underTest.build(params)); } @@ -85,7 +89,47 @@ void testAllParams() { .quality(60); assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.jpg" - + "?accept-experimental=1&crop=16%3A9%2Csmart&preferwebp=true&quality=60&rotate=90&width=100", + + "?crop=16%3A9%2Csmart&preferwebp=true&quality=60&rotate=90&width=100", + underTest.build(params)); + } + + @Test + void testAllParams_NamedSmartCrop() throws Exception { + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(METADATA_JSON_IMAGE); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(new MediaArgs(), metadata); + NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams() + .width(100L) + .cropSmartRatio(new Dimension(16, 9)) + .quality(60); + + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.jpg" + + "?preferwebp=true&quality=60&smartcrop=Landscape&width=100", + underTest.build(params)); + } + + @Test + void testAllParams_NamedSmartCrop_DefaultWidth() throws Exception { + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(METADATA_JSON_IMAGE); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(new MediaArgs(), metadata); + NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams() + .cropSmartRatio(new Dimension(16, 9)) + .quality(60); + + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.jpg" + + "?preferwebp=true&quality=60&smartcrop=Landscape&width=2048", + underTest.build(params)); + } + + @Test + void testAllParams_NamedSmartCrop_DefaultHeight() throws Exception { + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(METADATA_JSON_IMAGE); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(new MediaArgs(), metadata); + NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams() + .cropSmartRatio(new Dimension(1, 2)) + .quality(60); + + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.jpg" + + "?height=2048&preferwebp=true&quality=60&smartcrop=Portrait", underTest.build(params)); } @@ -116,19 +160,20 @@ void testWidthPlaceholder() { .quality(60); assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.jpg" - + "?accept-experimental=1&preferwebp=true&quality=60&width={w}", + + "?preferwebp=true&quality=60&width={w}", underTest.build(params)); } private NextGenDynamicMediaImageUrlBuilder getBuilder() { - return getBuilder(new MediaArgs()); + return getBuilder(new MediaArgs(), null); } @SuppressWarnings("null") - private NextGenDynamicMediaImageUrlBuilder getBuilder(MediaArgs mediaArgs) { + private NextGenDynamicMediaImageUrlBuilder getBuilder(@NotNull MediaArgs mediaArgs, + @Nullable NextGenDynamicMediaMetadata metadata) { NextGenDynamicMediaContext ctx = new NextGenDynamicMediaContext( NextGenDynamicMediaReference.fromReference(SAMPLE_REFERENCE), - null, + metadata, null, mediaArgs, nextGenDynamicMediaConfig, diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReferenceSample.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReferenceSample.java index e739242b..11be01b5 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReferenceSample.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReferenceSample.java @@ -24,7 +24,8 @@ */ public final class NextGenDynamicMediaReferenceSample { - public static final String SAMPLE_ASSET_ID = "urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678"; + public static final String SAMPLE_UUID = "12345678-abcd-abcd-abcd-abcd12345678"; + public static final String SAMPLE_ASSET_ID = "urn:aaid:aem:" + SAMPLE_UUID; public static final String SAMPLE_FILENAME = "my-image.jpg"; public static final String SAMPLE_REFERENCE = "/" + SAMPLE_ASSET_ID + "/" + SAMPLE_FILENAME; diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReferenceTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReferenceTest.java index 68faf849..366c4a29 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReferenceTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaReferenceTest.java @@ -19,9 +19,12 @@ */ package io.wcm.handler.mediasource.ngdm.impl; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_APPROVED; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_PROPERTY; import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_ASSET_ID; import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_FILENAME; import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_REFERENCE; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -29,10 +32,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.apache.sling.api.resource.ModifiableValueMap; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import com.day.cq.commons.jcr.JcrConstants; +import com.day.cq.dam.api.Asset; + +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.sling.commons.adapter.AdaptTo; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; +import io.wcm.wcm.commons.contenttype.ContentType; + +@ExtendWith(AemContextExtension.class) class NextGenDynamicMediaReferenceTest { + private final AemContext context = AppAemContext.newAemContext(); + @Test void testFromReference() { NextGenDynamicMediaReference underTest = NextGenDynamicMediaReference.fromReference(SAMPLE_REFERENCE); @@ -40,6 +57,7 @@ void testFromReference() { assertEquals(SAMPLE_ASSET_ID, underTest.getAssetId()); assertEquals(SAMPLE_FILENAME, underTest.getFileName()); assertEquals(SAMPLE_REFERENCE, underTest.toReference()); + assertNull(underTest.getAsset()); assertEquals(SAMPLE_REFERENCE, underTest.toString()); } @@ -66,4 +84,42 @@ void testNewInstance_IllegalArgument() { () -> new NextGenDynamicMediaReference("wurstbrot", SAMPLE_FILENAME)); } + @Test + void testFromDamAssetReference_Invalid() { + assertNull(NextGenDynamicMediaReference.fromDamAssetReference(null, context.resourceResolver())); + assertNull(NextGenDynamicMediaReference.fromDamAssetReference("/invalid/path", context.resourceResolver())); + assertNull(NextGenDynamicMediaReference.fromDamAssetReference(context.create().resource("/content/no-asset").getPath(), + context.resourceResolver())); + } + + @Test + void testFromDamAssetReference_AssetWithoutUUID_Approved() { + Asset asset = context.create().asset("/content/dam/asset1.jpg", 10, 10, ContentType.JPEG, + ASSET_STATUS_PROPERTY, ASSET_STATUS_APPROVED); + assertNull(NextGenDynamicMediaReference.fromDamAssetReference(asset.getPath(), context.resourceResolver())); + } + + @Test + void testFromDamAssetReference_AssetWithUUID_NotApproved() { + Asset asset = context.create().asset("/content/dam/" + SAMPLE_FILENAME, 10, 10, ContentType.JPEG); + assertNull(NextGenDynamicMediaReference.fromDamAssetReference(asset.getPath(), context.resourceResolver())); + } + + @Test + void testFromDamAssetReference_AssetWithUUID_Approved() { + Asset asset = context.create().asset("/content/dam/" + SAMPLE_FILENAME, 10, 10, ContentType.JPEG, + ASSET_STATUS_PROPERTY, ASSET_STATUS_APPROVED); + ModifiableValueMap props = AdaptTo.notNull(asset, ModifiableValueMap.class); + props.put(JcrConstants.JCR_UUID, SAMPLE_UUID); + + NextGenDynamicMediaReference underTest = NextGenDynamicMediaReference.fromDamAssetReference(asset.getPath(), + context.resourceResolver()); + assertNotNull(underTest); + assertEquals(SAMPLE_ASSET_ID, underTest.getAssetId()); + assertEquals(SAMPLE_FILENAME, underTest.getFileName()); + assertEquals(SAMPLE_REFERENCE, underTest.toReference()); + assertEquals(asset, underTest.getAsset()); + assertEquals(SAMPLE_REFERENCE, underTest.toString()); + } + } diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataSample.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataSample.java index 58bc8c82..40f291ef 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataSample.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataSample.java @@ -21,16 +21,48 @@ import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_ASSET_ID; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Next Gen Dynamic Media Metadata samples. */ public final class MetadataSample { + @SuppressFBWarnings("HSC_HUGE_SHARED_STRING_CONSTANT") public static final String METADATA_JSON_IMAGE = "{" + " \"assetId\": \"" + SAMPLE_ASSET_ID + "\"," + " \"repositoryMetadata\": {" + " \"repo:name\": \"test.jpg\"," - + " \"dc:format\": \"image/jpeg\"" + + " \"dc:format\": \"image/jpeg\"," + + " \"smartcrops\": {" + + " \"Landscape\": {" + + " \"height\": \"180\"," + + " \"left\": \"0.0\"," + + " \"manualCrop\": \"true\"," + + " \"normalizedHeight\": \"0.84375\"," + + " \"normalizedWidth\": \"1.0\"," + + " \"width\": \"320\"," + + " \"top\": \"0.5774\"" + + " }," + + " \"Portrait\": {" + + " \"height\": \"200\"," + + " \"left\": \"0.16792180740265983\"," + + " \"manualCrop\": \"false\"," + + " \"normalizedHeight\": \"0.9980170652565797\"," + + " \"normalizedWidth\": \"0.3326723533333333\"," + + " \"width\": \"100\"," + + " \"top\": \"0.0\"" + + " }," + + " \"Invalid\": {" + + " \"height\": \"200\"," + + " \"left\": \"-0.16792180740265983\"," + + " \"manualCrop\": \"false\"," + + " \"normalizedHeight\": \"-0.9980170652565797\"," + + " \"normalizedWidth\": \"-0.6666399615446242\"," + + " \"width\": \"100\"," + + " \"top\": \"0.0\"" + + " }" + + " }" + " }," + " \"assetMetadata\": {" + " \"dam:assetStatus\": \"approved\"," diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImplTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImplTest.java index eb12ac92..1ca2832b 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImplTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImplTest.java @@ -56,7 +56,7 @@ class NextGenDynamicMediaMetadataServiceImplTest { private final AemContext context = AppAemContext.newAemContext(); @BeforeEach - void setUp(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + void setUp(WireMockRuntimeInfo wmRuntimeInfo) { context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class) .setRepositoryId("localhost:" + wmRuntimeInfo.getHttpPort()); context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataTest.java index b49ff379..c1126bbf 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataTest.java @@ -19,8 +19,19 @@ */ package io.wcm.handler.mediasource.ngdm.impl.metadata; +import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_APPROVED; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_PENDING; +import static com.day.cq.dam.api.DamConstants.ASSET_STATUS_PROPERTY; +import static com.day.cq.dam.api.DamConstants.RENDITIONS_FOLDER; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_LEFT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_HEIGHT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_WIDTH; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_TOP; import static io.wcm.handler.mediasource.ngdm.impl.metadata.MetadataSample.METADATA_JSON_IMAGE; import static io.wcm.handler.mediasource.ngdm.impl.metadata.MetadataSample.METADATA_JSON_PDF; +import static io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadata.RT_RENDITION_SMARTCROP; +import static org.apache.sling.api.resource.ResourceResolver.PROPERTY_RESOURCE_TYPE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -28,43 +39,62 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Map; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import com.day.cq.dam.api.Asset; import com.fasterxml.jackson.core.JsonProcessingException; import io.wcm.handler.media.Dimension; +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; import io.wcm.wcm.commons.contenttype.ContentType; +@ExtendWith(AemContextExtension.class) class NextGenDynamicMediaMetadataTest { + private final AemContext context = AppAemContext.newAemContext(); + @Test void testEmptyJson() throws JsonProcessingException { NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson("{}"); assertEquals(ContentType.OCTET_STREAM, metadata.getMimeType()); assertNull(metadata.getDimension()); + assertNull(metadata.getAssetStatus()); + assertTrue(metadata.getSmartCrops().isEmpty()); assertFalse(metadata.isValid()); - assertEquals("[dimension=,mimeType=]", metadata.toString()); + assertEquals("[assetStatus=,dimension=,mimeType=,smartCrops=[]]", metadata.toString()); } @Test void testSampleJson_Image() throws JsonProcessingException { NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(METADATA_JSON_IMAGE); - assertEquals("image/jpeg", metadata.getMimeType()); + assertEquals(ContentType.JPEG, metadata.getMimeType()); Dimension dimension = metadata.getDimension(); assertNotNull(dimension); assertEquals(1200, dimension.getWidth()); assertEquals(800, dimension.getHeight()); + assertEquals(ASSET_STATUS_APPROVED, metadata.getAssetStatus()); + assertEquals(2, metadata.getSmartCrops().size()); assertTrue(metadata.isValid()); - assertEquals("[dimension=[width=1200,height=800],mimeType=image/jpeg]", metadata.toString()); + assertEquals("[assetStatus=approved,dimension=[width=1200,height=800],mimeType=image/jpeg,smartCrops=[" + + "[cropDimension=[left=0,top=462,width=1200,height=675],name=Landscape,ratio=1.7777777777777777], " + + "[cropDimension=[left=202,top=0,width=399,height=798],name=Portrait,ratio=0.5]" + + "]]", metadata.toString()); } @Test void testSampleJson_PDF() throws JsonProcessingException { NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(METADATA_JSON_PDF); - assertEquals("application/pdf", metadata.getMimeType()); + assertEquals(ContentType.PDF, metadata.getMimeType()); assertNull(metadata.getDimension()); + assertEquals(ASSET_STATUS_APPROVED, metadata.getAssetStatus()); + assertTrue(metadata.getSmartCrops().isEmpty()); assertTrue(metadata.isValid()); - assertEquals("[dimension=,mimeType=application/pdf]", metadata.toString()); + assertEquals("[assetStatus=approved,dimension=,mimeType=application/pdf,smartCrops=[]]", metadata.toString()); } @Test @@ -72,4 +102,61 @@ void testInvalidJson() { assertThrows(JsonProcessingException.class, () -> NextGenDynamicMediaMetadata.fromJson("no json")); } + @Test + void testFromAsset() { + Asset asset = context.create().asset("/content/dam/sample.jpg", 1200, 800, ContentType.JPEG, + ASSET_STATUS_PROPERTY, ASSET_STATUS_PENDING); + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromAsset(asset); + + assertEquals(ContentType.JPEG, metadata.getMimeType()); + Dimension dimension = metadata.getDimension(); + assertNotNull(dimension); + assertEquals(1200, dimension.getWidth()); + assertEquals(800, dimension.getHeight()); + assertEquals(ASSET_STATUS_PENDING, metadata.getAssetStatus()); + assertTrue(metadata.getSmartCrops().isEmpty()); + assertTrue(metadata.isValid()); + assertEquals("[assetStatus=pending,dimension=[width=1200,height=800],mimeType=image/jpeg,smartCrops=[]]", metadata.toString()); + } + + @Test + void testFromAsset_SmartCrops() { + Asset asset = context.create().asset("/content/dam/sample.jpg", 1200, 800, ContentType.JPEG, + ASSET_STATUS_PROPERTY, ASSET_STATUS_APPROVED); + + context.create().resource(asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + "/Landscape", + PROPERTY_RESOURCE_TYPE, RT_RENDITION_SMARTCROP, + JCR_CONTENT, Map.of( + PN_LEFT, 0d, + PN_TOP, 0.5774d, + PN_NORMALIZED_WIDTH, 1.0d, + PN_NORMALIZED_HEIGHT, 0.84375d)); + + context.create().resource(asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + "/Portrait", + PROPERTY_RESOURCE_TYPE, RT_RENDITION_SMARTCROP, + JCR_CONTENT, Map.of( + PN_LEFT, 0.16792180740265983d, + PN_TOP, 0.0d, + PN_NORMALIZED_WIDTH, 0.3326723533333333d, + PN_NORMALIZED_HEIGHT, 0.9980170652565797d)); + + context.create().resource(asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + "/Invalid", + PROPERTY_RESOURCE_TYPE, RT_RENDITION_SMARTCROP); + + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromAsset(asset); + + assertEquals(ContentType.JPEG, metadata.getMimeType()); + Dimension dimension = metadata.getDimension(); + assertNotNull(dimension); + assertEquals(1200, dimension.getWidth()); + assertEquals(800, dimension.getHeight()); + assertEquals(ASSET_STATUS_APPROVED, metadata.getAssetStatus()); + assertEquals(2, metadata.getSmartCrops().size()); + assertTrue(metadata.isValid()); + assertEquals("[assetStatus=approved,dimension=[width=1200,height=800],mimeType=image/jpeg,smartCrops=[" + + "[cropDimension=[left=0,top=462,width=1200,height=675],name=Landscape,ratio=1.7777777777777777], " + + "[cropDimension=[left=202,top=0,width=399,height=798],name=Portrait,ratio=0.5]" + + "]]", metadata.toString()); + } + } diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilderTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilderTest.java index 63bb31ee..ff338b49 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilderTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilderTest.java @@ -43,7 +43,7 @@ class NextGenDynamicMediaMetadataUrlBuilderTest { private NextGenDynamicMediaConfigService nextGenDynamicMediaConfig; @BeforeEach - void setUp() throws Exception { + void setUp() { context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class) .setRepositoryId("repo1"); nextGenDynamicMediaConfig = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class);