Skip to content

Commit

Permalink
Unable to publish/preview new assets when quick publish from tools co…
Browse files Browse the repository at this point in the history
…nfig page editor (#7)

Co-authored-by: srikanth gurram <srikanth.gurram@aldi-sued.com>
Co-authored-by: Stefan Seifert <stefanseifert@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 9, 2024
1 parent d430641 commit f0dea9a
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 16 deletions.
6 changes: 5 additions & 1 deletion changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd">
<body>

<release version="1.9.8" date="not released">
<release version="1.10.0" date="not released">
<action type="add" dev="srikanthgurram" issue="7">
Configuration Reference Provider: Check for asset reference contained in the Context-Aware configurations and add them to the list of reference to check for publication.
This feature is disabled by default, and can be enabled by OSGi configuration.
</action>
<action type="fix" dev="srikanthgurram" issue="8">
Saving of Context-Aware configuration collections: Update last modified date of item pages only if the configuration of it has actually changed.
</action>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* #%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.caconfig.extensions.references.impl;

import static com.day.cq.dam.api.DamConstants.MOUNTPOINT_ASSETS;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
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 com.day.cq.dam.api.Asset;
import com.day.cq.wcm.api.Page;

/**
* Recursively scans all string and string array properties in all resources of the given configuration page
* to check for asset references.
*/
class AssetRefereneDetector {

private final Page configPage;
private final Resource configResource;
private final ResourceResolver resourceResolver;
private final List<Asset> assets = new ArrayList<>();

private static final Pattern ASSET_PATH = Pattern.compile("^" + MOUNTPOINT_ASSETS + "/.*$");
private static final Logger log = LoggerFactory.getLogger(AssetRefereneDetector.class);

/**
* @param configPage Configuration page (must have a content resource).
*/
AssetRefereneDetector(@NotNull Page configPage) {
this.configPage = configPage;
this.configResource = configPage.getContentResource();
this.resourceResolver = configResource.getResourceResolver();
}

/**
* @return List of all assets referenced in the configuration page.
*/
List<Asset> getReferencedAssets() {
assets.clear();
findAssetReferencesRecursively(configResource);
return assets;
}

/**
* Recurse through all child resources of the given resource.
* @param resource Resource
*/
private void findAssetReferencesRecursively(@NotNull Resource resource) {
findAssetReferences(resource);
resource.getChildren().forEach(this::findAssetReferencesRecursively);
}

/**
* Find asset references in all properties of the given resource.
* @param resource Resource
*/
private void findAssetReferences(@NotNull Resource resource) {
ValueMap props = resource.getValueMap();
assets.addAll(props.values().stream()
.flatMap(this::getAssetsIfAssetReference)
.collect(Collectors.toList()));
}

/**
* Checks if the value is string which might be asset reference, or an array containing a string asset reference.
* @param value Value
* @return Found referenced assets
*/
private Stream<Asset> getAssetsIfAssetReference(@Nullable Object value) {
List<Asset> result = new ArrayList<>();
if (value instanceof String) {
getAssetIfAssetReference((String)value).ifPresent(result::add);
}
else if (value != null && value.getClass().isArray()) {
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
Object itemValue = Array.get(value, i);
if (itemValue instanceof String) {
getAssetIfAssetReference((String)itemValue).ifPresent(result::add);
}
}
}
return result.stream();
}

/**
* Checks if the given string points to an asset.
* @param value String value
* @return Asset if string is a valid asset reference.
*/
private Optional<Asset> getAssetIfAssetReference(@NotNull String value) {
if (isAssetReference(value)) {
Resource resource = resourceResolver.getResource(value);
if (resource != null) {
Asset asset = resource.adaptTo(Asset.class);
if (asset != null) {
log.trace("Found asset reference {} for resource {}", configPage.getPath(), resource.getPath());
return Optional.of(asset);
}
}
}
return Optional.empty();
}

static boolean isAssetReference(@NotNull String value) {
return ASSET_PATH.matcher(value).matches();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
*/
package io.wcm.caconfig.extensions.references.impl;

import static com.day.cq.dam.api.DamConstants.ACTIVITY_TYPE_ASSET;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
Expand Down Expand Up @@ -48,6 +50,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.dam.api.Asset;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageFilter;
import com.day.cq.wcm.api.PageManager;
Expand All @@ -60,8 +63,8 @@
*
* <p>
* This is for example used by ActivationReferenceSearchServlet to resolve referenced content of pages during activation
* of a page using AEM sites. Returning the configurations allows the editor to activate them along with the page
* referring to them.
* of a page using AEM sites. Returning the configurations and (if enabled) asset references allows the editor to activate
* them along with the page referring to them.
* </p>
*
* <p>
Expand All @@ -73,12 +76,18 @@
public class ConfigurationReferenceProvider implements ReferenceProvider {

@ObjectClassDefinition(name = "wcm.io Context-Aware Configuration Reference Provider",
description = "Allows to resolve references from resources to their Context-Aware configurations, for example during page activation.")
description = "Allows to resolve references from resources to their Context-Aware configuration pages "
+ "and referenced assets, for example during page activation.")
@interface Config {

@AttributeDefinition(name = "Enabled",
description = "Enable this reference provider.")
boolean enabled() default true;

@AttributeDefinition(name = "Asset References",
description = "Check for asset references within the context-aware configurations, and add them to the list of references.")
boolean assetReferences() default false;

}

static final String REFERENCE_TYPE = "caconfig";
Expand All @@ -93,6 +102,7 @@ public class ConfigurationReferenceProvider implements ReferenceProvider {
private ConfigurationResourceResolverConfig configurationResourceResolverConfig;

private boolean enabled;
private boolean assetReferencesEnabled;

private static final Logger log = LoggerFactory.getLogger(ConfigurationReferenceProvider.class);

Expand All @@ -102,6 +112,7 @@ public class ConfigurationReferenceProvider implements ReferenceProvider {
@Activate
protected void activate(Config config) {
enabled = config.enabled();
assetReferencesEnabled = config.assetReferences();
}

@Deactivate
Expand All @@ -127,6 +138,7 @@ public List<com.day.cq.wcm.api.reference.Reference> findReferences(Resource reso
Map<String, ConfigurationMetadata> configurationMetadatas = new TreeMap<>(configurationManager.getConfigurationNames().stream()
.collect(Collectors.toMap(configName -> configName, configName -> configurationManager.getConfigurationMetadata(configName))));
List<com.day.cq.wcm.api.reference.Reference> references = new ArrayList<>();
Map<String, Asset> referencedAssets = new TreeMap<>();
Set<String> configurationBuckets = new LinkedHashSet<>(configurationResourceResolverConfig.configBucketNames());

for (String configurationName : configurationMetadatas.keySet()) {
Expand All @@ -138,7 +150,7 @@ public List<com.day.cq.wcm.api.reference.Reference> findReferences(Resource reso
Resource configurationResource = configurationInheritanceChain.next();

// get page for configuration resource - and all children (e.g. for config collections)
// collect in map to elimnate duplicate pages
// collect in map to eliminate duplicate pages
Page configPage = pageManager.getContainingPage(configurationResource);
if (configPage != null) {
referencePages.put(configPage.getPath(), configPage);
Expand All @@ -152,8 +164,20 @@ public List<com.day.cq.wcm.api.reference.Reference> findReferences(Resource reso

// generate references for each page (but not if the context page itself is included as well)
referencePages.values().stream()
.filter(item -> !StringUtils.equals(contextPage.getPath(), item.getPath()))
.forEach(item -> references.add(toReference(resource, item, configurationMetadatas, configurationBuckets)));
.filter(configPage -> !StringUtils.equals(contextPage.getPath(), configPage.getPath()))
.forEach(configPage -> {
references.add(toReference(resource, configPage, configurationMetadatas, configurationBuckets));
// collect asset references
if (assetReferencesEnabled && configPage.getContentResource() != null) {
AssetRefereneDetector detector = new AssetRefereneDetector(configPage);
detector.getReferencedAssets().stream().forEach(asset -> referencedAssets.put(asset.getPath(), asset));
}
});
}

if (!referencedAssets.isEmpty()) {
// collect asset references detected in configuration pages (de-duplicated by using a map)
referencedAssets.values().forEach(asset -> references.add(toReference(resource, asset)));
}

log.debug("Found {} references for resource {}", references.size(), resource.getPath());
Expand All @@ -163,12 +187,18 @@ public List<com.day.cq.wcm.api.reference.Reference> findReferences(Resource reso
private com.day.cq.wcm.api.reference.Reference toReference(Resource resource, Page configPage,
Map<String, ConfigurationMetadata> configurationMetadatas, Set<String> configurationBuckets) {
log.trace("Found configuration reference {} for resource {}", configPage.getPath(), resource.getPath());
return new com.day.cq.wcm.api.reference.Reference(getType(),
return new com.day.cq.wcm.api.reference.Reference(REFERENCE_TYPE,
getReferenceName(configPage, configurationMetadatas, configurationBuckets),
configPage.adaptTo(Resource.class),
getLastModifiedOf(configPage));
}

private com.day.cq.wcm.api.reference.Reference toReference(Resource resource, Asset asset) {
log.trace("Found asset reference {} for resource {}", asset.getPath(), resource.getPath());
return new com.day.cq.wcm.api.reference.Reference(ACTIVITY_TYPE_ASSET,
asset.getName(), asset.adaptTo(Resource.class), asset.getLastModified());
}

/**
* Build reference display name from path with:
* - translating configuration names to labels
Expand Down Expand Up @@ -197,8 +227,4 @@ private static long getLastModifiedOf(Page page) {
return lastModified != null ? lastModified.getTimeInMillis() : 0;
}

private static String getType() {
return REFERENCE_TYPE;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* #%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.caconfig.extensions.references.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Set;
import java.util.stream.Collectors;

import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import com.day.cq.dam.api.Asset;
import com.day.cq.wcm.api.Page;

import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import io.wcm.wcm.commons.contenttype.ContentType;

@ExtendWith(AemContextExtension.class)
class AssetRefereneDetectorTest {

private static final String ASSET_1 = "/content/dam/asset1.jpg";
private static final String ASSET_2 = "/content/dam/asset2.jpg";
private static final String ASSET_3 = "/content/dam/asset3.jpg";

final AemContext context = new AemContext();

@BeforeEach
void setUp() {
context.create().asset(ASSET_1, 10, 10, ContentType.JPEG);
context.create().asset(ASSET_2, 10, 10, ContentType.JPEG);
context.create().asset(ASSET_3, 10, 10, ContentType.JPEG);
}

@Test
void testNoReferences() {
Page page = context.create().page("/content/test", null,
"prop1", "value1", "prop2", 5);
assertTrue(getReferences(page).isEmpty());
}

@Test
void testSimpleProperties() {
Page page = context.create().page("/content/test", null,
"prop1", "value1", "prop2", 5,
"ref1", ASSET_1, "ref2", ASSET_2);
assertEquals(Set.of(ASSET_1, ASSET_2), getReferences(page));
}

@Test
void testArrayProperty() {
Page page = context.create().page("/content/test", null,
"prop1", "value1", "prop2", 5,
"ref", ASSET_1, "refs", new String[] { ASSET_2, ASSET_3 });
assertEquals(Set.of(ASSET_1, ASSET_2, ASSET_3), getReferences(page));
}

@Test
void testNested() {
Page page = context.create().page("/content/test", null,
"prop1", "value1", "prop2", 5,
"ref", ASSET_1);
context.create().resource(page, "sub1",
"ref", ASSET_2);
context.create().resource(page, "sub2/sub21/sub211",
"ref", ASSET_3);
assertEquals(Set.of(ASSET_1, ASSET_2, ASSET_3), getReferences(page));
}

static Set<String> getReferences(@NotNull Page page) {
return new AssetRefereneDetector(page).getReferencedAssets().stream()
.map(Asset::getPath)
.collect(Collectors.toSet());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@

String key() default "";

String assetReference1() default "";

String assetReference2() default "";

}
Loading

0 comments on commit f0dea9a

Please sign in to comment.