Skip to content

Commit f0dea9a

Browse files
srikanthgurram-17SrikanthGurramALDISUEDstefanseifert
authored
Unable to publish/preview new assets when quick publish from tools config page editor (#7)
Co-authored-by: srikanth gurram <[email protected]> Co-authored-by: Stefan Seifert <[email protected]>
1 parent d430641 commit f0dea9a

File tree

8 files changed

+324
-16
lines changed

8 files changed

+324
-16
lines changed

changes.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd">
2424
<body>
2525

26-
<release version="1.9.8" date="not released">
26+
<release version="1.10.0" date="not released">
27+
<action type="add" dev="srikanthgurram" issue="7">
28+
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.
29+
This feature is disabled by default, and can be enabled by OSGi configuration.
30+
</action>
2731
<action type="fix" dev="srikanthgurram" issue="8">
2832
Saving of Context-Aware configuration collections: Update last modified date of item pages only if the configuration of it has actually changed.
2933
</action>
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* #%L
3+
* wcm.io
4+
* %%
5+
* Copyright (C) 2024 wcm.io
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package io.wcm.caconfig.extensions.references.impl;
21+
22+
import static com.day.cq.dam.api.DamConstants.MOUNTPOINT_ASSETS;
23+
24+
import java.lang.reflect.Array;
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import java.util.Optional;
28+
import java.util.regex.Pattern;
29+
import java.util.stream.Collectors;
30+
import java.util.stream.Stream;
31+
32+
import org.apache.sling.api.resource.Resource;
33+
import org.apache.sling.api.resource.ResourceResolver;
34+
import org.apache.sling.api.resource.ValueMap;
35+
import org.jetbrains.annotations.NotNull;
36+
import org.jetbrains.annotations.Nullable;
37+
import org.slf4j.Logger;
38+
import org.slf4j.LoggerFactory;
39+
40+
import com.day.cq.dam.api.Asset;
41+
import com.day.cq.wcm.api.Page;
42+
43+
/**
44+
* Recursively scans all string and string array properties in all resources of the given configuration page
45+
* to check for asset references.
46+
*/
47+
class AssetRefereneDetector {
48+
49+
private final Page configPage;
50+
private final Resource configResource;
51+
private final ResourceResolver resourceResolver;
52+
private final List<Asset> assets = new ArrayList<>();
53+
54+
private static final Pattern ASSET_PATH = Pattern.compile("^" + MOUNTPOINT_ASSETS + "/.*$");
55+
private static final Logger log = LoggerFactory.getLogger(AssetRefereneDetector.class);
56+
57+
/**
58+
* @param configPage Configuration page (must have a content resource).
59+
*/
60+
AssetRefereneDetector(@NotNull Page configPage) {
61+
this.configPage = configPage;
62+
this.configResource = configPage.getContentResource();
63+
this.resourceResolver = configResource.getResourceResolver();
64+
}
65+
66+
/**
67+
* @return List of all assets referenced in the configuration page.
68+
*/
69+
List<Asset> getReferencedAssets() {
70+
assets.clear();
71+
findAssetReferencesRecursively(configResource);
72+
return assets;
73+
}
74+
75+
/**
76+
* Recurse through all child resources of the given resource.
77+
* @param resource Resource
78+
*/
79+
private void findAssetReferencesRecursively(@NotNull Resource resource) {
80+
findAssetReferences(resource);
81+
resource.getChildren().forEach(this::findAssetReferencesRecursively);
82+
}
83+
84+
/**
85+
* Find asset references in all properties of the given resource.
86+
* @param resource Resource
87+
*/
88+
private void findAssetReferences(@NotNull Resource resource) {
89+
ValueMap props = resource.getValueMap();
90+
assets.addAll(props.values().stream()
91+
.flatMap(this::getAssetsIfAssetReference)
92+
.collect(Collectors.toList()));
93+
}
94+
95+
/**
96+
* Checks if the value is string which might be asset reference, or an array containing a string asset reference.
97+
* @param value Value
98+
* @return Found referenced assets
99+
*/
100+
private Stream<Asset> getAssetsIfAssetReference(@Nullable Object value) {
101+
List<Asset> result = new ArrayList<>();
102+
if (value instanceof String) {
103+
getAssetIfAssetReference((String)value).ifPresent(result::add);
104+
}
105+
else if (value != null && value.getClass().isArray()) {
106+
int length = Array.getLength(value);
107+
for (int i = 0; i < length; i++) {
108+
Object itemValue = Array.get(value, i);
109+
if (itemValue instanceof String) {
110+
getAssetIfAssetReference((String)itemValue).ifPresent(result::add);
111+
}
112+
}
113+
}
114+
return result.stream();
115+
}
116+
117+
/**
118+
* Checks if the given string points to an asset.
119+
* @param value String value
120+
* @return Asset if string is a valid asset reference.
121+
*/
122+
private Optional<Asset> getAssetIfAssetReference(@NotNull String value) {
123+
if (isAssetReference(value)) {
124+
Resource resource = resourceResolver.getResource(value);
125+
if (resource != null) {
126+
Asset asset = resource.adaptTo(Asset.class);
127+
if (asset != null) {
128+
log.trace("Found asset reference {} for resource {}", configPage.getPath(), resource.getPath());
129+
return Optional.of(asset);
130+
}
131+
}
132+
}
133+
return Optional.empty();
134+
}
135+
136+
static boolean isAssetReference(@NotNull String value) {
137+
return ASSET_PATH.matcher(value).matches();
138+
}
139+
140+
}

src/main/java/io/wcm/caconfig/extensions/references/impl/ConfigurationReferenceProvider.java

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
*/
2020
package io.wcm.caconfig.extensions.references.impl;
2121

22+
import static com.day.cq.dam.api.DamConstants.ACTIVITY_TYPE_ASSET;
23+
2224
import java.util.ArrayList;
2325
import java.util.Arrays;
2426
import java.util.Calendar;
@@ -48,6 +50,7 @@
4850
import org.slf4j.Logger;
4951
import org.slf4j.LoggerFactory;
5052

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

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

7983
@AttributeDefinition(name = "Enabled",
8084
description = "Enable this reference provider.")
8185
boolean enabled() default true;
86+
87+
@AttributeDefinition(name = "Asset References",
88+
description = "Check for asset references within the context-aware configurations, and add them to the list of references.")
89+
boolean assetReferences() default false;
90+
8291
}
8392

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

