Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASSETS-33747 named smartcrop in image core v3 component #2698

Merged
merged 17 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -135,6 +154,8 @@ public String getSrcset() {
// in case of dm image and auto smartcrop the srcset needs to generated client side
if (dmImage && StringUtils.equals(smartCropRendition, SMART_CROP_AUTO)) {
srcSet = EMPTY_PIXEL;
} else if (ngdmImage && StringUtils.equals(smartCropRendition, SMART_CROP_AUTO) && client != null) {
getRemoteAssetSrcset(srcUritemplate);
} else {
for (int i = 0; i < widthsArray.length; i++) {
if (srcUritemplate.contains("=" + URI_WIDTH_PLACEHOLDER)) {
Expand Down Expand Up @@ -234,6 +255,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 +323,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,42 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ 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> {

public String handleResponse(final @NotNull HttpResponse response) throws IOException {
Fixed Show fixed Hide fixed
//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
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
Expand Up @@ -15,12 +15,25 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.wcm.core.components.internal.models.v3;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;

import com.adobe.cq.wcm.core.components.testing.MockAssetDelivery;
import com.adobe.cq.wcm.core.components.testing.MockNextGenDynamicMediaConfig;
import org.apache.commons.lang.reflect.FieldUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
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.osgi.services.HttpClientBuilderFactory;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
Expand Down Expand Up @@ -51,8 +64,8 @@
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 static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ExtendWith(AemContextExtension.class)
Expand Down Expand Up @@ -82,6 +95,9 @@ class ImageImplTest extends com.adobe.cq.wcm.core.components.internal.models.v2.
private static final String PAGE2_IMAGE0_PATH = PAGE2 + "/jcr:content/root/page2_image0";
private static final String PAGE3_IMAGE0_PATH = PAGE3 + "/jcr:content/root/page3_image0";
private static final String NGDM_IMAGE1_PATH = "/content/ngdm_test_page/jcr:content/root/ngdm_test_page_image1";
private static final String NGDM_SMARTCROP_IMAGE_PATH = "/content/ngdm_test_page/jcr:content/root/ngdm_test_page_smartcrop_image";
private static final String NGDM_SMARTCROP_AUTO_IMAGE_PATH = "/content/ngdm_test_page/jcr:content/root/ngdm_test_page_smartcrop_image_auto";


@BeforeEach
@Override
Expand Down Expand Up @@ -671,6 +687,7 @@ void testNgdmImage() {
MockNextGenDynamicMediaConfig config = new MockNextGenDynamicMediaConfig();
config.setEnabled(true);
config.setRepositoryId("testrepo");
config.setAssetMetadataPath("/adobe/assets/{asset-id}/metadata");
context.registerInjectActivateService(config);

Image image = getImageUnderTest(NGDM_IMAGE1_PATH);
Expand All @@ -683,6 +700,7 @@ void testNgdmImageWithResizeWidth() {
MockNextGenDynamicMediaConfig config = new MockNextGenDynamicMediaConfig();
config.setEnabled(true);
config.setRepositoryId("testrepo");
config.setAssetMetadataPath("/adobe/assets/{asset-id}/metadata");
context.registerInjectActivateService(config);
context.contentPolicyMapping(resourceType, PN_DESIGN_RESIZE_WIDTH, 800);

Expand All @@ -700,4 +718,59 @@ void testNgdmImageWithNgdmConfigDisabled() {
Image image = getImageUnderTest(NGDM_IMAGE1_PATH);
Utils.testJSONExport(image, Utils.getTestExporterJSONPath(testBase, NGDM_IMAGE1_PATH + "_ngm_disabled"));
}

@Test
void testNgdmImageWithSmartCropRendition() {
MockNextGenDynamicMediaConfig config = new MockNextGenDynamicMediaConfig();
config.setEnabled(true);
config.setRepositoryId("testrepo");
config.setAssetMetadataPath("/adobe/assets/{asset-id}/metadata");
context.registerInjectActivateService(config);

Image image = getImageUnderTest(NGDM_SMARTCROP_IMAGE_PATH);
Utils.testJSONExport(image, Utils.getTestExporterJSONPath(testBase, NGDM_SMARTCROP_IMAGE_PATH));
}

@Test
void testNgdmImageWithAutoSmartCropRendition() throws Exception {
String expectedMetadataAPIResponseJSON = "{\"assetId\":\"urn:aaid:aem:33b6255d-a978-43ad-8e2e-ef5677c64715\",\"repositoryMetadata\":{\"smartcrops\":{\"Large\":{\"height\":\"1200\",\"left\":\"0.0\",\"manualCrop\":\"false\",\"width\":\"800\",\"top\":\"0.16\"},\"Medium\":{\"height\":\"800\",\"left\":\"0.0\",\"manualCrop\":\"false\",\"width\":\"600\",\"top\":\"0.0\"}}}}";
MockNextGenDynamicMediaConfig config = new MockNextGenDynamicMediaConfig();
config.setEnabled(true);
config.setRepositoryId("testrepo");
config.setAssetMetadataPath("/adobe/assets/{asset-id}/metadata");
context.registerInjectActivateService(config);
HttpClientBuilderFactory mockBuilderFactory = mock(HttpClientBuilderFactory.class);
HttpClientBuilder mockBuilder = mock(HttpClientBuilder.class);
when(mockBuilderFactory.newBuilder()).thenReturn(mockBuilder);

CloseableHttpClient mockClient = mock(CloseableHttpClient.class);
when(mockBuilder.setDefaultRequestConfig(any(RequestConfig.class))).thenReturn(mockBuilder);
when(mockBuilder.build()).thenReturn(mockClient);

when(mockClient.execute(any(HttpGet.class), any(ResponseHandler.class))).thenReturn(expectedMetadataAPIResponseJSON);
context.registerService(HttpClientBuilderFactory.class, mockBuilderFactory);

context.contentPolicyMapping(resourceType, new HashMap<String, Object>() {{
put(Image.PN_DESIGN_ALLOWED_RENDITION_WIDTHS, new int[]{600, 800});
}});
Image image = getImageUnderTest(NGDM_SMARTCROP_AUTO_IMAGE_PATH);
Utils.testJSONExport(image, Utils.getTestExporterJSONPath(testBase, NGDM_SMARTCROP_AUTO_IMAGE_PATH));
}

@Test
void testNgdmSrcsetBuilderResponseHandler() throws IOException {
HttpResponse httpResponse = mock(HttpResponse.class);
HttpEntity entity = mock(HttpEntity.class);
StatusLine statusLine = mock(StatusLine.class);
when(httpResponse.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(HttpStatus.SC_OK);
String contentString = "Mocked content";
InputStream contentStream = new ByteArrayInputStream(contentString.getBytes());
when(entity.getContent()).thenReturn(contentStream);
when(httpResponse.getEntity()).thenReturn(entity);

ResponseHandler<String> responseHandler = new NextGenDMSrcsetBuilderResponseHandler();
String result = responseHandler.handleResponse(httpResponse);
assertEquals("Mocked content", result);
}
}
Expand Up @@ -83,7 +83,6 @@ public void testThumbnailSrc() {
assertTrue(nextGenDMThumbnail.getSrc().contains(BASE_URI));
assertTrue(nextGenDMThumbnail.getSrc().contains("width=260"));
assertTrue(nextGenDMThumbnail.getSrc().contains("height=260"));
assertTrue(nextGenDMThumbnail.getSrc().contains("crop=2:3,smart"));
}

@Test
Expand Down
Expand Up @@ -85,17 +85,9 @@ public void testUriWithCustomPreferWebp() {

@Test
public void testUriWithSmartCrop() {
uriBuilder.withSmartCrop("2:3");
uriBuilder.withSmartCrop("Medium");
String uri = uriBuilder.build();
assertTrue(uri.contains("crop=2:3,smart"));
}


@Test
public void testUriWithInvalidSmartCrop() {
uriBuilder.withSmartCrop("x:y");
String uri = uriBuilder.build();
assertEquals(baseUri, uri);
assertTrue(uri.contains("smartcrop=Medium"));
}

@Test
Expand Down