DirectoryServerUtils.java
/*
* Copyright 2013-2025 Brian Thomas Matthews
*
* Licensed 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 com.buralotech.oss.ldapunit;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.ldif.LDIFChangeRecord;
import com.unboundid.ldif.LDIFException;
import com.unboundid.ldif.LDIFReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* Helper functions to start the in-memory LDAP directory server, load LDAP directory entries from an LDIF files and
* shut down the in-memory LDAP directory server.
*
* @author <a href="mailto:bmatthews68@gmail.com">Brian Matthews</a>
* @since 1.0.1
*/
final class DirectoryServerUtils {
/**
* Hidden constructor.
*/
private DirectoryServerUtils() {
}
/**
* Start the directory server using the configuration specified by the {@link DirectoryServerConfiguration}
* annotation.
*
* @param annotation The configuration.
* @return The {@link InMemoryDirectoryServer} object.
* @throws LDIFException If there was an error in the LDIF data.
* @throws LDAPException If there was a problem configuring or starting the embedded LDAP directory server.
* @throws IOException If there was a problem reading the LDIF data.
*/
static InMemoryDirectoryServer startServer(final DirectoryServerConfiguration annotation)
throws LDIFException, LDAPException, IOException {
return startServer(
annotation.port(),
annotation.baseDN(),
annotation.baseObjectClasses(),
annotation.baseAttributes(),
annotation.authDN(),
annotation.authPassword(),
annotation.ldifFiles(),
annotation.schemaFiles());
}
/**
* Create and configure an embedded LDAP directory server, load seed data and start the server.
*
* @param port The TCP port that the LDAP directory server will be configured to listen on.
* @param baseDN The DN that will be configured as the root of the LDAP directory.
* @param baseObjectClasses The object classes to use when creating the base DN.
* @param baseAttributes The attributes to set on the base DN.
* @param authDN The DN that will be configured as the administrator account identifier.
* @param authPassword The password that will be configured as the authentication credentials for
* the administrator account.
* @param ldifFiles The LDIF resources or files from which LDIF records will be loaded.
* @param schemaFiles The files from which to load custom schemas.
* @return The {@link InMemoryDirectoryServer} object.
* @throws LDIFException If there was an error in the LDIF data.
* @throws LDAPException If there was a problem configuring or starting the embedded LDAP directory server.
* @throws IOException If there was a problem reading the LDIF data.
*/
static InMemoryDirectoryServer startServer(final int port,
final String baseDN,
final String[] baseObjectClasses,
final String[] baseAttributes,
final String authDN,
final String authPassword,
final String[] ldifFiles,
final String[] schemaFiles)
throws LDIFException, LDAPException, IOException {
final InMemoryListenerConfig listenerConfig = InMemoryListenerConfig.createLDAPConfig("default", port);
final InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new DN(baseDN));
loadSchema(config, schemaFiles);
config.setListenerConfigs(listenerConfig);
config.addAdditionalBindCredentials(authDN, authPassword);
final InMemoryDirectoryServer server = new InMemoryDirectoryServer(config);
final int n = baseAttributes.length;
final Attribute[] attributes = new Attribute[1 + n];
for (int i = 0; i < n; i++) {
final int j = baseAttributes[i].indexOf('=');
final String name;
final String value;
if (j == -1) {
name = baseAttributes[i];
value = "";
} else {
name = baseAttributes[i].substring(0, j);
value = baseAttributes[i].substring(j + 1);
}
attributes[i] = new Attribute(name, value);
}
attributes[n] = new Attribute("objectclass", baseObjectClasses);
server .add(new Entry(baseDN, attributes));
server.startListening();
for (final String ldifFile : ldifFiles) {
loadData(server, ldifFile);
}
return server;
}
/**
* Look for path on the classpath or file system and return an {@link InputStream} if found.
*
* @param path The path.
* @return An {@link InputStream} if the path exists on the classpath or file sytem. Otherwise, {@code null}.
*/
private static InputStream getInputStream(final String path) {
if (path == null || path.isEmpty()) {
return null;
}
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final InputStream inputStream = classLoader.getResourceAsStream(path);
if (inputStream == null) {
final File file = new File(path);
if (file.exists()) {
try {
return new FileInputStream(file);
} catch (final FileNotFoundException e) {
return null;
}
} else {
return null;
}
} else {
return inputStream;
}
}
/**
* Load a custom schema.
*
* @param config The directory server configuration.
* @param schemaFiles The schema files.
* @throws LDIFException If there was a problem loading the schema into the LDAP directory.
* @throws LDAPException If there was a problem loading the schema into the LDAP directory.
* @throws IOException If there was a problem reading the schema from the file.
*/
private static void loadSchema(final InMemoryDirectoryServerConfig config,
final String[] schemaFiles)
throws LDIFException, LDAPException, IOException {
if (schemaFiles.length > 0) {
final Schema[] schemas = new Schema[schemaFiles.length];
for (int i = 0; i < schemaFiles.length; i++) {
if ("default".equals(schemaFiles[i])) {
schemas[i] = Schema.getDefaultStandardSchema();
} else {
try (InputStream inputStream = getInputStream(schemaFiles[i])) {
if (inputStream != null) {
schemas[i] = Schema.getSchema(inputStream);
}
}
}
}
config.setSchema(Schema.mergeSchemas(schemas));
}
}
/**
* Load LDIF records from a file to seed the LDAP directory.
*
* @param server The embedded LDAP directory server.
* @param ldifFile The LDIF resource or file from which LDIF records will be loaded.
* @throws LDIFException If there was an error in the LDIF data.
* @throws LDAPException If there was a problem loading the LDIF records into the LDAP directory.
* @throws IOException If there was a problem reading the LDIF records from the file.
*/
private static void loadData(final InMemoryDirectoryServer server,
final String ldifFile)
throws LDIFException, LDAPException, IOException {
try (InputStream inputStream = getInputStream(ldifFile)) {
if (inputStream != null) {
loadData(server.getConnection(), inputStream);
}
}
}
/**
* Load LDIF records from an input stream to seed the LDAP directory.
*
* @param connection A connection to the LDAP directory.
* @param inputStream TThe input stream from which LDIF records will be loaded.
* @throws LDIFException If there was an error in the LDIF data.
* @throws LDAPException If there was a problem loading the LDIF records into the LDAP directory.
* @throws IOException If there was a problem reading the LDIF records from the file.
*/
private static void loadData(final LDAPConnection connection,
final InputStream inputStream)
throws LDIFException, LDAPException, IOException {
try (LDIFReader reader = new LDIFReader(inputStream)) {
LDIFChangeRecord changeRecord = reader.readChangeRecord(true);
while (changeRecord != null) {
changeRecord.processChange(connection);
changeRecord = reader.readChangeRecord(true);
}
}
}
/**
* Shutdown the embedded LDAP directory server.
*
* @param server The embedded LDAP directory server.
*/
static void stopServer(final InMemoryDirectoryServer server) {
server.shutDown(true);
}
}