95104
private boolean enabled;
105+
private boolean assetReferencesEnabled;
96106

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

@@ -102,6 +112,7 @@ public class ConfigurationReferenceProvider implements ReferenceProvider {
102112
@Activate
103113
protected void activate(Config config) {
104114
enabled = config.enabled();
115+
assetReferencesEnabled = config.assetReferences();
105116
}
106117

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

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

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

153165
// generate references for each page (but not if the context page itself is included as well)
154166
referencePages.values().stream()
155-
.filter(item -> !StringUtils.equals(contextPage.getPath(), item.getPath()))
156-
.forEach(item -> references.add(toReference(resource, item, configurationMetadatas, configurationBuckets)));
167+
.filter(configPage -> !StringUtils.equals(contextPage.getPath(), configPage.getPath()))
168+
.forEach(configPage -> {
169+
references.add(toReference(resource, configPage, configurationMetadatas, configurationBuckets));
170+
// collect asset references
171+
if (assetReferencesEnabled && configPage.getContentResource() != null) {
172+
AssetRefereneDetector detector = new AssetRefereneDetector(configPage);
173+
detector.getReferencedAssets().stream().forEach(asset -> referencedAssets.put(asset.getPath(), asset));
174+
}
175+
});
176+
}
177+
178+
if (!referencedAssets.isEmpty()) {
179+
// collect asset references detected in configuration pages (de-duplicated by using a map)
180+
referencedAssets.values().forEach(asset -> references.add(toReference(resource, asset)));
157181
}
158182

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

