diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d633cfbe3..1b5dd499b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -55,16 +55,27 @@ jobs:
retention-days: 3
test:
- name: 'Test ACM Package'
+ name: 'Test ACM Package (AEM ${{ matrix.aem }})'
runs-on: ubuntu-latest
needs: build
timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - aem: '6.5.0'
+ quickstart_prefix: 'cq-quickstart-6.5.0.jar'
+ java_download_url: 'https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u432-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u432b06.tar.gz'
+ - aem: '6.6.x'
+ quickstart_prefix: 'cq-quickstart-6.6.'
+ java_download_url: 'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21%2B35/OpenJDK21U-jdk_x64_linux_hotspot_21_35.tar.gz'
permissions:
contents: read
actions: read
checks: write
env:
AEM_OUTPUT_LOG_MODE: none
+ AEM_VENDOR_JAVA_DOWNLOAD_URL: ${{ matrix.java_download_url }}
FORCE_COLOR: 1
steps:
- name: Checkout Code
@@ -110,7 +121,13 @@ jobs:
echo "Preparing AEM instance"
echo "AEM_PUBLISH_ACTIVE=false" >> .env
mkdir -p aem/home/lib
- aws s3 cp --no-progress --only-show-errors s3://${{ secrets.AWS_S3_BUCKET_NAME }}/aem/on-prem/cq-quickstart-6.5.0.jar aem/home/lib/cq-quickstart-6.5.0.jar
+ JAR_FILE=$(aws s3 ls "s3://${{ secrets.AWS_S3_BUCKET_NAME }}/aem/on-prem/${{ matrix.quickstart_prefix }}" | awk '{print $4}' | sort -V | tail -n 1)
+ if [ -z "$JAR_FILE" ]; then
+ echo "::error::No AEM quickstart matching '${{ matrix.quickstart_prefix }}' found in S3!"
+ exit 1
+ fi
+ echo "Downloading AEM quickstart '$JAR_FILE'"
+ aws s3 cp --no-progress --only-show-errors "s3://${{ secrets.AWS_S3_BUCKET_NAME }}/aem/on-prem/$JAR_FILE" "aem/home/lib/$JAR_FILE"
aws s3 cp --no-progress --only-show-errors s3://${{ secrets.AWS_S3_BUCKET_NAME }}/aem/on-prem/license.properties aem/home/lib/license.properties
echo "Prepared AEM instance"
echo "Launching AEM instance"
@@ -141,6 +158,6 @@ jobs:
uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
- name: acm-playwright-report
+ name: acm-playwright-report-${{ matrix.aem }}
path: ./test/e2e/playwright-report/
retention-days: 30
\ No newline at end of file
diff --git a/aem/default/etc/aem.yml b/aem/default/etc/aem.yml
index 14f058863..7727f1b64 100755
--- a/aem/default/etc/aem.yml
+++ b/aem/default/etc/aem.yml
@@ -260,10 +260,10 @@ vendor:
# Auto-installed JDK options
download:
# Source URL with template vars support
- url: "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u432-b06/OpenJDK8U-jdk_[[.Arch]]_[[.Os]]_hotspot_8u432b06.[[.ArchiveExt]]"
+ #url: "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u432-b06/OpenJDK8U-jdk_[[.Arch]]_[[.Os]]_hotspot_8u432b06.[[.ArchiveExt]]"
#url: "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_[[.Arch]]_[[.Os]]_hotspot_11.0.18_10.[[.ArchiveExt]]"
#url: "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8%2B7/OpenJDK17U-jdk_[[.Arch]]_[[.Os]]_hotspot_17.0.8_7.[[.ArchiveExt]]"
- #url: "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21%2B35/OpenJDK21U-jdk_[[.Arch]]_[[.Os]]_hotspot_21_35.[[.ArchiveExt]]"
+ url: "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21%2B35/OpenJDK21U-jdk_[[.Arch]]_[[.Os]]_hotspot_21_35.[[.ArchiveExt]]"
# Map source URL template vars to be compatible with Adoptium Java
replacements:
# Var 'Os' (GOOS)
diff --git a/core/pom.xml b/core/pom.xml
index fc7457906..ad84f5b68 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -95,7 +95,9 @@ DynamicImport-Package: *
src/main/java/**/*.java
src/test/java/**/*.java
-
+
+ ${palantir.java.format.version}
+
@@ -168,12 +170,6 @@ DynamicImport-Package: *
org.slf4j
slf4j-api
-
- org.apache.sling
- org.apache.sling.commons.classloader
- 1.4.2
- provided
-
javax.jcr
jcr
diff --git a/core/src/main/java/dev/vml/es/acm/core/acl/Acl.java b/core/src/main/java/dev/vml/es/acm/core/acl/Acl.java
index bc7668932..c3f8563f9 100644
--- a/core/src/main/java/dev/vml/es/acm/core/acl/Acl.java
+++ b/core/src/main/java/dev/vml/es/acm/core/acl/Acl.java
@@ -668,4 +668,8 @@ public AclAuthorizable authorizable(Authorizable authorizable) {
public AclChecker getChecker() {
return checker;
}
+
+ public AclContext getContext() {
+ return context;
+ }
}
diff --git a/core/src/main/java/dev/vml/es/acm/core/acl/AclContext.java b/core/src/main/java/dev/vml/es/acm/core/acl/AclContext.java
index 7f5259933..b8e0b08ac 100644
--- a/core/src/main/java/dev/vml/es/acm/core/acl/AclContext.java
+++ b/core/src/main/java/dev/vml/es/acm/core/acl/AclContext.java
@@ -23,7 +23,9 @@
public class AclContext {
- private final Logger logger;
+ private static final Logger LOG = LoggerFactory.getLogger(AclContext.class);
+
+ private Logger logger = LOG;
private final ResourceResolver resourceResolver;
@@ -37,7 +39,6 @@ public AclContext(ResourceResolver resourceResolver) {
public AclContext(ResourceResolver resourceResolver, CommitPolicy commitPolicy) {
try {
- this.logger = LoggerFactory.getLogger(AclContext.class);
JackrabbitSession session = (JackrabbitSession) resourceResolver.adaptTo(Session.class);
UserManager userManager = session.getUserManager();
AccessControlManager accessControlManager = session.getAccessControlManager();
@@ -54,6 +55,10 @@ public Logger getLogger() {
return logger;
}
+ public void setLogger(Logger logger) {
+ this.logger = logger != null ? logger : LOG;
+ }
+
public ResourceResolver getResourceResolver() {
return resourceResolver;
}
diff --git a/core/src/main/java/dev/vml/es/acm/core/code/CodePrintStream.java b/core/src/main/java/dev/vml/es/acm/core/code/CodePrintStream.java
index 76efe5dd1..d7c58c459 100644
--- a/core/src/main/java/dev/vml/es/acm/core/code/CodePrintStream.java
+++ b/core/src/main/java/dev/vml/es/acm/core/code/CodePrintStream.java
@@ -1,6 +1,7 @@
package dev.vml.es.acm.core.code;
import dev.vml.es.acm.core.AcmException;
+import dev.vml.es.acm.core.code.log.CodePrintLogger;
import dev.vml.es.acm.core.code.log.LogInterceptor;
import dev.vml.es.acm.core.code.log.LogInterceptorManager;
import dev.vml.es.acm.core.code.log.LogMessage;
@@ -36,6 +37,8 @@ public class CodePrintStream extends PrintStream {
private final Set loggerNames;
+ private final Set bridgedLoggerNames;
+
private boolean loggerTimestamps;
private LogInterceptor.Handle interceptorHandle;
@@ -51,17 +54,30 @@ public CodePrintStream(OutputStream output, String id, LogInterceptorManager log
this.logInterceptorManager = logInterceptorManager;
this.loggerNames = new HashSet<>();
+ this.bridgedLoggerNames = new HashSet<>();
this.loggerTimestamps = true;
- this.logger = LoggerFactory.getLogger(id);
+ this.logger = bridgeLogger(LoggerFactory.getLogger(id));
this.printerTimestamps = true;
}
+ Logger bridgeLogger(Logger delegate) {
+ bridgedLoggerNames.add(delegate.getName());
+ return new CodePrintLogger(delegate, this::handleLogMessage);
+ }
+
+ private boolean isBridgedLogger(String loggerName) {
+ return LOGGER_NAME_ACL.equals(loggerName)
+ || LOGGER_NAME_REPO.equals(loggerName)
+ || bridgedLoggerNames.contains(loggerName);
+ }
+
public void fromLogger(String loggerName) {
if (StringUtils.isBlank(loggerName)) {
throw new AcmException("Logger name cannot be blank!");
}
- loggerNames.add(loggerName);
- updateInterceptor();
+ if (loggerNames.add(loggerName.trim())) {
+ updateInterceptor();
+ }
}
@Override
@@ -73,8 +89,11 @@ public void close() {
private void updateInterceptor() {
detachInterceptor();
if (!loggerNames.isEmpty() && logInterceptorManager != null) {
- interceptorHandle =
- logInterceptorManager.attach(this::handleLogMessage, loggerNames.toArray(new String[0]));
+ String[] externalLoggers =
+ loggerNames.stream().filter(name -> !isBridgedLogger(name)).toArray(String[]::new);
+ if (externalLoggers.length > 0) {
+ interceptorHandle = logInterceptorManager.attach(this::handleLogMessage, externalLoggers);
+ }
}
}
@@ -134,13 +153,24 @@ public void fromRecommendedLoggers() {
}
public void fromLoggers(String... loggerNames) {
+ if (loggerNames == null) {
+ return;
+ }
+ boolean changed = false;
for (String loggerName : loggerNames) {
- fromLogger(loggerName);
+ if (StringUtils.isNotBlank(loggerName) && this.loggerNames.add(loggerName.trim())) {
+ changed = true;
+ }
+ }
+ if (changed) {
+ updateInterceptor();
}
}
public void fromLoggers(List loggerNames) {
- loggerNames.forEach(this::fromLogger);
+ if (loggerNames != null) {
+ fromLoggers(loggerNames.toArray(new String[0]));
+ }
}
public void setPrinterTimestamps(boolean flag) {
diff --git a/core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java b/core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java
index 7a19538a8..40b021c90 100644
--- a/core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java
+++ b/core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java
@@ -221,6 +221,15 @@ public Conditions getConditions() {
}
private void customizeBinding() {
+ codeContext
+ .getRepo()
+ .setLogger(printStream.bridgeLogger(codeContext.getRepo().getLogger()));
+ codeContext
+ .getAcl()
+ .getContext()
+ .setLogger(printStream.bridgeLogger(
+ codeContext.getAcl().getContext().getLogger()));
+
Binding binding = getCodeContext().getBinding();
binding.setVariable("context", this);
diff --git a/core/src/main/java/dev/vml/es/acm/core/code/log/CodePrintLogger.java b/core/src/main/java/dev/vml/es/acm/core/code/log/CodePrintLogger.java
new file mode 100644
index 000000000..70fe42a36
--- /dev/null
+++ b/core/src/main/java/dev/vml/es/acm/core/code/log/CodePrintLogger.java
@@ -0,0 +1,441 @@
+package dev.vml.es.acm.core.code.log;
+
+import java.util.function.Consumer;
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+import org.slf4j.helpers.MessageFormatter;
+
+/**
+ * SLF4J {@link Logger} decorator that forwards log messages directly to a {@link LogMessage} listener
+ * in addition to the underlying SLF4J logger, without relying on Logback internals or reflection.
+ */
+public class CodePrintLogger implements Logger {
+
+ private static final String LEVEL_TRACE = "TRACE";
+ private static final String LEVEL_DEBUG = "DEBUG";
+ private static final String LEVEL_INFO = "INFO";
+ private static final String LEVEL_WARN = "WARN";
+ private static final String LEVEL_ERROR = "ERROR";
+
+ private final Logger delegate;
+ private final Consumer listener;
+
+ public CodePrintLogger(Logger delegate, Consumer listener) {
+ this.delegate = delegate;
+ this.listener = listener;
+ }
+
+ private void emit(String level, String message) {
+ if (listener != null) {
+ listener.accept(new LogMessage(
+ delegate.getName(), level, message != null ? message : "", System.currentTimeMillis()));
+ }
+ }
+
+ private void emit(String level, String format, Object arg) {
+ emit(level, MessageFormatter.format(format, arg).getMessage());
+ }
+
+ private void emit(String level, String format, Object arg1, Object arg2) {
+ emit(level, MessageFormatter.format(format, arg1, arg2).getMessage());
+ }
+
+ private void emit(String level, String format, Object[] arguments) {
+ emit(level, MessageFormatter.arrayFormat(format, arguments).getMessage());
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return delegate.isTraceEnabled();
+ }
+
+ @Override
+ public void trace(String msg) {
+ delegate.trace(msg);
+ if (delegate.isTraceEnabled()) {
+ emit(LEVEL_TRACE, msg);
+ }
+ }
+
+ @Override
+ public void trace(String format, Object arg) {
+ delegate.trace(format, arg);
+ if (delegate.isTraceEnabled()) {
+ emit(LEVEL_TRACE, format, arg);
+ }
+ }
+
+ @Override
+ public void trace(String format, Object arg1, Object arg2) {
+ delegate.trace(format, arg1, arg2);
+ if (delegate.isTraceEnabled()) {
+ emit(LEVEL_TRACE, format, arg1, arg2);
+ }
+ }
+
+ @Override
+ public void trace(String format, Object... arguments) {
+ delegate.trace(format, arguments);
+ if (delegate.isTraceEnabled()) {
+ emit(LEVEL_TRACE, format, arguments);
+ }
+ }
+
+ @Override
+ public void trace(String msg, Throwable t) {
+ delegate.trace(msg, t);
+ if (delegate.isTraceEnabled()) {
+ emit(LEVEL_TRACE, msg);
+ }
+ }
+
+ @Override
+ public boolean isTraceEnabled(Marker marker) {
+ return delegate.isTraceEnabled(marker);
+ }
+
+ @Override
+ public void trace(Marker marker, String msg) {
+ delegate.trace(marker, msg);
+ if (delegate.isTraceEnabled(marker)) {
+ emit(LEVEL_TRACE, msg);
+ }
+ }
+
+ @Override
+ public void trace(Marker marker, String format, Object arg) {
+ delegate.trace(marker, format, arg);
+ if (delegate.isTraceEnabled(marker)) {
+ emit(LEVEL_TRACE, format, arg);
+ }
+ }
+
+ @Override
+ public void trace(Marker marker, String format, Object arg1, Object arg2) {
+ delegate.trace(marker, format, arg1, arg2);
+ if (delegate.isTraceEnabled(marker)) {
+ emit(LEVEL_TRACE, format, arg1, arg2);
+ }
+ }
+
+ @Override
+ public void trace(Marker marker, String format, Object... argArray) {
+ delegate.trace(marker, format, argArray);
+ if (delegate.isTraceEnabled(marker)) {
+ emit(LEVEL_TRACE, format, argArray);
+ }
+ }
+
+ @Override
+ public void trace(Marker marker, String msg, Throwable t) {
+ delegate.trace(marker, msg, t);
+ if (delegate.isTraceEnabled(marker)) {
+ emit(LEVEL_TRACE, msg);
+ }
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return delegate.isDebugEnabled();
+ }
+
+ @Override
+ public void debug(String msg) {
+ delegate.debug(msg);
+ if (delegate.isDebugEnabled()) {
+ emit(LEVEL_DEBUG, msg);
+ }
+ }
+
+ @Override
+ public void debug(String format, Object arg) {
+ delegate.debug(format, arg);
+ if (delegate.isDebugEnabled()) {
+ emit(LEVEL_DEBUG, format, arg);
+ }
+ }
+
+ @Override
+ public void debug(String format, Object arg1, Object arg2) {
+ delegate.debug(format, arg1, arg2);
+ if (delegate.isDebugEnabled()) {
+ emit(LEVEL_DEBUG, format, arg1, arg2);
+ }
+ }
+
+ @Override
+ public void debug(String format, Object... arguments) {
+ delegate.debug(format, arguments);
+ if (delegate.isDebugEnabled()) {
+ emit(LEVEL_DEBUG, format, arguments);
+ }
+ }
+
+ @Override
+ public void debug(String msg, Throwable t) {
+ delegate.debug(msg, t);
+ if (delegate.isDebugEnabled()) {
+ emit(LEVEL_DEBUG, msg);
+ }
+ }
+
+ @Override
+ public boolean isDebugEnabled(Marker marker) {
+ return delegate.isDebugEnabled(marker);
+ }
+
+ @Override
+ public void debug(Marker marker, String msg) {
+ delegate.debug(marker, msg);
+ if (delegate.isDebugEnabled(marker)) {
+ emit(LEVEL_DEBUG, msg);
+ }
+ }
+
+ @Override
+ public void debug(Marker marker, String format, Object arg) {
+ delegate.debug(marker, format, arg);
+ if (delegate.isDebugEnabled(marker)) {
+ emit(LEVEL_DEBUG, format, arg);
+ }
+ }
+
+ @Override
+ public void debug(Marker marker, String format, Object arg1, Object arg2) {
+ delegate.debug(marker, format, arg1, arg2);
+ if (delegate.isDebugEnabled(marker)) {
+ emit(LEVEL_DEBUG, format, arg1, arg2);
+ }
+ }
+
+ @Override
+ public void debug(Marker marker, String format, Object... arguments) {
+ delegate.debug(marker, format, arguments);
+ if (delegate.isDebugEnabled(marker)) {
+ emit(LEVEL_DEBUG, format, arguments);
+ }
+ }
+
+ @Override
+ public void debug(Marker marker, String msg, Throwable t) {
+ delegate.debug(marker, msg, t);
+ if (delegate.isDebugEnabled(marker)) {
+ emit(LEVEL_DEBUG, msg);
+ }
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return delegate.isInfoEnabled();
+ }
+
+ @Override
+ public void info(String msg) {
+ delegate.info(msg);
+ emit(LEVEL_INFO, msg);
+ }
+
+ @Override
+ public void info(String format, Object arg) {
+ delegate.info(format, arg);
+ emit(LEVEL_INFO, format, arg);
+ }
+
+ @Override
+ public void info(String format, Object arg1, Object arg2) {
+ delegate.info(format, arg1, arg2);
+ emit(LEVEL_INFO, format, arg1, arg2);
+ }
+
+ @Override
+ public void info(String format, Object... arguments) {
+ delegate.info(format, arguments);
+ emit(LEVEL_INFO, format, arguments);
+ }
+
+ @Override
+ public void info(String msg, Throwable t) {
+ delegate.info(msg, t);
+ emit(LEVEL_INFO, msg);
+ }
+
+ @Override
+ public boolean isInfoEnabled(Marker marker) {
+ return delegate.isInfoEnabled(marker);
+ }
+
+ @Override
+ public void info(Marker marker, String msg) {
+ delegate.info(marker, msg);
+ emit(LEVEL_INFO, msg);
+ }
+
+ @Override
+ public void info(Marker marker, String format, Object arg) {
+ delegate.info(marker, format, arg);
+ emit(LEVEL_INFO, format, arg);
+ }
+
+ @Override
+ public void info(Marker marker, String format, Object arg1, Object arg2) {
+ delegate.info(marker, format, arg1, arg2);
+ emit(LEVEL_INFO, format, arg1, arg2);
+ }
+
+ @Override
+ public void info(Marker marker, String format, Object... arguments) {
+ delegate.info(marker, format, arguments);
+ emit(LEVEL_INFO, format, arguments);
+ }
+
+ @Override
+ public void info(Marker marker, String msg, Throwable t) {
+ delegate.info(marker, msg, t);
+ emit(LEVEL_INFO, msg);
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return delegate.isWarnEnabled();
+ }
+
+ @Override
+ public void warn(String msg) {
+ delegate.warn(msg);
+ emit(LEVEL_WARN, msg);
+ }
+
+ @Override
+ public void warn(String format, Object arg) {
+ delegate.warn(format, arg);
+ emit(LEVEL_WARN, format, arg);
+ }
+
+ @Override
+ public void warn(String format, Object... arguments) {
+ delegate.warn(format, arguments);
+ emit(LEVEL_WARN, format, arguments);
+ }
+
+ @Override
+ public void warn(String format, Object arg1, Object arg2) {
+ delegate.warn(format, arg1, arg2);
+ emit(LEVEL_WARN, format, arg1, arg2);
+ }
+
+ @Override
+ public void warn(String msg, Throwable t) {
+ delegate.warn(msg, t);
+ emit(LEVEL_WARN, msg);
+ }
+
+ @Override
+ public boolean isWarnEnabled(Marker marker) {
+ return delegate.isWarnEnabled(marker);
+ }
+
+ @Override
+ public void warn(Marker marker, String msg) {
+ delegate.warn(marker, msg);
+ emit(LEVEL_WARN, msg);
+ }
+
+ @Override
+ public void warn(Marker marker, String format, Object arg) {
+ delegate.warn(marker, format, arg);
+ emit(LEVEL_WARN, format, arg);
+ }
+
+ @Override
+ public void warn(Marker marker, String format, Object arg1, Object arg2) {
+ delegate.warn(marker, format, arg1, arg2);
+ emit(LEVEL_WARN, format, arg1, arg2);
+ }
+
+ @Override
+ public void warn(Marker marker, String format, Object... arguments) {
+ delegate.warn(marker, format, arguments);
+ emit(LEVEL_WARN, format, arguments);
+ }
+
+ @Override
+ public void warn(Marker marker, String msg, Throwable t) {
+ delegate.warn(marker, msg, t);
+ emit(LEVEL_WARN, msg);
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return delegate.isErrorEnabled();
+ }
+
+ @Override
+ public void error(String msg) {
+ delegate.error(msg);
+ emit(LEVEL_ERROR, msg);
+ }
+
+ @Override
+ public void error(String format, Object arg) {
+ delegate.error(format, arg);
+ emit(LEVEL_ERROR, format, arg);
+ }
+
+ @Override
+ public void error(String format, Object arg1, Object arg2) {
+ delegate.error(format, arg1, arg2);
+ emit(LEVEL_ERROR, format, arg1, arg2);
+ }
+
+ @Override
+ public void error(String format, Object... arguments) {
+ delegate.error(format, arguments);
+ emit(LEVEL_ERROR, format, arguments);
+ }
+
+ @Override
+ public void error(String msg, Throwable t) {
+ delegate.error(msg, t);
+ emit(LEVEL_ERROR, msg);
+ }
+
+ @Override
+ public boolean isErrorEnabled(Marker marker) {
+ return delegate.isErrorEnabled(marker);
+ }
+
+ @Override
+ public void error(Marker marker, String msg) {
+ delegate.error(marker, msg);
+ emit(LEVEL_ERROR, msg);
+ }
+
+ @Override
+ public void error(Marker marker, String format, Object arg) {
+ delegate.error(marker, format, arg);
+ emit(LEVEL_ERROR, format, arg);
+ }
+
+ @Override
+ public void error(Marker marker, String format, Object arg1, Object arg2) {
+ delegate.error(marker, format, arg1, arg2);
+ emit(LEVEL_ERROR, format, arg1, arg2);
+ }
+
+ @Override
+ public void error(Marker marker, String format, Object... arguments) {
+ delegate.error(marker, format, arguments);
+ emit(LEVEL_ERROR, format, arguments);
+ }
+
+ @Override
+ public void error(Marker marker, String msg, Throwable t) {
+ delegate.error(marker, msg, t);
+ emit(LEVEL_ERROR, msg);
+ }
+}
diff --git a/core/src/main/java/dev/vml/es/acm/core/code/log/LogbackAppenderFactory.java b/core/src/main/java/dev/vml/es/acm/core/code/log/LogbackAppenderFactory.java
index 6da5f3416..693101e15 100644
--- a/core/src/main/java/dev/vml/es/acm/core/code/log/LogbackAppenderFactory.java
+++ b/core/src/main/java/dev/vml/es/acm/core/code/log/LogbackAppenderFactory.java
@@ -4,7 +4,9 @@
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,44 +21,70 @@ class LogbackAppenderFactory {
private static final String LOGBACK_LOGGING_EVENT = "ch.qos.logback.classic.spi.ILoggingEvent";
private final ClassLoader classLoader;
+ private final Object loggerContext;
private final Class> loggerContextClass;
private final Class> appenderClass;
private final Class> contextClass;
private final Class> eventClass;
- LogbackAppenderFactory(ClassLoader classLoader) throws ClassNotFoundException {
+ LogbackAppenderFactory(ClassLoader classLoader, Object loggerContext) throws ClassNotFoundException {
this.classLoader = classLoader;
this.loggerContextClass = classLoader.loadClass(LOGBACK_LOGGER_CONTEXT);
+ if (loggerContext == null || !loggerContextClass.isInstance(loggerContext)) {
+ throw new IllegalStateException("SLF4J is not using Logback");
+ }
+ this.loggerContext = loggerContext;
this.appenderClass = classLoader.loadClass(LOGBACK_APPENDER);
this.contextClass = classLoader.loadClass(LOGBACK_CONTEXT);
this.eventClass = classLoader.loadClass(LOGBACK_LOGGING_EVENT);
}
- Object attach(String name, Consumer listener, List loggerNames)
+ Attachment attach(String name, Consumer listener, List loggerNames)
throws ReflectiveOperationException {
Object appender = createAppender(name, listener, loggerNames);
+ List targetLoggers = resolveTargetLoggers(loggerNames);
List attached = new ArrayList<>();
try {
- Object loggerContext = getLoggerContext();
initAppender(appender, loggerContext);
- for (String loggerName : loggerNames) {
+ for (String loggerName : targetLoggers) {
addAppenderToLogger(loggerContext, appender, loggerName);
attached.add(loggerName);
}
- return appender;
+ return new Attachment(appender, attached);
} catch (ReflectiveOperationException e) {
detach(appender, attached);
throw e;
}
}
+ private List resolveTargetLoggers(List loggerNames) {
+ Set targets = new LinkedHashSet<>(loggerNames);
+ try {
+ List> existingLoggers =
+ (List>) loggerContextClass.getMethod("getLoggerList").invoke(loggerContext);
+ if (existingLoggers != null) {
+ for (Object logger : existingLoggers) {
+ String existingName =
+ (String) logger.getClass().getMethod("getName").invoke(logger);
+ Boolean additive =
+ (Boolean) logger.getClass().getMethod("isAdditive").invoke(logger);
+ if (Boolean.FALSE.equals(additive) && matchesPrefix(existingName, loggerNames)) {
+ targets.add(existingName);
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.debug("Cannot inspect existing loggers for non-additive children", e);
+ }
+ return new ArrayList<>(targets);
+ }
+
void detach(Object appender, List loggerNames) {
if (appender == null) {
return;
}
try {
if (loggerNames != null) {
- Object loggerContext = getLoggerContext();
for (String loggerName : loggerNames) {
try {
detachAppenderFromLogger(loggerContext, appender, loggerName);
@@ -65,8 +93,6 @@ void detach(Object appender, List loggerNames) {
}
}
}
- } catch (Exception e) {
- LOG.warn("Failed to obtain logger context for detach", e);
} finally {
try {
stopAppender(appender);
@@ -84,20 +110,12 @@ private Object createAppender(String name, Consumer listener, List loggerPrefixes) {
+ if (loggerName == null || loggerPrefixes == null) {
+ return false;
+ }
+ for (String prefix : loggerPrefixes) {
+ if (prefix != null && loggerName.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ class Attachment {
+ private final Object appender;
+ private final List attachedLoggers;
+
+ Attachment(Object appender, List attachedLoggers) {
+ this.appender = appender;
+ this.attachedLoggers = attachedLoggers;
+ }
+
+ List getAttachedLoggers() {
+ return attachedLoggers;
+ }
+
+ void detach() {
+ LogbackAppenderFactory.this.detach(appender, attachedLoggers);
+ }
+ }
+
private static class AppenderHandler implements InvocationHandler {
private final String appenderName;
@@ -119,7 +167,8 @@ private static class AppenderHandler implements InvocationHandler {
private final Consumer listener;
private final List loggerPrefixes;
private String name;
- private boolean started;
+ private boolean started = true;
+ private volatile Object lastEvent;
AppenderHandler(String name, Class> eventClass, Consumer listener, List loggerPrefixes) {
this.appenderName = name;
@@ -166,9 +215,13 @@ public Object invoke(Object proxy, Method method, Object[] args) {
private void processEvent(Object event) {
try {
+ if (event == lastEvent) {
+ return;
+ }
+ lastEvent = event;
String loggerName =
(String) eventClass.getMethod("getLoggerName").invoke(event);
- if (!matchesPrefix(loggerName)) {
+ if (!matchesPrefix(loggerName, loggerPrefixes)) {
return;
}
String message =
@@ -177,21 +230,9 @@ private void processEvent(Object event) {
Long ts = (Long) eventClass.getMethod("getTimeStamp").invoke(event);
listener.accept(
new LogMessage(loggerName, level, message, ts != null ? ts : System.currentTimeMillis()));
- } catch (Exception ignored) {
- // Never disrupt logging
- }
- }
-
- private boolean matchesPrefix(String loggerName) {
- if (loggerName == null) {
- return false;
- }
- for (String prefix : loggerPrefixes) {
- if (prefix != null && loggerName.startsWith(prefix)) {
- return true;
- }
+ } catch (Exception e) {
+ LOG.trace("Failed to process log event", e);
}
- return false;
}
private Object defaultValue(Class> type) {
diff --git a/core/src/main/java/dev/vml/es/acm/core/code/log/LogbackLogInterceptor.java b/core/src/main/java/dev/vml/es/acm/core/code/log/LogbackLogInterceptor.java
index a343dbff0..e5c99f246 100644
--- a/core/src/main/java/dev/vml/es/acm/core/code/log/LogbackLogInterceptor.java
+++ b/core/src/main/java/dev/vml/es/acm/core/code/log/LogbackLogInterceptor.java
@@ -3,9 +3,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
-import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,9 +14,6 @@ public class LogbackLogInterceptor implements LogInterceptor {
private static final String APPENDER_NAME = "ACM-LogbackLogInterceptor";
- @Reference
- private DynamicClassLoaderManager classLoaderManager;
-
@Override
public boolean isAvailable() {
return getFactory() != null;
@@ -36,9 +31,9 @@ public Handle attach(Consumer listener, String... loggerNames) {
}
try {
List loggerList = Arrays.asList(loggerNames);
- Object appender = factory.attach(APPENDER_NAME, listener, loggerList);
- LOG.debug("Attached appender to loggers: {}", loggerList);
- return () -> factory.detach(appender, loggerList);
+ LogbackAppenderFactory.Attachment attachment = factory.attach(APPENDER_NAME, listener, loggerList);
+ LOG.debug("Attached appender to loggers: {}", attachment.getAttachedLoggers());
+ return attachment::detach;
} catch (Exception e) {
LOG.error("Failed to attach log interceptor", e);
return () -> {};
@@ -46,16 +41,22 @@ public Handle attach(Consumer listener, String... loggerNames) {
}
private LogbackAppenderFactory getFactory() {
- if (classLoaderManager == null) {
- return null;
- }
try {
- ClassLoader cl = classLoaderManager.getDynamicClassLoader();
+ Object loggerContext = LoggerFactory.getILoggerFactory();
+ if (loggerContext == null) {
+ LOG.debug("Cannot obtain SLF4J ILoggerFactory");
+ return null;
+ }
+ ClassLoader cl = loggerContext.getClass().getClassLoader();
if (cl == null) {
+ LOG.debug(
+ "SLF4J ILoggerFactory '{}' has no class loader",
+ loggerContext.getClass().getName());
return null;
}
- return new LogbackAppenderFactory(cl);
+ return new LogbackAppenderFactory(cl, loggerContext);
} catch (Exception e) {
+ LOG.debug("Logback log interceptor is not available", e);
return null;
}
}
diff --git a/core/src/main/java/dev/vml/es/acm/core/repo/Repo.java b/core/src/main/java/dev/vml/es/acm/core/repo/Repo.java
index 2f00c4dc1..c1102f4b5 100644
--- a/core/src/main/java/dev/vml/es/acm/core/repo/Repo.java
+++ b/core/src/main/java/dev/vml/es/acm/core/repo/Repo.java
@@ -19,6 +19,8 @@ public class Repo implements CommitPolicy {
private static final Logger LOG = LoggerFactory.getLogger(Repo.class);
+ private Logger logger = LOG;
+
private final ResourceResolver resourceResolver;
private final Session session;
@@ -62,7 +64,11 @@ public void setQuiet(boolean quiet) {
}
public Logger getLogger() {
- return quiet ? NOPLogger.NOP_LOGGER : LOG;
+ return quiet ? NOPLogger.NOP_LOGGER : logger;
+ }
+
+ public void setLogger(Logger logger) {
+ this.logger = logger != null ? logger : LOG;
}
public void autoCommit(boolean enabled) {
diff --git a/core/src/test/java/dev/vml/es/acm/core/code/CodePrintStreamTest.java b/core/src/test/java/dev/vml/es/acm/core/code/CodePrintStreamTest.java
index 50c697962..3bca597cb 100644
--- a/core/src/test/java/dev/vml/es/acm/core/code/CodePrintStreamTest.java
+++ b/core/src/test/java/dev/vml/es/acm/core/code/CodePrintStreamTest.java
@@ -1,9 +1,19 @@
package dev.vml.es.acm.core.code;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import dev.vml.es.acm.core.code.log.LogInterceptor;
+import dev.vml.es.acm.core.code.log.LogInterceptorManager;
+import dev.vml.es.acm.core.code.log.LogMessage;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
class CodePrintStreamTest {
@@ -80,4 +90,64 @@ void shouldPrintMultipleMessagesWithDifferentLevels() {
assertTrue(output.contains("[DEBUG] Debug message"));
assertTrue(output.contains("[SUCCESS] Success message"));
}
+
+ @Test
+ void shouldBridgeSelfAndRecommendedLoggersWithoutInterceptor() {
+ List interceptedLoggers = new ArrayList<>();
+ LogInterceptorManager manager = new LogInterceptorManager() {
+ @Override
+ public LogInterceptor.Handle attach(Consumer listener, String... loggerNames) {
+ interceptedLoggers.addAll(Arrays.asList(loggerNames));
+ return () -> {};
+ }
+ };
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try (CodePrintStream out = new CodePrintStream(outputStream, "console|1", manager)) {
+ Logger repoLogger = out.bridgeLogger(LoggerFactory.getLogger(CodePrintStream.LOGGER_NAME_REPO));
+ out.fromSelfLogger();
+ out.fromRecommendedLoggers();
+
+ out.getLogger().info("Self message {}", 1);
+ repoLogger.info("Repo message {}", 2);
+ }
+
+ assertTrue(interceptedLoggers.isEmpty());
+ String output = outputStream.toString();
+ assertTrue(output.contains("[INFO] Self message 1"));
+ assertTrue(output.contains("[INFO] Repo message 2"));
+ }
+
+ @Test
+ void shouldKeepOutputEmptyWhenLogPrintingNotEnabled() {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try (CodePrintStream out = new CodePrintStream(outputStream, "console|1", null)) {
+ Logger repoLogger = out.bridgeLogger(LoggerFactory.getLogger(CodePrintStream.LOGGER_NAME_REPO));
+ out.getLogger().info("Should not be printed");
+ repoLogger.info("Should not be printed either");
+ }
+
+ assertEquals("", outputStream.toString());
+ }
+
+ @Test
+ void shouldAttachInterceptorOnlyForExternalLoggers() {
+ List interceptedLoggers = new ArrayList<>();
+ LogInterceptorManager manager = new LogInterceptorManager() {
+ @Override
+ public LogInterceptor.Handle attach(Consumer listener, String... loggerNames) {
+ interceptedLoggers.addAll(Arrays.asList(loggerNames));
+ return () -> {};
+ }
+ };
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try (CodePrintStream out = new CodePrintStream(outputStream, "console|1", manager)) {
+ out.fromSelfLogger();
+ out.fromRecommendedLoggers();
+ out.fromLogger("com.acme.custom");
+ }
+
+ assertEquals(Arrays.asList("com.acme.custom"), interceptedLoggers);
+ }
}
diff --git a/mvnw b/mvnw
index 88a9d2383..05820d275 100755
--- a/mvnw
+++ b/mvnw
@@ -60,6 +60,13 @@ if [ "${AEM_JAVA_ENABLED}" = true ]; then
JAVA_HOME="$AEM_JAVA_HOME"; export JAVA_HOME
fi
+if [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/java" ]; then
+ if "$JAVA_HOME/bin/java" --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED -version >/dev/null 2>&1; then
+ MAVEN_OPTS="${MAVEN_OPTS} --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"
+ export MAVEN_OPTS
+ fi
+fi
+
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.2.0
#
diff --git a/pom.xml b/pom.xml
index 9068ab8df..9a1cb7875 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,8 @@
admin
admin
5.1.2
+ 2.30.0
+ 1.1.0
UTF-8
UTF-8
false
@@ -203,7 +205,7 @@
com.diffplug.spotless
spotless-maven-plugin
- 2.30.0
+ ${spotless.version}
@@ -688,6 +690,25 @@ Private-Package: com.fasterxml.jackson.datatype.jsr310
+
+ java11-spotless
+
+ [11,17)
+
+
+ 2.28.0
+
+
+
+ java17plus-spotless
+
+ [17,)
+
+
+ 2.43.0
+ 2.39.0
+
+
diff --git a/test/e2e/002-console.spec.ts b/test/e2e/002-console.spec.ts
index e38831b3c..4d8f464c9 100644
--- a/test/e2e/002-console.spec.ts
+++ b/test/e2e/002-console.spec.ts
@@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test';
-import { expectCompilationSucceeded, expectExecutionProgressBarSucceeded, expectToHaveMultilineText } from './utils/expect'
+import { expectCompilationSucceeded, expectExecutionProgressBarSucceeded } from './utils/expect'
import { readFromCodeEditor, writeToCodeEditor } from './utils/editor';
import { attachScreenshot } from './utils/page';
@@ -16,6 +16,7 @@ test.describe('Console', () => {
void doRun() {
println "Hello World!"
+ log.info "Hello Logs!"
}
`);
await expectCompilationSucceeded(page);
@@ -27,9 +28,8 @@ test.describe('Console', () => {
await expectExecutionProgressBarSucceeded(page);
const output = await readFromCodeEditor(page, 'Console Output');
- expectToHaveMultilineText(output, `
- Hello World!
- `);
+ expect(output).toContain('Hello World!');
+ expect(output).toContain('[INFO] Hello Logs!');
await attachScreenshot(page, testInfo, 'Console Output');
});
});
diff --git a/tools/shared/src/catalog/api.json b/tools/shared/src/catalog/api.json
index f45ff4192..8251b4859 100644
--- a/tools/shared/src/catalog/api.json
+++ b/tools/shared/src/catalog/api.json
@@ -1262,6 +1262,11 @@
"name": "getChecker",
"returns": "AclChecker",
"params": []
+ },
+ {
+ "name": "getContext",
+ "returns": "AclContext",
+ "params": []
}
]
},
@@ -1672,6 +1677,152 @@
}
]
},
+ "dev.vml.es.acm.core.acl.AclContext": {
+ "name": "AclContext",
+ "methods": [
+ {
+ "name": "getLogger",
+ "returns": "Logger",
+ "params": []
+ },
+ {
+ "name": "setLogger",
+ "returns": "void",
+ "params": [
+ {
+ "type": "Logger",
+ "name": "logger"
+ }
+ ]
+ },
+ {
+ "name": "getResourceResolver",
+ "returns": "ResourceResolver",
+ "params": []
+ },
+ {
+ "name": "getAuthorizableManager",
+ "returns": "AuthorizableManager",
+ "params": []
+ },
+ {
+ "name": "getPermissionsManager",
+ "returns": "PermissionsManager",
+ "params": []
+ },
+ {
+ "name": "isCompositeNodeStore",
+ "returns": "boolean",
+ "params": []
+ },
+ {
+ "name": "determineUser",
+ "returns": "AclUser",
+ "params": [
+ {
+ "type": "User",
+ "name": "user"
+ }
+ ]
+ },
+ {
+ "name": "determineUser",
+ "returns": "AclUser",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ },
+ {
+ "name": "determineUser",
+ "returns": "AclUser",
+ "params": [
+ {
+ "type": "AclUser",
+ "name": "user"
+ },
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ },
+ {
+ "name": "determineGroup",
+ "returns": "AclGroup",
+ "params": [
+ {
+ "type": "Group",
+ "name": "group"
+ }
+ ]
+ },
+ {
+ "name": "determineGroup",
+ "returns": "AclGroup",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ },
+ {
+ "name": "determineGroup",
+ "returns": "AclGroup",
+ "params": [
+ {
+ "type": "AclGroup",
+ "name": "group"
+ },
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ },
+ {
+ "name": "determineAuthorizable",
+ "returns": "AclAuthorizable",
+ "params": [
+ {
+ "type": "AclAuthorizable",
+ "name": "authorizable"
+ },
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ },
+ {
+ "name": "determineAuthorizable",
+ "returns": "AclAuthorizable",
+ "params": [
+ {
+ "type": "Authorizable",
+ "name": "authorizable"
+ }
+ ]
+ },
+ {
+ "name": "determineId",
+ "returns": "String",
+ "params": [
+ {
+ "type": "AclAuthorizable",
+ "name": "authorizable"
+ },
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ }
+ ]
+ },
"dev.vml.es.acm.core.acl.authorizable.AclAuthorizable": {
"name": "AclAuthorizable",
"methods": [
@@ -4028,6 +4179,324 @@
}
]
},
+ "dev.vml.es.acm.core.acl.utils.AuthorizableManager": {
+ "name": "AuthorizableManager",
+ "methods": [
+ {
+ "name": "createUser",
+ "returns": "User",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ },
+ {
+ "type": "String",
+ "name": "password"
+ },
+ {
+ "type": "String",
+ "name": "path"
+ }
+ ]
+ },
+ {
+ "name": "createGroup",
+ "returns": "Group",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ },
+ {
+ "type": "String",
+ "name": "path"
+ },
+ {
+ "type": "String",
+ "name": "externalId"
+ }
+ ]
+ },
+ {
+ "name": "createSystemUser",
+ "returns": "User",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ },
+ {
+ "type": "String",
+ "name": "path"
+ }
+ ]
+ },
+ {
+ "name": "updateUser",
+ "returns": "void",
+ "params": [
+ {
+ "type": "User",
+ "name": "user"
+ },
+ {
+ "type": "String",
+ "name": "password"
+ },
+ {
+ "type": "Map",
+ "name": "properties"
+ }
+ ]
+ },
+ {
+ "name": "updateGroup",
+ "returns": "void",
+ "params": [
+ {
+ "type": "Group",
+ "name": "group"
+ },
+ {
+ "type": "Map",
+ "name": "properties"
+ }
+ ]
+ },
+ {
+ "name": "deleteAuthorizable",
+ "returns": "void",
+ "params": [
+ {
+ "type": "Authorizable",
+ "name": "authorizable"
+ }
+ ]
+ },
+ {
+ "name": "addMember",
+ "returns": "boolean",
+ "params": [
+ {
+ "type": "Group",
+ "name": "group"
+ },
+ {
+ "type": "Authorizable",
+ "name": "member"
+ }
+ ]
+ },
+ {
+ "name": "removeMember",
+ "returns": "boolean",
+ "params": [
+ {
+ "type": "Group",
+ "name": "group"
+ },
+ {
+ "type": "Authorizable",
+ "name": "member"
+ }
+ ]
+ },
+ {
+ "name": "getAuthorizable",
+ "returns": "Authorizable",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ },
+ {
+ "name": "getAuthorizable",
+ "returns": "T",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ },
+ {
+ "type": "Class",
+ "name": "clazz"
+ }
+ ]
+ },
+ {
+ "name": "getUser",
+ "returns": "User",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ },
+ {
+ "name": "getGroup",
+ "returns": "Group",
+ "params": [
+ {
+ "type": "String",
+ "name": "id"
+ }
+ ]
+ },
+ {
+ "name": "testPassword",
+ "returns": "boolean",
+ "params": [
+ {
+ "type": "User",
+ "name": "user"
+ },
+ {
+ "type": "String",
+ "name": "password"
+ }
+ ]
+ },
+ {
+ "name": "changePassword",
+ "returns": "void",
+ "params": [
+ {
+ "type": "User",
+ "name": "user"
+ },
+ {
+ "type": "String",
+ "name": "password"
+ }
+ ]
+ },
+ {
+ "name": "getProperty",
+ "returns": "List",
+ "params": [
+ {
+ "type": "Authorizable",
+ "name": "authorizable"
+ },
+ {
+ "type": "String",
+ "name": "relPath"
+ }
+ ]
+ },
+ {
+ "name": "setProperty",
+ "returns": "void",
+ "params": [
+ {
+ "type": "Authorizable",
+ "name": "authorizable"
+ },
+ {
+ "type": "String",
+ "name": "relPath"
+ },
+ {
+ "type": "String",
+ "name": "value"
+ }
+ ]
+ },
+ {
+ "name": "removeProperty",
+ "returns": "boolean",
+ "params": [
+ {
+ "type": "Authorizable",
+ "name": "authorizable"
+ },
+ {
+ "type": "String",
+ "name": "relPath"
+ }
+ ]
+ }
+ ]
+ },
+ "dev.vml.es.acm.core.acl.utils.PermissionsManager": {
+ "name": "PermissionsManager",
+ "methods": [
+ {
+ "name": "apply",
+ "returns": "void",
+ "params": [
+ {
+ "type": "Authorizable",
+ "name": "authorizable"
+ },
+ {
+ "type": "String",
+ "name": "path"
+ },
+ {
+ "type": "List",
+ "name": "permissions"
+ },
+ {
+ "type": "Map",
+ "name": "restrictions"
+ },
+ {
+ "type": "boolean",
+ "name": "allow"
+ }
+ ]
+ },
+ {
+ "name": "clear",
+ "returns": "boolean",
+ "params": [
+ {
+ "type": "Authorizable",
+ "name": "authorizable"
+ },
+ {
+ "type": "String",
+ "name": "path"
+ },
+ {
+ "type": "boolean",
+ "name": "strict"
+ }
+ ]
+ },
+ {
+ "name": "check",
+ "returns": "boolean",
+ "params": [
+ {
+ "type": "Authorizable",
+ "name": "authorizable"
+ },
+ {
+ "type": "String",
+ "name": "path"
+ },
+ {
+ "type": "List",
+ "name": "permissions"
+ },
+ {
+ "type": "Map",
+ "name": "restrictions"
+ },
+ {
+ "type": "boolean",
+ "name": "allow"
+ }
+ ]
+ }
+ ]
+ },
"dev.vml.es.acm.core.code.CodeContext": {
"name": "CodeContext",
"methods": [
@@ -11023,6 +11492,16 @@
"returns": "Logger",
"params": []
},
+ {
+ "name": "setLogger",
+ "returns": "void",
+ "params": [
+ {
+ "type": "Logger",
+ "name": "logger"
+ }
+ ]
+ },
{
"name": "autoCommit",
"returns": "void",
diff --git a/tools/skills/acm-groovy-script/references/api.md b/tools/skills/acm-groovy-script/references/api.md
index db1ecbcea..428053105 100644
--- a/tools/skills/acm-groovy-script/references/api.md
+++ b/tools/skills/acm-groovy-script/references/api.md
@@ -166,6 +166,7 @@ AclUser user(User user)
AclGroup group(Group group)
AclAuthorizable authorizable(Authorizable authorizable)
AclChecker getChecker()
+AclContext getContext()
```
### AclChecker
@@ -206,6 +207,28 @@ boolean password(String id, String password)
boolean property(String id, String relPath, String value)
```
+### AclContext
+
+`dev.vml.es.acm.core.acl.AclContext`
+
+```groovy
+Logger getLogger()
+void setLogger(Logger logger)
+ResourceResolver getResourceResolver()
+AuthorizableManager getAuthorizableManager()
+PermissionsManager getPermissionsManager()
+boolean isCompositeNodeStore()
+AclUser determineUser(User user)
+AclUser determineUser(String id)
+AclUser determineUser(AclUser user, String id)
+AclGroup determineGroup(Group group)
+AclGroup determineGroup(String id)
+AclGroup determineGroup(AclGroup group, String id)
+AclAuthorizable determineAuthorizable(AclAuthorizable authorizable, String id)
+AclAuthorizable determineAuthorizable(Authorizable authorizable)
+String determineId(AclAuthorizable authorizable, String id)
+```
+
### AclAuthorizable
`dev.vml.es.acm.core.acl.authorizable.AclAuthorizable`
@@ -690,6 +713,40 @@ String getAuthorizableId()
void setAuthorizableId(String authorizableId)
```
+### AuthorizableManager
+
+`dev.vml.es.acm.core.acl.utils.AuthorizableManager`
+
+```groovy
+User createUser(String id, String password, String path)
+Group createGroup(String id, String path, String externalId)
+User createSystemUser(String id, String path)
+void updateUser(User user, String password, Map properties)
+void updateGroup(Group group, Map properties)
+void deleteAuthorizable(Authorizable authorizable)
+boolean addMember(Group group, Authorizable member)
+boolean removeMember(Group group, Authorizable member)
+Authorizable getAuthorizable(String id)
+T getAuthorizable(String id, Class clazz)
+User getUser(String id)
+Group getGroup(String id)
+boolean testPassword(User user, String password)
+void changePassword(User user, String password)
+List getProperty(Authorizable authorizable, String relPath)
+void setProperty(Authorizable authorizable, String relPath, String value)
+boolean removeProperty(Authorizable authorizable, String relPath)
+```
+
+### PermissionsManager
+
+`dev.vml.es.acm.core.acl.utils.PermissionsManager`
+
+```groovy
+void apply(Authorizable authorizable, String path, List permissions, Map restrictions, boolean allow)
+boolean clear(Authorizable authorizable, String path, boolean strict)
+boolean check(Authorizable authorizable, String path, List permissions, Map restrictions, boolean allow)
+```
+
### CodeContext
`dev.vml.es.acm.core.code.CodeContext`
@@ -1989,6 +2046,7 @@ void setAutoCommit(boolean autoCommit)
boolean isQuiet()
void setQuiet(boolean quiet)
Logger getLogger()
+void setLogger(Logger logger)
void autoCommit(boolean enabled)
void commit()
void commit(String context)