Skip to content

Commit 8d44ee3

Browse files
etrandafir93jzheaux
authored andcommitted
EmbeddedLdapServer builder
Closes #938 Signed-off-by: Emanuel Trandafir <[email protected]> Signed-off-by: etrandafir93 <[email protected]>
1 parent f154e64 commit 8d44ee3

File tree

5 files changed

+229
-26
lines changed

5 files changed

+229
-26
lines changed

modules/ROOT/pages/testing.adoc

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,17 +182,19 @@ testCompile "com.unboundid:unboundid-ldapsdk:3.1.1"
182182
The following bean definition creates an embedded LDAP server:
183183

184184
====
185-
[source,xml]
185+
[source,java]
186186
----
187-
<bean id="embeddedLdapServer" class="org.springframework.ldap.test.unboundid.EmbeddedLdapServerFactoryBean">
188-
<property name="partitionName" value="example"/>
189-
<property name="partitionSuffix" value="dc=261consulting,dc=com" />
190-
<property name="port" value="9321" />
191-
</bean>
187+
@Bean
188+
EmbeddedLdapServer embeddedLdapServer() {
189+
return EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se")
190+
.partitionName("jayway")
191+
.port(18881)
192+
.configurationCustomizer((config) -> config.setCodeLogDetails(tempLogFile, true))
193+
.build();
194+
}
192195
----
193-
====
194196
195-
`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:
197+
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:
196198
197199
====
198200
[source,xml]

test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java

Lines changed: 142 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616

1717
package org.springframework.ldap.test.unboundid;
1818

19+
import java.util.List;
20+
import java.util.function.Consumer;
21+
22+
import javax.naming.InvalidNameException;
23+
import javax.naming.ldap.LdapName;
24+
import javax.naming.ldap.Rdn;
25+
1926
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
2027
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
2128
import com.unboundid.ldap.listener.InMemoryListenerConfig;
@@ -24,6 +31,7 @@
2431
import com.unboundid.ldap.sdk.LDAPException;
2532

2633
import org.springframework.util.Assert;
34+
import org.springframework.util.CollectionUtils;
2735

2836
/**
2937
* Helper class for embedded Unboundid ldap server.
@@ -45,27 +53,30 @@ public EmbeddedLdapServer(InMemoryDirectoryServer directoryServer) {
4553
this.directoryServer = directoryServer;
4654
}
4755

56+
/**
57+
* Creates a new {@link Builder} with a given partition suffix.
58+
*
59+
* @since 3.3
60+
*/
61+
public static Builder withPartitionSuffix(String partitionSuffix) {
62+
return new Builder(partitionSuffix);
63+
}
64+
4865
/**
4966
* Creates and starts new embedded LDAP server.
67+
* @deprecated Use the builder pattern exposed via
68+
* {@link #withPartitionSuffix(String)} instead.
5069
*/
70+
@Deprecated(since = "3.3")
5171
public static EmbeddedLdapServer newEmbeddedServer(String defaultPartitionName, String defaultPartitionSuffix,
52-
int port) throws Exception {
53-
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(defaultPartitionSuffix);
54-
config.addAdditionalBindCredentials("uid=admin,ou=system", "secret");
55-
56-
config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", port));
57-
58-
config.setEnforceSingleStructuralObjectClass(false);
59-
config.setEnforceAttributeSyntaxCompliance(true);
72+
int port) {
73+
EmbeddedLdapServer server = EmbeddedLdapServer.withPartitionSuffix(defaultPartitionSuffix)
74+
.partitionName(defaultPartitionName)
75+
.port(port)
76+
.build();
6077

61-
Entry entry = new Entry(new DN(defaultPartitionSuffix));
62-
entry.addAttribute("objectClass", "top", "domain", "extensibleObject");
63-
entry.addAttribute("dc", defaultPartitionName);
64-
65-
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
66-
directoryServer.add(entry);
67-
directoryServer.startListening();
68-
return new EmbeddedLdapServer(directoryServer);
78+
server.start();
79+
return server;
6980
}
7081

7182
/**
@@ -102,4 +113,119 @@ public void shutdown() {
102113
this.directoryServer.shutDown(true);
103114
}
104115

116+
/**
117+
* Helper class for embedded Unboundid ldap server.
118+
*
119+
* @author Emanuel Trandafir
120+
* @since 3.3
121+
*/
122+
public static final class Builder {
123+
124+
private final String partitionSuffix;
125+
126+
private String partitionName;
127+
128+
private int port = 0;
129+
130+
private Consumer<InMemoryDirectoryServerConfig> configurationCustomizer = (__) -> {
131+
};
132+
133+
private Builder(String partitionSuffix) {
134+
this.partitionSuffix = partitionSuffix;
135+
this.partitionName = leftMostElement(partitionSuffix);
136+
}
137+
138+
/**
139+
* Sets the port for the embedded LDAP server.
140+
* @param port the port for the embedded LDAP server. Defaults to 0 in which case
141+
* the server should automatically choose an available port.
142+
* @return this {@link Builder} instance.
143+
*/
144+
public Builder port(int port) {
145+
this.port = port;
146+
return this;
147+
}
148+
149+
/**
150+
* Sets a customizer for the {@link InMemoryDirectoryServerConfig}.
151+
* @param configurationCustomizer a {@link Consumer} function that will be applied
152+
* to the {@link InMemoryDirectoryServerConfig} before creating the
153+
* {@link InMemoryDirectoryServer}. The default values, it a Consumer function
154+
* that does nothing: (config) -> {}
155+
* @return this {@link Builder} instance.
156+
*/
157+
public Builder configurationCustomizer(Consumer<InMemoryDirectoryServerConfig> configurationCustomizer) {
158+
this.configurationCustomizer = configurationCustomizer;
159+
return this;
160+
}
161+
162+
/**
163+
* Sets the partition name for the embedded LDAP server.
164+
* @param partitionName the partition name for the embedded LDAP server. Defaults
165+
* to the left most element of the partition suffix.
166+
* @return this {@link Builder} instance.
167+
*/
168+
public Builder partitionName(String partitionName) {
169+
this.partitionName = partitionName;
170+
return this;
171+
}
172+
173+
/**
174+
* Builds and returns a {@link EmbeddedLdapServer}.
175+
* <p>
176+
* In order to start the server, you should call
177+
* {@link EmbeddedLdapServer#start()}.
178+
* @return a new {@link EmbeddedLdapServer}.
179+
*/
180+
public EmbeddedLdapServer build() {
181+
try {
182+
InMemoryDirectoryServerConfig config = inMemoryDirectoryServerConfig(this.partitionSuffix, this.port);
183+
this.configurationCustomizer.accept(config);
184+
185+
Entry entry = ldapEntry(this.partitionName, this.partitionSuffix);
186+
InMemoryDirectoryServer directoryServer = inMemoryDirectoryServer(config, entry);
187+
return new EmbeddedLdapServer(directoryServer);
188+
}
189+
catch (Exception ex) {
190+
throw new RuntimeException(ex);
191+
}
192+
}
193+
194+
static String leftMostElement(String partitionSuffix) {
195+
try {
196+
List<Rdn> rdns = new LdapName(partitionSuffix).getRdns();
197+
return CollectionUtils.lastElement(rdns).getValue().toString();
198+
}
199+
catch (InvalidNameException ex) {
200+
throw new RuntimeException(ex);
201+
}
202+
}
203+
204+
private static InMemoryDirectoryServerConfig inMemoryDirectoryServerConfig(String partitionSuffix, int port)
205+
throws LDAPException {
206+
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(partitionSuffix);
207+
config.addAdditionalBindCredentials("uid=admin,ou=system", "secret");
208+
config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", port));
209+
config.setEnforceSingleStructuralObjectClass(false);
210+
config.setEnforceAttributeSyntaxCompliance(true);
211+
return config;
212+
}
213+
214+
private static Entry ldapEntry(String defaultPartitionName, String defaultPartitionSuffix)
215+
throws LDAPException {
216+
Entry entry = new Entry(new DN(defaultPartitionSuffix));
217+
entry.addAttribute("objectClass", "top", "domain", "extensibleObject");
218+
entry.addAttribute("dc", defaultPartitionName);
219+
return entry;
220+
}
221+
222+
private static InMemoryDirectoryServer inMemoryDirectoryServer(InMemoryDirectoryServerConfig config,
223+
Entry entry) throws LDAPException {
224+
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
225+
directoryServer.add(entry);
226+
return directoryServer;
227+
}
228+
229+
}
230+
105231
}

test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ public void setPort(int port) {
4848

4949
@Override
5050
protected EmbeddedLdapServer createInstance() throws Exception {
51-
return EmbeddedLdapServer.newEmbeddedServer(this.partitionName, this.partitionSuffix, this.port);
51+
EmbeddedLdapServer server = EmbeddedLdapServer.withPartitionSuffix(this.partitionSuffix)
52+
.partitionName(this.partitionName)
53+
.port(this.port)
54+
.build();
55+
56+
server.start();
57+
return server;
5258
}
5359

5460
@Override

test-support/src/main/java/org/springframework/ldap/test/unboundid/LdapTestUtils.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,12 @@ public static void startEmbeddedServer(int port, String defaultPartitionSuffix,
7878
}
7979

8080
try {
81-
embeddedServer = EmbeddedLdapServer.newEmbeddedServer(defaultPartitionName, defaultPartitionSuffix, port);
81+
embeddedServer = EmbeddedLdapServer.withPartitionSuffix(defaultPartitionSuffix)
82+
.partitionName(defaultPartitionName)
83+
.port(port)
84+
.build();
85+
86+
embeddedServer.start();
8287
}
8388
catch (Exception ex) {
8489
throw new UncategorizedLdapException("Failed to start embedded server", ex);

test-support/src/test/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerTests.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,22 @@
1919
import java.io.IOException;
2020
import java.net.ServerSocket;
2121
import java.net.Socket;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
25+
import javax.naming.NamingException;
26+
import javax.naming.directory.Attributes;
2227

2328
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
2429
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
2530
import com.unboundid.ldap.listener.InMemoryListenerConfig;
2631
import org.junit.Test;
2732

33+
import org.springframework.ldap.core.AttributesMapper;
34+
import org.springframework.ldap.core.LdapTemplate;
35+
import org.springframework.ldap.core.support.LdapContextSource;
36+
import org.springframework.ldap.query.LdapQueryBuilder;
37+
2838
import static org.assertj.core.api.Assertions.assertThat;
2939
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
3040

@@ -97,6 +107,50 @@ public void startWhenAlreadyStartedThenFails() throws Exception {
97107
}
98108
}
99109

110+
@Test
111+
public void shouldBuildButNotStartTheServer() throws IOException {
112+
int port = getFreePort();
113+
EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se").port(port).build();
114+
assertThat(isPortOpen(port)).isFalse();
115+
}
116+
117+
@Test
118+
public void shouldBuildTheServerWithCustomPort() throws IOException {
119+
int port = getFreePort();
120+
EmbeddedLdapServer.Builder serverBuilder = EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se").port(port);
121+
122+
try (EmbeddedLdapServer server = serverBuilder.build()) {
123+
server.start();
124+
assertThat(isPortOpen(port)).isTrue();
125+
}
126+
assertThat(isPortOpen(port)).isFalse();
127+
}
128+
129+
@Test
130+
public void shouldBuildLdapServerAndApplyCustomConfiguration() throws IOException {
131+
int port = getFreePort();
132+
String tempLogFile = Files.createTempFile("ldap-log-", ".txt").toAbsolutePath().toString();
133+
134+
EmbeddedLdapServer.Builder serverBuilder = EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se")
135+
.port(port)
136+
.configurationCustomizer((config) -> config.setCodeLogDetails(tempLogFile, true));
137+
138+
try (EmbeddedLdapServer server = serverBuilder.build()) {
139+
server.start();
140+
141+
ldapTemplate("dc=jayway,dc=se", port).search(LdapQueryBuilder.query().where("objectclass").is("person"),
142+
new AttributesMapper<>() {
143+
public String mapFromAttributes(Attributes attrs) throws NamingException {
144+
return (String) attrs.get("cn").get();
145+
}
146+
});
147+
}
148+
149+
assertThat(Path.of(tempLogFile))
150+
.as("Applying the custom configuration should create a log file and populate it with the request")
151+
.isNotEmptyFile();
152+
}
153+
100154
static boolean isPortOpen(int port) {
101155
try (Socket ignored = new Socket("localhost", port)) {
102156
return true;
@@ -112,4 +166,14 @@ static int getFreePort() throws IOException {
112166
}
113167
}
114168

169+
static LdapTemplate ldapTemplate(String base, int port) {
170+
LdapContextSource ctx = new LdapContextSource();
171+
ctx.setBase(base);
172+
ctx.setUrl("ldap://127.0.0.1:" + port);
173+
ctx.setUserDn("uid=admin,ou=system");
174+
ctx.setPassword("secret");
175+
ctx.afterPropertiesSet();
176+
return new LdapTemplate(ctx);
177+
}
178+
115179
}

0 commit comments

Comments
 (0)