196+
private com.day.cq.wcm.api.reference.Reference toReference(Resource resource, Asset asset) {
197+
log.trace("Found asset reference {} for resource {}", asset.getPath(), resource.getPath());
198+
return new com.day.cq.wcm.api.reference.Reference(ACTIVITY_TYPE_ASSET,
199+
asset.getName(), asset.adaptTo(Resource.class), asset.getLastModified());
200+
}
201+
172202
/**
173203
* Build reference display name from path with:
174204
* - translating configuration names to labels
@@ -197,8 +227,4 @@ private static long getLastModifiedOf(Page page) {
197227
return lastModified != null ? lastModified.getTimeInMillis() : 0;
198228
}
199229

200-
private static String getType() {
201-
return REFERENCE_TYPE;
202-
}
203-
204230
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* #%L
3+
* wcm.io
4+
* %%
5+
* Copyright (C) 2024 wcm.io
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package io.wcm.caconfig.extensions.references.impl;
21+
22+
import static org.junit.jupiter.api.Assertions.assertEquals;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
24+
25+
import java.util.Set;
26+
import java.util.stream.Collectors;
27+
28+
import org.jetbrains.annotations.NotNull;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.extension.ExtendWith;
32+
33+
import com.day.cq.dam.api.Asset;
34+
import com.day.cq.wcm.api.Page;
35+
36+
import io.wcm.testing.mock.aem.junit5.AemContext;
37+
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
38+
import io.wcm.wcm.commons.contenttype.ContentType;
39+
40+
@ExtendWith(AemContextExtension.class)
41+
class AssetRefereneDetectorTest {
42+
43+
private static final String ASSET_1 = "/content/dam/asset1.jpg";
44+
private static final String ASSET_2 = "/content/dam/asset2.jpg";
45+
private static final String ASSET_3 = "/content/dam/asset3.jpg";
46+
47+
final AemContext context = new AemContext();
48+
49+
@BeforeEach
50+
void setUp() {
51+
context.create().asset(ASSET_1, 10, 10, ContentType.JPEG);
52+
context.create().asset(ASSET_2, 10, 10, ContentType.JPEG);
53+
context.create().asset(ASSET_3, 10, 10, ContentType.JPEG);
54+
}
55+
56+
@Test
57+
void testNoReferences() {
58+
Page page = context.create().page("/content/test", null,
59+
"prop1", "value1", "prop2", 5);
60+
assertTrue(getReferences(page).isEmpty());
61+
}
62+
63+
@Test
64+
void testSimpleProperties() {
65+
Page page = context.create().page("/content/test", null,
66+
"prop1", "value1", "prop2", 5,
67+
"ref1", ASSET_1, "ref2", ASSET_2);
68+
assertEquals(Set.of(ASSET_1, ASSET_2), getReferences(page));
69+
}
70+
71+
@Test
72+
void testArrayProperty() {
73+
Page page = context.create().page("/content/test", null,
74+
"prop1", "value1", "prop2", 5,
75+
"ref", ASSET_1, "refs", new String[] { ASSET_2, ASSET_3 });
76+
assertEquals(Set.of(ASSET_1, ASSET_2, ASSET_3), getReferences(page));
77+
}
78+
79+
@Test
80+
void testNested() {
81+
Page page = context.create().page("/content/test", null,
82+
"prop1", "value1", "prop2", 5,
83+
"ref", ASSET_1);
84+
context.create().resource(page, "sub1",
85+
"ref", ASSET_2);
86+
context.create().resource(page, "sub2/sub21/sub211",
87+
"ref", ASSET_3);
88+
assertEquals(Set.of(ASSET_1, ASSET_2, ASSET_3), getReferences(page));
89+
}
90+
91+
static Set<String> getReferences(@NotNull Page page) {
92+
return new AssetRefereneDetector(page).getReferencedAssets().stream()
93+
.map(Asset::getPath)
94+
.collect(Collectors.toSet());
95+
}
96+
97+
}

src/test/java/io/wcm/caconfig/extensions/references/impl/ConfigurationB.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@
2626

2727
String key() default "";
2828

29+
String assetReference1() default "";
30+
31+
String assetReference2() default "";
32+
2933
}

0 commit comments

Comments
 (0)