Skip to content

Commit 765ed51

Browse files
committed
Merge branch 'develop'
2 parents d7091df + d3e5693 commit 765ed51

File tree

14 files changed

+294
-32
lines changed

14 files changed

+294
-32
lines changed

.github/workflows/maven-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
git config --global user.name "${{ secrets.GH_SITE_DEPLOY_NAME }}"
2626
2727
- name: Setup JDK
28-
uses: actions/setup-java@v3
28+
uses: actions/setup-java@v4
2929
with:
3030
distribution: temurin
3131
java-version: 11

changes.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@
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.4.0" date="2024-09-16">
27+
<action type="add" dev="sseifert" issue="5">
28+
Do not include JS/CSS client libraries twice if the same library is requested multiple times within one request.
29+
This new behavior can be disabled by setting allowMultipleIncludes=true.
30+
</action>
31+
<action type="fix" dev="sseifert" issue="6">
32+
Ensure request context path is added to client library URLs.
33+
</action>
34+
<action type="fix" dev="sseifert" issue="7">
35+
CSS Include: Fix handling of "rel" property.
36+
</action>
37+
<action type="update" dev="sseifert">
38+
Switch to AEM 6.5.17 as minimum version.
39+
</action>
40+
</release>
41+
2642
<release version="1.3.0" date="2023-11-20">
2743
<action type="add" dev="sseifert" issue="3">
2844
Add optional support for rel=preload|prefetch for CSS includes.

pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
<parent>
2626
<groupId>io.wcm</groupId>
2727
<artifactId>io.wcm.parent_toplevel</artifactId>
28-
<version>2.2.4</version>
28+
<version>2.3.2</version>
2929
<relativePath/>
3030
</parent>
3131

3232
<groupId>io.wcm</groupId>
3333
<artifactId>io.wcm.wcm.ui.clientlibs</artifactId>
34-
<version>1.3.0</version>
34+
<version>1.4.0</version>
3535
<packaging>jar</packaging>
3636

3737
<name>WCM Clientlibs UI Extensions</name>
@@ -49,7 +49,7 @@
4949
<site.url.module.prefix>wcm/ui/clientlibs</site.url.module.prefix>
5050

5151
<!-- Enable reproducible builds -->
52-
<project.build.outputTimestamp>2023-11-20T15:22:05Z</project.build.outputTimestamp>
52+
<project.build.outputTimestamp>2024-09-16T09:10:07Z</project.build.outputTimestamp>
5353
</properties>
5454

5555
<dependencies>
@@ -94,7 +94,7 @@
9494
<dependency>
9595
<groupId>io.wcm</groupId>
9696
<artifactId>io.wcm.testing.aem-mock.junit5</artifactId>
97-
<version>5.0.0</version>
97+
<version>5.5.0</version>
9898
<scope>test</scope>
9999
</dependency>
100100
<dependency>

