diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index 48f0e968d..fdfeaca2a 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -81,6 +81,8 @@ The wiki pages https://github.com/spring-projects/spring-framework/wiki/Code-Sty To format the code as well as check the style, run `./gradlew format check`. +Lastly, run `./gradlew checkstyleMain checkstyleTest` before submitting a pull request, for additional style checks. + [[submit-a-pull-request]] === Submit a Pull Request diff --git a/modules/ROOT/pages/testing.adoc b/modules/ROOT/pages/testing.adoc index 76224492c..e1a211613 100644 --- a/modules/ROOT/pages/testing.adoc +++ b/modules/ROOT/pages/testing.adoc @@ -182,17 +182,19 @@ testCompile "com.unboundid:unboundid-ldapsdk:3.1.1" The following bean definition creates an embedded LDAP server: ==== -[source,xml] +[source,java] ---- - - - - - +@Bean +EmbeddedLdapServer embeddedLdapServer() { + return EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se") + .partitionName("jayway") + .port(18881) + .configurationCustomizer((config) -> config.setCodeLogDetails(tempLogFile, true)) + .build(); +} ---- -==== -`spring-ldap-test` provides a way to populate the LDAP server by using `org.springframework.ldap.test.unboundid.LdifPopulator`. To use it, create a bean similar to the following: +Alternatively, you can use the `org.springframework.ldap.test.unboundid.LdifPopulator` to create and populate the LDAP server. To use it, create a bean similar to the following: ==== [source,xml] diff --git a/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java b/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java index b764493ce..06d3baeb7 100644 --- a/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java +++ b/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java @@ -16,6 +16,13 @@ package org.springframework.ldap.test.unboundid; +import java.util.List; +import java.util.function.Consumer; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; @@ -24,6 +31,7 @@ import com.unboundid.ldap.sdk.LDAPException; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Helper class for embedded Unboundid ldap server. @@ -45,27 +53,30 @@ public EmbeddedLdapServer(InMemoryDirectoryServer directoryServer) { this.directoryServer = directoryServer; } + /** + * Creates a new {@link Builder} with a given partition suffix. + * + * @since 3.3 + */ + public static Builder withPartitionSuffix(String partitionSuffix) { + return new Builder(partitionSuffix); + } + /** * Creates and starts new embedded LDAP server. + * @deprecated Use the builder pattern exposed via + * {@link #withPartitionSuffix(String)} instead. */ + @Deprecated(since = "3.3") public static EmbeddedLdapServer newEmbeddedServer(String defaultPartitionName, String defaultPartitionSuffix, int port) throws Exception { - InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(defaultPartitionSuffix); - config.addAdditionalBindCredentials("uid=admin,ou=system", "secret"); - - config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", port)); - - config.setEnforceSingleStructuralObjectClass(false); - config.setEnforceAttributeSyntaxCompliance(true); + EmbeddedLdapServer server = EmbeddedLdapServer.withPartitionSuffix(defaultPartitionSuffix) + .partitionName(defaultPartitionName) + .port(port) + .build(); - Entry entry = new Entry(new DN(defaultPartitionSuffix)); - entry.addAttribute("objectClass", "top", "domain", "extensibleObject"); - entry.addAttribute("dc", defaultPartitionName); - - InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config); - directoryServer.add(entry); - directoryServer.startListening(); - return new EmbeddedLdapServer(directoryServer); + server.start(); + return server; } /** @@ -102,4 +113,119 @@ public void shutdown() { this.directoryServer.shutDown(true); } + /** + * Helper class for embedded Unboundid ldap server. + * + * @author Emanuel Trandafir + * @since 3.3 + */ + public static final class Builder { + + private final String partitionSuffix; + + private String partitionName; + + private int port = 0; + + private Consumer configurationCustomizer = (__) -> { + }; + + private Builder(String partitionSuffix) { + this.partitionSuffix = partitionSuffix; + this.partitionName = leftMostElement(partitionSuffix); + } + + /** + * Sets the port for the embedded LDAP server. + * @param port the port for the embedded LDAP server. Defaults to 0 in which case + * the server should automatically choose an available port. + * @return this {@link Builder} instance. + */ + public Builder port(int port) { + this.port = port; + return this; + } + + /** + * Sets a customizer for the {@link InMemoryDirectoryServerConfig}. + * @param configurationCustomizer a {@link Consumer} function that will be applied + * to the {@link InMemoryDirectoryServerConfig} before creating the + * {@link InMemoryDirectoryServer}. The default values, it a Consumer function + * that does nothing: (config) -> {} + * @return this {@link Builder} instance. + */ + public Builder configurationCustomizer(Consumer configurationCustomizer) { + this.configurationCustomizer = configurationCustomizer; + return this; + } + + /** + * Sets the partition name for the embedded LDAP server. + * @param partitionName the partition name for the embedded LDAP server. Defaults + * to the left most element of the partition suffix. + * @return this {@link Builder} instance. + */ + public Builder partitionName(String partitionName) { + this.partitionName = partitionName; + return this; + } + + /** + * Builds and returns a {@link EmbeddedLdapServer}. + *

