From 674f2bf5f01d3df9b0327c61a0768bb56c7edf8e Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Fri, 26 Jan 2024 20:11:53 +0100 Subject: [PATCH 01/13] [gitflow-maven-plugin] Update for next development version 2.0.1-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c3d5ba36..7590f46d 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ io.wcm io.wcm.handler.media - 2.0.0 + 2.0.1-SNAPSHOT jar Media Handler @@ -49,7 +49,7 @@ handler/media - 2024-01-26T19:10:03Z + 2024-01-26T19:11:51Z From cbec2a19fcb2326227a6cd3608cef86f965188d8 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 31 Jan 2024 14:05:11 +0100 Subject: [PATCH 02/13] Next Generation Dynamic Media: Support non-image assets and SVG assets. (#44) --- changes.xml | 6 + .../ngdm/NextGenDynamicMediaAsset.java | 3 - .../ngdm/NextGenDynamicMediaConfigModel.java | 104 +++++++++ .../ngdm/NextGenDynamicMediaMediaSource.java | 10 +- .../ngdm/NextGenDynamicMediaRendition.java | 29 ++- .../ngdm/NextGenDynamicMediaUriTemplate.java | 8 +- .../NextGenDynamicMediaBinaryUrlBuilder.java | 73 ++++++ .../NextGenDynamicMediaConfigService.java | 63 ++++++ .../NextGenDynamicMediaConfigServiceImpl.java | 50 ++++- ...> NextGenDynamicMediaImageUrlBuilder.java} | 4 +- .../mediasource/ngdm/package-info.java | 2 +- .../clientlibs/authoring/dialog/css.txt | 1 + .../dialog/css/nextGenDynamicMedia.less | 5 + .../clientlibs/authoring/dialog/js.txt | 1 + .../authoring/dialog/js/fileupload.js | 8 + .../dialog/js/nextGenDynamicMedia.js | 212 ++++++++++++++++++ .../authoring/dialog/js/pathfield.js | 5 + .../granite/form/fileupload/fileupload.jsp | 7 + .../granite/form/pathfield/pathfield.jsp | 7 + ...leTypesEnd2EndNextGenDynamicMediaTest.java | 54 +++++ .../NextGenDynamicMediaConfigModelTest.java | 75 +++++++ .../ngdm/NextGenDynamicMediaTest.java | 17 ++ ...xtGenDynamicMediaBinaryUrlBuilderTest.java | 77 +++++++ ...tGenDynamicMediaConfigServiceImplTest.java | 71 ++++++ ...xtGenDynamicMediaImageUrlBuilderTest.java} | 16 +- 25 files changed, 888 insertions(+), 20 deletions(-) create mode 100644 src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java create mode 100644 src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilder.java rename src/main/java/io/wcm/handler/mediasource/ngdm/impl/{NextGenDynamicMediaUrlBuilder.java => NextGenDynamicMediaImageUrlBuilder.java} (97%) create mode 100644 src/main/webapp/app-root/clientlibs/authoring/dialog/css/nextGenDynamicMedia.less create mode 100644 src/main/webapp/app-root/clientlibs/authoring/dialog/js/nextGenDynamicMedia.js create mode 100644 src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java create mode 100644 src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilderTest.java create mode 100644 src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImplTest.java rename src/test/java/io/wcm/handler/mediasource/ngdm/impl/{NextGenDynamicMediaUrlBuilderTest.java => NextGenDynamicMediaImageUrlBuilderTest.java} (87%) diff --git a/changes.xml b/changes.xml index 03006974..04a7ebbf 100644 --- a/changes.xml +++ b/changes.xml @@ -23,6 +23,12 @@ xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd"> + + + Next Generation Dynamic Media: Support non-image assets and SVG assets. + + + Migrate from wcm.io Handler 1.x to 2.x for details. 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 4a030663..4330065b 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaAsset.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaAsset.java @@ -110,9 +110,6 @@ final class NextGenDynamicMediaAsset implements Asset { @Override public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type) { - if (type == UriTemplateType.SCALE_HEIGHT) { - throw new IllegalArgumentException("URI template type not supported: " + type); - } return new NextGenDynamicMediaUriTemplate(context, type); } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java new file mode 100644 index 00000000..3b91bc13 --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java @@ -0,0 +1,104 @@ +/* + * #%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; + +import java.util.Map; +import java.util.TreeMap; + +import javax.annotation.PostConstruct; + +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; +import org.osgi.annotation.versioning.ProviderType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.json.JsonMapper; + +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService; + +/** + * Prepares Next Generation Dynamic Media configuration for GraniteUI components (fileupload, pathfield). + */ +@Model(adaptables = SlingHttpServletRequest.class) +@ProviderType +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) + private NextGenDynamicMediaConfigService config; + + private boolean enabled; + private String assetSelectorsJsUrl; + private String configJson; + + @PostConstruct + private void activate() { + if (config != null) { + enabled = config.enabled(); + assetSelectorsJsUrl = config.getAssetSelectorsJsUrl(); + configJson = buildConfigJsonString(config); + } + } + + private static String buildConfigJsonString(@NotNull NextGenDynamicMediaConfigService config) { + Map map = new TreeMap<>(); + map.put("repositoryId", config.getRepositoryId()); + map.put("apiKey", config.getApiKey()); + map.put("env", config.getEnv()); + map.put("imsClient", config.getImsClient()); + try { + return MAPPER.writeValueAsString(map); + } + catch (JsonProcessingException ex) { + log.warn("Unable to serialize Next Generation Dynamic Media config to JSON.", ex); + return "{}"; + } + } + + /** + * @return true if Next Generation Dynamic Media is available and enabled. + */ + public boolean isEnabled() { + return this.enabled; + } + + /** + * @return Asset Selectors URL + */ + public @Nullable String getAssetSelectorsJsUrl() { + return this.assetSelectorsJsUrl; + } + + /** + * @return JSON string with configuration data required on the client-side. + */ + public @Nullable String getConfigJson() { + return this.configJson; + } + +} 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 b336bd15..7e812dfc 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSource.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSource.java @@ -31,6 +31,8 @@ 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.wcm.api.WCMMode; import com.day.cq.wcm.api.components.ComponentContext; @@ -77,6 +79,8 @@ public final class NextGenDynamicMediaMediaSource extends MediaSource { @AemObject(injectionStrategy = InjectionStrategy.OPTIONAL) private ComponentContext componentContext; + private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaMediaSource.class); + @Override public @NotNull String getId() { return ID; @@ -88,7 +92,11 @@ public boolean accepts(@Nullable String mediaRef) { } private boolean isNextGenDynamicMediaEnabled() { - return nextGenDynamicMediaConfig != null && nextGenDynamicMediaConfig.enabled(); + 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(); } @Override 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 3fed7560..f7e89c19 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaRendition.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaRendition.java @@ -37,10 +37,11 @@ import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.mediasource.ngdm.impl.ImageQualityPercentage; import io.wcm.handler.mediasource.ngdm.impl.MediaArgsDimension; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaBinaryUrlBuilder; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaContext; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaImageDeliveryParams; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaImageUrlBuilder; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference; -import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaUrlBuilder; /** * {@link Rendition} implementation for Next Gen. Dynamic Media remote assets. @@ -71,6 +72,20 @@ final class NextGenDynamicMediaRendition implements Rendition { } } + if (isVectorImage() || !isImage()) { + // deliver as binary + this.url = buildBinaryUrl(); + } + else { + // deliver scaled image rendition + this.url = buildImageRenditionUrl(); + } + } + + /** + * Build image rendition URL which is dynamically scaled and/or cropped. + */ + private String buildImageRenditionUrl() { // calculate height if (this.width > 0) { double ratio = MediaArgsDimension.getRequestedRatio(mediaArgs); @@ -90,7 +105,14 @@ final class NextGenDynamicMediaRendition implements Rendition { params.cropSmartRatio(ratioDimension); } - this.url = new NextGenDynamicMediaUrlBuilder(context).build(params); + return new NextGenDynamicMediaImageUrlBuilder(context).build(params); + } + + /** + * Build URL which points directly to the binary file. + */ + private String buildBinaryUrl() { + return new NextGenDynamicMediaBinaryUrlBuilder(context).build(); } @Override @@ -183,6 +205,9 @@ public boolean isFallback() { @Override public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type) { + if (!isImage() || isVectorImage()) { + throw new UnsupportedOperationException("Unable to build URI template for " + reference.toReference()); + } return new NextGenDynamicMediaUriTemplate(context, type); } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaUriTemplate.java b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaUriTemplate.java index 1b1669b9..a0e32bc0 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaUriTemplate.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaUriTemplate.java @@ -29,7 +29,7 @@ import io.wcm.handler.mediasource.ngdm.impl.MediaArgsDimension; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaContext; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaImageDeliveryParams; -import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaUrlBuilder; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaImageUrlBuilder; /** * {@link UriTemplate} implementation for Next Gen. Dynamic Media remote assets. @@ -43,6 +43,10 @@ final class NextGenDynamicMediaUriTemplate implements UriTemplate { @NotNull UriTemplateType type) { this.type = type; + if (type == UriTemplateType.SCALE_HEIGHT) { + throw new IllegalArgumentException("URI template type not supported: " + type); + } + NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams() .widthPlaceholder(MediaNameConstants.URI_TEMPLATE_PLACEHOLDER_WIDTH) .rotation(context.getMedia().getRotation()) @@ -53,7 +57,7 @@ final class NextGenDynamicMediaUriTemplate implements UriTemplate { params.cropSmartRatio(ratio); } - this.uriTemplate = new NextGenDynamicMediaUrlBuilder(context).build(params); + this.uriTemplate = new NextGenDynamicMediaImageUrlBuilder(context).build(params); } @Override 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 new file mode 100644 index 00000000..3686f567 --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilder.java @@ -0,0 +1,73 @@ +/* + * #%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; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Builds URL to reference a binary file via NextGen Dynamic Media. + *

+ * Example URL that might be build: + * https://host/adobe/assets/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-file.pdf + *

+ */ +public final class NextGenDynamicMediaBinaryUrlBuilder { + + static final String PLACEHOLDER_ASSET_ID = "{asset-id}"; + static final String PLACEHOLDER_SEO_NAME = "{seo-name}"; + + private final NextGenDynamicMediaContext context; + + /** + * @param context Context + */ + public NextGenDynamicMediaBinaryUrlBuilder(@NotNull NextGenDynamicMediaContext context) { + this.context = context; + } + + /** + * Builds the URL for a rendition. + * @return URL or null if invalid/not possible + */ + public @Nullable String build() { + + // get parameters from nextgen dynamic media config for URL parameters + String repositoryId = context.getNextGenDynamicMediaConfig().getRepositoryId(); + String binaryDeliveryPath = context.getNextGenDynamicMediaConfig().getAssetOriginalBinaryDeliveryPath(); + if (StringUtils.isAnyEmpty(repositoryId, binaryDeliveryPath)) { + return null; + } + + // replace placeholders in image delivery path + String seoName = context.getReference().getFileName(); + binaryDeliveryPath = StringUtils.replace(binaryDeliveryPath, PLACEHOLDER_ASSET_ID, context.getReference().getAssetId()); + binaryDeliveryPath = StringUtils.replace(binaryDeliveryPath, PLACEHOLDER_SEO_NAME, seoName); + + // build URL + StringBuilder url = new StringBuilder(); + url.append("https://") + .append(repositoryId) + .append(binaryDeliveryPath); + 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 180e0724..e53fc094 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 @@ -30,6 +30,12 @@ public interface NextGenDynamicMediaConfigService { */ boolean enabled(); + /** + * 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 + */ + String getAssetSelectorsJsUrl(); + /** * Gets the path expression for the image delivery path. The following placeholders with the below meaning are * contained within that path: @@ -45,10 +51,67 @@ public interface NextGenDynamicMediaConfigService { */ String getImageDeliveryBasePath(); + /** + * Gets the path expression for the adaptive video manifest/player path. The + * following placeholders with the below meaning are contained + * within that path: + *
    + *
  • {asset-id} - the uuid of the asset in the format 'urn:aaid:aem:UUID' + * along with optional format + * e.g. urn:aaid:aem:1a034bee-ebda-4787-bad3-f924d0772b75 OR + * urn:aaid:aem:1a034bee-ebda-4787-bad3-f924d0772b75.mp4
  • + *
+ * @return the path expression for the video delivery path + */ + String getVideoDeliveryPath(); + + /** + * Gets the path expression for the the Original Asset Delivery which delivers + * the bitstream as-is + *
    + *
  • {asset-id} - the uuid of the asset in the format 'urn:aaid:aem:UUID', + * e.g. urn:aaid:aem:1a034bee-ebda-4787-bad3-f924d0772b75
  • + *
  • {seo-name} - any url-encoded or alphanumeric, non-whitespace set of + * characters. may contain hyphens and dots
  • + *
+ * @return the path expression for the asset (bitstream) delivery path + */ + String getAssetOriginalBinaryDeliveryPath(); + + /** + * Gets the path expression for getting the metadata of an asset. The following + * placeholders with the below meaning are contained within + * that path: + *
    + *
  • {asset-id} - the uuid of the asset in the format 'urn:aaid:aem:UUID', + * e.g. urn:aaid:aem:1a034bee-ebda-4787-bad3-f924d0772b75
  • + *
+ * @return the path expression for the metadata path + */ + String getAssetMetadataPath(); + /** * Gets the Next Generation Dynamic Media tenant (also known technically as the repository ID). * @return the repository ID */ String getRepositoryId(); + /** + * Gets the API key for accessing the asset selectors UI + * @return the API key for accessing the asset selectors UI + */ + String getApiKey(); + + /** + * Gets the environment string which should be 'PROD' or 'STAGE' + * @return the environment string + */ + String getEnv(); + + /** + * Gets the IMS client identifier + * @return the IMS client identifier + */ + String getImsClient(); + } 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 708e8037..7f6c72b5 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 @@ -19,8 +19,13 @@ */ package io.wcm.handler.mediasource.ngdm.impl; +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.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.adobe.cq.ui.wcm.commons.config.NextGenDynamicMediaConfig; @@ -30,22 +35,65 @@ @Component(service = NextGenDynamicMediaConfigService.class, immediate = true) public class NextGenDynamicMediaConfigServiceImpl implements NextGenDynamicMediaConfigService { - @Reference + private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaConfigServiceImpl.class); + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY) private NextGenDynamicMediaConfig nextGenDynamicMediaConfig; + @Activate + private void activate() { + log.debug("NGDM config: enabled={}, repositoryId={}, apiKey={}, env={}, imsClient={}", + enabled(), getRepositoryId(), getApiKey(), getEnv(), getImsClient()); + } + @Override public boolean enabled() { return this.nextGenDynamicMediaConfig.enabled(); } + @Override + public String getAssetSelectorsJsUrl() { + return this.nextGenDynamicMediaConfig.getAssetSelectorsJsUrl(); + } + @Override public String getImageDeliveryBasePath() { return this.nextGenDynamicMediaConfig.getImageDeliveryBasePath(); } + @Override + public String getVideoDeliveryPath() { + return this.nextGenDynamicMediaConfig.getVideoDeliveryPath(); + } + + @Override + public String getAssetOriginalBinaryDeliveryPath() { + return this.nextGenDynamicMediaConfig.getAssetOriginalBinaryDeliveryPath(); + } + + @Override + public String getAssetMetadataPath() { + return this.nextGenDynamicMediaConfig.getAssetMetadataPath(); + } + @Override public String getRepositoryId() { return this.nextGenDynamicMediaConfig.getRepositoryId(); } + @Override + public String getApiKey() { + return this.nextGenDynamicMediaConfig.getApiKey(); + } + + @Override + public String getEnv() { + return this.nextGenDynamicMediaConfig.getEnv(); + } + + @Override + public String getImsClient() { + return this.nextGenDynamicMediaConfig.getImsClient(); + } + } diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaUrlBuilder.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilder.java similarity index 97% rename from src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaUrlBuilder.java rename to src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilder.java index 7f9cbb2d..35c0d12d 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaUrlBuilder.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilder.java @@ -41,7 +41,7 @@ * https://host/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg?preferwebp=true&quality=85&width=300&crop=16:9,smart *