src/main/java/io/wcm/wcm/ui/clientlibs/components/CSSInclude.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public class CSSInclude {
5151
private static final Set<String> REL_ALLOWED_VALUES = Set.of(
5252
"prefetch", "preload");
5353

54+
@SlingObject
55+
private SlingHttpServletRequest request;
5456
@SlingObject
5557
private ResourceResolver resourceResolver;
5658
@OSGiService
@@ -63,6 +65,8 @@ public class CSSInclude {
6365
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
6466
private String rel;
6567
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
68+
private Object allowMultipleIncludes;
69+
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
6670
private Object customAttributes;
6771

6872
private String include;
@@ -109,15 +113,14 @@ private void activate() {
109113
*/
110114
private @NotNull String buildIncludeString(@NotNull List<String> libraryPaths, @NotNull Map<String, String> attrs,
111115
@NotNull Map<String, String> customAttrs) {
112-
StringBuilder markup = new StringBuilder();
113-
for (String libraryPath : libraryPaths) {
114-
HtmlTagBuilder builder = new HtmlTagBuilder("link", false, xssApi);
115-
builder.setAttrs(attrs);
116-
builder.setAttrs(customAttrs);
117-
builder.setAttr("href", libraryPath);
118-
markup.append(builder.build());
119-
}
120-
return markup.toString();
116+
return new RequestIncludedLibraries(request, allowMultipleIncludes)
117+
.buildMarkupIgnoringDuplicateLibraries(libraryPaths, libraryPath -> {
118+
HtmlTagBuilder builder = new HtmlTagBuilder("link", false, xssApi);
119+
builder.setAttrs(attrs);
120+
builder.setAttrs(customAttrs);
121+
builder.setAttr("href", IncludeUtil.appendRequestPath(libraryPath, request));
122+
return builder;
123+
});
121124
}
122125

123126
/**

src/main/java/io/wcm/wcm/ui/clientlibs/components/HtmlTagBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ String build() {
7070
for (Map.Entry<String, String> attr : this.attrs.entrySet()) {
7171
markup.append(" ").append(attr.getKey());
7272
if (attr.getValue() != null) {
73-
markup.append("=\"");
74-
markup.append(xssApi.encodeForHTMLAttr(attr.getValue()));
75-
markup.append("\"");
73+
markup.append("=\"")
74+
.append(xssApi.encodeForHTMLAttr(attr.getValue()))
75+
.append("\"");
7676
}
7777
}
7878
markup.append(">");

src/main/java/io/wcm/wcm/ui/clientlibs/components/IncludeUtil.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Objects;
2828
import java.util.stream.Collectors;
2929

30+
import org.apache.sling.api.SlingHttpServletRequest;
3031
import org.apache.sling.api.resource.ResourceResolver;
3132
import org.jetbrains.annotations.NotNull;
3233
import org.jetbrains.annotations.Nullable;
@@ -97,7 +98,7 @@ else if (categories != null && categories.getClass().isArray()) {
9798
path = "/etc.clientlibs" + path.substring(5);
9899
}
99100
else if (resourceResolver.getResource(library.getPath()) == null) {
100-
// current render resourcer resolver has no access to the client library - ignore it
101+
// current render resource resolver has no access to the client library - ignore it
101102
path = null;
102103
}
103104
return path;
@@ -133,4 +134,14 @@ else if (resourceResolver.getResource(library.getPath()) == null) {
133134
return result;
134135
}
135136

137+
/**
138+
* Appends context path from current request.
139+
* @param path Path
140+
* @param request Current request
141+
* @return Path with context path
142+
*/
143+
public static @NotNull String appendRequestPath(@NotNull String path, @NotNull SlingHttpServletRequest request) {
144+
return request.getContextPath() + path;
145+
}
146+
136147
}

src/main/java/io/wcm/wcm/ui/clientlibs/components/JSInclude.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public class JSInclude {
5757
private static final Set<String> TYPE_ALLOWED_VALUES = Set.of(
5858
"text/javascript", "module");
5959

60+
@SlingObject
61+
private SlingHttpServletRequest request;
6062
@SlingObject
6163
private ResourceResolver resourceResolver;
6264
@OSGiService
@@ -83,6 +85,8 @@ public class JSInclude {
8385
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
8486
private String type;
8587
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
88+
private Object allowMultipleIncludes;
89+
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
8690
private Object customAttributes;
8791

8892
private String include;
@@ -145,15 +149,14 @@ private void activate() {
145149
*/
146150
private @NotNull String buildIncludeString(@NotNull List<String> libraryPaths, @NotNull Map<String, String> attrs,
147151
@NotNull Map<String, String> customAttrs) {
148-
StringBuilder markup = new StringBuilder();
149-
for (String libraryPath : libraryPaths) {
150-
HtmlTagBuilder builder = new HtmlTagBuilder("script", true, xssApi);
151-
builder.setAttrs(attrs);
152-
builder.setAttrs(customAttrs);
153-
builder.setAttr("src", libraryPath);
154-
markup.append(builder.build());
155-
}
156-
return markup.toString();
152+
return new RequestIncludedLibraries(request, allowMultipleIncludes)
153+
.buildMarkupIgnoringDuplicateLibraries(libraryPaths, libraryPath -> {
154+
HtmlTagBuilder builder = new HtmlTagBuilder("script", true, xssApi);
155+
builder.setAttrs(attrs);
156+
builder.setAttrs(customAttrs);
157+
builder.setAttr("src", IncludeUtil.appendRequestPath(libraryPath, request));
158+
return builder;
159+
});
157160
}
158161

159162
/**
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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.wcm.ui.clientlibs.components;
21+
22+
import java.util.HashSet;
23+
import java.util.List;
24+
import java.util.Set;
25+
import java.util.function.Function;
26+
27+
import org.apache.commons.lang3.BooleanUtils;
28+
import org.apache.sling.api.SlingHttpServletRequest;
29+
import org.jetbrains.annotations.Nullable;
30+
31+
import com.adobe.granite.ui.clientlibs.HtmlLibraryManager;
32+
import com.drew.lang.annotations.NotNull;
33+
34+
class RequestIncludedLibraries {
35+
36+
/**
37+
* Request attributes that is also used by Granite UI clientlib manager to store the (raw) library
38+
* paths that are already included in the current request.
39+
*/
40+
private static final String RA_INCLUDED_LIBRARY_PATHS = HtmlLibraryManager.class.getName() + ".included";
41+
42+
private final SlingHttpServletRequest request;
43+
private final boolean allowMultipleIncludes;
44+
45+
RequestIncludedLibraries(@NotNull SlingHttpServletRequest request,
46+
@Nullable Object allowMultipleIncludes) {
47+
this.request = request;
48+
this.allowMultipleIncludes = toBoolean(allowMultipleIncludes);
49+
}
50+
51+
/**
52+
* Gets set of library paths from request attribute. If not set, attribute is initialized with an empty set.
53+
* @return Set of library paths attached to current request
54+
*/
55+
@SuppressWarnings("unchecked")
56+
private @NotNull Set<String> getLibaryPathsSetFromRequest() {
57+
Set<String> libraryPaths = (Set<String>)request.getAttribute(RA_INCLUDED_LIBRARY_PATHS);
58+
if (libraryPaths == null) {
59+
libraryPaths = new HashSet<>();
60+
request.setAttribute(RA_INCLUDED_LIBRARY_PATHS, libraryPaths);
61+
}
62+
return libraryPaths;
63+
}
64+
65+
/**
66+
* @param libraryPath Library path
67+
* @return true if given library was already included in current request.
68+
*/
69+
boolean isInlucded(@NotNull String libraryPath) {
70+
return getLibaryPathsSetFromRequest().contains(libraryPath);
71+
}
72+
73+
/**
74+
* Store library path as included in current request.
75+
* @param libraryPath Library path
76+
*/
77+
void storeIncluded(@NotNull String libraryPath) {
78+
getLibaryPathsSetFromRequest().add(libraryPath);
79+
}
80+
81+
/**
82+
* Builds the markup for all given HTML libraries that are not already included in the current request.
83+
* @param libraryPaths Library paths
84+
* @param htmlTagBuilderFactory Factory to create HTML tag builders
85+
* @return Markup
86+
*/
87+
String buildMarkupIgnoringDuplicateLibraries(@NotNull List<String> libraryPaths,
88+
@NotNull Function<String, HtmlTagBuilder> htmlTagBuilderFactory) {
89+
StringBuilder markup = new StringBuilder();
90+
for (String libraryPath : libraryPaths) {
91+
// ignore libraries that are already included
92+
if (!allowMultipleIncludes && isInlucded(libraryPath)) {
93+
continue;
94+
}
95+
// build markup for library
96+
markup.append(htmlTagBuilderFactory.apply(libraryPath).build());
97+
// mark library as included
98+
storeIncluded(libraryPath);
99+
}
100+
return markup.toString();
101+
}
102+
103+
private static boolean toBoolean(Object value) {
104+
if (value instanceof Boolean) {
105+
return (Boolean)value;
106+
}
107+
else if (value instanceof String) {
108+
return BooleanUtils.toBoolean((String)value);
109+
}
110+
return false;
111+
}
112+
113+
}

src/main/webapp/app-root/sightly/templates/clientlib.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
* @param nonce see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce
1010
* @param referrerpolicy see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-referrerpolicy
1111
* @param type see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
12+
* @param allowMultipleIncludes If set to true, duplicate inclusions of this client library in a single request are not eliminated.
1213
* @param customAttributes List of custom attributes, each list item in syntax 'attr=value' or just 'attr'
1314
*/-->
1415
<template data-sly-template.js="${@ categories, async, crossorigin, defer, integrity,
15-
nomodule, nonce, referrerpolicy, type, customAttributes}">
16+
nomodule, nonce, referrerpolicy, type, allowMultipleIncludes, customAttributes}">
1617
<sly data-sly-test="${request.getResourceResolver}"
1718
data-sly-use.clientlib="${'io.wcm.wcm.ui.clientlibs.components.JSInclude' @
1819
categories=categories, async=async, crossorigin=crossorigin, defer=defer, integrity=integrity,
19-
nomodule=nomodule, nonce=nonce, referrerpolicy=referrerpolicy, type=type, customAttributes=customAttributes}">
20+
nomodule=nomodule, nonce=nonce, referrerpolicy=referrerpolicy, type=type,
21+
allowMultipleIncludes=allowMultipleIncludes, customAttributes=customAttributes}">
2022
${clientlib.include @ context='unsafe'}
2123
</sly>
2224
</template>
@@ -25,12 +27,14 @@
2527
* Template used for including CSS client libraries.
2628
* @param categories Client Library categories
2729
* @param rel prefetch|preload see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel
30+
* @param allowMultipleIncludes If set to true, duplicate inclusions of this client library in a single request are not eliminated.
2831
* @param customAttributes List of custom attributes, each list item in syntax 'attr=value' or just 'attr'
2932
*/-->
30-
<template data-sly-template.css="${@ categories, customAttributes}">
33+
<template data-sly-template.css="${@ categories, rel, allowMultipleIncludes, customAttributes}">
3134
<sly data-sly-test="${request.getResourceResolver}"
3235
data-sly-use.clientlib="${'io.wcm.wcm.ui.clientlibs.components.CSSInclude' @
33-
categories=categories, rel=rel, customAttributes=customAttributes}">
36+
categories=categories, rel=rel,
37+
allowMultipleIncludes=allowMultipleIncludes, customAttributes=customAttributes}">
3438
${clientlib.include @ context='unsafe'}
3539
</sly>
3640
</template>