+ * In order to start the server, you should call + * {@link EmbeddedLdapServer#start()}. + * @return a new {@link EmbeddedLdapServer}. + */ + public EmbeddedLdapServer build() { + try { + InMemoryDirectoryServerConfig config = inMemoryDirectoryServerConfig(this.partitionSuffix, this.port); + this.configurationCustomizer.accept(config); + + Entry entry = ldapEntry(this.partitionName, this.partitionSuffix); + InMemoryDirectoryServer directoryServer = inMemoryDirectoryServer(config, entry); + return new EmbeddedLdapServer(directoryServer); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + static String leftMostElement(String partitionSuffix) { + try { + List rdns = new LdapName(partitionSuffix).getRdns(); + return CollectionUtils.lastElement(rdns).getValue().toString(); + } + catch (InvalidNameException ex) { + throw new RuntimeException(ex); + } + } + + private static InMemoryDirectoryServerConfig inMemoryDirectoryServerConfig(String partitionSuffix, int port) + throws LDAPException { + InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(partitionSuffix); + config.addAdditionalBindCredentials("uid=admin,ou=system", "secret"); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", port)); + config.setEnforceSingleStructuralObjectClass(false); + config.setEnforceAttributeSyntaxCompliance(true); + return config; + } + + private static Entry ldapEntry(String defaultPartitionName, String defaultPartitionSuffix) + throws LDAPException { + Entry entry = new Entry(new DN(defaultPartitionSuffix)); + entry.addAttribute("objectClass", "top", "domain", "extensibleObject"); + entry.addAttribute("dc", defaultPartitionName); + return entry; + } + + private static InMemoryDirectoryServer inMemoryDirectoryServer(InMemoryDirectoryServerConfig config, + Entry entry) throws LDAPException { + InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config); + directoryServer.add(entry); + return directoryServer; + } + + } + } diff --git a/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java b/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java index 360a68679..1db350956 100644 --- a/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java +++ b/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java @@ -48,7 +48,13 @@ public void setPort(int port) { @Override protected EmbeddedLdapServer createInstance() throws Exception { - return EmbeddedLdapServer.newEmbeddedServer(this.partitionName, this.partitionSuffix, this.port); + EmbeddedLdapServer server = EmbeddedLdapServer.withPartitionSuffix(this.partitionSuffix) + .partitionName(this.partitionName) + .port(this.port) + .build(); + + server.start(); + return server; } @Override diff --git a/test-support/src/main/java/org/springframework/ldap/test/unboundid/LdapTestUtils.java b/test-support/src/main/java/org/springframework/ldap/test/unboundid/LdapTestUtils.java index 9ec82ed37..1da4aa7a0 100644 --- a/test-support/src/main/java/org/springframework/ldap/test/unboundid/LdapTestUtils.java +++ b/test-support/src/main/java/org/springframework/ldap/test/unboundid/LdapTestUtils.java @@ -78,7 +78,12 @@ public static void startEmbeddedServer(int port, String defaultPartitionSuffix, } try { - embeddedServer = EmbeddedLdapServer.newEmbeddedServer(defaultPartitionName, defaultPartitionSuffix, port); + embeddedServer = EmbeddedLdapServer.withPartitionSuffix(defaultPartitionSuffix) + .partitionName(defaultPartitionName) + .port(port) + .build(); + + embeddedServer.start(); } catch (Exception ex) { throw new UncategorizedLdapException("Failed to start embedded server", ex); diff --git a/test-support/src/test/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerTests.java b/test-support/src/test/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerTests.java index 22bc987cf..1e2e5dfda 100644 --- a/test-support/src/test/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerTests.java +++ b/test-support/src/test/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerTests.java @@ -19,84 +19,147 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; +import org.junit.Before; import org.junit.Test; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.ldap.query.LdapQueryBuilder; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; public class EmbeddedLdapServerTests { + private int port; + + @Before + public void setUp() throws IOException { + this.port = getFreePort(); + } + @Test public void shouldStartAndCloseServer() throws Exception { - int port = getFreePort(); - assertThat(isPortOpen(port)).isFalse(); + assertPortIsFree(this.port); - EmbeddedLdapServer server = EmbeddedLdapServer.newEmbeddedServer("jayway", "dc=jayway,dc=se", port); - assertThat(isPortOpen(port)).isTrue(); + EmbeddedLdapServer server = EmbeddedLdapServer.newEmbeddedServer("jayway", "dc=jayway,dc=se", this.port); + assertPortIsUsed(this.port); server.close(); - assertThat(isPortOpen(port)).isFalse(); + assertPortIsFree(this.port); } @Test public void shouldStartAndAutoCloseServer() throws Exception { - int port = getFreePort(); - assertThat(isPortOpen(port)).isFalse(); + assertPortIsFree(this.port); - try (EmbeddedLdapServer ignored = EmbeddedLdapServer.newEmbeddedServer("jayway", "dc=jayway,dc=se", port)) { - assertThat(isPortOpen(port)).isTrue(); + try (EmbeddedLdapServer ignored = EmbeddedLdapServer.newEmbeddedServer("jayway", "dc=jayway,dc=se", + this.port)) { + assertPortIsUsed(this.port); } - assertThat(isPortOpen(port)).isFalse(); + assertPortIsFree(this.port); } @Test public void shouldStartAndCloseServerViaLdapTestUtils() throws Exception { - int port = getFreePort(); - assertThat(isPortOpen(port)).isFalse(); + assertPortIsFree(this.port); - LdapTestUtils.startEmbeddedServer(port, "dc=jayway,dc=se", "jayway"); - assertThat(isPortOpen(port)).isTrue(); + LdapTestUtils.startEmbeddedServer(this.port, "dc=jayway,dc=se", "jayway"); + assertPortIsUsed(this.port); LdapTestUtils.shutdownEmbeddedServer(); - assertThat(isPortOpen(port)).isFalse(); + assertPortIsFree(this.port); } @Test public void startWhenNewEmbeddedServerThenException() throws Exception { - int port = getFreePort(); - EmbeddedLdapServer server = EmbeddedLdapServer.newEmbeddedServer("jayway", "dc=jayway,dc=se", port); + EmbeddedLdapServer server = EmbeddedLdapServer.newEmbeddedServer("jayway", "dc=jayway,dc=se", this.port); assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(server::start); } @Test public void startWhenUnstartedThenWorks() throws Exception { - int port = getFreePort(); InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=jayway,dc=se"); - config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", port)); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.port)); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); try (EmbeddedLdapServer server = new EmbeddedLdapServer(ds)) { server.start(); - assertThat(isPortOpen(port)).isTrue(); + assertPortIsUsed(this.port); } } @Test public void startWhenAlreadyStartedThenFails() throws Exception { - int port = getFreePort(); InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=jayway,dc=se"); - config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", port)); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.port)); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); try (EmbeddedLdapServer server = new EmbeddedLdapServer(ds)) { server.start(); - assertThat(isPortOpen(port)).isTrue(); + assertPortIsUsed(this.port); assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(server::start); } } + @Test + public void shouldBuildButNotStartTheServer() { + EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se").port(this.port).build(); + assertPortIsFree(this.port); + } + + @Test + public void shouldBuildTheServerWithCustomPort() { + EmbeddedLdapServer.Builder serverBuilder = EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se") + .port(this.port); + + try (EmbeddedLdapServer server = serverBuilder.build()) { + server.start(); + assertPortIsUsed(this.port); + } + assertPortIsFree(this.port); + } + + @Test + public void shouldBuildLdapServerAndApplyCustomConfiguration() throws IOException { + String tempLogFile = Files.createTempFile("ldap-log-", ".txt").toAbsolutePath().toString(); + + EmbeddedLdapServer.Builder serverBuilder = EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se") + .port(this.port) + .configurationCustomizer((config) -> config.setCodeLogDetails(tempLogFile, true)); + + try (EmbeddedLdapServer server = serverBuilder.build()) { + server.start(); + + ldapTemplate("dc=jayway,dc=se", this.port) + .search(LdapQueryBuilder.query().where("objectclass").is("person"), new AttributesMapper<>() { + public String mapFromAttributes(Attributes attrs) throws NamingException { + return (String) attrs.get("cn").get(); + } + }); + } + + assertThat(Path.of(tempLogFile)) + .as("Applying the custom configuration should create a log file and populate it with the request") + .isNotEmptyFile(); + } + + static void assertPortIsFree(int port) { + assertThat(isPortOpen(port)).isFalse(); + } + + static void assertPortIsUsed(int port) { + assertThat(isPortOpen(port)).isTrue(); + } + static boolean isPortOpen(int port) { try (Socket ignored = new Socket("localhost", port)) { return true; @@ -112,4 +175,14 @@ static int getFreePort() throws IOException { } } + static LdapTemplate ldapTemplate(String base, int port) { + LdapContextSource ctx = new LdapContextSource(); + ctx.setBase(base); + ctx.setUrl("ldap://127.0.0.1:" + port); + ctx.setUserDn("uid=admin,ou=system"); + ctx.setPassword("secret"); + ctx.afterPropertiesSet(); + return new LdapTemplate(ctx); + } + }