From cf8d91b540a2a97988560799edb7871ae6207fbe Mon Sep 17 00:00:00 2001 From: Matt Pavlovich Date: Thu, 13 Nov 2025 09:57:32 -0600 Subject: [PATCH 1/8] [#] NO-JIRA: Update Jenkinsfile to add JDK 25 --- Jenkinsfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d1891aad95e..c0a11a98e7c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -43,7 +43,7 @@ pipeline { parameters { choice(name: 'nodeLabel', choices: ['ubuntu', 's390x', 'arm', 'Windows']) - choice(name: 'jdkVersion', choices: ['jdk_17_latest', 'jdk_21_latest', 'jdk_24_latest', 'jdk_17_latest_windows', 'jdk_21_latest_windows', 'jdk_24_latest_windows']) + choice(name: 'jdkVersion', choices: ['jdk_17_latest', 'jdk_21_latest', 'jdk_25_latest', 'jdk_17_latest_windows', 'jdk_21_latest_windows', 'jdk_25_latest_windows']) booleanParam(name: 'deployEnabled', defaultValue: false) booleanParam(name: 'sonarEnabled', defaultValue: false) booleanParam(name: 'testsEnabled', defaultValue: true) @@ -72,12 +72,12 @@ pipeline { } } - stage('Build JDK 24') { + stage('Build JDK 25') { tools { - jdk "jdk_24_latest" + jdk "jdk_25_latest" } steps { - echo 'Building JDK 24' + echo 'Building JDK 25' sh 'java -version' sh 'mvn -version' sh 'mvn -U -B -e clean install -DskipTests' From 53559a358cafcd21bfff56ab13da9d15b67db595 Mon Sep 17 00:00:00 2001 From: Jean-Louis Monteiro Date: Fri, 14 Nov 2025 10:57:19 +0100 Subject: [PATCH 2/8] feat: create a module for SecurityManager MRJAR --- activemq-compat/pom.xml | 95 +++++++++ .../compat/sm/SecurityManagerShim.java | 199 ++++++++++++++++++ .../compat/sm/SecurityManagerShim.java | 96 +++++++++ pom.xml | 6 + 4 files changed, 396 insertions(+) create mode 100644 activemq-compat/pom.xml create mode 100644 activemq-compat/src/main/java/org/apache/activemq/compat/sm/SecurityManagerShim.java create mode 100644 activemq-compat/src/main/java21/org/apache/activemq/compat/sm/SecurityManagerShim.java diff --git a/activemq-compat/pom.xml b/activemq-compat/pom.xml new file mode 100644 index 00000000000..809ef884b36 --- /dev/null +++ b/activemq-compat/pom.xml @@ -0,0 +1,95 @@ + + + + + 4.0.0 + + + org.apache.activemq + activemq-parent + 6.2.1-SNAPSHOT + + + activemq-compat + bundle + ActiveMQ :: Compat + The ActiveMQ Compatibility (MRJAR) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + + !java.*, + !javax.naming*, + !javax.net*, + !org.apache.activemq*, + !com.google.errorprone.annotations, + !com.google.errorprone.annotations.concurrent, + com.thoughtworks.xstream.*;resolution:="optional", + javax.jmdns.*;resolution:="optional", + * + + + com.google.errorprone.annotations, + com.google.errorprone.annotations.concurrent + + true + <_noee>true + + + + + + + + + jdk21-plus + + [21,) + + + + + maven-compiler-plugin + + + java21-compile + compile + + compile + + + 21 + ${project.basedir}/src/main/java21 + true + + + + + + + + + + diff --git a/activemq-compat/src/main/java/org/apache/activemq/compat/sm/SecurityManagerShim.java b/activemq-compat/src/main/java/org/apache/activemq/compat/sm/SecurityManagerShim.java new file mode 100644 index 00000000000..f45051418b0 --- /dev/null +++ b/activemq-compat/src/main/java/org/apache/activemq/compat/sm/SecurityManagerShim.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.compat.sm; + +import javax.security.auth.Subject; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionException; + +/* + * SecurityManager related shim. + * This specific class uses legacy methods toward usage on Java 17 - 23. + * + * The API of this class must be kept the same as the Java 24+ implementation + * variant of this class which can be found at: + * src/main/java24/org/apache/activemq/artemis/utils/sm/SecurityManagerShim.java + * + * Did not use a shared interface since the shim seems unlikely to be changed much, + * other than future removals, and doing so would need singleton instance indirection + * at every call site to access the purely static onward methods being called. + */ +@SuppressWarnings("removal") +public class SecurityManagerShim { + + /** + * Returns the current associated subject. + *

+ * On Java 17-23, retrieves the current AccessControlContext by calling + * {@code AccessController.getContext()} and then returns result of method + * {@code Subject.getSubject(accessControlContext)}. + *

+ * On Java 24+, returns result of method {@code Subject.current()}. + * + * @return the current associated subject, or null if none was found. + */ + public static Subject currentSubject() { + AccessControlContext accessControlContext = AccessController.getContext(); + if (accessControlContext != null) { + return Subject.getSubject(accessControlContext); + } + return null; + } + + /** + * Perform work as a particular {@code Subject}. + *

+ * On Java 17-23, wraps the given {@code callable} as a {@code PrivilegedExceptionAction} and executes it + * via {@code Subject.doAs(final Subject subject, final java.security.PrivilegedExceptionAction action)}. + *

+ * On Java 24+, returns result of calling {@code Subject.callAs(final Subject subject, + * final Callable action)}. + *

+ * Any exceptions thrown by the {@code callable.call()} will result in a {@code CompletionException} being + * thrown with the original exception as its cause. + * + * @param subject the {@code Subject} that the given {@code callable} will run as, may be null. + * @param callable the {@code Callable} to be run, must not be {@code null}. + * @param the type of value returned by the {@code callable}. + * @return the value returned by the {@code callable}. + * @throws NullPointerException if {@code callable} is {@code null}. + * @throws CompletionException if {@code callable.call()} throws an exception. + * The cause is set to the exception thrown by {@code callable.call()}. + */ + public static T callAs(final Subject subject, final Callable callable) throws CompletionException { + // Subject is allowed to be null + Objects.requireNonNull(callable, "callable must be provided"); + + try { + final PrivilegedExceptionAction pa = () -> callable.call(); + + return Subject.doAs(subject, pa); + } catch (PrivilegedActionException e) { + throw new CompletionException(e.getCause()); + } catch (Exception e) { + throw new CompletionException(e); + } + } + + /** + * Returns whether a SecurityManager is enabled. + *

+ * On Java 17-23, returns whether result of check: {@code System.getSecurityManager() != null}. + *

+ * On Java 24+, returns false as a SecurityManager can never be present. + * + * @return true if a SecurityManager is present, or false otherwise. + */ + public static boolean isSecurityManagerEnabled() { + return System.getSecurityManager() != null; + } + + /** + * Returns the current AccessControlContext. + *

+ * On Java 17-23, returns the result of {@code AccessController.getContext()}. + *

+ * On Java 24+, always returns null. + * + * @return the current AccessControlContext, or null if none. + */ + public static Object getAccessControlContext() { + return AccessController.getContext(); + } + + /** + * Performs the specified {@code PrivilegedAction}. + *

+ * On Java 17-23, returns the result of passing the given {@code action} to the + * {@code AccessController.doPrivileged(PrivilegedAction action)} method. + *

+ * On Java 24+, returns the result of running {@code action.run()} directly. + *

+ * If the action's {@code run} method throws an unchecked exception it will + * propagate through this method. + * + * @param action the {@code PrivilegedAction} to be run, must not be {@code null}. + * @param the type of value returned by the {@code action}. + * @return the value returned by the {@code action}. + * @throws NullPointerException if {@code action} is {@code null}. + */ + public static T doPrivileged(final PrivilegedAction action) { + Objects.requireNonNull(action, "action must be provided"); + + return AccessController.doPrivileged(action); + } + + /** + * Performs the specified {@code PrivilegedAction}. + *

+ * On Java 17-23, returns the result of calling the + * {@code AccessController.doPrivileged(PrivilegedAction action, AccessControlContext context)} + * method with the given {@code action} and {@code accessControlContext}. + *

+ * On Java 24+, returns the result of running {@code action.run()} directly, + * ignoring the accessControlContext parameter. + * + * If the action's {@code run} method throws an unchecked exception it will + * propagate through this method. + * + * @param action the {@code PrivilegedAction} to be run, must not be {@code null}. + * @param accessControlContext the {@code AccessControlContext} object, may be null. + * @param the type of value returned by the {@code action}. + * @return the value returned by the {@code action}. + * @throws NullPointerException if {@code action} is {@code null}. + */ + public static T doPrivileged(final PrivilegedAction action, final Object accessControlContext) { + // AccessControlContext may be null + Objects.requireNonNull(action, "action must be provided"); + + final AccessControlContext acc = AccessControlContext.class.cast(accessControlContext); + + return AccessController.doPrivileged(action, acc); + } + + /** + * Performs the specified {@code PrivilegedExceptionAction}. + *

+ * On Java 17-23, returns the result of calling + * {@code AccessController.doPrivileged(PrivilegedExceptionAction action)} + * with the given {@code exceptionAction}. + *

+ * On Java 24+, returns the result of running {@code exceptionAction.run()} directly. + *

+ * If the action's {@code run} method throws an unchecked exception it will + * propagate through this method. + * + * @param exceptionAction the {@code PrivilegedExceptionAction} to be run, must not be {@code null}. + * @param the type of value returned by the {@code exceptionAction}. + * @return the value returned by the {@code action}. + * @throws NullPointerException if {@code exceptionAction} is {@code null}. + * @throws PrivilegedActionException if {@code exceptionAction.run()} throws a checked exception. + * The cause is set to the exception thrown {@code callable.call()}. + */ + public static T doPrivileged(final PrivilegedExceptionAction exceptionAction) throws PrivilegedActionException { + Objects.requireNonNull(exceptionAction, "exceptionAction must be provided"); + + return AccessController.doPrivileged(exceptionAction); + } + +} \ No newline at end of file diff --git a/activemq-compat/src/main/java21/org/apache/activemq/compat/sm/SecurityManagerShim.java b/activemq-compat/src/main/java21/org/apache/activemq/compat/sm/SecurityManagerShim.java new file mode 100644 index 00000000000..6f76cb4f1bc --- /dev/null +++ b/activemq-compat/src/main/java21/org/apache/activemq/compat/sm/SecurityManagerShim.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.compat.sm; + +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionException; + +import javax.security.auth.Subject; + +/* + * SecurityManager related shim. + * This specific class uses replacement APIs, direct-executes actions, + * or no-ops as appropriate towards usage on Java 24+. + * + * The API of this class must be kept the same as the Java 17-23 implementation + * variant of this class which can be found at: + * src/main/java/org/apache/activemq/artemis/utils/sm/SecurityManagerShim.java + * + * The javadoc there should also be kept in sync, it covers both classes behaviour. + * + * Did not use a shared interface since the shim seems unlikely to be changed much, + * other than future removals, and doing so would need singleton instance indirection + * at every call site to access the purely static onward methods being called. + */ +public class SecurityManagerShim { + + public static Subject currentSubject() { + return Subject.current(); + } + + public static T callAs(final Subject subject, final Callable callable) throws CompletionException { + // Subject is allowed to be null + Objects.requireNonNull(callable); + + return Subject.callAs(subject, callable); + } + + public static boolean isSecurityManagerEnabled() { + // Can never be enabled, it was removed in Java 24+. + return false; + } + + public static Object getAccessControlContext() { + // AccessControlContext is now only useful with a SecurityManager, + // which can never be used in Java 24+. Return null so calling + // code can determine there is nothing to be do re: SecurityManager. + return null; + } + + public static T doPrivileged(final PrivilegedAction action) { + Objects.requireNonNull(action, "action must be provided"); + + return action.run(); + } + + public static T doPrivileged(final PrivilegedAction action, final Object accessControlContext) { + // We ignore the accessControlContext parameter as it was only useful + // with a SecurityManager, which can never be used in Java 24+. + Objects.requireNonNull(action, "action must be provided"); + + return action.run(); + } + + public static T doPrivileged(final PrivilegedExceptionAction exceptionAction) throws PrivilegedActionException { + Objects.requireNonNull(exceptionAction, "exceptionAction must be provided"); + + try { + return exceptionAction.run(); + } catch (RuntimeException re) { + // RuntimeExceptions were re-thrown directly by doPrivileged, only checked + // Exceptions were wrapped in PrivilegedActionException to throw for intercept. + throw re; + } catch (Exception e) { + throw new PrivilegedActionException(e); + } + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 028cf65864c..d62eca07b02 100644 --- a/pom.xml +++ b/pom.xml @@ -182,6 +182,7 @@ bom activemq-openwire-generator activemq-client + activemq-compat activemq-openwire-legacy activemq-broker activemq-stomp @@ -270,6 +271,11 @@ ${project.version} + + org.apache.activemq + activemq-compat + ${project.version} + org.apache.activemq activemq-client From 4b76ecbf6b8de67a735966e7ff056f268dc0e9c2 Mon Sep 17 00:00:00 2001 From: Jean-Louis Monteiro Date: Fri, 14 Nov 2025 10:57:55 +0100 Subject: [PATCH 3/8] fix: use compat module to retrieve the subject instead of the SecurityManager directly --- activemq-broker/pom.xml | 4 +++ .../activemq/broker/jmx/AnnotatedMBean.java | 28 +++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/activemq-broker/pom.xml b/activemq-broker/pom.xml index 3aa264161d6..e77e1244fcf 100644 --- a/activemq-broker/pom.xml +++ b/activemq-broker/pom.xml @@ -42,6 +42,10 @@ org.apache.activemq activemq-client + + org.apache.activemq + activemq-compat + org.apache.activemq activemq-openwire-legacy diff --git a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/AnnotatedMBean.java b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/AnnotatedMBean.java index 51c135ddf56..1f6639a31b2 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/AnnotatedMBean.java +++ b/activemq-broker/src/main/java/org/apache/activemq/broker/jmx/AnnotatedMBean.java @@ -16,13 +16,12 @@ */ package org.apache.activemq.broker.jmx; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.security.AccessController; -import java.security.Principal; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; +import org.apache.activemq.broker.util.AuditLogEntry; +import org.apache.activemq.broker.util.AuditLogService; +import org.apache.activemq.broker.util.JMXAuditLogEntry; +import org.apache.activemq.compat.sm.SecurityManagerShim; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; @@ -33,12 +32,12 @@ import javax.management.ReflectionException; import javax.management.StandardMBean; import javax.security.auth.Subject; - -import org.apache.activemq.broker.util.AuditLogEntry; -import org.apache.activemq.broker.util.AuditLogService; -import org.apache.activemq.broker.util.JMXAuditLogEntry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.security.Principal; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; /** * MBean that looks for method/parameter descriptions in the Info annotation. @@ -205,8 +204,7 @@ public Object invoke(String s, Object[] objects, String[] strings) throws MBeanE objects = (objects == null) ? new Object[]{} : objects; JMXAuditLogEntry entry = null; if (audit != OFF) { - // [AMQ-9563] TODO: JDK 21 use Subject.current() instead - Subject subject = Subject.getSubject(AccessController.getContext()); + Subject subject = SecurityManagerShim.currentSubject(); String caller = "anonymous"; if (subject != null) { caller = ""; From bdda31aeb6fad0bd625567cc73c1af36b64af8bd Mon Sep 17 00:00:00 2001 From: Jean-Louis Monteiro Date: Fri, 14 Nov 2025 10:58:25 +0100 Subject: [PATCH 4/8] fix: get rid of the SecurityManager usage --- .../store/kahadb/JournalArchiveTest.java | 164 ++++++++++++------ 1 file changed, 113 insertions(+), 51 deletions(-) diff --git a/activemq-kahadb-store/src/test/java/org/apache/activemq/store/kahadb/JournalArchiveTest.java b/activemq-kahadb-store/src/test/java/org/apache/activemq/store/kahadb/JournalArchiveTest.java index 2a8bd60165f..01b15a0ca59 100644 --- a/activemq-kahadb-store/src/test/java/org/apache/activemq/store/kahadb/JournalArchiveTest.java +++ b/activemq-kahadb-store/src/test/java/org/apache/activemq/store/kahadb/JournalArchiveTest.java @@ -16,6 +16,11 @@ */ package org.apache.activemq.store.kahadb; +import jakarta.jms.Connection; +import jakarta.jms.Destination; +import jakarta.jms.Message; +import jakarta.jms.MessageProducer; +import jakarta.jms.Session; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.policy.PolicyEntry; @@ -27,22 +32,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jakarta.jms.Connection; -import jakarta.jms.Destination; -import jakarta.jms.Message; -import jakarta.jms.MessageProducer; -import jakarta.jms.Session; import java.io.File; import java.io.IOException; -import java.security.Permission; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; import java.util.Collection; +import java.util.EnumSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicBoolean; import static org.apache.activemq.store.kahadb.JournalCorruptionEofIndexRecoveryTest.drain; -import static org.apache.activemq.store.kahadb.disk.journal.Journal.DEFAULT_ARCHIVE_DIRECTORY; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class JournalArchiveTest { @@ -117,63 +121,94 @@ public void tearDown() throws Exception { @Test public void testRecoveryOnArchiveFailure() throws Exception { - final AtomicInteger atomicInteger = new AtomicInteger(); - - System.setSecurityManager(new SecurityManager() { - public void checkPermission(Permission perm) {} - public void checkPermission(Permission perm, Object context) {} - - public void checkWrite(String file) { - if (file.contains(DEFAULT_ARCHIVE_DIRECTORY) && atomicInteger.incrementAndGet() > 4) { - throw new SecurityException("No Perms to write to archive times:" + atomicInteger.get()); - } - } - }); startBroker(); + final Path archivePath = prepareArchivePath(); - int sent = produceMessagesToConsumeMultipleDataFiles(50); - - int numFilesAfterSend = getNumberOfJournalFiles(); - LOG.info("Num journal files: " + numFilesAfterSend); - - assertTrue("more than x files: " + numFilesAfterSend, numFilesAfterSend > 4); - + // configure a shutdown hook to detect broker shutdown on archive failure final CountDownLatch gotShutdown = new CountDownLatch(1); broker.addShutdownHook(new Runnable() { @Override public void run() { + LOG.info("Broker shutdown hook called"); gotShutdown.countDown(); } }); - int received = tryConsume(destination, sent); - assertEquals("all message received", sent, received); - assertTrue("broker got shutdown on page in error", gotShutdown.await(10, TimeUnit.SECONDS)); + /* + * It used to be using a fake security manager to simulate the failure but that approach is no longer viable + * with modern JVMs. Instead, we will produce some messages to create multiple data files, then change the + * archive directory permissions to read-only, to simulate the failure to archive. Then produce some more + * messages to trigger the archive attempts. + */ + final AtomicBoolean permissionsLocked = new AtomicBoolean(); + try { + LOG.info("Producing messages to create multiple data files"); + int sent = produceMessagesToConsumeMultipleDataFiles(25); + + LOG.info("Number of journal files before archive failure: {}", getNumberOfJournalFiles()); + if (permissionsLocked.compareAndSet(false, true)) { + try { + LOG.info("Making archive directory read-only to simulate archive failure: {}", archivePath); + makeReadOnly(archivePath); + } catch (IOException e) { + permissionsLocked.set(false); + LOG.warn("Unable to lock archive directory {}", archivePath, e); + } + } - // no restrictions - System.setSecurityManager(null); + LOG.info("Producing messages to trigger archive attempts"); + sent += produceMessagesToConsumeMultipleDataFiles(25); - int numFilesAfterRestart = 0; - try { - // ensure we can restart after failure to archive - doStartBroker(false); - numFilesAfterRestart = getNumberOfJournalFiles(); - LOG.info("Num journal files before gc: " + numFilesAfterRestart); + int numFilesAfterSend = getNumberOfJournalFiles(); + LOG.info("Num journal files: {}", numFilesAfterSend); - // force gc - ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore().checkpoint(true); + assertTrue("more than x files: " + numFilesAfterSend, numFilesAfterSend > 4); - } catch (Exception error) { - LOG.error("Failed to restart!", error); - fail("Failed to restart after failure to archive"); - } - int numFilesAfterGC = getNumberOfJournalFiles(); - LOG.info("Num journal files after restart nd gc: " + numFilesAfterGC); - assertTrue("Gc has happened", numFilesAfterGC < numFilesAfterRestart); - assertTrue("Gc has worked", numFilesAfterGC < 4); + int received = tryConsume(destination, sent); + assertEquals("all message received", sent, received); + assertTrue("broker got shutdown on page in error", gotShutdown.await(10, TimeUnit.SECONDS)); - File archiveDirectory = ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore().getJournal().getDirectoryArchive(); - assertEquals("verify files in archive dir", numFilesAfterSend, archiveDirectory.listFiles().length); + // remove restrictions to see if it catches up again + if (permissionsLocked.get()) { + try { + makeWritable(archivePath); + } catch (IOException e) { + LOG.warn("Unable to restore archive directory permissions: {}", archivePath, e); + } + } + + int numFilesAfterRestart = 0; + try { + // ensure we can restart after failure to archive + doStartBroker(false); + numFilesAfterRestart = getNumberOfJournalFiles(); + LOG.info("Num journal files before gc: {}", numFilesAfterRestart); + + // force gc + ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore().checkpoint(true); + + } catch (Exception error) { + LOG.error("Failed to restart!", error); + fail("Failed to restart after failure to archive"); + } + int numFilesAfterGC = getNumberOfJournalFiles(); + LOG.info("Num journal files after restart nd gc: {}", numFilesAfterGC); + assertTrue("Gc has happened", numFilesAfterGC < numFilesAfterRestart); + assertTrue("Gc has worked", numFilesAfterGC < 4); + + File archiveDirectory = ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore() + .getJournal() + .getDirectoryArchive(); + assertEquals("verify files in archive dir", numFilesAfterSend, archiveDirectory.listFiles().length); + } finally { + if (permissionsLocked.get()) { + try { + makeWritable(archivePath); + } catch (IOException e) { + LOG.warn("Unable to restore archive directory permissions: {}", archivePath, e); + } + } + } } @@ -218,4 +253,31 @@ private int produceMessagesToConsumeMultipleDataFiles(int numToSend) throws Exce private Message createMessage(Session session, int i) throws Exception { return session.createTextMessage(payload + "::" + i); } + + private Path prepareArchivePath() throws IOException { + File archiveDirectory = ((KahaDBPersistenceAdapter) broker.getPersistenceAdapter()).getStore().getJournal().getDirectoryArchive(); + Files.createDirectories(archiveDirectory.toPath()); + return archiveDirectory.toPath(); + } + + private void makeReadOnly(Path path) throws IOException { + try { + Files.setPosixFilePermissions(path, EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_EXECUTE)); + } catch (UnsupportedOperationException ex) { + if (!path.toFile().setWritable(false, false)) { + throw new IOException("Unable to mark directory as read-only: " + path); + } + } + } + + private void makeWritable(Path path) throws IOException { + try { + Files.setPosixFilePermissions(path, EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE)); + } catch (UnsupportedOperationException ex) { + if (!path.toFile().setWritable(true, false)) { + throw new IOException("Unable to restore write permissions to directory: " + path); + } + } + } } From f94e49ac4c21359593c677a641817e793fa05fa1 Mon Sep 17 00:00:00 2001 From: Jean-Louis Monteiro Date: Fri, 14 Nov 2025 11:08:18 +0100 Subject: [PATCH 5/8] fix: Spring 6 no longer accepts local attribute ref. Use a modern bean reference. --- .../src/test/resources/org/apache/activemq/broker/spring.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/activemq-unit-tests/src/test/resources/org/apache/activemq/broker/spring.xml b/activemq-unit-tests/src/test/resources/org/apache/activemq/broker/spring.xml index 7d37217f476..f20eedb5dfd 100644 --- a/activemq-unit-tests/src/test/resources/org/apache/activemq/broker/spring.xml +++ b/activemq-unit-tests/src/test/resources/org/apache/activemq/broker/spring.xml @@ -46,9 +46,7 @@ - - - + From 4784112bafbb9e5aeee00e8eacb8b0f50f2b4b90 Mon Sep 17 00:00:00 2001 From: Jean-Louis Monteiro Date: Fri, 14 Nov 2025 11:58:30 +0100 Subject: [PATCH 6/8] fix: prevent activemq-data to be created outside target/ build directory --- activemq-client/pom.xml | 2 +- activemq-console/pom.xml | 2 +- activemq-jaas/pom.xml | 2 +- activemq-jdbc-store/pom.xml | 2 +- activemq-kahadb-store/pom.xml | 2 +- activemq-mqtt/pom.xml | 2 +- activemq-runtime-config/pom.xml | 2 +- activemq-shiro/pom.xml | 2 +- activemq-stomp/pom.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/activemq-client/pom.xml b/activemq-client/pom.xml index ebc8c5facf1..70cc08f6053 100644 --- a/activemq-client/pom.xml +++ b/activemq-client/pom.xml @@ -227,7 +227,7 @@ ${surefire.argLine} alphabetical - target + target/ diff --git a/activemq-jaas/pom.xml b/activemq-jaas/pom.xml index 34f1552de34..ef60b64089d 100644 --- a/activemq-jaas/pom.xml +++ b/activemq-jaas/pom.xml @@ -40,7 +40,7 @@ -Xmx512M - target + target/ - ${project.basedir}/src/main/java21 + 24 + ${project.basedir}/src/main/java24 true diff --git a/activemq-compat/src/main/java21/org/apache/activemq/compat/sm/SecurityManagerShim.java b/activemq-compat/src/main/java24/org/apache/activemq/compat/sm/SecurityManagerShim.java similarity index 100% rename from activemq-compat/src/main/java21/org/apache/activemq/compat/sm/SecurityManagerShim.java rename to activemq-compat/src/main/java24/org/apache/activemq/compat/sm/SecurityManagerShim.java