src/site/markdown/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ Extensions for AEM HTML client libraries.
2222

2323
|WCM Clientlibs UI Extensions version |AEM version supported
2424
|-------------------------------------|----------------------
25-
|1.3.x or higher |AEM 6.5.7+, AEMaaCS
25+
|1.4.0 or higher |AEM 6.5.17+, AEMaaCS
26+
|1.3.0 |AEM 6.5.7+, AEMaaCS
2627
|1.2.x |AEM 6.4+, AEMaaCS
2728
|1.1.x |AEM 6.3+
2829
|1.0.x |AEM 6.2+

src/site/markdown/usage.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The following advanced script tag attributes are supported:
2727
* `nonce` = {string}
2828
* `referrerpolicy` = no-referrer | no-referrer-when-downgrade | origin | origin-when-cross-origin | same-origin | strict-origin | strict-origin-when-cross-origin | unsafe-url
2929
* `type` = module | text/javascript
30+
* `allowMultipleIncludes` - by default, multiple includes of the same script in a single request are eliminated. If setting this property to `true`, they are included multiple times if requested.
3031
* `customAttributes` - set arbitrary HTML attributes, e.g. `customAttributes=['attr1=value 1','data-attr2=5','attr3']`
3132

3233
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#Attributes for a full documentation of this attributes.
@@ -44,4 +45,5 @@ Include CSS without special attributes:
4445
The following advanced link tag attributes are supported:
4546

