/******************************************************************************* * ADSync4J (https://github.com/zagyi/adsync4j) * * Copyright (c) 2013 Balazs Zagyvai * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Balazs Zagyvai ***************************************************************************** */ package org.adsync4j.testutils.ldap; import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import com.google.common.io.Files; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.schema.Schema; import com.unboundid.ldif.LDIFException; import com.unboundid.ldif.LDIFReader; import com.unboundid.util.LDAPSDKException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.concurrent.NotThreadSafe; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Throwables.propagate; import static java.util.Objects.requireNonNull; /** * Utility class that makes it easy to start an embedded LDAP server. It's basically a lightweight wrapper around UnboundID's * {@link com.unboundid.ldap.listener.InMemoryDirectoryServer}. */ @NotThreadSafe public class EmbeddedUnboundIDLdapServer { private final static Logger LOG = LoggerFactory.getLogger(EmbeddedUnboundIDLdapServer.class); private final static boolean CLOSE_EXISTING_CONNECTIONS_ON_SHUTDOWN = true; private final static boolean CLEAR_BEFORE_LDIF_IMPORT = false; @Nullable private Integer _port; private List<String> _rootDNs = new ArrayList<>(); private List<SchemaFileSupplier> _schemas = new ArrayList<>(); private List<InputStream> _ldifs = new ArrayList<>(); private Map<String, String> _bindCredentials = new HashMap<>(); private boolean _includeStandardSchema = false; private boolean _initialized; private InMemoryDirectoryServer _server; @PostConstruct public EmbeddedUnboundIDLdapServer init() { assertUninitialized(); try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig( _rootDNs.toArray(new String[_rootDNs.size()])); initSchema(config); initPort(config); initBindCredentials(config); _server = createInMemoryDirectoryServer(config); _server.startListening(); loadLdifs(_server); LOG.debug("LDAP Server is up and listening on: {}", getAddress()); _initialized = true; } catch (LDAPSDKException | IOException e) { throw propagate(e); } return this; } protected InMemoryDirectoryServer createInMemoryDirectoryServer(InMemoryDirectoryServerConfig config) throws LDAPException { return new InMemoryDirectoryServer(config); } @PreDestroy public void shutDown() { if (_initialized) { _server.shutDown(CLOSE_EXISTING_CONNECTIONS_ON_SHUTDOWN); } else { LOG.warn("shutDown() called on an uninitialized instance."); } } public LDAPConnection getConnection() throws LDAPException { return _server.getConnection(); } private void initSchema(InMemoryDirectoryServerConfig config) throws LDAPSDKException, IOException { if (_schemas.size() > 0) { config.setSchema(Schema.mergeSchemas(getSchemasInArray())); } } private Schema[] getSchemasInArray() throws LDAPSDKException, IOException { Schema[] schemaArray; int i = 0; if (_includeStandardSchema) { schemaArray = new Schema[_schemas.size() + 1]; schemaArray[i++] = Schema.getDefaultStandardSchema(); } else { schemaArray = new Schema[_schemas.size()]; } for (SchemaFileSupplier schemaFileSupplier : _schemas) { File schemaFile = schemaFileSupplier.getFile(); try { Schema schema = Schema.getSchema(schemaFile); schemaArray[i++] = schema; if (schemaFileSupplier.isBackedByTempFile()) { schemaFile.deleteOnExit(); } } catch (LDIFException e) { throw new RuntimeException("Could not load schema file: " + schemaFile.getAbsolutePath(), e); } } return schemaArray; } private void initPort(InMemoryDirectoryServerConfig config) { if (_port != null) { try { InMemoryListenerConfig listenerConfig = new InMemoryListenerConfig("default listener", null, _port, null, null, null); config.setListenerConfigs(listenerConfig); LOG.debug("Successfully configured listener on port: {}", _port); } catch (LDAPException e) { throw propagate(e); } } } private void initBindCredentials(InMemoryDirectoryServerConfig config) { for (Map.Entry<String, String> bindCredentialEntry : _bindCredentials.entrySet()) { String user = bindCredentialEntry.getKey(); String pwd = bindCredentialEntry.getValue(); try { config.addAdditionalBindCredentials(user, pwd); } catch (LDAPException e) { throw propagate(e); } } } private void loadLdifs(InMemoryDirectoryServer ds) { try { for (InputStream ldif : _ldifs) { LDIFReader ldifReader = new LDIFReader(ldif); ds.importFromLDIF(CLEAR_BEFORE_LDIF_IMPORT, ldifReader); } } catch (LDAPException e) { throw propagate(e); } } private int obtainPort() { int port = _server.getListenPort(); boolean isListenerActive = port != -1; checkState(isListenerActive, "Listener has not yet been actived."); _port = port; return port; } private static class SchemaFileSupplier { private final File _file; private final boolean _isBackedByTempFile; public SchemaFileSupplier(File file) { _file = file; _isBackedByTempFile = false; } public SchemaFileSupplier(InputStream inputStream) { _file = dumpStreamToTempFile(inputStream); _isBackedByTempFile = true; } public File getFile() { return _file; } private boolean isBackedByTempFile() { return _isBackedByTempFile; } private static File dumpStreamToTempFile(InputStream inputStream) { try { File tmpDir = new File(System.getProperty("java.io.tmpdir")); File schemaTempFile = File.createTempFile("unboundid-schema", "tmp", tmpDir); ByteStreams.copy(inputStream, Files.newOutputStreamSupplier(schemaTempFile)); schemaTempFile.deleteOnExit(); inputStream.close(); return schemaTempFile; } catch (IOException e) { throw propagate(e); } } } private void assertUninitialized() { checkState(!_initialized, "Instance already initialized."); } //region ############## getters ############## public int getPort() { return _port == null ? obtainPort() : _port; } public String getAddress() { return "ldap://127.0.0.1:" + getPort(); } public List<String> getRootDNs() { return _rootDNs; } public boolean isInitialized() { return _initialized; } //endregion //region ############## setters/adders ############## public EmbeddedUnboundIDLdapServer setPort(int port) { assertUninitialized(); _port = port; return this; } public EmbeddedUnboundIDLdapServer setLdifs(Iterable<InputStream> ldifs) { assertUninitialized(); requireNonNull(ldifs); _ldifs = Lists.newArrayList(ldifs); return this; } public EmbeddedUnboundIDLdapServer addLdif(InputStream ldif) { assertUninitialized(); requireNonNull(ldif); _ldifs.add(ldif); return this; } public EmbeddedUnboundIDLdapServer setSchemaFiles(Iterable<File> schemaFiles) { assertUninitialized(); requireNonNull(schemaFiles); _schemas = new ArrayList<>(); for (File schemaFile : schemaFiles) { _schemas.add(new SchemaFileSupplier(schemaFile)); } return this; } public EmbeddedUnboundIDLdapServer setSchemaStreams(Iterable<InputStream> schemasStreams) { assertUninitialized(); requireNonNull(schemasStreams); _schemas = new ArrayList<>(); for (InputStream schemasStream : schemasStreams) { _schemas.add(new SchemaFileSupplier(schemasStream)); } return this; } public EmbeddedUnboundIDLdapServer addSchema(InputStream schema) { assertUninitialized(); requireNonNull(schema); _schemas.add(new SchemaFileSupplier(schema)); return this; } public EmbeddedUnboundIDLdapServer addSchema(File schema) { assertUninitialized(); requireNonNull(schema); _schemas.add(new SchemaFileSupplier(schema)); return this; } public EmbeddedUnboundIDLdapServer setBindCredentials(Map<String, String> bindCredentials) { assertUninitialized(); requireNonNull(bindCredentials); _bindCredentials = bindCredentials; return this; } public EmbeddedUnboundIDLdapServer addBindCredentials(String bindUser, String bindPassword) { assertUninitialized(); requireNonNull(bindUser); requireNonNull(bindPassword); _bindCredentials.put(bindUser, bindPassword); return this; } public EmbeddedUnboundIDLdapServer setRootDN(String rootDN) { assertUninitialized(); requireNonNull(rootDN); _rootDNs = Lists.newArrayList(rootDN); return this; } public EmbeddedUnboundIDLdapServer setRootDNs(String firstRootDN, String... restRootDNs) { assertUninitialized(); requireNonNull(firstRootDN); requireNonNull(restRootDNs); _rootDNs = Lists.newArrayList(Lists.asList(firstRootDN, restRootDNs)); return this; } public EmbeddedUnboundIDLdapServer setRootDNs(List<String> rootDNs) { assertUninitialized(); requireNonNull(rootDNs); _rootDNs = rootDNs; return this; } public EmbeddedUnboundIDLdapServer addRootDN(String rootDN) { assertUninitialized(); requireNonNull(rootDN); _rootDNs.add(rootDN); return this; } public EmbeddedUnboundIDLdapServer setIncludeStandardSchema(boolean includeStandardSchema) { assertUninitialized(); _includeStandardSchema = includeStandardSchema; return this; } public EmbeddedUnboundIDLdapServer includeStandardSchema() { assertUninitialized(); _includeStandardSchema = true; return this; } //endregion }