Skip to content

Commit 86d78d6

Browse files
Merge pull request #205 from wttech/executor-session-isolation
Executor session isolation
2 parents 3f96826 + 9465b40 commit 86d78d6

File tree

13 files changed

+103
-40
lines changed

13 files changed

+103
-40
lines changed

core/src/main/java/dev/vml/es/acm/core/code/Executor.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import java.util.Map;
1818
import java.util.Optional;
1919
import java.util.concurrent.ConcurrentHashMap;
20+
import java.util.function.Consumer;
21+
import java.util.function.Function;
2022
import java.util.regex.Pattern;
2123
import org.apache.commons.lang3.StringUtils;
2224
import org.apache.sling.api.resource.LoginException;
@@ -92,7 +94,7 @@ public class Executor {
9294
}
9395

9496
@Reference
95-
private ResourceResolverFactory resourceResolverFactory;
97+
private ResourceResolverFactory resolverFactory;
9698

9799
@Reference
98100
private OsgiContext osgiContext;
@@ -120,10 +122,9 @@ public ExecutionContext createContext(
120122
}
121123

122124
public Execution execute(Executable executable, ExecutionContextOptions contextOptions) throws AcmException {
123-
try (ResourceResolver resourceResolver =
124-
ResolverUtils.contentResolver(resourceResolverFactory, contextOptions.getUserId());
125+
try (ResourceResolver resolver = ResolverUtils.contentResolver(resolverFactory, contextOptions.getUserId());
125126
ExecutionContext executionContext = createContext(
126-
ExecutionId.generate(), contextOptions.getExecutionMode(), executable, resourceResolver)) {
127+
ExecutionId.generate(), contextOptions.getExecutionMode(), executable, resolver)) {
127128
return execute(executionContext);
128129
} catch (LoginException e) {
129130
throw new AcmException(
@@ -166,15 +167,13 @@ private ImmediateExecution executeImmediately(ExecutionContext context) {
166167
return execution.end(ExecutionStatus.SUCCEEDED);
167168
}
168169

169-
Locker locker = context.getCodeContext().getLocker();
170170
String lockName = executableLockName(context);
171-
172-
if (locker.isLocked(lockName)) {
171+
if (queryLocker(resolverFactory, l -> l.isLocked(lockName))) {
173172
return execution.end(ExecutionStatus.SKIPPED);
174173
}
175174

176175
try {
177-
locker.lock(lockName);
176+
useLocker(resolverFactory, l -> l.lock(lockName));
178177
statuses.put(context.getId(), ExecutionStatus.RUNNING);
179178
if (config.logPrintingEnabled()) {
180179
context.getOut().fromSelfLogger();
@@ -184,7 +183,7 @@ private ImmediateExecution executeImmediately(ExecutionContext context) {
184183
contentScript.run();
185184
return execution.end(ExecutionStatus.SUCCEEDED);
186185
} finally {
187-
locker.unlock(lockName);
186+
useLocker(resolverFactory, l -> l.unlock(lockName));
188187
}
189188
} catch (Throwable e) {
190189
execution.error(e);
@@ -209,9 +208,7 @@ public Optional<ExecutionStatus> checkStatus(String executionId) {
209208

210209
private void handleHistory(ExecutionContext context, ImmediateExecution execution) {
211210
if (context.isHistory() && (context.isDebug() || (execution.getStatus() != ExecutionStatus.SKIPPED))) {
212-
ExecutionHistory history =
213-
new ExecutionHistory(context.getCodeContext().getResourceResolver());
214-
history.save(context, execution);
211+
useHistory(resolverFactory, h -> h.save(context, execution));
215212
}
216213
}
217214

@@ -299,6 +296,10 @@ public ScheduleResult schedule(ExecutionContext context) {
299296
}
300297
}
301298

299+
public void reset() {
300+
useLocker(resolverFactory, l -> l.unlockAll());
301+
}
302+
302303
public boolean isDebug() {
303304
return config.debug();
304305
}
@@ -310,4 +311,16 @@ public boolean isHistory() {
310311
public boolean isLocked(ExecutionContext context) {
311312
return context.getCodeContext().getLocker().isLocked(executableLockName(context));
312313
}
314+
315+
private <T> T queryLocker(ResourceResolverFactory resolverFactory, Function<Locker, T> consumer) {
316+
return ResolverUtils.queryContentResolver(resolverFactory, null, r -> consumer.apply(new Locker(r)));
317+
}
318+
319+
private void useLocker(ResourceResolverFactory resolverFactory, Consumer<Locker> consumer) {
320+
ResolverUtils.useContentResolver(resolverFactory, null, r -> consumer.accept(new Locker(r)));
321+
}
322+
323+
private void useHistory(ResourceResolverFactory resolverFactory, Consumer<ExecutionHistory> consumer) {
324+
ResolverUtils.useContentResolver(resolverFactory, null, r -> consumer.accept(new ExecutionHistory(r)));
325+
}
313326
}

core/src/main/java/dev/vml/es/acm/core/code/FileManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ private void validatePath(File targetFile) {
7272
String rootCanonical = rootDir.getCanonicalPath();
7373
String targetCanonical = targetFile.getCanonicalPath();
7474
if (!targetCanonical.startsWith(rootCanonical + File.separator) && !targetCanonical.equals(rootCanonical)) {
75-
throw new AcmException(
76-
String.format("File path '%s' must be within the root directory '%s'!", targetFile.getPath(), rootCanonical));
75+
throw new AcmException(String.format(
76+
"File path '%s' must be within the root directory '%s'!", targetFile.getPath(), rootCanonical));
7777
}
7878
} catch (IOException e) {
7979
throw new AcmException(String.format("File path resolution error '%s'!", targetFile.getPath()), e);

core/src/main/java/dev/vml/es/acm/core/code/Locker.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ public void unlock(String lockName) {
8888
}
8989
}
9090

91+
public void unlockAll() {
92+
Resource root = resolver.getResource(ROOT);
93+
if (root == null) {
94+
return;
95+
}
96+
try {
97+
resolver.delete(root);
98+
resolver.commit();
99+
LOG.debug("Deleted all locks");
100+
} catch (PersistenceException e) {
101+
throw new AcmException("Cannot delete all locks!", e);
102+
}
103+
}
104+
91105
private Resource getLock(String lockName) {
92106
String name = normalizeName(lockName);
93107
if (StringUtils.isBlank(name)) {

core/src/main/java/dev/vml/es/acm/core/event/EventDispatcher.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dev.vml.es.acm.core.code.ExecutionHistory;
44
import dev.vml.es.acm.core.code.ExecutionQueue;
5+
import dev.vml.es.acm.core.code.Executor;
56
import dev.vml.es.acm.core.util.ResolverUtils;
67
import java.util.Collections;
78
import org.apache.sling.api.resource.LoginException;
@@ -25,6 +26,9 @@ public class EventDispatcher implements EventListener {
2526
@Reference
2627
private ExecutionQueue executionQueue;
2728

29+
@Reference
30+
private Executor executor;
31+
2832
@Reference
2933
private ResourceResolverFactory resourceResolverFactory;
3034

@@ -39,8 +43,9 @@ public void onEvent(Event event) {
3943
return;
4044
}
4145
switch (eventType) {
42-
case EXECUTION_QUEUE_RESET:
46+
case EXECUTOR_RESET:
4347
executionQueue.reset();
48+
executor.reset();
4449
break;
4550
case HISTORY_CLEAR:
4651
doHistoryClear();

core/src/main/java/dev/vml/es/acm/core/event/EventType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import java.util.Optional;
55

66
public enum EventType {
7-
EXECUTION_QUEUE_RESET,
7+
EXECUTOR_RESET,
88
HISTORY_CLEAR;
99

1010
public static Optional<EventType> of(String name) {

core/src/main/java/dev/vml/es/acm/core/instance/HealthChecker.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ private void checkCodeExecutor(List<HealthIssue> issues, ResourceResolver resour
268268
ExecutionId.generate(), ExecutionMode.RUN, Code.consoleMinimal(), resourceResolver)) {
269269
context.setHistory(false);
270270
Execution execution = executor.execute(context);
271+
if (execution.getStatus() == ExecutionStatus.SKIPPED) {
272+
return; // do not check when locked
273+
}
271274
if (execution.getStatus() != ExecutionStatus.SUCCEEDED) {
272275
issues.add(new HealthIssue(
273276
HealthIssueSeverity.CRITICAL,

core/src/main/java/dev/vml/es/acm/core/util/ResolverUtils.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import dev.vml.es.acm.core.repo.RepoException;
55
import java.util.HashMap;
66
import java.util.Map;
7+
import java.util.function.Consumer;
8+
import java.util.function.Function;
79
import javax.jcr.*;
810
import org.apache.commons.lang3.StringUtils;
911
import org.apache.sling.api.resource.*;
@@ -35,6 +37,25 @@ public static ResourceResolver contentResolver(
3537
return serviceResolver(resourceResolverFactory, Subservice.CONTENT, userImpersonationId);
3638
}
3739

40+
public static <T> T queryContentResolver(
41+
ResourceResolverFactory resolverFactory,
42+
String userImpersonationId,
43+
Function<ResourceResolver, T> consumer) {
44+
try (ResourceResolver resourceResolver = ResolverUtils.contentResolver(resolverFactory, userImpersonationId)) {
45+
return consumer.apply(resourceResolver);
46+
} catch (LoginException e) {
47+
throw new RepoException("Cannot access repository while using content resolver!", e);
48+
}
49+
}
50+
51+
public static void useContentResolver(
52+
ResourceResolverFactory resolverFactory, String userImpersonationId, Consumer<ResourceResolver> consumer) {
53+
queryContentResolver(resolverFactory, userImpersonationId, r -> {
54+
consumer.accept(r);
55+
return null;
56+
});
57+
}
58+
3859
public static ResourceResolver mockResolver(ResourceResolverFactory resourceResolverFactory) throws LoginException {
3960
return serviceResolver(resourceResolverFactory, Subservice.MOCK, null);
4061
}

core/src/main/java/dev/vml/es/acm/core/util/StringUtil.java

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,27 @@ private static String truncateCode(String code, int maxLength, boolean skipStart
3333
}
3434
String humanSize = FileUtils.byteCountToDisplaySize(code.length());
3535
String text = StringUtils.trimToEmpty(code);
36+
37+
int totalLines = text.isEmpty() ? 0 : text.split("\r?\n").length;
38+
String lineWord = totalLines == 1 ? "line" : "lines";
39+
3640
if (maxLength < 0 || text.length() <= maxLength) {
37-
return String.format("%s\n```\n%s\n```", humanSize, text);
41+
String msg = String.format("%s, %d %s", humanSize, totalLines, lineWord);
42+
return String.format("%s\n```\n%s\n```", msg, text);
3843
}
39-
int skippedChars = text.length() - maxLength;
40-
String remaining;
41-
int skippedLines;
4244

45+
String remaining;
46+
String rangeInfo;
4347
if (skipStart) {
48+
int skippedChars = text.length() - maxLength;
4449
remaining = text.substring(skippedChars);
45-
String skippedPart = text.substring(0, skippedChars);
46-
skippedLines = skippedPart.isEmpty() ? 0 : skippedPart.split("\r?\n").length;
50+
rangeInfo = String.format("last %d chars", maxLength);
4751
} else {
4852
remaining = text.substring(0, maxLength);
49-
String skippedPart = text.substring(maxLength);
50-
skippedLines = skippedPart.isEmpty() ? 0 : skippedPart.split("\r?\n").length;
51-
}
52-
String lineWord = skippedLines == 1 ? "line" : "lines";
53-
String msg;
54-
if (skippedLines > 0) {
55-
msg = String.format("%s, %d %s skipped", humanSize, skippedLines, lineWord);
56-
} else {
57-
msg = humanSize;
53+
rangeInfo = String.format("first %d chars", maxLength);
5854
}
55+
56+
String msg = String.format("%s, %d %s (%s)", humanSize, totalLines, lineWord, rangeInfo);
5957
return String.format("%s\n```\n%s\n```", msg, remaining);
6058
}
6159

test/project/setup.sh

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ print_error() {
3535
echo >&2
3636
}
3737

38-
package_append_to_all() {
38+
# https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/aem-project-content-package-structure
39+
add_vendor_package() {
3940
local groupId="$1"
4041
local artifactId="$2"
4142
local version="$3"
@@ -52,7 +53,7 @@ package_append_to_all() {
5253
<groupId>${groupId}</groupId>
5354
<artifactId>${artifactId}</artifactId>
5455
<type>${type}</type>
55-
<target>/apps/${PROJECT_NAME}-packages/application/install</target>
56+
<target>/apps/vendor-packages/container/install</target>
5657
</embedded>"
5758
local all_pom="all/pom.xml"
5859

@@ -110,8 +111,8 @@ git commit -m "AEM Compose setup"
110111

111112
print_step "Setting up ACM in the project"
112113

113-
package_append_to_all "dev.vml.es" "acm.all" "$ACM_VERSION" "zip"
114-
package_append_to_all "dev.vml.es" "acm.ui.content.example" "$ACM_VERSION" "zip"
114+
add_vendor_package "dev.vml.es" "acm.all" "$ACM_VERSION" "zip"
115+
add_vendor_package "dev.vml.es" "acm.ui.content.example" "$ACM_VERSION" "zip"
115116

116117
git add -A
117118
git commit -m "ACM packages added to all/pom.xml"

ui.config/src/main/content/jcr_root/apps/acm-config/osgiconfig/config/org.apache.sling.jcr.repoinit.RepositoryInitializer~acmcore.config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ scripts=["
1919
allow jcr:read on /conf
2020
end
2121

22+
create path /apps/acm(sling:Folder)
23+
create path /apps/cq/core/content/nav/tools/acm(sling:Folder)
24+
2225
set ACL for everyone
2326
deny jcr:read on /apps/acm
2427
deny jcr:read on /apps/cq/core/content/nav/tools/acm

0 commit comments

Comments
 (0)