Skip to content

Commit

Permalink
ASSETS-33747 - named smartcrop in image core v3 component powered by …
Browse files Browse the repository at this point in the history
…fastly (#2698)

* Instead of fastly smartcrops, expect named smartcrops in delivery URLs.
* add editor code to render image through delivery URL and support smartcrop.
* @trivial fix linting errors.
* Show smartcrop renditions group even if DM is disabled but polaris is enabled.
* Update code to also show dynamicmedia group when dialog is loaded with remote asset.
* Make dependency on clientbuilderfactory optional and check for null. This is to avoid rewriting/fixing all Image test cases.
* Minor bug fixes.
* Fix test cases.
* @trivial Fix existing test cases.
* Review comments. Add a separate test case for smartcrop. Add a failing testcase for auto smartcrop.
* @trivial Hide the smartcrop option if remote asset does not have smartcrops.
* Add test cases for srcset and responsehandler code.
* @trivial Move the code to show smartcrop dropdown only after fetching the metadata.
* @trivial Fix eslint error.

---------

Co-authored-by: Levente Sántha <levente@adobe.com>

* ASSETS-33747 named smartcrop in image core v3 component

Incorporate review comments.

* @trivial review comments.

* ASSETS-33747 - named smartcrop in image core v3 component

 * fixed unit test failures

* @trivial Review comments.

* Enable ES6 for image editor client library.

* ASSETS-33747 named smartcrop in image core v3 component powered by fastly

Auto smartcrop for remote assets should not depend on whether widths is configured or not.

* Fix the IT and remove the button for fastly smartcrop which is no longer supported.

* @trivial Update as per code scanning.

* Add selenium test case for smart crop functionality.

* @trivial fix test cases.

---------

Co-authored-by: Levente Sántha <levente@adobe.com>
  • Loading branch information
mohiaror and LSantha committed Apr 30, 2024
1 parent 4ff24d4 commit eedb92c
Show file tree
Hide file tree
Showing 27 changed files with 509 additions and 66 deletions.
Expand Up @@ -16,13 +16,25 @@
package com.adobe.cq.wcm.core.components.internal.models.v3;

import java.awt.*;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import javax.annotation.PostConstruct;
import javax.json.Json;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.osgi.services.HttpClientBuilderFactory;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
Expand Down Expand Up @@ -68,6 +80,10 @@ public class ImageImpl extends com.adobe.cq.wcm.core.components.internal.models.
@Optional
private NextGenDynamicMediaConfig nextGenDynamicMediaConfig;

@OSGiService
@Optional
private HttpClientBuilderFactory clientBuilderFactory;

private boolean imageLinkHidden = false;

private String srcSet = StringUtils.EMPTY;
Expand All @@ -77,6 +93,9 @@ public class ImageImpl extends com.adobe.cq.wcm.core.components.internal.models.
private Dimension dimension;

private boolean ngdmImage = false;
private CloseableHttpClient client;
private static final String PATH_PLACEHOLDER_ASSET_ID = "{asset-id}";
private String metadataDeliveryEndpoint;

@PostConstruct
protected void initModel() {
Expand Down Expand Up @@ -128,6 +147,15 @@ public String getSrcset() {

int[] widthsArray = getWidths();
String srcUritemplate = getSrcUriTemplate();

// handle srcset creation for auto smartcrop of remote assets
if (ngdmImage && StringUtils.equals(smartCropRendition, SMART_CROP_AUTO) && client != null
&& srcUritemplate != null) {
srcUritemplate = StringUtils.replace(srcUriTemplate, URI_WIDTH_PLACEHOLDER_ENCODED, URI_WIDTH_PLACEHOLDER);
getRemoteAssetSrcset(srcUritemplate);
return srcSet;
}

String[] srcsetArray = new String[widthsArray.length];
if (widthsArray.length > 0 && srcUritemplate != null) {
srcUritemplate = StringUtils.replace(srcUriTemplate, URI_WIDTH_PLACEHOLDER_ENCODED, URI_WIDTH_PLACEHOLDER);
Expand Down Expand Up @@ -234,6 +262,37 @@ private Dimension getOriginalDimension() {
return this.dimension;
}

private void getRemoteAssetSrcset(String srcUritemplate) {
String endPointUrl = "https://" + nextGenDynamicMediaConfig.getRepositoryId() + metadataDeliveryEndpoint;
HttpGet get = new HttpGet(endPointUrl);
get.setHeader("X-Adobe-Accept-Experimental", "1");
ResponseHandler<String> responseHandler = new NextGenDMSrcsetBuilderResponseHandler();
try {
String response = client.execute(get, responseHandler);
if (!StringUtils.isEmpty(response)) {
JsonReader jsonReader = Json.createReader(new StringReader(response));
JsonObject metadata = jsonReader.readObject();
jsonReader.close();
JsonObject repositoryMetadata = metadata.getJsonObject("repositoryMetadata");
JsonObject smartCrops = repositoryMetadata.getJsonObject("smartcrops");
String[] ngdmSrcsetArray = new String[smartCrops.size()];
int i = 0;
for (Map.Entry<String, JsonValue> entry : smartCrops.entrySet()) {
String namedSmartCrop = entry.getKey();
if (srcUritemplate.contains("=" + URI_WIDTH_PLACEHOLDER)) {
JsonValue smartCropWidth = smartCrops.getJsonObject(namedSmartCrop).get("width");
ngdmSrcsetArray[i] =
srcUritemplate.replace("width={.width}", String.format("smartcrop=%s", namedSmartCrop)) + " " + smartCropWidth.toString().replaceAll("\"", "") + "w";
i++;
}
}
srcSet = StringUtils.join(ngdmSrcsetArray, ',');
}
} catch (IOException | JsonException e) {
LOGGER.warn("Couldn't generate srcset for remote asset");
}
}

private Dimension getOriginalDimensionInternal() {
ValueMap inheritedResourceProperties = resource.getValueMap();
String inheritedFileReference = inheritedResourceProperties.get(DownloadResource.PN_REFERENCE, String.class);
Expand Down Expand Up @@ -271,18 +330,26 @@ private void initNextGenerationDynamicMedia() {
initResource();
properties = resource.getValueMap();
String fileReference = properties.get("fileReference", String.class);
String smartCrop = properties.get("smartCrop", String.class);
String smartCrop = properties.get("smartCropRendition", String.class);
if (isNgdmImageReference(fileReference)) {
int width = currentStyle.get(PN_DESIGN_RESIZE_WIDTH, DEFAULT_NGDM_ASSET_WIDTH);
NextGenDMImageURIBuilder builder = new NextGenDMImageURIBuilder(nextGenDynamicMediaConfig, fileReference)
.withPreferWebp(true)
.withWidth(width);
if(StringUtils.isNotEmpty(smartCrop)) {
if(StringUtils.isNotEmpty(smartCrop) && !StringUtils.equals(smartCrop, SMART_CROP_AUTO)) {
builder.withSmartCrop(smartCrop);
}
src = builder.build();
ngdmImage = true;
hasContent = true;
if (clientBuilderFactory != null) {
client = clientBuilderFactory.newBuilder().build();
}
metadataDeliveryEndpoint = nextGenDynamicMediaConfig.getAssetMetadataPath();
Scanner scanner = new Scanner(fileReference);
scanner.useDelimiter("/");
String assetId = scanner.next();
metadataDeliveryEndpoint = metadataDeliveryEndpoint.replace(PATH_PLACEHOLDER_ASSET_ID, assetId);
}
}

Expand Down
Expand Up @@ -36,7 +36,7 @@ public class NextGenDMImageURIBuilder {

private NextGenDynamicMediaConfig config;
private String fileReference;
private String smartCropAspectRatio;
private String smartCropName;
private int width = DEFAULT_NGDM_ASSET_WIDTH;

private int height;
Expand All @@ -48,11 +48,11 @@ public NextGenDMImageURIBuilder(NextGenDynamicMediaConfig config, String fileRef
}

/**
* Smart Crop aspect ratio string.
* @param smartCropAspectRatio - a string in "width:height" format;
* Smart Crop name.
* @param smartCropName - a string denoting the name of the smartcrop;
*/
public NextGenDMImageURIBuilder withSmartCrop(String smartCropAspectRatio) {
this.smartCropAspectRatio = smartCropAspectRatio;
public NextGenDMImageURIBuilder withSmartCrop(String smartCropName) {
this.smartCropName = smartCropName;
return this;
}

Expand Down Expand Up @@ -113,12 +113,8 @@ public String build() {
if(this.preferWebp) {
params.put("preferwebp", "true");
}
if (StringUtils.isNotEmpty(this.smartCropAspectRatio)) {
if (isValidSmartCrop(this.smartCropAspectRatio)) {
params.put("crop", String.format("%s,smart", this.smartCropAspectRatio));
} else {
LOGGER.info("Invalid smartCrop value at {}", this.smartCropAspectRatio);
}
if (StringUtils.isNotEmpty(this.smartCropName)) {
params.put("smartcrop", this.smartCropName);
}
if(params.size() > 0) {
uriBuilder.append("?");
Expand All @@ -133,18 +129,4 @@ public String build() {
LOGGER.info("Invalid fileReference or NGDMConfig. fileReference = {}", this.fileReference);
return null;
}

private boolean isValidSmartCrop(String smartCropStr) {
String[] crops = smartCropStr.split(":");
if (crops.length == 2) {
try {
Integer.parseInt(crops[0]);
Integer.parseInt(crops[1]);
return true;
} catch (NumberFormatException ex) {
return false;
}
}
return false;
}
}
@@ -0,0 +1,43 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2024 Adobe
~
~ 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.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

package com.adobe.cq.wcm.core.components.internal.models.v3;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ResponseHandler;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;

public class NextGenDMSrcsetBuilderResponseHandler implements ResponseHandler<String> {

@Override
public String handleResponse(final @NotNull HttpResponse response) throws IOException {
//Get the status of the response
String responseEntity = "";
int status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
if (entity != null) {
responseEntity = EntityUtils.toString(entity);
}
}
return responseEntity;
}
}
Expand Up @@ -70,7 +70,7 @@ private void initModel() {
Resource component = request.getResourceResolver().getResource(componentPath);
ValueMap properties = component.getValueMap();
String fileReference = properties.get("fileReference", String.class);
String smartCrop = properties.get("smartCrop", String.class);
String smartCrop = properties.get("smartCropRendition", String.class);
ValueMap configs = resource.getValueMap();
int width = configs.get("width", 480);
int height = configs.get("height", 480);
Expand Down
@@ -0,0 +1,59 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2024 Adobe
~
~ 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.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.wcm.core.components.internal.servlets;

import com.adobe.cq.ui.wcm.commons.config.NextGenDynamicMediaConfig;
import com.adobe.granite.ui.components.rendercondition.RenderCondition;
import com.adobe.granite.ui.components.rendercondition.SimpleRenderCondition;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicyOption;

@Component(
service = { Servlet.class },
property = {
ServletResolverConstants.SLING_SERVLET_METHODS + "=GET",
ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES + "=core/wcm/components/rendercondition/isNGDMEnabled"
}
)
public class NGDMEnableRenderCondition extends SlingSafeMethodsServlet {

@Reference(cardinality= ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY)
private NextGenDynamicMediaConfig nextGenDynamicMediaConfig;

@Override
protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response)
throws ServletException, IOException {

boolean isNGDMEnabled = false;
if (nextGenDynamicMediaConfig != null && nextGenDynamicMediaConfig.enabled() &&
StringUtils.isNotBlank(nextGenDynamicMediaConfig.getRepositoryId())) {
isNGDMEnabled = true;
}

request.setAttribute(RenderCondition.class.getName(), new SimpleRenderCondition(isNGDMEnabled));
}
}
Expand Up @@ -53,6 +53,7 @@ protected void setUp() {
MockNextGenDynamicMediaConfig config = new MockNextGenDynamicMediaConfig();
config.setEnabled(true);
config.setRepositoryId("testrepo");
config.setAssetMetadataPath("/adobe/assets/{asset-id}/metadata");
context.registerInjectActivateService(config);
}

Expand Down

0 comments on commit eedb92c

Please sign in to comment.