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