*/ -public final class NextGenDynamicMediaUrlBuilder { +public final class NextGenDynamicMediaImageUrlBuilder { static final String PLACEHOLDER_ASSET_ID = "{asset-id}"; static final String PLACEHOLDER_SEO_NAME = "{seo-name}"; @@ -64,7 +64,7 @@ public final class NextGenDynamicMediaUrlBuilder { /** * @param context Context */ - public NextGenDynamicMediaUrlBuilder(@NotNull NextGenDynamicMediaContext context) { + public NextGenDynamicMediaImageUrlBuilder(@NotNull NextGenDynamicMediaContext context) { this.context = context; } 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 8dd7288e..7e14a8b2 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 @@ -20,5 +20,5 @@ /** * Media source implementation for Next Generation Dynamic Media. */ -@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.versioning.Version("1.1.0") package io.wcm.handler.mediasource.ngdm; diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/css.txt b/src/main/webapp/app-root/clientlibs/authoring/dialog/css.txt index 40b73518..2f9de227 100644 --- a/src/main/webapp/app-root/clientlibs/authoring/dialog/css.txt +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/css.txt @@ -1,2 +1,3 @@ #base=css fileupload.less +nextGenDynamicMedia.less diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/css/nextGenDynamicMedia.less b/src/main/webapp/app-root/clientlibs/authoring/dialog/css/nextGenDynamicMedia.less new file mode 100644 index 00000000..dd147a2e --- /dev/null +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/css/nextGenDynamicMedia.less @@ -0,0 +1,5 @@ +.wcmio-handler-media-ngdm-assetselector-dialogbody { + width: 80vw; + height: 80vh; + padding-bottom: 5px; +} diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/js.txt b/src/main/webapp/app-root/clientlibs/authoring/dialog/js.txt index c554c796..3959d5c1 100644 --- a/src/main/webapp/app-root/clientlibs/authoring/dialog/js.txt +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/js.txt @@ -1,6 +1,7 @@ #base=js namespace.js mediaFormatValidate.js +nextGenDynamicMedia.js fileupload.js pathfield.js validation.js diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js index 9f2ab209..0bd7ce18 100644 --- a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js @@ -30,6 +30,14 @@ self._addClearTransformationButton(); self._updatePickLink(); + // enable nextgen dynamic media + self._validate = new ns.NextGenDynamicMedia({ + pathfield: self._pathfield, + assetSelectedCallback: (assetReference) => { + self._triggerAssetSelected(assetReference); + } + }); + // enable asset validation self._validate = new ns.MediaFormatValidate({ pathfield: self._pathfield diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/nextGenDynamicMedia.js b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/nextGenDynamicMedia.js new file mode 100644 index 00000000..f33990a7 --- /dev/null +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/nextGenDynamicMedia.js @@ -0,0 +1,212 @@ +/*- + * #%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% + */ +;(function ($, ns, channel, document, window, undefined) { + "use strict"; + + var NextGenDynamicMedia = function (config) { + const self = this + + self._pathfield = config.pathfield; + self._$pathfield = $(config.pathfield); + self._assetSelectedCallback = config.assetSelectedCallback; + self._filterImagesOnly = config.filterImagesOnly; + + self._ngdmConfig = self._$pathfield.data("wcmio-nextgendynamicmedia-config"); + if (!self._ngdmConfig) { + // NGDM not enabled + return; + } + + self._addPickRemoteButton(() => { + self.pickRemoteAsset(assetReference => { + if (assetReference != self._$pathfield.val()) { + self._$pathfield.val(assetReference); + if (self._assetSelectedCallback) { + self._assetSelectedCallback(assetReference); + } + } + }); + }); + }; + + /** + * Opens NGDM asset selector to pick a remote asset. + * @param assetReferenceCallback called when asset is picked with the asset reference as parameter + */ + NextGenDynamicMedia.prototype.pickRemoteAsset = function(assetReferenceCallback) { + const self = this + + self._prepareAssetSelectorDialog(); + const assetSelectorProps = self._prepareAssetSelectorProps(assetReferenceCallback); + PureJSSelectors.renderAssetSelectorWithAuthFlow( + self._assetSelectorDialogContainer.get(0), + assetSelectorProps, + () => self._assetSelectorDialog.show() + ); + } + + /** + * Add additional button to pathfield to pick remote assets. + * @param onclickHandler Method that is called when button is clicked + */ + NextGenDynamicMedia.prototype._addPickRemoteButton = function(onclickHandler) { + const self = this + + const label = Granite.I18n.get("Remote"); + const pickRemoteButton = new Coral.Button().set({ + icon: "popOut", + title: label, + type: "button" + }); + pickRemoteButton.setAttribute("aria-label", label); + $(pickRemoteButton).on("click", onclickHandler); + + const buttonWrapper = document.createElement("span"); + buttonWrapper.classList.add("coral-InputGroup-button"); + buttonWrapper.appendChild(pickRemoteButton); + + // add new button (wrapper) after existing one to pick local assets + $(buttonWrapper).insertAfter(self._$pathfield.find(".coral-InputGroup-button")); + } + + /** + * Prepares a Coral Dialog in DOM to render the asset selector. + * If the markup already exists, it is reused. + * Sets variables self._assetSelectorDialog and self._assetSelectorDialogContainer as a result. + */ + NextGenDynamicMedia.prototype._prepareAssetSelectorDialog = function() { + const self = this + + self._assetSelectorDialog = $("#wcmio-handler-media-ngdm-assetselector"); + if (self._assetSelectorDialog.length === 0) { + self._assetSelectorDialog = new Coral.Dialog().set({ + id: "wcmio-handler-media-ngdm-assetselector", + content: { + innerHTML: '
' + }, + fullscreen: false + }); + document.body.appendChild(self._assetSelectorDialog); + } + self._assetSelectorDialogContainer = $(self._assetSelectorDialog).find(".wcmio-handler-media-ngdm-assetselector-dialogbody"); + } + + /** + * Prepare configuration for asset selector. + * @param assetReferenceCallback called when asset is picked with the asset reference as parameter + */ + NextGenDynamicMedia.prototype._prepareAssetSelectorProps = function(assetReferenceCallback) { + const self = this + + const assetSelectorProps = { + repositoryId: self._ngdmConfig.repositoryId, + apiKey: self._ngdmConfig.apiKey, + env: self._ngdmConfig.env, + handleSelection: (selection) => { + const selectedAsset = selection[0]; + const assetReference = self._toAssetReference(selectedAsset); + if (assetReference) { + assetReferenceCallback(assetReference); + } + }, + onClose: () => { + const $underlay = $("._coral-Underlay"); + $underlay.removeClass("is-open"); + self._assetSelectorDialog.remove(); + }, + hideTreeNav: true, + acvConfig: { + selectionType: "single", + } + }; + + if (self._filterImagesOnly) { + assetSelectorProps.filterSchema = [ + { + header: "File Type", + groupKey: "TopGroup", + fields: [ + { + element: "checkbox", + name: "type", + defaultValue: ["image/*"], + readOnly: true, + options: [ + { + label: "Images", + value: "image/*" + } + ] + } + ] + }, + { + fields: [ + { + element: "checkbox", + name: "type", + options: [ + { + label: "JPG", + value: "image/jpeg" + }, + { + label: "PNG", + value: "image/png" + }, + { + label: "TIFF", + value: "image/tiff" + }, + { + label: "GIF", + value: "image/gif" + }, + { + label: "SVG", + value: "image/svg+xml" + } + ], + columns: 2, + } + ], + header: "Mime Types", + groupKey: "MimeTypeGroup" + } + ]; + } + + return assetSelectorProps; + } + + /** + * Converts select asset object to asset reference string. + */ + NextGenDynamicMedia.prototype._toAssetReference = function(selectedAsset) { + const { name, "repo:assetId": assetId } = selectedAsset; + if (name && assetId) { + return `/${assetId}/${name}`; + } + return undefined; + } + + ns.NextGenDynamicMedia = NextGenDynamicMedia; + +}(Granite.$, wcmio.handler.media, jQuery(document), document, this)); diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/pathfield.js b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/pathfield.js index 8018b8e2..3e23a844 100644 --- a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/pathfield.js +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/pathfield.js @@ -26,6 +26,11 @@ self._$pathfield = $(config.pathfield); self._bindEvents(); + // enable nextgen dynamic media + self._validate = new ns.NextGenDynamicMedia({ + pathfield: self._pathfield + }); + // enable asset validation self._validate = new ns.MediaFormatValidate({ pathfield: self._pathfield diff --git a/src/main/webapp/app-root/components/granite/form/fileupload/fileupload.jsp b/src/main/webapp/app-root/components/granite/form/fileupload/fileupload.jsp index 1b20995e..6f5f7530 100644 --- a/src/main/webapp/app-root/components/granite/form/fileupload/fileupload.jsp +++ b/src/main/webapp/app-root/components/granite/form/fileupload/fileupload.jsp @@ -29,6 +29,7 @@ <%@page import="com.adobe.granite.ui.components.ExpressionHelper"%> <%@page import="io.wcm.handler.media.MediaNameConstants"%> <%@page import="io.wcm.handler.media.MediaComponentPropertyResolver"%> +<%@page import="io.wcm.handler.mediasource.ngdm.NextGenDynamicMediaConfigModel"%> <%@page import="io.wcm.handler.media.spi.MediaHandlerConfig"%> <%@page import="io.wcm.wcm.ui.granite.resource.GraniteUiSyntheticResource"%> <%@page import="io.wcm.wcm.ui.granite.util.GraniteUi"%> @@ -227,6 +228,12 @@ if (!dataProps.isEmpty()) { GraniteUiSyntheticResource.child(pathField, "granite:data", null, new ValueMapDecorator(dataProps)); } +// NGDM config (if enabled) +NextGenDynamicMediaConfigModel nextGenDynamicMediaConfig = slingRequest.adaptTo(NextGenDynamicMediaConfigModel.class); +if (nextGenDynamicMediaConfig.isEnabled()) { + dataProps.put("wcmio-nextgendynamicmedia-config", nextGenDynamicMediaConfig.getConfigJson()); +} + dispatcher = slingRequest.getRequestDispatcher(pathField); dispatcher.include(slingRequest, slingResponse); diff --git a/src/main/webapp/app-root/components/granite/form/pathfield/pathfield.jsp b/src/main/webapp/app-root/components/granite/form/pathfield/pathfield.jsp index d516f731..f75c722f 100644 --- a/src/main/webapp/app-root/components/granite/form/pathfield/pathfield.jsp +++ b/src/main/webapp/app-root/components/granite/form/pathfield/pathfield.jsp @@ -29,6 +29,7 @@ <%@page import="com.adobe.granite.ui.components.ExpressionHelper"%> <%@page import="io.wcm.handler.media.MediaNameConstants"%> <%@page import="io.wcm.handler.media.MediaComponentPropertyResolver"%> +<%@page import="io.wcm.handler.mediasource.ngdm.NextGenDynamicMediaConfigModel"%> <%@page import="io.wcm.handler.media.spi.MediaHandlerConfig"%> <%@page import="io.wcm.wcm.ui.granite.resource.GraniteUiSyntheticResource"%> <%@page import="io.wcm.wcm.ui.granite.util.GraniteUi"%> @@ -171,6 +172,12 @@ if (!dataProps.isEmpty()) { GraniteUiSyntheticResource.child(pathField, "granite:data", null, new ValueMapDecorator(dataProps)); } +// NGDM config (if enabled) +NextGenDynamicMediaConfigModel nextGenDynamicMediaConfig = slingRequest.adaptTo(NextGenDynamicMediaConfigModel.class); +if (nextGenDynamicMediaConfig.isEnabled()) { + dataProps.put("wcmio-nextgendynamicmedia-config", nextGenDynamicMediaConfig.getConfigJson()); +} + // render original component RequestDispatcherOptions options = new RequestDispatcherOptions(); options.setForceResourceType("wcm-io/wcm/ui/granite/components/form/pathfield"); 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 bb1061aa..d33fd448 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest.java @@ -61,6 +61,15 @@ void testAsset_JPEG_Original() { ContentType.JPEG); } + @Override + @Test + void testAsset_JPEG_Original_ContentDisposition() { + Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); + buildAssertMedia_ContentDisposition(asset, 100, 50, + "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?preferwebp=true&quality=85", + ContentType.JPEG); + } + @Override @Test void testAsset_JPEG_Rescale() { @@ -151,6 +160,15 @@ void testAsset_TIFF_Original() { ContentType.JPEG); } + @Override + @Test + void testAsset_TIFF_Original_ContentDisposition() { + Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); + buildAssertMedia_ContentDisposition(asset, 100, 50, + "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?preferwebp=true&quality=85", + ContentType.TIFF); + } + @Override @Test void testAsset_TIFF_Rescale() { @@ -169,6 +187,42 @@ void testAsset_TIFF_AutoCrop() { ContentType.JPEG); } + @Override + @Test + void testAsset_SVG_Original() { + Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); + buildAssertMedia(asset, 0, 0, + "https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/sample.svg", + ContentType.SVG); + } + + @Override + @Test + void testAsset_SVG_Original_ContentDisposition() { + Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); + buildAssertMedia_ContentDisposition(asset, 0, 0, + "https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/sample.svg", + ContentType.SVG); + } + + @Override + @Test + void testAsset_SVG_Rescale() { + Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); + buildAssertMedia_Rescale(asset, 0, 0, + "https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/sample.svg", + ContentType.SVG); + } + + @Override + @Test + void testAsset_SVG_AutoCrop() { + Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); + buildAssertMedia_AutoCrop(asset, 0, 0, + "https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/sample.svg", + ContentType.JPEG); + } + @SuppressWarnings("null") Asset createNextGenDynamicMediaReferenceAsAsset(String fileName) { String reference = new NextGenDynamicMediaReference(SAMPLE_ASSET_ID, fileName).toReference(); diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java new file mode 100644 index 00000000..e55115fd --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.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; + +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; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.skyscreamer.jsonassert.JSONAssert; + +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigServiceImpl; +import io.wcm.sling.commons.adapter.AdaptTo; +import io.wcm.testing.mock.aem.dam.ngdm.MockNextGenDynamicMediaConfig; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; + +@ExtendWith(AemContextExtension.class) +class NextGenDynamicMediaConfigModelTest { + + private final AemContext context = AppAemContext.newAemContext(); + + @BeforeEach + void setUp() { + MockNextGenDynamicMediaConfig config = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); + config.setEnabled(true); + config.setAssetSelectorsJsUrl("/selector1"); + config.setRepositoryId("repo1"); + config.setApiKey("key1"); + config.setEnv("env1"); + config.setImsClient("client1"); + } + + @Test + void testWithConfigService() throws JSONException { + context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + + NextGenDynamicMediaConfigModel underTest = AdaptTo.notNull(context.request(), NextGenDynamicMediaConfigModel.class); + assertTrue(underTest.isEnabled()); + assertEquals("/selector1", underTest.getAssetSelectorsJsUrl()); + JSONAssert.assertEquals("{repositoryId:'repo1',apiKey:'key1',env:'env1',imsClient:'client1'}", + 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/NextGenDynamicMediaTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java index 43934767..95fcec99 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java @@ -19,6 +19,7 @@ */ package io.wcm.handler.mediasource.ngdm; +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 org.junit.jupiter.api.Assertions.assertEquals; @@ -197,6 +198,22 @@ void testRendition_NonFixedMinWidthHeightMediaFormat() { assertUrl(rendition, "preferwebp=true&quality=85", "jpg"); } + @Test + @SuppressWarnings("null") + void testPDFDownload() { + Resource downloadResource = context.create().resource(context.currentPage(), "download", + MediaNameConstants.PN_MEDIA_REF, "/" + SAMPLE_ASSET_ID + "/myfile.pdf"); + + Media media = mediaHandler.get(downloadResource) + .args(new MediaArgs().download(true)) + .build(); + assertTrue(media.isValid()); + + Rendition rendition = media.getRendition(); + assertNotNull(rendition); + assertEquals("https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/myfile.pdf", rendition.getUrl()); + } + private static void assertUrl(Media media, String urlParams, String extension) { assertEquals(buildUrl(urlParams, extension), media.getUrl()); } 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 new file mode 100644 index 00000000..dcd507c7 --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaBinaryUrlBuilderTest.java @@ -0,0 +1,77 @@ +/* + * #%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; + +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_REFERENCE; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.sling.commons.mime.MimeTypeService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.wcm.handler.media.MediaArgs; +import io.wcm.handler.media.spi.MediaHandlerConfig; +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.sling.commons.adapter.AdaptTo; +import io.wcm.testing.mock.aem.dam.ngdm.MockNextGenDynamicMediaConfig; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; + +@ExtendWith(AemContextExtension.class) +class NextGenDynamicMediaBinaryUrlBuilderTest { + + private final AemContext context = AppAemContext.newAemContext(); + + private NextGenDynamicMediaConfigService nextGenDynamicMediaConfig; + private MediaHandlerConfig mediaHandlerConfig; + private MimeTypeService mimeTypeService; + + @BeforeEach + void setUp() throws Exception { + context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class) + .setRepositoryId("repo1"); + nextGenDynamicMediaConfig = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + + mediaHandlerConfig = AdaptTo.notNull(context.request(), MediaHandlerConfig.class); + mimeTypeService = context.getService(MimeTypeService.class); + } + + @Test + void testBuild() { + NextGenDynamicMediaBinaryUrlBuilder underTest = getBuilder(new MediaArgs()); + + assertEquals("https://repo1/adobe/assets/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg", + underTest.build()); + } + + @SuppressWarnings("null") + private NextGenDynamicMediaBinaryUrlBuilder getBuilder(MediaArgs mediaArgs) { + NextGenDynamicMediaContext ctx = new NextGenDynamicMediaContext( + NextGenDynamicMediaReference.fromReference(SAMPLE_REFERENCE), + null, + mediaArgs, + nextGenDynamicMediaConfig, + mediaHandlerConfig, + mimeTypeService); + return new NextGenDynamicMediaBinaryUrlBuilder(ctx); + } + +} 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 new file mode 100644 index 00000000..e28beff7 --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaConfigServiceImplTest.java @@ -0,0 +1,71 @@ +/* + * #%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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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; + +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.testing.mock.aem.dam.ngdm.MockNextGenDynamicMediaConfig; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; + +@ExtendWith(AemContextExtension.class) +class NextGenDynamicMediaConfigServiceImplTest { + + private final AemContext context = AppAemContext.newAemContext(); + + private NextGenDynamicMediaConfigService underTest; + + @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"); + underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + } + + @Test + void testProperties() { + assertTrue(underTest.enabled()); + 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("key1", underTest.getApiKey()); + assertEquals("env1", underTest.getEnv()); + assertEquals("client1", underTest.getImsClient()); + } + +} diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaUrlBuilderTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilderTest.java similarity index 87% rename from src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaUrlBuilderTest.java rename to src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilderTest.java index 2afbdead..a401e2b5 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaUrlBuilderTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaImageUrlBuilderTest.java @@ -37,7 +37,7 @@ import io.wcm.testing.mock.aem.junit5.AemContextExtension; @ExtendWith(AemContextExtension.class) -class NextGenDynamicMediaUrlBuilderTest { +class NextGenDynamicMediaImageUrlBuilderTest { private final AemContext context = AppAemContext.newAemContext(); @@ -57,7 +57,7 @@ void setUp() throws Exception { @Test void testDefaultParams() { - NextGenDynamicMediaUrlBuilder underTest = getBuilder(); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(); NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams(); assertEquals("https://repo1/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg" @@ -67,7 +67,7 @@ void testDefaultParams() { @Test void testForceOutputExtension() { - NextGenDynamicMediaUrlBuilder underTest = getBuilder(new MediaArgs().enforceOutputFileExtension("png")); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(new MediaArgs().enforceOutputFileExtension("png")); NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams(); assertEquals("https://repo1/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.png" @@ -77,7 +77,7 @@ void testForceOutputExtension() { @Test void testAllParams() { - NextGenDynamicMediaUrlBuilder underTest = getBuilder(); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(); NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams() .width(100L) .cropSmartRatio(new Dimension(16, 9)) @@ -91,7 +91,7 @@ void testAllParams() { @Test void testWidthPlaceholder() { - NextGenDynamicMediaUrlBuilder underTest = getBuilder(); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(); NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams() .widthPlaceholder("{w}") .quality(60); @@ -101,12 +101,12 @@ void testWidthPlaceholder() { underTest.build(params)); } - private NextGenDynamicMediaUrlBuilder getBuilder() { + private NextGenDynamicMediaImageUrlBuilder getBuilder() { return getBuilder(new MediaArgs()); } @SuppressWarnings("null") - private NextGenDynamicMediaUrlBuilder getBuilder(MediaArgs mediaArgs) { + private NextGenDynamicMediaImageUrlBuilder getBuilder(MediaArgs mediaArgs) { NextGenDynamicMediaContext ctx = new NextGenDynamicMediaContext( NextGenDynamicMediaReference.fromReference(SAMPLE_REFERENCE), null, @@ -114,7 +114,7 @@ private NextGenDynamicMediaUrlBuilder getBuilder(MediaArgs mediaArgs) { nextGenDynamicMediaConfig, mediaHandlerConfig, mimeTypeService); - return new NextGenDynamicMediaUrlBuilder(ctx); + return new NextGenDynamicMediaImageUrlBuilder(ctx); } } From 2c0f1afeec70ae1ad551db43b849316a9407ceb9 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 31 Jan 2024 13:43:09 +0100 Subject: [PATCH 03/13] fix comments --- .../ngdm/impl/NextGenDynamicMediaBinaryUrlBuilder.java | 2 +- .../ngdm/impl/NextGenDynamicMediaImageUrlBuilder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 3686f567..210afa69 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 @@ -57,7 +57,7 @@ public NextGenDynamicMediaBinaryUrlBuilder(@NotNull NextGenDynamicMediaContext c return null; } - // replace placeholders in image delivery path + // replace placeholders in delivery path String seoName = context.getReference().getFileName(); binaryDeliveryPath = StringUtils.replace(binaryDeliveryPath, PLACEHOLDER_ASSET_ID, context.getReference().getAssetId()); binaryDeliveryPath = StringUtils.replace(binaryDeliveryPath, PLACEHOLDER_SEO_NAME, seoName); 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 35c0d12d..823fc5b9 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 @@ -82,7 +82,7 @@ public NextGenDynamicMediaImageUrlBuilder(@NotNull NextGenDynamicMediaContext co return null; } - // replace placeholders in image delivery path + // replace placeholders in delivery path String seoName = FilenameUtils.getBaseName(context.getReference().getFileName()); String format = context.getDefaultMediaArgs().getEnforceOutputFileExtension(); if (StringUtils.isEmpty(format)) { From 291dae7fa48ec6f1e382599ddf411cf6a3179114 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 31 Jan 2024 17:51:23 +0100 Subject: [PATCH 04/13] minor refactorings: move constants to (private) interface, imsclient not required for asset selector config --- .../ngdm/NextGenDynamicMediaConfigModel.java | 1 - .../NextGenDynamicMediaBinaryUrlBuilder.java | 6 +++--- .../impl/NextGenDynamicMediaConfigService.java | 15 +++++++++++++++ .../NextGenDynamicMediaConfigServiceImpl.java | 17 +++++++++++++++++ .../NextGenDynamicMediaImageUrlBuilder.java | 8 ++++---- .../NextGenDynamicMediaConfigModelTest.java | 2 +- 6 files changed, 40 insertions(+), 9 deletions(-) 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 3b91bc13..86049455 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModel.java @@ -70,7 +70,6 @@ private static String buildConfigJsonString(@NotNull NextGenDynamicMediaConfigSe map.put("repositoryId", config.getRepositoryId()); map.put("apiKey", config.getApiKey()); map.put("env", config.getEnv()); - map.put("imsClient", config.getImsClient()); try { return MAPPER.writeValueAsString(map); } 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 210afa69..6beb0515 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 @@ -19,6 +19,9 @@ */ package io.wcm.handler.mediasource.ngdm.impl; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService.PLACEHOLDER_ASSET_ID; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService.PLACEHOLDER_SEO_NAME; + import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -32,9 +35,6 @@ */ public final class NextGenDynamicMediaBinaryUrlBuilder { - static final String PLACEHOLDER_ASSET_ID = "{asset-id}"; - static final String PLACEHOLDER_SEO_NAME = "{seo-name}"; - private final NextGenDynamicMediaContext context; /** 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 e53fc094..5779fe1a 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 @@ -24,6 +24,21 @@ */ public interface NextGenDynamicMediaConfigService { + /** + * Placeholder for Asset ID used in delivery base paths. + */ + String PLACEHOLDER_ASSET_ID = "{asset-id}"; + + /** + * Placeholder for SEO name used in delivery base paths. + */ + String PLACEHOLDER_SEO_NAME = "{seo-name}"; + + /** + * Placeholder for format (image file extension) used in delivery base paths. + */ + String PLACEHOLDER_FORMAT = "{format}"; + /** * Checks if the configuration/feature is enabled. * @return true if enabled and false otherwise 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 7f6c72b5..9efc31ce 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 @@ -24,6 +24,9 @@ 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,8 +36,22 @@ * Wraps access to NextGenDynamicMediaConfig - which is deployed but not accessible on AEM 6.5. */ @Component(service = NextGenDynamicMediaConfigService.class, immediate = true) +@Designate(ocd = NextGenDynamicMediaConfigServiceImpl.Config.class) public class NextGenDynamicMediaConfigServiceImpl implements NextGenDynamicMediaConfigService { + @ObjectClassDefinition( + name = "wcm.io Next Generation Dynamic Media Support", + description = "Support for Next Generation Dynamic Media.") + @interface Config { + + @AttributeDefinition( + name = "Enabled", + description = "Enable support for Web-Optimized Image Delivery (if available).") + boolean enabled() default true; + + } + + private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaConfigServiceImpl.class); @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY) 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 823fc5b9..5c074ecd 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 @@ -19,6 +19,10 @@ */ package io.wcm.handler.mediasource.ngdm.impl; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService.PLACEHOLDER_ASSET_ID; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService.PLACEHOLDER_FORMAT; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService.PLACEHOLDER_SEO_NAME; + import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Set; @@ -43,10 +47,6 @@ */ public final class NextGenDynamicMediaImageUrlBuilder { - static final String PLACEHOLDER_ASSET_ID = "{asset-id}"; - static final String PLACEHOLDER_SEO_NAME = "{seo-name}"; - static final String PLACEHOLDER_FORMAT = "{format}"; - static final String PARAM_PREFER_WEBP = "preferwebp"; static final String PARAM_WIDTH = "width"; static final String PARAM_CROP = "crop"; 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 e55115fd..57c49cff 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaConfigModelTest.java @@ -60,7 +60,7 @@ void testWithConfigService() throws JSONException { NextGenDynamicMediaConfigModel underTest = AdaptTo.notNull(context.request(), NextGenDynamicMediaConfigModel.class); assertTrue(underTest.isEnabled()); assertEquals("/selector1", underTest.getAssetSelectorsJsUrl()); - JSONAssert.assertEquals("{repositoryId:'repo1',apiKey:'key1',env:'env1',imsClient:'client1'}", + JSONAssert.assertEquals("{repositoryId:'repo1',apiKey:'key1',env:'env1'}", underTest.getConfigJson(), true); } From 3fb761bf19ff6c5d3510882fc2d174a455af6b21 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Thu, 1 Feb 2024 09:56:19 +0100 Subject: [PATCH 05/13] remove unfinished configuration fragment --- .../NextGenDynamicMediaConfigServiceImpl.java | 17 ----------------- 1 file changed, 17 deletions(-) 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 9efc31ce..7f6c72b5 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 @@ -24,9 +24,6 @@ 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,22 +33,8 @@ * Wraps access to NextGenDynamicMediaConfig - which is deployed but not accessible on AEM 6.5. */ @Component(service = NextGenDynamicMediaConfigService.class, immediate = true) -@Designate(ocd = NextGenDynamicMediaConfigServiceImpl.Config.class) public class NextGenDynamicMediaConfigServiceImpl implements NextGenDynamicMediaConfigService { - @ObjectClassDefinition( - name = "wcm.io Next Generation Dynamic Media Support", - description = "Support for Next Generation Dynamic Media.") - @interface Config { - - @AttributeDefinition( - name = "Enabled", - description = "Enable support for Web-Optimized Image Delivery (if available).") - boolean enabled() default true; - - } - - private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaConfigServiceImpl.class); @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY) From 0fcca0707413fa734f111e53d62ada6a11f15189 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Thu, 15 Feb 2024 11:28:31 +0100 Subject: [PATCH 06/13] Use latest NextGen Dynamic Media Asset URLs (#45) --- changes.xml | 3 + .../NextGenDynamicMediaConfigService.java | 11 +++ .../NextGenDynamicMediaConfigServiceImpl.java | 84 ++++++++++++++++++- .../NextGenDynamicMediaImageUrlBuilder.java | 8 +- ...leTypesEnd2EndNextGenDynamicMediaTest.java | 38 ++++----- ...aHandlerImplImageFileTypesEnd2EndTest.java | 3 +- .../ngdm/NextGenDynamicMediaTest.java | 6 +- ...xtGenDynamicMediaBinaryUrlBuilderTest.java | 2 +- ...tGenDynamicMediaConfigServiceImplTest.java | 29 ++++++- ...extGenDynamicMediaImageUrlBuilderTest.java | 31 +++++-- 10 files changed, 176 insertions(+), 39 deletions(-) diff --git a/changes.xml b/changes.xml index 04a7ebbf..32146585 100644 --- a/changes.xml +++ b/changes.xml @@ -27,6 +27,9 @@ Next Generation Dynamic Media: 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. +
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..ff2c45e4 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,10 @@ */ package io.wcm.handler.mediasource.ngdm.impl; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; + /** * Service to access Next Generation Dynamic Media configuration. */ @@ -105,6 +109,13 @@ public interface NextGenDynamicMediaConfigService { */ String getAssetMetadataPath(); + /** + * HTTP headers to be send with the asset metadata request. + * @return Asset Metadata Headers + */ + @NotNull + Map getAssetMetadataHeaders(); + /** * Gets the Next Generation Dynamic Media tenant (also known technically as the repository ID). * @return the repository ID 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 7f6c72b5..258457eb 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 @@ -19,11 +19,19 @@ */ package io.wcm.handler.mediasource.ngdm.impl; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; 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.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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,17 +41,80 @@ * Wraps access to NextGenDynamicMediaConfig - which is deployed but not accessible on AEM 6.5. */ @Component(service = NextGenDynamicMediaConfigService.class, immediate = true) +@Designate(ocd = NextGenDynamicMediaConfigServiceImpl.Config.class) public class NextGenDynamicMediaConfigServiceImpl implements NextGenDynamicMediaConfigService { + @ObjectClassDefinition( + name = "wcm.io Next Generation Dynamic Media Support", + description = "Support for Next Generation Dynamic Media.") + @interface Config { + + @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"; + + @AttributeDefinition( + name = "Asset Original Binary Delivery Path", + description = "Base path with placeholders to deliver asset original binaries. " + + "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"; + + @AttributeDefinition( + name = "Asset Metadata Path", + description = "Base path to get asset metadata. " + + "Placeholder: " + PLACEHOLDER_ASSET_ID + ". " + + "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 = "Asset Metadata Headers", + description = "HTTP headers to be send with the asset metadata request. " + + "Format: 'header1:value1'.") + String[] assetMetadataHeaders() default { "X-Adobe-Accept-Experimental:1" }; + + } + + private static final String ADOBE_ASSETS_PREFIX = "/adobe/assets/"; private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaConfigServiceImpl.class); + private String imageDeliveryBasePath; + private String assetOriginalBinaryDeliveryPath; + private String assetMetadataPath; + private Map assetMetadataHeaders; + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY) private NextGenDynamicMediaConfig nextGenDynamicMediaConfig; @Activate - private void 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()); + this.assetMetadataHeaders = headersToMap(config.assetMetadataHeaders()); + + } + + private static Map headersToMap(String[] headers) { + Map map = new LinkedHashMap<>(); + for (String header : headers) { + String[] parts = header.split(":", 2); + if (parts.length == 2) { + map.put(parts[0], parts[1]); + } + } + return map; } @Override @@ -58,7 +129,7 @@ public String getAssetSelectorsJsUrl() { @Override public String getImageDeliveryBasePath() { - return this.nextGenDynamicMediaConfig.getImageDeliveryBasePath(); + return imageDeliveryBasePath; } @Override @@ -68,12 +139,17 @@ public String getVideoDeliveryPath() { @Override public String getAssetOriginalBinaryDeliveryPath() { - return this.nextGenDynamicMediaConfig.getAssetOriginalBinaryDeliveryPath(); + return assetOriginalBinaryDeliveryPath; } @Override public String getAssetMetadataPath() { - return this.nextGenDynamicMediaConfig.getAssetMetadataPath(); + return assetMetadataPath; + } + + @Override + public @NotNull Map getAssetMetadataHeaders() { + return assetMetadataHeaders; } @Override 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 5c074ecd..e14591fd 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 @@ -129,7 +129,13 @@ else if (width != null) { .map(entry -> toUrlParam(entry.getKey(), entry.getValue())) .collect(Collectors.joining("&")); if (StringUtils.isNotEmpty(urlParams)) { - url.append("?").append(urlParams); + if (url.indexOf("?") < 0) { + url.append("?"); + } + else { + url.append("&"); + } + url.append(urlParams); } return url.toString(); } 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 d33fd448..e46bb970 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndNextGenDynamicMediaTest.java @@ -57,7 +57,7 @@ void setUp() { void testAsset_JPEG_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia(asset, 100, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&preferwebp=true&quality=85", ContentType.JPEG); } @@ -66,7 +66,7 @@ void testAsset_JPEG_Original() { void testAsset_JPEG_Original_ContentDisposition() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia_ContentDisposition(asset, 100, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&preferwebp=true&quality=85", ContentType.JPEG); } @@ -75,7 +75,7 @@ void testAsset_JPEG_Original_ContentDisposition() { void testAsset_JPEG_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia_Rescale(asset, 80, 40, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", ContentType.JPEG); } @@ -84,7 +84,7 @@ void testAsset_JPEG_Rescale() { void testAsset_JPEG_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=85", ContentType.JPEG); } @@ -93,7 +93,7 @@ void testAsset_JPEG_AutoCrop() { void testAsset_JPEG_AutoCrop_ImageQuality() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.jpg"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=60", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=60", ContentType.JPEG, 0.6d); } @@ -102,7 +102,7 @@ void testAsset_JPEG_AutoCrop_ImageQuality() { void testAsset_GIF_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.gif"); buildAssertMedia(asset, 100, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.gif?preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?accept-experimental=1&preferwebp=true&quality=85", ContentType.GIF); } @@ -111,7 +111,7 @@ void testAsset_GIF_Original() { void testAsset_GIF_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.gif"); buildAssertMedia_Rescale(asset, 80, 40, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.gif?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?accept-experimental=1&crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", ContentType.GIF); } @@ -120,7 +120,7 @@ void testAsset_GIF_Rescale() { void testAsset_GIF_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.gif"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.gif?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.gif?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=85", ContentType.GIF); } @@ -129,7 +129,7 @@ void testAsset_GIF_AutoCrop() { void testAsset_PNG_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.png"); buildAssertMedia(asset, 100, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.png?preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?accept-experimental=1&preferwebp=true&quality=85", ContentType.PNG); } @@ -138,7 +138,7 @@ void testAsset_PNG_Original() { void testAsset_PNG_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.png"); buildAssertMedia_Rescale(asset, 80, 40, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.png?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?accept-experimental=1&crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", ContentType.PNG); } @@ -147,7 +147,7 @@ void testAsset_PNG_Rescale() { void testAsset_PNG_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.png"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.png?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.png?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=85", ContentType.PNG); } @@ -156,7 +156,7 @@ void testAsset_PNG_AutoCrop() { void testAsset_TIFF_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); buildAssertMedia(asset, 100, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&preferwebp=true&quality=85", ContentType.JPEG); } @@ -165,7 +165,7 @@ void testAsset_TIFF_Original() { void testAsset_TIFF_Original_ContentDisposition() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); buildAssertMedia_ContentDisposition(asset, 100, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&preferwebp=true&quality=85", ContentType.TIFF); } @@ -174,7 +174,7 @@ void testAsset_TIFF_Original_ContentDisposition() { void testAsset_TIFF_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); buildAssertMedia_Rescale(asset, 80, 40, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=80%3A40%2Csmart&preferwebp=true&quality=85&width=80", ContentType.JPEG); } @@ -183,7 +183,7 @@ void testAsset_TIFF_Rescale() { void testAsset_TIFF_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.tif"); buildAssertMedia_AutoCrop(asset, 50, 50, - "https://repo1/adobe/dynamicmedia/deliver/" + SAMPLE_ASSET_ID + "/sample.jpg?crop=1%3A1%2Csmart&preferwebp=true&quality=85", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/as/sample.jpg?accept-experimental=1&crop=1%3A1%2Csmart&preferwebp=true&quality=85", ContentType.JPEG); } @@ -192,7 +192,7 @@ void testAsset_TIFF_AutoCrop() { void testAsset_SVG_Original() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); buildAssertMedia(asset, 0, 0, - "https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/sample.svg", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?accept-experimental=1", ContentType.SVG); } @@ -201,7 +201,7 @@ void testAsset_SVG_Original() { void testAsset_SVG_Original_ContentDisposition() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); buildAssertMedia_ContentDisposition(asset, 0, 0, - "https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/sample.svg", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?accept-experimental=1", ContentType.SVG); } @@ -210,7 +210,7 @@ void testAsset_SVG_Original_ContentDisposition() { void testAsset_SVG_Rescale() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); buildAssertMedia_Rescale(asset, 0, 0, - "https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/sample.svg", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?accept-experimental=1", ContentType.SVG); } @@ -219,7 +219,7 @@ void testAsset_SVG_Rescale() { void testAsset_SVG_AutoCrop() { Asset asset = createNextGenDynamicMediaReferenceAsAsset("sample.svg"); buildAssertMedia_AutoCrop(asset, 0, 0, - "https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/sample.svg", + "https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/sample.svg?accept-experimental=1", 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 38fe934b..f2e98bff 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java @@ -474,7 +474,8 @@ void assertMedia(@Nullable Resource resource, Media media, int width, int height if (!StringUtils.contains(mediaUrl, ".download_attachment.") && !StringUtils.contains(mediaUrl, "/is/image/") - && !StringUtils.contains(mediaUrl, "/adobe/dynamicmedia/deliver/")) { + && !StringUtils.contains(mediaUrl, "/adobe/dynamicmedia/deliver/") + && !StringUtils.contains(mediaUrl, "/adobe/assets/")) { String strippedMediaUrl = StringUtils.removeEnd(mediaUrl, DynamicMediaPath.DOWNLOAD_SUFFIX); assertEquals(FilenameUtils.getName(strippedMediaUrl), rendition.getFileName()); assertEquals(FilenameUtils.getExtension(strippedMediaUrl), rendition.getFileExtension()); 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 95fcec99..3649a41a 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java @@ -211,7 +211,7 @@ void testPDFDownload() { Rendition rendition = media.getRendition(); assertNotNull(rendition); - assertEquals("https://repo1/adobe/assets/deliver/" + SAMPLE_ASSET_ID + "/myfile.pdf", rendition.getUrl()); + assertEquals("https://repo1/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/myfile.pdf?accept-experimental=1", rendition.getUrl()); } private static void assertUrl(Media media, String urlParams, String extension) { @@ -230,8 +230,8 @@ private static void assertUriTemplate(UriTemplate uriTemplate, String urlParams, } private static String buildUrl(String urlParams, String extension) { - return "https://repo1/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image." - + extension + "?" + urlParams; + return "https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image." + + extension + "?accept-experimental=1&" + urlParams; } } 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 dcd507c7..734dc27c 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 @@ -58,7 +58,7 @@ void setUp() throws Exception { void testBuild() { NextGenDynamicMediaBinaryUrlBuilder underTest = getBuilder(new MediaArgs()); - assertEquals("https://repo1/adobe/assets/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg", + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/original/as/my-image.jpg?accept-experimental=1", underTest.build()); } 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 e28beff7..6de62284 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 @@ -22,6 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,8 +38,6 @@ class NextGenDynamicMediaConfigServiceImplTest { private final AemContext context = AppAemContext.newAemContext(); - private NextGenDynamicMediaConfigService underTest; - @BeforeEach void setUp() { MockNextGenDynamicMediaConfig config = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); @@ -51,17 +51,38 @@ void setUp() { config.setApiKey("key1"); config.setEnv("env1"); config.setImsClient("client1"); - underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); } @Test - void testProperties() { + void testPropertiesDefaultConfig() { + NextGenDynamicMediaConfigService underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + assertTrue(underTest.enabled()); + assertEquals("/selector1", underTest.getAssetSelectorsJsUrl()); + assertEquals("/adobe/assets/{asset-id}/as/{seo-name}.{format}?accept-experimental=1", 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}/metadata", underTest.getAssetMetadataPath()); + assertEquals(Map.of("X-Adobe-Accept-Experimental", "1"), underTest.getAssetMetadataHeaders()); + assertEquals("repo1", underTest.getRepositoryId()); + assertEquals("key1", underTest.getApiKey()); + assertEquals("env1", underTest.getEnv()); + assertEquals("client1", underTest.getImsClient()); + } + + @Test + void testPropertiesEmptyConfig() { + NextGenDynamicMediaConfigService underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class, + "imageDeliveryBasePath", "", + "assetOriginalBinaryDeliveryPath", "", + "assetMetadataPath", "", + "assetMetadataHeaders", new String[0]); assertTrue(underTest.enabled()); assertEquals("/selector1", underTest.getAssetSelectorsJsUrl()); assertEquals("/imagepath1", underTest.getImageDeliveryBasePath()); assertEquals("/videopath1", underTest.getVideoDeliveryPath()); assertEquals("/assetpath1", underTest.getAssetOriginalBinaryDeliveryPath()); assertEquals("/metadatapath1", underTest.getAssetMetadataPath()); + assertEquals(Map.of(), underTest.getAssetMetadataHeaders()); assertEquals("repo1", underTest.getRepositoryId()); assertEquals("key1", underTest.getApiKey()); assertEquals("env1", underTest.getEnv()); 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 a401e2b5..6e4eff6b 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 @@ -60,8 +60,8 @@ void testDefaultParams() { NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(); NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams(); - assertEquals("https://repo1/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg" - + "?preferwebp=true", + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.jpg" + + "?accept-experimental=1&preferwebp=true", underTest.build(params)); } @@ -70,8 +70,8 @@ void testForceOutputExtension() { NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(new MediaArgs().enforceOutputFileExtension("png")); NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams(); - assertEquals("https://repo1/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.png" - + "?preferwebp=true", + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.png" + + "?accept-experimental=1&preferwebp=true", underTest.build(params)); } @@ -84,6 +84,25 @@ void testAllParams() { .rotation(90) .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", + underTest.build(params)); + } + + @Test + void testAllParams_EmptyOsgiConfig() { + nextGenDynamicMediaConfig = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class, + "imageDeliveryBasePath", "", + "assetOriginalBinaryDeliveryPath", "", + "assetMetadataPath", "", + "assetMetadataHeaders", new String[0]); + NextGenDynamicMediaImageUrlBuilder underTest = getBuilder(); + NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams() + .width(100L) + .cropSmartRatio(new Dimension(16, 9)) + .rotation(90) + .quality(60); + assertEquals("https://repo1/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg" + "?crop=16%3A9%2Csmart&preferwebp=true&quality=60&rotate=90&width=100", underTest.build(params)); @@ -96,8 +115,8 @@ void testWidthPlaceholder() { .widthPlaceholder("{w}") .quality(60); - assertEquals("https://repo1/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg" - + "?preferwebp=true&quality=60&width={w}", + 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}", underTest.build(params)); } From 7300b59523c645f4b8981ed9264ae070cdd939de Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Thu, 15 Feb 2024 11:30:17 +0100 Subject: [PATCH 07/13] return unmodifiable map --- .../ngdm/impl/NextGenDynamicMediaConfigServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 258457eb..d300453e 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 @@ -19,6 +19,7 @@ */ package io.wcm.handler.mediasource.ngdm.impl; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -149,7 +150,7 @@ public String getAssetMetadataPath() { @Override public @NotNull Map getAssetMetadataHeaders() { - return assetMetadataHeaders; + return Collections.unmodifiableMap(assetMetadataHeaders); } @Override From cbe0fa25404a39e2b138880140bd54651bdb3cf1 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Thu, 15 Feb 2024 13:48:40 +0100 Subject: [PATCH 08/13] fileupload: Replace default pick/remote trigger with customized one (#47) --- changes.xml | 3 ++ .../authoring/dialog/js/fileupload.js | 1 + .../dialog/js/nextGenDynamicMedia.js | 38 ++++++++++++++----- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/changes.xml b/changes.xml index 32146585..df212ef5 100644 --- a/changes.xml +++ b/changes.xml @@ -30,6 +30,9 @@ Next Generation Dynamic Media: 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. + diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js index 0bd7ce18..85401996 100644 --- a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js @@ -32,6 +32,7 @@ // enable nextgen dynamic media self._validate = new ns.NextGenDynamicMedia({ + fileupload: self._element, pathfield: self._pathfield, assetSelectedCallback: (assetReference) => { self._triggerAssetSelected(assetReference); diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/nextGenDynamicMedia.js b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/nextGenDynamicMedia.js index f33990a7..d0863884 100644 --- a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/nextGenDynamicMedia.js +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/nextGenDynamicMedia.js @@ -20,21 +20,25 @@ ;(function ($, ns, channel, document, window, undefined) { "use strict"; - var NextGenDynamicMedia = function (config) { - const self = this + const NextGenDynamicMedia = function (config) { + const self = this; + if (config.fileupload) { + self._fileupload = config.fileupload; + self._$fileupload = $(config.fileupload); + } self._pathfield = config.pathfield; self._$pathfield = $(config.pathfield); self._assetSelectedCallback = config.assetSelectedCallback; self._filterImagesOnly = config.filterImagesOnly; - + self._ngdmConfig = self._$pathfield.data("wcmio-nextgendynamicmedia-config"); if (!self._ngdmConfig) { // NGDM not enabled return; } - self._addPickRemoteButton(() => { + const pickRemoteButtonOnClick = () => { self.pickRemoteAsset(assetReference => { if (assetReference != self._$pathfield.val()) { self._$pathfield.val(assetReference); @@ -43,7 +47,9 @@ } } }); - }); + }; + self._addPickRemoteButton(pickRemoteButtonOnClick); + self._updateRemotePickLink(pickRemoteButtonOnClick); }; /** @@ -51,7 +57,7 @@ * @param assetReferenceCallback called when asset is picked with the asset reference as parameter */ NextGenDynamicMedia.prototype.pickRemoteAsset = function(assetReferenceCallback) { - const self = this + const self = this; self._prepareAssetSelectorDialog(); const assetSelectorProps = self._prepareAssetSelectorProps(assetReferenceCallback); @@ -67,7 +73,7 @@ * @param onclickHandler Method that is called when button is clicked */ NextGenDynamicMedia.prototype._addPickRemoteButton = function(onclickHandler) { - const self = this + const self = this; const label = Granite.I18n.get("Remote"); const pickRemoteButton = new Coral.Button().set({ @@ -92,7 +98,7 @@ * Sets variables self._assetSelectorDialog and self._assetSelectorDialogContainer as a result. */ NextGenDynamicMedia.prototype._prepareAssetSelectorDialog = function() { - const self = this + const self = this; self._assetSelectorDialog = $("#wcmio-handler-media-ngdm-assetselector"); if (self._assetSelectorDialog.length === 0) { @@ -113,7 +119,7 @@ * @param assetReferenceCallback called when asset is picked with the asset reference as parameter */ NextGenDynamicMedia.prototype._prepareAssetSelectorProps = function(assetReferenceCallback) { - const self = this + const self = this; const assetSelectorProps = { repositoryId: self._ngdmConfig.repositoryId, @@ -207,6 +213,20 @@ return undefined; } + /** + * Replace click handler for "Pick/Remote" link - apply same feature as the browse remote button in the path field. + * @param onclickHandler Method that is called when button is clicked + */ + NextGenDynamicMedia.prototype._updateRemotePickLink = function (onclickHandler) { + const self = this; + if (self._$fileupload) { + // it would be more correct to look for the .cq-FileUpload-picker-polaris in scope of self._$fileupload, + // but the click tricker is registered this way in /libs/cq/gui/components/authoring/dialog/fileupload/clientlibs/fileupload/js/fileupload-polaris.js + // so we have to do it the same way. + $(document).off("click", ".cq-FileUpload-picker-polaris").on("click", ".cq-FileUpload-picker-polaris", onclickHandler); + } + }; + ns.NextGenDynamicMedia = NextGenDynamicMedia; }(Granite.$, wcmio.handler.media, jQuery(document), document, this)); From 9088fe218b404ffb628ac6f6ff067021c56ae476 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 6 Mar 2024 11:39:53 +0100 Subject: [PATCH 09/13] update to actions/setup-java@v4 --- .github/workflows/maven-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index c709b1cc..763171a0 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -25,7 +25,7 @@ jobs: git config --global user.name "${{ secrets.GH_SITE_DEPLOY_NAME }}" - name: Setup JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 11 From 2d0879db168ac102239eeb3724cfd3468829140e Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Tue, 12 Mar 2024 12:27:59 +0100 Subject: [PATCH 10/13] Next Generation Dynamic Media: Asset metadata support (#48) --- changes.xml | 3 + pom.xml | 12 + .../ngdm/NextGenDynamicMediaMediaSource.java | 16 +- .../ngdm/NextGenDynamicMediaRendition.java | 49 ++++- .../NextGenDynamicMediaBinaryUrlBuilder.java | 4 +- .../NextGenDynamicMediaConfigService.java | 11 - .../NextGenDynamicMediaConfigServiceImpl.java | 29 --- .../ngdm/impl/NextGenDynamicMediaContext.java | 10 + .../NextGenDynamicMediaImageUrlBuilder.java | 2 +- .../ngdm/impl/metadata/MetadataResponse.java | 52 +++++ .../metadata/NextGenDynamicMediaMetadata.java | 103 +++++++++ .../NextGenDynamicMediaMetadataService.java | 45 ++++ ...extGenDynamicMediaMetadataServiceImpl.java | 206 ++++++++++++++++++ ...NextGenDynamicMediaMetadataUrlBuilder.java | 80 +++++++ .../ngdm/NextGenDynamicMediaTest.java | 18 +- .../NextGenDynamicMediaWithMetadataTest.java | 169 ++++++++++++++ ...xtGenDynamicMediaBinaryUrlBuilderTest.java | 1 + ...tGenDynamicMediaConfigServiceImplTest.java | 7 +- ...extGenDynamicMediaImageUrlBuilderTest.java | 1 + .../ngdm/impl/metadata/MetadataSample.java | 61 ++++++ ...enDynamicMediaMetadataServiceImplTest.java | 149 +++++++++++++ .../NextGenDynamicMediaMetadataTest.java | 75 +++++++ ...GenDynamicMediaMetadataUrlBuilderTest.java | 60 +++++ 23 files changed, 1109 insertions(+), 54 deletions(-) create mode 100644 src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataResponse.java create mode 100644 src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadata.java create mode 100644 src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataService.java create mode 100644 src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImpl.java create mode 100644 src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilder.java create mode 100644 src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaWithMetadataTest.java create mode 100644 src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataSample.java create mode 100644 src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImplTest.java create mode 100644 src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataTest.java create mode 100644 src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilderTest.java diff --git a/changes.xml b/changes.xml index df212ef5..0e727b5b 100644 --- a/changes.xml +++ b/changes.xml @@ -33,6 +33,9 @@ Next Generation Dynamic Media: 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. + diff --git a/pom.xml b/pom.xml index 7590f46d..5e400337 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,12 @@ compile + + org.apache.httpcomponents + httpclient + compile + + com.github.ben-manes.caffeine caffeine @@ -207,6 +213,12 @@ 2.8.0 test + + org.wiremock + wiremock + 3.4.0 + test + 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 7e812dfc..cb6f1491 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSource.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaMediaSource.java @@ -49,6 +49,8 @@ import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaContext; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference; +import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadata; +import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadataService; import io.wcm.sling.models.annotations.AemObject; /** @@ -71,6 +73,8 @@ public final class NextGenDynamicMediaMediaSource extends MediaSource { private MediaHandlerConfig mediaHandlerConfig; @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL) private NextGenDynamicMediaConfigService nextGenDynamicMediaConfig; + @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL) + private NextGenDynamicMediaMetadataService metadataService; @OSGiService private MimeTypeService mimeTypeService; @@ -121,13 +125,23 @@ private boolean isNextGenDynamicMediaEnabled() { return media; } + // If enabled: Fetch asset metadata to validate existence and get original dimensions + NextGenDynamicMediaMetadata metadata = null; + if (metadataService != null && metadataService.isEnabled()) { + metadata = metadataService.fetchMetadata(reference); + if (metadata == null) { + media.setMediaInvalidReason(MediaInvalidReason.MEDIA_REFERENCE_INVALID); + return media; + } + } + // Update media args settings from resource (e.g. alt. text setings) Resource referencedResource = media.getMediaRequest().getResource(); if (referencedResource != null) { updateMediaArgsFromResource(mediaArgs, referencedResource, mediaHandlerConfig); } - NextGenDynamicMediaContext context = new NextGenDynamicMediaContext(reference, media, mediaArgs, + NextGenDynamicMediaContext context = new NextGenDynamicMediaContext(reference, metadata, media, mediaArgs, nextGenDynamicMediaConfig, mediaHandlerConfig, mimeTypeService); NextGenDynamicMediaAsset asset = new NextGenDynamicMediaAsset(context); media.setAsset(asset); 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 f7e89c19..252228f4 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaRendition.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaRendition.java @@ -27,6 +27,8 @@ import org.apache.sling.api.resource.ValueMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.wcm.handler.media.Dimension; import io.wcm.handler.media.MediaArgs; @@ -42,6 +44,7 @@ import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaImageDeliveryParams; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaImageUrlBuilder; import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference; +import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadata; /** * {@link Rendition} implementation for Next Gen. Dynamic Media remote assets. @@ -49,6 +52,8 @@ final class NextGenDynamicMediaRendition implements Rendition { private final NextGenDynamicMediaContext context; + private final NextGenDynamicMediaMetadata metadata; + private final Dimension originalDimension; private final NextGenDynamicMediaReference reference; private final MediaArgs mediaArgs; private final String url; @@ -56,8 +61,17 @@ final class NextGenDynamicMediaRendition implements Rendition { private long width; private long height; + private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaRendition.class); + NextGenDynamicMediaRendition(@NotNull NextGenDynamicMediaContext context, @NotNull MediaArgs mediaArgs) { this.context = context; + this.metadata = context.getMetadata(); + if (this.metadata != null) { + this.originalDimension = metadata.getDimension(); + } + else { + this.originalDimension = null; + } this.reference = context.getReference(); this.mediaArgs = mediaArgs; this.width = mediaArgs.getFixedWidth(); @@ -69,6 +83,7 @@ final class NextGenDynamicMediaRendition implements Rendition { this.resolvedMediaFormat = firstMediaFormat; if (this.width == 0) { this.width = firstMediaFormat.getEffectiveMinWidth(); + this.height = firstMediaFormat.getEffectiveMinHeight(); } } @@ -76,6 +91,10 @@ final class NextGenDynamicMediaRendition implements Rendition { // deliver as binary this.url = buildBinaryUrl(); } + else if (isRequestedDimensionLargerThanOriginal()) { + // image upscaling is not supported + this.url = null; + } else { // deliver scaled image rendition this.url = buildImageRenditionUrl(); @@ -108,6 +127,23 @@ private String buildImageRenditionUrl() { return new NextGenDynamicMediaImageUrlBuilder(context).build(params); } + /** + * Checks if the original dimension is available in remote asset metadata, and if that dimension + * is smaller than the requested width/height. Upscaling should be avoided. + * @return true if requested dimension is larger than original dimension + */ + private boolean isRequestedDimensionLargerThanOriginal() { + if (originalDimension != null + && (this.width > originalDimension.getWidth() || this.height > originalDimension.getHeight())) { + if (log.isTraceEnabled()) { + log.trace("Requested dimension {} is larger than original image dimension {} of {}", + new Dimension(this.width, this.height), originalDimension, context.getReference()); + } + return true; + } + return false; + } + /** * Build URL which points directly to the binary file. */ @@ -148,7 +184,12 @@ public long getFileSize() { @Override public @Nullable String getMimeType() { - return context.getMimeTypeService().getMimeType(getFileExtension()); + if (this.metadata != null) { + return this.metadata.getMimeType(); + } + else { + return context.getMimeTypeService().getMimeType(getFileExtension()); + } } @Override @@ -183,11 +224,17 @@ public boolean isDownload() { @Override public long getWidth() { + if (width == 0 && originalDimension != null) { + return originalDimension.getWidth(); + } return width; } @Override public long getHeight() { + if (height == 0 && originalDimension != null) { + return originalDimension.getHeight(); + } return height; } 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 6beb0515..4184ea62 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 @@ -30,7 +30,7 @@ * Builds URL to reference a binary file via NextGen Dynamic Media. *

* Example URL that might be build: - * https://host/adobe/assets/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-file.pdf + * https://host/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/original/as/my-file.pdf *

*/ public final class NextGenDynamicMediaBinaryUrlBuilder { @@ -45,7 +45,7 @@ public NextGenDynamicMediaBinaryUrlBuilder(@NotNull NextGenDynamicMediaContext c } /** - * Builds the URL for a rendition. + * Builds the URL for a binary. * @return URL or null if invalid/not possible */ public @Nullable String build() { 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 ff2c45e4..5779fe1a 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,10 +19,6 @@ */ package io.wcm.handler.mediasource.ngdm.impl; -import java.util.Map; - -import org.jetbrains.annotations.NotNull; - /** * Service to access Next Generation Dynamic Media configuration. */ @@ -109,13 +105,6 @@ public interface NextGenDynamicMediaConfigService { */ String getAssetMetadataPath(); - /** - * HTTP headers to be send with the asset metadata request. - * @return Asset Metadata Headers - */ - @NotNull - Map getAssetMetadataHeaders(); - /** * Gets the Next Generation Dynamic Media tenant (also known technically as the repository ID). * @return the repository ID 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 d300453e..8af01ebf 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 @@ -19,12 +19,7 @@ */ package io.wcm.handler.mediasource.ngdm.impl; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -73,12 +68,6 @@ 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 = "Asset Metadata Headers", - description = "HTTP headers to be send with the asset metadata request. " - + "Format: 'header1:value1'.") - String[] assetMetadataHeaders() default { "X-Adobe-Accept-Experimental:1" }; - } private static final String ADOBE_ASSETS_PREFIX = "/adobe/assets/"; @@ -87,7 +76,6 @@ String assetOriginalBinaryDeliveryPath() default ADOBE_ASSETS_PREFIX + PLACEHOLD private String imageDeliveryBasePath; private String assetOriginalBinaryDeliveryPath; private String assetMetadataPath; - private Map assetMetadataHeaders; @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY) private NextGenDynamicMediaConfig nextGenDynamicMediaConfig; @@ -103,19 +91,7 @@ private void activate(Config config) { this.nextGenDynamicMediaConfig.getAssetOriginalBinaryDeliveryPath()); this.assetMetadataPath = StringUtils.defaultIfBlank(config.assetMetadataPath(), this.nextGenDynamicMediaConfig.getAssetMetadataPath()); - this.assetMetadataHeaders = headersToMap(config.assetMetadataHeaders()); - - } - private static Map headersToMap(String[] headers) { - Map map = new LinkedHashMap<>(); - for (String header : headers) { - String[] parts = header.split(":", 2); - if (parts.length == 2) { - map.put(parts[0], parts[1]); - } - } - return map; } @Override @@ -148,11 +124,6 @@ public String getAssetMetadataPath() { return assetMetadataPath; } - @Override - public @NotNull Map getAssetMetadataHeaders() { - return Collections.unmodifiableMap(assetMetadataHeaders); - } - @Override public String getRepositoryId() { return this.nextGenDynamicMediaConfig.getRepositoryId(); diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaContext.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaContext.java index 3644ee6f..2fe2f431 100644 --- a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaContext.java +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/NextGenDynamicMediaContext.java @@ -21,10 +21,12 @@ import org.apache.sling.commons.mime.MimeTypeService; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import io.wcm.handler.media.Media; import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.spi.MediaHandlerConfig; +import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadata; /** * Relevant context objects for media resolution. @@ -32,6 +34,7 @@ public final class NextGenDynamicMediaContext { private final NextGenDynamicMediaReference reference; + private final NextGenDynamicMediaMetadata metadata; private final Media media; private final MediaArgs defaultMediaArgs; private final NextGenDynamicMediaConfigService nextGenDynamicMediaConfig; @@ -40,18 +43,21 @@ public final class NextGenDynamicMediaContext { /** * @param reference NextGen Dynamic Media reference + * @param metadata NextGen Dynamic Media metadata (if enabled) * @param defaultMediaArgs Default media args * @param nextGenDynamicMediaConfig NextGen Dynamic Media config * @param mediaHandlerConfig Media handler config * @param mimeTypeService Mime type service */ public NextGenDynamicMediaContext(@NotNull NextGenDynamicMediaReference reference, + @Nullable NextGenDynamicMediaMetadata metadata, @NotNull Media media, @NotNull MediaArgs defaultMediaArgs, @NotNull NextGenDynamicMediaConfigService nextGenDynamicMediaConfig, @NotNull MediaHandlerConfig mediaHandlerConfig, @NotNull MimeTypeService mimeTypeService) { this.reference = reference; + this.metadata = metadata; this.media = media; this.defaultMediaArgs = defaultMediaArgs; this.nextGenDynamicMediaConfig = nextGenDynamicMediaConfig; @@ -63,6 +69,10 @@ public NextGenDynamicMediaContext(@NotNull NextGenDynamicMediaReference referenc return this.reference; } + public @Nullable NextGenDynamicMediaMetadata getMetadata() { + return this.metadata; + } + public @NotNull Media getMedia() { return this.media; } 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 e14591fd..78d45283 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 @@ -42,7 +42,7 @@ * Builds URL to render image rendition via NextGen Dynamic Media. *

* Example URL that might be build: - * https://host/adobe/dynamicmedia/deliver/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/my-image.jpg?preferwebp=true&quality=85&width=300&crop=16:9,smart + * https://host/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/as/my-image.jpg?preferwebp=true&quality=85&width=300&crop=16:9,smart *

*/ public final class NextGenDynamicMediaImageUrlBuilder { 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 new file mode 100644 index 00000000..86719a34 --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataResponse.java @@ -0,0 +1,52 @@ +/* + * #%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 com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Used for Jackson Object mapping of JSON response from NGDM HTTP API. + */ +@SuppressWarnings({ "checkstyle:VisibilityModifierCheck", "java:S1104" }) +@SuppressFBWarnings("UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD") +@JsonIgnoreProperties(ignoreUnknown = true) +final class MetadataResponse { + + public RepositoryMetadata repositoryMetadata; + public AssetMetadata assetMetadata; + + @JsonIgnoreProperties(ignoreUnknown = true) + static final class RepositoryMetadata { + @JsonProperty("dc:format") + public String dcFormat; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + static final class AssetMetadata { + @JsonProperty("tiff:ImageWidth") + public long tiffImageWidth; + @JsonProperty("tiff:ImageLength") + public long tiffImageLength; + } + +} 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 new file mode 100644 index 00000000..3d24c2ef --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadata.java @@ -0,0 +1,103 @@ +/* + * #%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 java.util.Objects; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.json.JsonMapper; + +import io.wcm.handler.media.Dimension; +import io.wcm.wcm.commons.contenttype.ContentType; + +/** + * Metadata for Next Gen Dynamic Media asset fetched from the HTTP API. + */ +public final class NextGenDynamicMediaMetadata { + + private final String mimeType; + private final Dimension dimension; + + private static final JsonMapper OBJECT_MAPPER = new JsonMapper(); + + NextGenDynamicMediaMetadata(@Nullable String mimeType, long width, long height) { + this.mimeType = mimeType; + if (width > 0 && height > 0) { + this.dimension = new Dimension(width, height); + } + else { + this.dimension = null; + } + } + + /** + * @return Mime type + */ + public @NotNull String getMimeType() { + return Objects.toString(mimeType, ContentType.OCTET_STREAM); + } + + /** + * @return Image Dimension or null if no image or dimension not available + */ + public @Nullable Dimension getDimension() { + return dimension; + } + + boolean isValid() { + return mimeType != null; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.NO_CLASS_NAME_STYLE); + } + + /** + * Converts JSON response from NGDM API to metadata object. + * @param jsonResponse JSON response + * @return Metadata object + * @throws JsonProcessingException If JSON parsing fails + */ + @SuppressWarnings("null") + public static @NotNull NextGenDynamicMediaMetadata fromJson(@NotNull String jsonResponse) throws JsonProcessingException { + MetadataResponse response = OBJECT_MAPPER.readValue(jsonResponse, MetadataResponse.class); + + String mimeType = null; + if (response.repositoryMetadata != null) { + mimeType = response.repositoryMetadata.dcFormat; + } + + long width = 0; + long height = 0; + if (response.assetMetadata != null) { + width = response.assetMetadata.tiffImageWidth; + height = response.assetMetadata.tiffImageLength; + } + + return new NextGenDynamicMediaMetadata(mimeType, width, height); + } + +} diff --git a/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataService.java b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataService.java new file mode 100644 index 00000000..3669dc3b --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataService.java @@ -0,0 +1,45 @@ +/* + * #%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.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference; + +/** + * Fetches metadata for Next Gen Dynamic Media assets via the HTTP API. + */ +public interface NextGenDynamicMediaMetadataService { + + /** + * @return true if metadata fetching is enabled. + */ + boolean isEnabled(); + + /** + * Fetch asset metadata. + * @param reference Asset reference + * @return Asset metadata or null if not available + */ + @Nullable + NextGenDynamicMediaMetadata fetchMetadata(@NotNull NextGenDynamicMediaReference reference); + +} 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 new file mode 100644 index 00000000..cbcee591 --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImpl.java @@ -0,0 +1,206 @@ +/* + * #%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 java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicHeader; +import org.apache.http.util.EntityUtils; +import org.jetbrains.annotations.NotNull; +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.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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference; + +/** + * Fetches metadata for Next Gen Dynamic Media assets via the HTTP API. + */ +@Component(service = NextGenDynamicMediaMetadataService.class, immediate = true) +@Designate(ocd = NextGenDynamicMediaMetadataServiceImpl.Config.class) +public class NextGenDynamicMediaMetadataServiceImpl implements NextGenDynamicMediaMetadataService { + + @ObjectClassDefinition( + name = "wcm.io Next Generation Dynamic Media Metadata Service", + description = "Fetches metadata for Next Generation Dynamic Media 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; + + @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" }; + + @AttributeDefinition( + name = "Connect Timeout", + description = "HTTP Connect timeout in milliseconds.") + int connectTimeout() default 5000; + + @AttributeDefinition( + name = "Connection Request Timeout", + description = "HTTP connection request timeout in milliseconds.") + int connectionRequestTimeout() default 5000; + + @AttributeDefinition( + name = "Socket Timeout", + description = "HTTP socket timeout in milliseconds.") + int socketTimeout() default 5000; + + @AttributeDefinition( + name = "Proxy Host", + description = "Proxy host name") + String proxyHost(); + + @AttributeDefinition( + name = "Proxy Port", + description = "Proxy port") + int proxyPort(); + + } + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY) + private NextGenDynamicMediaConfigService nextGenDynamicMediaConfig; + + private boolean enabled; + private CloseableHttpClient httpClient; + + private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaMetadataServiceImpl.class); + + @Activate + private void activate(Config config) { + this.enabled = config.enabled(); + if (enabled) { + httpClient = createHttpClient(config); + } + } + + private static CloseableHttpClient createHttpClient(Config config) { + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(config.connectTimeout()) + .setConnectionRequestTimeout(config.connectionRequestTimeout()) + .setSocketTimeout(config.socketTimeout()) + .build(); + HttpClientBuilder builder = HttpClientBuilder.create() + .setDefaultRequestConfig(requestConfig) + .setDefaultHeaders(convertHeaders(config.httpHeaders())); + if (StringUtils.isNotBlank(config.proxyHost()) && config.proxyPort() > 0) { + builder.setProxy(new HttpHost(config.proxyHost(), config.proxyPort())); + } + return builder.build(); + } + + private static Collection
convertHeaders(String[] headers) { + List
result = new ArrayList<>(); + for (String header : headers) { + String[] parts = header.split(":", 2); + if (parts.length == 2) { + result.add(new BasicHeader(parts[0], parts[1])); + } + } + return result; + } + + @Deactivate + private void deactivate() throws IOException { + if (httpClient != null) { + httpClient.close(); + } + } + + @Override + public boolean isEnabled() { + return enabled; + } + + /** + * Fetch asset metadata. + * @param reference Asset reference + * @return Valid asset metadata or null if not available or metadata is invalid + */ + @Override + public @Nullable NextGenDynamicMediaMetadata fetchMetadata(@NotNull NextGenDynamicMediaReference reference) { + if (!enabled) { + return null; + } + String metadataUrl = new NextGenDynamicMediaMetadataUrlBuilder(nextGenDynamicMediaConfig).build(reference); + if (metadataUrl == null) { + return null; + } + + HttpGet httpGet = new HttpGet(metadataUrl); + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + return processResponse(response, metadataUrl); + } + catch (IOException ex) { + log.warn("Unable to fetch NGDM asset metadata from URL {}", metadataUrl, ex); + return null; + } + } + + private @Nullable NextGenDynamicMediaMetadata processResponse(@NotNull CloseableHttpResponse response, + @NotNull String metadataUrl) throws IOException { + switch (response.getStatusLine().getStatusCode()) { + case HttpStatus.SC_OK: + String jsonResponse = EntityUtils.toString(response.getEntity()); + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(jsonResponse); + log.trace("HTTP response for NGDM asset metadata {} returns: {}", metadataUrl, metadata); + if (metadata.isValid()) { + return metadata; + } + break; + case HttpStatus.SC_NOT_FOUND: + log.trace("HTTP response for NGDM asset metadata {} returns HTTP 404", metadataUrl); + break; + default: + log.warn("Unexpected HTTP response for NGDM asset metadata {}: {}", metadataUrl, response.getStatusLine()); + break; + } + return null; + } + +} 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 new file mode 100644 index 00000000..44c5726c --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilder.java @@ -0,0 +1,80 @@ +/* + * #%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 static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService.PLACEHOLDER_ASSET_ID; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference; + +/** + * Builds URL to reference a asset metadata via NextGen Dynamic Media. + *

+ * Example URL that might be build: + * https://host/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/metadata + *

+ */ +final class NextGenDynamicMediaMetadataUrlBuilder { + + private final NextGenDynamicMediaConfigService config; + + /** + * @param config Config + */ + NextGenDynamicMediaMetadataUrlBuilder(@NotNull NextGenDynamicMediaConfigService config) { + this.config = config; + } + + /** + * Builds the URL for metadata. + * @return URL or null if invalid/not possible + */ + public @Nullable String build(@NotNull NextGenDynamicMediaReference reference) { + + // get parameters from nextgen dynamic media config for URL parameters + String repositoryId = config.getRepositoryId(); + String metadataPath = config.getAssetMetadataPath(); + if (StringUtils.isAnyEmpty(repositoryId, metadataPath)) { + return null; + } + + // replace placeholders in delivery path + metadataPath = StringUtils.replace(metadataPath, PLACEHOLDER_ASSET_ID, reference.getAssetId()); + + // build URL + StringBuilder url = new StringBuilder(); + if (StringUtils.startsWith(repositoryId, "localhost:")) { + // switch to HTTP for unit tests/local testing + url.append("http"); + } + else { + url.append("https"); + } + url.append("://") + .append(repositoryId) + .append(metadataPath); + return url.toString(); + } + +} 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 3649a41a..2e4ad3d4 100644 --- a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaTest.java @@ -91,8 +91,18 @@ void testAsset() { assertUrl(asset.getDefaultRendition(), "preferwebp=true&quality=85", "jpg"); + // default rendition + Rendition defaultRendition = asset.getDefaultRendition(); + assertNotNull(defaultRendition); + assertEquals(ContentType.JPEG, defaultRendition.getMimeType()); + assertEquals(0, defaultRendition.getWidth()); + assertEquals(0, defaultRendition.getHeight()); + assertUrl(defaultRendition, "preferwebp=true&quality=85", "jpg"); + + // fixed rendition Rendition fixedRendition = asset.getRendition(new MediaArgs().fixedDimension(100, 50)); assertNotNull(fixedRendition); + assertEquals(ContentType.JPEG, fixedRendition.getMimeType()); assertUrl(fixedRendition, "crop=100%3A50%2Csmart&preferwebp=true&quality=85&width=100", "jpg"); assertNotNull(asset.getImageRendition(new MediaArgs())); @@ -106,12 +116,13 @@ void testAsset() { void testRendition_16_10() { Media media = mediaHandler.get(resource) .mediaFormat(DummyMediaFormats.RATIO_16_10) + .fixedWidth(2048) .build(); assertTrue(media.isValid()); Rendition rendition = media.getRendition(); assertNotNull(rendition); - assertUrl(rendition, "crop=16%3A10%2Csmart&preferwebp=true&quality=85", "jpg"); + assertUrl(rendition, "crop=16%3A10%2Csmart&preferwebp=true&quality=85&width=2048", "jpg"); assertNull(rendition.getPath()); assertEquals(SAMPLE_FILENAME, rendition.getFileName()); @@ -124,8 +135,8 @@ void testRendition_16_10() { assertTrue(rendition.isBrowserImage()); assertFalse(rendition.isVectorImage()); assertFalse(rendition.isDownload()); - assertEquals(0, rendition.getWidth()); - assertEquals(0, rendition.getHeight()); + assertEquals(2048, rendition.getWidth()); + assertEquals(1280, rendition.getHeight()); assertNull(rendition.getModificationDate()); assertFalse(rendition.isFallback()); assertNull(rendition.adaptTo(Resource.class)); @@ -211,6 +222,7 @@ 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()); } diff --git a/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaWithMetadataTest.java b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaWithMetadataTest.java new file mode 100644 index 00000000..7f38d46a --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/NextGenDynamicMediaWithMetadataTest.java @@ -0,0 +1,169 @@ +/* + * #%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; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +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.metadata.MetadataSample.METADATA_JSON_IMAGE; +import static io.wcm.handler.mediasource.ngdm.impl.metadata.MetadataSample.METADATA_JSON_PDF; +import static org.junit.jupiter.api.Assertions.assertEquals; +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.http.HttpStatus; +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.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +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.MediaNameConstants; +import io.wcm.handler.media.Rendition; +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigServiceImpl; +import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadataServiceImpl; +import io.wcm.sling.commons.adapter.AdaptTo; +import io.wcm.testing.mock.aem.dam.ngdm.MockNextGenDynamicMediaConfig; +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) +@WireMockTest +class NextGenDynamicMediaWithMetadataTest { + + private final AemContext context = AppAemContext.newAemContext(); + + private MockNextGenDynamicMediaConfig nextGenDynamicMediaConfig; + private MediaHandler mediaHandler; + private Resource resource; + + @BeforeEach + @SuppressWarnings("null") + void setUp(WireMockRuntimeInfo wmRuntimeInfo) { + nextGenDynamicMediaConfig = context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class); + nextGenDynamicMediaConfig.setEnabled(true); + nextGenDynamicMediaConfig.setRepositoryId("localhost:" + wmRuntimeInfo.getHttpPort()); + context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + context.registerInjectActivateService(NextGenDynamicMediaMetadataServiceImpl.class, + "enabled", true); + + resource = context.create().resource(context.currentPage(), "test", + MediaNameConstants.PN_MEDIA_REF, SAMPLE_REFERENCE); + + mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + } + + @Test + void testAsset() { + stubFor(get("/adobe/assets/" + SAMPLE_ASSET_ID + "/metadata") + .willReturn(aResponse() + .withStatus(HttpStatus.SC_OK) + .withHeader("Content-Type", ContentType.JSON) + .withBody(METADATA_JSON_IMAGE))); + + Media media = mediaHandler.get(resource) + .build(); + assertTrue(media.isValid()); + assertUrl(media, "preferwebp=true&quality=85", "jpg"); + + Asset asset = media.getAsset(); + assertNotNull(asset); + assertEquals(SAMPLE_FILENAME, asset.getTitle()); + assertNull(asset.getAltText()); + assertNull(asset.getDescription()); + assertEquals(SAMPLE_REFERENCE, asset.getPath()); + assertEquals(ValueMap.EMPTY, asset.getProperties()); + assertNull(asset.adaptTo(Resource.class)); + + assertUrl(asset.getDefaultRendition(), "preferwebp=true&quality=85", "jpg"); + + // default rendition + Rendition defaultRendition = asset.getDefaultRendition(); + assertNotNull(defaultRendition); + assertEquals(ContentType.JPEG, defaultRendition.getMimeType()); + assertEquals(1200, defaultRendition.getWidth()); + assertEquals(800, defaultRendition.getHeight()); + assertUrl(defaultRendition, "preferwebp=true&quality=85", "jpg"); + + // fixed rendition + Rendition fixedRendition = asset.getRendition(new MediaArgs().fixedDimension(100, 50)); + assertNotNull(fixedRendition); + assertEquals(ContentType.JPEG, fixedRendition.getMimeType()); + assertEquals(100, fixedRendition.getWidth()); + assertEquals(50, fixedRendition.getHeight()); + assertUrl(fixedRendition, "crop=100%3A50%2Csmart&preferwebp=true&quality=85&width=100", "jpg"); + + // avoid upscaling + Rendition tooLargeRendition = asset.getRendition(new MediaArgs().fixedDimension(2048, 1024)); + assertNull(tooLargeRendition); + } + + @Test + @SuppressWarnings("null") + void testPDFDownload() { + stubFor(get("/adobe/assets/" + SAMPLE_ASSET_ID + "/metadata") + .willReturn(aResponse() + .withStatus(HttpStatus.SC_OK) + .withHeader("Content-Type", ContentType.JSON) + .withBody(METADATA_JSON_PDF))); + + Resource downloadResource = context.create().resource(context.currentPage(), "download", + MediaNameConstants.PN_MEDIA_REF, "/" + SAMPLE_ASSET_ID + "/myfile.pdf"); + + Media media = mediaHandler.get(downloadResource) + .args(new MediaArgs().download(true)) + .build(); + assertTrue(media.isValid()); + + Rendition rendition = media.getRendition(); + assertNotNull(rendition); + assertEquals(ContentType.PDF, rendition.getMimeType()); + assertEquals( + "https://" + nextGenDynamicMediaConfig.getRepositoryId() + "/adobe/assets/" + SAMPLE_ASSET_ID + "/original/as/myfile.pdf?accept-experimental=1", + rendition.getUrl()); + } + + private void assertUrl(Media media, String urlParams, String extension) { + assertEquals(buildUrl(urlParams, extension), media.getUrl()); + } + + private void assertUrl(Rendition rendition, String urlParams, String extension) { + assertEquals(buildUrl(urlParams, extension), rendition.getUrl()); + } + + 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; + } + +} 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 734dc27c..ee17b7df 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 @@ -67,6 +67,7 @@ private NextGenDynamicMediaBinaryUrlBuilder getBuilder(MediaArgs mediaArgs) { NextGenDynamicMediaContext ctx = new NextGenDynamicMediaContext( NextGenDynamicMediaReference.fromReference(SAMPLE_REFERENCE), null, + null, mediaArgs, nextGenDynamicMediaConfig, mediaHandlerConfig, 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 6de62284..8ed82bbf 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 @@ -22,8 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Map; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -62,7 +60,6 @@ void testPropertiesDefaultConfig() { assertEquals("/videopath1", underTest.getVideoDeliveryPath()); assertEquals("/adobe/assets/{asset-id}/original/as/{seo-name}?accept-experimental=1", underTest.getAssetOriginalBinaryDeliveryPath()); assertEquals("/adobe/assets/{asset-id}/metadata", underTest.getAssetMetadataPath()); - assertEquals(Map.of("X-Adobe-Accept-Experimental", "1"), underTest.getAssetMetadataHeaders()); assertEquals("repo1", underTest.getRepositoryId()); assertEquals("key1", underTest.getApiKey()); assertEquals("env1", underTest.getEnv()); @@ -74,15 +71,13 @@ void testPropertiesEmptyConfig() { NextGenDynamicMediaConfigService underTest = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class, "imageDeliveryBasePath", "", "assetOriginalBinaryDeliveryPath", "", - "assetMetadataPath", "", - "assetMetadataHeaders", new String[0]); + "assetMetadataPath", ""); assertTrue(underTest.enabled()); assertEquals("/selector1", underTest.getAssetSelectorsJsUrl()); assertEquals("/imagepath1", underTest.getImageDeliveryBasePath()); assertEquals("/videopath1", underTest.getVideoDeliveryPath()); assertEquals("/assetpath1", underTest.getAssetOriginalBinaryDeliveryPath()); assertEquals("/metadatapath1", underTest.getAssetMetadataPath()); - assertEquals(Map.of(), underTest.getAssetMetadataHeaders()); assertEquals("repo1", underTest.getRepositoryId()); assertEquals("key1", underTest.getApiKey()); assertEquals("env1", underTest.getEnv()); 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 6e4eff6b..595c3a16 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 @@ -129,6 +129,7 @@ private NextGenDynamicMediaImageUrlBuilder getBuilder(MediaArgs mediaArgs) { NextGenDynamicMediaContext ctx = new NextGenDynamicMediaContext( NextGenDynamicMediaReference.fromReference(SAMPLE_REFERENCE), null, + null, mediaArgs, nextGenDynamicMediaConfig, mediaHandlerConfig, 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 new file mode 100644 index 00000000..58bc8c82 --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/MetadataSample.java @@ -0,0 +1,61 @@ +/* + * #%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 static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_ASSET_ID; + +/** + * Next Gen Dynamic Media Metadata samples. + */ +public final class MetadataSample { + + public static final String METADATA_JSON_IMAGE = "{" + + " \"assetId\": \"" + SAMPLE_ASSET_ID + "\"," + + " \"repositoryMetadata\": {" + + " \"repo:name\": \"test.jpg\"," + + " \"dc:format\": \"image/jpeg\"" + + " }," + + " \"assetMetadata\": {" + + " \"dam:assetStatus\": \"approved\"," + + " \"dc:description\": \"Test Description\"," + + " \"dc:title\": \"Test Image\"," + + " \"tiff:ImageLength\": 800," + + " \"tiff:ImageWidth\": 1200" + + " }" + + "}"; + + public static final String METADATA_JSON_PDF = "{" + + " \"assetId\": \"" + SAMPLE_ASSET_ID + "\"," + + " \"repositoryMetadata\": {" + + " \"repo:name\": \"test.pdf\"," + + " \"dc:format\": \"application/pdf\"" + + " }," + + " \"assetMetadata\": {" + + " \"dam:assetStatus\": \"approved\"," + + " \"dc:description\": \"Test Description\"," + + " \"dc:title\": \"Test Document\"" + + " }" + + "}"; + + private MetadataSample() { + // constants only + } + +} 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 new file mode 100644 index 00000000..eb12ac92 --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataServiceImplTest.java @@ -0,0 +1,149 @@ +/* + * #%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 static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_ASSET_ID; +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 static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +import io.wcm.handler.media.Dimension; +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigServiceImpl; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference; +import io.wcm.testing.mock.aem.dam.ngdm.MockNextGenDynamicMediaConfig; +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) +@WireMockTest +class NextGenDynamicMediaMetadataServiceImplTest { + + private static final NextGenDynamicMediaReference REFERENCE = NextGenDynamicMediaReference.fromReference(SAMPLE_REFERENCE); + + private final AemContext context = AppAemContext.newAemContext(); + + @BeforeEach + void setUp(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class) + .setRepositoryId("localhost:" + wmRuntimeInfo.getHttpPort()); + context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + } + + @Test + void testNotFound() { + NextGenDynamicMediaMetadataService underTest = context.registerInjectActivateService(NextGenDynamicMediaMetadataServiceImpl.class, + "enabled", true); + NextGenDynamicMediaMetadata metadata = underTest.fetchMetadata(REFERENCE); + assertNull(metadata); + } + + @Test + void testValidResponse() { + NextGenDynamicMediaMetadataService underTest = context.registerInjectActivateService(NextGenDynamicMediaMetadataServiceImpl.class, + "enabled", true, + "httpHeaders", new String[] { "header1:1", "header2:2" }); + stubFor(get("/adobe/assets/" + SAMPLE_ASSET_ID + "/metadata") + .withHeader("header1", equalTo("1")) + .withHeader("header2", equalTo("2")) + .willReturn(aResponse() + .withStatus(HttpStatus.SC_OK) + .withHeader("Content-Type", ContentType.JSON) + .withBody(METADATA_JSON_IMAGE))); + + NextGenDynamicMediaMetadata metadata = underTest.fetchMetadata(REFERENCE); + assertNotNull(metadata); + Dimension dimension = metadata.getDimension(); + assertNotNull(dimension); + assertEquals(1200, dimension.getWidth()); + assertEquals(800, dimension.getHeight()); + assertEquals("image/jpeg", metadata.getMimeType()); + } + + @Test + void testValidResponse_Disabled() { + NextGenDynamicMediaMetadataService underTest = context.registerInjectActivateService(NextGenDynamicMediaMetadataServiceImpl.class, + "enabled", false); + stubFor(get("/adobe/assets/" + SAMPLE_ASSET_ID + "/metadata") + .willReturn(aResponse() + .withStatus(HttpStatus.SC_OK) + .withHeader("Content-Type", ContentType.JSON) + .withBody(METADATA_JSON_IMAGE))); + + NextGenDynamicMediaMetadata metadata = underTest.fetchMetadata(REFERENCE); + assertNull(metadata); + } + + @Test + void testEmptyJson() { + NextGenDynamicMediaMetadataService underTest = context.registerInjectActivateService(NextGenDynamicMediaMetadataServiceImpl.class, + "enabled", true); + stubFor(get("/adobe/assets/" + SAMPLE_ASSET_ID + "/metadata") + .willReturn(aResponse() + .withStatus(HttpStatus.SC_OK) + .withHeader("Content-Type", ContentType.JSON) + .withBody("{}"))); + + NextGenDynamicMediaMetadata metadata = underTest.fetchMetadata(REFERENCE); + assertNull(metadata); + } + + @Test + void testInvalidResponse() { + NextGenDynamicMediaMetadataService underTest = context.registerInjectActivateService(NextGenDynamicMediaMetadataServiceImpl.class, + "enabled", true); + stubFor(get("/adobe/assets/" + SAMPLE_ASSET_ID + "/metadata") + .willReturn(aResponse() + .withStatus(HttpStatus.SC_OK) + .withHeader("Content-Type", ContentType.JSON) + .withBody("no json"))); + + NextGenDynamicMediaMetadata metadata = underTest.fetchMetadata(REFERENCE); + assertNull(metadata); + } + + @Test + void testUnexpectdReturnCode() { + NextGenDynamicMediaMetadataService underTest = context.registerInjectActivateService(NextGenDynamicMediaMetadataServiceImpl.class, + "enabled", true); + stubFor(get("/adobe/assets/" + SAMPLE_ASSET_ID + "/metadata") + .willReturn(aResponse() + .withStatus(HttpStatus.SC_FORBIDDEN))); + + NextGenDynamicMediaMetadata metadata = underTest.fetchMetadata(REFERENCE); + assertNull(metadata); + } + +} 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 new file mode 100644 index 00000000..b49ff379 --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataTest.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 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 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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import io.wcm.handler.media.Dimension; +import io.wcm.wcm.commons.contenttype.ContentType; + +class NextGenDynamicMediaMetadataTest { + + @Test + void testEmptyJson() throws JsonProcessingException { + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson("{}"); + assertEquals(ContentType.OCTET_STREAM, metadata.getMimeType()); + assertNull(metadata.getDimension()); + assertFalse(metadata.isValid()); + assertEquals("[dimension=,mimeType=]", metadata.toString()); + } + + @Test + void testSampleJson_Image() throws JsonProcessingException { + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(METADATA_JSON_IMAGE); + assertEquals("image/jpeg", metadata.getMimeType()); + Dimension dimension = metadata.getDimension(); + assertNotNull(dimension); + assertEquals(1200, dimension.getWidth()); + assertEquals(800, dimension.getHeight()); + assertTrue(metadata.isValid()); + assertEquals("[dimension=[width=1200,height=800],mimeType=image/jpeg]", metadata.toString()); + } + + @Test + void testSampleJson_PDF() throws JsonProcessingException { + NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(METADATA_JSON_PDF); + assertEquals("application/pdf", metadata.getMimeType()); + assertNull(metadata.getDimension()); + assertTrue(metadata.isValid()); + assertEquals("[dimension=,mimeType=application/pdf]", metadata.toString()); + } + + @Test + void testInvalidJson() { + assertThrows(JsonProcessingException.class, () -> NextGenDynamicMediaMetadata.fromJson("no json")); + } + +} 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 new file mode 100644 index 00000000..63bb31ee --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/ngdm/impl/metadata/NextGenDynamicMediaMetadataUrlBuilderTest.java @@ -0,0 +1,60 @@ +/* + * #%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 static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_ASSET_ID; +import static io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReferenceSample.SAMPLE_FILENAME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigServiceImpl; +import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference; +import io.wcm.testing.mock.aem.dam.ngdm.MockNextGenDynamicMediaConfig; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; + +@ExtendWith(AemContextExtension.class) +class NextGenDynamicMediaMetadataUrlBuilderTest { + + private final AemContext context = AppAemContext.newAemContext(); + + private NextGenDynamicMediaConfigService nextGenDynamicMediaConfig; + + @BeforeEach + void setUp() throws Exception { + context.registerInjectActivateService(MockNextGenDynamicMediaConfig.class) + .setRepositoryId("repo1"); + nextGenDynamicMediaConfig = context.registerInjectActivateService(NextGenDynamicMediaConfigServiceImpl.class); + } + + @Test + void testBuild() { + NextGenDynamicMediaMetadataUrlBuilder underTest = new NextGenDynamicMediaMetadataUrlBuilder(nextGenDynamicMediaConfig); + + assertEquals("https://repo1/adobe/assets/urn:aaid:aem:12345678-abcd-abcd-abcd-abcd12345678/metadata", + underTest.build(new NextGenDynamicMediaReference(SAMPLE_ASSET_ID, SAMPLE_FILENAME))); + } + +} From 6dda1d38069f9672d6fc533b055f8a4d15f830f2 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Tue, 12 Mar 2024 13:51:33 +0100 Subject: [PATCH 11/13] add next gen DM documentation --- changes.xml | 2 +- src/site/markdown/dynamic-media.md | 16 +++--- src/site/markdown/index.md | 6 +- src/site/markdown/nextgen-dynamic-media.md | 65 ++++++++++++++++++++++ 4 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 src/site/markdown/nextgen-dynamic-media.md diff --git a/changes.xml b/changes.xml index 0e727b5b..a379ae13 100644 --- a/changes.xml +++ b/changes.xml @@ -47,7 +47,7 @@ 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 improved in subsequent releases. + Add support for 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/src/site/markdown/dynamic-media.md b/src/site/markdown/dynamic-media.md index 998e80cf..ed138c37 100644 --- a/src/site/markdown/dynamic-media.md +++ b/src/site/markdown/dynamic-media.md @@ -8,14 +8,14 @@ wcm.io Media Handler optionally supports the [Dynamic Media][aem-dynamic-media] Only the "Scene7" mode is supported (see [Setting Up Dynamic Media][aem-dynamic-media-administration]). The old and deprecated "hybrid" mode is not supported. -The dynamic media support automatically gets active when the instance was set up with the additional `dynamicmedia_scene7` run mode, and the assets are replicated to the Dynamic Media servers. +The Dynamic Media support automatically gets active when the instance was set up with the additional `dynamicmedia_scene7` run mode, and the assets are replicated to the Dynamic Media servers. ### Dynamic Media concept -The integration with 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. +The integration with 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 dynamic media is active, the media handler returns rendition URLs pointing to the Dynamic Media delivery servers instead of the Servlet the renders the renditions inside the publish instance without dynamic media. From the [supported file formats][file-format-support] dynamic media is used for JPEG, PNG and TIFF images. It is not used for GIF images (potentially animated) or SVG images (which can be scaled by the browser itself). +If Dynamic Media is active, the media handler returns rendition URLs pointing to the Dynamic Media delivery servers instead of the Servlet the renders the renditions inside the publish instance without Dynamic Media. From the [supported file formats][file-format-support] Dynamic Media supports dynamic renditions for JPEG, PNG and TIFF images. All other file formats including GIF and SVG images are delivered as original binary via the Dynamic Media CDN. It is not required to create image profiles or image presets in AEM for the basic functionality. It's also not required to configure anything in the component instance edit dialogs or content policies. @@ -31,16 +31,16 @@ See also [this video][aem-smart-crop-video] for general information about smart ### System configuration -Make sure to configure the service user mapping for dynamic media as described in the [system configuration][configuration]. +Make sure to configure the service user mapping for Dynamic Media as described in the [system configuration][configuration]. The "wcm.io Media Handler Dynamic Media Support" OSGi configuration supports additional options: -* Enabled: Dynamic media support is enabled by default, if dynamic media is configured for the AEM instance. With this flag it is possible to disable it in the media handler. -* Author Preview Mode: If activated, dynamic media requests are routed through the AEM instance. Ths is useful when the "Publish Assets" configuration is not set to "Immediately", and thus images that are not yet accessible live via dynamic media can be previewed on author instances. Must not be activated on publish instances. +* Enabled: Dynamic media support is enabled by default, if Dynamic Media is configured for the AEM instance. With this flag it is possible to disable it in the media handler. +* Author Preview Mode: If activated, Dynamic Media requests are routed through the AEM instance. Ths is useful when the "Publish Assets" configuration is not set to "Immediately", and thus images that are not yet accessible live via Dynamic Media can be previewed on author instances. Must not be activated on publish instances. * Disable AEM Fallback: Disable the automatic fallback to AEM-based rendering of renditions (via Media Handler) if Dynamic Media is enabled, but the asset has not the appropriate Dynamic Media metadata. The asset is then handled as invalid. -* Image width/height limit: Has to be configured to the same values as the image server "Reply Image Size Limit" in dynamic media. +* Image width/height limit: Has to be configured to the same values as the image server "Reply Image Size Limit" in Dynamic Media. -To support serving static content for "download" (with `Content-Disposition: attachment` header) please activate this ruleset in dynamic media for the image servers: +To support serving static content for "download" (with `Content-Disposition: attachment` header) please activate this ruleset in Dynamic Media for the image servers: ```xml diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 9104e756..e30aeecc 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -13,6 +13,7 @@ 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] * [API documentation][apidocs] * [Changelog][changelog] @@ -34,9 +35,9 @@ The Media Handler provides: * Generic Sling Models for usage in views: [Sling Models][ui-package] * Generic HTL Placeholder template * Generic [Granite UI components][graniteui-components] that can be used in media/image component dialogs -* Support for Web-Optimized Image Delivery: On AEMaaCS instances renditions are transparently rendered on the edge -* Support for [Next Generation Dynamic Media][nextgen-dm] on AEMaaCS * Support for [Dynamic Media][dynamic-media] +* Support for [Next Generation Dynamic Media][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. @@ -81,6 +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 [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 new file mode 100644 index 00000000..2605b288 --- /dev/null +++ b/src/site/markdown/nextgen-dynamic-media.md @@ -0,0 +1,65 @@ +## 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. + +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. 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 From 29a04f2ee1e9202691b1b56d195568a40104bd0f Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 13 Mar 2024 10:11:19 +0100 Subject: [PATCH 12/13] prepare release --- changes.xml | 2 +- src/site/markdown/nextgen-dynamic-media.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changes.xml b/changes.xml index a379ae13..81edcc1e 100644 --- a/changes.xml +++ b/changes.xml @@ -23,7 +23,7 @@ xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd"> - + Next Generation Dynamic Media: Support non-image assets and SVG assets. diff --git a/src/site/markdown/nextgen-dynamic-media.md b/src/site/markdown/nextgen-dynamic-media.md index 2605b288..8307e8b4 100644 --- a/src/site/markdown/nextgen-dynamic-media.md +++ b/src/site/markdown/nextgen-dynamic-media.md @@ -45,7 +45,7 @@ If Next Generation Dynamic Media is enabled for a AEMaaCS instance, it will work 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. Optionally, you can configure an proxy server and timeouts. +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) From c5ba1199044a6625f6f5d0ad2e37c18eada44b21 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 13 Mar 2024 10:35:15 +0100 Subject: [PATCH 13/13] [gitflow-maven-plugin] Update versions for release 2.0.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5e400337..322b2a39 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ io.wcm io.wcm.handler.media - 2.0.1-SNAPSHOT + 2.0.2 jar Media Handler @@ -49,7 +49,7 @@ handler/media - 2024-01-26T19:11:51Z + 2024-03-13T09:35:13Z