| | |
| | | * (the "License"). You may not use this file except in compliance |
| | | * with the License. |
| | | * |
| | | * You can obtain a copy of the license at |
| | | * trunk/opendj3/legal-notices/CDDLv1_0.txt |
| | | * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt |
| | | * or http://forgerock.org/license/CDDLv1.0.html. |
| | | * See the License for the specific language governing permissions |
| | | * and limitations under the License. |
| | | * |
| | | * When distributing Covered Code, include this CDDL HEADER in each |
| | | * file and include the License file at |
| | | * trunk/opendj3/legal-notices/CDDLv1_0.txt. If applicable, |
| | | * add the following below this CDDL HEADER, with the fields enclosed |
| | | * by brackets "[]" replaced with your own identifying information: |
| | | * file and include the License file at legal-notices/CDDLv1_0.txt. |
| | | * If applicable, add the following below this CDDL HEADER, with the |
| | | * fields enclosed by brackets "[]" replaced with your own identifying |
| | | * information: |
| | | * Portions Copyright [yyyy] [name of copyright owner] |
| | | * |
| | | * CDDL HEADER END |
| | |
| | | |
| | | package com.forgerock.opendj.ldap; |
| | | |
| | | |
| | | |
| | | import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult; |
| | | |
| | | import java.io.IOException; |
| | |
| | | |
| | | import javax.net.ssl.SSLEngine; |
| | | |
| | | import org.forgerock.opendj.ldap.*; |
| | | import org.forgerock.opendj.ldap.Connection; |
| | | import org.forgerock.opendj.ldap.ConnectionFactory; |
| | | import org.forgerock.opendj.ldap.ErrorResultException; |
| | | import org.forgerock.opendj.ldap.FutureResult; |
| | | import org.forgerock.opendj.ldap.LDAPOptions; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.ResultHandler; |
| | | import org.forgerock.opendj.ldap.requests.Requests; |
| | | import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | |
| | | import com.forgerock.opendj.util.FutureResultTransformer; |
| | | import com.forgerock.opendj.util.RecursiveFutureResult; |
| | | |
| | | |
| | | |
| | | /** |
| | | * LDAP connection factory implementation. |
| | | */ |
| | | public final class LDAPConnectionFactoryImpl implements ConnectionFactory |
| | | { |
| | | public final class LDAPConnectionFactoryImpl implements ConnectionFactory { |
| | | |
| | | @SuppressWarnings("rawtypes") |
| | | private final class ConnectionCompletionHandler implements |
| | | CompletionHandler<org.glassfish.grizzly.Connection> |
| | | { |
| | | private final FutureResultTransformer<Result, Connection> startTLSFutureResult; |
| | | private final RecursiveFutureResult<LDAPConnection, ExtendedResult> connectionFutureResult; |
| | | private LDAPConnection connection; |
| | | @SuppressWarnings("rawtypes") |
| | | private final class ConnectionCompletionHandler implements |
| | | CompletionHandler<org.glassfish.grizzly.Connection> { |
| | | private final FutureResultTransformer<Result, Connection> startTLSFutureResult; |
| | | private final RecursiveFutureResult<LDAPConnection, ExtendedResult> connectionFutureResult; |
| | | private LDAPConnection connection; |
| | | |
| | | private ConnectionCompletionHandler(final ResultHandler<? super Connection> handler) { |
| | | this.startTLSFutureResult = new FutureResultTransformer<Result, Connection>(handler) { |
| | | |
| | | |
| | | private ConnectionCompletionHandler(final ResultHandler<? super Connection> handler) |
| | | { |
| | | this.startTLSFutureResult = new FutureResultTransformer<Result, Connection>( |
| | | handler) |
| | | { |
| | | |
| | | @Override |
| | | protected ErrorResultException transformErrorResult( |
| | | final ErrorResultException errorResult) |
| | | { |
| | | // Ensure that the connection is closed. |
| | | try |
| | | { |
| | | if (connection != null) |
| | | { |
| | | connection.close(); |
| | | } |
| | | } |
| | | catch (final Exception e) |
| | | { |
| | | // Ignore. |
| | | } |
| | | return errorResult; |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | protected LDAPConnection transformResult(final Result result) |
| | | throws ErrorResultException |
| | | { |
| | | return connection; |
| | | } |
| | | |
| | | }; |
| | | |
| | | this.connectionFutureResult = new RecursiveFutureResult<LDAPConnection, ExtendedResult>( |
| | | startTLSFutureResult) |
| | | { |
| | | |
| | | @Override |
| | | protected FutureResult<? extends ExtendedResult> chainResult( |
| | | final LDAPConnection innerResult, |
| | | final ResultHandler<? super ExtendedResult> handler) |
| | | throws ErrorResultException |
| | | { |
| | | connection = innerResult; |
| | | |
| | | if (options.getSSLContext() != null && options.useStartTLS()) |
| | | { |
| | | // Chain StartTLS extended request. |
| | | final StartTLSExtendedRequest startTLS = Requests |
| | | .newStartTLSExtendedRequest(options.getSSLContext()); |
| | | startTLS.addEnabledCipherSuite(options.getEnabledCipherSuites() |
| | | .toArray(new String[options.getEnabledCipherSuites().size()])); |
| | | startTLS.addEnabledProtocol(options.getEnabledProtocols().toArray( |
| | | new String[options.getEnabledProtocols().size()])); |
| | | return connection.extendedRequestAsync(startTLS, null, handler); |
| | | } |
| | | else if (options.getSSLContext() != null) |
| | | { |
| | | // Install SSL/TLS layer. |
| | | try |
| | | { |
| | | connection.startTLS(options.getSSLContext(), |
| | | options.getEnabledProtocols(), |
| | | options.getEnabledCipherSuites(), |
| | | new EmptyCompletionHandler<SSLEngine>() |
| | | { |
| | | @Override |
| | | public void completed(final SSLEngine result) |
| | | { |
| | | handler.handleResult(null); |
| | | @Override |
| | | protected ErrorResultException transformErrorResult( |
| | | final ErrorResultException errorResult) { |
| | | // Ensure that the connection is closed. |
| | | try { |
| | | if (connection != null) { |
| | | connection.close(); |
| | | } |
| | | } catch (final Exception e) { |
| | | // Ignore. |
| | | } |
| | | return errorResult; |
| | | } |
| | | |
| | | @Override |
| | | protected LDAPConnection transformResult(final Result result) |
| | | throws ErrorResultException { |
| | | return connection; |
| | | } |
| | | |
| | | }; |
| | | |
| | | @Override |
| | | public void failed(final Throwable throwable) |
| | | { |
| | | handler.handleErrorResult(newErrorResult( |
| | | ResultCode.CLIENT_SIDE_CONNECT_ERROR, |
| | | throwable.getMessage(), throwable)); |
| | | } |
| | | }); |
| | | return null; |
| | | } |
| | | catch (final IOException ioe) |
| | | { |
| | | throw newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR, |
| | | ioe.getMessage(), ioe); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | // Plain connection. |
| | | handler.handleResult(null); |
| | | return new CompletedFutureResult<ExtendedResult>( |
| | | (ExtendedResult) null); |
| | | } |
| | | this.connectionFutureResult = |
| | | new RecursiveFutureResult<LDAPConnection, ExtendedResult>(startTLSFutureResult) { |
| | | |
| | | @Override |
| | | protected FutureResult<? extends ExtendedResult> chainResult( |
| | | final LDAPConnection innerResult, |
| | | final ResultHandler<? super ExtendedResult> handler) |
| | | throws ErrorResultException { |
| | | connection = innerResult; |
| | | |
| | | if (options.getSSLContext() != null && options.useStartTLS()) { |
| | | // Chain StartTLS extended request. |
| | | final StartTLSExtendedRequest startTLS = |
| | | Requests.newStartTLSExtendedRequest(options.getSSLContext()); |
| | | startTLS.addEnabledCipherSuite(options |
| | | .getEnabledCipherSuites() |
| | | .toArray( |
| | | new String[options.getEnabledCipherSuites().size()])); |
| | | startTLS.addEnabledProtocol(options.getEnabledProtocols().toArray( |
| | | new String[options.getEnabledProtocols().size()])); |
| | | return connection.extendedRequestAsync(startTLS, null, handler); |
| | | } else if (options.getSSLContext() != null) { |
| | | // Install SSL/TLS layer. |
| | | try { |
| | | connection.startTLS(options.getSSLContext(), options |
| | | .getEnabledProtocols(), options |
| | | .getEnabledCipherSuites(), |
| | | new EmptyCompletionHandler<SSLEngine>() { |
| | | @Override |
| | | public void completed(final SSLEngine result) { |
| | | handler.handleResult(null); |
| | | } |
| | | |
| | | @Override |
| | | public void failed(final Throwable throwable) { |
| | | handler.handleErrorResult(newErrorResult( |
| | | ResultCode.CLIENT_SIDE_CONNECT_ERROR, |
| | | throwable.getMessage(), throwable)); |
| | | } |
| | | }); |
| | | return null; |
| | | } catch (final IOException ioe) { |
| | | throw newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR, ioe |
| | | .getMessage(), ioe); |
| | | } |
| | | } else { |
| | | // Plain connection. |
| | | handler.handleResult(null); |
| | | return new CompletedFutureResult<ExtendedResult>( |
| | | (ExtendedResult) null); |
| | | } |
| | | } |
| | | |
| | | }; |
| | | |
| | | startTLSFutureResult.setFutureResult(connectionFutureResult); |
| | | } |
| | | |
| | | }; |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void cancelled() { |
| | | // Ignore this. |
| | | } |
| | | |
| | | startTLSFutureResult.setFutureResult(connectionFutureResult); |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void completed(final org.glassfish.grizzly.Connection connection) { |
| | | connectionFutureResult.handleResult(adaptConnection(connection)); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void failed(final Throwable throwable) { |
| | | connectionFutureResult.handleErrorResult(adaptConnectionException(throwable)); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void updated(final org.glassfish.grizzly.Connection connection) { |
| | | // Ignore this. |
| | | } |
| | | |
| | | } |
| | | |
| | | private final SocketAddress socketAddress; |
| | | private final TCPNIOTransport transport; |
| | | private final FilterChain defaultFilterChain; |
| | | private final LDAPClientFilter clientFilter; |
| | | private final LDAPOptions options; |
| | | |
| | | /** |
| | | * Creates a new LDAP connection factory implementation which can be used to |
| | | * create connections to the Directory Server at the provided host and port |
| | | * address using provided connection options. |
| | | * |
| | | * @param address |
| | | * The address of the Directory Server to connect to. |
| | | * @param options |
| | | * The LDAP connection options to use when creating connections. |
| | | */ |
| | | public LDAPConnectionFactoryImpl(final SocketAddress address, final LDAPOptions options) { |
| | | if (options.getTCPNIOTransport() == null) { |
| | | this.transport = DefaultTCPNIOTransport.getInstance(); |
| | | } else { |
| | | this.transport = options.getTCPNIOTransport(); |
| | | } |
| | | this.socketAddress = address; |
| | | this.options = new LDAPOptions(options); |
| | | this.clientFilter = |
| | | new LDAPClientFilter(new LDAPReader(this.options.getDecodeOptions()), 0); |
| | | this.defaultFilterChain = |
| | | FilterChainBuilder.stateless().add(new TransportFilter()).add(clientFilter).build(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void cancelled() |
| | | { |
| | | // Ignore this. |
| | | public Connection getConnection() throws ErrorResultException, InterruptedException { |
| | | return getConnectionAsync(null).get(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void completed(final org.glassfish.grizzly.Connection connection) |
| | | { |
| | | connectionFutureResult.handleResult(adaptConnection(connection)); |
| | | public FutureResult<Connection> getConnectionAsync( |
| | | final ResultHandler<? super Connection> handler) { |
| | | final ConnectionCompletionHandler ch = new ConnectionCompletionHandler(handler); |
| | | try { |
| | | ch.connectionFutureResult.setFutureResult(transport.connect(socketAddress, ch)); |
| | | return ch.startTLSFutureResult; |
| | | } catch (final IOException e) { |
| | | final ErrorResultException result = adaptConnectionException(e); |
| | | return new CompletedFutureResult<Connection>(result); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Returns the address of the Directory Server. |
| | | * |
| | | * @return The address of the Directory Server. |
| | | */ |
| | | public SocketAddress getSocketAddress() { |
| | | return socketAddress; |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void failed(final Throwable throwable) |
| | | { |
| | | connectionFutureResult |
| | | .handleErrorResult(adaptConnectionException(throwable)); |
| | | public String toString() { |
| | | final StringBuilder builder = new StringBuilder(); |
| | | builder.append("LDAPConnectionFactory("); |
| | | builder.append(getSocketAddress().toString()); |
| | | builder.append(')'); |
| | | return builder.toString(); |
| | | } |
| | | |
| | | private LDAPConnection adaptConnection(final org.glassfish.grizzly.Connection<?> connection) { |
| | | // Test shows that its much faster with non block writes but risk |
| | | // running out of memory if the server is slow. |
| | | connection.configureBlocking(true); |
| | | connection.setProcessor(defaultFilterChain); |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void updated(final org.glassfish.grizzly.Connection connection) |
| | | { |
| | | // Ignore this. |
| | | final LDAPConnection ldapConnection = new LDAPConnection(connection, options); |
| | | clientFilter.registerConnection(connection, ldapConnection); |
| | | return ldapConnection; |
| | | } |
| | | |
| | | } |
| | | private ErrorResultException adaptConnectionException(Throwable t) { |
| | | if (t instanceof ExecutionException) { |
| | | t = t.getCause(); |
| | | } |
| | | |
| | | |
| | | |
| | | private final SocketAddress socketAddress; |
| | | private final TCPNIOTransport transport; |
| | | private final FilterChain defaultFilterChain; |
| | | private final LDAPClientFilter clientFilter; |
| | | private final LDAPOptions options; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new LDAP connection factory implementation which can be used to |
| | | * create connections to the Directory Server at the provided host and port |
| | | * address using provided connection options. |
| | | * |
| | | * @param address |
| | | * The address of the Directory Server to connect to. |
| | | * @param options |
| | | * The LDAP connection options to use when creating connections. |
| | | */ |
| | | public LDAPConnectionFactoryImpl(final SocketAddress address, |
| | | final LDAPOptions options) |
| | | { |
| | | if (options.getTCPNIOTransport() == null) |
| | | { |
| | | this.transport = DefaultTCPNIOTransport.getInstance(); |
| | | return newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR, t.getMessage(), t); |
| | | } |
| | | else |
| | | { |
| | | this.transport = options.getTCPNIOTransport(); |
| | | } |
| | | this.socketAddress = address; |
| | | this.options = new LDAPOptions(options); |
| | | this.clientFilter = new LDAPClientFilter(new LDAPReader( |
| | | this.options.getDecodeOptions()), 0); |
| | | this.defaultFilterChain = FilterChainBuilder.stateless() |
| | | .add(new TransportFilter()) |
| | | .add(clientFilter) |
| | | .build(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public Connection getConnection() throws ErrorResultException, |
| | | InterruptedException |
| | | { |
| | | return getConnectionAsync(null).get(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public FutureResult<Connection> getConnectionAsync( |
| | | final ResultHandler<? super Connection> handler) |
| | | { |
| | | final ConnectionCompletionHandler ch = new ConnectionCompletionHandler(handler); |
| | | try |
| | | { |
| | | ch.connectionFutureResult.setFutureResult(transport.connect(socketAddress, ch)); |
| | | return ch.startTLSFutureResult; |
| | | } |
| | | catch (final IOException e) |
| | | { |
| | | final ErrorResultException result = adaptConnectionException(e); |
| | | return new CompletedFutureResult<Connection>(result); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Returns the address of the Directory Server. |
| | | * |
| | | * @return The address of the Directory Server. |
| | | */ |
| | | public SocketAddress getSocketAddress() |
| | | { |
| | | return socketAddress; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public String toString() |
| | | { |
| | | final StringBuilder builder = new StringBuilder(); |
| | | builder.append("LDAPConnectionFactory("); |
| | | builder.append(getSocketAddress().toString()); |
| | | builder.append(')'); |
| | | return builder.toString(); |
| | | } |
| | | |
| | | |
| | | |
| | | private LDAPConnection adaptConnection( |
| | | final org.glassfish.grizzly.Connection<?> connection) |
| | | { |
| | | // Test shows that its much faster with non block writes but risk |
| | | // running out of memory if the server is slow. |
| | | connection.configureBlocking(true); |
| | | connection.setProcessor(defaultFilterChain); |
| | | |
| | | final LDAPConnection ldapConnection = new LDAPConnection(connection, |
| | | options); |
| | | clientFilter.registerConnection(connection, ldapConnection); |
| | | return ldapConnection; |
| | | } |
| | | |
| | | |
| | | |
| | | private ErrorResultException adaptConnectionException(Throwable t) |
| | | { |
| | | if (t instanceof ExecutionException) |
| | | { |
| | | t = t.getCause(); |
| | | } |
| | | |
| | | return newErrorResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR, t.getMessage(), |
| | | t); |
| | | } |
| | | } |