/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014-2015 ForgeRock AS.
*/
package org.forgerock.openam.sm.datalayer.providers;
import static java.util.concurrent.TimeUnit.*;
import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.forgerock.openam.cts.api.CoreTokenConstants;
import org.forgerock.openam.ldap.LDAPUtils;
import org.forgerock.openam.sm.ConnectionConfig;
import org.forgerock.openam.sm.ConnectionConfigFactory;
import org.forgerock.openam.sm.datalayer.api.ConnectionFactory;
import org.forgerock.openam.sm.datalayer.api.ConnectionType;
import org.forgerock.openam.sm.datalayer.api.DataLayerConstants;
import org.forgerock.openam.sm.datalayer.api.DataLayerException;
import org.forgerock.openam.sm.datalayer.api.LdapOperationFailedException;
import org.forgerock.openam.sm.datalayer.api.StoreMode;
import org.forgerock.openam.sm.datalayer.utils.TimeoutConfig;
import org.forgerock.openam.sm.exceptions.InvalidConfigurationException;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.util.Function;
import org.forgerock.util.Options;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.time.Duration;
import com.sun.identity.shared.debug.Debug;
/**
* Responsible for generating ConnectionFactory instances. The instances generated are tailored to
* the {@link ConnectionType} required by the caller.
*
* This factory provider is aware of two main use cases for the service management layer (also known
* as Data Layer).
*
* Default - Uses the service management configuration for connections. This will connect to the
* defined LDAP server, whether that is embedded or external.
*
* External - Uses CTS Configuration for CTS connections which are pointed towards an external
* LDAP server. Uses service management configuration for {@link StoreMode#DEFAULT} connections.
*/
@Singleton
public class LdapConnectionFactoryProvider implements ConnectionFactoryProvider {
// Injected
private final TimeoutConfig timeoutConfig;
private final ConnectionConfigFactory configFactory;
private final Debug debug;
private final ConnectionType connectionType;
/**
* Generates an instance and registers the shutdown listener.
*
* @param connectionConfigFactory Required to resolve configuration parameters, non null.
* @param timeoutConfig Timeout Configuration, Non null.
* @param debug Required for debugging.
*/
@Inject
public LdapConnectionFactoryProvider(ConnectionType connectionType,
ConnectionConfigFactory connectionConfigFactory,
TimeoutConfig timeoutConfig,
@Named(DataLayerConstants.DATA_LAYER_DEBUG) Debug debug) {
this.configFactory = connectionConfigFactory;
this.timeoutConfig = timeoutConfig;
this.debug = debug;
this.connectionType = connectionType;
}
/**
* Creates instances of ConnectionFactory which are aware of the need to share the
* DataLayer and CTS connections in the same connection pool.
*
* @return {@inheritDoc}
*/
public ConnectionFactory createFactory() throws InvalidConfigurationException {
ConnectionConfig config = configFactory.getConfig(connectionType);
int timeout = timeoutConfig.getTimeout(connectionType);
Options options = Options.defaultOptions()
.set(REQUEST_TIMEOUT, new Duration((long) timeout, TimeUnit.SECONDS));
debug("Creating Embedded Factory:\nURL: {0}\nMax Connections: {1}\nHeartbeat: {2}\nOperation Timeout: {3}",
config.getLDAPURLs(),
config.getMaxConnections(),
config.getLdapHeartbeat(),
timeout);
final org.forgerock.opendj.ldap.ConnectionFactory ldapConnectionFactory = LDAPUtils.newFailoverConnectionPool(
config.getLDAPURLs(),
config.getBindDN(),
config.getBindPassword(),
config.getMaxConnections(),
config.getLdapHeartbeat(),
SECONDS.toString(),
options);
return new LdapConnectionFactory(ldapConnectionFactory);
}
private void debug(String format, Object... args) {
if (debug.messageEnabled()) {
debug.message(MessageFormat.format(CoreTokenConstants.DEBUG_ASYNC_HEADER + format, args));
}
}
private static class LdapConnectionFactory implements ConnectionFactory {
private final org.forgerock.opendj.ldap.ConnectionFactory ldapConnectionFactory;
private static final Function IDENTITY_FUNCTION =
new Function() {
@Override
public Connection apply(Connection value) throws DataLayerException {
return value;
}
};
private static final Function EXCEPTION_FUNCTION =
new Function() {
@Override
public Connection apply(LdapException value) throws DataLayerException {
throw new LdapOperationFailedException(value.getResult());
}
};
public LdapConnectionFactory(org.forgerock.opendj.ldap.ConnectionFactory ldapConnectionFactory) {
this.ldapConnectionFactory = ldapConnectionFactory;
}
@Override
public Promise createAsync() {
final Promise promise = ldapConnectionFactory.getConnectionAsync();
return promise.then(IDENTITY_FUNCTION, EXCEPTION_FUNCTION);
}
@Override
public Connection create() throws DataLayerException {
try {
return ldapConnectionFactory.getConnection();
} catch (LdapException e) {
throw new LdapOperationFailedException(e.getResult());
}
}
@Override
public void close() {
ldapConnectionFactory.close();
}
@Override
public boolean isValid(Connection connection) {
return connection != null && !connection.isClosed() && connection.isValid();
}
}
}