This is an automated email from the ASF dual-hosted git repository. quantranhong1999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 791bb3a664ec4da6353717c014a6a155ce3e046a Author: Quan Tran <[email protected]> AuthorDate: Fri Jul 10 15:49:15 2026 +0700 JAMES-4210 [FIX] Reuse protocol default SASL factories for explicit configuration Resolve explicitly configured SASL factory names against protocol-provided defaults before falling back to Guice loading. This enables constructor-configured factories such as SMTP LOGIN and preserves protocol-specific PLAIN policies for explicit configuration. Support both simple and fully qualified factory names. --- .../james/utils/GuiceSaslMechanismResolver.java | 18 ++++++- .../utils/GuiceSaslMechanismResolverTest.java | 63 ++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/server/container/guice/common/src/main/java/org/apache/james/utils/GuiceSaslMechanismResolver.java b/server/container/guice/common/src/main/java/org/apache/james/utils/GuiceSaslMechanismResolver.java index c34d2e5804..0bc526a2c9 100644 --- a/server/container/guice/common/src/main/java/org/apache/james/utils/GuiceSaslMechanismResolver.java +++ b/server/container/guice/common/src/main/java/org/apache/james/utils/GuiceSaslMechanismResolver.java @@ -54,7 +54,7 @@ public class GuiceSaslMechanismResolver { ImmutableList<SaslMechanismFactory> factories = configuredFactoryClassNames.isEmpty() ? enabledDefaultFactories : configuredFactoryClassNames.stream() - .map(Throwing.function(this::instantiateFactory)) + .map(Throwing.function(className -> resolveConfiguredFactory(className, enabledDefaultFactories))) .collect(ImmutableList.toImmutableList()); return factories.stream() @@ -75,6 +75,22 @@ public class GuiceSaslMechanismResolver { } } + private SaslMechanismFactory resolveConfiguredFactory(String className, ImmutableList<SaslMechanismFactory> enabledDefaultFactories) throws ConfigurationException { + return enabledDefaultFactories.stream() + .filter(factory -> matchesCanonicalName(className, factory) || matchesSimpleName(className, factory)) + .findFirst() + .orElseGet(Throwing.supplier(() -> instantiateFactory(className))); + } + + private boolean matchesCanonicalName(String className, SaslMechanismFactory factory) { + return className.equals(factory.getClass().getCanonicalName()); + } + + private boolean matchesSimpleName(String className, SaslMechanismFactory factory) { + return !className.contains(".") + && className.equals(factory.getClass().getSimpleName()); + } + private SaslMechanismFactory instantiateFactory(String className) throws ConfigurationException { try { return factoryLoader.instantiate(new ClassName(className)); diff --git a/server/container/guice/common/src/test/java/org/apache/james/utils/GuiceSaslMechanismResolverTest.java b/server/container/guice/common/src/test/java/org/apache/james/utils/GuiceSaslMechanismResolverTest.java index e3551ee400..83b1fb3813 100644 --- a/server/container/guice/common/src/test/java/org/apache/james/utils/GuiceSaslMechanismResolverTest.java +++ b/server/container/guice/common/src/test/java/org/apache/james/utils/GuiceSaslMechanismResolverTest.java @@ -103,6 +103,38 @@ class GuiceSaslMechanismResolverTest { .containsExactly("EXTERNAL-FAKE"); } + @Test + void resolveShouldReuseDefaultFactoryMatchingConfiguredSimpleNameWithoutGuiceLoadingIt() throws Exception { + // GIVEN a configured factory simple name matching a protocol-provided default factory + GuiceSaslMechanismResolver testee = new GuiceSaslMechanismResolver(new FailingGuiceLoader()); + + // WHEN resolving it + ImmutableList<SaslMechanism> mechanisms = testee.resolve(ImmutableList.of(ConstructorOnlySaslMechanismFactory.class.getSimpleName()), + ImmutableList.of(new ConstructorOnlySaslMechanismFactory("LOGIN")), + EMPTY_CONFIGURATION); + + // THEN the default factory instance is reused instead of Guice-loading a fresh standalone factory + assertThat(mechanisms) + .extracting(SaslMechanism::name) + .containsExactly("LOGIN"); + } + + @Test + void resolveShouldReuseDefaultFactoryMatchingConfiguredFullyQualifiedNameWithoutGuiceLoadingIt() throws Exception { + // GIVEN a configured factory FQCN matching a protocol-provided default factory + GuiceSaslMechanismResolver testee = new GuiceSaslMechanismResolver(new FailingGuiceLoader()); + + // WHEN resolving it + ImmutableList<SaslMechanism> mechanisms = testee.resolve(ImmutableList.of(ConstructorOnlySaslMechanismFactory.class.getCanonicalName()), + ImmutableList.of(new ConstructorOnlySaslMechanismFactory("LOGIN")), + EMPTY_CONFIGURATION); + + // THEN the default factory instance is reused before falling back to Guice loading + assertThat(mechanisms) + .extracting(SaslMechanism::name) + .containsExactly("LOGIN"); + } + @Test void resolveShouldCreateConfiguredFactoriesFromCurrentServerConfiguration() throws Exception { // GIVEN two server configurations using the same configured SASL factory @@ -177,6 +209,19 @@ class GuiceSaslMechanismResolverTest { return serverConfiguration -> new FixedNameSaslMechanism(mechanismName); } + private static class ConstructorOnlySaslMechanismFactory implements SaslMechanismFactory { + private final String mechanismName; + + private ConstructorOnlySaslMechanismFactory(String mechanismName) { + this.mechanismName = mechanismName; + } + + @Override + public SaslMechanism create(HierarchicalConfiguration<ImmutableNode> serverConfiguration) { + return new FixedNameSaslMechanism(mechanismName); + } + } + private static class ReflectionGuiceLoader implements GuiceLoader { @Override public <T> T instantiate(ClassName className) throws ClassNotFoundException { @@ -194,6 +239,24 @@ class GuiceSaslMechanismResolverTest { } } + private static class FailingGuiceLoader extends ReflectionGuiceLoader { + @Override + public <T> InvocationPerformer<T> withNamingSheme(NamingScheme namingSheme) { + return new FailingInvocationPerformer<>(); + } + } + + private static class FailingInvocationPerformer<T> extends ReflectionInvocationPerformer<T> { + private FailingInvocationPerformer() { + super(NamingScheme.IDENTITY); + } + + @Override + public T instantiate(ClassName className) { + throw new AssertionError("Default factory match should not instantiate " + className.getName()); + } + } + private static class ReflectionInvocationPerformer<T> implements GuiceLoader.InvocationPerformer<T> { private final NamingScheme namingScheme; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
