This is an automated email from the ASF dual-hosted git repository.
hanicz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new be8679c17 KNOX-3332: Add listeners on gateway config changes (#1243)
be8679c17 is described below
commit be8679c17915c2733b3e5d42f6458a1badc0ea41
Author: hanicz <[email protected]>
AuthorDate: Mon Jun 1 12:35:41 2026 +0200
KNOX-3332: Add listeners on gateway config changes (#1243)
* KNOX-3332: New gatewayconfig change listener
* KNOX-3332: Improve testOnGatewayConfigChanged test
* KNOX-3332: Moved the listener into KnoxLDAPService
* KNOX-3332: Simplify restart logic
---
.../org/apache/knox/gateway/GatewayServer.java | 15 ++++
.../gateway/services/DefaultGatewayServices.java | 2 +
.../services/ldap/KnoxLDAPServerManager.java | 37 ++++++---
.../gateway/services/ldap/KnoxLDAPService.java | 51 ++++++------
.../knox/gateway/services/ldap/LdapMessages.java | 8 ++
.../org/apache/knox/gateway/GatewayServerTest.java | 32 ++++++++
.../services/ldap/KnoxLDAPServerManagerTest.java | 94 +++++++++++++++-------
.../gateway/services/ldap/KnoxLDAPServiceTest.java | 27 ++++++-
.../config/GatewayConfigChangeListener.java | 22 +++++
9 files changed, 216 insertions(+), 72 deletions(-)
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
index cf1e8f998..a39e5e6c1 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
@@ -28,6 +28,7 @@ import org.apache.knox.gateway.audit.api.Auditor;
import org.apache.knox.gateway.audit.api.ResourceType;
import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.config.GatewayConfigChangeListener;
import org.apache.knox.gateway.config.GatewayConfigurationException;
import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
import org.apache.knox.gateway.deploy.DeploymentException;
@@ -127,6 +128,7 @@ import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -148,6 +150,8 @@ public class GatewayServer {
private static GatewayServer server;
private static GatewayServices services;
+ private static final List<GatewayConfigChangeListener> configChangeListeners
= new CopyOnWriteArrayList<>();
+
private static Properties buildProperties;
private static final ScheduledExecutorService exec =
Executors.newSingleThreadScheduledExecutor();
@@ -239,6 +243,14 @@ public class GatewayServer {
return services;
}
+ public static void registerConfigChangeListener(GatewayConfigChangeListener
listener) {
+ configChangeListeners.add(listener);
+ }
+
+ public static void
unregisterConfigChangeListener(GatewayConfigChangeListener listener) {
+ configChangeListeners.remove(listener);
+ }
+
private static void setupGatewayConfigRefresh(GatewayConfigImpl config) {
Path resourcePath = Paths.get(config.getGatewayConfDir(),
RELOADABLE_CONFIG_FILENAME);
int refreshInterval = config.getConfigRefreshInterval();
@@ -261,6 +273,9 @@ public class GatewayServer {
lastReloadTime = lastModifiedTime;
config.reloadConfiguration();
log.refreshedGatewayConfig();
+ for (GatewayConfigChangeListener listener : configChangeListeners) {
+ listener.onGatewayConfigChanged(config);
+ }
}
}
} catch (IOException e) {
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
index 06ce95d93..3f0359976 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import org.apache.knox.gateway.GatewayMessages;
+import org.apache.knox.gateway.GatewayServer;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.deploy.DeploymentContext;
import org.apache.knox.gateway.descriptor.FilterParamDescriptor;
@@ -88,6 +89,7 @@ public class DefaultGatewayServices extends
AbstractGatewayServices {
if (config.isLDAPEnabled()) {
KnoxLDAPService ldapService = new KnoxLDAPService();
ldapService.init(config, options);
+ GatewayServer.registerConfigChangeListener(ldapService);
addService(ServiceType.LDAP_SERVICE, ldapService);
}
}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
index 300e6f936..cd7bb1a80 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
@@ -30,6 +30,7 @@ import
org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
import org.apache.directory.server.core.partition.ldif.LdifPartition;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
+import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.ldap.backend.BackendFactory;
import org.apache.knox.gateway.services.ldap.backend.LdapBackend;
@@ -56,21 +57,33 @@ public class KnoxLDAPServerManager {
/**
* Initialize the LDAP server with the given configuration
*
- * @param workDir Directory for LDAP data storage
- * @param port Port for LDAP server to listen on
- * @param baseDn Base DN for LDAP entries in the proxy server
- * @param backendType Type of backend to use
- * @param backendConfig Backend-specific configuration
- * @param remoteBaseDn Base DN of the remote LDAP server (for proxy
backends)
+ * @param config Gateway configuration
*/
- public void initialize(File workDir, int port, String baseDn, String
backendType, Map<String, String> backendConfig, String remoteBaseDn) throws
Exception {
- this.workDir = workDir;
- this.port = port;
- this.baseDn = baseDn;
- this.remoteBaseDn = remoteBaseDn;
+ public void initialize(GatewayConfig config) throws Exception {
+ // Prepare work directory for LDAP data
+ File gatewayDataDir = new File(config.getGatewayDataDir());
+ this.workDir = new File(gatewayDataDir, "ldap-server");
- // Initialize backend
+ // Get configuration
+ this.port = config.getLDAPPort();
+ this.baseDn = config.getLDAPBaseDN();
+ String backendType = config.getLDAPBackendType();
+
+ // Get backend-specific configuration using prefixed properties
+ Map<String, String> backendConfig =
config.getLDAPBackendConfig(backendType);
+
+ // Add common configuration
backendConfig.put("baseDn", baseDn);
+
+ // Add legacy dataFile property for backwards compatibility with file
backend
+ if ("file".equalsIgnoreCase(backendType) &&
!backendConfig.containsKey("dataFile")) {
+ backendConfig.put("dataFile", config.getLDAPBackendDataFile());
+ }
+
+ // For proxy backends, extract remoteBaseDn if present
+ this.remoteBaseDn = backendConfig.get("remoteBaseDn");
+
+ // Initialize backend
backend = BackendFactory.createBackend(backendType, backendConfig);
// Clean up previous run if it didn't shut down cleanly
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java
index 1ec748da9..a6f66a67e 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java
@@ -18,18 +18,18 @@
package org.apache.knox.gateway.services.ldap;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.config.GatewayConfigChangeListener;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.Service;
import org.apache.knox.gateway.services.ServiceLifecycleException;
-import java.io.File;
import java.util.Map;
/**
* Knox LDAP Service - provides an embedded LDAP server with pluggable backends
* for user and group lookups.
*/
-public class KnoxLDAPService implements Service {
+public class KnoxLDAPService implements Service, GatewayConfigChangeListener {
private static final LdapMessages LOG =
MessagesFactory.get(LdapMessages.class);
private KnoxLDAPServerManager ldapServerManager;
@@ -46,32 +46,7 @@ public class KnoxLDAPService implements Service {
try {
// Initialize the LDAP server manager with configuration
ldapServerManager = new KnoxLDAPServerManager();
-
- // Prepare work directory for LDAP data
- File gatewayDataDir = new File(config.getGatewayDataDir());
- File ldapWorkDir = new File(gatewayDataDir, "ldap-server");
-
- // Get configuration
- int port = config.getLDAPPort();
- String baseDn = config.getLDAPBaseDN();
- String backendType = config.getLDAPBackendType();
-
- // Get backend-specific configuration using prefixed properties
- Map<String, String> backendConfig =
config.getLDAPBackendConfig(backendType);
-
- // Add common configuration
- backendConfig.put("baseDn", baseDn);
-
- // Add legacy dataFile property for backwards compatibility with
file backend
- if ("file".equalsIgnoreCase(backendType) &&
!backendConfig.containsKey("dataFile")) {
- backendConfig.put("dataFile", config.getLDAPBackendDataFile());
- }
-
- // For proxy backends, extract remoteBaseDn if present
- String remoteBaseDn = backendConfig.get("remoteBaseDn");
-
- // Initialize but don't start yet
- ldapServerManager.initialize(ldapWorkDir, port, baseDn,
backendType, backendConfig, remoteBaseDn);
+ ldapServerManager.initialize(config);
} catch (Exception e) {
throw new ServiceLifecycleException("Failed to initialize LDAP
service", e);
@@ -107,6 +82,26 @@ public class KnoxLDAPService implements Service {
}
}
+ @Override
+ public void onGatewayConfigChanged(GatewayConfig config) {
+ LOG.ldapReloadingConfig();
+ try {
+ this.enabled = config.isLDAPEnabled();
+
+ if (this.enabled) {
+ this.ldapServerManager = this.ldapServerManager == null ? new
KnoxLDAPServerManager() : this.ldapServerManager;
+ ldapServerManager.stop();
+ ldapServerManager.initialize(config);
+ ldapServerManager.start();
+ } else if (ldapServerManager != null) {
+ ldapServerManager.stop();
+ ldapServerManager = null;
+ }
+ } catch (Exception e) {
+ LOG.ldapServiceReloadFailed(e);
+ }
+ }
+
/**
* Get the port the LDAP server is listening on
*/
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
index 4845cda79..cc92a099d 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
@@ -102,4 +102,12 @@ public interface LdapMessages {
@Message(level = MessageLevel.WARN, text = "LDAP authentication failed for
user: {0}")
void ldapAuthFailed(String user, @StackTrace(level = MessageLevel.INFO)
Throwable cause);
+
+ @Message(level = MessageLevel.INFO,
+ text = "Reloading LDAP configuration")
+ void ldapReloadingConfig();
+
+ @Message(level = MessageLevel.ERROR,
+ text = "Failed to reload LDAP service: {0}")
+ void ldapServiceReloadFailed(@StackTrace(level = MessageLevel.DEBUG)
Exception e);
}
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
index 33d348190..a5938d7cd 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
@@ -17,6 +17,7 @@
*/
package org.apache.knox.gateway;
+import org.apache.knox.gateway.config.GatewayConfigChangeListener;
import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
import org.easymock.EasyMock;
import org.junit.Rule;
@@ -32,6 +33,7 @@ import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
public class GatewayServerTest {
@@ -95,4 +97,34 @@ public class GatewayServerTest {
refreshMethod.invoke(null, config, configFile.toPath());
EasyMock.verify(config);
}
+
+ @Test
+ public void testGatewayConfigChangeListener() throws Exception {
+ GatewayConfigImpl config =
EasyMock.createNiceMock(GatewayConfigImpl.class);
+ File configFile = folder.newFile("gateway-reloadable-listener.xml");
+ try (PrintWriter writer = new PrintWriter(configFile,
StandardCharsets.UTF_8)) {
+ writer.println("<configuration></configuration>");
+ }
+
+ final boolean[] notified = {false};
+ GatewayConfigChangeListener listener = (c) -> notified[0] = true;
+
+ GatewayServer.registerConfigChangeListener(listener);
+
+ try {
+ Method refreshMethod =
GatewayServer.class.getDeclaredMethod("refreshGatewayConfig",
GatewayConfigImpl.class, Path.class);
+ refreshMethod.setAccessible(true);
+
+ // Reset lastReloadTime to ensure refresh happens
+ Field lastReloadTimeField =
GatewayServer.class.getDeclaredField("lastReloadTime");
+ lastReloadTimeField.setAccessible(true);
+ lastReloadTimeField.set(null, null);
+
+ refreshMethod.invoke(null, config, configFile.toPath());
+
+ assertTrue("Listener should have been notified", notified[0]);
+ } finally {
+ GatewayServer.unregisterConfigChangeListener(listener);
+ }
+ }
}
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
index a9450a96f..4528c8b4a 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
@@ -17,6 +17,8 @@
*/
package org.apache.knox.gateway.services.ldap;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.easymock.EasyMock;
import org.junit.Test;
import org.junit.Before;
import org.junit.After;
@@ -25,6 +27,8 @@ import java.io.File;
import java.util.HashMap;
import java.util.Map;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
@@ -71,9 +75,16 @@ public class KnoxLDAPServerManagerTest {
@Test
public void testInitializeWithFileBackend() throws Exception {
- Map<String, String> backendConfig = createFileBackendConfig();
+ GatewayConfig mockConfig =
EasyMock.createNiceMock(GatewayConfig.class);
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+ expect(mockConfig.getLDAPPort()).andReturn(3890).anyTimes();
+
expect(mockConfig.getLDAPBaseDN()).andReturn("dc=test,dc=com").anyTimes();
+ expect(mockConfig.getLDAPBackendType()).andReturn("file").anyTimes();
+
expect(mockConfig.getLDAPBackendDataFile()).andReturn(tempLdapFile.getAbsolutePath()).anyTimes();
+ expect(mockConfig.getLDAPBackendConfig("file")).andReturn(new
HashMap<>()).anyTimes();
+ replay(mockConfig);
- serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", "file",
backendConfig, null);
+ serverManager.initialize(mockConfig);
assertEquals("Port should be set correctly", 3890,
serverManager.getPort());
assertEquals("Base DN should be set correctly", "dc=test,dc=com",
serverManager.getBaseDn());
@@ -82,9 +93,22 @@ public class KnoxLDAPServerManagerTest {
@Test
public void testInitializeWithLdapBackend() throws Exception {
- Map<String, String> backendConfig = createLdapBackendConfig();
+ GatewayConfig mockConfig =
EasyMock.createNiceMock(GatewayConfig.class);
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+ expect(mockConfig.getLDAPPort()).andReturn(3891).anyTimes();
+
expect(mockConfig.getLDAPBaseDN()).andReturn("dc=proxy,dc=com").anyTimes();
+ expect(mockConfig.getLDAPBackendType()).andReturn("ldap").anyTimes();
- serverManager.initialize(tempWorkDir, 3891, "dc=proxy,dc=com", "ldap",
backendConfig, "dc=hadoop,dc=apache,dc=org");
+ Map<String, String> backendConfig = new HashMap<>();
+ backendConfig.put("url", "ldap://localhost:33389");
+ backendConfig.put("remoteBaseDn", "dc=hadoop,dc=apache,dc=org");
+ backendConfig.put("systemUsername",
"cn=admin,dc=hadoop,dc=apache,dc=org");
+ backendConfig.put("systemPassword", "admin-password");
+
+
expect(mockConfig.getLDAPBackendConfig("ldap")).andReturn(backendConfig).anyTimes();
+ replay(mockConfig);
+
+ serverManager.initialize(mockConfig);
assertEquals("Port should be set correctly", 3891,
serverManager.getPort());
assertEquals("Base DN should be set correctly", "dc=proxy,dc=com",
serverManager.getBaseDn());
@@ -93,23 +117,36 @@ public class KnoxLDAPServerManagerTest {
@Test(expected = Exception.class)
public void testInitializeWithInvalidBackendType() throws Exception {
- Map<String, String> backendConfig = new HashMap<>();
- backendConfig.put("baseDn", "dc=test,dc=com");
+ GatewayConfig mockConfig =
EasyMock.createNiceMock(GatewayConfig.class);
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+
expect(mockConfig.getLDAPBackendType()).andReturn("invalid").anyTimes();
+ expect(mockConfig.getLDAPBackendConfig("invalid")).andReturn(new
HashMap<>()).anyTimes();
+ replay(mockConfig);
- serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com",
"invalid", backendConfig, null);
+ serverManager.initialize(mockConfig);
}
@Test
public void testLockFileCleanup() throws Exception {
+ GatewayConfig mockConfig =
EasyMock.createNiceMock(GatewayConfig.class);
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+ expect(mockConfig.getLDAPBackendType()).andReturn("file").anyTimes();
+
expect(mockConfig.getLDAPBackendDataFile()).andReturn(tempLdapFile.getAbsolutePath()).anyTimes();
+ expect(mockConfig.getLDAPBackendConfig("file")).andReturn(new
HashMap<>()).anyTimes();
+ replay(mockConfig);
+
+ // The manager will use tempWorkDir.getParent()/ldap-server as workDir
+ File actualWorkDir = new File(tempWorkDir.getParent(), "ldap-server");
+ actualWorkDir.mkdirs();
+
// Create a lock file to simulate previous unclean shutdown
- File runDir = new File(tempWorkDir, "run");
+ File runDir = new File(actualWorkDir, "run");
runDir.mkdirs();
File lockFile = new File(runDir, "instance.lock");
lockFile.createNewFile();
assertTrue("Lock file should exist before initialization",
lockFile.exists());
- Map<String, String> backendConfig = createFileBackendConfig();
- serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", "file",
backendConfig, null);
+ serverManager.initialize(mockConfig);
assertFalse("Lock file should be cleaned up during initialization",
lockFile.exists());
}
@@ -123,8 +160,14 @@ public class KnoxLDAPServerManagerTest {
@Test
public void testStopBeforeStart() throws Exception {
- Map<String, String> backendConfig = createFileBackendConfig();
- serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", "file",
backendConfig, null);
+ GatewayConfig mockConfig =
EasyMock.createNiceMock(GatewayConfig.class);
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+ expect(mockConfig.getLDAPBackendType()).andReturn("file").anyTimes();
+
expect(mockConfig.getLDAPBackendDataFile()).andReturn(tempLdapFile.getAbsolutePath()).anyTimes();
+ expect(mockConfig.getLDAPBackendConfig("file")).andReturn(new
HashMap<>()).anyTimes();
+ replay(mockConfig);
+
+ serverManager.initialize(mockConfig);
// Should not throw exception when stopping before starting
serverManager.stop();
@@ -132,8 +175,14 @@ public class KnoxLDAPServerManagerTest {
@Test
public void testMultipleStopCalls() throws Exception {
- Map<String, String> backendConfig = createFileBackendConfig();
- serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", "file",
backendConfig, null);
+ GatewayConfig mockConfig =
EasyMock.createNiceMock(GatewayConfig.class);
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+ expect(mockConfig.getLDAPBackendType()).andReturn("file").anyTimes();
+
expect(mockConfig.getLDAPBackendDataFile()).andReturn(tempLdapFile.getAbsolutePath()).anyTimes();
+ expect(mockConfig.getLDAPBackendConfig("file")).andReturn(new
HashMap<>()).anyTimes();
+ replay(mockConfig);
+
+ serverManager.initialize(mockConfig);
// Multiple stop calls should not throw exceptions
serverManager.stop();
@@ -147,23 +196,6 @@ public class KnoxLDAPServerManagerTest {
serverManager.start();
}
- private Map<String, String> createFileBackendConfig() {
- Map<String, String> config = new HashMap<>();
- config.put("baseDn", "dc=test,dc=com");
- config.put("dataFile", tempLdapFile.getAbsolutePath());
- return config;
- }
-
- private Map<String, String> createLdapBackendConfig() {
- Map<String, String> config = new HashMap<>();
- config.put("baseDn", "dc=proxy,dc=com");
- config.put("url", "ldap://localhost:33389");
- config.put("remoteBaseDn", "dc=hadoop,dc=apache,dc=org");
- config.put("systemUsername", "cn=admin,dc=hadoop,dc=apache,dc=org");
- config.put("systemPassword", "admin-password");
- return config;
- }
-
private void cleanupTempFiles() {
if (tempLdapFile != null && tempLdapFile.exists()) {
tempLdapFile.delete();
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
index dbdb081da..fe045194b 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
@@ -165,6 +165,31 @@ public class KnoxLDAPServiceTest {
assertFalse("Should not be enabled when not initialized",
ldapService.isEnabled());
}
+ @Test
+ public void testOnGatewayConfigChanged() throws Exception {
+ expect(mockConfig.isLDAPEnabled()).andReturn(true).anyTimes();
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempDataDir.getAbsolutePath()).anyTimes();
+
expect(mockConfig.getLDAPPort()).andReturn(3890).times(1).andReturn(3891).anyTimes();
+
expect(mockConfig.getLDAPBaseDN()).andReturn("dc=test,dc=com").anyTimes();
+ expect(mockConfig.getLDAPBackendType()).andReturn("file").anyTimes();
+
+ Map<String, String> fileBackendConfig = new HashMap<>();
+ fileBackendConfig.put("dataFile", tempLdapFile.getAbsolutePath());
+
expect(mockConfig.getLDAPBackendConfig("file")).andReturn(fileBackendConfig).anyTimes();
+
+ replay(mockConfig);
+
+ ldapService.init(mockConfig, new HashMap<>());
+ assertEquals("Initial port should be 3890", 3890,
ldapService.getLdapPort());
+
+ // Test reload with new port
+ ldapService.onGatewayConfigChanged(mockConfig);
+
+ assertEquals("Port should be updated to 3891 after reload", 3891,
ldapService.getLdapPort());
+
+ verify(mockConfig);
+ }
+
private void setupMockConfigForFileBackend() {
expect(mockConfig.isLDAPEnabled()).andReturn(true);
expect(mockConfig.getGatewayDataDir()).andReturn(tempDataDir.getAbsolutePath());
@@ -191,4 +216,4 @@ public class KnoxLDAPServiceTest {
ldapBackendConfig.put("systemPassword", "admin-password");
expect(mockConfig.getLDAPBackendConfig("ldap")).andReturn(ldapBackendConfig);
}
-}
\ No newline at end of file
+}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfigChangeListener.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfigChangeListener.java
new file mode 100644
index 000000000..5dfd0fbf2
--- /dev/null
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfigChangeListener.java
@@ -0,0 +1,22 @@
+/*
+ * 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.knox.gateway.config;
+
+public interface GatewayConfigChangeListener {
+ void onGatewayConfigChanged(GatewayConfig config);
+}