4647
* `rel` = prefetch | preload (if not given, `rel="stylesheet" type="text/css"` is set)
48+
* `allowMultipleIncludes` - by default, multiple includes of the same script in a single request are eliminated. If setting this property to `true`, they are included multiple times if requested.
4749
* `customAttributes` - set arbitrary HTML attributes, e.g. `customAttributes=['attr1=value 1','data-attr2=5','attr3']`

src/test/java/io/wcm/wcm/ui/clientlibs/components/AbstractIncludeTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828

2929
import java.util.Collection;
3030
import java.util.List;
31+
import java.util.stream.Stream;
3132

3233
import org.apache.sling.xss.XSSAPI;
3334
import org.junit.jupiter.api.BeforeEach;
35+
import org.junit.jupiter.params.provider.Arguments;
3436
import org.mockito.Mock;
3537
import org.mockito.invocation.InvocationOnMock;
3638
import org.mockito.stubbing.Answer;
@@ -99,4 +101,10 @@ public String answer(InvocationOnMock invocation) throws Throwable {
99101
return clientlib;
100102
}
101103

104+
static Stream<Arguments> booleanTrueVariants() {
105+
return Stream.of(
106+
Arguments.of(true),
107+
Arguments.of("true"));
108+
}
109+
102110
}

0 commit comments

Comments
 (0)